OSDN Git Service

dmix - Allow more flexible buffer sizes
[android-x86/external-alsa-lib.git] / src / pcm / pcm_dmix.c
index 415f75e..54706af 100644 (file)
@@ -34,6 +34,7 @@
 #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;
@@ -67,11 +74,12 @@ static int shm_sum_create_or_connect(snd_pcm_direct_t *dmix)
               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 */
@@ -79,10 +87,20 @@ retryshm:
                    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;
@@ -141,7 +159,8 @@ static void mix_areas(snd_pcm_direct_t *dmix,
        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) {
@@ -169,7 +188,8 @@ static void mix_areas(snd_pcm_direct_t *dmix,
                        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) {
@@ -197,43 +217,113 @@ static void mix_areas(snd_pcm_direct_t *dmix,
                        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);
 }
 
 /*
@@ -248,7 +338,7 @@ static int snd_pcm_dmix_sync_ptr(snd_pcm_t *pcm)
        switch (snd_pcm_state(dmix->spcm)) {
        case SND_PCM_STATE_DISCONNECTED:
                dmix->state = SND_PCM_STATE_DISCONNECTED;
-               return -ENOTTY;
+               return -ENODEV;
        default:
                break;
        }
@@ -259,25 +349,35 @@ static int snd_pcm_dmix_sync_ptr(snd_pcm_t *pcm)
        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;
 }
 
@@ -285,10 +385,27 @@ static int snd_pcm_dmix_sync_ptr(snd_pcm_t *pcm)
  *  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:
@@ -299,8 +416,7 @@ static int snd_pcm_dmix_status(snd_pcm_t *pcm, snd_pcm_status_t * status)
                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);
@@ -309,21 +425,6 @@ static int snd_pcm_dmix_status(snd_pcm_t *pcm, snd_pcm_status_t * status)
        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;
@@ -335,14 +436,16 @@ static int snd_pcm_dmix_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp)
                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;
        }
@@ -355,14 +458,16 @@ static int snd_pcm_dmix_hwsync(snd_pcm_t *pcm)
        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;
        }
@@ -373,19 +478,31 @@ static int snd_pcm_dmix_prepare(snd_pcm_t *pcm)
        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;
 }
 
@@ -398,18 +515,16 @@ static int snd_pcm_dmix_start(snd_pcm_t *pcm)
        
        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;
@@ -421,7 +536,7 @@ static int snd_pcm_dmix_drop(snd_pcm_t *pcm)
        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;
 }
@@ -434,42 +549,48 @@ static int snd_pcm_dmix_drain(snd_pcm_t *pcm)
 
        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? */
@@ -493,13 +614,6 @@ static snd_pcm_sframes_t snd_pcm_dmix_forward(snd_pcm_t *pcm, snd_pcm_uframes_t
        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;
@@ -523,14 +637,9 @@ static int snd_pcm_dmix_close(snd_pcm_t *pcm)
        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;
@@ -551,38 +660,53 @@ static snd_pcm_sframes_t snd_pcm_dmix_mmap_commit(snd_pcm_t *pcm,
        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)
@@ -600,7 +724,6 @@ static snd_pcm_ops_t snd_pcm_dmix_ops = {
        .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,
 };
@@ -618,8 +741,7 @@ static snd_pcm_fast_ops_t snd_pcm_dmix_fast_ops = {
        .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,
@@ -629,17 +751,17 @@ static snd_pcm_fast_ops_t snd_pcm_dmix_fast_ops = {
        .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)
@@ -650,10 +772,8 @@ static snd_pcm_fast_ops_t snd_pcm_dmix_fast_ops = {
  *          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)
 {
@@ -672,15 +792,16 @@ int snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name,
        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;
 
@@ -693,13 +814,13 @@ int snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name,
                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;
@@ -715,11 +836,15 @@ int snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name,
        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;
@@ -749,31 +874,18 @@ int snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name,
 
                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;
        }
 
@@ -807,27 +919,23 @@ int snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name,
        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)
@@ -966,81 +1074,17 @@ int _snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name,
                       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;
@@ -1050,7 +1094,7 @@ int _snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name,
        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, &params.format,
                                 SND_PCM_HW_PARAM_RATE, 0, &params.rate,
                                 SND_PCM_HW_PARAM_CHANNELS, 0, &params.channels,
@@ -1067,9 +1111,8 @@ int _snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name,
                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;
        }
@@ -1077,7 +1120,15 @@ int _snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name,
        params.period_size = psize;
        params.buffer_size = bsize;
 
-       err = snd_pcm_dmix_open(pcmp, name, ipc_key, ipc_perm, &params, 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, &params,
+                               root, sconf, stream, mode);
        if (err < 0)
                snd_config_delete(sconf);
        return err;