summaryrefslogtreecommitdiffstats
path: root/alsa-pcm.c
diff options
context:
space:
mode:
authorJames O'Shannessy <james.oshannessy@fiberdyne.com.au>2018-08-27 15:08:14 +1000
committerMark Farrugia <mark.farrugia@fiberdyne.com.au>2018-10-26 17:27:24 +1100
commitbc8c3a602bceaba0e6d34a1ba8b776b56b00d766 (patch)
treeae8cec69c910144611e06f272033cc8c2aee7032 /alsa-pcm.c
parent416c9b0f9b816a6b2eb5c544f21567ad286dd4be (diff)
Public push of AVIRT.
Follow readme for building in/out of tree for Ubuntu/AGL/etc. Signed-off-by: James O'Shannessy <james.oshannessy@fiberdyne.com.au>
Diffstat (limited to 'alsa-pcm.c')
-rw-r--r--alsa-pcm.c381
1 files changed, 381 insertions, 0 deletions
diff --git a/alsa-pcm.c b/alsa-pcm.c
new file mode 100644
index 0000000..bd705f4
--- /dev/null
+++ b/alsa-pcm.c
@@ -0,0 +1,381 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * ALSA Virtual Soundcard
+ *
+ * alsa-pcm.c - ALSA PCM implementation
+ *
+ * Copyright (C) 2010-2018 Fiberdyne Systems Pty Ltd
+ */
+
+#include "core.h"
+#include "alsa.h"
+
+#define DO_AUDIOPATH_CB(callback, substream, ...) \
+ do { \
+ struct avirt_audiopath *ap; \
+ ap = avirt_get_current_audiopath(); \
+ CHK_NULL_V(ap, "Cannot find Audio Path!"); \
+ if (ap->pcm_ops->callback) { \
+ return ap->pcm_ops->callback(substream, \
+ ##__VA_ARGS__); \
+ } \
+ } while (0)
+
+/**
+ * configure_pcm - set up substream properties from user configuration
+ * @substream: pointer to ALSA PCM substream
+ * @return 0 on success or error code otherwise
+ */
+static int configure_pcm(struct snd_pcm_substream *substream)
+{
+ struct avirt_alsa_dev_config *config;
+ struct avirt_audiopath *audiopath;
+ struct avirt_alsa_dev_group *group;
+ struct snd_pcm_hardware *hw;
+ unsigned bytes_per_sample = 0, blocksize = 0;
+
+ audiopath = avirt_get_current_audiopath();
+ CHK_NULL_V(audiopath, "Cannot find Audio Path!");
+
+ blocksize = audiopath->blocksize;
+
+ // Copy the hw params from the audiopath to the pcm
+ hw = &substream->runtime->hw;
+ memcpy(hw, audiopath->hw, sizeof(struct snd_pcm_hardware));
+ pr_info("%s %d %d", __func__, blocksize, hw->periods_max);
+
+ if (hw->formats == SNDRV_PCM_FMTBIT_S16_LE)
+ bytes_per_sample = 2;
+ else {
+ pr_err("[%s] PCM only supports SNDRV_PCM_FMTBIT_S16_LE",
+ __func__);
+ return -EINVAL;
+ }
+
+ // Get device group (playback/capture)
+ group = avirt_alsa_get_dev_group(substream->stream);
+ CHK_NULL(group);
+
+ // Check if substream id is valid
+ if (substream->pcm->device >= group->devices)
+ return -1;
+
+ // Setup remaining hw properties
+ config = &group->config[substream->pcm->device];
+ hw->channels_min = config->channels;
+ hw->channels_max = config->channels;
+ hw->buffer_bytes_max = blocksize * hw->periods_max * bytes_per_sample *
+ config->channels;
+ hw->period_bytes_min = blocksize * bytes_per_sample * config->channels;
+ hw->period_bytes_max = blocksize * bytes_per_sample * config->channels;
+
+ return 0;
+}
+
+/*******************************************************************************
+ * ALSA PCM Callbacks
+ ******************************************************************************/
+/**
+ * pcm_open - Implements 'open' callback for PCM middle layer
+ * @substream: pointer to ALSA PCM substream
+ *
+ * This is called when an ALSA PCM substream is opened. The substream device is
+ * configured here.
+ *
+ * Returns 0 on success or error code otherwise.
+ */
+static int pcm_open(struct snd_pcm_substream *substream)
+{
+ // Setup the pcm device based on the configuration assigned
+ CHK_ERR_V(configure_pcm(substream), "Failed to setup pcm device");
+
+ // Do additional Audio Path 'open' callback
+ DO_AUDIOPATH_CB(open, substream);
+
+ return 0;
+}
+
+/**
+ * pcm_close - Implements 'close' callback for PCM middle layer
+ * @substream: pointer to ALSA PCM substream
+ *
+ * This is called when a PCM substream is closed.
+ *
+ * Returns 0 on success or error code otherwise.
+ */
+static int pcm_close(struct snd_pcm_substream *substream)
+{
+ // Do additional Audio Path 'close' callback
+ DO_AUDIOPATH_CB(close, substream);
+
+ return 0;
+}
+
+/**
+ * pcm_hw_params - Implements 'hw_params' callback for PCM middle layer
+ * @substream: pointer to ALSA PCM substream
+ * @hw_params: contains the hardware parameters for the PCM
+ *
+ * This is called when the hardware parameters are set, including buffer size,
+ * the period size, the format, etc. The buffer allocation should be done here.
+ *
+ * Returns 0 on success or error code otherwise.
+ */
+static int pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ int channels, err;
+ size_t bufsz;
+ struct avirt_audiopath *audiopath;
+ struct avirt_alsa_dev_group *group;
+
+ group = avirt_alsa_get_dev_group(substream->stream);
+ CHK_NULL(group);
+
+ channels = group->config[substream->pcm->device].channels;
+
+ if ((params_channels(hw_params) > channels) ||
+ (params_channels(hw_params) < channels)) {
+ pr_err("Requested number of channels not supported.\n");
+ return -EINVAL;
+ }
+
+ audiopath = avirt_get_current_audiopath();
+ CHK_NULL_V(audiopath, "Cannot find Audio Path!");
+
+ bufsz = params_buffer_bytes(hw_params) * audiopath->hw->periods_max;
+
+ err = snd_pcm_lib_alloc_vmalloc_buffer(substream, bufsz);
+ if (err <= 0) {
+ pr_err("pcm: buffer allocation failed (%d)\n", err);
+ return err;
+ }
+
+ // Do additional Audio Path 'hw_params' callback
+ // DO_AUDIOPATH_CB(hw_params, substream, hw_params);
+
+ return 0;
+}
+
+/**
+ * pcm_hw_free - Implements 'hw_free' callback for PCM middle layer
+ * @substream: pointer to ALSA PCM substream
+ *
+ * Release the resources allocated via 'hw_params'.
+ * This function is always called before the 'close' callback .
+ *
+ * Returns 0 on success or error code otherwise.
+ */
+static int pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ CHK_ERR(snd_pcm_lib_free_vmalloc_buffer(substream));
+
+ // Do additional Audio Path 'hw_free' callback
+ // DO_AUDIOPATH_CB(hw_free, substream);
+
+ return 0;
+}
+
+/**
+ * pcm_prepare - Implements 'prepare' callback for PCM middle layer
+ * @substream: pointer to ALSA PCM substream
+ *
+ * The format rate, sample rate, etc., can be set here. This callback can be
+ * called many times at each setup. This function is also used to handle overrun
+ * and underrun conditions when we try and resync the DMA (if we're using DMA).
+ *
+ * Returns 0 on success or error code otherwise.
+ */
+static int pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct avirt_alsa_dev_group *group;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ group = avirt_alsa_get_dev_group(substream->stream);
+ CHK_NULL(group);
+
+ // Reset HW buffer index for the device
+ group->streams[substream->pcm->device].hw_frame_idx = 0;
+
+ group->buffersize = frames_to_bytes(runtime, runtime->buffer_size);
+
+ // Do additional Audio Path 'prepare' callback
+ DO_AUDIOPATH_CB(prepare, substream);
+
+ return 0;
+}
+
+/**
+ * pcm_trigger - Implements 'trigger' callback for PCM middle layer
+ * @substream: pointer to ALSA PCM substream
+ * @cmd: action to be performed (start or stop)
+ *
+ * This is called when the PCM is started, stopped or paused. The action
+ * indicated action is specified in the second argument, SNDRV_PCM_TRIGGER_XXX
+ *
+ * Returns 0 on success or error code otherwise.
+ */
+static int pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct avirt_alsa_dev_group *group;
+
+ group = avirt_alsa_get_dev_group(substream->stream);
+ CHK_NULL(group);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ group->streams[substream->pcm->device].substream = substream;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ break;
+ default:
+ pr_err("trigger must be START or STOP");
+ return -EINVAL;
+ }
+
+ // Do additional Audio Path 'trigger' callback
+ DO_AUDIOPATH_CB(trigger, substream, cmd);
+
+ return 0;
+}
+
+/**
+ * pcm_pointer - Implements 'pointer' callback for PCM middle layer
+ * @substream: pointer to ALSA PCM substream
+ *
+ * This gets called when the user space needs a DMA buffer index. IO errors will
+ * be generated if the index does not increment, or drives beyond the frame
+ * threshold of the buffer itself.
+ *
+ * Returns the current hardware buffer frame index.
+ */
+static snd_pcm_uframes_t pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct avirt_alsa_dev_group *group;
+
+ group = avirt_alsa_get_dev_group(substream->stream);
+ CHK_NULL(group);
+
+ return group->streams[substream->pcm->device].hw_frame_idx;
+}
+
+/**
+ * pcm_pointer - Implements 'get_time_info' callback for PCM middle layer
+ * @substream: pointer to ALSA PCM substream
+ * @system_ts
+ * @audio_ts
+ * @audio_tstamp_config
+ * @audio_tstamp_report
+ *
+ * Generic way to get system timestamp and audio timestamp info
+ *
+ * Returns 0 on success or error code otherwise
+ */
+static int pcm_get_time_info(
+ struct snd_pcm_substream *substream, struct timespec *system_ts,
+ struct timespec *audio_ts,
+ struct snd_pcm_audio_tstamp_config *audio_tstamp_config,
+ struct snd_pcm_audio_tstamp_report *audio_tstamp_report)
+{
+ struct avirt_alsa_dev_group *group;
+
+ group = avirt_alsa_get_dev_group(substream->stream);
+ CHK_NULL(group);
+
+ DO_AUDIOPATH_CB(get_time_info, substream, system_ts, audio_ts,
+ audio_tstamp_config, audio_tstamp_report);
+
+ return 0;
+}
+
+/**
+ * pcm_copy_user - Implements 'copy_user' callback for PCM middle layer
+ * @substream: pointer to ALSA PCM substream
+ * @channel:
+ * @pos: The offset in the DMA
+ * @src: Audio PCM data from the user space
+ * @count:
+ *
+ * This is where we need to copy user audio PCM data into the sound driver
+ *
+ * Returns 0 on success or error code otherwise.
+ *
+ */
+static int pcm_copy_user(struct snd_pcm_substream *substream, int channel,
+ snd_pcm_uframes_t pos, void __user *src,
+ snd_pcm_uframes_t count)
+{
+ //struct snd_pcm_runtime *runtime;
+ //int offset;
+
+ //runtime = substream->runtime;
+ //offset = frames_to_bytes(runtime, pos);
+
+ // Do additional Audio Path 'copy_user' callback
+ DO_AUDIOPATH_CB(copy_user, substream, channel, pos, src, count);
+
+ return 0;
+}
+
+/**
+ * pcm_copy_kernel - Implements 'copy_kernel' callback for PCM middle layer
+ * @substream: pointer to ALSA PCM substream
+ * @channel:
+ * @pos: The offset in the DMA
+ * @src: Audio PCM data from the user space
+ * @count:
+ *
+ * This is where we need to copy kernel audio PCM data into the sound driver
+ *
+ * Returns 0 on success or error code otherwise.
+ *
+ */
+static int pcm_copy_kernel(struct snd_pcm_substream *substream, int channel,
+ unsigned long pos, void *buf, unsigned long count)
+{
+ DO_AUDIOPATH_CB(copy_kernel, substream, channel, pos, buf, count);
+ return 0;
+}
+
+/**
+ * pcm_ack - Implements 'ack' callback for PCM middle layer
+ * @substream: pointer to ALSA PCM substream
+ *
+ * This is where we need to ack
+ *
+ * Returns 0 on success or error code otherwise.
+ *
+ */
+int pcm_ack(struct snd_pcm_substream *substream)
+{
+ DO_AUDIOPATH_CB(ack, substream);
+ return 0;
+}
+
+static int pcm_silence(struct snd_pcm_substream *substream, int channel,
+ snd_pcm_uframes_t pos, snd_pcm_uframes_t count)
+{
+ DO_AUDIOPATH_CB(fill_silence, substream, channel, pos, count);
+ return 0;
+}
+
+struct snd_pcm_ops pcm_ops = {
+ .open = pcm_open,
+ .close = pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = pcm_hw_params,
+ .hw_free = pcm_hw_free,
+ .prepare = pcm_prepare,
+ .trigger = pcm_trigger,
+ .pointer = pcm_pointer,
+ .get_time_info = pcm_get_time_info,
+ .fill_silence = pcm_silence,
+ .copy_user = pcm_copy_user,
+ .copy_kernel = pcm_copy_kernel,
+ .page = snd_pcm_lib_get_vmalloc_page,
+ .mmap = snd_pcm_lib_mmap_vmalloc,
+ .ack = pcm_ack,
+
+};