2018-11-13 07:41:13 +01:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
// main.c - an entry point for this program.
|
|
|
|
//
|
|
|
|
// Copyright (c) 2018 Takashi Sakamoto <o-takashi@sakamocchi.jp>
|
|
|
|
//
|
|
|
|
// Originally written as 'aplay', by Michael Beck and Jaroslav Kysela.
|
|
|
|
//
|
|
|
|
// Licensed under the terms of the GNU General Public License, version 2.
|
|
|
|
|
|
|
|
#include "subcmd.h"
|
|
|
|
#include "misc.h"
|
|
|
|
|
|
|
|
#include "version.h"
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
enum subcmds {
|
|
|
|
SUBCMD_TRANSFER = 0,
|
|
|
|
SUBCMD_LIST,
|
|
|
|
SUBCMD_HELP,
|
|
|
|
SUBCMD_VERSION,
|
|
|
|
};
|
|
|
|
|
axfer: add a parser for command-line options
In aplay, many command-line options are supported. Some of them have
dependency or conflicts. Furthemore, some of them are just for
runtime configuration of alsa-lib(libasound), and some options can
be used by several xfer backends commonly; e.g. options for file name,
sample format and sampling rate.
This commit adds a parser for the common options below.
* --help (-h)
* Just output 'help' string (not written yet).
* --verbose (-v)
* For verbose output, including information about xfer, mapper and
container.
* --format (-f): string. format literals or one of ['cd'|'cdr'|'dat']
* For sample format supported by ALSA PCM interface. Special format
can be used. For playback, this is auto-detected according to actual
file format.
* --channels (-c)
* For the number of samples included in one data frame. For playback,
this is auto-detected according to actual file format, except for
'raw' format. This option can conflict to above format option.
* --rate (-r)
* For the number of data frames transferred in one second. For playback,
this is auto-detected according to actual file format, except for
'raw' format. This option can conflict to format option above.
* --file-type (-f): string. one of ['wav'|'au'|'voc'|'raw']
* For format of files of given paths. For playback, this is optional
because the format is auto-detected. For capture, this is optional too
because the format is decided according to suffix of given path.
Anyway, this option is used for cases to fail to detect or decide.
* --separate-channels (-I)
* When using several files as source or destination for transmission
of data frame, this option can be used with several file paths.
When '--separate-channels' option is used, users can give several file
paths to source/destination of data transmission, else they can give single
file path for the purpose. When multiple files are handled by this option,
for playback, data frames in first channel is used to construct buffer for
data transmission with multi channel. For capture, data frames in each
channel of buffer are written to each of given path. Furthermore, when a
single path is given for capture, file paths are auto-generated according
to available number of channels. For example, 'name.wav' is given for
2 channels capture, 'name-0.wav' and 'name-1.wav' are generated. In a
case of no suffix, 'name-0' and 'name-1' are generated.
Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2018-11-13 07:41:26 +01:00
|
|
|
char *arg_duplicate_string(const char *str, int *err)
|
|
|
|
{
|
|
|
|
char *ptr;
|
|
|
|
|
|
|
|
// For safe.
|
|
|
|
if (strlen(str) > 1024) {
|
|
|
|
*err = -EINVAL;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ptr = strdup(str);
|
|
|
|
if (ptr == NULL)
|
|
|
|
*err = -ENOMEM;
|
|
|
|
|
|
|
|
return ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
long arg_parse_decimal_num(const char *str, int *err)
|
|
|
|
{
|
|
|
|
long val;
|
|
|
|
char *endptr;
|
|
|
|
|
|
|
|
errno = 0;
|
|
|
|
val = strtol(str, &endptr, 0);
|
|
|
|
if (errno > 0) {
|
|
|
|
*err = -errno;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (*endptr != '\0') {
|
|
|
|
*err = -EINVAL;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
2018-11-13 07:41:13 +01:00
|
|
|
static void print_version(const char *const cmdname)
|
|
|
|
{
|
|
|
|
printf("%s: version %s\n", cmdname, SND_UTIL_VERSION_STR);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void print_help(void)
|
|
|
|
{
|
2018-12-07 10:41:02 +01:00
|
|
|
printf(
|
|
|
|
"Usage:\n"
|
|
|
|
" axfer transfer DIRECTION OPTIONS\n"
|
|
|
|
" axfer list DIRECTION OPTIONS\n"
|
|
|
|
" axfer version\n"
|
|
|
|
" axfer help\n"
|
|
|
|
"\n"
|
|
|
|
" where:\n"
|
|
|
|
" DIRECTION = capture | playback\n"
|
|
|
|
" OPTIONS = -h | --help | (subcommand specific)\n"
|
|
|
|
);
|
2018-11-13 07:41:13 +01:00
|
|
|
}
|
|
|
|
|
2018-12-03 22:33:42 +01:00
|
|
|
// Backward compatibility to aplay(1).
|
|
|
|
static bool decide_subcmd(int argc, char *const *argv, enum subcmds *subcmd)
|
2018-11-13 07:41:13 +01:00
|
|
|
{
|
|
|
|
static const struct {
|
|
|
|
const char *const name;
|
|
|
|
enum subcmds subcmd;
|
|
|
|
} long_opts[] = {
|
|
|
|
{"--list-devices", SUBCMD_LIST},
|
|
|
|
{"--list-pcms", SUBCMD_LIST},
|
|
|
|
{"--help", SUBCMD_HELP},
|
|
|
|
{"--version", SUBCMD_VERSION},
|
|
|
|
};
|
|
|
|
static const struct {
|
|
|
|
unsigned char c;
|
|
|
|
enum subcmds subcmd;
|
|
|
|
} short_opts[] = {
|
|
|
|
{'l', SUBCMD_LIST},
|
|
|
|
{'L', SUBCMD_LIST},
|
|
|
|
{'h', SUBCMD_HELP},
|
|
|
|
};
|
|
|
|
char *pos;
|
|
|
|
int i, j;
|
|
|
|
|
2018-12-03 22:33:42 +01:00
|
|
|
if (argc == 1)
|
|
|
|
return false;
|
2018-11-13 07:41:13 +01:00
|
|
|
|
|
|
|
// Original command system. For long options.
|
2023-08-30 12:35:57 +02:00
|
|
|
for (i = 0; i < (int)ARRAY_SIZE(long_opts); ++i) {
|
2018-11-13 07:41:13 +01:00
|
|
|
for (j = 0; j < argc; ++j) {
|
|
|
|
if (!strcmp(long_opts[i].name, argv[j])) {
|
|
|
|
*subcmd = long_opts[i].subcmd;
|
2018-12-03 22:33:42 +01:00
|
|
|
return true;
|
2018-11-13 07:41:13 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Original command system. For short options.
|
|
|
|
for (i = 1; i < argc; ++i) {
|
|
|
|
// Pick up short options only.
|
|
|
|
if (argv[i][0] != '-' || argv[i][0] == '\0' ||
|
|
|
|
argv[i][1] == '-' || argv[i][1] == '\0')
|
|
|
|
continue;
|
|
|
|
for (pos = argv[i]; *pos != '\0'; ++pos) {
|
2023-08-30 12:35:57 +02:00
|
|
|
for (j = 0; j < (int)ARRAY_SIZE(short_opts); ++j) {
|
2018-11-13 07:41:13 +01:00
|
|
|
if (*pos == short_opts[j].c) {
|
|
|
|
*subcmd = short_opts[j].subcmd;
|
2018-12-03 22:33:42 +01:00
|
|
|
return true;
|
2018-11-13 07:41:13 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-03 22:33:42 +01:00
|
|
|
return false;
|
2018-11-13 07:41:13 +01:00
|
|
|
}
|
|
|
|
|
2018-12-03 22:33:42 +01:00
|
|
|
// Backward compatibility to aplay(1).
|
2018-11-13 07:41:13 +01:00
|
|
|
static bool decide_direction(int argc, char *const *argv,
|
|
|
|
snd_pcm_stream_t *direction)
|
|
|
|
{
|
|
|
|
static const struct {
|
|
|
|
const char *const name;
|
|
|
|
snd_pcm_stream_t direction;
|
|
|
|
} long_opts[] = {
|
|
|
|
{"--capture", SND_PCM_STREAM_CAPTURE},
|
|
|
|
{"--playback", SND_PCM_STREAM_PLAYBACK},
|
|
|
|
};
|
|
|
|
static const struct {
|
|
|
|
unsigned char c;
|
|
|
|
snd_pcm_stream_t direction;
|
|
|
|
} short_opts[] = {
|
|
|
|
{'C', SND_PCM_STREAM_CAPTURE},
|
|
|
|
{'P', SND_PCM_STREAM_PLAYBACK},
|
|
|
|
};
|
|
|
|
static const char *const aliases[] = {
|
|
|
|
[SND_PCM_STREAM_CAPTURE] = "arecord",
|
|
|
|
[SND_PCM_STREAM_PLAYBACK] = "aplay",
|
|
|
|
};
|
|
|
|
int i, j;
|
|
|
|
char *pos;
|
|
|
|
|
|
|
|
// Original command system. For long options.
|
2023-08-30 12:35:57 +02:00
|
|
|
for (i = 0; i < (int)ARRAY_SIZE(long_opts); ++i) {
|
2018-11-13 07:41:13 +01:00
|
|
|
for (j = 0; j < argc; ++j) {
|
|
|
|
if (!strcmp(long_opts[i].name, argv[j])) {
|
|
|
|
*direction = long_opts[i].direction;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Original command system. For short options.
|
|
|
|
for (i = 1; i < argc; ++i) {
|
|
|
|
// Pick up short options only.
|
|
|
|
if (argv[i][0] != '-' || argv[i][0] == '\0' ||
|
|
|
|
argv[i][1] == '-' || argv[i][1] == '\0')
|
|
|
|
continue;
|
|
|
|
for (pos = argv[i]; *pos != '\0'; ++pos) {
|
2023-08-30 12:35:57 +02:00
|
|
|
for (j = 0; j < (int)ARRAY_SIZE(short_opts); ++j) {
|
2018-11-13 07:41:13 +01:00
|
|
|
if (*pos == short_opts[j].c) {
|
|
|
|
*direction = short_opts[j].direction;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If not decided yet, judge according to command name.
|
2023-08-30 12:35:57 +02:00
|
|
|
for (i = 0; i < (int)ARRAY_SIZE(aliases); ++i) {
|
2018-11-13 07:41:13 +01:00
|
|
|
for (pos = argv[0] + strlen(argv[0]); pos != argv[0]; --pos) {
|
|
|
|
if (strstr(pos, aliases[i]) != NULL) {
|
|
|
|
*direction = i;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-12-03 22:33:42 +01:00
|
|
|
static bool detect_subcmd(int argc, char *const *argv, enum subcmds *subcmd)
|
|
|
|
{
|
|
|
|
static const char *const subcmds[] = {
|
|
|
|
[SUBCMD_TRANSFER] = "transfer",
|
|
|
|
[SUBCMD_LIST] = "list",
|
|
|
|
[SUBCMD_HELP] = "help",
|
|
|
|
[SUBCMD_VERSION] = "version",
|
|
|
|
};
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (argc < 2)
|
|
|
|
return false;
|
|
|
|
|
2023-08-30 12:35:57 +02:00
|
|
|
for (i = 0; i < (int)ARRAY_SIZE(subcmds); ++i) {
|
2018-12-03 22:33:42 +01:00
|
|
|
if (!strcmp(argv[1], subcmds[i])) {
|
|
|
|
*subcmd = i;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool detect_direction(int argc, char *const *argv,
|
|
|
|
snd_pcm_stream_t *direction)
|
|
|
|
{
|
|
|
|
if (argc < 3)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!strcmp(argv[2], "capture")) {
|
|
|
|
*direction = SND_PCM_STREAM_CAPTURE;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!strcmp(argv[2], "playback")) {
|
|
|
|
*direction = SND_PCM_STREAM_PLAYBACK;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-11-13 07:41:13 +01:00
|
|
|
int main(int argc, char *const *argv)
|
|
|
|
{
|
|
|
|
snd_pcm_stream_t direction;
|
|
|
|
enum subcmds subcmd;
|
|
|
|
int err = 0;
|
|
|
|
|
2018-12-03 22:33:42 +01:00
|
|
|
// For compatibility to aplay(1) implementation.
|
|
|
|
if (strstr(argv[0], "arecord") == argv[0] + strlen(argv[0]) - 7 ||
|
|
|
|
strstr(argv[0], "aplay") == argv[0] + strlen(argv[0]) - 5) {
|
|
|
|
if (!decide_direction(argc, argv, &direction))
|
2018-12-03 22:33:42 +01:00
|
|
|
direction = SND_PCM_STREAM_PLAYBACK;
|
|
|
|
if (!decide_subcmd(argc, argv, &subcmd))
|
|
|
|
subcmd = SUBCMD_TRANSFER;
|
2018-12-03 22:33:42 +01:00
|
|
|
} else {
|
|
|
|
// The first option should be one of subcommands.
|
|
|
|
if (!detect_subcmd(argc, argv, &subcmd))
|
|
|
|
subcmd = SUBCMD_HELP;
|
|
|
|
// The second option should be either 'capture' or 'direction'
|
|
|
|
// if subcommand is neither 'version' nor 'help'.
|
|
|
|
if (subcmd != SUBCMD_VERSION && subcmd != SUBCMD_HELP) {
|
2018-12-03 22:33:42 +01:00
|
|
|
if (!detect_direction(argc, argv, &direction)) {
|
2018-12-03 22:33:42 +01:00
|
|
|
subcmd = SUBCMD_HELP;
|
2018-12-03 22:33:42 +01:00
|
|
|
} else {
|
|
|
|
// argv[0] is needed for unparsed option to use
|
|
|
|
// getopt_long(3).
|
|
|
|
argc -= 2;
|
|
|
|
argv += 2;
|
|
|
|
}
|
2018-12-03 22:33:42 +01:00
|
|
|
}
|
|
|
|
}
|
2018-11-13 07:41:13 +01:00
|
|
|
|
|
|
|
if (subcmd == SUBCMD_TRANSFER)
|
2018-11-13 07:41:29 +01:00
|
|
|
err = subcmd_transfer(argc, argv, direction);
|
2018-11-13 07:41:13 +01:00
|
|
|
else if (subcmd == SUBCMD_LIST)
|
2018-11-13 07:41:14 +01:00
|
|
|
err = subcmd_list(argc, argv, direction);
|
2018-11-13 07:41:13 +01:00
|
|
|
else if (subcmd == SUBCMD_VERSION)
|
|
|
|
print_version(argv[0]);
|
|
|
|
else
|
|
|
|
print_help();
|
|
|
|
if (err < 0)
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
}
|