1 /*
2 * Copyright (C) 2013-2015 Intel Corporation
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 */
15
16 #include <unistd.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <stdbool.h>
20 #include <string.h>
21 #include <errno.h>
22 #include <pthread.h>
23 #include <getopt.h>
24 #include <math.h>
25 #include <limits.h>
26 #include <locale.h>
27 #include <math.h>
28
29 #include "aconfig.h"
30 #include "gettext.h"
31 #include "version.h"
32
33 #include "common.h"
34
35 #ifdef HAVE_LIBTINYALSA
36 #include "tinyalsa.h"
37 #else
38 #include "alsa.h"
39 #endif
40 #include "convert.h"
41 #ifdef HAVE_LIBFFTW3F
42 #include "analyze.h"
43 #endif
44 #include "latencytest.h"
45
46 /* get snr threshold in dB */
get_snr_thd_db(struct bat * bat,char * thd)47 static void get_snr_thd_db(struct bat *bat, char *thd)
48 {
49 int err;
50 float thd_db;
51 char *ptrf;
52
53 thd_db = strtof(thd, &ptrf);
54 err = -errno;
55 if (!snr_is_valid(thd_db)) {
56 fprintf(bat->err, _("Invalid threshold '%s':%d\n"), thd, err);
57 exit(EXIT_FAILURE);
58 }
59 bat->snr_thd_db = thd_db;
60 }
61
62 /* get snr threshold in %, and convert to dB */
get_snr_thd_pc(struct bat * bat,char * thd)63 static void get_snr_thd_pc(struct bat *bat, char *thd)
64 {
65 int err;
66 float thd_pc;
67 char *ptrf;
68
69 thd_pc = strtof(thd, &ptrf);
70 err = -errno;
71 if (thd_pc <= 0.0 || thd_pc >= 100.0) {
72 fprintf(bat->err, _("Invalid threshold '%s':%d\n"), thd, err);
73 exit(EXIT_FAILURE);
74 }
75 bat->snr_thd_db = 20.0 * log10f(100.0 / thd_pc);
76 }
77
get_duration(struct bat * bat)78 static int get_duration(struct bat *bat)
79 {
80 int err;
81 float duration_f;
82 long duration_i;
83 char *ptrf, *ptri;
84
85 duration_f = strtof(bat->narg, &ptrf);
86 err = -errno;
87 if (duration_f == HUGE_VALF || duration_f == -HUGE_VALF
88 || (duration_f == 0.0 && err != 0))
89 goto err_exit;
90
91 duration_i = strtol(bat->narg, &ptri, 10);
92 if (duration_i == LONG_MAX || duration_i == LONG_MIN)
93 goto err_exit;
94
95 if (*ptrf == 's')
96 bat->frames = duration_f * bat->rate;
97 else if (*ptri == 0)
98 bat->frames = duration_i;
99 else
100 bat->frames = -1;
101
102 if (bat->frames <= 0 || bat->frames > MAX_FRAMES) {
103 fprintf(bat->err, _("Invalid duration. Range: (0, %d(%fs))\n"),
104 MAX_FRAMES, (float)MAX_FRAMES / bat->rate);
105 return -EINVAL;
106 }
107
108 return 0;
109
110 err_exit:
111 fprintf(bat->err, _("Duration overflow/underflow: %d\n"), err);
112
113 return err;
114 }
115
get_sine_frequencies(struct bat * bat,char * freq)116 static void get_sine_frequencies(struct bat *bat, char *freq)
117 {
118 char *tmp1;
119
120 tmp1 = strchr(freq, ':');
121 if (tmp1 == NULL) {
122 bat->target_freq[1] = bat->target_freq[0] = atof(optarg);
123 } else {
124 *tmp1 = '\0';
125 bat->target_freq[0] = atof(optarg);
126 bat->target_freq[1] = atof(tmp1 + 1);
127 }
128 }
129
get_format(struct bat * bat,char * optarg)130 static void get_format(struct bat *bat, char *optarg)
131 {
132 if (strcasecmp(optarg, "cd") == 0) {
133 bat->format = BAT_PCM_FORMAT_S16_LE;
134 bat->rate = 44100;
135 bat->channels = 2;
136 bat->sample_size = 2;
137 } else if (strcasecmp(optarg, "dat") == 0) {
138 bat->format = BAT_PCM_FORMAT_S16_LE;
139 bat->rate = 48000;
140 bat->channels = 2;
141 bat->sample_size = 2;
142 } else if (strcasecmp(optarg, "U8") == 0) {
143 bat->format = BAT_PCM_FORMAT_U8;
144 bat->sample_size = 1;
145 } else if (strcasecmp(optarg, "S16_LE") == 0) {
146 bat->format = BAT_PCM_FORMAT_S16_LE;
147 bat->sample_size = 2;
148 } else if (strcasecmp(optarg, "S24_3LE") == 0) {
149 bat->format = BAT_PCM_FORMAT_S24_3LE;
150 bat->sample_size = 3;
151 } else if (strcasecmp(optarg, "S32_LE") == 0) {
152 bat->format = BAT_PCM_FORMAT_S32_LE;
153 bat->sample_size = 4;
154 } else {
155 bat->format = BAT_PCM_FORMAT_UNKNOWN;
156 fprintf(bat->err, _("wrong extended format '%s'\n"), optarg);
157 exit(EXIT_FAILURE);
158 }
159 }
160
thread_wait_completion(struct bat * bat,pthread_t id,int ** val)161 static inline int thread_wait_completion(struct bat *bat,
162 pthread_t id, int **val)
163 {
164 int err;
165
166 err = pthread_join(id, (void **) val);
167 if (err)
168 pthread_cancel(id);
169
170 return err;
171 }
172
173 /* loopback test where we play sine wave and capture the same sine wave */
test_loopback(struct bat * bat)174 static void test_loopback(struct bat *bat)
175 {
176 pthread_t capture_id, playback_id;
177 int err;
178 int *thread_result_capture, *thread_result_playback;
179
180 /* start playback */
181 err = pthread_create(&playback_id, NULL,
182 (void *) bat->playback.fct, bat);
183 if (err != 0) {
184 fprintf(bat->err, _("Cannot create playback thread: %d\n"),
185 err);
186 exit(EXIT_FAILURE);
187 }
188
189 /* TODO: use a pipe to signal stream start etc - i.e. to sync threads */
190 /* Let some time for playing something before capturing */
191 usleep(CAPTURE_DELAY * 1000);
192
193 /* start capture */
194 err = pthread_create(&capture_id, NULL, (void *) bat->capture.fct, bat);
195 if (err != 0) {
196 fprintf(bat->err, _("Cannot create capture thread: %d\n"), err);
197 pthread_cancel(playback_id);
198 exit(EXIT_FAILURE);
199 }
200
201 /* wait for playback to complete */
202 err = thread_wait_completion(bat, playback_id, &thread_result_playback);
203 if (err != 0) {
204 fprintf(bat->err, _("Cannot join playback thread: %d\n"), err);
205 free(thread_result_playback);
206 pthread_cancel(capture_id);
207 exit(EXIT_FAILURE);
208 }
209
210 /* check playback status */
211 if (*thread_result_playback != 0) {
212 fprintf(bat->err, _("Exit playback thread fail: %d\n"),
213 *thread_result_playback);
214 pthread_cancel(capture_id);
215 exit(EXIT_FAILURE);
216 } else {
217 fprintf(bat->log, _("Playback completed.\n"));
218 }
219
220 /* now stop and wait for capture to finish */
221 pthread_cancel(capture_id);
222 err = thread_wait_completion(bat, capture_id, &thread_result_capture);
223 if (err != 0) {
224 fprintf(bat->err, _("Cannot join capture thread: %d\n"), err);
225 free(thread_result_capture);
226 exit(EXIT_FAILURE);
227 }
228
229 /* check if capture thread is canceled or not */
230 if (thread_result_capture == PTHREAD_CANCELED) {
231 fprintf(bat->log, _("Capture canceled.\n"));
232 return;
233 }
234
235 /* check capture status */
236 if (*thread_result_capture != 0) {
237 fprintf(bat->err, _("Exit capture thread fail: %d\n"),
238 *thread_result_capture);
239 exit(EXIT_FAILURE);
240 } else {
241 fprintf(bat->log, _("Capture completed.\n"));
242 }
243 }
244
245 /* single ended playback only test */
test_playback(struct bat * bat)246 static void test_playback(struct bat *bat)
247 {
248 pthread_t playback_id;
249 int err;
250 int *thread_result;
251
252 /* start playback */
253 err = pthread_create(&playback_id, NULL,
254 (void *) bat->playback.fct, bat);
255 if (err != 0) {
256 fprintf(bat->err, _("Cannot create playback thread: %d\n"),
257 err);
258 exit(EXIT_FAILURE);
259 }
260
261 /* wait for playback to complete */
262 err = thread_wait_completion(bat, playback_id, &thread_result);
263 if (err != 0) {
264 fprintf(bat->err, _("Cannot join playback thread: %d\n"), err);
265 free(thread_result);
266 exit(EXIT_FAILURE);
267 }
268
269 /* check playback status */
270 if (*thread_result != 0) {
271 fprintf(bat->err, _("Exit playback thread fail: %d\n"),
272 *thread_result);
273 exit(EXIT_FAILURE);
274 } else {
275 fprintf(bat->log, _("Playback completed.\n"));
276 }
277 }
278
279 /* single ended capture only test */
test_capture(struct bat * bat)280 static void test_capture(struct bat *bat)
281 {
282 pthread_t capture_id;
283 int err;
284 int *thread_result;
285
286 /* start capture */
287 err = pthread_create(&capture_id, NULL, (void *) bat->capture.fct, bat);
288 if (err != 0) {
289 fprintf(bat->err, _("Cannot create capture thread: %d\n"), err);
290 exit(EXIT_FAILURE);
291 }
292
293 /* TODO: stop capture */
294
295 /* wait for capture to complete */
296 err = thread_wait_completion(bat, capture_id, &thread_result);
297 if (err != 0) {
298 fprintf(bat->err, _("Cannot join capture thread: %d\n"), err);
299 free(thread_result);
300 exit(EXIT_FAILURE);
301 }
302
303 /* check playback status */
304 if (*thread_result != 0) {
305 fprintf(bat->err, _("Exit capture thread fail: %d\n"),
306 *thread_result);
307 exit(EXIT_FAILURE);
308 } else {
309 fprintf(bat->log, _("Capture completed.\n"));
310 }
311 }
312
usage(struct bat * bat)313 static void usage(struct bat *bat)
314 {
315 fprintf(bat->log,
316 _("Usage: alsabat [-options]...\n"
317 "\n"
318 " -h, --help this help\n"
319 " -D pcm device for both playback and capture\n"
320 " -P pcm device for playback\n"
321 " -C pcm device for capture\n"
322 " -f sample format\n"
323 " -c number of channels\n"
324 " -r sampling rate\n"
325 " -n frames to playback or capture\n"
326 " -k parameter for frequency detecting threshold\n"
327 " -F target frequency\n"
328 " -p total number of periods to play/capture\n"
329 " -B buffer size in frames\n"
330 " -E period size in frames\n"
331 " --log=# file that both stdout and strerr redirecting to\n"
332 " --file=# file for playback\n"
333 " --saveplay=# file that storing playback content, for debug\n"
334 " --local internal loop, set to bypass pcm hardware devices\n"
335 " --standalone standalone mode, to bypass analysis\n"
336 " --roundtriplatency round trip latency mode\n"
337 " --snr-db=# noise detect threshold, in SNR(dB)\n"
338 " --snr-pc=# noise detect threshold, in noise percentage(%%)\n"
339 ));
340 fprintf(bat->log, _("Recognized sample formats are: "));
341 fprintf(bat->log, _("U8 S16_LE S24_3LE S32_LE\n"));
342 fprintf(bat->log, _("The available format shotcuts are:\n"));
343 fprintf(bat->log, _("-f cd (16 bit little endian, 44100, stereo)\n"));
344 fprintf(bat->log, _("-f dat (16 bit little endian, 48000, stereo)\n"));
345 }
346
set_defaults(struct bat * bat)347 static void set_defaults(struct bat *bat)
348 {
349 memset(bat, 0, sizeof(struct bat));
350
351 /* Set default values */
352 bat->rate = 44100;
353 bat->frame_size = 2;
354 bat->sample_size = 2;
355 bat->format = BAT_PCM_FORMAT_S16_LE;
356 bat->convert_float_to_sample = convert_float_to_int16;
357 bat->convert_sample_to_float = convert_int16_to_float;
358 bat->frames = bat->rate * 2;
359 bat->target_freq[0] = 997.0;
360 bat->target_freq[1] = 997.0;
361 bat->sigma_k = 3.0;
362 bat->snr_thd_db = SNR_DB_INVALID;
363 bat->playback.device = NULL;
364 bat->capture.device = NULL;
365 bat->buf = NULL;
366 bat->local = false;
367 bat->buffer_size = 0;
368 bat->period_size = 0;
369 bat->roundtriplatency = false;
370 #ifdef HAVE_LIBTINYALSA
371 bat->channels = 2;
372 bat->playback.fct = &playback_tinyalsa;
373 bat->capture.fct = &record_tinyalsa;
374 #else
375 bat->channels = 1;
376 bat->playback.fct = &playback_alsa;
377 bat->capture.fct = &record_alsa;
378 #endif
379 bat->playback.mode = MODE_LOOPBACK;
380 bat->capture.mode = MODE_LOOPBACK;
381 bat->period_is_limited = false;
382 bat->log = stdout;
383 bat->err = stderr;
384 }
385
parse_arguments(struct bat * bat,int argc,char * argv[])386 static void parse_arguments(struct bat *bat, int argc, char *argv[])
387 {
388 int c, option_index, err;
389 static const char short_options[] = "D:P:C:f:n:F:c:r:s:k:p:B:E:lth";
390 static const struct option long_options[] = {
391 {"help", 0, 0, 'h'},
392 {"log", 1, 0, OPT_LOG},
393 {"file", 1, 0, OPT_READFILE},
394 {"saveplay", 1, 0, OPT_SAVEPLAY},
395 {"local", 0, 0, OPT_LOCAL},
396 {"standalone", 0, 0, OPT_STANDALONE},
397 {"roundtriplatency", 0, 0, OPT_ROUNDTRIPLATENCY},
398 {"snr-db", 1, 0, OPT_SNRTHD_DB},
399 {"snr-pc", 1, 0, OPT_SNRTHD_PC},
400 {0, 0, 0, 0}
401 };
402
403 while ((c = getopt_long(argc, argv, short_options, long_options,
404 &option_index)) != -1) {
405 switch (c) {
406 case OPT_LOG:
407 bat->logarg = optarg;
408 break;
409 case OPT_READFILE:
410 bat->playback.file = optarg;
411 break;
412 case OPT_SAVEPLAY:
413 bat->debugplay = optarg;
414 break;
415 case OPT_LOCAL:
416 bat->local = true;
417 break;
418 case OPT_STANDALONE:
419 bat->standalone = true;
420 break;
421 case OPT_ROUNDTRIPLATENCY:
422 bat->roundtriplatency = true;
423 break;
424 case OPT_SNRTHD_DB:
425 get_snr_thd_db(bat, optarg);
426 break;
427 case OPT_SNRTHD_PC:
428 get_snr_thd_pc(bat, optarg);
429 break;
430 case 'D':
431 if (bat->playback.device == NULL)
432 bat->playback.device = optarg;
433 if (bat->capture.device == NULL)
434 bat->capture.device = optarg;
435 break;
436 case 'P':
437 if (bat->capture.mode == MODE_SINGLE)
438 bat->capture.mode = MODE_LOOPBACK;
439 else
440 bat->playback.mode = MODE_SINGLE;
441 bat->playback.device = optarg;
442 break;
443 case 'C':
444 if (bat->playback.mode == MODE_SINGLE)
445 bat->playback.mode = MODE_LOOPBACK;
446 else
447 bat->capture.mode = MODE_SINGLE;
448 bat->capture.device = optarg;
449 break;
450 case 'n':
451 bat->narg = optarg;
452 break;
453 case 'F':
454 get_sine_frequencies(bat, optarg);
455 break;
456 case 'c':
457 bat->channels = atoi(optarg);
458 break;
459 case 'r':
460 bat->rate = atoi(optarg);
461 break;
462 case 'f':
463 get_format(bat, optarg);
464 break;
465 case 'k':
466 bat->sigma_k = atof(optarg);
467 break;
468 case 'p':
469 bat->periods_total = atoi(optarg);
470 bat->period_is_limited = true;
471 break;
472 case 'B':
473 err = atoi(optarg);
474 bat->buffer_size = err >= MIN_BUFFERSIZE
475 && err < MAX_BUFFERSIZE ? err : 0;
476 break;
477 case 'E':
478 err = atoi(optarg);
479 bat->period_size = err >= MIN_PERIODSIZE
480 && err < MAX_PERIODSIZE ? err : 0;
481 break;
482 case 'h':
483 default:
484 usage(bat);
485 exit(EXIT_SUCCESS);
486 }
487 }
488 }
489
validate_options(struct bat * bat)490 static int validate_options(struct bat *bat)
491 {
492 int c;
493 float freq_low, freq_high;
494
495 /* check if we have an input file for local mode */
496 if ((bat->local == true) && (bat->capture.file == NULL)) {
497 fprintf(bat->err, _("no input file for local testing\n"));
498 return -EINVAL;
499 }
500
501 /* check supported channels */
502 if (bat->channels > MAX_CHANNELS || bat->channels < MIN_CHANNELS) {
503 fprintf(bat->err, _("%d channels not supported\n"),
504 bat->channels);
505 return -EINVAL;
506 }
507
508 /* check single ended is in either playback or capture - not both */
509 if ((bat->playback.mode == MODE_SINGLE)
510 && (bat->capture.mode == MODE_SINGLE)) {
511 fprintf(bat->err, _("single ended mode is simplex\n"));
512 return -EINVAL;
513 }
514
515 /* check sine wave frequency range */
516 freq_low = DC_THRESHOLD;
517 freq_high = bat->rate * RATE_FACTOR;
518 for (c = 0; c < bat->channels; c++) {
519 if (bat->target_freq[c] < freq_low
520 || bat->target_freq[c] > freq_high) {
521 fprintf(bat->err, _("sine wave frequency out of"));
522 fprintf(bat->err, _(" range: (%.1f, %.1f)\n"),
523 freq_low, freq_high);
524 return -EINVAL;
525 }
526 }
527
528 return 0;
529 }
530
bat_init(struct bat * bat)531 static int bat_init(struct bat *bat)
532 {
533 int err = 0;
534 int fd = 0;
535 char name[] = TEMP_RECORD_FILE_NAME;
536
537 /* Determine logging to a file or stdout and stderr */
538 if (bat->logarg) {
539 bat->log = NULL;
540 bat->log = fopen(bat->logarg, "wb");
541 err = -errno;
542 if (bat->log == NULL) {
543 fprintf(bat->err, _("Cannot open file: %s %d\n"),
544 bat->logarg, err);
545 return err;
546 }
547 bat->err = bat->log;
548 }
549
550 /* Determine duration of playback and/or capture */
551 if (bat->narg) {
552 err = get_duration(bat);
553 if (err < 0)
554 return err;
555 }
556
557 /* Set default playback and capture devices */
558 if (bat->playback.device == NULL && bat->capture.device == NULL)
559 bat->playback.device = bat->capture.device = DEFAULT_DEV_NAME;
560
561 /* Determine capture file */
562 if (bat->local) {
563 bat->capture.file = bat->playback.file;
564 } else {
565 /* create temp file for sound record and analysis */
566 fd = mkstemp(name);
567 err = -errno;
568 if (fd == -1) {
569 fprintf(bat->err, _("Fail to create record file: %d\n"),
570 err);
571 return err;
572 }
573 /* store file name which is dynamically created */
574 bat->capture.file = strdup(name);
575 err = -errno;
576 if (bat->capture.file == NULL)
577 return err;
578 /* close temp file */
579 close(fd);
580 }
581
582 /* Initial for playback */
583 if (bat->playback.file == NULL) {
584 /* No input file so we will generate our own sine wave */
585 if (bat->frames) {
586 if (bat->playback.mode == MODE_SINGLE) {
587 /* Play nb of frames given by -n argument */
588 bat->sinus_duration = bat->frames;
589 } else {
590 /* Play CAPTURE_DELAY msec +
591 * 150% of the nb of frames to be analyzed */
592 bat->sinus_duration = bat->rate *
593 CAPTURE_DELAY / 1000;
594 bat->sinus_duration +=
595 (bat->frames + bat->frames / 2);
596 }
597 } else {
598 /* Special case where we want to generate a sine wave
599 * endlessly without capturing */
600 bat->sinus_duration = 0;
601 bat->playback.mode = MODE_SINGLE;
602 }
603 } else {
604 bat->fp = fopen(bat->playback.file, "rb");
605 err = -errno;
606 if (bat->fp == NULL) {
607 fprintf(bat->err, _("Cannot open file: %s %d\n"),
608 bat->playback.file, err);
609 return err;
610 }
611 err = read_wav_header(bat, bat->playback.file, bat->fp, false);
612 fclose(bat->fp);
613 if (err != 0)
614 return err;
615 }
616
617 bat->frame_size = bat->sample_size * bat->channels;
618
619 /* Set conversion functions */
620 switch (bat->sample_size) {
621 case 1:
622 bat->convert_float_to_sample = convert_float_to_uint8;
623 bat->convert_sample_to_float = convert_uint8_to_float;
624 break;
625 case 2:
626 bat->convert_float_to_sample = convert_float_to_int16;
627 bat->convert_sample_to_float = convert_int16_to_float;
628 break;
629 case 3:
630 bat->convert_float_to_sample = convert_float_to_int24;
631 bat->convert_sample_to_float = convert_int24_to_float;
632 break;
633 case 4:
634 bat->convert_float_to_sample = convert_float_to_int32;
635 bat->convert_sample_to_float = convert_int32_to_float;
636 break;
637 default:
638 fprintf(bat->err, _("Invalid PCM format: size=%d\n"),
639 bat->sample_size);
640 return -EINVAL;
641 }
642
643 return err;
644 }
645
main(int argc,char * argv[])646 int main(int argc, char *argv[])
647 {
648 struct bat bat;
649 int err = 0;
650
651 set_defaults(&bat);
652
653 #ifdef ENABLE_NLS
654 setlocale(LC_ALL, "");
655 textdomain(PACKAGE);
656 #endif
657
658 fprintf(bat.log, _("%s version %s\n\n"), PACKAGE_NAME, PACKAGE_VERSION);
659
660 parse_arguments(&bat, argc, argv);
661
662 err = bat_init(&bat);
663 if (err < 0)
664 goto out;
665
666 err = validate_options(&bat);
667 if (err < 0)
668 goto out;
669
670 /* round trip latency test thread */
671 if (bat.roundtriplatency) {
672 while (1) {
673 fprintf(bat.log,
674 _("\nStart round trip latency\n"));
675 roundtrip_latency_init(&bat);
676 test_loopback(&bat);
677
678 if (bat.latency.xrun_error == false)
679 break;
680 else {
681 /* Xrun error in playback or capture,
682 increase period size and try again */
683 bat.period_size += bat.rate / 1000;
684 bat.buffer_size =
685 bat.period_size * DIV_BUFFERSIZE;
686
687 /* terminate the test if period_size is
688 large enough */
689 if (bat.period_size > bat.rate * 0.2)
690 break;
691 }
692
693 /* Waiting 500ms and start the next round */
694 usleep(CAPTURE_DELAY * 1000);
695 }
696 goto out;
697 }
698
699 /* single line playback thread: playback only, no capture */
700 if (bat.playback.mode == MODE_SINGLE) {
701 test_playback(&bat);
702 goto out;
703 }
704
705 /* single line capture thread: capture only, no playback */
706 if (bat.capture.mode == MODE_SINGLE) {
707 test_capture(&bat);
708 goto analyze;
709 }
710
711 /* loopback thread: playback and capture in a loop */
712 if (bat.local == false)
713 test_loopback(&bat);
714
715 analyze:
716 #ifdef HAVE_LIBFFTW3F
717 if (!bat.standalone || snr_is_valid(bat.snr_thd_db))
718 err = analyze_capture(&bat);
719 #else
720 fprintf(bat.log, _("No libfftw3 library. Exit without analysis.\n"));
721 #endif
722 out:
723 fprintf(bat.log, _("\nReturn value is %d\n"), err);
724
725 if (bat.logarg)
726 fclose(bat.log);
727 if (!bat.local)
728 free(bat.capture.file);
729
730 return err;
731 }
732