]> git.alsa-project.org Git - alsa-lib.git/commitdiff
Fix dshare plugin codes
authorTakashi Iwai <tiwai@suse.de>
Mon, 23 May 2005 08:51:59 +0000 (08:51 +0000)
committerTakashi Iwai <tiwai@suse.de>
Mon, 23 May 2005 08:51:59 +0000 (08:51 +0000)
Modify dshare plugin codes to follow the recent change of dmix:
proper XRUN and poll handling, async data transfer.

src/pcm/pcm_dshare.c

index c1181f95264bcb7da1d09e9fcecdca89c966368d..097e2ba6a319ebc88eae53fc8eefb20b9748309d 100644 (file)
@@ -49,6 +49,9 @@
 const char *_snd_module_pcm_dshare = "";
 #endif
 
+/* start is pending - this state happens when rate plugin does a delayed commit */
+#define STATE_RUN_PENDING      1024
+
 static void do_silence(snd_pcm_t *pcm)
 {
        snd_pcm_direct_t *dshare = pcm->private_data;
@@ -94,30 +97,51 @@ static void share_areas(snd_pcm_direct_t *dshare,
 /*
  *  synchronize shm ring buffer with hardware
  */
-static void snd_pcm_dshare_sync_area(snd_pcm_t *pcm, snd_pcm_uframes_t size)
+static void snd_pcm_dshare_sync_area(snd_pcm_t *pcm)
 {
        snd_pcm_direct_t *dshare = pcm->private_data;
-       snd_pcm_uframes_t appl_ptr, slave_appl_ptr, transfer;
+       snd_pcm_uframes_t appl_ptr, slave_appl_ptr, slave_bsize;
+       snd_pcm_uframes_t size, slave_hw_ptr;
        const snd_pcm_channel_area_t *src_areas, *dst_areas;
        
-       /* get the start of update area */
-       appl_ptr = dshare->appl_ptr - size;
-       if (appl_ptr > pcm->boundary)
-               appl_ptr += pcm->boundary;
-       appl_ptr %= pcm->buffer_size;
+       /* calculate the size to transfer */
+       size = dshare->appl_ptr - dshare->last_appl_ptr;
+       if (! size)
+               return;
+       slave_bsize = dshare->shmptr->s.buffer_size;
+       slave_hw_ptr = dshare->slave_hw_ptr;
+       /* don't write on the last active period - this area may be cleared
+        * by the driver during write operation...
+        */
+       slave_hw_ptr -= slave_hw_ptr % dshare->shmptr->s.period_size;
+       slave_hw_ptr += slave_bsize;
+       if (dshare->slave_hw_ptr > dshare->slave_appl_ptr)
+               slave_hw_ptr -= dshare->shmptr->s.boundary;
+       if (dshare->slave_appl_ptr + size >= slave_hw_ptr)
+               size = slave_hw_ptr - dshare->slave_appl_ptr;
+       if (! size)
+               return;
        /* add sample areas here */
        src_areas = snd_pcm_mmap_areas(pcm);
        dst_areas = snd_pcm_mmap_areas(dshare->spcm);
-       slave_appl_ptr = dshare->slave_appl_ptr % dshare->shmptr->s.buffer_size;
+       appl_ptr = dshare->last_appl_ptr % pcm->buffer_size;
+       dshare->last_appl_ptr += size;
+       dshare->last_appl_ptr %= pcm->boundary;
+       slave_appl_ptr = dshare->slave_appl_ptr % slave_bsize;
        dshare->slave_appl_ptr += size;
        dshare->slave_appl_ptr %= dshare->shmptr->s.boundary;
-       while (size > 0) {
-               transfer = appl_ptr + size > pcm->buffer_size ? pcm->buffer_size - appl_ptr : size;
-               transfer = slave_appl_ptr + transfer > dshare->shmptr->s.buffer_size ? dshare->shmptr->s.buffer_size - slave_appl_ptr : transfer;
-               size -= transfer;
+       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 > slave_bsize)
+                       transfer = slave_bsize - slave_appl_ptr;
                share_areas(dshare, src_areas, dst_areas, appl_ptr, slave_appl_ptr, transfer);
+               size -= transfer;
+               if (! size)
+                       break;
                slave_appl_ptr += transfer;
-               slave_appl_ptr %= dshare->shmptr->s.buffer_size;
+               slave_appl_ptr %= slave_bsize;
                appl_ptr += transfer;
                appl_ptr %= pcm->buffer_size;
        }
@@ -146,6 +170,10 @@ static int snd_pcm_dshare_sync_ptr(snd_pcm_t *pcm)
        diff = slave_hw_ptr - old_slave_hw_ptr;
        if (diff == 0)          /* fast path */
                return 0;
+       if (dshare->state != SND_PCM_STATE_RUNNING &&
+           dshare->state != SND_PCM_STATE_DRAINING)
+               /* not really started yet - don't update hw_ptr */
+               return 0;
        if (diff < 0) {
                slave_hw_ptr += dshare->shmptr->s.boundary;
                diff = slave_hw_ptr - old_slave_hw_ptr;
@@ -155,17 +183,23 @@ static int snd_pcm_dshare_sync_ptr(snd_pcm_t *pcm)
        // printf("sync ptr diff = %li\n", diff);
        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 > dshare->avail_max)
+               dshare->avail_max = avail;
+       if (avail >= pcm->stop_threshold) {
                struct timeval tv;
+               snd_timer_stop(dshare->timer);
                gettimeofday(&tv, 0);
                dshare->trigger_tstamp.tv_sec = tv.tv_sec;
                dshare->trigger_tstamp.tv_nsec = tv.tv_usec * 1000L;
-               dshare->state = SND_PCM_STATE_XRUN;
-               dshare->avail_max = avail;
-               return -EPIPE;
+               if (dshare->state == SND_PCM_STATE_RUNNING) {
+                       dshare->state = SND_PCM_STATE_XRUN;
+                       return -EPIPE;
+               }
+               dshare->state = SND_PCM_STATE_SETUP;
+               /* clear queue to remove pending poll events */
+               snd_pcm_direct_clear_timer_queue(dshare);
        }
-       if (avail > dshare->avail_max)
-               dshare->avail_max = avail;
        return 0;
 }
 
@@ -176,7 +210,6 @@ static int snd_pcm_dshare_sync_ptr(snd_pcm_t *pcm)
 static int snd_pcm_dshare_status(snd_pcm_t *pcm, snd_pcm_status_t * status)
 {
        snd_pcm_direct_t *dshare = pcm->private_data;
-       snd_pcm_state_t state;
 
        switch (dshare->state) {
        case SNDRV_PCM_STATE_DRAINING:
@@ -187,8 +220,7 @@ static int snd_pcm_dshare_status(snd_pcm_t *pcm, snd_pcm_status_t * status)
                break;
        }
        memset(status, 0, sizeof(*status));
-       state = snd_pcm_state(dshare->spcm);
-       status->state = state == SND_PCM_STATE_RUNNING ? dshare->state : state;
+       status->state = snd_pcm_state(dshare->spcm);
        status->trigger_tstamp = dshare->trigger_tstamp;
        status->tstamp = snd_pcm_hw_fast_tstamp(dshare->spcm);
        status->avail = snd_pcm_mmap_playback_avail(pcm);
@@ -204,11 +236,12 @@ static snd_pcm_state_t snd_pcm_dshare_state(snd_pcm_t *pcm)
        case SND_PCM_STATE_SUSPENDED:
                return SND_PCM_STATE_SUSPENDED;
        case SND_PCM_STATE_DISCONNECTED:
-               dshare->state = SNDRV_PCM_STATE_DISCONNECTED;
-               return -ENOTTY;
+               return SND_PCM_STATE_DISCONNECTED;
        default:
                break;
        }
+       if (dshare->state == STATE_RUN_PENDING)
+               return SNDRV_PCM_STATE_RUNNING;
        return dshare->state;
 }
 
@@ -223,8 +256,10 @@ static int snd_pcm_dshare_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp)
                err = snd_pcm_dshare_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:
@@ -263,7 +298,7 @@ static int snd_pcm_dshare_prepare(snd_pcm_t *pcm)
        snd_pcm_direct_check_interleave(dshare, pcm);
        // assert(pcm->boundary == dshare->shmptr->s.boundary); /* for sure */
        dshare->state = SND_PCM_STATE_PREPARED;
-       dshare->appl_ptr = 0;
+       dshare->appl_ptr = dshare->last_appl_ptr = 0;
        dshare->hw_ptr = 0;
        return snd_pcm_direct_set_timer_params(dshare);
 }
@@ -272,11 +307,24 @@ static int snd_pcm_dshare_reset(snd_pcm_t *pcm)
 {
        snd_pcm_direct_t *dshare = pcm->private_data;
        dshare->hw_ptr %= pcm->period_size;
-       dshare->appl_ptr = dshare->hw_ptr;
+       dshare->appl_ptr = dshare->last_appl_ptr = dshare->hw_ptr;
        dshare->slave_appl_ptr = dshare->slave_hw_ptr = *dshare->spcm->hw.ptr;
        return 0;
 }
 
+static int snd_pcm_dshare_start_timer(snd_pcm_direct_t *dshare)
+{
+       int err;
+
+       snd_pcm_hwsync(dshare->spcm);
+       dshare->slave_appl_ptr = dshare->slave_hw_ptr = *dshare->spcm->hw.ptr;
+       err = snd_timer_start(dshare->timer);
+       if (err < 0)
+               return err;
+       dshare->state = SND_PCM_STATE_RUNNING;
+       return 0;
+}
+
 static int snd_pcm_dshare_start(snd_pcm_t *pcm)
 {
        snd_pcm_direct_t *dshare = pcm->private_data;
@@ -286,18 +334,16 @@ static int snd_pcm_dshare_start(snd_pcm_t *pcm)
        
        if (dshare->state != SND_PCM_STATE_PREPARED)
                return -EBADFD;
-       snd_pcm_hwsync(dshare->spcm);
-       dshare->slave_appl_ptr = dshare->slave_hw_ptr = *dshare->spcm->hw.ptr;
-       err = snd_timer_start(dshare->timer);
-       if (err < 0)
-               return err;
-       dshare->state = SND_PCM_STATE_RUNNING;
        avail = snd_pcm_mmap_playback_hw_avail(pcm);
-       if (avail < 0)
+       if (avail == 0)
+               dshare->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_dshare_sync_area(pcm, avail);
+       else {
+               if ((err = snd_pcm_dshare_start_timer(dshare)) < 0)
+                       return err;
+               snd_pcm_dshare_sync_area(pcm);
+       }
        gettimeofday(&tv, 0);
        dshare->trigger_tstamp.tv_sec = tv.tv_sec;
        dshare->trigger_tstamp.tv_nsec = tv.tv_usec * 1000L;
@@ -309,7 +355,7 @@ static int snd_pcm_dshare_drop(snd_pcm_t *pcm)
        snd_pcm_direct_t *dshare = pcm->private_data;
        if (dshare->state == SND_PCM_STATE_OPEN)
                return -EBADFD;
-       snd_timer_stop(dshare->timer);
+       snd_pcm_direct_timer_stop(dshare);
        do_silence(pcm);
        dshare->state = SND_PCM_STATE_SETUP;
        return 0;
@@ -323,22 +369,34 @@ static int snd_pcm_dshare_drain(snd_pcm_t *pcm)
 
        if (dshare->state == SND_PCM_STATE_OPEN)
                return -EBADFD;
+       if (pcm->mode & SND_PCM_NONBLOCK)
+               return -EAGAIN;
+       if (dshare->state == SND_PCM_STATE_PREPARED) {
+               if (snd_pcm_mmap_playback_hw_avail(pcm) > 0)
+                       snd_pcm_dshare_start(pcm);
+               else {
+                       snd_pcm_dshare_drop(pcm);
+                       return 0;
+               }
+       }
        stop_threshold = pcm->stop_threshold;
        if (pcm->stop_threshold > pcm->buffer_size)
                pcm->stop_threshold = pcm->buffer_size;
-       if (dshare->state == SND_PCM_STATE_PREPARED &&
-           snd_pcm_mmap_playback_hw_avail(pcm) > 0)
-               snd_pcm_dshare_start(pcm);
-       while (dshare->state == SND_PCM_STATE_RUNNING) {
+       dshare->state = SND_PCM_STATE_DRAINING;
+       do {
                err = snd_pcm_dshare_sync_ptr(pcm);
-               if (err < 0)
+               if (err < 0) {
+                       snd_pcm_dshare_drop(pcm);
                        break;
-               if (pcm->mode & SND_PCM_NONBLOCK)
-                       return -EAGAIN;
-               snd_pcm_wait(pcm, -1);
-       }
+               }
+               if (dshare->state == SND_PCM_STATE_DRAINING) {
+                       snd_pcm_dshare_sync_area(pcm);
+                       snd_pcm_wait_nocheck(pcm, -1);
+                       snd_pcm_direct_clear_timer_queue(dshare); /* force poll to wait */
+               }
+       } while (dshare->state == SND_PCM_STATE_DRAINING);
        pcm->stop_threshold = stop_threshold;
-       return snd_pcm_dshare_drop(pcm);
+       return 0;
 }
 
 static int snd_pcm_dshare_pause(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int enable ATTRIBUTE_UNUSED)
@@ -346,18 +404,22 @@ static int snd_pcm_dshare_pause(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int enable ATTR
        return -EIO;
 }
 
-static snd_pcm_sframes_t snd_pcm_dshare_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+static snd_pcm_sframes_t snd_pcm_dshare_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? */
        snd_pcm_mmap_appl_backward(pcm, frames);
        return frames;
+#else
+       return -EIO;
+#endif
 }
 
 static snd_pcm_sframes_t snd_pcm_dshare_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
 {
        snd_pcm_sframes_t avail;
 
-       avail = snd_pcm_mmap_avail(pcm);
+       avail = snd_pcm_mmap_playback_avail(pcm);
        if (avail < 0)
                return 0;
        if (frames > (snd_pcm_uframes_t)avail)
@@ -425,28 +487,34 @@ static snd_pcm_sframes_t snd_pcm_dshare_mmap_commit(snd_pcm_t *pcm,
        default:
                break;
        }
+       if (! size)
+               return 0;
        snd_pcm_mmap_appl_forward(pcm, size);
-       if (dshare->state == SND_PCM_STATE_RUNNING) {
-               err = snd_pcm_dshare_sync_ptr(pcm);
-               if (err < 0)
+       if (dshare->state == STATE_RUN_PENDING) {
+               if ((err = snd_pcm_dshare_start_timer(dshare)) < 0)
                        return err;
+       } else if (dshare->state == SND_PCM_STATE_RUNNING ||
+                  dshare->state == SND_PCM_STATE_DRAINING)
+               snd_pcm_dshare_sync_ptr(pcm);
+       if (dshare->state == SND_PCM_STATE_RUNNING ||
+           dshare->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_dshare_sync_area(pcm, size);
+               snd_pcm_dshare_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(dshare);
        }
        return size;
 }
 
-static snd_pcm_sframes_t snd_pcm_dshare_avail_update(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
+static snd_pcm_sframes_t snd_pcm_dshare_avail_update(snd_pcm_t *pcm)
 {
        snd_pcm_direct_t *dshare = pcm->private_data;
-       int err;
        
-       if (dshare->state == SND_PCM_STATE_RUNNING) {
-               err = snd_pcm_dshare_sync_ptr(pcm);
-               if (err < 0)
-                       return err;
-       }
+       if (dshare->state == SND_PCM_STATE_RUNNING ||
+           dshare->state == SND_PCM_STATE_DRAINING)
+               snd_pcm_dshare_sync_ptr(pcm);
        return snd_pcm_mmap_playback_avail(pcm);
 }