OSDN Git Service

axfer: add informative output and an option to suppress it
[android-x86/external-alsa-utils.git] / axfer / xfer-options.c
1 // SPDX-License-Identifier: GPL-2.0
2 //
3 // xfer-options.c - a parser of commandline options for xfer.
4 //
5 // Copyright (c) 2018 Takashi Sakamoto <o-takashi@sakamocchi.jp>
6 //
7 // Licensed under the terms of the GNU General Public License, version 2.
8
9 #include "xfer.h"
10 #include "misc.h"
11
12 #include <getopt.h>
13 #include <math.h>
14 #include <limits.h>
15
16 enum no_short_opts {
17         // 128 or later belong to non us-ascii character set.
18         OPT_XFER_TYPE = 128,
19 };
20
21 static int allocate_paths(struct xfer_context *xfer, char *const *paths,
22                            unsigned int count)
23 {
24         bool stdio = false;
25         int i;
26
27         if (count == 0) {
28                 stdio = true;
29                 count = 1;
30         }
31
32         xfer->paths = calloc(count, sizeof(xfer->paths[0]));
33         if (xfer->paths == NULL)
34                 return -ENOMEM;
35         xfer->path_count = count;
36
37         if (stdio) {
38                 xfer->paths[0] = strndup("-", PATH_MAX);
39                 if (xfer->paths[0] == NULL)
40                         return -ENOMEM;
41         } else {
42                 for (i = 0; i < count; ++i) {
43                         xfer->paths[i] = strndup(paths[i], PATH_MAX);
44                         if (xfer->paths[i] == NULL)
45                                 return -ENOMEM;
46                 }
47         }
48
49         return 0;
50 }
51
52 static int verify_cntr_format(struct xfer_context *xfer)
53 {
54         static const struct {
55                 const char *const literal;
56                 enum container_format cntr_format;
57         } *entry, entries[] = {
58                 {"raw",         CONTAINER_FORMAT_RAW},
59                 {"voc",         CONTAINER_FORMAT_VOC},
60                 {"wav",         CONTAINER_FORMAT_RIFF_WAVE},
61                 {"au",          CONTAINER_FORMAT_AU},
62                 {"sparc",       CONTAINER_FORMAT_AU},
63         };
64         int i;
65
66         for (i = 0; i < ARRAY_SIZE(entries); ++i) {
67                 entry = &entries[i];
68                 if (strcasecmp(xfer->cntr_format_literal, entry->literal))
69                         continue;
70
71                 xfer->cntr_format = entry->cntr_format;
72                 return 0;
73         }
74
75         fprintf(stderr, "unrecognized file format '%s'\n",
76                 xfer->cntr_format_literal);
77
78         return -EINVAL;
79 }
80
81 // This should be called after 'verify_cntr_format()'.
82 static int verify_sample_format(struct xfer_context *xfer)
83 {
84         static const struct {
85                 const char *const literal;
86                 unsigned int frames_per_second;
87                 unsigned int samples_per_frame;
88                 snd_pcm_format_t le_format;
89                 snd_pcm_format_t be_format;
90         } *entry, entries[] = {
91                 {"cd",  44100, 2, SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S16_BE},
92                 {"cdr", 44100, 2, SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S16_BE},
93                 {"dat", 48000, 2, SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S16_BE},
94         };
95         int i;
96
97         xfer->sample_format = snd_pcm_format_value(xfer->sample_format_literal);
98         if (xfer->sample_format != SND_PCM_FORMAT_UNKNOWN)
99                 return 0;
100
101         for (i = 0; i < ARRAY_SIZE(entries); ++i) {
102                 entry = &entries[i];
103                 if (strcmp(entry->literal, xfer->sample_format_literal))
104                         continue;
105
106                 if (xfer->frames_per_second > 0 &&
107                     xfer->frames_per_second != entry->frames_per_second) {
108                         fprintf(stderr,
109                                 "'%s' format can't be used with rate except "
110                                 "for %u.\n",
111                                 entry->literal, entry->frames_per_second);
112                         return -EINVAL;
113                 }
114
115                 if (xfer->samples_per_frame > 0 &&
116                     xfer->samples_per_frame != entry->samples_per_frame) {
117                         fprintf(stderr,
118                                 "'%s' format can't be used with channel except "
119                                 "for %u.\n",
120                                 entry->literal, entry->samples_per_frame);
121                         return -EINVAL;
122                 }
123
124                 xfer->frames_per_second = entry->frames_per_second;
125                 xfer->samples_per_frame = entry->samples_per_frame;
126                 if (xfer->cntr_format == CONTAINER_FORMAT_AU)
127                         xfer->sample_format = entry->be_format;
128                 else
129                         xfer->sample_format = entry->le_format;
130
131                 return 0;
132         }
133
134         fprintf(stderr, "wrong extended format '%s'\n",
135                 xfer->sample_format_literal);
136
137         return -EINVAL;
138 }
139
140 static int validate_options(struct xfer_context *xfer)
141 {
142         unsigned int val;
143         int err = 0;
144
145         if (xfer->cntr_format_literal == NULL) {
146                 if (xfer->direction == SND_PCM_STREAM_CAPTURE) {
147                         // To stdout.
148                         if (xfer->path_count == 1 &&
149                             !strcmp(xfer->paths[0], "-")) {
150                                 xfer->cntr_format = CONTAINER_FORMAT_RAW;
151                         } else {
152                                 // Use first path as a representative.
153                                 xfer->cntr_format = container_format_from_path(
154                                                                 xfer->paths[0]);
155                         }
156                 }
157                 // For playback, perform auto-detection.
158         } else {
159                 err = verify_cntr_format(xfer);
160         }
161         if (err < 0)
162                 return err;
163
164         if (xfer->multiple_cntrs) {
165                 if (!strcmp(xfer->paths[0], "-")) {
166                         fprintf(stderr,
167                                 "An option for separated channels is not "
168                                 "available with stdin/stdout.\n");
169                         return -EINVAL;
170                 }
171
172                 // For captured PCM frames, even if one path is given for
173                 // container files, it can be used to generate several paths.
174                 // For this purpose, please see
175                 // 'xfer_options_fixup_paths()'.
176                 if (xfer->direction == SND_PCM_STREAM_PLAYBACK) {
177                         // Require several paths for containers.
178                         if (xfer->path_count == 1) {
179                                 fprintf(stderr,
180                                         "An option for separated channels "
181                                         "requires several files to playback "
182                                         "PCM frames.\n");
183                                 return -EINVAL;
184                         }
185                 }
186         } else {
187                 // A single path is available only.
188                 if (xfer->path_count > 1) {
189                         fprintf(stderr,
190                                 "When using several files, an option for "
191                                 "sepatated channels is used with.\n");
192                         return -EINVAL;
193                 }
194         }
195
196         xfer->sample_format = SND_PCM_FORMAT_UNKNOWN;
197         if (xfer->sample_format_literal) {
198                 err = verify_sample_format(xfer);
199                 if (err < 0)
200                         return err;
201         }
202
203         val = xfer->frames_per_second;
204         if (xfer->frames_per_second == 0)
205                 xfer->frames_per_second = 8000;
206         if (xfer->frames_per_second < 1000)
207                 xfer->frames_per_second *= 1000;
208         if (xfer->frames_per_second < 2000 ||
209             xfer->frames_per_second > 192000) {
210                 fprintf(stderr, "bad speed value '%i'\n", val);
211                 return -EINVAL;
212         }
213
214         if (xfer->samples_per_frame > 0) {
215                 if (xfer->samples_per_frame < 1 ||
216                     xfer->samples_per_frame > 256) {
217                         fprintf(stderr, "invalid channels argument '%u'\n",
218                                 xfer->samples_per_frame);
219                         return -EINVAL;
220                 }
221         }
222
223         return err;
224 }
225
226 int xfer_options_parse_args(struct xfer_context *xfer,
227                             const struct xfer_data *data, int argc,
228                             char *const *argv)
229 {
230         static const char *short_opts = "CPhvqf:c:r:t:I";
231         static const struct option long_opts[] = {
232                 // For generic purposes.
233                 {"capture",             0, 0, 'C'},
234                 {"playback",            0, 0, 'P'},
235                 {"xfer-type",           1, 0, OPT_XFER_TYPE},
236                 {"help",                0, 0, 'h'},
237                 {"verbose",             0, 0, 'v'},
238                 {"quiet",               0, 0, 'q'},
239                 // For transfer backend.
240                 {"format",              1, 0, 'f'},
241                 {"channels",            1, 0, 'c'},
242                 {"rate",                1, 0, 'r'},
243                 // For containers.
244                 {"file-type",           1, 0, 't'},
245                 // For mapper.
246                 {"separate-channels",   0, 0, 'I'},
247         };
248         char *s_opts;
249         struct option *l_opts;
250         int l_index;
251         int key;
252         int err = 0;
253
254         // Concatenate short options.
255         s_opts = malloc(strlen(data->s_opts) + strlen(short_opts) + 1);
256         if (s_opts == NULL)
257                 return -ENOMEM;
258         strcpy(s_opts, data->s_opts);
259         strcpy(s_opts + strlen(s_opts), short_opts);
260         s_opts[strlen(data->s_opts) + strlen(short_opts)] = '\0';
261
262         // Concatenate long options, including a sentinel.
263         l_opts = calloc(ARRAY_SIZE(long_opts) * data->l_opts_count + 1,
264                         sizeof(*l_opts));
265         if (l_opts == NULL) {
266                 free(s_opts);
267                 return -ENOMEM;
268         }
269         memcpy(l_opts, long_opts, ARRAY_SIZE(long_opts) * sizeof(*l_opts));
270         memcpy(&l_opts[ARRAY_SIZE(long_opts)], data->l_opts,
271                data->l_opts_count * sizeof(*l_opts));
272
273         // Parse options.
274         l_index = 0;
275         optarg = NULL;
276         optind = 1;
277         opterr = 1;     // use error output.
278         optopt = 0;
279         while (1) {
280                 key = getopt_long(argc, argv, s_opts, l_opts, &l_index);
281                 if (key < 0)
282                         break;
283                 else if (key == 'C')
284                         ;       // already parsed.
285                 else if (key == 'P')
286                         ;       // already parsed.
287                 else if (key == OPT_XFER_TYPE)
288                         ;       // already parsed.
289                 else if (key == 'h')
290                         xfer->help = true;
291                 else if (key == 'v')
292                         ++xfer->verbose;
293                 else if (key == 'q')
294                         xfer->quiet = true;
295                 else if (key == 'f')
296                         xfer->sample_format_literal = arg_duplicate_string(optarg, &err);
297                 else if (key == 'c')
298                         xfer->samples_per_frame = arg_parse_decimal_num(optarg, &err);
299                 else if (key == 'r')
300                         xfer->frames_per_second = arg_parse_decimal_num(optarg, &err);
301                 else if (key == 't')
302                         xfer->cntr_format_literal = arg_duplicate_string(optarg, &err);
303                 else if (key == 'I')
304                         xfer->multiple_cntrs = true;
305                 else if (key == '?')
306                         return -EINVAL;
307                 else {
308                         err = xfer->ops->parse_opt(xfer, key, optarg);
309                         if (err < 0 && err != -ENXIO)
310                                 break;
311                 }
312         }
313
314         free(l_opts);
315         free(s_opts);
316
317         err = allocate_paths(xfer, argv + optind, argc - optind);
318         if (err < 0)
319                 return err;
320
321         return validate_options(xfer);
322 }
323
324 static const char *const allowed_duplication[] = {
325         "/dev/null",
326         "/dev/zero",
327         "/dev/full",
328         "/dev/random",
329         "/dev/urandom",
330 };
331
332 static int generate_path_with_suffix(struct xfer_context *xfer,
333                                      const char *template, unsigned int index,
334                                      const char *suffix)
335 {
336         static const char *const single_format = "%s%s";
337         static const char *const multiple_format = "%s-%i%s";
338         unsigned int len;
339
340         len = strlen(template) + strlen(suffix) + 1;
341         if (xfer->path_count > 1)
342                 len += (unsigned int)log10(xfer->path_count) + 2;
343
344         xfer->paths[index] = malloc(len);
345         if (xfer->paths[index] == NULL)
346                 return -ENOMEM;
347
348         if (xfer->path_count == 1) {
349                 snprintf(xfer->paths[index], len, single_format, template,
350                          suffix);
351         } else {
352                 snprintf(xfer->paths[index], len, multiple_format, template,
353                          index, suffix);
354         }
355
356         return 0;
357 }
358
359 static int generate_path_without_suffix(struct xfer_context *xfer,
360                                         const char *template,
361                                         unsigned int index, const char *suffix)
362 {
363         static const char *const single_format = "%s";
364         static const char *const multiple_format = "%s-%i";
365         unsigned int len;
366
367         len = strlen(template) + 1;
368         if (xfer->path_count > 1)
369                 len += (unsigned int)log10(xfer->path_count) + 2;
370
371         xfer->paths[index] = malloc(len);
372         if (xfer->paths[index] == NULL)
373                 return -ENOMEM;
374
375         if (xfer->path_count == 1) {
376                 snprintf(xfer->paths[index], len, single_format, template);
377         } else {
378                 snprintf(xfer->paths[index], len, multiple_format, template,
379                         index);
380         }
381
382         return 0;
383 }
384
385 static int generate_path(struct xfer_context *xfer, char *template,
386                          unsigned int index, const char *suffix)
387 {
388         int (*generator)(struct xfer_context *xfer, const char *template,
389                          unsigned int index, const char *suffix);
390         char *pos;
391
392         if (strlen(suffix) > 0) {
393                 pos = template + strlen(template) - strlen(suffix);
394                 // Separate filename and suffix.
395                 if (!strcmp(pos, suffix))
396                         *pos = '\0';
397         }
398
399         // Select handlers.
400         if (strlen(suffix) > 0)
401                 generator = generate_path_with_suffix;
402         else
403                 generator = generate_path_without_suffix;
404
405         return generator(xfer, template, index, suffix);
406 }
407
408 static int create_paths(struct xfer_context *xfer, unsigned int path_count)
409 {
410         char *template;
411         const char *suffix;
412         int i, j;
413         int err = 0;
414
415         // Can cause memory leak.
416         assert(xfer->path_count == 1);
417         assert(xfer->paths);
418         assert(xfer->paths[0]);
419         assert(xfer->paths[0][0] != '\0');
420
421         // Release at first.
422         template = xfer->paths[0];
423         free(xfer->paths);
424         xfer->paths = NULL;
425
426         // Allocate again.
427         xfer->paths = calloc(path_count, sizeof(*xfer->paths));
428         if (xfer->paths == NULL) {
429                 err = -ENOMEM;
430                 goto end;
431         }
432         xfer->path_count = path_count;
433
434         suffix = container_suffix_from_format(xfer->cntr_format);
435
436         for (i = 0; i < xfer->path_count; ++i) {
437                 // Some file names are allowed to be duplicated.
438                 for (j = 0; j < ARRAY_SIZE(allowed_duplication); ++j) {
439                         if (!strcmp(template, allowed_duplication[j]))
440                                 break;
441                 }
442                 if (j < ARRAY_SIZE(allowed_duplication))
443                         continue;
444
445                 err = generate_path(xfer, template, i, suffix);
446                 if (err < 0)
447                         break;
448         }
449 end:
450         free(template);
451
452         return err;
453 }
454
455 static int fixup_paths(struct xfer_context *xfer)
456 {
457         const char *suffix;
458         char *template;
459         int i, j;
460         int err = 0;
461
462         suffix = container_suffix_from_format(xfer->cntr_format);
463
464         for (i = 0; i < xfer->path_count; ++i) {
465                 // Some file names are allowed to be duplicated.
466                 for (j = 0; j < ARRAY_SIZE(allowed_duplication); ++j) {
467                         if (!strcmp(xfer->paths[i], allowed_duplication[j]))
468                                 break;
469                 }
470                 if (j < ARRAY_SIZE(allowed_duplication))
471                         continue;
472
473                 template = xfer->paths[i];
474                 xfer->paths[i] = NULL;
475                 err = generate_path(xfer, template, i, suffix);
476                 free(template);
477                 if (err < 0)
478                         break;
479         }
480
481         return err;
482 }
483
484 int xfer_options_fixup_paths(struct xfer_context *xfer)
485 {
486         int i, j;
487         int err;
488
489         if (xfer->path_count == 1) {
490                 // Nothing to do for sign of stdin/stdout.
491                 if (!strcmp(xfer->paths[0], "-"))
492                         return 0;
493                 if (!xfer->multiple_cntrs)
494                         err = fixup_paths(xfer);
495                 else
496                         err = create_paths(xfer, xfer->samples_per_frame);
497         } else {
498                 if (!xfer->multiple_cntrs)
499                         return -EINVAL;
500                 if (xfer->path_count != xfer->samples_per_frame)
501                         return -EINVAL;
502                 else
503                         err = fixup_paths(xfer);
504         }
505         if (err < 0)
506                 return err;
507
508         // Check duplication of the paths.
509         for (i = 0; i < xfer->path_count - 1; ++i) {
510                 // Some file names are allowed to be duplicated.
511                 for (j = 0; j < ARRAY_SIZE(allowed_duplication); ++j) {
512                         if (!strcmp(xfer->paths[i], allowed_duplication[j]))
513                                 break;
514                 }
515                 if (j < ARRAY_SIZE(allowed_duplication))
516                         continue;
517
518                 for (j = i + 1; j < xfer->path_count; ++j) {
519                         if (!strcmp(xfer->paths[i], xfer->paths[j])) {
520                                 fprintf(stderr,
521                                         "Detect duplicated file names:\n");
522                                 err = -EINVAL;
523                                 break;
524                         }
525                 }
526                 if (j < xfer->path_count)
527                         break;
528         }
529
530         if (xfer->verbose > 1)
531                 fprintf(stderr, "Handled file names:\n");
532         if (err < 0 || xfer->verbose > 1) {
533                 for (i = 0; i < xfer->path_count; ++i)
534                         fprintf(stderr, "    %d: %s\n", i, xfer->paths[i]);
535         }
536
537         return err;
538 }