[PATCH 1/2] ALSA: aloop: Add support for the non-interleaved access mode
The current version of the loopback driver supports interleaved access mode only. This patch introduces support for the non-interleaved access mode.
When in the interleaved mode, the 'copy_play_buf' function copies data from the playback to the capture buffer using one memcpy call. This call copies samples for multiple, interleaved channels.
In the non-interleaved mode we have multiple channel buffers, so we have to perform multiple memcpy calls to copy samples channel after channel.
Add new function called 'copy_play_buf_part_n', which copies a part of each channel buffer from playback to capture. Modify the 'copy_play_buf' to use the corresponding memory copy function(just memcpy / copy_play_buf_part_n) depending on the access mode.
Signed-off-by: Ivan Orlov ivan.orlov0322@gmail.com --- sound/drivers/aloop.c | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-)
diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c index a38e602b4fc6..ab116b1fed96 100644 --- a/sound/drivers/aloop.c +++ b/sound/drivers/aloop.c @@ -158,6 +158,9 @@ struct loopback_pcm { unsigned long last_jiffies; /* If jiffies timer is used */ struct timer_list timer; + + /* size of per channel buffer in case of non-interleaved access */ + unsigned int channel_buf_n; };
static struct platform_device *devices[SNDRV_CARDS]; @@ -335,7 +338,8 @@ static int loopback_check_format(struct loopback_cable *cable, int stream) substream->runtime; check = runtime->format != cruntime->format || runtime->rate != cruntime->rate || - runtime->channels != cruntime->channels; + runtime->channels != cruntime->channels || + runtime->access != cruntime->access; if (!check) return 0; if (stream == SNDRV_PCM_STREAM_CAPTURE) { @@ -472,6 +476,7 @@ static int loopback_prepare(struct snd_pcm_substream *substream)
dpcm->buf_pos = 0; dpcm->pcm_buffer_size = frames_to_bytes(runtime, runtime->buffer_size); + dpcm->channel_buf_n = dpcm->pcm_buffer_size / runtime->channels; if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { /* clear capture buffer */ dpcm->silent_size = dpcm->pcm_buffer_size; @@ -522,6 +527,22 @@ static void clear_capture_buf(struct loopback_pcm *dpcm, unsigned int bytes) } }
+static void copy_play_buf_part_n(struct loopback_pcm *play, struct loopback_pcm *capt, + unsigned int size, unsigned int src_off, unsigned int dst_off) +{ + unsigned int channels = capt->substream->runtime->channels; + unsigned int size_p_ch = size / channels; + unsigned int src_off_ch = src_off / channels; + unsigned int dst_off_ch = dst_off / channels; + int i; + + for (i = 0; i < channels; i++) { + memcpy(capt->substream->runtime->dma_area + capt->channel_buf_n * i + dst_off_ch, + play->substream->runtime->dma_area + play->channel_buf_n * i + src_off_ch, + size_p_ch); + } +} + static void copy_play_buf(struct loopback_pcm *play, struct loopback_pcm *capt, unsigned int bytes) @@ -556,7 +577,11 @@ static void copy_play_buf(struct loopback_pcm *play, size = play->pcm_buffer_size - src_off; if (dst_off + size > capt->pcm_buffer_size) size = capt->pcm_buffer_size - dst_off; - memcpy(dst + dst_off, src + src_off, size); + if (runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED || + runtime->access == SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED) + copy_play_buf_part_n(play, capt, size, src_off, dst_off); + else + memcpy(dst + dst_off, src + src_off, size); capt->silent_size = 0; bytes -= size; if (!bytes) @@ -878,7 +903,7 @@ static const struct snd_pcm_hardware loopback_pcm_hardware = { .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE | - SNDRV_PCM_INFO_RESUME), + SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_NONINTERLEAVED), .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE |
Add new control element 'PCM Slave Access Mode' which shows the access mode (interleaved/non-interleaved) for the PCM playing device. Add corresponding control change notification calls.
Signed-off-by: Ivan Orlov ivan.orlov0322@gmail.com --- sound/drivers/aloop.c | 45 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-)
diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c index ab116b1fed96..e87dc67f33c6 100644 --- a/sound/drivers/aloop.c +++ b/sound/drivers/aloop.c @@ -119,11 +119,13 @@ struct loopback_setup { unsigned int rate_shift; snd_pcm_format_t format; unsigned int rate; + snd_pcm_access_t access; unsigned int channels; struct snd_ctl_elem_id active_id; struct snd_ctl_elem_id format_id; struct snd_ctl_elem_id rate_id; struct snd_ctl_elem_id channels_id; + struct snd_ctl_elem_id access_id; };
struct loopback { @@ -367,6 +369,11 @@ static int loopback_check_format(struct loopback_cable *cable, int stream) &setup->channels_id); setup->channels = runtime->channels; } + if (setup->access != runtime->access) { + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &setup->access_id); + setup->access = runtime->access; + } } return 0; } @@ -1520,6 +1527,30 @@ static int loopback_channels_get(struct snd_kcontrol *kcontrol, return 0; }
+static int loopback_access_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + const char * const texts[] = {"Interleaved", "Non-interleaved"}; + + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); +} + +static int loopback_access_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + snd_pcm_access_t access; + + mutex_lock(&loopback->cable_lock); + access = loopback->setup[kcontrol->id.subdevice][kcontrol->id.device].access; + + ucontrol->value.enumerated.item[0] = access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED || + access == SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED; + + mutex_unlock(&loopback->cable_lock); + return 0; +} + static const struct snd_kcontrol_new loopback_controls[] = { { .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1566,7 +1597,15 @@ static const struct snd_kcontrol_new loopback_controls[] = { .name = "PCM Slave Channels", .info = loopback_channels_info, .get = loopback_channels_get -} +}, +#define ACCESS_IDX 6 +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "PCM Slave Access Mode", + .info = loopback_access_info, + .get = loopback_access_get, +}, };
static int loopback_mixer_new(struct loopback *loopback, int notify) @@ -1587,6 +1626,7 @@ static int loopback_mixer_new(struct loopback *loopback, int notify) setup->notify = notify; setup->rate_shift = NO_PITCH; setup->format = SNDRV_PCM_FORMAT_S16_LE; + setup->access = SNDRV_PCM_ACCESS_RW_INTERLEAVED; setup->rate = 48000; setup->channels = 2; for (idx = 0; idx < ARRAY_SIZE(loopback_controls); @@ -1618,6 +1658,9 @@ static int loopback_mixer_new(struct loopback *loopback, int notify) case CHANNELS_IDX: setup->channels_id = kctl->id; break; + case ACCESS_IDX: + setup->access_id = kctl->id; + break; default: break; }
On 27. 09. 23 13:35, Ivan Orlov wrote:
Add new control element 'PCM Slave Access Mode' which shows the access mode (interleaved/non-interleaved) for the PCM playing device. Add corresponding control change notification calls.
Signed-off-by: Ivan Orlov ivan.orlov0322@gmail.com
Reviewed-by: Jaroslav Kysela perex@perex.cz
Jaroslav
On 27. 09. 23 13:35, Ivan Orlov wrote:
The current version of the loopback driver supports interleaved access mode only. This patch introduces support for the non-interleaved access mode.
When in the interleaved mode, the 'copy_play_buf' function copies data from the playback to the capture buffer using one memcpy call. This call copies samples for multiple, interleaved channels.
In the non-interleaved mode we have multiple channel buffers, so we have to perform multiple memcpy calls to copy samples channel after channel.
Add new function called 'copy_play_buf_part_n', which copies a part of each channel buffer from playback to capture. Modify the 'copy_play_buf' to use the corresponding memory copy function(just memcpy / copy_play_buf_part_n) depending on the access mode.
Signed-off-by: Ivan Orlov ivan.orlov0322@gmail.com
Nice extension. Thank you.
+static void copy_play_buf_part_n(struct loopback_pcm *play, struct loopback_pcm *capt,
unsigned int size, unsigned int src_off, unsigned int dst_off)
I would probably prefer to have dst,src,size arguments to follow memcpy, but it's really nitpicking.
Reviewed-by: Jaroslav Kysela perex@perex.cz
Jaroslav
On 10/5/23 08:23, Jaroslav Kysela wrote:
On 27. 09. 23 13:35, Ivan Orlov wrote:
The current version of the loopback driver supports interleaved access mode only. This patch introduces support for the non-interleaved access mode.
When in the interleaved mode, the 'copy_play_buf' function copies data from the playback to the capture buffer using one memcpy call. This call copies samples for multiple, interleaved channels.
In the non-interleaved mode we have multiple channel buffers, so we have to perform multiple memcpy calls to copy samples channel after channel.
Add new function called 'copy_play_buf_part_n', which copies a part of each channel buffer from playback to capture. Modify the 'copy_play_buf' to use the corresponding memory copy function(just memcpy / copy_play_buf_part_n) depending on the access mode.
Signed-off-by: Ivan Orlov ivan.orlov0322@gmail.com
Nice extension. Thank you.
+static void copy_play_buf_part_n(struct loopback_pcm *play, struct loopback_pcm *capt, + unsigned int size, unsigned int src_off, unsigned int dst_off)
I would probably prefer to have dst,src,size arguments to follow memcpy, but it's really nitpicking.
Reviewed-by: Jaroslav Kysela perex@perex.cz
Jaroslav
Hi Jaroslav,
Thank you for the review!
I agree that parameters similar to the memcpy would look better than that, I'll keep it in mind when I send the next patch :)
-- Kind regards, Ivan Orlov
On Wed, 27 Sep 2023 13:35:54 +0200, Ivan Orlov wrote:
The current version of the loopback driver supports interleaved access mode only. This patch introduces support for the non-interleaved access mode.
When in the interleaved mode, the 'copy_play_buf' function copies data from the playback to the capture buffer using one memcpy call. This call copies samples for multiple, interleaved channels.
In the non-interleaved mode we have multiple channel buffers, so we have to perform multiple memcpy calls to copy samples channel after channel.
Add new function called 'copy_play_buf_part_n', which copies a part of each channel buffer from playback to capture. Modify the 'copy_play_buf' to use the corresponding memory copy function(just memcpy / copy_play_buf_part_n) depending on the access mode.
Signed-off-by: Ivan Orlov ivan.orlov0322@gmail.com
Sorry for the late reaction, as I've been (still) off since the last week.
Now applied both patches. Thanks.
Takashi
On 10/6/23 09:52, Takashi Iwai wrote:
On Wed, 27 Sep 2023 13:35:54 +0200, Ivan Orlov wrote:
The current version of the loopback driver supports interleaved access mode only. This patch introduces support for the non-interleaved access mode.
When in the interleaved mode, the 'copy_play_buf' function copies data from the playback to the capture buffer using one memcpy call. This call copies samples for multiple, interleaved channels.
In the non-interleaved mode we have multiple channel buffers, so we have to perform multiple memcpy calls to copy samples channel after channel.
Add new function called 'copy_play_buf_part_n', which copies a part of each channel buffer from playback to capture. Modify the 'copy_play_buf' to use the corresponding memory copy function(just memcpy / copy_play_buf_part_n) depending on the access mode.
Signed-off-by: Ivan Orlov ivan.orlov0322@gmail.com
Sorry for the late reaction, as I've been (still) off since the last week.
Now applied both patches. Thanks.
Hi Takashi,
No worries and thank you for applying both patches :)
-- Kind regards, Ivan Orlov
participants (3)
-
Ivan Orlov
-
Jaroslav Kysela
-
Takashi Iwai