[alsa-devel] [RFC][PATCH 15/23] aplay: add implementation of I/O
Takashi Sakamoto
o-takashi at sakamocchi.jp
Thu Aug 17 13:59:56 CEST 2017
This commit adds I/O implementation for 'mmap' and 'rw' operations.
In 'mmap' operation, a pointer to mapped page frame is given to
'aligner' function to copy file data directly. In 'rw' operations,
this implementation has buffers to cache PCM frames to absorb a case
that requested number of frames are not fully handled.
---
aplay/Makefile.am | 4 +-
aplay/xfer-alsa-io-mmap.c | 134 +++++++++++++++++
aplay/xfer-alsa-io-rw.c | 368 ++++++++++++++++++++++++++++++++++++++++++++++
aplay/xfer-alsa.c | 12 ++
aplay/xfer-alsa.h | 4 +
5 files changed, 521 insertions(+), 1 deletion(-)
create mode 100644 aplay/xfer-alsa-io-mmap.c
create mode 100644 aplay/xfer-alsa-io-rw.c
diff --git a/aplay/Makefile.am b/aplay/Makefile.am
index 9108049..9d5e044 100644
--- a/aplay/Makefile.am
+++ b/aplay/Makefile.am
@@ -35,7 +35,9 @@ aplay_SOURCES = \
xfer.h \
xfer.c \
xfer-alsa.h \
- xfer-alsa.c
+ xfer-alsa.c \
+ xfer-alsa-io-mmap.c \
+ xfer-alsa-io-rw.c
EXTRA_DIST = aplay.1 arecord.1
EXTRA_CLEAN = arecord
diff --git a/aplay/xfer-alsa-io-mmap.c b/aplay/xfer-alsa-io-mmap.c
new file mode 100644
index 0000000..7ddb880
--- /dev/null
+++ b/aplay/xfer-alsa-io-mmap.c
@@ -0,0 +1,134 @@
+/*
+ * xfer-alsa-io-mmap.c - I/O helper for SND_PCM_ACCESS_MMAP_XXX.
+ *
+ * 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"
+
+static int alsa_mmap_pre_process(struct alsa_state *state)
+{
+ const snd_pcm_channel_area_t *areas;
+ snd_pcm_uframes_t frame_offset;
+ snd_pcm_uframes_t avail = 0;
+ unsigned int samples_per_frame;
+ int i;
+ int err;
+
+ if (state->verbose) {
+ err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset,
+ &avail);
+ if (err < 0)
+ return err;
+
+ err = snd_pcm_hw_params_get_channels(state->hw_params,
+ &samples_per_frame);
+ if (err < 0)
+ return err;
+
+ printf("attributes for mapped page frame:\n");
+ for (i = 0; i < samples_per_frame; ++i) {
+ const snd_pcm_channel_area_t *area = areas + i;
+ printf(" sample number: %d\n", i);
+ printf(" address: %p\n", area->addr);
+ printf(" bits for offset: %u\n", area->first);
+ printf(" bits/frame: %u\n", area->step);
+ }
+ printf("\n");
+ }
+ return 0;
+}
+
+static int alsa_mmap_process_frames(struct alsa_state *state,
+ unsigned *frame_count,
+ struct aligner_context *aligner,
+ struct container_context *cntrs)
+{
+ const snd_pcm_channel_area_t *areas;
+ snd_pcm_uframes_t frame_offset;
+ snd_pcm_uframes_t avail;
+ unsigned int avail_count;
+ char *frame_buf;
+ int err;
+
+ /* For capture direction, need to start stream explicitly. */
+ if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE) {
+ snd_pcm_state_t s = snd_pcm_state(state->handle);
+ if (s != SND_PCM_STATE_RUNNING) {
+ if (s != SND_PCM_STATE_PREPARED) {
+ err = -EPIPE;
+ goto error;
+ }
+
+ err = snd_pcm_start(state->handle);
+ if (err < 0)
+ goto error;
+ }
+ }
+
+ if (state->waiter) {
+ err = waiter_context_wait_event(state->waiter);
+ if (err < 0)
+ goto error;
+ }
+
+ avail = *frame_count;
+ err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset, &avail);
+ if (err < 0)
+ goto error;
+
+ /* Trim according up to expected frame count. */
+ if (*frame_count < avail)
+ avail_count = *frame_count;
+ else
+ avail_count = (unsigned int)avail;
+
+ frame_buf = areas[0].addr;
+ /* TODO: What's going on non-interleaved buffer??? */
+ frame_buf += snd_pcm_frames_to_bytes(state->handle, frame_offset);
+ err = aligner_context_process_frames(aligner, frame_buf, &avail_count,
+ cntrs);
+ if (err < 0)
+ goto error;
+ if (avail_count == 0)
+ goto error;
+
+ err = snd_pcm_mmap_commit(state->handle, frame_offset, avail_count);
+ if (err < 0)
+ goto error;
+
+ /* Need to start playback stream explicitly */
+ if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_PLAYBACK) {
+ snd_pcm_state_t s = snd_pcm_state(state->handle);
+ if (s != SND_PCM_STATE_RUNNING) {
+ if (s != SND_PCM_STATE_PREPARED) {
+ err = -EPIPE;
+ goto error;
+ }
+
+ err = snd_pcm_start(state->handle);
+ if (err < 0)
+ goto error;
+ }
+ }
+
+ *frame_count = avail_count;
+
+ return 0;
+error:
+ *frame_count = 0;
+ return err;
+}
+
+static void alsa_mmap_post_process(struct alsa_state *state)
+{
+ return;
+}
+
+const struct xfer_alsa_io_ops xfer_alsa_mmap_ops = {
+ .pre_process = alsa_mmap_pre_process,
+ .process_frames = alsa_mmap_process_frames,
+ .post_process = alsa_mmap_post_process,
+};
diff --git a/aplay/xfer-alsa-io-rw.c b/aplay/xfer-alsa-io-rw.c
new file mode 100644
index 0000000..a69d7e0
--- /dev/null
+++ b/aplay/xfer-alsa-io-rw.c
@@ -0,0 +1,368 @@
+/*
+ * xfer-alsa-io-rw.c - I/O helper for SND_PCM_ACCESS_RW_XXX.
+ *
+ * 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"
+
+struct frame_cache {
+ void *buf;
+ void *buf_ptr;
+
+ unsigned int bytes_per_sample;
+ unsigned int samples_per_frame;
+ unsigned int frames_per_buffer;
+ snd_pcm_access_t access;
+
+ void (*align_frames)(struct frame_cache *cache,
+ unsigned int avail_count,
+ unsigned int consumed_count,
+ unsigned int bytes_per_sample,
+ unsigned int samples_per_frame);
+
+ unsigned int remained_count;
+};
+
+static void align_frames_in_i(struct frame_cache *cache,
+ unsigned int avail_count,
+ unsigned int consumed_count,
+ unsigned int bytes_per_sample,
+ unsigned int samples_per_frame)
+{
+ char *buf = cache->buf;
+ unsigned int offset;
+ unsigned int size;
+
+ cache->remained_count = avail_count - consumed_count;
+
+ if (cache->remained_count == 0) {
+ cache->buf_ptr = buf;
+ } else {
+ offset = bytes_per_sample * samples_per_frame * consumed_count;
+ size = bytes_per_sample * samples_per_frame *
+ cache->remained_count;
+ if (offset > 0)
+ memcpy(buf, buf + offset, size);
+ cache->buf_ptr = buf + size;
+ }
+}
+
+static void align_frames_in_n(struct frame_cache *cache,
+ unsigned int avail_count,
+ unsigned int consumed_count,
+ unsigned int bytes_per_sample,
+ unsigned int samples_per_frame)
+{
+ char **bufs = cache->buf;
+ char **buf_ptrs = cache->buf_ptr;
+ char *buf;
+ unsigned int offset;
+ unsigned int size;
+ int i;
+
+ cache->remained_count = avail_count - consumed_count;
+
+ if (cache->remained_count == 0) {
+ for (i = 0; i < samples_per_frame; ++i)
+ buf_ptrs[i] = bufs[i];
+ return;
+ }
+
+ for (i = 0; i < samples_per_frame; ++i) {
+ buf = bufs[i];
+ offset = bytes_per_sample * consumed_count;
+ size = bytes_per_sample * cache->remained_count;
+ memcpy(buf, buf + offset, size);
+ buf_ptrs[i] = buf + size;
+ }
+}
+
+static int alsa_rw_pre_process(struct alsa_state *state)
+{
+ struct frame_cache *cache = state->io_private_data;
+ snd_pcm_format_t format;
+ snd_pcm_uframes_t frames_per_buffer;
+ uint64_t bytes_per_buffer;
+ int err;
+
+ err = snd_pcm_hw_params_get_format(state->hw_params, &format);
+ if (err < 0)
+ return err;
+ cache->bytes_per_sample = snd_pcm_format_physical_width(format);
+
+ err = snd_pcm_hw_params_get_channels(state->hw_params,
+ &cache->samples_per_frame);
+ if (err < 0)
+ return err;
+
+ err = snd_pcm_hw_params_get_buffer_size(state->hw_params,
+ &frames_per_buffer);
+ if (err < 0)
+ return err;
+ cache->frames_per_buffer = (unsigned int)frames_per_buffer;
+
+ /* Allocate buffer and assign callback for alignment. */
+ err = snd_pcm_hw_params_get_access(state->hw_params, &cache->access);
+ if (err < 0)
+ return err;
+ if (cache->access == SND_PCM_ACCESS_RW_INTERLEAVED) {
+ bytes_per_buffer = cache->bytes_per_sample *
+ cache->samples_per_frame *
+ cache->frames_per_buffer;
+ cache->buf = malloc(bytes_per_buffer);
+ if (cache->buf == NULL)
+ return -ENOMEM;
+ cache->buf_ptr = cache->buf;
+ cache->align_frames = align_frames_in_i;
+ } else if (cache->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
+ char **bufs;
+ char **buf_ptrs;
+ int i;
+ bufs = calloc(cache->samples_per_frame * 2, sizeof(*bufs));
+ if (bufs == NULL)
+ return -ENOMEM;
+ buf_ptrs = bufs + cache->samples_per_frame * sizeof(*bufs);
+ for (i = 0; i < cache->samples_per_frame; ++i) {
+ bytes_per_buffer = cache->bytes_per_sample *
+ cache->frames_per_buffer;
+ bufs[i] = malloc(bytes_per_buffer);
+ if (bufs[i] == NULL)
+ return -ENOMEM;
+ buf_ptrs[i] = bufs[i];
+ }
+ cache->buf = bufs;
+ cache->buf_ptr = buf_ptrs;
+ cache->align_frames = align_frames_in_n;
+ } else {
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static int alsa_r_process_frames(struct alsa_state *state,
+ unsigned int *frame_count,
+ struct aligner_context *aligner,
+ struct container_context *cntrs)
+{
+ struct frame_cache *cache = state->io_private_data;
+ snd_pcm_sframes_t avail;
+ snd_pcm_uframes_t avail_count;
+ unsigned int consumed_count;
+ int err = 0;
+
+ /* In non-blocking mode, work with I/O notifier. */
+ if (state->waiter) {
+ snd_pcm_state_t s = snd_pcm_state(state->handle);
+ if (s != SND_PCM_STATE_RUNNING) {
+ if (s != SND_PCM_STATE_PREPARED) {
+ err = -EPIPE;
+ goto error;
+ }
+
+ err = snd_pcm_start(state->handle);
+ if (err < 0)
+ goto error;
+ }
+
+ err = waiter_context_wait_event(state->waiter);
+ if (err < 0)
+ goto error;
+ }
+
+ /* Check available space on the buffer. */
+ avail = snd_pcm_avail(state->handle);
+ if (avail < 0) {
+ err = avail;
+ goto error;
+ }
+ avail_count = (snd_pcm_uframes_t)avail;
+
+ if (avail_count == 0) {
+ /* In non-blocking operation, let's go to a next iteration. */
+ if (state->waiter) {
+ err = -EAGAIN;
+ goto error;
+ }
+
+ /* Prepare for blocking this process till 'avail_min'. */
+ err = snd_pcm_sw_params_get_avail_min(state->sw_params,
+ &avail_count);
+ if (err < 0)
+ goto error;
+ }
+
+ /* Trim according up to expected frame count. */
+ if (*frame_count < avail_count)
+ avail_count = *frame_count;
+
+ /* Cache required amount of frames. */
+ if (avail_count > cache->remained_count) {
+ avail_count -= cache->remained_count;
+
+ /*
+ * Execute write operation according to the shape of buffer.
+ * These operations automatically start the substream.
+ */
+ if (cache->access == SND_PCM_ACCESS_RW_INTERLEAVED) {
+ err = snd_pcm_readi(state->handle, cache->buf_ptr,
+ avail_count);
+ } else {
+ err = snd_pcm_readn(state->handle, cache->buf_ptr,
+ avail_count);
+ }
+ if (err < 0)
+ goto error;
+ cache->remained_count += err;
+ avail_count = cache->remained_count;
+ }
+
+ /* Write out to file descriptors. */
+ consumed_count = avail_count;
+ err = aligner_context_process_frames(aligner, cache->buf,
+ &consumed_count, cntrs);
+ if (err < 0)
+ goto error;
+ cache->align_frames(cache, cache->remained_count, consumed_count,
+ cache->bytes_per_sample, cache->samples_per_frame);
+
+ *frame_count = consumed_count;
+
+ return 0;
+error:
+ *frame_count = 0;
+ return err;
+}
+
+static int alsa_w_process_frames(struct alsa_state *state,
+ unsigned *frame_count,
+ struct aligner_context *aligner,
+ struct container_context *cntrs)
+{
+ struct frame_cache *cache = state->io_private_data;
+ snd_pcm_sframes_t avail;
+ unsigned int avail_count;
+ snd_pcm_uframes_t consumed_count;
+ int err;
+
+ /* In non-blocking mode, work with I/O notifier. */
+ if (state->waiter) {
+ snd_pcm_state_t s = snd_pcm_state(state->handle);
+ /* Need to recover the stream. */
+ if (s != SND_PCM_STATE_RUNNING && s != SND_PCM_STATE_PREPARED) {
+ err = -EPIPE;
+ goto error;
+ }
+
+ err = waiter_context_wait_event(state->waiter);
+ if (err < 0)
+ goto error;
+ }
+
+ /* Check available space on the buffer. */
+ avail = snd_pcm_avail(state->handle);
+ if (avail < 0) {
+ err = avail;
+ goto error;
+ }
+ avail_count = (unsigned int)avail;
+
+ if (avail_count == 0) {
+ /* In non-blocking operation, let's go to a next iteration. */
+ if (state->waiter) {
+ err = 0;
+ goto error;
+ }
+
+ if (snd_pcm_state(state->handle) == SND_PCM_STATE_RUNNING) {
+ snd_pcm_uframes_t avail_min;
+ err = snd_pcm_sw_params_get_avail_min(state->sw_params,
+ &avail_min);
+ if (err < 0)
+ goto error;
+ avail_count = (unsigned int)avail_min;
+ } else {
+ /* NOTE: precisely, start_threshold is important. */
+ avail_count = state->frames_per_buffer;
+ }
+ }
+
+ /* Trim according up to expected frame count. */
+ if (*frame_count < avail_count)
+ avail_count = *frame_count;
+
+ /* Cache required amount of frames. */
+ if (avail_count > cache->remained_count) {
+ avail_count -= cache->remained_count;
+
+ /* Read frames to transfer. */
+ err = aligner_context_process_frames(aligner, cache->buf_ptr,
+ &avail_count, cntrs);
+ if (err < 0)
+ goto error;
+ cache->remained_count += avail_count;
+ avail_count = cache->remained_count;
+ }
+
+ /*
+ * Execute write operation according to the shape of buffer. These
+ * operations automatically start the stream.
+ */
+ consumed_count = avail_count;
+ if (cache->access == SND_PCM_ACCESS_RW_INTERLEAVED)
+ err = snd_pcm_writei(state->handle, cache->buf, consumed_count);
+ else
+ err = snd_pcm_writen(state->handle, cache->buf, consumed_count);
+ if (err < 0)
+ goto error;
+ cache->align_frames(cache, cache->remained_count, (unsigned int)err,
+ cache->bytes_per_sample, cache->samples_per_frame);
+
+ *frame_count = consumed_count;
+
+ return 0;
+error:
+ *frame_count = 0;
+ return err;
+}
+
+static void alsa_rw_post_process(struct alsa_state *state)
+{
+ struct frame_cache *cache = state->io_private_data;
+
+ if (cache->buf == NULL) {
+ if (cache->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
+ char **bufs = cache->buf;
+ char **buf_ptrs = cache->buf_ptr;
+ int i;
+ for (i = 0; i < cache->samples_per_frame; ++i) {
+ if (bufs[i])
+ free(bufs[i]);
+ bufs[i] = NULL;
+ buf_ptrs[i] = NULL;
+ }
+
+ }
+ free(cache->buf);
+ free(cache->buf_ptr);
+ }
+ cache->buf = NULL;
+ cache->buf_ptr = NULL;
+}
+
+const struct xfer_alsa_io_ops xfer_alsa_r_ops = {
+ .pre_process = alsa_rw_pre_process,
+ .process_frames = alsa_r_process_frames,
+ .post_process = alsa_rw_post_process,
+ .private_size = sizeof(struct frame_cache),
+};
+
+const struct xfer_alsa_io_ops xfer_alsa_w_ops = {
+ .pre_process = alsa_rw_pre_process,
+ .process_frames = alsa_w_process_frames,
+ .post_process = alsa_rw_post_process,
+ .private_size = sizeof(struct frame_cache),
+};
diff --git a/aplay/xfer-alsa.c b/aplay/xfer-alsa.c
index 4a84153..57be488 100644
--- a/aplay/xfer-alsa.c
+++ b/aplay/xfer-alsa.c
@@ -391,6 +391,18 @@ static int xfer_alsa_pre_process(struct xfer_context *xfer,
return err;
/* Assign I/O operation. */
+ if (*access == SND_PCM_ACCESS_MMAP_INTERLEAVED ||
+ *access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) {
+ state->io_ops = &xfer_alsa_mmap_ops;
+ } else if (*access == SND_PCM_ACCESS_RW_INTERLEAVED ||
+ *access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
+ if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE)
+ state->io_ops = &xfer_alsa_r_ops;
+ else
+ state->io_ops = &xfer_alsa_w_ops;
+ } else {
+ return -ENXIO;
+ }
if (state->io_ops->private_size > 0) {
state->io_private_data = malloc(state->io_ops->private_size);
if (state->io_private_data == NULL)
diff --git a/aplay/xfer-alsa.h b/aplay/xfer-alsa.h
index 10c417b..1731f04 100644
--- a/aplay/xfer-alsa.h
+++ b/aplay/xfer-alsa.h
@@ -54,4 +54,8 @@ struct xfer_alsa_sched_ops {
int (*pre_process)(struct xfer_context *xfer);
};
+extern const struct xfer_alsa_io_ops xfer_alsa_mmap_ops;
+extern const struct xfer_alsa_io_ops xfer_alsa_r_ops;
+extern const struct xfer_alsa_io_ops xfer_alsa_w_ops;
+
#endif
--
2.11.0
More information about the Alsa-devel
mailing list