In current implementation of aplay, default action is to transfer data frames from/to devices.
This commit adds a file to handle the action. In future commit, this is handled by 'transfer' sub-command. Event loop is included in this file. In the loop, the number of handled data frames is maintained by an appropriate way. As a result, users can stop data transmission frames by frame. --- aplay/Makefile.am | 3 +- aplay/subcmd-transfer.c | 414 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 416 insertions(+), 1 deletion(-) create mode 100644 aplay/subcmd-transfer.c
diff --git a/aplay/Makefile.am b/aplay/Makefile.am index 0113b5c..f604083 100644 --- a/aplay/Makefile.am +++ b/aplay/Makefile.am @@ -42,7 +42,8 @@ aplay_SOURCES = \ xfer-alsa-sched-irq.c \ xfer-alsa-sched-timer.c \ main.h \ - subcmd-list.c + subcmd-list.c \ + subcmd-transfer.c
EXTRA_DIST = aplay.1 arecord.1 EXTRA_CLEAN = arecord diff --git a/aplay/subcmd-transfer.c b/aplay/subcmd-transfer.c new file mode 100644 index 0000000..f163b05 --- /dev/null +++ b/aplay/subcmd-transfer.c @@ -0,0 +1,414 @@ +/* + * subcmd-transfer.c - operations for transfer sub command. + * + * Copyright (c) 2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "xfer.h" +#include "key-event.h" + +#include <signal.h> +#include <gettext.h> + +struct context { + struct xfer_context xfer; + struct aligner_context aligner; + struct container_context *cntrs; + unsigned int cntr_count; + + struct context_options opts; + struct keyevent_context key; + + /* 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. Run default handler(SIG_DFL) for SIGTSTP to stop this process, by + * yielding CPU. + */ + if (sigaction(SIGTSTP, NULL, &sa) < 0) { + printf("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) { + printf("sigaction(2)\n"); + exit(EXIT_FAILURE); + } + + /* Queue SIGTSTP. */ + raise(SIGTSTP); + + /* + * Release the queued signal from being blocked, then yield CPU to the + * default handler. + */ + sigemptyset(&curr); + sigaddset(&curr, SIGTSTP); + if (sigprocmask(SIG_UNBLOCK, &curr, &prev) < 0) { + printf("sigprocmask(2)\n"); + exit(EXIT_FAILURE); + } + + /* + * 3. SIGCONT is cought and rescheduled again, then continue substream. + */ + if (sigprocmask(SIG_SETMASK, &prev, NULL) < 0) { + printf("sigprocmask(2)\n"); + exit(EXIT_FAILURE); + } + + /* Reconfigure this handler for SIGTSTP. */ + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sa.sa_handler = handle_unix_signal_for_suspend; + if (sigaction(SIGTSTP, &sa, NULL) < 0) { + printf("sigaction(2)\n"); + exit(EXIT_FAILURE); + } + + 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; + if (sigaction(SIGABRT, &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) +{ + enum aligner_type type; + int err; + + /* Initialize transfer. */ + err = xfer_context_init(&ctx->xfer, XFER_TYPE_ALSA, direction, + &ctx->opts); + if (err < 0) + return err; + + /* Allocate containers. */ + ctx->cntrs = calloc(ctx->opts.path_count, sizeof(*ctx->cntrs)); + if (ctx->cntrs == NULL) + return -ENOMEM; + ctx->cntr_count = ctx->opts.path_count; + + /* Initialize aligner. */ + if (direction == SND_PCM_STREAM_CAPTURE) + type = ALIGNER_TYPE_DEMUXER; + else + type = ALIGNER_TYPE_MUXER; + err = aligner_context_init(&ctx->aligner, type, ctx->cntr_count, + ctx->opts.verbose); + if (err < 0) + return err; + + return 0; +} + +static uint64_t calculate_expected_frame_count(struct context_options *opts, + unsigned int frames_per_second, + uint64_t total_frame_count) +{ + uint64_t frame_count; + + if (opts->timelimit_seconds > 0) { + frame_count = frames_per_second * opts->timelimit_seconds; + + if (frame_count < total_frame_count) + return frame_count; + } + + return total_frame_count; +} + +static int capture_context_pre_process(struct context *ctx, + uint64_t *total_frame_count) +{ + snd_pcm_access_t access = 0; + snd_pcm_format_t sample_format = SND_PCM_FORMAT_UNKNOWN; + unsigned int samples_per_frame = 0; + unsigned int frames_per_second = 0; + snd_pcm_uframes_t frames_per_buffer = 0; + int i; + int err; + + /* + * Use given options for common parameters. Else, leave the decition to + * each backend. + */ + if (ctx->opts.sample_format != SND_PCM_FORMAT_UNKNOWN) + sample_format = ctx->opts.sample_format; + if (ctx->opts.samples_per_frame > 0) + samples_per_frame = ctx->opts.samples_per_frame; + if (ctx->opts.frames_per_second > 0) + frames_per_second = ctx->opts.frames_per_second; + + err = xfer_context_pre_process(&ctx->xfer, &sample_format, + &samples_per_frame, &frames_per_second, + &access, &frames_per_buffer, + ctx->opts.chmap, ctx->opts.vu_mode); + if (err < 0) + return err; + + /* Normalize paths. */ + if (ctx->opts.path_count != samples_per_frame) { + err = context_options_normalize_paths(&ctx->opts, + ctx->opts.cntr_format, + samples_per_frame); + if (err < 0) + return err; + } + + for (i = 0; i < ctx->cntr_count; ++i) { + err = container_builder_init(ctx->cntrs + i, ctx->opts.paths[i], + ctx->opts.cntr_format, + ctx->opts.verbose); + if (err < 0) + return err; + + err = container_context_pre_process(ctx->cntrs + i, + &sample_format, + &samples_per_frame, + &frames_per_second, + total_frame_count); + if (err < 0) + return err; + } + + err = aligner_context_pre_process(&ctx->aligner, access, sample_format, + samples_per_frame, frames_per_buffer, + ctx->cntrs); + if (err < 0) + return err; + + *total_frame_count = calculate_expected_frame_count(&ctx->opts, + frames_per_second, *total_frame_count); + + return 0; +} + +static int playback_context_pre_process(struct context *ctx, + uint64_t *total_frame_count) +{ + snd_pcm_access_t access = 0; + snd_pcm_format_t sample_format; + unsigned int samples_per_frame; + unsigned int frames_per_second; + snd_pcm_uframes_t frames_per_buffer; + int i; + int err; + + /* Initialize containers to retrieve parameters. */ + for (i = 0; i < ctx->cntr_count; ++i) { + err = container_parser_init(ctx->cntrs + i, ctx->opts.paths[i], + ctx->opts.verbose); + if (err < 0) + return err; + + err = container_context_pre_process(ctx->cntrs + i, + &sample_format, + &samples_per_frame, + &frames_per_second, + total_frame_count); + if (err < 0) + return err; + } + + /* Configure hardware with these parameters. */ + err = xfer_context_pre_process(&ctx->xfer, &sample_format, + &samples_per_frame, &frames_per_second, + &access, &frames_per_buffer, + ctx->opts.chmap, ctx->opts.vu_mode); + if (err < 0) + return err; + + err = aligner_context_pre_process(&ctx->aligner, access, sample_format, + samples_per_frame, frames_per_buffer, + ctx->cntrs); + if (err < 0) + return err; + + *total_frame_count = calculate_expected_frame_count(&ctx->opts, + frames_per_second, *total_frame_count); + + return 0; +} + +static int context_process_frames(struct context *ctx, + uint64_t expected_frame_count, + uint64_t *actual_frame_count) +{ + bool verbose = ctx->opts.verbose; + unsigned int frame_count; + bool emitted; + int i; + int err; + + *actual_frame_count = 0; + while (!ctx->interrupted) { + /* Tell remains to expected frame count. */ + frame_count = expected_frame_count - *actual_frame_count; + err = xfer_context_process_frames(&ctx->xfer, &ctx->aligner, + ctx->cntrs, &frame_count); + if (err < 0) { + if (err == -EAGAIN || err == -EINTR) + continue; + break; + } + /* Perhaps, reach EOF of any container file for playback. */ + if (frame_count == 0) { + struct container_context *cntr; + for (i = 0; i < ctx->cntr_count; ++i) { + cntr = &ctx->cntrs[i]; + if (cntr->type != CONTAINER_TYPE_PARSER && + cntr->eof) + break; + } + } + + *actual_frame_count += frame_count; + if (*actual_frame_count >= expected_frame_count) + break; + } + + if (verbose) { + printf(_("Total frames: expected: %lu, actual: %lu\n"), + expected_frame_count, *actual_frame_count); + } + + return err; +} + +static void context_post_process(struct context *ctx, + uint64_t accumulated_frame_count) +{ + int i; + + for (i = 0; i < ctx->cntr_count; ++i) { + container_context_post_process(ctx->cntrs + i, + accumulated_frame_count); + container_context_destroy(ctx->cntrs + i); + } + + xfer_context_post_process(&ctx->xfer); + + aligner_context_post_process(&ctx->aligner); +} + +static void context_destroy(struct context *ctx) +{ + context_options_destroy(&ctx->opts); + + free(ctx->cntrs); + + aligner_context_destroy(&ctx->aligner); + 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; + 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_options_init(&ctx.opts, argc, argv, direction); + if (err < 0) + return err; + if (ctx.opts.help) { + printf("xfer help.\n"); + return 0; + } + + err = context_init(&ctx, direction); + if (err < 0) + goto end; + + if (direction == SND_PCM_STREAM_CAPTURE) + err = capture_context_pre_process(&ctx, &expected_frame_count); + else + err = playback_context_pre_process(&ctx, &expected_frame_count); + if (err < 0) + goto end; + + err = context_process_frames(&ctx, expected_frame_count, + &actual_frame_count); +end: + context_post_process(&ctx, actual_frame_count); + + context_destroy(&ctx); + + if (ctx.interrupted) { + printf(_("Aborted by signal: %s\n"), + strsignal(ctx.signal)); + return 0; + } + + return err; +} +