Skip to content

Commit

Permalink
Improve PCM plugin drain timeout calculation
Browse files Browse the repository at this point in the history
Also adds additional debug logging to the PCM plugin drain function.
  • Loading branch information
borine committed Sep 29, 2023
1 parent ae1f6b5 commit 54c23f1
Showing 1 changed file with 30 additions and 15 deletions.
45 changes: 30 additions & 15 deletions src/asound/bluealsa-pcm.c
Original file line number Diff line number Diff line change
Expand Up @@ -692,29 +692,44 @@ static int bluealsa_drain(snd_pcm_ioplug_t *io) {
snd_pcm_sframes_t hw_ptr;
while ((hw_ptr = bluealsa_pointer(io)) >= 0 && io->state == SND_PCM_STATE_DRAINING) {

/* We set a timeout to ensure that the plugin cannot block forever in
* case the server has stopped reading from the FIFO. There may be a
* wait of up to one period before the IO thread next writes to the
* FIFO, so we allow for that in setting the maximum wait time. If the
* wait is re-started after being interrupted by a signal then we must
* re-calculate the maximum waiting time that remains. */
snd_pcm_uframes_t avail = snd_pcm_ioplug_hw_avail(io, hw_ptr, io->appl_ptr);
int timeout = (avail + io->period_size) * 1000 / io->rate;

/* If the buffer is empty then the local drain is complete. */
if (avail == 0)
break;

/* We set a timeout to ensure that the plugin cannot block forever in
* case the server has stopped reading from the FIFO. Allow enough time
* to drain the available frames as full periods, plus 100 ms:
* e.g. one or less periods in buffer, allow 100ms + one period,
* more than one but not more than two, allow 100ms + two periods,
* etc. If the wait is re-started after being interrupted by a signal
* then we must re-calculate the maximum waiting time that remains. */
int timeout = 100 + (((avail - 1) / io->period_size) + 1) * io->period_size * 1000 / io->rate;

int nready = poll(&pfd, 1, timeout);
if (nready == -1) {
/* It is not well documented by ALSA, but if the application
* has requested that the PCM should be aborted by a signal
* then the ioplug nonblock flag is set to the special value 2. */
if (errno == EINTR && io->nonblock != 2)
continue;
/* Application has aborted the drain. */
aborted = true;
if (errno == EINTR) {
/* It is not well documented by ALSA, but if the application
* has requested that the PCM should be aborted by a signal
* then the ioplug nonblock flag is set to the special value 2
*/
if (io->nonblock != 2)
continue;
/* Application has aborted the drain. */
debug2("Drain aborted by signal");
aborted = true;
}
else
debug2("Drain poll error - %s", strerror(errno));

break;
}
if (nready == 0)
if (nready == 0) {
/* Timeout - do not wait any longer. */
SNDERR("Drain timed out - possible Bluetooth transport failure");
break;
}

if (pfd.revents & POLLIN) {
eventfd_t event;
Expand Down

0 comments on commit 54c23f1

Please sign in to comment.