third_party_alsa-utils/axfer/xfer-options.c
xuxuehai 624a3e8926 update alsa-utils-1.2.11
Signed-off-by: xuxuehai <xuxuehai2@huawei.com>
2024-05-22 16:37:50 +08:00

629 lines
16 KiB
C

// SPDX-License-Identifier: GPL-2.0
//
// xfer-options.c - a parser of commandline options for xfer.
//
// Copyright (c) 2018 Takashi Sakamoto <o-takashi@sakamocchi.jp>
//
// Licensed under the terms of the GNU General Public License, version 2.
#include "xfer.h"
#include "misc.h"
#include <getopt.h>
#include <math.h>
#include <limits.h>
enum no_short_opts {
// 128 or later belong to non us-ascii character set.
OPT_XFER_TYPE = 128,
OPT_DUMP_HW_PARAMS,
OPT_PERIOD_SIZE,
OPT_BUFFER_SIZE,
// Obsoleted.
OPT_MAX_FILE_TIME,
OPT_USE_STRFTIME,
OPT_PROCESS_ID_FILE,
};
static void print_help()
{
printf(
"Usage:\n"
" axfer transfer DIRECTION [ COMMON-OPTIONS ] [ BACKEND-OPTIONS ]\n"
"\n"
" where:\n"
" DIRECTION = capture | playback\n"
" COMMON-OPTIONS =\n"
" -h, --help help\n"
" -v, --verbose verbose\n"
" -q, --quiet quiet mode\n"
" -d, --duration=# interrupt after # seconds\n"
" -s, --samples=# interrupt after # frames\n"
" -f, --format=FORMAT sample format (case-insensitive)\n"
" -c, --channels=# channels\n"
" -r, --rate=# numeric sample rate in unit of Hz or kHz\n"
" -t, --file-type=TYPE file type (wav, au, sparc, voc or raw, case-insentive)\n"
" -I, --separate-channels one file for each channel\n"
" --dump-hw-params dump hw_params of the device\n"
" --xfer-type=BACKEND backend type (libasound, libffado)\n"
);
}
static int allocate_paths(struct xfer_context *xfer, char *const *paths,
unsigned int count)
{
bool stdio = false;
unsigned int i;
if (count == 0) {
stdio = true;
count = 1;
}
xfer->paths = calloc(count, sizeof(xfer->paths[0]));
if (xfer->paths == NULL)
return -ENOMEM;
xfer->path_count = count;
if (stdio) {
xfer->paths[0] = strdup("-");
if (xfer->paths[0] == NULL)
return -ENOMEM;
} else {
for (i = 0; i < count; ++i) {
xfer->paths[i] = strndup(paths[i], PATH_MAX);
if (xfer->paths[i] == NULL)
return -ENOMEM;
}
}
return 0;
}
static int verify_cntr_format(struct xfer_context *xfer)
{
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 < (int)ARRAY_SIZE(entries); ++i) {
entry = &entries[i];
if (strcasecmp(xfer->cntr_format_literal, entry->literal))
continue;
xfer->cntr_format = entry->cntr_format;
return 0;
}
fprintf(stderr, "unrecognized file format '%s'\n",
xfer->cntr_format_literal);
return -EINVAL;
}
// This should be called after 'verify_cntr_format()'.
static int verify_sample_format(struct xfer_context *xfer)
{
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},
};
unsigned int i;
xfer->sample_format = snd_pcm_format_value(xfer->sample_format_literal);
if (xfer->sample_format != SND_PCM_FORMAT_UNKNOWN)
return 0;
for (i = 0; i < ARRAY_SIZE(entries); ++i) {
entry = &entries[i];
if (strcmp(entry->literal, xfer->sample_format_literal))
continue;
if (xfer->frames_per_second > 0 &&
xfer->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 (xfer->samples_per_frame > 0 &&
xfer->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;
}
xfer->frames_per_second = entry->frames_per_second;
xfer->samples_per_frame = entry->samples_per_frame;
if (xfer->cntr_format == CONTAINER_FORMAT_AU)
xfer->sample_format = entry->be_format;
else
xfer->sample_format = entry->le_format;
return 0;
}
fprintf(stderr, "wrong extended format '%s'\n",
xfer->sample_format_literal);
return -EINVAL;
}
static int validate_options(struct xfer_context *xfer)
{
unsigned int val;
int err = 0;
if (xfer->cntr_format_literal == NULL) {
if (xfer->direction == SND_PCM_STREAM_CAPTURE) {
// To stdout.
if (xfer->path_count == 1 &&
!strcmp(xfer->paths[0], "-")) {
xfer->cntr_format = CONTAINER_FORMAT_RAW;
} else {
// Use first path as a representative.
xfer->cntr_format = container_format_from_path(
xfer->paths[0]);
}
}
// For playback, perform auto-detection.
} else {
err = verify_cntr_format(xfer);
}
if (err < 0)
return err;
if (xfer->multiple_cntrs) {
if (!strcmp(xfer->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
// 'xfer_options_fixup_paths()'.
if (xfer->direction == SND_PCM_STREAM_PLAYBACK) {
// Require several paths for containers.
if (xfer->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 (xfer->path_count > 1) {
fprintf(stderr,
"When using several files, an option for "
"sepatated channels is used with.\n");
return -EINVAL;
}
}
xfer->sample_format = SND_PCM_FORMAT_UNKNOWN;
if (xfer->sample_format_literal) {
err = verify_sample_format(xfer);
if (err < 0)
return err;
}
val = xfer->frames_per_second;
if (xfer->frames_per_second == 0)
xfer->frames_per_second = 8000;
if (xfer->frames_per_second < 1000)
xfer->frames_per_second *= 1000;
if (xfer->frames_per_second < 2000 ||
xfer->frames_per_second > 192000) {
fprintf(stderr, "bad speed value '%u'\n", val);
return -EINVAL;
}
if (xfer->samples_per_frame > 0) {
if (xfer->samples_per_frame < 1 ||
xfer->samples_per_frame > 256) {
fprintf(stderr, "invalid channels argument '%u'\n",
xfer->samples_per_frame);
return -EINVAL;
}
}
return err;
}
int xfer_options_parse_args(struct xfer_context *xfer,
const struct xfer_data *data, int argc,
char *const *argv)
{
static const char *short_opts = "CPhvqd:s:f:c:r:t:IV:i";
static const struct option long_opts[] = {
// For generic purposes.
{"capture", 0, 0, 'C'},
{"playback", 0, 0, 'P'},
{"xfer-type", 1, 0, OPT_XFER_TYPE},
{"help", 0, 0, 'h'},
{"verbose", 0, 0, 'v'},
{"quiet", 0, 0, 'q'},
{"duration", 1, 0, 'd'},
{"samples", 1, 0, 's'},
// For transfer backend.
{"format", 1, 0, 'f'},
{"channels", 1, 0, 'c'},
{"rate", 1, 0, 'r'},
// For containers.
{"file-type", 1, 0, 't'},
// For mapper.
{"separate-channels", 0, 0, 'I'},
// For debugging.
{"dump-hw-params", 0, 0, OPT_DUMP_HW_PARAMS},
// Obsoleted.
{"max-file-time", 1, 0, OPT_MAX_FILE_TIME},
{"use-strftime", 0, 0, OPT_USE_STRFTIME},
{"process-id-file", 1, 0, OPT_PROCESS_ID_FILE},
{"vumeter", 1, 0, 'V'},
{"interactive", 0, 0, 'i'},
};
char *s_opts;
struct option *l_opts;
int l_index;
int key;
int err = 0;
// Concatenate short options.
s_opts = malloc(strlen(data->s_opts) + strlen(short_opts) + 1);
if (s_opts == NULL)
return -ENOMEM;
strcpy(s_opts, data->s_opts);
strcpy(s_opts + strlen(s_opts), short_opts);
s_opts[strlen(data->s_opts) + strlen(short_opts)] = '\0';
// Concatenate long options, including a sentinel.
l_opts = calloc(ARRAY_SIZE(long_opts) * data->l_opts_count + 1,
sizeof(*l_opts));
if (l_opts == NULL) {
free(s_opts);
return -ENOMEM;
}
memcpy(l_opts, long_opts, ARRAY_SIZE(long_opts) * sizeof(*l_opts));
memcpy(&l_opts[ARRAY_SIZE(long_opts)], data->l_opts,
data->l_opts_count * sizeof(*l_opts));
// Parse options.
l_index = 0;
optarg = NULL;
optind = 1;
opterr = 1; // use error output.
optopt = 0;
while (1) {
key = getopt_long(argc, argv, s_opts, l_opts, &l_index);
if (key < 0)
break;
else if (key == 'C')
; // already parsed.
else if (key == 'P')
; // already parsed.
else if (key == OPT_XFER_TYPE)
; // already parsed.
else if (key == 'h')
xfer->help = true;
else if (key == 'v')
++xfer->verbose;
else if (key == 'q')
xfer->quiet = true;
else if (key == 'd')
xfer->duration_seconds = arg_parse_decimal_num(optarg, &err);
else if (key == 's')
xfer->duration_frames = arg_parse_decimal_num(optarg, &err);
else if (key == 'f')
xfer->sample_format_literal = arg_duplicate_string(optarg, &err);
else if (key == 'c')
xfer->samples_per_frame = arg_parse_decimal_num(optarg, &err);
else if (key == 'r')
xfer->frames_per_second = arg_parse_decimal_num(optarg, &err);
else if (key == 't')
xfer->cntr_format_literal = arg_duplicate_string(optarg, &err);
else if (key == 'I')
xfer->multiple_cntrs = true;
else if (key == OPT_DUMP_HW_PARAMS)
xfer->dump_hw_params = true;
else if (key == '?') {
free(l_opts);
free(s_opts);
return -EINVAL;
}
else if (key == OPT_MAX_FILE_TIME ||
key == OPT_USE_STRFTIME ||
key == OPT_PROCESS_ID_FILE ||
key == 'V' ||
key == 'i') {
fprintf(stderr,
"An option '--%s' is obsoleted and has no "
"effect.\n",
l_opts[l_index].name);
err = -EINVAL;
} else {
err = xfer->ops->parse_opt(xfer, key, optarg);
if (err < 0 && err != -ENXIO)
break;
}
}
free(l_opts);
free(s_opts);
if (xfer->help) {
print_help();
if (xfer->ops->help) {
printf("\n");
printf(" BACKEND-OPTIONS (%s) =\n",
xfer_label_from_type(xfer->type));
xfer->ops->help(xfer);
}
return 0;
}
err = allocate_paths(xfer, argv + optind, argc - optind);
if (err < 0)
return err;
return validate_options(xfer);
}
void xfer_options_calculate_duration(struct xfer_context *xfer,
uint64_t *total_frame_count)
{
uint64_t frame_count;
if (xfer->duration_seconds > 0) {
frame_count = (uint64_t)xfer->duration_seconds * (uint64_t)xfer->frames_per_second;
if (frame_count < *total_frame_count)
*total_frame_count = frame_count;
}
if (xfer->duration_frames > 0) {
frame_count = xfer->duration_frames;
if (frame_count < *total_frame_count)
*total_frame_count = frame_count;
}
}
static const char *const allowed_duplication[] = {
"/dev/null",
"/dev/zero",
"/dev/full",
"/dev/random",
"/dev/urandom",
};
static int generate_path_with_suffix(struct xfer_context *xfer,
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 (xfer->path_count > 1)
len += (unsigned int)log10(xfer->path_count) + 2;
xfer->paths[index] = malloc(len);
if (xfer->paths[index] == NULL)
return -ENOMEM;
if (xfer->path_count == 1) {
snprintf(xfer->paths[index], len, single_format, template,
suffix);
} else {
snprintf(xfer->paths[index], len, multiple_format, template,
index, suffix);
}
return 0;
}
static int generate_path_without_suffix(struct xfer_context *xfer,
const char *template,
unsigned int index,
const char *suffix ATTRIBUTE_UNUSED)
{
static const char *const single_format = "%s";
static const char *const multiple_format = "%s-%i";
unsigned int len;
len = strlen(template) + 1;
if (xfer->path_count > 1)
len += (unsigned int)log10(xfer->path_count) + 2;
xfer->paths[index] = malloc(len);
if (xfer->paths[index] == NULL)
return -ENOMEM;
if (xfer->path_count == 1) {
snprintf(xfer->paths[index], len, single_format, template);
} else {
snprintf(xfer->paths[index], len, multiple_format, template,
index);
}
return 0;
}
static int generate_path(struct xfer_context *xfer, char *template,
unsigned int index, const char *suffix)
{
int (*generator)(struct xfer_context *xfer, 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(xfer, template, index, suffix);
}
static int create_paths(struct xfer_context *xfer, unsigned int path_count)
{
char *template;
const char *suffix;
unsigned int i, j;
int err = 0;
// Can cause memory leak.
assert(xfer->path_count == 1);
assert(xfer->paths);
assert(xfer->paths[0]);
assert(xfer->paths[0][0] != '\0');
// Release at first.
template = xfer->paths[0];
free(xfer->paths);
xfer->paths = NULL;
// Allocate again.
xfer->paths = calloc(path_count, sizeof(*xfer->paths));
if (xfer->paths == NULL) {
err = -ENOMEM;
goto end;
}
xfer->path_count = path_count;
suffix = container_suffix_from_format(xfer->cntr_format);
for (i = 0; i < xfer->path_count; ++i) {
// Some file names are allowed to be duplicated.
for (j = 0; j < ARRAY_SIZE(allowed_duplication); ++j) {
if (!strcmp(template, allowed_duplication[j]))
break;
}
if (j < ARRAY_SIZE(allowed_duplication))
continue;
err = generate_path(xfer, template, i, suffix);
if (err < 0)
break;
}
end:
free(template);
return err;
}
static int fixup_paths(struct xfer_context *xfer)
{
const char *suffix;
char *template;
unsigned int i, j;
int err = 0;
suffix = container_suffix_from_format(xfer->cntr_format);
for (i = 0; i < xfer->path_count; ++i) {
// Some file names are allowed to be duplicated.
for (j = 0; j < ARRAY_SIZE(allowed_duplication); ++j) {
if (!strcmp(xfer->paths[i], allowed_duplication[j]))
break;
}
if (j < ARRAY_SIZE(allowed_duplication))
continue;
template = xfer->paths[i];
xfer->paths[i] = NULL;
err = generate_path(xfer, template, i, suffix);
free(template);
if (err < 0)
break;
}
return err;
}
int xfer_options_fixup_paths(struct xfer_context *xfer)
{
unsigned int i, j;
int err;
if (xfer->path_count == 1) {
// Nothing to do for sign of stdin/stdout.
if (!strcmp(xfer->paths[0], "-"))
return 0;
if (!xfer->multiple_cntrs)
err = fixup_paths(xfer);
else
err = create_paths(xfer, xfer->samples_per_frame);
} else {
if (!xfer->multiple_cntrs)
return -EINVAL;
if (xfer->path_count != xfer->samples_per_frame)
return -EINVAL;
else
err = fixup_paths(xfer);
}
if (err < 0)
return err;
// Check duplication of the paths.
for (i = 0; i < xfer->path_count - 1; ++i) {
// Some file names are allowed to be duplicated.
for (j = 0; j < ARRAY_SIZE(allowed_duplication); ++j) {
if (!strcmp(xfer->paths[i], allowed_duplication[j]))
break;
}
if (j < ARRAY_SIZE(allowed_duplication))
continue;
for (j = i + 1; j < xfer->path_count; ++j) {
if (!strcmp(xfer->paths[i], xfer->paths[j])) {
fprintf(stderr,
"Detect duplicated file names:\n");
err = -EINVAL;
break;
}
}
if (j < xfer->path_count)
break;
}
if (xfer->verbose > 1)
fprintf(stderr, "Handled file names:\n");
if (err < 0 || xfer->verbose > 1) {
for (i = 0; i < xfer->path_count; ++i)
fprintf(stderr, " %d: %s\n", i, xfer->paths[i]);
}
return err;
}