[alsa-devel] [RFCv2][PATCH 13/38] axfer: add a parser for command-line options
Takashi Sakamoto
o-takashi at sakamocchi.jp
Tue Sep 19 02:43:53 CEST 2017
In aplay, many command-line options are supported. Some of them have
dependency or conflicts.
This commit adds a structure and a parser for the options. In this patch,
minimal set of options is supported. Additional options will be supported
later.
Signed-off-by: Takashi Sakamoto <o-takashi at sakamocchi.jp>
---
axfer/Makefile.am | 5 +-
axfer/options.c | 594 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
axfer/options.h | 47 +++++
3 files changed, 645 insertions(+), 1 deletion(-)
create mode 100644 axfer/options.c
create mode 100644 axfer/options.h
diff --git a/axfer/Makefile.am b/axfer/Makefile.am
index f17e59b0..3b1a87c8 100644
--- a/axfer/Makefile.am
+++ b/axfer/Makefile.am
@@ -19,6 +19,7 @@ noinst_HEADERS = \
subcmd.h \
container.h \
mapper.h
+ options.h
axfer_SOURCES = \
misc.h \
@@ -34,4 +35,6 @@ axfer_SOURCES = \
mapper.h \
mapper.c \
mapper-single.c \
- mapper-multiple.c
+ mapper-multiple.c \
+ options.h \
+ options.c
diff --git a/axfer/options.c b/axfer/options.c
new file mode 100644
index 00000000..f3b0414b
--- /dev/null
+++ b/axfer/options.c
@@ -0,0 +1,594 @@
+/*
+ * options.c - a parser of commandline options.
+ *
+ * Copyright (c) 2017 Takashi Sakamoto <o-takashi at sakamocchi.jp>
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "options.h"
+#include "misc.h"
+
+#include <getopt.h>
+#include <math.h>
+
+static const char *const allowed_duplication[] = {
+ "/dev/null",
+ "/dev/zero",
+ "/dev/full",
+ "/dev/random",
+ "/dev/urandom",
+};
+
+static long parse_l(const char *str, int *err)
+{
+ long val;
+ char *endptr;
+
+ val = strtol(str, &endptr, 10);
+ if (errno)
+ return -errno;
+ if (*endptr != '\0')
+ return -EINVAL;
+
+ return val;
+}
+
+static int allocate_paths(struct context_options *opts, char *const *paths,
+ unsigned int count)
+{
+ bool stdio = false;
+ char *path;
+ int i;
+
+ if (count == 0) {
+ stdio = true;
+ count = 1;
+ }
+
+ opts->paths = calloc(count, sizeof(opts->paths[0]));
+ if (opts->paths == NULL)
+ return -ENOMEM;
+ opts->path_count = count;
+
+ if (stdio) {
+ opts->paths[0] = malloc(2);
+ if (opts->paths[0] == NULL)
+ return -ENOMEM;
+ strcpy(opts->paths[0], "-");
+ return 0;
+ }
+
+ for (i = 0; i < count; ++i) {
+ path = malloc(strlen(paths[i]) + 1);
+ if (path == NULL)
+ return -ENOMEM;
+ strcpy(path, paths[i]);
+ opts->paths[i] = path;
+ }
+
+ return 0;
+}
+
+static int verify_cntr_format(struct context_options *opts, const char *literal)
+{
+ static const struct {
+ const char *const literal;
+ enum container_format cntr_format;
+ } *entry, entries[] = {
+ {"raw", CONTAINER_FORMAT_RAW},
+ {"voc", CONTAINER_FORMAT_VOC},
+ {"wav", CONTAINER_FORMAT_RIFF_WAVE},
+ {"au", CONTAINER_FORMAT_AU},
+ {"sparc", CONTAINER_FORMAT_AU},
+ };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(entries); ++i) {
+ entry = &entries[i];
+ if (strcasecmp(literal, entry->literal))
+ continue;
+
+ opts->cntr_format = entry->cntr_format;
+ return 0;
+ }
+
+ fprintf(stderr, "unrecognized file format '%s'\n", literal);
+
+ return -EINVAL;
+}
+
+/* This should be called after 'verify_cntr_format()'. */
+static int verify_sample_format(struct context_options *opts,
+ const char *literal)
+{
+ static const struct {
+ const char *const literal;
+ unsigned int frames_per_second;
+ unsigned int samples_per_frame;
+ snd_pcm_format_t le_format;
+ snd_pcm_format_t be_format;
+ } *entry, entries[] = {
+ {"cd", 44100, 2, SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S16_BE},
+ {"cdr", 44100, 2, SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S16_BE},
+ {"dat", 48000, 2, SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S16_BE},
+ };
+ int i;
+
+ opts->sample_format = snd_pcm_format_value(literal);
+ if (opts->sample_format != SND_PCM_FORMAT_UNKNOWN)
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(entries); ++i) {
+ entry = &entries[i];
+ if (strcmp(entry->literal, literal))
+ continue;
+
+ if (opts->frames_per_second > 0 &&
+ opts->frames_per_second != entry->frames_per_second) {
+ fprintf(stderr,
+ "'%s' format can't be used with rate except "
+ "for %u.\n",
+ entry->literal, entry->frames_per_second);
+ return -EINVAL;
+ }
+
+ if (opts->samples_per_frame > 0 &&
+ opts->samples_per_frame != entry->samples_per_frame) {
+ fprintf(stderr,
+ "'%s' format can't be used with channel except "
+ "for %u.\n",
+ entry->literal, entry->samples_per_frame);
+ return -EINVAL;
+ }
+
+ opts->frames_per_second = entry->frames_per_second;
+ opts->samples_per_frame = entry->samples_per_frame;
+ if (opts->cntr_format == CONTAINER_FORMAT_AU)
+ opts->sample_format = entry->be_format;
+ else
+ opts->sample_format = entry->le_format;
+
+ return 0;
+ }
+
+ fprintf(stderr, "wrong extended format '%s'\n", literal);
+
+ return -EINVAL;
+}
+
+static int apply_policies(struct context_options *opts,
+ snd_pcm_stream_t direction,
+ const char *cntr_format_literal,
+ const char *node_literal,
+ const char *sample_format_literal)
+{
+ int err;
+
+ if (node_literal != NULL) {
+ opts->node = malloc(strlen(node_literal) + 1);
+ if (opts->node == NULL) {
+ fprintf(stderr, "Fail to allocate for node: %s\n",
+ node_literal);
+ return -ENOMEM;
+ }
+ strcpy(opts->node, node_literal);
+ }
+
+ if (!cntr_format_literal) {
+ if (direction == SND_PCM_STREAM_CAPTURE) {
+ /* To stdout. */
+ if (opts->path_count == 1 &&
+ !strcmp(opts->paths[0], "-")) {
+ opts->cntr_format = CONTAINER_FORMAT_RAW;
+ } else {
+ /* Use first path as a representative. */
+ opts->cntr_format = container_format_from_path(
+ opts->paths[0]);
+ }
+ }
+ /* For playback, perform auto-detection. */
+ } else {
+ err = verify_cntr_format(opts, cntr_format_literal);
+ if (err < 0)
+ return err;
+ }
+
+ if (opts->samples_per_frame > 0) {
+ if (opts->samples_per_frame < 1 ||
+ opts->samples_per_frame > 256) {
+ fprintf(stderr, "invalid channels argument '%u'\n",
+ opts->samples_per_frame);
+ return -EINVAL;
+ }
+ }
+
+ if (opts->multiple_cntrs) {
+ if (!strcmp(opts->paths[0], "-")) {
+ fprintf(stderr,
+ "An option for separated channels is not "
+ "available with stdin/stdout.\n");
+ return -EINVAL;
+ }
+
+ /*
+ * For captured PCM frames, even if one path is given for
+ * container files, it can be used to generate several paths.
+ * For this purpose, please see
+ * 'context_options_fixup_for_capture()'.
+ */
+ if (direction == SND_PCM_STREAM_PLAYBACK) {
+ /* Require several paths for containers. */
+ if (opts->path_count == 1) {
+ fprintf(stderr,
+ "An option for separated channels "
+ "requires several files to playback "
+ "PCM frames.\n");
+ return -EINVAL;
+ }
+ }
+ } else {
+ /* A single path is available only. */
+ if (opts->path_count > 1) {
+ fprintf(stderr,
+ "When using several files, an option for "
+ "sepatated channels is used with.\n");
+ return -EINVAL;
+ }
+ }
+
+ if (opts->frames_per_second > 0) {
+ unsigned int orig = opts->frames_per_second;
+
+ /* For backward compatibility. */
+ if (opts->frames_per_second < 300)
+ opts->frames_per_second *= 300;
+ if (opts->frames_per_second < 2000 ||
+ opts->frames_per_second > 192000) {
+ fprintf(stderr, "bad speed value '%i'\n", orig);
+ return -EINVAL;
+ }
+ }
+
+ opts->sample_format = SND_PCM_FORMAT_UNKNOWN;
+ if (sample_format_literal) {
+ err = verify_sample_format(opts, sample_format_literal);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+int context_options_init(struct context_options *opts, int argc,
+ char *const *argv, snd_pcm_stream_t direction)
+{
+ static const char *s_opts = "hvt:ID:f:c:r:";
+ static const struct option l_opts[] = {
+ /* For generic purposes. */
+ {"help", 0, 0, 'h'},
+ {"verbose", 0, 0, 'v'},
+ /* For containers. */
+ {"file-type", 1, 0, 't'},
+ /* For mapper. */
+ {"separate-channels", 0, 0, 'I'},
+ /* For transfer backend. */
+ {"device", 1, 0, 'D'},
+ {"format", 1, 0, 'f'},
+ {"channels", 1, 0, 'c'},
+ {"rate", 1, 0, 'r'},
+ {NULL, 0, 0, 0},
+ };
+ const char *cntr_format_literal = NULL;
+ const char *node_literal = NULL;
+ const char *sample_format_literal = NULL;
+ int c;
+ int err = 0;
+
+ optind = 0;
+ opterr = 0;
+ while (1) {
+ c = getopt_long(argc, argv, s_opts, l_opts, NULL);
+ if (c < 0)
+ break;
+ else if (c == 'h')
+ opts->help = true;
+ else if (c == 'v')
+ ++opts->verbose;
+ else if (c == 't')
+ cntr_format_literal = optarg;
+ else if (c == 'I')
+ opts->multiple_cntrs = true;
+ else if (c == 'D')
+ node_literal = optarg;
+ else if (c == 'f')
+ sample_format_literal = optarg;
+ else if (c == 'c')
+ opts->samples_per_frame = parse_l(optarg, &err);
+ else if (c == 'r')
+ opts->frames_per_second = parse_l(optarg, &err);
+ else
+ continue;
+
+ if (err < 0)
+ return err;
+ }
+
+ err = allocate_paths(opts, argv + optind, argc - optind);
+ if (err < 0)
+ return err;
+
+ return apply_policies(opts, direction, cntr_format_literal,
+ node_literal, sample_format_literal);
+}
+
+static int generate_path_with_suffix(struct context_options *opts,
+ const char *template, unsigned int index,
+ const char *suffix)
+{
+ static const char *const single_format = "%s%s";
+ static const char *const multiple_format = "%s-%i%s";
+ unsigned int len;
+
+ len = strlen(template) + strlen(suffix) + 1;
+ if (opts->path_count > 1)
+ len += (unsigned int)log10(opts->path_count) + 2;
+
+ opts->paths[index] = malloc(len);
+ if (opts->paths[index] == NULL)
+ return -ENOMEM;
+
+ if (opts->path_count == 1) {
+ snprintf(opts->paths[index], len, single_format, template,
+ suffix);
+ } else {
+ snprintf(opts->paths[index], len, multiple_format, template,
+ index, suffix);
+ }
+
+ return 0;
+}
+
+static int generate_path_without_suffix(struct context_options *opts,
+ const char *template,
+ unsigned int index, const char *suffix)
+{
+ static const char *const single_format = "%s";
+ static const char *const multiple_format = "%s-%i";
+ unsigned int len;
+
+ len = strlen(template) + 1;
+ if (opts->path_count > 1)
+ len += (unsigned int)log10(opts->path_count) + 2;
+
+ opts->paths[index] = malloc(len);
+ if (opts->paths[index] == NULL)
+ return -ENOMEM;
+
+ if (opts->path_count == 1) {
+ snprintf(opts->paths[index], len, single_format, template);
+ } else {
+ snprintf(opts->paths[index], len, multiple_format, template,
+ index);
+ }
+
+ return 0;
+}
+
+static int generate_path(struct context_options *opts, char *template,
+ unsigned int index, const char *suffix)
+{
+ int (*generator)(struct context_options *opts, const char *template,
+ unsigned int index, const char *suffix);
+ char *pos;
+
+ if (strlen(suffix) > 0) {
+ pos = template + strlen(template) - strlen(suffix);
+ /* Separate filename and suffix. */
+ if (!strcmp(pos, suffix))
+ *pos = '\0';
+ }
+
+ /* Select handlers. */
+ if (strlen(suffix) > 0)
+ generator = generate_path_with_suffix;
+ else
+ generator = generate_path_without_suffix;
+
+ return generator(opts, template, index, suffix);
+}
+
+static int create_paths(struct context_options *opts, unsigned int path_count)
+{
+ char *template;
+ const char *suffix;
+ int i, j;
+ int err = 0;
+
+ /* Can cause memory leak. */
+ assert(opts->path_count == 1);
+ assert(opts->paths);
+ assert(opts->paths[0]);
+ assert(opts->paths[0][0] != '\0');
+
+ /* Release at first. */
+ template = opts->paths[0];
+ free(opts->paths);
+ opts->paths = NULL;
+
+ /* Allocate again. */
+ opts->paths = calloc(path_count, sizeof(*opts->paths));
+ if (opts->paths == NULL) {
+ err = -ENOMEM;
+ goto end;
+ }
+ opts->path_count = path_count;
+
+ suffix = container_suffix_from_format(opts->cntr_format);
+
+ for (i = 0; i < opts->path_count; ++i) {
+ /* Some file names are allowed to be duplicated. */
+ for (j = 0; j < ARRAY_SIZE(allowed_duplication); ++j) {
+ if (!strcmp(opts->paths[i], allowed_duplication[j]))
+ break;
+ }
+ if (j < ARRAY_SIZE(allowed_duplication))
+ continue;
+
+ err = generate_path(opts, template, i, suffix);
+ if (err < 0)
+ break;
+ }
+end:
+ free(template);
+
+ return err;
+}
+
+static int fixup_paths(struct context_options *opts)
+{
+ const char *suffix;
+ char *template;
+ int i, j;
+ int err = 0;
+
+ suffix = container_suffix_from_format(opts->cntr_format);
+
+ for (i = 0; i < opts->path_count; ++i) {
+ /* Some file names are allowed to be duplicated. */
+ for (j = 0; j < ARRAY_SIZE(allowed_duplication); ++j) {
+ if (!strcmp(opts->paths[i], allowed_duplication[j]))
+ break;
+ }
+ if (j < ARRAY_SIZE(allowed_duplication))
+ continue;
+
+ template = opts->paths[i];
+ opts->paths[i] = NULL;
+ err = generate_path(opts, template, i, suffix);
+ free(template);
+ if (err < 0)
+ break;
+ }
+
+ return err;
+}
+
+int context_options_fixup_for_capture(struct context_options *opts,
+ unsigned int samples_per_frame)
+{
+ int i, j;
+ int err;
+
+ if (!opts->multiple_cntrs) {
+ if (opts->path_count == 1)
+ err = fixup_paths(opts);
+ else
+ return -EINVAL;
+ } else {
+ if (opts->path_count == 1) {
+ err = create_paths(opts, samples_per_frame);
+ } else {
+ if (opts->path_count == samples_per_frame)
+ err = fixup_paths(opts);
+ else
+ return -EINVAL;
+ }
+ }
+ if (err < 0)
+ return err;
+
+ /* Check duplication of the paths. */
+ for (i = 0; i < opts->path_count - 1; ++i) {
+ /* Some file names are allowed to be duplicated. */
+ for (j = 0; j < ARRAY_SIZE(allowed_duplication); ++j) {
+ if (!strcmp(opts->paths[i], allowed_duplication[j]))
+ break;
+ }
+ if (j < ARRAY_SIZE(allowed_duplication))
+ continue;
+
+ for (j = i + 1; j < opts->path_count; ++j) {
+ if (!strcmp(opts->paths[i], opts->paths[j])) {
+ fprintf(stderr,
+ "Detect duplicated file names:\n");
+ err = -EINVAL;
+ break;
+ }
+ }
+ if (j < opts->path_count)
+ break;
+ }
+
+ if (opts->verbose)
+ fprintf(stderr, "Handled file names:\n");
+ if (err < 0 || opts->verbose) {
+ for (i = 0; i < opts->path_count; ++i)
+ fprintf(stderr, " %d: %s\n", i, opts->paths[i]);
+ }
+
+ return err;
+}
+
+int context_options_fixup_for_playback(struct context_options *opts,
+ snd_pcm_format_t sample_format,
+ unsigned int samples_per_frame,
+ unsigned int frames_per_second)
+{
+ if (opts->sample_format != SND_PCM_FORMAT_UNKNOWN) {
+ if (sample_format != opts->sample_format) {
+ fprintf(stderr,
+ "Sample format mismatch: %s is given but %s by "
+ "files\n",
+ snd_pcm_format_name(opts->sample_format),
+ snd_pcm_format_name(sample_format));
+ return -EINVAL;
+ }
+ }
+
+ if (opts->samples_per_frame > 0) {
+ if (samples_per_frame != opts->samples_per_frame) {
+ fprintf(stderr,
+ "The number of channels mismatch: %u is given "
+ "but %u by files\n",
+ opts->samples_per_frame, samples_per_frame);
+ return -EINVAL;
+ }
+ }
+
+ if (opts->frames_per_second > 0) {
+ if (frames_per_second != opts->frames_per_second) {
+ fprintf(stderr,
+ "Sampling rate mismatch: %u is given but %u by "
+ "files\n",
+ opts->frames_per_second, frames_per_second);
+ return -EINVAL;
+ }
+ }
+
+ if (opts->verbose) {
+ int i;
+
+ fprintf(stderr, "Handled file names:\n");
+ for (i = 0; i < opts->path_count; ++i) {
+ fprintf(stderr, " %d: %s\n", i, opts->paths[i]);
+ }
+ }
+
+ return 0;
+}
+
+void context_options_destroy(struct context_options *opts)
+{
+ int i;
+
+ if (opts->paths) {
+ for (i = 0; i < opts->path_count; ++i)
+ free(opts->paths[i]);
+ free(opts->paths);
+ }
+ if (opts->node)
+ free(opts->node);
+ opts->paths = NULL;
+ opts->node = NULL;
+}
diff --git a/axfer/options.h b/axfer/options.h
new file mode 100644
index 00000000..38bdbb74
--- /dev/null
+++ b/axfer/options.h
@@ -0,0 +1,47 @@
+/*
+ * options.h - a header for a parser of commandline options.
+ *
+ * Copyright (c) 2017 Takashi Sakamoto <o-takashi at sakamocchi.jp>
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#ifndef __ALSA_UTILS_AXFER_OPTIONS__H_
+#define __ALSA_UTILS_AXFER_OPTIONS__H_
+
+#include "container.h"
+
+struct context_options {
+ bool help;
+
+ /* For generic purposes. */
+ unsigned int verbose;
+
+ /* For containers. */
+ char **paths;
+ unsigned int path_count;
+ enum container_format cntr_format;
+
+ /* For mapper. */
+ bool multiple_cntrs;
+
+ /* For transfer backend. */
+ char *node;
+ snd_pcm_format_t sample_format;
+ unsigned int samples_per_frame;
+ unsigned int frames_per_second;
+};
+
+int context_options_init(struct context_options *opts, int argc,
+ char *const *argv, snd_pcm_stream_t direction);
+int context_options_fixup_for_playback(struct context_options *opts,
+ snd_pcm_format_t sample_format,
+ unsigned int samples_per_frame,
+ unsigned int frames_per_second);
+int context_options_fixup_for_capture(struct context_options *opts,
+ unsigned int samples_per_frame);
+void context_options_calculate_duration(struct context_options *opts,
+ unsigned int frames_per_second, uint64_t *total_frame_count);
+void context_options_destroy(struct context_options *opts);
+
+#endif
--
2.11.0
More information about the Alsa-devel
mailing list