diff mbox

[RFC,15/23] aplay: add implementation of I/O

Message ID 20170817120004.15326-16-o-takashi@sakamocchi.jp (mailing list archive)
State New, archived
Headers show

Commit Message

Takashi Sakamoto Aug. 17, 2017, 11:59 a.m. UTC
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 mbox

Patch

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@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@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