[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