alsa-lib/src/pcm/pcm_mmap.c

1142 lines
31 KiB
C
Raw Normal View History

2000-05-08 18:53:38 +00:00
/*
* PCM Interface - mmap
* Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <stdio.h>
#include <malloc.h>
#include <errno.h>
#include <assert.h>
#include <sys/poll.h>
#include <sys/uio.h>
#include "pcm_local.h"
static void snd_pcm_mmap_clear(snd_pcm_t *pcm, int channel)
{
struct snd_pcm_chan *chan = &pcm->chan[channel];
chan->mmap_control->frag_io = 0;
chan->mmap_control->frag_data = 0;
chan->mmap_control->pos_io = 0;
chan->mmap_control->pos_data = 0;
}
void snd_pcm_mmap_status_change(snd_pcm_t *pcm, int channel, int newstatus)
{
struct snd_pcm_chan *chan = &pcm->chan[channel];
if (!chan->mmap_control_emulation)
return;
if (newstatus < 0) {
snd_pcm_channel_status_t status;
status.channel = channel;
if (snd_pcm_channel_status(pcm, &status) < 0)
newstatus = SND_PCM_STATUS_NOTREADY;
else
newstatus = status.status;
}
if (chan->mmap_control->status != newstatus) {
if (newstatus == SND_PCM_STATUS_READY ||
(newstatus == SND_PCM_STATUS_PREPARED &&
chan->mmap_control->status != SND_PCM_STATUS_READY))
snd_pcm_mmap_clear(pcm, channel);
chan->mmap_control->status = newstatus;
pthread_mutex_lock(&chan->mutex);
pthread_cond_signal(&chan->status_cond);
pthread_mutex_unlock(&chan->mutex);
}
}
static ssize_t snd_pcm_mmap_playback_frags_used(snd_pcm_t *pcm)
{
struct snd_pcm_chan *chan;
ssize_t frags_used;
chan = &pcm->chan[SND_PCM_CHANNEL_PLAYBACK];
frags_used = chan->mmap_control->frag_data - chan->mmap_control->frag_io;
if (frags_used < (ssize_t)(chan->setup.frags - chan->setup.frag_boundary))
frags_used += chan->setup.frag_boundary;
return frags_used;
}
static size_t snd_pcm_mmap_capture_frags_used(snd_pcm_t *pcm)
{
struct snd_pcm_chan *chan;
ssize_t frags_used;
chan = &pcm->chan[SND_PCM_CHANNEL_CAPTURE];
frags_used = chan->mmap_control->frag_io - chan->mmap_control->frag_data;
if (frags_used < 0)
frags_used += chan->setup.frag_boundary;
return frags_used;
}
static size_t snd_pcm_mmap_playback_frags_free(snd_pcm_t *pcm)
{
return pcm->chan[SND_PCM_CHANNEL_PLAYBACK].setup.frags - snd_pcm_mmap_playback_frags_used(pcm);
}
static ssize_t snd_pcm_mmap_capture_frags_free(snd_pcm_t *pcm)
{
return pcm->chan[SND_PCM_CHANNEL_CAPTURE].setup.frags - snd_pcm_mmap_capture_frags_used(pcm);
}
int snd_pcm_mmap_frags_used(snd_pcm_t *pcm, int channel, ssize_t *frags)
{
struct snd_pcm_chan *chan;
if (!pcm)
return -EFAULT;
if (channel < 0 || channel > 1)
return -EINVAL;
chan = &pcm->chan[channel];
if (!chan->open || !chan->mmap_control)
return -EBADFD;
if (channel == SND_PCM_CHANNEL_PLAYBACK)
*frags = snd_pcm_mmap_playback_frags_used(pcm);
else
*frags = snd_pcm_mmap_capture_frags_used(pcm);
return 0;
}
int snd_pcm_mmap_frags_free(snd_pcm_t *pcm, int channel, ssize_t *frags)
{
struct snd_pcm_chan *chan;
if (!pcm)
return -EFAULT;
if (channel < 0 || channel > 1)
return -EINVAL;
chan = &pcm->chan[channel];
if (!chan->open || !chan->mmap_control)
return -EBADFD;
if (channel == SND_PCM_CHANNEL_PLAYBACK)
*frags = snd_pcm_mmap_playback_frags_free(pcm);
else
*frags = snd_pcm_mmap_capture_frags_free(pcm);
return 0;
}
static ssize_t snd_pcm_mmap_playback_bytes_used(snd_pcm_t *pcm)
{
struct snd_pcm_chan *chan;
ssize_t bytes_used;
chan = &pcm->chan[SND_PCM_CHANNEL_PLAYBACK];
bytes_used = chan->mmap_control->pos_data - chan->mmap_control->pos_io;
if (bytes_used < (ssize_t)(chan->setup.buffer_size - chan->setup.pos_boundary))
bytes_used += chan->setup.pos_boundary;
return bytes_used;
}
static size_t snd_pcm_mmap_capture_bytes_used(snd_pcm_t *pcm)
{
struct snd_pcm_chan *chan;
ssize_t bytes_used;
chan = &pcm->chan[SND_PCM_CHANNEL_CAPTURE];
bytes_used = chan->mmap_control->pos_io - chan->mmap_control->pos_data;
if (bytes_used < 0)
bytes_used += chan->setup.pos_boundary;
return bytes_used;
}
int snd_pcm_mmap_bytes_used(snd_pcm_t *pcm, int channel, ssize_t *frags)
{
struct snd_pcm_chan *chan;
if (!pcm)
return -EFAULT;
if (channel < 0 || channel > 1)
return -EINVAL;
chan = &pcm->chan[channel];
if (!chan->open || !chan->mmap_control)
return -EBADFD;
if (channel == SND_PCM_CHANNEL_PLAYBACK)
*frags = snd_pcm_mmap_playback_bytes_used(pcm);
else
*frags = snd_pcm_mmap_capture_bytes_used(pcm);
return 0;
}
static size_t snd_pcm_mmap_playback_bytes_free(snd_pcm_t *pcm)
{
return pcm->chan[SND_PCM_CHANNEL_PLAYBACK].setup.buffer_size - snd_pcm_mmap_playback_bytes_used(pcm);
}
static ssize_t snd_pcm_mmap_capture_bytes_free(snd_pcm_t *pcm)
{
return pcm->chan[SND_PCM_CHANNEL_CAPTURE].setup.buffer_size - snd_pcm_mmap_capture_bytes_used(pcm);
}
int snd_pcm_mmap_bytes_free(snd_pcm_t *pcm, int channel, ssize_t *frags)
{
struct snd_pcm_chan *chan;
if (!pcm)
return -EFAULT;
if (channel < 0 || channel > 1)
return -EINVAL;
chan = &pcm->chan[channel];
if (!chan->open || !chan->mmap_control)
return -EBADFD;
if (channel == SND_PCM_CHANNEL_PLAYBACK)
*frags = snd_pcm_mmap_playback_bytes_free(pcm);
else
*frags = snd_pcm_mmap_capture_bytes_free(pcm);
return 0;
}
static int snd_pcm_mmap_playback_ready(snd_pcm_t *pcm)
{
struct snd_pcm_chan *chan;
chan = &pcm->chan[SND_PCM_CHANNEL_PLAYBACK];
if (chan->mmap_control->status == SND_PCM_STATUS_XRUN)
return -EPIPE;
if (chan->setup.mode == SND_PCM_MODE_BLOCK) {
return (chan->setup.frags - snd_pcm_mmap_playback_frags_used(pcm)) >= chan->setup.buf.block.frags_min;
} else {
return (chan->setup.buffer_size - snd_pcm_mmap_playback_bytes_used(pcm)) >= chan->setup.buf.stream.bytes_min;
}
}
static int snd_pcm_mmap_capture_ready(snd_pcm_t *pcm)
{
struct snd_pcm_chan *chan;
int ret = 0;
chan = &pcm->chan[SND_PCM_CHANNEL_CAPTURE];
if (chan->mmap_control->status == SND_PCM_STATUS_XRUN) {
ret = -EPIPE;
if (chan->setup.xrun_mode == SND_PCM_XRUN_DRAIN)
return -EPIPE;
}
if (chan->setup.mode == SND_PCM_MODE_BLOCK) {
if (snd_pcm_mmap_capture_frags_used(pcm) >= chan->setup.buf.block.frags_min)
return 1;
} else {
if (snd_pcm_mmap_capture_bytes_used(pcm) >= chan->setup.buf.stream.bytes_min)
return 1;
}
return ret;
}
int snd_pcm_mmap_ready(snd_pcm_t *pcm, int channel)
{
struct snd_pcm_chan *chan;
snd_pcm_mmap_control_t *ctrl;
if (!pcm)
return -EFAULT;
if (channel < 0 || channel > 1)
return -EINVAL;
chan = &pcm->chan[channel];
if (!chan->open || !chan->mmap_control)
return -EBADFD;
ctrl = chan->mmap_control;
if (ctrl->status < SND_PCM_STATUS_PREPARED)
return -EBADFD;
if (channel == SND_PCM_CHANNEL_PLAYBACK) {
return snd_pcm_mmap_playback_ready(pcm);
} else {
return snd_pcm_mmap_capture_ready(pcm);
}
}
/* Bytes transferrable */
static size_t snd_pcm_mmap_bytes_playback(snd_pcm_t *pcm, size_t size)
{
struct snd_pcm_chan *chan = &pcm->chan[SND_PCM_CHANNEL_PLAYBACK];
snd_pcm_mmap_control_t *ctrl = chan->mmap_control;
size_t bytes_cont, bytes_free;
unsigned int pos_data = ctrl->pos_data;
unsigned int pos_io = ctrl->pos_io;
int bytes_used = pos_data - pos_io;
if (bytes_used < -(int)(chan->setup.buf.stream.bytes_xrun_max + chan->setup.frag_size))
bytes_used += chan->setup.pos_boundary;
bytes_cont = chan->setup.buffer_size - (pos_data % chan->setup.buffer_size);
if (bytes_cont < size)
size = bytes_cont;
bytes_free = chan->setup.buffer_size - bytes_used;
if (bytes_free < size)
size = (bytes_free / chan->setup.buf.stream.bytes_align) * chan->setup.buf.stream.bytes_align;
return size;
}
/* Bytes transferrable */
static size_t snd_pcm_mmap_bytes_capture(snd_pcm_t *pcm, size_t size)
{
struct snd_pcm_chan *chan = &pcm->chan[SND_PCM_CHANNEL_CAPTURE];
snd_pcm_mmap_control_t *ctrl = chan->mmap_control;
size_t bytes_cont;
unsigned int pos_data = ctrl->pos_data;
unsigned int pos_io = ctrl->pos_io;
int bytes_used = pos_io - pos_data;
if (bytes_used < 0)
bytes_used += chan->setup.pos_boundary;
bytes_cont = chan->setup.buffer_size - (pos_data % chan->setup.buffer_size);
if (bytes_cont < size)
size = bytes_cont;
if ((size_t) bytes_used < size)
size = (bytes_used / chan->setup.buf.stream.bytes_align) * chan->setup.buf.stream.bytes_align;
return size;
}
typedef int (*transfer_f)(snd_pcm_t *pcm, size_t hwoff, void *data, size_t off, size_t size);
static ssize_t snd_pcm_mmap_write1(snd_pcm_t *pcm, const void *data, size_t count, transfer_f transfer)
{
struct snd_pcm_chan *chan;
snd_pcm_mmap_control_t *ctrl;
size_t frag_size;
size_t offset = 0;
size_t result = 0;
int err;
chan = &pcm->chan[SND_PCM_CHANNEL_PLAYBACK];
ctrl = chan->mmap_control;
if (ctrl->status < SND_PCM_STATUS_PREPARED)
return -EBADFD;
frag_size = chan->setup.frag_size;
if (chan->setup.mode == SND_PCM_MODE_BLOCK) {
if (count % frag_size != 0)
return -EINVAL;
} else {
int tmp = snd_pcm_format_size(chan->setup.format.format, chan->setup.format.voices);
if (tmp > 0) {
int r = count % tmp;
if (r > 0) {
count -= r;
if (count == 0)
return -EINVAL;
}
}
}
2000-05-09 10:46:43 +00:00
if (chan->mode & SND_PCM_NONBLOCK)
snd_pcm_channel_update(pcm, SND_PCM_CHANNEL_PLAYBACK);
2000-05-08 18:53:38 +00:00
while (count > 0) {
size_t bytes;
int ready = snd_pcm_mmap_playback_ready(pcm);
if (ready < 0)
return ready;
if (!ready) {
struct pollfd pfd;
if (ctrl->status != SND_PCM_STATUS_RUNNING)
return result > 0 ? result : -EPIPE;
if (chan->mode & SND_PCM_NONBLOCK)
return result > 0 ? result : -EAGAIN;
pfd.fd = snd_pcm_file_descriptor(pcm, SND_PCM_CHANNEL_PLAYBACK);
pfd.events = POLLOUT | POLLERR;
ready = poll(&pfd, 1, 10000);
if (ready < 0)
return result > 0 ? result : ready;
if (ready && pfd.revents & POLLERR)
return result > 0 ? result : -EPIPE;
assert(snd_pcm_mmap_playback_ready(pcm));
}
if (chan->setup.mode == SND_PCM_MODE_BLOCK) {
size_t frag_data, frag;
frag_data = ctrl->frag_data;
frag = frag_data % chan->setup.frags;
err = transfer(pcm, frag_size * frag, (void *) data, offset, frag_size);
if (err < 0)
return result > 0 ? result : err;
if (ctrl->status == SND_PCM_STATUS_XRUN)
return result > 0 ? result : -EPIPE;
frag_data++;
if (frag_data == chan->setup.frag_boundary) {
ctrl->frag_data = 0;
ctrl->pos_data = 0;
} else {
ctrl->frag_data = frag_data;
ctrl->pos_data += frag_size;
}
bytes = frag_size;
} else {
size_t pos_data;
bytes = snd_pcm_mmap_bytes_playback(pcm, count);
pos_data = ctrl->pos_data;
err = transfer(pcm, pos_data % chan->setup.buffer_size, (void *) data, offset, bytes);
if (err < 0)
return result > 0 ? result : err;
if (ctrl->status == SND_PCM_STATUS_XRUN)
return result > 0 ? result : -EPIPE;
pos_data += bytes;
if (pos_data == chan->setup.pos_boundary) {
ctrl->pos_data = 0;
ctrl->frag_data = 0;
} else {
ctrl->pos_data = pos_data;
ctrl->frag_data = pos_data / chan->setup.frags;
}
}
offset += bytes;
count -= bytes;
result += bytes;
}
if (ctrl->status == SND_PCM_STATUS_PREPARED &&
(chan->setup.start_mode == SND_PCM_START_DATA ||
(chan->setup.start_mode == SND_PCM_START_FULL &&
!snd_pcm_mmap_playback_ready(pcm)))) {
err = snd_pcm_channel_go(pcm, SND_PCM_CHANNEL_PLAYBACK);
if (err < 0)
return result > 0 ? result : err;
}
return result;
}
static int transfer_write(snd_pcm_t *pcm, size_t hwoff, void* data, size_t off, size_t size)
{
struct snd_pcm_chan *chan = &pcm->chan[SND_PCM_CHANNEL_PLAYBACK];
const char *buf = data;
unsigned int v, voices;
#define COPY_LABELS
#include "plugin/plugin_ops.h"
#undef COPY_LABELS
void *copy;
snd_pcm_voice_setup_t *vsetup;
int idx;
size_t vsize, ssize;
idx = copy_index(chan->setup.format.format);
if (idx < 0)
return idx;
copy = copy_labels[idx];
voices = chan->setup.format.voices;
vsetup = chan->voices_setup;
vsize = snd_pcm_format_size(chan->setup.format.format, 1);
ssize = vsize * chan->setup.format.voices;
hwoff /= ssize;
size /= ssize;
for (v = 0; v < voices; ++v, ++vsetup) {
const char *src;
char *dst;
size_t dst_step;
size_t samples = size;
if (vsetup->first % 8 != 0 ||
vsetup->step % 8 != 0)
return -EINVAL;
src = buf + off + v * vsize;
dst_step = vsetup->step / 8;
dst = chan->mmap_data + vsetup->addr + (vsetup->first + vsetup->step * hwoff) / 8;
while (samples-- > 0) {
goto *copy;
#define COPY_END after
#include "plugin/plugin_ops.h"
#undef COPY_END
after:
src += ssize;
dst += dst_step;
}
}
return 0;
}
ssize_t snd_pcm_mmap_write(snd_pcm_t *pcm, const void *buffer, size_t count)
{
struct snd_pcm_chan *chan;
if (!pcm)
return -EFAULT;
chan = &pcm->chan[SND_PCM_CHANNEL_PLAYBACK];
if (!chan->open || !chan->valid_setup || !chan->valid_voices_setup)
return -EBADFD;
if (!chan->mmap_data || !chan->mmap_control)
return -EBADFD;
if (count > 0 && !buffer)
return -EFAULT;
if (!chan->setup.format.interleave && chan->setup.format.voices > 1)
return -EINVAL;
return snd_pcm_mmap_write1(pcm, buffer, count, transfer_write);
}
static int transfer_writev(snd_pcm_t *pcm, size_t hwoff, void* data, size_t off, size_t size)
{
struct snd_pcm_chan *chan = &pcm->chan[SND_PCM_CHANNEL_PLAYBACK];
struct iovec *vec = data;
unsigned int v, voices;
#define COPY_LABELS
#include "plugin/plugin_ops.h"
#undef COPY_LABELS
void *copy;
snd_pcm_voice_setup_t *vsetup;
int idx;
size_t ssize;
idx = copy_index(chan->setup.format.format);
if (idx < 0)
return idx;
copy = copy_labels[idx];
voices = chan->setup.format.voices;
vsetup = chan->voices_setup;
ssize = snd_pcm_format_size(chan->setup.format.format, chan->setup.format.voices);
hwoff /= ssize;
size /= ssize;
off /= voices;
for (v = 0; v < voices; ++v, ++vsetup, ++vec) {
const char *src;
char *dst;
size_t dst_step;
size_t samples = size;
if (vsetup->first % 8 != 0 ||
vsetup->step % 8 != 0)
return -EINVAL;
src = vec->iov_base + off;
dst_step = vsetup->step / 8;
dst = chan->mmap_data + vsetup->addr + (vsetup->first + vsetup->step * hwoff) / 8;
while (samples-- > 0) {
goto *copy;
#define COPY_END after
#include "plugin/plugin_ops.h"
#undef COPY_END
after:
src += ssize;
dst += dst_step;
}
}
return 0;
}
ssize_t snd_pcm_mmap_writev(snd_pcm_t *pcm, const struct iovec *vector, unsigned long vcount)
{
struct snd_pcm_chan *chan;
size_t result = 0;
unsigned int b;
if (!pcm)
return -EFAULT;
chan = &pcm->chan[SND_PCM_CHANNEL_PLAYBACK];
if (!chan->open || !chan->valid_setup || !chan->valid_voices_setup)
return -EBADFD;
if (!chan->mmap_data || !chan->mmap_control)
return -EBADFD;
if (vcount > 0 && !vector)
return -EFAULT;
if (chan->setup.format.interleave) {
for (b = 0; b < vcount; b++) {
int ret;
ret = snd_pcm_mmap_write1(pcm, vector[b].iov_base, vector[b].iov_len, transfer_write);
if (ret < 0)
return result > 0 ? result : ret;
result += ret;
}
} else {
unsigned int voices = chan->setup.format.voices;
unsigned long bcount;
if (vcount % voices)
return -EINVAL;
bcount = vcount / voices;
for (b = 0; b < bcount; b++) {
unsigned int v;
int ret;
size_t count = 0;
count = vector[0].iov_len;
for (v = 0; v < voices; ++v) {
if (vector[v].iov_len != count)
return -EINVAL;
}
ret = snd_pcm_mmap_write1(pcm, vector, count * voices, transfer_writev);
if (ret < 0)
return result > 0 ? result : ret;
result += ret;
if ((size_t)ret != count * voices)
break;
vector += voices;
}
}
return result;
}
static ssize_t snd_pcm_mmap_read1(snd_pcm_t *pcm, void *data, size_t count, transfer_f transfer)
{
struct snd_pcm_chan *chan;
snd_pcm_mmap_control_t *ctrl;
size_t frag_size;
size_t offset = 0;
size_t result = 0;
int err;
chan = &pcm->chan[SND_PCM_CHANNEL_CAPTURE];
ctrl = chan->mmap_control;
if (ctrl->status < SND_PCM_STATUS_PREPARED)
return -EBADFD;
frag_size = chan->setup.frag_size;
if (chan->setup.mode == SND_PCM_MODE_BLOCK) {
if (count % frag_size != 0)
return -EINVAL;
} else {
int tmp = snd_pcm_format_size(chan->setup.format.format, chan->setup.format.voices);
if (tmp > 0) {
int r = count % tmp;
if (r > 0) {
count -= r;
if (count == 0)
return -EINVAL;
}
}
}
if (ctrl->status == SND_PCM_STATUS_PREPARED &&
chan->setup.start_mode == SND_PCM_START_DATA) {
err = snd_pcm_channel_go(pcm, SND_PCM_CHANNEL_CAPTURE);
if (err < 0)
return err;
}
2000-05-09 10:46:43 +00:00
if (chan->mode & SND_PCM_NONBLOCK)
snd_pcm_channel_update(pcm, SND_PCM_CHANNEL_CAPTURE);
2000-05-08 18:53:38 +00:00
while (count > 0) {
size_t bytes;
int ready = snd_pcm_mmap_capture_ready(pcm);
if (ready < 0)
return ready;
if (!ready) {
struct pollfd pfd;
if (ctrl->status != SND_PCM_STATUS_RUNNING)
return result > 0 ? result : -EPIPE;
if (chan->mode & SND_PCM_NONBLOCK)
return result > 0 ? result : -EAGAIN;
pfd.fd = snd_pcm_file_descriptor(pcm, SND_PCM_CHANNEL_CAPTURE);
pfd.events = POLLIN | POLLERR;
ready = poll(&pfd, 1, 10000);
if (ready < 0)
return result > 0 ? result : ready;
if (ready && pfd.revents & POLLERR)
return result > 0 ? result : -EPIPE;
assert(snd_pcm_mmap_capture_ready(pcm));
}
if (chan->setup.mode == SND_PCM_MODE_BLOCK) {
size_t frag_data, frag;
frag_data = ctrl->frag_data;
frag = frag_data % chan->setup.frags;
err = transfer(pcm, frag_size * frag, data, offset, frag_size);
if (err < 0)
return result > 0 ? result : err;
if (ctrl->status == SND_PCM_STATUS_XRUN &&
chan->setup.xrun_mode == SND_PCM_XRUN_DRAIN)
return result > 0 ? result : -EPIPE;
frag_data++;
if (frag_data == chan->setup.frag_boundary) {
ctrl->frag_data = 0;
ctrl->pos_data = 0;
} else {
ctrl->frag_data = frag_data;
ctrl->pos_data += frag_size;
}
bytes = frag_size;
} else {
size_t pos_data;
bytes = snd_pcm_mmap_bytes_capture(pcm, count);
pos_data = ctrl->pos_data;
err = transfer(pcm, pos_data % chan->setup.buffer_size, data, offset, bytes);
if (err < 0)
return result > 0 ? result : err;
if (ctrl->status == SND_PCM_STATUS_XRUN &&
chan->setup.xrun_mode == SND_PCM_XRUN_DRAIN)
return result > 0 ? result : -EPIPE;
pos_data += bytes;
if (pos_data == chan->setup.pos_boundary) {
ctrl->pos_data = 0;
ctrl->frag_data = 0;
} else {
ctrl->pos_data = pos_data;
ctrl->frag_data = pos_data / chan->setup.frags;
}
}
offset += bytes;
count -= bytes;
result += bytes;
}
return result;
}
static int transfer_read(snd_pcm_t *pcm, size_t hwoff, void* data, size_t off, size_t size)
{
struct snd_pcm_chan *chan = &pcm->chan[SND_PCM_CHANNEL_CAPTURE];
char *buf = data;
unsigned int v, voices;
#define COPY_LABELS
#include "plugin/plugin_ops.h"
#undef COPY_LABELS
void *copy;
snd_pcm_voice_setup_t *vsetup;
int idx;
size_t vsize, ssize;
idx = copy_index(chan->setup.format.format);
if (idx < 0)
return idx;
copy = copy_labels[idx];
voices = chan->setup.format.voices;
vsetup = chan->voices_setup;
vsize = snd_pcm_format_size(chan->setup.format.format, 1);
ssize = vsize * chan->setup.format.voices;
hwoff /= ssize;
size /= ssize;
for (v = 0; v < voices; ++v, ++vsetup) {
const char *src;
size_t src_step;
char *dst;
size_t samples = size;
if (vsetup->first % 8 != 0 ||
vsetup->step % 8 != 0)
return -EINVAL;
src_step = vsetup->step / 8;
src = chan->mmap_data + vsetup->addr + (vsetup->first + vsetup->step * hwoff) / 8;
dst = buf + off + v * vsize;
while (samples-- > 0) {
goto *copy;
#define COPY_END after
#include "plugin/plugin_ops.h"
#undef COPY_END
after:
src += src_step;
dst += ssize;
}
}
return 0;
}
ssize_t snd_pcm_mmap_read(snd_pcm_t *pcm, void *buffer, size_t count)
{
struct snd_pcm_chan *chan;
if (!pcm)
return -EFAULT;
chan = &pcm->chan[SND_PCM_CHANNEL_CAPTURE];
if (!chan->open || !chan->valid_setup || !chan->valid_voices_setup)
return -EBADFD;
if (!chan->mmap_data || !chan->mmap_control)
return -EBADFD;
if (count > 0 && !buffer)
return -EFAULT;
if (!chan->setup.format.interleave && chan->setup.format.voices > 1)
return -EINVAL;
return snd_pcm_mmap_read1(pcm, buffer, count, transfer_read);
}
static int transfer_readv(snd_pcm_t *pcm, size_t hwoff, void* data, size_t off, size_t size)
{
struct snd_pcm_chan *chan = &pcm->chan[SND_PCM_CHANNEL_CAPTURE];
struct iovec *vec = data;
unsigned int v, voices;
#define COPY_LABELS
#include "plugin/plugin_ops.h"
#undef COPY_LABELS
void *copy;
snd_pcm_voice_setup_t *vsetup;
int idx;
size_t ssize;
idx = copy_index(chan->setup.format.format);
if (idx < 0)
return idx;
copy = copy_labels[idx];
voices = chan->setup.format.voices;
vsetup = chan->voices_setup;
ssize = snd_pcm_format_size(chan->setup.format.format, chan->setup.format.voices);
hwoff /= ssize;
size /= ssize;
off /= voices;
for (v = 0; v < voices; ++v, ++vsetup, ++vec) {
const char *src;
size_t src_step;
char *dst;
size_t samples = size;
if (vsetup->first % 8 != 0 ||
vsetup->step % 8 != 0)
return -EINVAL;
src_step = vsetup->step / 8;
src = chan->mmap_data + vsetup->addr + (vsetup->first + vsetup->step * hwoff) / 8;
dst = vec->iov_base + off;
while (samples-- > 0) {
goto *copy;
#define COPY_END after
#include "plugin/plugin_ops.h"
#undef COPY_END
after:
src += src_step;
dst += ssize;
}
}
return 0;
}
ssize_t snd_pcm_mmap_readv(snd_pcm_t *pcm, const struct iovec *vector, unsigned long vcount)
{
struct snd_pcm_chan *chan;
size_t result = 0;
unsigned int b;
if (!pcm)
return -EFAULT;
chan = &pcm->chan[SND_PCM_CHANNEL_CAPTURE];
if (!chan->open || !chan->valid_setup || !chan->valid_voices_setup)
return -EBADFD;
if (!chan->mmap_data || !chan->mmap_control)
return -EBADFD;
if (vcount > 0 && !vector)
return -EFAULT;
if (chan->setup.format.interleave) {
for (b = 0; b < vcount; b++) {
int ret;
ret = snd_pcm_mmap_read1(pcm, vector[b].iov_base, vector[b].iov_len, transfer_write);
if (ret < 0)
return result > 0 ? result : ret;
result += ret;
}
} else {
unsigned int voices = chan->setup.format.voices;
unsigned long bcount;
if (vcount % voices)
return -EINVAL;
bcount = vcount / voices;
for (b = 0; b < bcount; b++) {
unsigned int v;
int ret;
size_t count = 0;
count = vector[0].iov_len;
for (v = 0; v < voices; ++v) {
if (vector[v].iov_len != count)
return -EINVAL;
}
ret = snd_pcm_mmap_read1(pcm, (void *) vector, count * voices, transfer_readv);
if (ret < 0)
return result > 0 ? result : ret;
result += ret;
if ((size_t)ret != count * voices)
break;
vector += voices;
}
}
return result;
}
static void *playback_mmap(void *d)
{
snd_pcm_t *pcm = d;
struct snd_pcm_chan *chan = &pcm->chan[SND_PCM_CHANNEL_PLAYBACK];
snd_pcm_mmap_control_t *control;
char *data;
int frags;
int frag_size, voice_size, voice_frag_size;
int voices;
control = chan->mmap_control;
data = chan->mmap_data;
frags = chan->setup.frags;
frag_size = chan->setup.frag_size;
voices = chan->setup.format.voices;
voice_size = chan->mmap_data_size / voices;
voice_frag_size = voice_size / frags;
while (1) {
int err;
struct pollfd pfd;
unsigned int f, frag;
if (chan->mmap_thread_stop)
break;
pthread_mutex_lock(&chan->mutex);
if (control->status != SND_PCM_STATUS_RUNNING) {
pthread_cond_wait(&chan->status_cond, &chan->mutex);
pthread_mutex_unlock(&chan->mutex);
continue;
}
pthread_mutex_unlock(&chan->mutex);
pfd.fd = snd_pcm_file_descriptor(pcm, SND_PCM_CHANNEL_PLAYBACK);
pfd.events = POLLOUT | POLLERR;
err = poll(&pfd, 1, -1);
if (err < 0) {
fprintf(stderr, "poll err=%d\n", err);
continue;
}
if (pfd.revents & POLLERR) {
snd_pcm_mmap_status_change(pcm, SND_PCM_CHANNEL_PLAYBACK, -1);
fprintf(stderr, "pollerr %d\n", control->status);
continue;
}
frag = control->frag_io;
if (snd_pcm_mmap_playback_frags_used(pcm) <= 0) {
fprintf(stderr, "underrun\n");
usleep(10000);
continue;
}
f = frag % frags;
if (chan->setup.format.interleave) {
err = snd_pcm_write(pcm, data + f * frag_size, frag_size);
} else {
struct iovec vector[voices];
struct iovec *v = vector;
int voice;
for (voice = 0; voice < voices; ++voice) {
v->iov_base = data + voice_size * voice + f * voice_frag_size;
v->iov_len = voice_frag_size;
v++;
}
err = snd_pcm_writev(pcm, vector, voice_frag_size);
}
if (err <= 0) {
fprintf(stderr, "write err=%d\n", err);
snd_pcm_mmap_status_change(pcm, SND_PCM_CHANNEL_PLAYBACK, -1);
continue;
}
pthread_mutex_lock(&chan->mutex);
pthread_cond_signal(&chan->ready_cond);
pthread_mutex_unlock(&chan->mutex);
frag++;
if (frag == chan->setup.frag_boundary)
frag = 0;
control->frag_io = frag;
}
return 0;
}
static void *capture_mmap(void *d)
{
snd_pcm_t *pcm = d;
struct snd_pcm_chan *chan = &pcm->chan[SND_PCM_CHANNEL_CAPTURE];
snd_pcm_mmap_control_t *control;
char *data;
int frags;
int frag_size, voice_size, voice_frag_size;
int voices;
control = chan->mmap_control;
data = chan->mmap_data;
frags = chan->setup.frags;
frag_size = chan->setup.frag_size;
voices = chan->setup.format.voices;
voice_size = chan->mmap_data_size / voices;
voice_frag_size = voice_size / frags;
while (1) {
int err;
struct pollfd pfd;
unsigned int f, frag;
if (chan->mmap_thread_stop)
break;
pthread_mutex_lock(&chan->mutex);
if (control->status != SND_PCM_STATUS_RUNNING) {
pthread_cond_wait(&chan->status_cond, &chan->mutex);
pthread_mutex_unlock(&chan->mutex);
continue;
}
pthread_mutex_unlock(&chan->mutex);
pfd.fd = snd_pcm_file_descriptor(pcm, SND_PCM_CHANNEL_CAPTURE);
pfd.events = POLLIN | POLLERR;
err = poll(&pfd, 1, -1);
if (err < 0) {
fprintf(stderr, "poll err=%d\n", err);
continue;
}
if (pfd.revents & POLLERR) {
snd_pcm_mmap_status_change(pcm, SND_PCM_CHANNEL_CAPTURE, -1);
fprintf(stderr, "pollerr %d\n", control->status);
continue;
}
frag = control->frag_io;
if (snd_pcm_mmap_capture_frags_free(pcm) <= 0) {
fprintf(stderr, "overrun\n");
usleep(10000);
continue;
}
f = frag % frags;
if (chan->setup.format.interleave) {
err = snd_pcm_read(pcm, data + f * frag_size, frag_size);
} else {
struct iovec vector[voices];
struct iovec *v = vector;
int voice;
for (voice = 0; voice < voices; ++voice) {
v->iov_base = data + voice_size * voice + f * voice_frag_size;
v->iov_len = voice_frag_size;
v++;
}
err = snd_pcm_readv(pcm, vector, voice_frag_size);
}
if (err < 0) {
fprintf(stderr, "read err=%d\n", err);
snd_pcm_mmap_status_change(pcm, SND_PCM_CHANNEL_CAPTURE, -1);
continue;
}
frag++;
if (frag == chan->setup.frag_boundary)
frag = 0;
control->frag_io = frag;
}
return 0;
}
int snd_pcm_mmap_control(snd_pcm_t *pcm, int channel, snd_pcm_mmap_control_t **control)
{
struct snd_pcm_chan *chan;
snd_pcm_channel_info_t info;
size_t csize;
int err;
if (!pcm || !control)
return -EFAULT;
if (channel < 0 || channel > 1)
return -EINVAL;
chan = &pcm->chan[channel];
if (!chan->open)
return -EBADFD;
if (chan->mmap_control) {
*control = chan->mmap_control;
return 0;
}
if (!chan->valid_setup)
return -EBADFD;
csize = sizeof(snd_pcm_mmap_control_t);
info.channel = channel;
err = snd_pcm_channel_info(pcm, &info);
if (err < 0)
return err;
if (info.flags & SND_PCM_CHNINFO_MMAP) {
if ((err = pcm->ops->mmap_control(pcm, channel, control, csize)) < 0)
return err;
} else {
*control = calloc(1, csize);
chan->mmap_control_emulation = 1;
}
chan->mmap_control = *control;
chan->mmap_control_size = csize;
return 0;
}
int snd_pcm_mmap_data(snd_pcm_t *pcm, int channel, void **data)
{
struct snd_pcm_chan *chan;
snd_pcm_channel_info_t info;
size_t bsize;
int err;
if (!pcm || !data)
return -EFAULT;
if (channel < 0 || channel > 1)
return -EINVAL;
chan = &pcm->chan[channel];
if (!chan->open)
return -EBADFD;
if (chan->mmap_data) {
*data = chan->mmap_data;
return 0;
}
if (!chan->valid_setup)
return -EBADFD;
info.channel = channel;
err = snd_pcm_channel_info(pcm, &info);
if (err < 0)
return err;
bsize = info.mmap_size;
if (info.flags & SND_PCM_CHNINFO_MMAP) {
if ((err = pcm->ops->mmap_data(pcm, channel, data, bsize)) < 0)
return err;
} else {
*data = calloc(1, bsize);
pthread_mutex_init(&chan->mutex, NULL);
pthread_cond_init(&chan->status_cond, NULL);
pthread_cond_init(&chan->ready_cond, NULL);
chan->mmap_thread_stop = 0;
if (channel == SND_PCM_CHANNEL_PLAYBACK)
err = pthread_create(&chan->mmap_thread, NULL, playback_mmap, pcm);
else
err = pthread_create(&chan->mmap_thread, NULL, capture_mmap, pcm);
if (err < 0) {
pthread_cond_destroy(&chan->status_cond);
pthread_cond_destroy(&chan->ready_cond);
pthread_mutex_destroy(&chan->mutex);
free(*data);
*data = 0;
return err;
}
chan->mmap_data_emulation = 1;
}
chan->mmap_data = *data;
chan->mmap_data_size = bsize;
snd_pcm_all_voices_setup(pcm, channel, NULL);
return 0;
}
int snd_pcm_mmap(snd_pcm_t *pcm, int channel, snd_pcm_mmap_control_t **control, void **data)
{
int err;
err = snd_pcm_mmap_control(pcm, channel, control);
if (err < 0)
return err;
err = snd_pcm_mmap_data(pcm, channel, data);
if (err < 0) {
snd_pcm_munmap_control(pcm, channel);
return err;
}
return 0;
}
int snd_pcm_munmap_control(snd_pcm_t *pcm, int channel)
{
int err;
struct snd_pcm_chan *chan;
if (!pcm)
return -EFAULT;
if (channel < 0 || channel > 1)
return -EINVAL;
chan = &pcm->chan[channel];
if (!chan->open)
return -EBADFD;
if (!chan->mmap_control)
return -EINVAL;
if (chan->mmap_control_emulation) {
free(chan->mmap_control);
chan->mmap_control_emulation = 0;
} else {
if ((err = pcm->ops->munmap_control(pcm, channel, chan->mmap_control, chan->mmap_control_size)) < 0)
return err;
}
chan->mmap_control = 0;
chan->mmap_control_size = 0;
return 0;
}
int snd_pcm_munmap_data(snd_pcm_t *pcm, int channel)
{
int err;
struct snd_pcm_chan *chan;
if (!pcm)
return -EFAULT;
if (channel < 0 || channel > 1)
return -EINVAL;
chan = &pcm->chan[channel];
if (!chan->open)
return -EBADFD;
if (!chan->mmap_data)
return -EINVAL;
if (chan->mmap_data_emulation) {
chan->mmap_thread_stop = 1;
pthread_mutex_lock(&chan->mutex);
pthread_cond_signal(&chan->status_cond);
pthread_mutex_unlock(&chan->mutex);
pthread_join(chan->mmap_thread, NULL);
pthread_cond_destroy(&chan->status_cond);
pthread_cond_destroy(&chan->ready_cond);
pthread_mutex_destroy(&chan->mutex);
free(chan->mmap_data);
chan->mmap_data_emulation = 0;
} else {
if ((err = pcm->ops->munmap_data(pcm, channel, chan->mmap_data, chan->mmap_data_size)) < 0)
return err;
}
chan->mmap_data = 0;
chan->mmap_data_size = 0;
return 0;
}
int snd_pcm_munmap(snd_pcm_t *pcm, int channel)
{
int err;
err = snd_pcm_munmap_control(pcm, channel);
if (err < 0)
return err;
return snd_pcm_munmap_data(pcm, channel);
}