mirror of
https://github.com/alsa-project/alsa-utils
synced 2024-12-22 13:06:32 +01:00
a90faa2dd6
We need to support older compilers than GCC 11.
Link: https://github.com/alsa-project/alsa-utils/issues/233
Fixes: ad5a1c0
("axfer: fix the verbose compilation warnings for latest gcc")
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
628 lines
16 KiB
C
628 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;
|
|
}
|