[alsa-devel] [RFCv3][PATCH 24/39] axfer: add support for non-blocking operation

Takashi Sakamoto o-takashi at sakamocchi.jp
Mon Oct 2 02:19:25 CEST 2017


In alsa-lib PCM API, snd_pcm_read[i|n]() and snd_pcm_write[i|n] can be
used with non-blocking mode. This is available when SND_PCM_NONBLOCK is
used as 'mode' argument for a call of snd_pcm_open().

This commit adds support this type of operation. To reduce CPU usage, this
commit uses added waiter interface for event notification.

Signed-off-by: Takashi Sakamoto <o-takashi at sakamocchi.jp>
---
 axfer/options.c               |   5 +-
 axfer/options.h               |   1 +
 axfer/xfer-libasound-irq-rw.c | 104 ++++++++++++++++++++++++++++++++++++++++--
 axfer/xfer-libasound.c        |  79 +++++++++++++++++++++++++++++++-
 axfer/xfer-libasound.h        |   4 ++
 5 files changed, 187 insertions(+), 6 deletions(-)

diff --git a/axfer/options.c b/axfer/options.c
index 91708bb4..a8deaf98 100644
--- a/axfer/options.c
+++ b/axfer/options.c
@@ -289,7 +289,7 @@ void context_options_calculate_duration(struct context_options *opts,
 int context_options_init(struct context_options *opts, int argc,
 			 char *const *argv, snd_pcm_stream_t direction)
 {
-	static const char *s_opts = "hvqd:s:t:ID:f:c:r:";
+	static const char *s_opts = "hvqd:s:t:ID:f:c:r:N";
 	static const struct option l_opts[] = {
 		/* For generic purposes. */
 		{"help",		0, 0, 'h'},
@@ -306,6 +306,7 @@ int context_options_init(struct context_options *opts, int argc,
 		{"format",		1, 0, 'f'},
 		{"channels",		1, 0, 'c'},
 		{"rate",		1, 0, 'r'},
+		{"nonblock",            0, 0, 'N'},
 		/* For debugging. */
 		{"dump-hw-params",	0, 0, OPT_DUMP_HW_PARAMS},
 		{"fatal-errors",	0, 0, OPT_FATAL_ERRORS},
@@ -348,6 +349,8 @@ int context_options_init(struct context_options *opts, int argc,
 			opts->samples_per_frame = parse_l(optarg, &err);
 		else if (c == 'r')
 			opts->frames_per_second = parse_l(optarg, &err);
+		else if (c == 'N')
+			opts->nonblock = true;
 		else if (c == OPT_DUMP_HW_PARAMS)
 			opts->dump_hw_params = true;
 		else if (c == OPT_FATAL_ERRORS)
diff --git a/axfer/options.h b/axfer/options.h
index de9ac998..fc4f03c1 100644
--- a/axfer/options.h
+++ b/axfer/options.h
@@ -33,6 +33,7 @@ struct context_options {
 	snd_pcm_format_t sample_format;
 	unsigned int samples_per_frame;
 	unsigned int frames_per_second;
+	bool nonblock;
 
 	/* For debugging. */
 	bool dump_hw_params;
diff --git a/axfer/xfer-libasound-irq-rw.c b/axfer/xfer-libasound-irq-rw.c
index acbeaee9..afe2d34c 100644
--- a/axfer/xfer-libasound-irq-rw.c
+++ b/axfer/xfer-libasound-irq-rw.c
@@ -169,6 +169,51 @@ error:
 	return err;
 }
 
+static int r_process_frames_nonblocking(struct libasound_state *state,
+					snd_pcm_state_t status,
+					unsigned int *frame_count,
+					struct mapper_context *mapper,
+					struct container_context *cntrs)
+{
+	snd_pcm_sframes_t avail;
+	snd_pcm_uframes_t avail_count;
+	int err = 0;
+
+	if (status != SND_PCM_STATE_RUNNING) {
+		err = snd_pcm_start(state->handle);
+		if (err < 0)
+			goto error;
+	}
+
+	/* Wait for hardware IRQ when no available space. */
+	err = waiter_context_wait_event(state->waiter, -1);
+	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) {
+		/* Let's go to a next iteration. */
+		err = 0;
+		goto error;
+	}
+
+	err = read_frames(state, frame_count, avail_count, mapper, cntrs);
+	if (err < 0)
+		goto error;
+
+	return 0;
+error:
+	*frame_count = 0;
+	return err;
+}
+
 static int write_frames(struct libasound_state *state,
 			unsigned int *frame_count, unsigned int avail_count,
 			struct mapper_context *mapper,
@@ -286,6 +331,50 @@ error:
 	return err;
 }
 
+static int w_process_frames_nonblocking(struct libasound_state *state,
+					snd_pcm_state_t status,
+					unsigned int *frame_count,
+					struct mapper_context *mapper,
+					struct container_context *cntrs)
+{
+	snd_pcm_sframes_t avail;
+	unsigned int avail_count;
+	int err;
+
+	/* Wait for hardware IRQ when no left space. */
+	err = waiter_context_wait_event(state->waiter, -1);
+	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) {
+		/* Let's go to a next iteration. */
+		err = 0;
+		goto error;
+	}
+
+	err = write_frames(state, frame_count, avail_count, mapper, cntrs);
+	if (err < 0)
+		goto error;
+
+	/*
+	 * NOTE: The substream starts automatically when the accumulated number
+	 * of queued data frame exceeds start_threshold.
+	 */
+
+	return 0;
+error:
+	*frame_count = 0;
+	return err;
+}
+
 static int irq_rw_pre_process(struct libasound_state *state)
 {
 	struct frame_cache *cache = state->private_data;
@@ -350,10 +439,17 @@ static int irq_rw_pre_process(struct libasound_state *state)
 		return -ENXIO;
 	}
 
-	if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE)
-		cache->handle = r_process_frames_blocking;
-	else
-		cache->handle = w_process_frames_blocking;
+	if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE) {
+		if (state->nonblock)
+			cache->handle = r_process_frames_nonblocking;
+		else
+			cache->handle = r_process_frames_blocking;
+	} else {
+		if (state->nonblock)
+			cache->handle = w_process_frames_nonblocking;
+		else
+			cache->handle = w_process_frames_blocking;
+	}
 
 	return 0;
 }
diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c
index 7ffbc287..1987b573 100644
--- a/axfer/xfer-libasound.c
+++ b/axfer/xfer-libasound.c
@@ -9,6 +9,16 @@
 #include "xfer-libasound.h"
 #include "misc.h"
 
+static int get_mode(struct context_options *options)
+{
+	int mode = 0;
+
+	if (options->nonblock)
+		mode |= SND_PCM_NONBLOCK;
+
+	return mode;
+}
+
 static int set_access_hw_param(snd_pcm_t *handle,
 			       snd_pcm_hw_params_t *hw_params,
 			       struct context_options *opts)
@@ -34,6 +44,7 @@ static int xfer_libasound_init(struct xfer_context *xfer,
 {
 	struct libasound_state *state = xfer->private_data;
 	char *node;
+	int mode;
 	int err;
 
 	err = snd_output_stdio_attach(&state->log, stderr, 0);
@@ -48,13 +59,25 @@ static int xfer_libasound_init(struct xfer_context *xfer,
 	else
 		node = opts->node;
 
-	err = snd_pcm_open(&state->handle, node, direction, 0);
+	mode = get_mode(opts);
+	err = snd_pcm_open(&state->handle, node, direction, mode);
 	if (err < 0) {
 		logging(state, "Fail to open libasound PCM node for %s: %s\n",
 			snd_pcm_stream_name(direction), node);
 		return err;
 	}
 
+	state->nonblock = !!(mode & SND_PCM_NONBLOCK);
+	if (opts->nonblock) {
+		state->waiter = malloc(sizeof(*state->waiter));
+		if (state->waiter == NULL)
+			return -ENOMEM;
+
+		err = waiter_context_init(state->waiter, WAITER_TYPE_POLL);
+		if (err < 0)
+			return err;
+	}
+
 	err = snd_pcm_hw_params_malloc(&state->hw_params);
 	if (err < 0)
 		return err;
@@ -82,6 +105,40 @@ static int xfer_libasound_init(struct xfer_context *xfer,
 	return set_access_hw_param(state->handle, state->hw_params, opts);
 }
 
+static int prepare_waiter(struct libasound_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_hw_params(struct libasound_state *state,
 			       snd_pcm_format_t format,
 			       unsigned int samples_per_frame,
@@ -254,6 +311,16 @@ static int xfer_libasound_pre_process(struct xfer_context *xfer,
 		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 > 0)
 		snd_pcm_dump(state->handle, state->log);
 
@@ -341,7 +408,12 @@ static void xfer_libasound_post_process(struct xfer_context *xfer)
 				logging(state, "snd_pcm_drop(): %s\n",
 				       snd_strerror(err));
 		} else {
+			/* TODO: this is a bug in kernel land. */
+			if (state->nonblock)
+				snd_pcm_nonblock(state->handle, 0);
 			err = snd_pcm_drain(state->handle);
+			if (state->nonblock)
+				snd_pcm_nonblock(state->handle, 1);
 			if (err < 0)
 				logging(state, "snd_pcm_drain(): %s\n",
 				       snd_strerror(err));
@@ -366,6 +438,11 @@ static void xfer_libasound_destroy(struct xfer_context *xfer)
 {
 	struct libasound_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)
diff --git a/axfer/xfer-libasound.h b/axfer/xfer-libasound.h
index 25768c41..342651d1 100644
--- a/axfer/xfer-libasound.h
+++ b/axfer/xfer-libasound.h
@@ -10,6 +10,7 @@
 #define __ALSA_UTILS_AXFER_XFER_LIBASOUND__H_
 
 #include "xfer.h"
+#include "waiter.h"
 
 #define logging(state, ...) \
 	snd_output_printf(state->log, __VA_ARGS__)
@@ -25,6 +26,9 @@ struct libasound_state {
 	snd_pcm_sw_params_t *sw_params;
 
 	bool running;
+	bool nonblock;
+
+	struct waiter_context *waiter;
 
 	const struct xfer_libasound_ops *ops;
 	void *private_data;
-- 
2.11.0



More information about the Alsa-devel mailing list