[alsa-devel] [RFC][PATCH 14/23] aplay: add an implementation for transferring by ALSA PCM APIs
Takashi Sakamoto
o-takashi at sakamocchi.jp
Thu Aug 17 13:59:55 CEST 2017
This commit adds an implementation of xfer to transfer data frames
via ALSA PCM interface. Actually, APIs in alsa-lib are used. Hardware
and software parameters are configured directly by this implementation.
There're two ways to handle PCM frames; on mapped page frames or call
system call with buffer in user space. For these ways, this
implementation has an abstraction named as 'xfer_alsa_io_ops'.
Furthermore, in a recent decade, timer-based scheduling model is
introduced by PulseAudio developers. To support this model, this
implementation has an abstraction named as 'xfer_alsa_sched_ops'.
---
aplay/Makefile.am | 7 +-
aplay/xfer-alsa.c | 537 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
aplay/xfer-alsa.h | 57 ++++++
aplay/xfer.c | 2 +-
4 files changed, 600 insertions(+), 3 deletions(-)
create mode 100644 aplay/xfer-alsa.c
create mode 100644 aplay/xfer-alsa.h
diff --git a/aplay/Makefile.am b/aplay/Makefile.am
index 9056f56..9108049 100644
--- a/aplay/Makefile.am
+++ b/aplay/Makefile.am
@@ -11,7 +11,8 @@ noinst_HEADERS = \
aligner.h \
waiter.h \
options.h \
- xfer.h
+ xfer.h \
+ xfer-alsa.h
aplay_SOURCES = \
formats.h \
@@ -32,7 +33,9 @@ aplay_SOURCES = \
options.h \
options.c \
xfer.h \
- xfer.c
+ xfer.c \
+ xfer-alsa.h \
+ xfer-alsa.c
EXTRA_DIST = aplay.1 arecord.1
EXTRA_CLEAN = arecord
diff --git a/aplay/xfer-alsa.c b/aplay/xfer-alsa.c
new file mode 100644
index 0000000..4a84153
--- /dev/null
+++ b/aplay/xfer-alsa.c
@@ -0,0 +1,537 @@
+/*
+ * xfer-alsa.c - receive/transmit frames by ALSA.
+ *
+ * Copyright (c) 2017 Takashi Sakamoto <o-takashi at sakamocchi.jp>
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "xfer-alsa.h"
+#include <gettext.h>
+
+static int get_mode(struct context_options *options)
+{
+ int mode = 0;
+
+ if (options->nonblock)
+ mode |= SND_PCM_NONBLOCK;
+ if (options->no_auto_resample)
+ mode |= SND_PCM_NO_AUTO_RESAMPLE;
+ if (options->no_auto_channels)
+ mode |= SND_PCM_NO_AUTO_CHANNELS;
+ if (options->no_auto_format)
+ mode |= SND_PCM_NO_AUTO_FORMAT;
+ if (options->no_softvol)
+ mode |= SND_PCM_NO_SOFTVOL;
+
+ return mode;
+}
+
+static int set_access_hw_param(snd_pcm_t *handle,
+ snd_pcm_hw_params_t *hw_params,
+ struct context_options *opts)
+{
+ snd_pcm_access_mask_t *mask;
+ int err;
+
+ err = snd_pcm_access_mask_malloc(&mask);
+ if (err < 0)
+ return err;
+ snd_pcm_access_mask_none(mask);
+ if (opts->mmap) {
+ snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_INTERLEAVED);
+ snd_pcm_access_mask_set(mask,
+ SND_PCM_ACCESS_MMAP_NONINTERLEAVED);
+ } else {
+ snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_RW_INTERLEAVED);
+ snd_pcm_access_mask_set(mask,
+ SND_PCM_ACCESS_RW_NONINTERLEAVED);
+ }
+ err = snd_pcm_hw_params_set_access_mask(handle, hw_params, mask);
+ snd_pcm_access_mask_free(mask);
+
+ return err;
+}
+
+static void dump_available_hw_params(snd_pcm_hw_params_t *hw_params,
+ const char *const node)
+{
+ unsigned int min_i, max_i;
+ snd_pcm_uframes_t min_l, max_l;
+ snd_pcm_access_mask_t *access_mask;
+ snd_pcm_format_mask_t *format_mask;
+ snd_pcm_subformat_mask_t *subformat_mask;
+ int i;
+ int err;
+
+ printf("Avail params for node: %s\n", node);
+
+ err = snd_pcm_hw_params_get_channels_min(hw_params, &min_i);
+ if (err < 0)
+ return;
+ err = snd_pcm_hw_params_get_channels_max(hw_params, &max_i);
+ if (err < 0)
+ return;
+ printf(" samples/frame: %u to %u\n", min_i, max_i);
+
+ err = snd_pcm_hw_params_get_rate_min(hw_params, &min_i, NULL);
+ if (err < 0)
+ return;
+ err = snd_pcm_hw_params_get_rate_max(hw_params, &max_i, NULL);
+ if (err < 0)
+ return;
+ printf(" frames/second: %u to %u\n", min_i, max_i);
+
+ err = snd_pcm_hw_params_get_period_time_min(hw_params, &min_i, NULL);
+ if (err < 0)
+ return;
+ err = snd_pcm_hw_params_get_period_time_max(hw_params, &max_i, NULL);
+ if (err < 0)
+ return;
+ printf(" msec/period: %u to %u\n", min_i, max_i);
+
+ err = snd_pcm_hw_params_get_period_size_min(hw_params, &min_l, NULL);
+ if (err < 0)
+ return;
+ err = snd_pcm_hw_params_get_period_size_max(hw_params, &max_l, NULL);
+ if (err < 0)
+ return;
+ printf(" frames/period: %lu to %lu\n", min_l, max_l);
+
+ err = snd_pcm_hw_params_get_periods_min(hw_params, &min_i, NULL);
+ if (err < 0)
+ return;
+ err = snd_pcm_hw_params_get_periods_max(hw_params, &max_i, NULL);
+ if (err < 0)
+ return;
+ printf(" periods/buffer: %u to %u\n", min_i, max_i);
+
+ err = snd_pcm_hw_params_get_buffer_time_min(hw_params, &min_i, NULL);
+ if (err < 0)
+ return;
+ err = snd_pcm_hw_params_get_buffer_time_max(hw_params, &max_i, NULL);
+ if (err < 0)
+ return;
+ printf(" msec/buffer: %u to %u\n", min_i, max_i);
+
+ err = snd_pcm_hw_params_get_buffer_size_min(hw_params, &min_l);
+ if (err < 0)
+ return;
+ err = snd_pcm_hw_params_get_buffer_size_max(hw_params, &max_l);
+ if (err < 0)
+ return;
+ printf(" frames/buffer: %lu to %lu\n", min_l, max_l);
+
+ err = snd_pcm_access_mask_malloc(&access_mask);
+ if (err < 0)
+ return;
+ err = snd_pcm_hw_params_get_access_mask(hw_params, access_mask);
+ if (err < 0)
+ return;
+ printf(" supported access methods:\n");
+ for (i = 0; i <= SND_PCM_ACCESS_LAST; ++i) {
+ if (!snd_pcm_access_mask_test(access_mask, i))
+ continue;
+ printf(" '%s'\n", snd_pcm_access_name(i));
+ }
+ snd_pcm_access_mask_free(access_mask);
+
+ err = snd_pcm_format_mask_malloc(&format_mask);
+ if (err < 0)
+ return;
+ snd_pcm_hw_params_get_format_mask(hw_params, format_mask);
+ printf(" supported sample formats:\n");
+ for (i = 0; i <= SND_PCM_FORMAT_LAST; ++i) {
+ if (!snd_pcm_format_mask_test(format_mask, i))
+ continue;
+ printf(" '%s'\n", snd_pcm_format_name(i));
+ }
+ snd_pcm_format_mask_free(format_mask);
+
+ err = snd_pcm_subformat_mask_malloc(&subformat_mask);
+ if (err < 0)
+ return;
+ snd_pcm_hw_params_get_subformat_mask(hw_params, subformat_mask);
+ printf(" supported sample sub-formats:\n");
+ for (i = 0; i <= SND_PCM_SUBFORMAT_LAST; ++i) {
+ if (!snd_pcm_subformat_mask_test(subformat_mask, i))
+ continue;
+ printf(" '%s'\n", snd_pcm_subformat_name(i));
+ }
+ printf("\n");
+}
+
+static int xfer_alsa_init(struct xfer_context *xfer,
+ snd_pcm_stream_t direction,
+ struct context_options *opts)
+{
+ struct alsa_state *state = xfer->private_data;
+ char *node;
+ int mode = 0;
+ int err;
+
+ state->verbose = xfer->verbose;
+
+ if (opts->node == NULL)
+ node = "default";
+ else
+ node = opts->node;
+
+ mode = get_mode(opts);
+ if (mode & SND_PCM_NONBLOCK) {
+ state->waiter = malloc(sizeof(*state->waiter));
+ if (state->waiter == NULL)
+ return -ENOMEM;
+
+ /* TODO: configurable. */
+ err = waiter_context_init(state->waiter, WAITER_TYPE_POLL);
+ if (err < 0)
+ return err;
+ }
+
+ err = snd_pcm_open(&state->handle, node, direction, mode);
+ if (err < 0)
+ return err;
+
+ err = snd_pcm_hw_params_malloc(&state->hw_params);
+ if (err < 0)
+ return err;
+ err = snd_pcm_sw_params_malloc(&state->sw_params);
+ if (err < 0)
+ return err;
+
+ err = snd_pcm_hw_params_any(state->handle, state->hw_params);
+ if (err < 0)
+ return err;
+
+ if (xfer->verbose)
+ dump_available_hw_params(state->hw_params, node);
+
+ return set_access_hw_param(state->handle, state->hw_params, opts);
+}
+
+static int prepare_waiter(struct alsa_state *state)
+{
+ int fd_count;
+ int *fds;
+ struct pollfd *pfds;
+ int i;
+ int err;
+
+ fd_count = snd_pcm_poll_descriptors_count(state->handle);
+
+ fds = calloc(fd_count, sizeof(int));
+ if (fds == NULL)
+ return -ENOMEM;
+
+ pfds = calloc(fd_count, sizeof(struct pollfd));
+ if (pfds == NULL) {
+ free(fds);
+ return -ENOMEM;
+ }
+
+ err = snd_pcm_poll_descriptors(state->handle, pfds, fd_count);
+ if (err < 0)
+ goto end;
+
+ for (i = 0; i < fd_count; ++i)
+ fds[i] = pfds[i].fd;
+
+ err = waiter_context_prepare(state->waiter, fds, fd_count);
+end:
+ free(pfds);
+ free(fds);
+ return err;
+}
+
+static int configure_requested_params(struct alsa_state *state,
+ snd_pcm_format_t format,
+ unsigned int samples_per_frame,
+ unsigned int frames_per_second)
+{
+ int err;
+
+ if (format != SND_PCM_FORMAT_UNKNOWN) {
+ err = snd_pcm_hw_params_set_format(state->handle,
+ state->hw_params, format);
+ if (err < 0) {
+ printf(_("Sample format '%s' is not available: %s\n"),
+ snd_pcm_format_name(format), snd_strerror(err));
+ return err;
+ }
+ }
+
+ if (samples_per_frame > 0) {
+ err = snd_pcm_hw_params_set_channels(state->handle,
+ state->hw_params,
+ samples_per_frame);
+ if (err < 0) {
+ printf(_("Channels count '%u' is not available: %s\n"),
+ samples_per_frame, snd_strerror(err));
+ return err;
+ }
+ }
+
+ if (frames_per_second > 0) {
+ err = snd_pcm_hw_params_set_rate(state->handle,
+ state->hw_params,
+ frames_per_second, 0);
+ if (err < 0) {
+ printf(_("Sampling rate '%u' is not available: %s\n"),
+ frames_per_second, snd_strerror(err));
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static int retrieve_actual_params(snd_pcm_hw_params_t *hw_params,
+ snd_pcm_format_t *format,
+ unsigned int *samples_per_frame,
+ unsigned int *frames_per_second,
+ snd_pcm_access_t *access,
+ snd_pcm_uframes_t *frames_per_buffer)
+{
+ int err;
+
+ err = snd_pcm_hw_params_get_format(hw_params, format);
+ if (err < 0)
+ return err;
+
+ err = snd_pcm_hw_params_get_channels(hw_params,
+ samples_per_frame);
+ if (err < 0)
+ return err;
+
+ err = snd_pcm_hw_params_get_rate(hw_params, frames_per_second,
+ NULL);
+ if (err < 0)
+ return err;
+
+ err = snd_pcm_hw_params_get_access(hw_params, access);
+ if (err < 0)
+ return err;
+
+ return snd_pcm_hw_params_get_buffer_size(hw_params, frames_per_buffer);
+}
+
+static void dump_sw_params(struct alsa_state *state)
+{
+ snd_pcm_uframes_t val_l;
+ int val_i;
+ int err;
+
+ printf("sw params\n");
+
+ err = snd_pcm_sw_params_get_avail_min(state->sw_params, &val_l);
+ if (err < 0)
+ return;
+ printf(" avail-min: %lu\n", val_l);
+
+ err = snd_pcm_sw_params_get_period_event(state->sw_params, &val_i);
+ if (err < 0)
+ return;
+ printf(" period-event: %d\n", val_i);
+
+ err = snd_pcm_sw_params_get_start_threshold(state->sw_params, &val_l);
+ if (err < 0)
+ return;
+ printf(" start-threshold: %lu\n", val_l);
+
+ err = snd_pcm_sw_params_get_stop_threshold(state->sw_params, &val_l);
+ if (err < 0)
+ return;
+ printf(" stop-threshold: %lu\n", val_l);
+
+ err = snd_pcm_sw_params_get_silence_threshold(state->sw_params, &val_l);
+ if (err < 0)
+ return;
+ printf(" silence-threshold: %lu\n", val_l);
+
+ err = snd_pcm_sw_params_get_silence_size(state->sw_params, &val_l);
+ if (err < 0)
+ return;
+ printf(" silence-size: %lu\n", val_l);
+
+ printf("\n");
+}
+
+static int xfer_alsa_pre_process(struct xfer_context *xfer,
+ snd_pcm_format_t *format,
+ unsigned int *samples_per_frame,
+ unsigned int *frames_per_second,
+ snd_pcm_access_t *access,
+ snd_pcm_uframes_t *frames_per_buffer)
+{
+ struct alsa_state *state = xfer->private_data;
+ int err;
+
+ err = configure_requested_params(state, *format, *samples_per_frame,
+ *frames_per_second);
+ if (err < 0)
+ return err;
+
+ /* Configure hardware parameters. */
+ err = snd_pcm_hw_params(state->handle, state->hw_params);
+ if (err < 0)
+ return err;
+
+ /* Retrieve actual parameters. */
+ err = retrieve_actual_params(state->hw_params, format,
+ samples_per_frame, frames_per_second,
+ access, frames_per_buffer);
+ if (err < 0)
+ return err;
+ state->frames_per_buffer = *frames_per_buffer;
+
+ /* Query software parameters. */
+ err = snd_pcm_sw_params_current(state->handle, state->sw_params);
+ if (err < 0)
+ return err;
+
+ /* Assign I/O operation. */
+ if (state->io_ops->private_size > 0) {
+ state->io_private_data = malloc(state->io_ops->private_size);
+ if (state->io_private_data == NULL)
+ return -ENOMEM;
+ memset(state->io_private_data, 0, state->io_ops->private_size);
+ }
+ state->access = *access;
+ err = state->io_ops->pre_process(state);
+ if (err < 0)
+ return err;
+
+ /* Assign scheduling operation. */
+ err = state->sched_ops->pre_process(xfer);
+ if (err < 0)
+ return err;
+
+ err = snd_pcm_sw_params(state->handle, state->sw_params);
+ if (err < 0)
+ return err;
+
+ if (state->waiter) {
+ /*
+ * NOTE: This should be after configuring sw_params due to
+ * timer descriptor.
+ */
+ err = prepare_waiter(state);
+ if (err < 0)
+ return err;
+ }
+
+ if (xfer->verbose)
+ dump_sw_params(state);
+
+ return 0;
+}
+
+static int xfer_alsa_process_frames(struct xfer_context *xfer,
+ unsigned int *frame_count,
+ struct aligner_context *aligner,
+ struct container_context *cntrs)
+{
+ struct alsa_state *state = xfer->private_data;
+ int err;
+ err = state->io_ops->process_frames(state, frame_count, aligner, cntrs);
+ if (err < 0) {
+ if (err == -EPIPE)
+ err = snd_pcm_prepare(state->handle);
+ }
+
+ return err;
+}
+
+static void xfer_alsa_pause(struct xfer_context *xfer, bool enable)
+{
+ struct alsa_state *state = xfer->private_data;
+ snd_pcm_state_t s = snd_pcm_state(state->handle);
+ int err;
+
+ if (enable) {
+ if (s != SND_PCM_STATE_RUNNING)
+ return;
+ } else {
+ if (s != SND_PCM_STATE_PAUSED)
+ return;
+ }
+
+ /* Not supported. Leave the substream to enter XRUN state. */
+ if (!snd_pcm_hw_params_can_pause(state->hw_params))
+ return;
+
+ err = snd_pcm_pause(state->handle, enable);
+ if (err < 0 && xfer->verbose > 0) {
+ printf("snd_pcm_pause(): %s\n",
+ snd_strerror(err));
+ }
+}
+
+static void xfer_alsa_post_process(struct xfer_context *xfer)
+{
+ struct alsa_state *state = xfer->private_data;
+ snd_pcm_state_t pcm_state;
+ int err;
+
+ pcm_state = snd_pcm_state(state->handle);
+ if (pcm_state != SND_PCM_STATE_OPEN &&
+ pcm_state != SND_PCM_STATE_DISCONNECTED) {
+ if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE) {
+ err = snd_pcm_drop(state->handle);
+ if (err < 0)
+ printf("snd_pcm_drop(): %s\n",
+ snd_strerror(err));
+ } else {
+ err = snd_pcm_drain(state->handle);
+ if (err < 0)
+ printf("snd_pcm_drain(): %s\n",
+ snd_strerror(err));
+ }
+ }
+
+ err = snd_pcm_hw_free(state->handle);
+ if (err < 0)
+ printf("snd_pcm_hw_free(): %s\n", snd_strerror(err));
+
+ snd_pcm_close(state->handle);
+ state->handle = NULL;
+
+ if (state->io_ops && state->io_ops->post_process)
+ state->io_ops->post_process(state);
+ if (state->io_private_data)
+ free(state->io_private_data);
+ state->io_private_data = NULL;
+
+ if (state->waiter)
+ waiter_context_release(state->waiter);
+}
+
+static void xfer_alsa_destroy(struct xfer_context *xfer)
+{
+ struct alsa_state *state = xfer->private_data;
+
+ if (state->waiter)
+ waiter_context_destroy(state->waiter);
+ free(state->waiter);
+ state->waiter = NULL;
+
+ if (state->hw_params)
+ snd_pcm_hw_params_free(state->hw_params);
+ if (state->sw_params)
+ snd_pcm_sw_params_free(state->sw_params);
+ state->hw_params = NULL;
+ state->sw_params = NULL;
+}
+
+const struct xfer_data xfer_alsa = {
+ .ops = {
+ .init = xfer_alsa_init,
+ .pre_process = xfer_alsa_pre_process,
+ .process_frames = xfer_alsa_process_frames,
+ .pause = xfer_alsa_pause,
+ .post_process = xfer_alsa_post_process,
+ .destroy = xfer_alsa_destroy,
+ },
+ .private_size = sizeof(struct alsa_state),
+};
diff --git a/aplay/xfer-alsa.h b/aplay/xfer-alsa.h
new file mode 100644
index 0000000..10c417b
--- /dev/null
+++ b/aplay/xfer-alsa.h
@@ -0,0 +1,57 @@
+/*
+ * xfer-alsa.h - a header for receiver/transmitter of frames by ALSA.
+ *
+ * Copyright (c) 2017 Takashi Sakamoto <o-takashi at sakamocchi.jp>
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#ifndef __ALSA_UTILS_APLAY_XFER_ALSA__H_
+#define __ALSA_UTILS_APLAY_XFER_ALSA__H_
+
+#include "xfer.h"
+#include "waiter.h"
+
+enum xfer_alsa_sched_mode {
+ XFER_ALSA_SCHED_MODE_IRQ = 0,
+ XFER_ALSA_SCHED_MODE_TIMER,
+};
+
+struct xfer_alsa_io_ops;
+struct xfer_alsa_sched_ops;
+
+struct alsa_state {
+ snd_pcm_t *handle;
+ snd_pcm_hw_params_t *hw_params;
+ snd_pcm_sw_params_t *sw_params;
+
+ struct waiter_context *waiter;
+
+ snd_pcm_access_t access;
+ snd_pcm_uframes_t frames_per_buffer;
+ bool running;
+
+ const struct xfer_alsa_io_ops *io_ops;
+ void *io_private_data;
+
+ const struct xfer_alsa_sched_ops *sched_ops;
+ void *private_data;
+
+ bool verbose;
+};
+
+struct xfer_alsa_io_ops {
+ int (*pre_process)(struct alsa_state *state);
+ int (*process_frames)(struct alsa_state *state,
+ unsigned int *frame_count,
+ struct aligner_context *aligner,
+ struct container_context *cntrs);
+ void (*post_process)(struct alsa_state *state);
+ unsigned int private_size;
+};
+
+struct xfer_alsa_sched_ops {
+ int (*pre_process)(struct xfer_context *xfer);
+};
+
+#endif
diff --git a/aplay/xfer.c b/aplay/xfer.c
index 3fe094e..977f00a 100644
--- a/aplay/xfer.c
+++ b/aplay/xfer.c
@@ -21,7 +21,7 @@ int xfer_context_init(struct xfer_context *xfer, enum xfer_type type,
enum xfer_type type;
const struct xfer_data *data;
} entries[] = {
- {XFER_TYPE_ALSA, NULL},
+ {XFER_TYPE_ALSA, &xfer_alsa},
};
int i;
--
2.11.0
More information about the Alsa-devel
mailing list