Skip to content

Commit

Permalink
pcm: ioplug: Implement proper drain behavior
Browse files Browse the repository at this point in the history
This patch fixes the draining behavior of ioplug in the following
ways:

- When no draining ioplug callback is defined, implement the draining
  loop using snd_pcm_wait*() and sync with the drain finishes.
  This is equivalent with the implementation in the kernel write().
  Similarly as in kernel code, for non-blocking mode, it returns
  immediately after setting DRAINING state.

- The hw_ptr update function checks the PCM state and stops the stream
  if the draining finishes.

- When draining ioplug callback is defined, leave the whole draining
  operation to it.  The callback is supposed to return -EAGAIN for
  non-blocking case, too.

- When an error happens during draining, it drops the stream, for a
  safety reason.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
  • Loading branch information
tiwai committed Mar 29, 2018
1 parent d3d42f6 commit ce2095c
Showing 1 changed file with 67 additions and 9 deletions.
76 changes: 67 additions & 9 deletions src/pcm/pcm_ioplug.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ typedef struct snd_pcm_ioplug_priv {
snd_htimestamp_t trigger_tstamp;
} ioplug_priv_t;

static int snd_pcm_ioplug_drop(snd_pcm_t *pcm);
static int snd_pcm_ioplug_poll_descriptors_count(snd_pcm_t *pcm);
static int snd_pcm_ioplug_poll_descriptors(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int space);
static int snd_pcm_ioplug_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents);

/* update the hw pointer */
/* called in lock */
static void snd_pcm_ioplug_hw_ptr_update(snd_pcm_t *pcm)
Expand All @@ -57,6 +62,7 @@ static void snd_pcm_ioplug_hw_ptr_update(snd_pcm_t *pcm)
hw = io->data->callback->pointer(io->data);
if (hw >= 0) {
snd_pcm_uframes_t delta;
snd_pcm_uframes_t avail;

if ((snd_pcm_uframes_t)hw >= io->last_hw)
delta = hw - io->last_hw;
Expand All @@ -67,9 +73,19 @@ static void snd_pcm_ioplug_hw_ptr_update(snd_pcm_t *pcm)
delta = wrap_point + hw - io->last_hw;
}
snd_pcm_mmap_hw_forward(io->data->pcm, delta);
/* stop the stream if all samples are drained */
if (io->data->state == SND_PCM_STATE_DRAINING) {
avail = snd_pcm_mmap_avail(pcm);
if (avail >= pcm->buffer_size)
snd_pcm_ioplug_drop(pcm);
}
io->last_hw = (snd_pcm_uframes_t)hw;
} else
io->data->state = SNDRV_PCM_STATE_XRUN;
} else {
if (io->data->state == SND_PCM_STATE_DRAINING)
snd_pcm_ioplug_drop(pcm);
else
io->data->state = SNDRV_PCM_STATE_XRUN;
}
}

static int snd_pcm_ioplug_info(snd_pcm_t *pcm, snd_pcm_info_t *info)
Expand Down Expand Up @@ -488,20 +504,62 @@ static int snd_pcm_ioplug_drop(snd_pcm_t *pcm)
return 0;
}

static int ioplug_drain_via_poll(snd_pcm_t *pcm)
{
ioplug_priv_t *io = pcm->private_data;

while (io->data->state == SND_PCM_STATE_DRAINING) {
snd_pcm_ioplug_hw_ptr_update(pcm);
if (io->data->state != SND_PCM_STATE_DRAINING)
break;
/* in non-blocking mode, let application to poll() by itself */
if (io->data->nonblock)
return -EAGAIN;
if (snd_pcm_wait_nocheck(pcm, -1) < 0)
break;
}

return 0; /* force to drop at error */
}

/* need own locking */
static int snd_pcm_ioplug_drain(snd_pcm_t *pcm)
{
ioplug_priv_t *io = pcm->private_data;
int err;
int err = 0;

if (io->data->state == SND_PCM_STATE_OPEN)
snd_pcm_lock(pcm);
switch (io->data->state) {
case SND_PCM_STATE_OPEN:
case SND_PCM_STATE_DISCONNECTED:
case SND_PCM_STATE_SUSPENDED:
return -EBADFD;
case SND_PCM_STATE_PREPARED:
if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
err = snd_pcm_ioplug_start(pcm);
if (err < 0)
goto unlock;
io->data->state = SND_PCM_STATE_DRAINING;
}
break;
case SND_PCM_STATE_RUNNING:
io->data->state = SND_PCM_STATE_DRAINING;
break;
}

io->data->state = SND_PCM_STATE_DRAINING;
if (io->data->callback->drain)
io->data->callback->drain(io->data);
snd_pcm_lock(pcm);
err = snd_pcm_ioplug_drop(pcm);
if (io->data->state == SND_PCM_STATE_DRAINING) {
if (io->data->callback->drain) {
snd_pcm_unlock(pcm); /* let plugin own locking */
err = io->data->callback->drain(io->data);
snd_pcm_lock(pcm);
} else {
err = ioplug_drain_via_poll(pcm);
}
}

unlock:
if (!err && io->data->state != SND_PCM_STATE_SETUP)
snd_pcm_ioplug_drop(pcm);
snd_pcm_unlock(pcm);
return err;
}
Expand Down

0 comments on commit ce2095c

Please sign in to comment.