[alsa-devel] [RFCv3][PATCH 15/39] axfer: add support to transfer data frames by alsa-lib PCM APIs

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


This commit adds support fo alsa-lib PCM API as a backend of 'xfer'
module. In a set of alsa-lib PCM API, there're two ways to handle data
frames; by calling ioctl(2) with some specific commands and buffer in
user space, or copying data frames on mapped page frames. To support
these two ways, this commit adds operation structure.

Signed-off-by: Takashi Sakamoto <o-takashi at sakamocchi.jp>
---
 axfer/Makefile.am      |   7 +-
 axfer/xfer-libasound.c | 374 +++++++++++++++++++++++++++++++++++++++++++++++++
 axfer/xfer-libasound.h |  45 ++++++
 axfer/xfer.c           |   4 +-
 axfer/xfer.h           |   4 +-
 5 files changed, 428 insertions(+), 6 deletions(-)
 create mode 100644 axfer/xfer-libasound.c
 create mode 100644 axfer/xfer-libasound.h

diff --git a/axfer/Makefile.am b/axfer/Makefile.am
index c1534411..ab4e3ae1 100644
--- a/axfer/Makefile.am
+++ b/axfer/Makefile.am
@@ -20,7 +20,8 @@ noinst_HEADERS = \
 	container.h \
 	mapper.h
 	options.h \
-	xfer.h
+	xfer.h \
+	xfer-alsa.h
 
 axfer_SOURCES = \
 	misc.h \
@@ -40,4 +41,6 @@ axfer_SOURCES = \
 	options.h \
 	options.c \
 	xfer.h \
-	xfer.c
+	xfer.c \
+	xfer-libasound.h \
+	xfer-libasound.c
diff --git a/axfer/xfer-libasound.c b/axfer/xfer-libasound.c
new file mode 100644
index 00000000..66e8e1de
--- /dev/null
+++ b/axfer/xfer-libasound.c
@@ -0,0 +1,374 @@
+/*
+ * xfer-libasound.c - receive/transmit frames by alsa-lib.
+ *
+ * Copyright (c) 2017 Takashi Sakamoto <o-takashi at sakamocchi.jp>
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "xfer-libasound.h"
+#include "misc.h"
+
+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);
+	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 int xfer_libasound_init(struct xfer_context *xfer,
+			       snd_pcm_stream_t direction,
+			       struct context_options *opts)
+{
+	struct libasound_state *state = xfer->private_data;
+	char *node;
+	int err;
+
+	err = snd_output_stdio_attach(&state->log, stderr, 0);
+	if (err < 0)
+		return err;
+
+	state->verbose = xfer->verbose > 1;
+
+	if (opts->node == NULL)
+		node = "default";
+	else
+		node = opts->node;
+
+	err = snd_pcm_open(&state->handle, node, direction, 0);
+	if (err < 0) {
+		logging(state, "Fail to open libasound PCM node for %s: %s\n",
+			snd_pcm_stream_name(direction), node);
+		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;
+
+	/* TODO: Applying NO_PERIOD_WAKEUP should be done here. */
+
+	return set_access_hw_param(state->handle, state->hw_params, opts);
+}
+
+static int configure_hw_params(struct libasound_state *state,
+			       snd_pcm_format_t format,
+			       unsigned int samples_per_frame,
+			       unsigned int frames_per_second)
+{
+	int err;
+
+	/* Configure sample format. */
+	if (format == SND_PCM_FORMAT_UNKNOWN) {
+		snd_pcm_format_mask_t *mask;
+
+		err = snd_pcm_format_mask_malloc(&mask);
+		if (err < 0)
+			return err;
+		snd_pcm_hw_params_get_format_mask(state->hw_params, mask);
+		for (format = 0; format <= SND_PCM_FORMAT_LAST; ++format) {
+			if (snd_pcm_format_mask_test(mask, format))
+				break;
+		}
+		snd_pcm_format_mask_free(mask);
+		if (format > SND_PCM_FORMAT_LAST) {
+			logging(state,
+				"Any sample format is not available.\n");
+			return -EINVAL;
+		}
+	}
+	err = snd_pcm_hw_params_set_format(state->handle, state->hw_params,
+					   format);
+	if (err < 0) {
+		logging(state,
+			_("Sample format '%s' is not available: %s\n"),
+			snd_pcm_format_name(format), snd_strerror(err));
+		return err;
+	}
+
+	/* Configure channels. */
+	if (samples_per_frame == 0) {
+		err = snd_pcm_hw_params_get_channels_min(state->hw_params,
+							 &samples_per_frame);
+		if (err < 0) {
+			logging(state,
+				_("Any channel number is not available.\n"));
+			return err;
+		}
+	}
+	err = snd_pcm_hw_params_set_channels(state->handle, state->hw_params,
+					     samples_per_frame);
+	if (err < 0) {
+		logging(state,
+			_("Channels count '%u' is not available: %s\n"),
+			samples_per_frame, snd_strerror(err));
+		return err;
+	}
+
+	/* Configure rate. */
+	if (frames_per_second == 0) {
+		err = snd_pcm_hw_params_get_rate_min(state->hw_params,
+						     &frames_per_second, NULL);
+		if (err < 0) {
+			logging(state,
+				_("Any rate is not available.\n"));
+			return err;
+		}
+
+	}
+	err = snd_pcm_hw_params_set_rate(state->handle, state->hw_params,
+					 frames_per_second, 0);
+	if (err < 0) {
+		logging(state,
+			_("Sampling rate '%u' is not available: %s\n"),
+			frames_per_second, snd_strerror(err));
+		return err;
+	}
+
+	return snd_pcm_hw_params(state->handle, state->hw_params);
+}
+
+static int retrieve_actual_hw_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 int configure_sw_params(struct libasound_state *state,
+			       unsigned int frames_per_second,
+			       unsigned int frames_per_buffer)
+{
+	return snd_pcm_sw_params(state->handle, state->sw_params);
+}
+
+static int xfer_libasound_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 libasound_state *state = xfer->private_data;
+	int err;
+
+	if (state->handle == NULL)
+		return -ENXIO;
+
+	err = configure_hw_params(state, *format, *samples_per_frame,
+				  *frames_per_second);
+	if (err < 0) {
+		logging(state, _("Current hardware parameters:\n"));
+		snd_pcm_hw_params_dump(state->hw_params, state->log);
+		return err;
+	}
+
+	/* Retrieve actual parameters. */
+	err = retrieve_actual_hw_params(state->hw_params, format,
+					samples_per_frame, frames_per_second,
+					access, frames_per_buffer);
+	if (err < 0)
+		return err;
+
+	/* 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->ops->private_size > 0) {
+		state->private_data = malloc(state->ops->private_size);
+		if (state->private_data == NULL)
+			return -ENOMEM;
+		memset(state->private_data, 0, state->ops->private_size);
+	}
+	err = state->ops->pre_process(state);
+	if (err < 0)
+		return err;
+
+	err = configure_sw_params(state, *frames_per_second,
+				  *frames_per_buffer);
+	if (err < 0) {
+		logging(state, _("Current software parameters:\n"));
+		snd_pcm_sw_params_dump(state->sw_params, state->log);
+		return err;
+	}
+
+	if (xfer->verbose > 0)
+		snd_pcm_dump(state->handle, state->log);
+
+	return 0;
+}
+
+static int xfer_libasound_process_frames(struct xfer_context *xfer,
+					 unsigned int *frame_count,
+					 struct mapper_context *mapper,
+					 struct container_context *cntrs)
+{
+	struct libasound_state *state = xfer->private_data;
+	int err;
+
+	if (state->handle == NULL)
+		return -ENXIO;
+
+	err = state->ops->process_frames(state, frame_count, mapper, cntrs);
+	if (err < 0) {
+		if (err == -EPIPE) {
+			/*
+			 * Recover the stream and continue processing
+			 * immediately. In this program -EPIPE comes from
+			 * libasound implementation instead of file I/O.
+			 */
+
+			err = snd_pcm_prepare(state->handle);
+		}
+
+		if (err < 0) {
+			/*
+			 * TODO: -EIO from libasound for hw PCM node means
+			 * that IRQ disorder. This should be reported to help
+			 * developers for drivers.
+			 */
+			logging(state, "Fail to process frames: %s\n",
+				snd_strerror(err));
+		}
+	}
+
+	return err;
+}
+
+static void xfer_libasound_pause(struct xfer_context *xfer, bool enable)
+{
+	struct libasound_state *state = xfer->private_data;
+	snd_pcm_state_t s = snd_pcm_state(state->handle);
+	int err;
+
+	if (state->handle == NULL)
+		return;
+
+	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 && state->verbose) {
+		logging(state, "snd_pcm_pause(): %s\n", snd_strerror(err));
+	}
+}
+
+static void xfer_libasound_post_process(struct xfer_context *xfer)
+{
+	struct libasound_state *state = xfer->private_data;
+	snd_pcm_state_t pcm_state;
+	int err;
+
+	if (state->handle == NULL)
+		return;
+
+	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)
+				logging(state, "snd_pcm_drop(): %s\n",
+				       snd_strerror(err));
+		} else {
+			err = snd_pcm_drain(state->handle);
+			if (err < 0)
+				logging(state, "snd_pcm_drain(): %s\n",
+				       snd_strerror(err));
+		}
+	}
+
+	err = snd_pcm_hw_free(state->handle);
+	if (err < 0)
+		logging(state, "snd_pcm_hw_free(): %s\n", snd_strerror(err));
+
+	snd_pcm_close(state->handle);
+	state->handle = NULL;
+
+	if (state->ops && state->ops->post_process)
+		state->ops->post_process(state);
+	if (state->private_data)
+		free(state->private_data);
+	state->private_data = NULL;
+}
+
+static void xfer_libasound_destroy(struct xfer_context *xfer)
+{
+	struct libasound_state *state = xfer->private_data;
+
+	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;
+
+	if (state->log)
+		snd_output_close(state->log);
+	state->log = NULL;
+}
+
+const struct xfer_data xfer_libasound = {
+	.ops = {
+		.init		= xfer_libasound_init,
+		.pre_process	= xfer_libasound_pre_process,
+		.process_frames	= xfer_libasound_process_frames,
+		.pause		= xfer_libasound_pause,
+		.post_process	= xfer_libasound_post_process,
+		.destroy	= xfer_libasound_destroy,
+	},
+	.private_size = sizeof(struct libasound_state),
+};
diff --git a/axfer/xfer-libasound.h b/axfer/xfer-libasound.h
new file mode 100644
index 00000000..72807cdd
--- /dev/null
+++ b/axfer/xfer-libasound.h
@@ -0,0 +1,45 @@
+/*
+ * xfer-libasound.h - a header for receiver/transmitter of frames by alsa-lib.
+ *
+ * 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_AXFER_XFER_LIBASOUND__H_
+#define __ALSA_UTILS_AXFER_XFER_LIBASOUND__H_
+
+#include "xfer.h"
+
+#define logging(state, ...) \
+	snd_output_printf(state->log, __VA_ARGS__)
+
+struct xfer_libasound_ops;
+
+struct libasound_state {
+	snd_pcm_t *handle;
+	snd_output_t *log;
+
+	snd_pcm_hw_params_t *hw_params;
+
+	snd_pcm_sw_params_t *sw_params;
+
+	bool running;
+
+	const struct xfer_libasound_ops *ops;
+	void *private_data;
+
+	bool verbose;
+};
+
+struct xfer_libasound_ops {
+	int (*pre_process)(struct libasound_state *state);
+	int (*process_frames)(struct libasound_state *state,
+			      unsigned int *frame_count,
+			      struct mapper_context *mapper,
+			      struct container_context *cntrs);
+	void (*post_process)(struct libasound_state *state);
+	unsigned int private_size;
+};
+
+#endif
diff --git a/axfer/xfer.c b/axfer/xfer.c
index 46b36d42..4677bd5b 100644
--- a/axfer/xfer.c
+++ b/axfer/xfer.c
@@ -12,7 +12,7 @@
 #include <stdio.h>
 
 static const char *const xfer_type_labels[] = {
-	[XFER_TYPE_COUNT] = "",
+	[XFER_TYPE_LIBASOUND] = "libasound",
 };
 
 int xfer_context_init(struct xfer_context *xfer, enum xfer_type type,
@@ -22,7 +22,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_COUNT, NULL},
+		{XFER_TYPE_LIBASOUND, &xfer_libasound},
 	};
 	int i;
 
diff --git a/axfer/xfer.h b/axfer/xfer.h
index 4e3f1f37..b90c005b 100644
--- a/axfer/xfer.h
+++ b/axfer/xfer.h
@@ -13,7 +13,7 @@
 #include "options.h"
 
 enum xfer_type {
-	XFER_TYPE_COUNT,
+	XFER_TYPE_LIBASOUND = 0,
 };
 
 struct xfer_ops;
@@ -66,6 +66,6 @@ struct xfer_data {
 	unsigned int private_size;
 };
 
-extern const struct xfer_data xfer_alsa;
+extern const struct xfer_data xfer_libasound;
 
 #endif
-- 
2.11.0



More information about the Alsa-devel mailing list