mirror of
https://github.com/alsa-project/alsa-utils
synced 2024-11-09 03:35:41 +01:00
269 lines
7.2 KiB
C
269 lines
7.2 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
//
|
||
|
// xfer-libasound-irq-mmap.c - IRQ-based scheduling model for mmap operation.
|
||
|
//
|
||
|
// Copyright (c) 2018 Takashi Sakamoto <o-takashi@sakamocchi.jp>
|
||
|
//
|
||
|
// Licensed under the terms of the GNU General Public License, version 2.
|
||
|
|
||
|
#include "xfer-libasound.h"
|
||
|
#include "misc.h"
|
||
|
|
||
|
struct map_layout {
|
||
|
snd_pcm_status_t *status;
|
||
|
|
||
|
char **vector;
|
||
|
unsigned int samples_per_frame;
|
||
|
};
|
||
|
|
||
|
static int irq_mmap_pre_process(struct libasound_state *state)
|
||
|
{
|
||
|
struct map_layout *layout = state->private_data;
|
||
|
snd_pcm_access_t access;
|
||
|
snd_pcm_uframes_t frame_offset;
|
||
|
snd_pcm_uframes_t avail = 0;
|
||
|
int i;
|
||
|
int err;
|
||
|
|
||
|
err = snd_pcm_status_malloc(&layout->status);
|
||
|
if (err < 0)
|
||
|
return err;
|
||
|
|
||
|
err = snd_pcm_hw_params_get_access(state->hw_params, &access);
|
||
|
if (err < 0)
|
||
|
return err;
|
||
|
|
||
|
err = snd_pcm_hw_params_get_channels(state->hw_params,
|
||
|
&layout->samples_per_frame);
|
||
|
if (err < 0)
|
||
|
return err;
|
||
|
|
||
|
if (access == SND_PCM_ACCESS_MMAP_NONINTERLEAVED) {
|
||
|
layout->vector = calloc(layout->samples_per_frame,
|
||
|
sizeof(*layout->vector));
|
||
|
if (layout->vector == NULL)
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
if (state->verbose) {
|
||
|
const snd_pcm_channel_area_t *areas;
|
||
|
err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset,
|
||
|
&avail);
|
||
|
if (err < 0)
|
||
|
return err;
|
||
|
|
||
|
logging(state, "attributes for mapped page frame:\n");
|
||
|
for (i = 0; i < layout->samples_per_frame; ++i) {
|
||
|
const snd_pcm_channel_area_t *area = areas + i;
|
||
|
|
||
|
logging(state, " sample number: %d\n", i);
|
||
|
logging(state, " address: %p\n", area->addr);
|
||
|
logging(state, " bits for offset: %u\n", area->first);
|
||
|
logging(state, " bits/frame: %u\n", area->step);
|
||
|
}
|
||
|
logging(state, "\n");
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int irq_mmap_process_frames(struct libasound_state *state,
|
||
|
unsigned int *frame_count,
|
||
|
struct mapper_context *mapper,
|
||
|
struct container_context *cntrs)
|
||
|
{
|
||
|
struct map_layout *layout = state->private_data;
|
||
|
const snd_pcm_channel_area_t *areas;
|
||
|
snd_pcm_uframes_t frame_offset;
|
||
|
snd_pcm_uframes_t avail;
|
||
|
unsigned int avail_count;
|
||
|
void *frame_buf;
|
||
|
snd_pcm_sframes_t consumed_count;
|
||
|
int err;
|
||
|
|
||
|
// Wait for hardware IRQ when no avail space in buffer.
|
||
|
err = snd_pcm_wait(state->handle, -1);
|
||
|
if (err < 0)
|
||
|
return err;
|
||
|
|
||
|
// Sync cache in user space to data in kernel space to calculate avail
|
||
|
// frames according to the latest positions on PCM buffer.
|
||
|
//
|
||
|
// This has an additional advantage to handle libasound PCM plugins.
|
||
|
// Most of libasound PCM plugins perform resampling in .avail_update()
|
||
|
// callback for capture PCM substream, then update positions on buffer.
|
||
|
//
|
||
|
// MEMO: either snd_pcm_avail_update() and snd_pcm_mmap_begin() can
|
||
|
// return the same number of available frames.
|
||
|
avail = snd_pcm_avail_update(state->handle);
|
||
|
if (avail < 0)
|
||
|
return (int)avail;
|
||
|
if (*frame_count < avail)
|
||
|
avail = *frame_count;
|
||
|
|
||
|
err = snd_pcm_mmap_begin(state->handle, &areas, &frame_offset, &avail);
|
||
|
if (err < 0)
|
||
|
return err;
|
||
|
|
||
|
// Trim according up to expected frame count.
|
||
|
if (*frame_count < avail)
|
||
|
avail_count = *frame_count;
|
||
|
else
|
||
|
avail_count = (unsigned int)avail;
|
||
|
|
||
|
// TODO: Perhaps, the complex layout can be supported as a variation of
|
||
|
// vector type. However, there's no driver with this layout.
|
||
|
if (layout->vector == NULL) {
|
||
|
frame_buf = areas[0].addr;
|
||
|
frame_buf += snd_pcm_frames_to_bytes(state->handle,
|
||
|
frame_offset);
|
||
|
} else {
|
||
|
int i;
|
||
|
for (i = 0; i < layout->samples_per_frame; ++i) {
|
||
|
layout->vector[i] = areas[i].addr;
|
||
|
layout->vector[i] += snd_pcm_samples_to_bytes(
|
||
|
state->handle, frame_offset);
|
||
|
}
|
||
|
frame_buf = layout->vector;
|
||
|
}
|
||
|
|
||
|
err = mapper_context_process_frames(mapper, frame_buf, &avail_count,
|
||
|
cntrs);
|
||
|
if (err < 0)
|
||
|
return err;
|
||
|
if (avail_count == 0) {
|
||
|
*frame_count = 0;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
consumed_count = snd_pcm_mmap_commit(state->handle, frame_offset,
|
||
|
avail_count);
|
||
|
if (consumed_count < 0)
|
||
|
return (int)consumed_count;
|
||
|
if (consumed_count != avail_count)
|
||
|
logging(state, "A bug of access plugin for this PCM node.\n");
|
||
|
|
||
|
*frame_count = consumed_count;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int irq_mmap_r_process_frames(struct libasound_state *state,
|
||
|
unsigned *frame_count,
|
||
|
struct mapper_context *mapper,
|
||
|
struct container_context *cntrs)
|
||
|
{
|
||
|
struct map_layout *layout = state->private_data;
|
||
|
snd_pcm_state_t s;
|
||
|
int err;
|
||
|
|
||
|
// To querying current status of hardware, we need to care of
|
||
|
// synchronization between 3 levels:
|
||
|
// 1. status to actual hardware by driver.
|
||
|
// 2. status data in kernel space.
|
||
|
// 3. status data in user space.
|
||
|
//
|
||
|
// Kernel driver query 1 and sync 2, according to requests of some
|
||
|
// ioctl(2) commands. For synchronization between 2 and 3, ALSA PCM core
|
||
|
// supports mmap(2) operation on cache coherent architectures, some
|
||
|
// ioctl(2) commands on cache incoherent architecture. In usage of the
|
||
|
// former mechanism, we need to care of concurrent access by IRQ context
|
||
|
// and process context to the mapped page frame.
|
||
|
// In a call of ioctl(2) with SNDRV_PCM_IOCTL_STATUS and
|
||
|
// SNDRV_PCM_IOCTL_STATUS_EXT, the above care is needless because
|
||
|
// mapped page frame is unused regardless of architectures in a point of
|
||
|
// cache coherency.
|
||
|
err = snd_pcm_status(state->handle, layout->status);
|
||
|
if (err < 0)
|
||
|
goto error;
|
||
|
s = snd_pcm_status_get_state(layout->status);
|
||
|
|
||
|
// TODO: if reporting something, do here with the status data.
|
||
|
|
||
|
// For capture direction, need to start stream explicitly.
|
||
|
if (s != SND_PCM_STATE_RUNNING) {
|
||
|
if (s != SND_PCM_STATE_PREPARED) {
|
||
|
err = -EPIPE;
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
err = snd_pcm_start(state->handle);
|
||
|
if (err < 0)
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
err = irq_mmap_process_frames(state, frame_count, mapper, cntrs);
|
||
|
if (err < 0)
|
||
|
goto error;
|
||
|
|
||
|
return 0;
|
||
|
error:
|
||
|
*frame_count = 0;
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
static int irq_mmap_w_process_frames(struct libasound_state *state,
|
||
|
unsigned *frame_count,
|
||
|
struct mapper_context *mapper,
|
||
|
struct container_context *cntrs)
|
||
|
{
|
||
|
struct map_layout *layout = state->private_data;
|
||
|
snd_pcm_state_t s;
|
||
|
int err;
|
||
|
|
||
|
// Read my comment in 'irq_mmap_r_process_frames().
|
||
|
err = snd_pcm_status(state->handle, layout->status);
|
||
|
if (err < 0)
|
||
|
goto error;
|
||
|
s = snd_pcm_status_get_state(layout->status);
|
||
|
|
||
|
// TODO: if reporting something, do here with the status data.
|
||
|
|
||
|
err = irq_mmap_process_frames(state, frame_count, mapper, cntrs);
|
||
|
if (err < 0)
|
||
|
goto error;
|
||
|
|
||
|
// Need to start playback stream explicitly
|
||
|
if (s != SND_PCM_STATE_RUNNING) {
|
||
|
if (s != SND_PCM_STATE_PREPARED) {
|
||
|
err = -EPIPE;
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
err = snd_pcm_start(state->handle);
|
||
|
if (err < 0)
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
error:
|
||
|
*frame_count = 0;
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
static void irq_mmap_post_process(struct libasound_state *state)
|
||
|
{
|
||
|
struct map_layout *layout = state->private_data;
|
||
|
|
||
|
if (layout->status)
|
||
|
snd_pcm_status_free(layout->status);
|
||
|
layout->status = NULL;
|
||
|
|
||
|
free(layout->vector);
|
||
|
layout->vector = NULL;
|
||
|
}
|
||
|
|
||
|
const struct xfer_libasound_ops xfer_libasound_irq_mmap_w_ops = {
|
||
|
.pre_process = irq_mmap_pre_process,
|
||
|
.process_frames = irq_mmap_w_process_frames,
|
||
|
.post_process = irq_mmap_post_process,
|
||
|
.private_size = sizeof(struct map_layout),
|
||
|
};
|
||
|
|
||
|
const struct xfer_libasound_ops xfer_libasound_irq_mmap_r_ops = {
|
||
|
.pre_process = irq_mmap_pre_process,
|
||
|
.process_frames = irq_mmap_r_process_frames,
|
||
|
.post_process = irq_mmap_post_process,
|
||
|
.private_size = sizeof(struct map_layout),
|
||
|
};
|