alsa-utils/axfer/xfer-options.c
Takashi Sakamoto f5776e232c axfer: add options related to duration and obsolete '--max-file-size' option
In aplay, some options are available to stop data transmission by frame
unit. This commit adds support for the options below:
 * --duration (-d)
  * For duration seconds. The number of data frames transferred in this
  * runtime is calculated by this value and sampling rate.
 * --samples (-s)
  * For the number of data frames to handle in this runtime.

An original aplay has a similar option; '--max-file-time'. This option
is used for capture data transmission to switch file to write data frame
up to maximum number of frames which container format supports, instead
of terminating. However, this may brings complicated file handling to
this program. To reduce maintaining cost, this option is obsoleted.
Additionally, a handler for SIGUSR1 Unix signal has similar feature to
switch the file. For the same reason, the handler is also obsoleted.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2018-11-13 12:04:35 +01:00

578 lines
14 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,
// Obsoleted.
OPT_MAX_FILE_TIME,
};
static int allocate_paths(struct xfer_context *xfer, char *const *paths,
unsigned int count)
{
bool stdio = false;
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] = strndup("-", PATH_MAX);
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 < 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},
};
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 '%i'\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: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},
};
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 == '?')
return -EINVAL;
else if (key == OPT_MAX_FILE_TIME) {
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);
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 = xfer->duration_seconds * 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)
{
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;
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;
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)
{
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;
}