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;
/*
* 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;
}
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;
// 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;
}
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:
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);
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;
}
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:
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);
}
{
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;
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;
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;
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)
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)
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);
}