#include <string.h>
#include <fcntl.h>
#include <ctype.h>
+#include <grp.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/shm.h>
const char *_snd_module_pcm_dmix = "";
#endif
+#ifndef DOC_HIDDEN
+/* start is pending - this state happens when rate plugin does a delayed commit */
+#define STATE_RUN_PENDING 1024
+#endif
+
/*
*
*/
+static int shm_sum_discard(snd_pcm_direct_t *dmix);
+
/*
* sum ring buffer shared memory area
*/
static int shm_sum_create_or_connect(snd_pcm_direct_t *dmix)
{
- static int shm_sum_discard(snd_pcm_direct_t *dmix);
struct shmid_ds buf;
int tmpid, err;
size_t size;
dmix->shmptr->s.buffer_size *
sizeof(signed int);
retryshm:
- dmix->u.dmix.shmid_sum = shmget(dmix->ipc_key + 1, size, IPC_CREAT | 0666);
+ dmix->u.dmix.shmid_sum = shmget(dmix->ipc_key + 1, size,
+ IPC_CREAT | dmix->ipc_perm);
err = -errno;
- if (dmix->u.dmix.shmid_sum < 0){
+ if (dmix->u.dmix.shmid_sum < 0) {
if (errno == EINVAL)
- if ((tmpid = shmget(dmix->ipc_key + 1, 0, 0666)) != -1)
+ if ((tmpid = shmget(dmix->ipc_key + 1, 0, dmix->ipc_perm)) != -1)
if (!shmctl(tmpid, IPC_STAT, &buf))
if (!buf.shm_nattch)
/* no users so destroy the segment */
goto retryshm;
return err;
}
+ if (shmctl(dmix->u.dmix.shmid_sum, IPC_STAT, &buf) < 0) {
+ err = -errno;
+ shm_sum_discard(dmix);
+ return err;
+ }
+ if (dmix->ipc_gid >= 0) {
+ buf.shm_perm.gid = dmix->ipc_gid;
+ shmctl(dmix->u.dmix.shmid_sum, IPC_SET, &buf);
+ }
dmix->u.dmix.sum_buffer = shmat(dmix->u.dmix.shmid_sum, 0, 0);
if (dmix->u.dmix.sum_buffer == (void *) -1) {
+ err = -errno;
shm_sum_discard(dmix);
- return -errno;
+ return err;
}
mlock(dmix->u.dmix.sum_buffer, size);
return 0;
unsigned int chn, dchn, channels;
channels = dmix->channels;
- if (dmix->shmptr->s.format == SND_PCM_FORMAT_S16) {
+ if (dmix->shmptr->s.format == SND_PCM_FORMAT_S16_LE ||
+ dmix->shmptr->s.format == SND_PCM_FORMAT_S16_BE) {
signed short *src;
volatile signed short *dst;
if (dmix->interleaved) {
sum = dmix->u.dmix.sum_buffer + channels * dst_ofs + chn;
dmix->u.dmix.mix_areas1(size, dst, src, sum, dst_step, src_step, channels * sizeof(signed int));
}
- } else {
+ } else if (dmix->shmptr->s.format == SND_PCM_FORMAT_S32_LE ||
+ dmix->shmptr->s.format == SND_PCM_FORMAT_S32_BE) {
signed int *src;
volatile signed int *dst;
if (dmix->interleaved) {
sum = dmix->u.dmix.sum_buffer + channels * dst_ofs + chn;
dmix->u.dmix.mix_areas2(size, dst, src, sum, dst_step, src_step, channels * sizeof(signed int));
}
+ } else { /* SND_PCM_FORMAT_S24_3LE */
+ unsigned char *src;
+ volatile unsigned char *dst;
+ if (dmix->interleaved) {
+ /*
+ * process all areas in one loop
+ * it optimizes the memory accesses for this case
+ */
+ dmix->u.dmix.mix_areas3(size * channels,
+ ((unsigned char *)dst_areas[0].addr) + 3 * dst_ofs * channels,
+ ((unsigned char *)src_areas[0].addr) + 3 * src_ofs * channels,
+ dmix->u.dmix.sum_buffer + (dst_ofs * channels),
+ 3, 3, sizeof(signed int));
+ return;
+ }
+ for (chn = 0; chn < channels; chn++) {
+ dchn = dmix->bindings ? dmix->bindings[chn] : chn;
+ if (dchn >= dmix->shmptr->s.channels)
+ continue;
+ src_step = src_areas[chn].step / 8;
+ dst_step = dst_areas[dchn].step / 8;
+ src = (unsigned char *)(((char *)src_areas[chn].addr + src_areas[chn].first / 8) + (src_ofs * src_step));
+ dst = (unsigned char *)(((char *)dst_areas[dchn].addr + dst_areas[dchn].first / 8) + (dst_ofs * dst_step));
+ sum = dmix->u.dmix.sum_buffer + channels * dst_ofs + chn;
+ dmix->u.dmix.mix_areas3(size, dst, src, sum, dst_step, src_step, channels * sizeof(signed int));
+ }
}
}
/*
+ * if no concurrent access is allowed in the mixing routines, we need to protect
+ * the area via semaphore
+ */
+#ifndef DOC_HIDDEN
+#ifdef NO_CONCURRENT_ACCESS
+#define dmix_down_sem(dmix) snd_pcm_direct_semaphore_down(dmix, DIRECT_IPC_SEM_CLIENT)
+#define dmix_up_sem(dmix) snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT)
+#else
+#define dmix_down_sem(dmix)
+#define dmix_up_sem(dmix)
+#endif
+#endif
+
+/*
* synchronize shm ring buffer with hardware
*/
-static void snd_pcm_dmix_sync_area(snd_pcm_t *pcm, snd_pcm_uframes_t size)
+static void snd_pcm_dmix_sync_area(snd_pcm_t *pcm)
{
snd_pcm_direct_t *dmix = pcm->private_data;
- snd_pcm_uframes_t appl_ptr, slave_appl_ptr, transfer;
+ snd_pcm_uframes_t slave_hw_ptr, slave_appl_ptr, slave_size;
+ snd_pcm_uframes_t appl_ptr, size;
const snd_pcm_channel_area_t *src_areas, *dst_areas;
- /* get the start of update area */
- appl_ptr = dmix->appl_ptr - size;
- if (appl_ptr > pcm->boundary)
- appl_ptr += pcm->boundary;
- appl_ptr %= pcm->buffer_size;
+ /* calculate the size to transfer */
+ /* check the available size in the local buffer
+ * last_appl_ptr keeps the last updated position
+ */
+ size = dmix->appl_ptr - dmix->last_appl_ptr;
+ if (! size)
+ return;
+ if (size >= pcm->boundary / 2)
+ size = pcm->boundary - size;
+
+ /* check the available size in the slave PCM buffer */
+ slave_hw_ptr = dmix->slave_hw_ptr;
+ /* don't write on the last active period - this area may be cleared
+ * by the driver during mix operation...
+ */
+ slave_hw_ptr -= slave_hw_ptr % dmix->slave_period_size;
+ slave_hw_ptr += dmix->slave_buffer_size;
+ if (slave_hw_ptr >= dmix->slave_boundary)
+ slave_hw_ptr -= dmix->slave_boundary;
+ if (slave_hw_ptr < dmix->slave_appl_ptr)
+ slave_size = slave_hw_ptr + (dmix->slave_boundary - dmix->slave_appl_ptr);
+ else
+ slave_size = slave_hw_ptr - dmix->slave_appl_ptr;
+ if (slave_size < size)
+ size = slave_size;
+ if (! size)
+ return;
+
/* add sample areas here */
src_areas = snd_pcm_mmap_areas(pcm);
dst_areas = snd_pcm_mmap_areas(dmix->spcm);
- slave_appl_ptr = dmix->slave_appl_ptr % dmix->shmptr->s.buffer_size;
+ appl_ptr = dmix->last_appl_ptr % pcm->buffer_size;
+ dmix->last_appl_ptr += size;
+ dmix->last_appl_ptr %= pcm->boundary;
+ slave_appl_ptr = dmix->slave_appl_ptr % dmix->slave_buffer_size;
dmix->slave_appl_ptr += size;
- dmix->slave_appl_ptr %= dmix->shmptr->s.boundary;
- while (size > 0) {
- transfer = appl_ptr + size > pcm->buffer_size ? pcm->buffer_size - appl_ptr : size;
- if (slave_appl_ptr + transfer > dmix->shmptr->s.buffer_size)
- transfer = dmix->shmptr->s.buffer_size - slave_appl_ptr;
- if (transfer)
- mix_areas(dmix, src_areas, dst_areas, appl_ptr, slave_appl_ptr, transfer);
- if (transfer >= size)
- return;
+ dmix->slave_appl_ptr %= dmix->slave_boundary;
+ dmix_down_sem(dmix);
+ for (;;) {
+ snd_pcm_uframes_t transfer = size;
+ if (appl_ptr + transfer > pcm->buffer_size)
+ transfer = pcm->buffer_size - appl_ptr;
+ if (slave_appl_ptr + transfer > dmix->slave_buffer_size)
+ transfer = dmix->slave_buffer_size - slave_appl_ptr;
+ mix_areas(dmix, src_areas, dst_areas, appl_ptr, slave_appl_ptr, transfer);
size -= transfer;
+ if (! size)
+ break;
slave_appl_ptr += transfer;
- slave_appl_ptr %= dmix->shmptr->s.buffer_size;
+ slave_appl_ptr %= dmix->slave_buffer_size;
appl_ptr += transfer;
appl_ptr %= pcm->buffer_size;
}
+ dmix_up_sem(dmix);
}
/*
switch (snd_pcm_state(dmix->spcm)) {
case SND_PCM_STATE_DISCONNECTED:
dmix->state = SND_PCM_STATE_DISCONNECTED;
- return -ENOTTY;
+ return -ENODEV;
default:
break;
}
diff = slave_hw_ptr - old_slave_hw_ptr;
if (diff == 0) /* fast path */
return 0;
+ if (dmix->state != SND_PCM_STATE_RUNNING &&
+ dmix->state != SND_PCM_STATE_DRAINING)
+ /* not really started yet - don't update hw_ptr */
+ return 0;
if (diff < 0) {
- slave_hw_ptr += dmix->shmptr->s.boundary;
+ slave_hw_ptr += dmix->slave_boundary;
diff = slave_hw_ptr - old_slave_hw_ptr;
}
dmix->hw_ptr += diff;
dmix->hw_ptr %= pcm->boundary;
if (pcm->stop_threshold >= pcm->boundary) /* don't care */
return 0;
- if ((avail = snd_pcm_mmap_playback_avail(pcm)) >= pcm->stop_threshold) {
+ avail = snd_pcm_mmap_playback_avail(pcm);
+ if (avail > dmix->avail_max)
+ dmix->avail_max = avail;
+ if (avail >= pcm->stop_threshold) {
struct timeval tv;
+ snd_timer_stop(dmix->timer);
gettimeofday(&tv, 0);
dmix->trigger_tstamp.tv_sec = tv.tv_sec;
dmix->trigger_tstamp.tv_nsec = tv.tv_usec * 1000L;
- dmix->state = SND_PCM_STATE_XRUN;
- dmix->avail_max = avail;
- return -EPIPE;
+ if (dmix->state == SND_PCM_STATE_RUNNING) {
+ dmix->state = SND_PCM_STATE_XRUN;
+ return -EPIPE;
+ }
+ dmix->state = SND_PCM_STATE_SETUP;
+ /* clear queue to remove pending poll events */
+ snd_pcm_direct_clear_timer_queue(dmix);
}
- if (avail > dmix->avail_max)
- dmix->avail_max = avail;
return 0;
}
* plugin implementation
*/
-static int snd_pcm_dmix_status(snd_pcm_t *pcm, snd_pcm_status_t * status)
+static snd_pcm_state_t snd_pcm_dmix_state(snd_pcm_t *pcm)
{
snd_pcm_direct_t *dmix = pcm->private_data;
snd_pcm_state_t state;
+ state = snd_pcm_state(dmix->spcm);
+ switch (state) {
+ case SND_PCM_STATE_SUSPENDED:
+ return state;
+ case SND_PCM_STATE_DISCONNECTED:
+ return state;
+ default:
+ break;
+ }
+ if (dmix->state == STATE_RUN_PENDING)
+ return SNDRV_PCM_STATE_RUNNING;
+ return dmix->state;
+}
+
+static int snd_pcm_dmix_status(snd_pcm_t *pcm, snd_pcm_status_t * status)
+{
+ snd_pcm_direct_t *dmix = pcm->private_data;
switch (dmix->state) {
case SNDRV_PCM_STATE_DRAINING:
break;
}
memset(status, 0, sizeof(*status));
- state = snd_pcm_state(dmix->spcm);
- status->state = state == SNDRV_PCM_STATE_RUNNING ? dmix->state : state;
+ status->state = snd_pcm_dmix_state(pcm);
status->trigger_tstamp = dmix->trigger_tstamp;
status->tstamp = snd_pcm_hw_fast_tstamp(dmix->spcm);
status->avail = snd_pcm_mmap_playback_avail(pcm);
return 0;
}
-static snd_pcm_state_t snd_pcm_dmix_state(snd_pcm_t *pcm)
-{
- snd_pcm_direct_t *dmix = pcm->private_data;
- switch (snd_pcm_state(dmix->spcm)) {
- case SND_PCM_STATE_SUSPENDED:
- return SND_PCM_STATE_SUSPENDED;
- case SND_PCM_STATE_DISCONNECTED:
- dmix->state = SND_PCM_STATE_DISCONNECTED;
- return -ENOTTY;
- default:
- break;
- }
- return dmix->state;
-}
-
static int snd_pcm_dmix_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp)
{
snd_pcm_direct_t *dmix = pcm->private_data;
err = snd_pcm_dmix_sync_ptr(pcm);
if (err < 0)
return err;
+ /* fallthru */
case SNDRV_PCM_STATE_PREPARED:
case SNDRV_PCM_STATE_SUSPENDED:
+ case STATE_RUN_PENDING:
*delayp = snd_pcm_mmap_playback_hw_avail(pcm);
return 0;
case SNDRV_PCM_STATE_XRUN:
return -EPIPE;
case SNDRV_PCM_STATE_DISCONNECTED:
- return -ENOTTY;
+ return -ENODEV;
default:
return -EBADFD;
}
switch(dmix->state) {
case SNDRV_PCM_STATE_DRAINING:
case SNDRV_PCM_STATE_RUNNING:
+ /* sync slave PCM */
return snd_pcm_dmix_sync_ptr(pcm);
case SNDRV_PCM_STATE_PREPARED:
case SNDRV_PCM_STATE_SUSPENDED:
+ case STATE_RUN_PENDING:
return 0;
case SNDRV_PCM_STATE_XRUN:
return -EPIPE;
case SNDRV_PCM_STATE_DISCONNECTED:
- return -ENOTTY;
+ return -ENODEV;
default:
return -EBADFD;
}
snd_pcm_direct_t *dmix = pcm->private_data;
snd_pcm_direct_check_interleave(dmix, pcm);
- // assert(pcm->boundary == dmix->shmptr->s.boundary); /* for sure */
dmix->state = SND_PCM_STATE_PREPARED;
- dmix->appl_ptr = 0;
+ dmix->appl_ptr = dmix->last_appl_ptr = 0;
dmix->hw_ptr = 0;
- return 0;
+ return snd_pcm_direct_set_timer_params(dmix);
}
static int snd_pcm_dmix_reset(snd_pcm_t *pcm)
{
snd_pcm_direct_t *dmix = pcm->private_data;
dmix->hw_ptr %= pcm->period_size;
- dmix->appl_ptr = dmix->hw_ptr;
+ dmix->appl_ptr = dmix->last_appl_ptr = dmix->hw_ptr;
+ dmix->slave_appl_ptr = dmix->slave_hw_ptr = *dmix->spcm->hw.ptr;
+ return 0;
+}
+
+static int snd_pcm_dmix_start_timer(snd_pcm_direct_t *dmix)
+{
+ int err;
+
+ snd_pcm_hwsync(dmix->spcm);
dmix->slave_appl_ptr = dmix->slave_hw_ptr = *dmix->spcm->hw.ptr;
+ err = snd_timer_start(dmix->timer);
+ if (err < 0)
+ return err;
+ dmix->state = SND_PCM_STATE_RUNNING;
return 0;
}
if (dmix->state != SND_PCM_STATE_PREPARED)
return -EBADFD;
- err = snd_timer_start(dmix->timer);
- if (err < 0)
- return err;
- dmix->state = SND_PCM_STATE_RUNNING;
- snd_pcm_hwsync(dmix->spcm);
- dmix->slave_appl_ptr = dmix->slave_hw_ptr = *dmix->spcm->hw.ptr;
avail = snd_pcm_mmap_playback_hw_avail(pcm);
- if (avail < 0)
+ if (avail == 0)
+ dmix->state = STATE_RUN_PENDING;
+ else if (avail < 0)
return 0;
- if (avail > (snd_pcm_sframes_t)pcm->buffer_size)
- avail = pcm->buffer_size;
- snd_pcm_dmix_sync_area(pcm, avail);
+ else {
+ if ((err = snd_pcm_dmix_start_timer(dmix)) < 0)
+ return err;
+ snd_pcm_dmix_sync_area(pcm);
+ }
gettimeofday(&tv, 0);
dmix->trigger_tstamp.tv_sec = tv.tv_sec;
dmix->trigger_tstamp.tv_nsec = tv.tv_usec * 1000L;
snd_pcm_direct_t *dmix = pcm->private_data;
if (dmix->state == SND_PCM_STATE_OPEN)
return -EBADFD;
- snd_timer_stop(dmix->timer);
+ snd_pcm_direct_timer_stop(dmix);
dmix->state = SND_PCM_STATE_SETUP;
return 0;
}
if (dmix->state == SND_PCM_STATE_OPEN)
return -EBADFD;
+ if (pcm->mode & SND_PCM_NONBLOCK)
+ return -EAGAIN;
+ if (dmix->state == SND_PCM_STATE_PREPARED) {
+ if (snd_pcm_mmap_playback_hw_avail(pcm) > 0)
+ snd_pcm_dmix_start(pcm);
+ else {
+ snd_pcm_dmix_drop(pcm);
+ return 0;
+ }
+ }
+
+ if (dmix->state == SND_PCM_STATE_XRUN) {
+ snd_pcm_dmix_drop(pcm);
+ return 0;
+ }
+
stop_threshold = pcm->stop_threshold;
if (pcm->stop_threshold > pcm->buffer_size)
pcm->stop_threshold = pcm->buffer_size;
- if (dmix->state == SND_PCM_STATE_PREPARED &&
- snd_pcm_mmap_playback_hw_avail(pcm) > 0)
- snd_pcm_dmix_start(pcm);
- while (dmix->state == SND_PCM_STATE_RUNNING) {
+ dmix->state = SND_PCM_STATE_DRAINING;
+ do {
err = snd_pcm_dmix_sync_ptr(pcm);
- if (err < 0)
- break;
- if (pcm->mode & SND_PCM_NONBLOCK)
- return -EAGAIN;
- snd_pcm_wait(pcm, -1);
- }
+ if (err < 0) {
+ snd_pcm_dmix_drop(pcm);
+ return err;
+ }
+ if (dmix->state == SND_PCM_STATE_DRAINING) {
+ snd_pcm_dmix_sync_area(pcm);
+ snd_pcm_wait_nocheck(pcm, -1);
+ snd_pcm_direct_clear_timer_queue(dmix); /* force poll to wait */
+ }
+ } while (dmix->state == SND_PCM_STATE_DRAINING);
pcm->stop_threshold = stop_threshold;
- return snd_pcm_dmix_drop(pcm);
+ return 0;
}
-static int snd_pcm_dmix_pause(snd_pcm_t *pcm, int enable)
+static int snd_pcm_dmix_pause(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int enable ATTRIBUTE_UNUSED)
{
- snd_pcm_direct_t *dmix = pcm->private_data;
- if (enable) {
- if (dmix->state != SND_PCM_STATE_RUNNING)
- return -EBADFD;
- dmix->state = SND_PCM_STATE_PAUSED;
- snd_timer_stop(dmix->timer);
- } else {
- if (dmix->state != SND_PCM_STATE_PAUSED)
- return -EBADFD;
- dmix->state = SND_PCM_STATE_RUNNING;
- snd_timer_start(dmix->timer);
- }
- return 0;
+ return -EIO;
}
-static snd_pcm_sframes_t snd_pcm_dmix_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+static snd_pcm_sframes_t snd_pcm_dmix_rewind(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_uframes_t frames ATTRIBUTE_UNUSED)
{
#if 0
/* FIXME: substract samples from the mix ring buffer, too? */
return frames;
}
-static int snd_pcm_dmix_resume(snd_pcm_t *pcm)
-{
- snd_pcm_direct_t *dmix = pcm->private_data;
- snd_pcm_resume(dmix->spcm);
- return 0;
-}
-
static snd_pcm_sframes_t snd_pcm_dmix_readi(snd_pcm_t *pcm ATTRIBUTE_UNUSED, void *buffer ATTRIBUTE_UNUSED, snd_pcm_uframes_t size ATTRIBUTE_UNUSED)
{
return -ENODEV;
if (dmix->client)
snd_pcm_direct_client_discard(dmix);
shm_sum_discard(dmix);
- if (snd_pcm_direct_shm_discard(dmix) > 0) {
- if (snd_pcm_direct_semaphore_discard(dmix) < 0)
- snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT);
- } else {
- snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT);
- }
- if (dmix->bindings)
- free(dmix->bindings);
+ snd_pcm_direct_shm_discard(dmix);
+ snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT);
+ free(dmix->bindings);
pcm->private_data = NULL;
free(dmix);
return 0;
default:
break;
}
+ if (! size)
+ return 0;
snd_pcm_mmap_appl_forward(pcm, size);
- if (dmix->state == SND_PCM_STATE_RUNNING) {
- err = snd_pcm_dmix_sync_ptr(pcm);
- if (err < 0)
+ if (dmix->state == STATE_RUN_PENDING) {
+ if ((err = snd_pcm_dmix_start_timer(dmix)) < 0)
return err;
+ } else if (dmix->state == SND_PCM_STATE_RUNNING ||
+ dmix->state == SND_PCM_STATE_DRAINING)
+ snd_pcm_dmix_sync_ptr(pcm);
+ if (dmix->state == SND_PCM_STATE_RUNNING ||
+ dmix->state == SND_PCM_STATE_DRAINING) {
/* ok, we commit the changes after the validation of area */
/* it's intended, although the result might be crappy */
- snd_pcm_dmix_sync_area(pcm, size);
+ snd_pcm_dmix_sync_area(pcm);
+ /* clear timer queue to avoid a bogus return from poll */
+ if (snd_pcm_mmap_playback_avail(pcm) < pcm->avail_min)
+ snd_pcm_direct_clear_timer_queue(dmix);
}
return size;
}
-static snd_pcm_sframes_t snd_pcm_dmix_avail_update(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
+static snd_pcm_sframes_t snd_pcm_dmix_avail_update(snd_pcm_t *pcm)
{
snd_pcm_direct_t *dmix = pcm->private_data;
- int err;
- if (dmix->state == SND_PCM_STATE_RUNNING) {
- err = snd_pcm_dmix_sync_ptr(pcm);
- if (err < 0)
- return err;
- }
+ if (dmix->state == SND_PCM_STATE_RUNNING ||
+ dmix->state == SND_PCM_STATE_DRAINING)
+ snd_pcm_dmix_sync_ptr(pcm);
return snd_pcm_mmap_playback_avail(pcm);
}
+static int snd_pcm_dmix_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents)
+{
+ snd_pcm_direct_t *dmix = pcm->private_data;
+ if (dmix->state == SND_PCM_STATE_RUNNING)
+ snd_pcm_dmix_sync_area(pcm);
+ return snd_pcm_direct_poll_revents(pcm, pfds, nfds, revents);
+}
+
+
static void snd_pcm_dmix_dump(snd_pcm_t *pcm, snd_output_t *out)
{
snd_pcm_direct_t *dmix = pcm->private_data;
snd_output_printf(out, "Direct Stream Mixing PCM\n");
if (pcm->setup) {
- snd_output_printf(out, "\nIts setup is:\n");
+ snd_output_printf(out, "Its setup is:\n");
snd_pcm_dump_setup(pcm, out);
}
if (dmix->spcm)
.dump = snd_pcm_dmix_dump,
.nonblock = snd_pcm_direct_nonblock,
.async = snd_pcm_direct_async,
- .poll_revents = snd_pcm_direct_poll_revents,
.mmap = snd_pcm_direct_mmap,
.munmap = snd_pcm_direct_munmap,
};
.pause = snd_pcm_dmix_pause,
.rewind = snd_pcm_dmix_rewind,
.forward = snd_pcm_dmix_forward,
- .resume = snd_pcm_dmix_resume,
- .poll_ask = NULL,
+ .resume = snd_pcm_direct_resume,
.link_fd = NULL,
.link = NULL,
.unlink = NULL,
.readn = snd_pcm_dmix_readn,
.avail_update = snd_pcm_dmix_avail_update,
.mmap_commit = snd_pcm_dmix_mmap_commit,
+ .poll_descriptors = NULL,
+ .poll_descriptors_count = NULL,
+ .poll_revents = snd_pcm_dmix_poll_revents,
};
/**
* \brief Creates a new dmix PCM
* \param pcmp Returns created PCM handle
* \param name Name of PCM
- * \param ipc_key IPC key for semaphore and shared memory
- * \param ipc_perm IPC permissions for semaphore and shared memory
+ * \param opts Direct PCM configurations
* \param params Parameters for slave
- * \param bindings Channel bindings
- * \param slowptr Slow but more precise pointer updates
* \param root Configuration root
* \param sconf Slave configuration
* \param stream PCM Direction (stream)
* changed in future.
*/
int snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name,
- key_t ipc_key, mode_t ipc_perm,
+ struct snd_pcm_direct_open_conf *opts,
struct slave_params *params,
- snd_config_t *bindings,
- int slowptr,
snd_config_t *root, snd_config_t *sconf,
snd_pcm_stream_t stream, int mode)
{
dmix = calloc(1, sizeof(snd_pcm_direct_t));
if (!dmix) {
ret = -ENOMEM;
- goto _err;
+ goto _err_nosem;
}
- ret = snd_pcm_direct_parse_bindings(dmix, bindings);
+ ret = snd_pcm_direct_parse_bindings(dmix, opts->bindings);
if (ret < 0)
- goto _err;
+ goto _err_nosem;
- dmix->ipc_key = ipc_key;
- dmix->ipc_perm = ipc_perm;
+ dmix->ipc_key = opts->ipc_key;
+ dmix->ipc_perm = opts->ipc_perm;
+ dmix->ipc_gid = opts->ipc_gid;
dmix->semid = -1;
dmix->shmid = -1;
ret = snd_pcm_direct_semaphore_create_or_connect(dmix);
if (ret < 0) {
SNDERR("unable to create IPC semaphore");
- goto _err;
+ goto _err_nosem;
}
ret = snd_pcm_direct_semaphore_down(dmix, DIRECT_IPC_SEM_CLIENT);
if (ret < 0) {
snd_pcm_direct_semaphore_discard(dmix);
if (--fail_sem_loop <= 0)
- goto _err;
+ goto _err_nosem;
continue;
}
break;
pcm->fast_ops = &snd_pcm_dmix_fast_ops;
pcm->private_data = dmix;
dmix->state = SND_PCM_STATE_OPEN;
- dmix->slowptr = slowptr;
+ dmix->slowptr = opts->slowptr;
+ dmix->variable_buffer_size = opts->variable_buffer_size;
dmix->sync_ptr = snd_pcm_dmix_sync_ptr;
if (first_instance) {
- ret = snd_pcm_open_slave(&spcm, root, sconf, stream, mode);
+ /* recursion is already checked in
+ snd_pcm_direct_get_slave_ipc_offset() */
+ ret = snd_pcm_open_slave(&spcm, root, sconf, stream,
+ mode | SND_PCM_NONBLOCK, NULL);
if (ret < 0) {
SNDERR("unable to open slave");
goto _err;
dmix->shmptr->type = spcm->type;
} else {
+ /* up semaphore to avoid deadlock */
+ snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT);
ret = snd_pcm_direct_client_connect(dmix);
if (ret < 0) {
SNDERR("unable to connect client");
- return ret;
+ goto _err_nosem;
}
- ret = snd_pcm_hw_open_fd(&spcm, "dmix_client", dmix->hw_fd, 0, 0);
- if (ret < 0) {
- SNDERR("unable to open hardware");
- goto _err;
- }
-
- spcm->donot_close = 1;
- spcm->setup = 1;
- spcm->buffer_size = dmix->shmptr->s.buffer_size;
- spcm->sample_bits = dmix->shmptr->s.sample_bits;
- spcm->channels = dmix->shmptr->s.channels;
- spcm->format = dmix->shmptr->s.format;
- spcm->boundary = dmix->shmptr->s.boundary;
- spcm->info = dmix->shmptr->s.info;
- ret = snd_pcm_mmap(spcm);
- if (ret < 0) {
- SNDERR("unable to mmap channels");
+ snd_pcm_direct_semaphore_down(dmix, DIRECT_IPC_SEM_CLIENT);
+ ret = snd_pcm_direct_open_secondary_client(&spcm, dmix, "dmix_client");
+ if (ret < 0)
goto _err;
- }
dmix->spcm = spcm;
}
return 0;
_err:
+ if (dmix->timer)
+ snd_timer_close(dmix->timer);
+ if (dmix->server)
+ snd_pcm_direct_server_discard(dmix);
+ if (dmix->client)
+ snd_pcm_direct_client_discard(dmix);
+ if (spcm)
+ snd_pcm_close(spcm);
+ if (dmix->u.dmix.shmid_sum >= 0)
+ shm_sum_discard(dmix);
+ if (dmix->shmid >= 0)
+ snd_pcm_direct_shm_discard(dmix);
+ if (snd_pcm_direct_semaphore_discard(dmix) < 0)
+ snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT);
+ _err_nosem:
if (dmix) {
- if (dmix->timer)
- snd_timer_close(dmix->timer);
- if (dmix->server)
- snd_pcm_direct_server_discard(dmix);
- if (dmix->client)
- snd_pcm_direct_client_discard(dmix);
- if (spcm)
- snd_pcm_close(spcm);
- if (dmix->u.dmix.shmid_sum >= 0)
- shm_sum_discard(dmix);
- if (dmix->shmid >= 0) {
- if (snd_pcm_direct_shm_discard(dmix) > 0) {
- if (dmix->semid >= 0) {
- if (snd_pcm_direct_semaphore_discard(dmix) < 0)
- snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT);
- }
- }
- }
- if (dmix->bindings)
- free(dmix->bindings);
+ free(dmix->bindings);
free(dmix);
}
if (pcm)
snd_config_t *root, snd_config_t *conf,
snd_pcm_stream_t stream, int mode)
{
- snd_config_iterator_t i, next;
- snd_config_t *slave = NULL, *bindings = NULL, *sconf;
+ snd_config_t *sconf;
struct slave_params params;
- int bsize, psize, ipc_key_add_uid = 0, slowptr = 0;
- key_t ipc_key = 0;
- mode_t ipc_perm = 0600;
+ struct snd_pcm_direct_open_conf dopen;
+ int bsize, psize;
+ int ipc_offset;
int err;
- snd_config_for_each(i, next, conf) {
- snd_config_t *n = snd_config_iterator_entry(i);
- const char *id;
- if (snd_config_get_id(n, &id) < 0)
- continue;
- if (snd_pcm_conf_generic_id(id))
- continue;
- if (strcmp(id, "ipc_key") == 0) {
- long key;
- err = snd_config_get_integer(n, &key);
- if (err < 0) {
- SNDERR("The field ipc_key must be an integer type");
- return err;
- }
- ipc_key = key;
- continue;
- }
- if (strcmp(id, "ipc_perm") == 0) {
- char *perm;
- char *endp;
- err = snd_config_get_ascii(n, &perm);
- if (err < 0) {
- SNDERR("The field ipc_perm must be a valid file permission");
- return err;
- }
- if (isdigit(*perm) == 0) {
- SNDERR("The field ipc_perm must be a valid file permission");
- return -EINVAL;
- }
- ipc_perm = strtol(perm, &endp, 8);
- continue;
- }
- if (strcmp(id, "ipc_key_add_uid") == 0) {
- if ((err = snd_config_get_bool(n)) < 0) {
- SNDERR("The field ipc_key_add_uid must be a boolean type");
- return err;
- }
- ipc_key_add_uid = err;
- continue;
- }
- if (strcmp(id, "slave") == 0) {
- slave = n;
- continue;
- }
- if (strcmp(id, "bindings") == 0) {
- bindings = n;
- continue;
- }
- if (strcmp(id, "slowptr") == 0) {
- err = snd_config_get_bool(n);
- if (err < 0)
- return err;
- slowptr = err;
- continue;
- }
- SNDERR("Unknown field %s", id);
- return -EINVAL;
- }
- if (!slave) {
- SNDERR("slave is not defined");
- return -EINVAL;
- }
- if (ipc_key_add_uid)
- ipc_key += getuid();
- if (!ipc_key) {
- SNDERR("Unique IPC key is not defined");
- return -EINVAL;
- }
+
+ err = snd_pcm_direct_parse_open_conf(conf, &dopen);
+ if (err < 0)
+ return err;
+
/* the default settings, it might be invalid for some hardware */
params.format = SND_PCM_FORMAT_S16;
params.rate = 48000;
bsize = psize = -1;
params.periods = 3;
- err = snd_pcm_slave_conf(root, slave, &sconf, 8,
+ err = snd_pcm_slave_conf(root, dopen.slave, &sconf, 8,
SND_PCM_HW_PARAM_FORMAT, 0, ¶ms.format,
SND_PCM_HW_PARAM_RATE, 0, ¶ms.rate,
SND_PCM_HW_PARAM_CHANNELS, 0, ¶ms.channels,
params.period_time = 125000; /* 0.125 seconds */
/* sorry, limited features */
- if (params.format != SND_PCM_FORMAT_S16 &&
- params.format != SND_PCM_FORMAT_S32) {
- SNDERR("invalid format, specify s16 or s32");
+ if (! (dmix_supported_format & (1ULL << params.format))) {
+ SNDERR("Unsupported format");
snd_config_delete(sconf);
return -EINVAL;
}
params.period_size = psize;
params.buffer_size = bsize;
- err = snd_pcm_dmix_open(pcmp, name, ipc_key, ipc_perm, ¶ms, bindings, slowptr, root, sconf, stream, mode);
+ ipc_offset = snd_pcm_direct_get_slave_ipc_offset(root, sconf, stream);
+ if (ipc_offset < 0) {
+ snd_config_delete(sconf);
+ return ipc_offset;
+ }
+ dopen.ipc_key += ipc_offset;
+
+ err = snd_pcm_dmix_open(pcmp, name, &dopen, ¶ms,
+ root, sconf, stream, mode);
if (err < 0)
snd_config_delete(sconf);
return err;