alsa-utils/axfer/main.c
Takashi Sakamoto 3266d71c0d axfer: add a sub-command to transfer data frames
In current aplay, default action is to transfer data frames from/to
devices. This commit adds support for this functionality.

Event loop is included in an added file. In the loop, the number of
handled data frames is manipulated by an appropriate way. As a result, users
can stop data transmission frames by frame.

Unlike aplay, when catching SIGSTP, this application performs to suspend
PCM substream. When catching SIGCONT, it performs to resume the PCM
substream. The aim of this design is to avoid XRUN state of the PCM
substream. If users/developers need to any XRUN-recovery test, it's
better to work for the other ways.

Below lines are examples to execute:
$ axfer transfer -P -D hw:0,3 /dev/urandom -f dat -vvv
$ axfer transfer -C -D hw:1,0 /dev/null -r 48000 -vvv

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

230 lines
4.7 KiB
C

// 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,
};
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;
}
static void print_version(const char *const cmdname)
{
printf("%s: version %s\n", cmdname, SND_UTIL_VERSION_STR);
}
static void print_help(void)
{
printf("help\n");
}
static void decide_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",
};
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;
if (argc == 1) {
*subcmd = SUBCMD_HELP;
return;
}
// sub-command system.
for (i = 0; i < ARRAY_SIZE(subcmds); ++i) {
if (!strcmp(argv[1], subcmds[i])) {
*subcmd = i;
return;
}
}
// Original command system. For long options.
for (i = 0; i < ARRAY_SIZE(long_opts); ++i) {
for (j = 0; j < argc; ++j) {
if (!strcmp(long_opts[i].name, argv[j])) {
*subcmd = long_opts[i].subcmd;
return;
}
}
}
// 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) {
for (j = 0; j < ARRAY_SIZE(short_opts); ++j) {
if (*pos == short_opts[j].c) {
*subcmd = short_opts[j].subcmd;
return;
}
}
}
}
*subcmd = SUBCMD_TRANSFER;
}
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.
for (i = 0; i < ARRAY_SIZE(long_opts); ++i) {
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) {
for (j = 0; j < ARRAY_SIZE(short_opts); ++j) {
if (*pos == short_opts[j].c) {
*direction = short_opts[j].direction;
return true;
}
}
}
}
// If not decided yet, judge according to command name.
for (i = 0; i < ARRAY_SIZE(aliases); ++i) {
for (pos = argv[0] + strlen(argv[0]); pos != argv[0]; --pos) {
if (strstr(pos, aliases[i]) != NULL) {
*direction = i;
return true;
}
}
}
return false;
}
int main(int argc, char *const *argv)
{
snd_pcm_stream_t direction;
enum subcmds subcmd;
int err = 0;
if (!decide_direction(argc, argv, &direction))
subcmd = SUBCMD_HELP;
else
decide_subcmd(argc, argv, &subcmd);
if (subcmd == SUBCMD_TRANSFER)
err = subcmd_transfer(argc, argv, direction);
else if (subcmd == SUBCMD_LIST)
err = subcmd_list(argc, argv, direction);
else if (subcmd == SUBCMD_VERSION)
print_version(argv[0]);
else
print_help();
if (err < 0)
return EXIT_FAILURE;
return EXIT_SUCCESS;
}