alsa-utils/axfer/subcmd-transfer.c
Takashi Sakamoto ae378611b7 axfer: add an option to dump available hardware parameters
In ALSA PCM interface, before configuring hardware actually, applications
can request available set of hardware parameters for runtime of PCM
substream. The set of parameters are represented and delivered by a
structure.

In alsa-lib PCM API, the above design is abstracted by a series of
snd_pcm_hw_params_xxx() functions. An actual layout of the structure is
hidden from applications by an opaque pointer.

In aplay, '--dump-hw-params' option is for this purpose. With this option,
the command output available set of the hardware parameters.

This commit adds support for the option. Unlike aplay, this commit takes
this program to finish after dumping the parameters for simplicity of
usage.

I note that all of combinations in the set are not necessarily available
when the PCM substream includes dependencies of parameters described by
constraints and rules.

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

465 lines
11 KiB
C

// SPDX-License-Identifier: GPL-2.0
//
// subcmd-transfer.c - operations for transfer sub command.
//
// 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 "subcmd.h"
#include "misc.h"
#include <signal.h>
struct context {
struct xfer_context xfer;
struct mapper_context mapper;
struct container_context *cntrs;
unsigned int cntr_count;
// NOTE: To handling Unix signal.
bool interrupted;
int signal;
};
// NOTE: To handling Unix signal.
static struct context *ctx_ptr;
static void handle_unix_signal_for_finish(int sig)
{
int i;
for (i = 0; i < ctx_ptr->cntr_count; ++i)
ctx_ptr->cntrs[i].interrupted = true;
ctx_ptr->signal = sig;
ctx_ptr->interrupted = true;
}
static void handle_unix_signal_for_suspend(int sig)
{
sigset_t curr, prev;
struct sigaction sa = {0};
// 1. suspend substream.
xfer_context_pause(&ctx_ptr->xfer, true);
// 2. Prepare for default handler(SIG_DFL) of SIGTSTP to stop this
// process.
if (sigaction(SIGTSTP, NULL, &sa) < 0) {
fprintf(stderr, "sigaction(2)\n");
exit(EXIT_FAILURE);
}
if (sa.sa_handler == SIG_ERR)
exit(EXIT_FAILURE);
if (sa.sa_handler == handle_unix_signal_for_suspend)
sa.sa_handler = SIG_DFL;
if (sigaction(SIGTSTP, &sa, NULL) < 0) {
fprintf(stderr, "sigaction(2)\n");
exit(EXIT_FAILURE);
}
// Queue SIGTSTP.
raise(SIGTSTP);
// Release the queued signal from being blocked. This causes an
// additional interrupt for the default handler.
sigemptyset(&curr);
sigaddset(&curr, SIGTSTP);
if (sigprocmask(SIG_UNBLOCK, &curr, &prev) < 0) {
fprintf(stderr, "sigprocmask(2)\n");
exit(EXIT_FAILURE);
}
// 3. SIGCONT is cought and rescheduled. Recover blocking status of
// UNIX signals.
if (sigprocmask(SIG_SETMASK, &prev, NULL) < 0) {
fprintf(stderr, "sigprocmask(2)\n");
exit(EXIT_FAILURE);
}
// Reconfigure this handler for SIGTSTP, instead of default one.
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = handle_unix_signal_for_suspend;
if (sigaction(SIGTSTP, &sa, NULL) < 0) {
fprintf(stderr, "sigaction(2)\n");
exit(EXIT_FAILURE);
}
// 4. Continue the PCM substream.
xfer_context_pause(&ctx_ptr->xfer, false);
}
static int prepare_signal_handler(struct context *ctx)
{
struct sigaction sa = {0};
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = handle_unix_signal_for_finish;
if (sigaction(SIGINT, &sa, NULL) < 0)
return -errno;
if (sigaction(SIGTERM, &sa, NULL) < 0)
return -errno;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = handle_unix_signal_for_suspend;
if (sigaction(SIGTSTP, &sa, NULL) < 0)
return -errno;
ctx_ptr = ctx;
return 0;
}
static int context_init(struct context *ctx, snd_pcm_stream_t direction,
int argc, char *const *argv)
{
const char *xfer_type_literal;
enum xfer_type xfer_type;
int i;
// Decide transfer backend before option parser runs.
xfer_type_literal = NULL;
for (i = 0; i < argc; ++i) {
if (strstr(argv[i], "--xfer-type") != argv[i])
continue;
xfer_type_literal = argv[i] + 12;
}
if (xfer_type_literal == NULL) {
xfer_type = XFER_TYPE_LIBASOUND;
} else {
xfer_type = xfer_type_from_label(xfer_type_literal);
if (xfer_type == XFER_TYPE_UNSUPPORTED) {
fprintf(stderr, "The '%s' xfer type is not supported\n",
xfer_type_literal);
return -EINVAL;
}
}
// Initialize transfer.
return xfer_context_init(&ctx->xfer, xfer_type, direction, argc, argv);
}
static int capture_pre_process(struct context *ctx, snd_pcm_access_t *access,
snd_pcm_uframes_t *frames_per_buffer,
uint64_t *total_frame_count)
{
snd_pcm_format_t sample_format = SND_PCM_FORMAT_UNKNOWN;
unsigned int samples_per_frame = 0;
unsigned int frames_per_second = 0;
unsigned int channels;
int i;
int err;
err = xfer_context_pre_process(&ctx->xfer, &sample_format,
&samples_per_frame, &frames_per_second,
access, frames_per_buffer);
if (err < 0)
return err;
// Prepare for containers.
ctx->cntrs = calloc(ctx->xfer.path_count, sizeof(*ctx->cntrs));
if (ctx->cntrs == NULL)
return -ENOMEM;
ctx->cntr_count = ctx->xfer.path_count;
if (ctx->cntr_count > 1)
channels = 1;
else
channels = samples_per_frame;
*total_frame_count = 0;
for (i = 0; i < ctx->cntr_count; ++i) {
uint64_t frame_count;
err = container_builder_init(ctx->cntrs + i,
ctx->xfer.paths[i],
ctx->xfer.cntr_format,
ctx->xfer.verbose > 1);
if (err < 0)
return err;
err = container_context_pre_process(ctx->cntrs + i,
&sample_format, &channels,
&frames_per_second,
&frame_count);
if (err < 0)
return err;
if (*total_frame_count == 0)
*total_frame_count = frame_count;
if (frame_count < *total_frame_count)
*total_frame_count = frame_count;
}
return 0;
}
static int playback_pre_process(struct context *ctx, snd_pcm_access_t *access,
snd_pcm_uframes_t *frames_per_buffer,
uint64_t *total_frame_count)
{
snd_pcm_format_t sample_format = SND_PCM_FORMAT_UNKNOWN;
unsigned int samples_per_frame = 0;
unsigned int frames_per_second = 0;
int i;
int err;
// Prepare for containers.
ctx->cntrs = calloc(ctx->xfer.path_count, sizeof(*ctx->cntrs));
if (ctx->cntrs == NULL)
return -ENOMEM;
ctx->cntr_count = ctx->xfer.path_count;
for (i = 0; i < ctx->cntr_count; ++i) {
snd_pcm_format_t format;
unsigned int channels;
unsigned int rate;
uint64_t frame_count;
err = container_parser_init(ctx->cntrs + i,
ctx->xfer.paths[i],
ctx->xfer.verbose > 1);
if (err < 0)
return err;
if (i == 0) {
// For a raw container.
format = ctx->xfer.sample_format;
channels = ctx->xfer.samples_per_frame;
rate = ctx->xfer.frames_per_second;
} else {
format = sample_format;
channels = samples_per_frame;
rate = frames_per_second;
}
err = container_context_pre_process(ctx->cntrs + i, &format,
&channels, &rate,
&frame_count);
if (err < 0)
return err;
if (format == SND_PCM_FORMAT_UNKNOWN || channels == 0 ||
rate == 0) {
fprintf(stderr,
"Sample format, channels and rate should be "
"indicated for given files.\n");
return -EINVAL;
}
if (i == 0) {
sample_format = format;
samples_per_frame = channels;
frames_per_second = rate;
*total_frame_count = frame_count;
} else {
if (format != sample_format) {
fprintf(stderr,
"When using several files, they "
"should include the same sample "
"format.\n");
return -EINVAL;
}
// No need to check channels to handle multiple
// containers.
if (rate != frames_per_second) {
fprintf(stderr,
"When using several files, they "
"should include samples at the same "
"sampling rate.\n");
return -EINVAL;
}
if (frame_count < *total_frame_count)
*total_frame_count = frame_count;
}
}
if (ctx->cntr_count > 1)
samples_per_frame = ctx->cntr_count;
// Configure hardware with these parameters.
return xfer_context_pre_process(&ctx->xfer, &sample_format,
&samples_per_frame, &frames_per_second,
access, frames_per_buffer);
}
static int context_pre_process(struct context *ctx, snd_pcm_stream_t direction,
uint64_t *total_frame_count)
{
snd_pcm_access_t access;
snd_pcm_uframes_t frames_per_buffer = 0;
unsigned int bytes_per_sample = 0;
enum mapper_type mapper_type;
int err;
if (direction == SND_PCM_STREAM_CAPTURE) {
mapper_type = MAPPER_TYPE_DEMUXER;
err = capture_pre_process(ctx, &access, &frames_per_buffer,
total_frame_count);
} else {
mapper_type = MAPPER_TYPE_MUXER;
err = playback_pre_process(ctx, &access, &frames_per_buffer,
total_frame_count);
}
if (err < 0)
return err;
// Prepare for mapper.
err = mapper_context_init(&ctx->mapper, mapper_type, ctx->cntr_count,
ctx->xfer.verbose > 1);
if (err < 0)
return err;
bytes_per_sample =
snd_pcm_format_physical_width(ctx->xfer.sample_format) / 8;
if (bytes_per_sample <= 0)
return -ENXIO;
err = mapper_context_pre_process(&ctx->mapper, access, bytes_per_sample,
ctx->xfer.samples_per_frame,
frames_per_buffer, ctx->cntrs);
if (err < 0)
return err;
return 0;
}
static int context_process_frames(struct context *ctx,
snd_pcm_stream_t direction,
uint64_t expected_frame_count,
uint64_t *actual_frame_count)
{
bool verbose = ctx->xfer.verbose > 2;
unsigned int frame_count;
int i;
int err = 0;
if (!ctx->xfer.quiet) {
fprintf(stderr,
"%s: Format '%s', Rate %u Hz, Channels ",
snd_pcm_stream_name(direction),
snd_pcm_format_description(ctx->xfer.sample_format),
ctx->xfer.frames_per_second);
if (ctx->xfer.samples_per_frame == 1)
fprintf(stderr, "'monaural'");
else if (ctx->xfer.samples_per_frame == 2)
fprintf(stderr, "'Stereo'");
else
fprintf(stderr, "%u", ctx->xfer.samples_per_frame);
fprintf(stderr, "\n");
}
*actual_frame_count = 0;
while (!ctx->interrupted) {
struct container_context *cntr;
// Tell remains to expected frame count.
frame_count = expected_frame_count - *actual_frame_count;
err = xfer_context_process_frames(&ctx->xfer, &ctx->mapper,
ctx->cntrs, &frame_count);
if (err < 0) {
if (err == -EAGAIN || err == -EINTR)
continue;
break;
}
if (verbose) {
fprintf(stderr,
" handled: %u\n", frame_count);
}
for (i = 0; i < ctx->cntr_count; ++i) {
cntr = &ctx->cntrs[i];
if (cntr->eof)
break;
}
if (i < ctx->cntr_count)
break;
*actual_frame_count += frame_count;
if (*actual_frame_count >= expected_frame_count)
break;
}
if (!ctx->xfer.quiet) {
fprintf(stderr,
"%s: Expected %lu frames, Actual %lu frames\n",
snd_pcm_stream_name(direction), expected_frame_count,
*actual_frame_count);
if (ctx->interrupted) {
fprintf(stderr, "Aborted by signal: %s\n",
strsignal(ctx->signal));
return 0;
}
}
return err;
}
static void context_post_process(struct context *ctx,
uint64_t accumulated_frame_count)
{
uint64_t total_frame_count;
int i;
xfer_context_post_process(&ctx->xfer);
if (ctx->cntrs) {
for (i = 0; i < ctx->cntr_count; ++i) {
container_context_post_process(ctx->cntrs + i,
&total_frame_count);
container_context_destroy(ctx->cntrs + i);
}
free(ctx->cntrs);
}
mapper_context_post_process(&ctx->mapper);
mapper_context_destroy(&ctx->mapper);
}
static void context_destroy(struct context *ctx)
{
xfer_context_destroy(&ctx->xfer);
}
int subcmd_transfer(int argc, char *const *argv, snd_pcm_stream_t direction)
{
struct context ctx = {0};
uint64_t expected_frame_count = 0;
uint64_t actual_frame_count = 0;
int err = 0;
// Renewed command system.
if (argc > 2 && !strcmp(argv[1], "transfer")) {
// Go ahead to parse file paths properly.
--argc;
++argv;
}
err = prepare_signal_handler(&ctx);
if (err < 0)
return err;
err = context_init(&ctx, direction, argc, argv);
if (err < 0)
goto end;
if (ctx.xfer.help || ctx.xfer.dump_hw_params)
goto end;
err = context_pre_process(&ctx, direction, &expected_frame_count);
if (err < 0)
goto end;
err = context_process_frames(&ctx, direction, expected_frame_count,
&actual_frame_count);
end:
context_post_process(&ctx, actual_frame_count);
context_destroy(&ctx);
return err;
}