1 // SPDX-License-Identifier: GPL-2.0
3 // xfer-options.c - a parser of commandline options for xfer.
5 // Copyright (c) 2018 Takashi Sakamoto <o-takashi@sakamocchi.jp>
7 // Licensed under the terms of the GNU General Public License, version 2.
17 // 128 or later belong to non us-ascii character set.
21 static int allocate_paths(struct xfer_context *xfer, char *const *paths,
32 xfer->paths = calloc(count, sizeof(xfer->paths[0]));
33 if (xfer->paths == NULL)
35 xfer->path_count = count;
38 xfer->paths[0] = strndup("-", PATH_MAX);
39 if (xfer->paths[0] == NULL)
42 for (i = 0; i < count; ++i) {
43 xfer->paths[i] = strndup(paths[i], PATH_MAX);
44 if (xfer->paths[i] == NULL)
52 static int verify_cntr_format(struct xfer_context *xfer)
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},
66 for (i = 0; i < ARRAY_SIZE(entries); ++i) {
68 if (strcasecmp(xfer->cntr_format_literal, entry->literal))
71 xfer->cntr_format = entry->cntr_format;
75 fprintf(stderr, "unrecognized file format '%s'\n",
76 xfer->cntr_format_literal);
81 // This should be called after 'verify_cntr_format()'.
82 static int verify_sample_format(struct xfer_context *xfer)
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},
97 xfer->sample_format = snd_pcm_format_value(xfer->sample_format_literal);
98 if (xfer->sample_format != SND_PCM_FORMAT_UNKNOWN)
101 for (i = 0; i < ARRAY_SIZE(entries); ++i) {
103 if (strcmp(entry->literal, xfer->sample_format_literal))
106 if (xfer->frames_per_second > 0 &&
107 xfer->frames_per_second != entry->frames_per_second) {
109 "'%s' format can't be used with rate except "
111 entry->literal, entry->frames_per_second);
115 if (xfer->samples_per_frame > 0 &&
116 xfer->samples_per_frame != entry->samples_per_frame) {
118 "'%s' format can't be used with channel except "
120 entry->literal, entry->samples_per_frame);
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;
129 xfer->sample_format = entry->le_format;
134 fprintf(stderr, "wrong extended format '%s'\n",
135 xfer->sample_format_literal);
140 static int validate_options(struct xfer_context *xfer)
145 if (xfer->cntr_format_literal == NULL) {
146 if (xfer->direction == SND_PCM_STREAM_CAPTURE) {
148 if (xfer->path_count == 1 &&
149 !strcmp(xfer->paths[0], "-")) {
150 xfer->cntr_format = CONTAINER_FORMAT_RAW;
152 // Use first path as a representative.
153 xfer->cntr_format = container_format_from_path(
157 // For playback, perform auto-detection.
159 err = verify_cntr_format(xfer);
164 if (xfer->multiple_cntrs) {
165 if (!strcmp(xfer->paths[0], "-")) {
167 "An option for separated channels is not "
168 "available with stdin/stdout.\n");
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) {
180 "An option for separated channels "
181 "requires several files to playback "
187 // A single path is available only.
188 if (xfer->path_count > 1) {
190 "When using several files, an option for "
191 "sepatated channels is used with.\n");
196 xfer->sample_format = SND_PCM_FORMAT_UNKNOWN;
197 if (xfer->sample_format_literal) {
198 err = verify_sample_format(xfer);
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);
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);
226 int xfer_options_parse_args(struct xfer_context *xfer,
227 const struct xfer_data *data, int argc,
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},
237 {"verbose", 0, 0, 'v'},
238 {"quiet", 0, 0, 'q'},
239 // For transfer backend.
240 {"format", 1, 0, 'f'},
241 {"channels", 1, 0, 'c'},
244 {"file-type", 1, 0, 't'},
246 {"separate-channels", 0, 0, 'I'},
249 struct option *l_opts;
254 // Concatenate short options.
255 s_opts = malloc(strlen(data->s_opts) + strlen(short_opts) + 1);
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';
262 // Concatenate long options, including a sentinel.
263 l_opts = calloc(ARRAY_SIZE(long_opts) * data->l_opts_count + 1,
265 if (l_opts == NULL) {
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));
277 opterr = 1; // use error output.
280 key = getopt_long(argc, argv, s_opts, l_opts, &l_index);
287 else if (key == OPT_XFER_TYPE)
296 xfer->sample_format_literal = arg_duplicate_string(optarg, &err);
298 xfer->samples_per_frame = arg_parse_decimal_num(optarg, &err);
300 xfer->frames_per_second = arg_parse_decimal_num(optarg, &err);
302 xfer->cntr_format_literal = arg_duplicate_string(optarg, &err);
304 xfer->multiple_cntrs = true;
308 err = xfer->ops->parse_opt(xfer, key, optarg);
309 if (err < 0 && err != -ENXIO)
317 err = allocate_paths(xfer, argv + optind, argc - optind);
321 return validate_options(xfer);
324 static const char *const allowed_duplication[] = {
332 static int generate_path_with_suffix(struct xfer_context *xfer,
333 const char *template, unsigned int index,
336 static const char *const single_format = "%s%s";
337 static const char *const multiple_format = "%s-%i%s";
340 len = strlen(template) + strlen(suffix) + 1;
341 if (xfer->path_count > 1)
342 len += (unsigned int)log10(xfer->path_count) + 2;
344 xfer->paths[index] = malloc(len);
345 if (xfer->paths[index] == NULL)
348 if (xfer->path_count == 1) {
349 snprintf(xfer->paths[index], len, single_format, template,
352 snprintf(xfer->paths[index], len, multiple_format, template,
359 static int generate_path_without_suffix(struct xfer_context *xfer,
360 const char *template,
361 unsigned int index, const char *suffix)
363 static const char *const single_format = "%s";
364 static const char *const multiple_format = "%s-%i";
367 len = strlen(template) + 1;
368 if (xfer->path_count > 1)
369 len += (unsigned int)log10(xfer->path_count) + 2;
371 xfer->paths[index] = malloc(len);
372 if (xfer->paths[index] == NULL)
375 if (xfer->path_count == 1) {
376 snprintf(xfer->paths[index], len, single_format, template);
378 snprintf(xfer->paths[index], len, multiple_format, template,
385 static int generate_path(struct xfer_context *xfer, char *template,
386 unsigned int index, const char *suffix)
388 int (*generator)(struct xfer_context *xfer, const char *template,
389 unsigned int index, const char *suffix);
392 if (strlen(suffix) > 0) {
393 pos = template + strlen(template) - strlen(suffix);
394 // Separate filename and suffix.
395 if (!strcmp(pos, suffix))
400 if (strlen(suffix) > 0)
401 generator = generate_path_with_suffix;
403 generator = generate_path_without_suffix;
405 return generator(xfer, template, index, suffix);
408 static int create_paths(struct xfer_context *xfer, unsigned int path_count)
415 // Can cause memory leak.
416 assert(xfer->path_count == 1);
418 assert(xfer->paths[0]);
419 assert(xfer->paths[0][0] != '\0');
422 template = xfer->paths[0];
427 xfer->paths = calloc(path_count, sizeof(*xfer->paths));
428 if (xfer->paths == NULL) {
432 xfer->path_count = path_count;
434 suffix = container_suffix_from_format(xfer->cntr_format);
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]))
442 if (j < ARRAY_SIZE(allowed_duplication))
445 err = generate_path(xfer, template, i, suffix);
455 static int fixup_paths(struct xfer_context *xfer)
462 suffix = container_suffix_from_format(xfer->cntr_format);
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]))
470 if (j < ARRAY_SIZE(allowed_duplication))
473 template = xfer->paths[i];
474 xfer->paths[i] = NULL;
475 err = generate_path(xfer, template, i, suffix);
484 int xfer_options_fixup_paths(struct xfer_context *xfer)
489 if (xfer->path_count == 1) {
490 // Nothing to do for sign of stdin/stdout.
491 if (!strcmp(xfer->paths[0], "-"))
493 if (!xfer->multiple_cntrs)
494 err = fixup_paths(xfer);
496 err = create_paths(xfer, xfer->samples_per_frame);
498 if (!xfer->multiple_cntrs)
500 if (xfer->path_count != xfer->samples_per_frame)
503 err = fixup_paths(xfer);
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]))
515 if (j < ARRAY_SIZE(allowed_duplication))
518 for (j = i + 1; j < xfer->path_count; ++j) {
519 if (!strcmp(xfer->paths[i], xfer->paths[j])) {
521 "Detect duplicated file names:\n");
526 if (j < xfer->path_count)
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]);