From 8b2c68af0ef72631ddaf5f9e49ce18846e68aa07 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Wed, 16 Mar 2016 22:54:27 +0100 Subject: [PATCH] [alsa] Try to fix issue where devices with small buffers would overrun, rendering the device unusable because snd_pcm_writei() starts blocking. This is just a poor temporary fix until we either start using SND_PCM_NONBLOCK or put alsa in it's own thread or implement Pulseaudio... --- src/laudio_alsa.c | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/src/laudio_alsa.c b/src/laudio_alsa.c index 17b0b79f..731332af 100644 --- a/src/laudio_alsa.c +++ b/src/laudio_alsa.c @@ -52,6 +52,7 @@ static uint64_t pcm_start_pos; static int pcm_last_error; static int pcm_recovery; static int pcm_buf_threshold; +static int pcm_period_size; static struct pcm_packet *pcm_pkt_head; static struct pcm_packet *pcm_pkt_tail; @@ -213,7 +214,7 @@ laudio_alsa_write(uint8_t *buf, uint64_t rtptime) return; } - else if ((pcm_status != LAUDIO_RUNNING) && (pcm_pos + pcm_buf_threshold >= pcm_start_pos)) + else if ((pcm_status != LAUDIO_RUNNING) && (pcm_pos >= pcm_start_pos)) { /* Kill threshold */ ret = laudio_alsa_set_start_threshold(0); @@ -283,9 +284,8 @@ laudio_alsa_write(uint8_t *buf, uint64_t rtptime) } /* Don't let ALSA fill up the buffer too much */ -// Disabled - seems to cause buffer underruns -// if (nsamp == AIRTUNES_V2_PACKET_SAMPLES) -// return; + if (nsamp == AIRTUNES_V2_PACKET_SAMPLES) + return; } } @@ -358,17 +358,16 @@ laudio_alsa_start(uint64_t cur_pos, uint64_t next_pkt) } DPRINTF(E_DBG, L_LAUDIO, "Start local audio curpos %" PRIu64 ", next_pkt %" PRIu64 "\n", cur_pos, next_pkt); - DPRINTF(E_DBG, L_LAUDIO, "PCM will start after %d samples (%d packets)\n", pcm_buf_threshold, pcm_buf_threshold / AIRTUNES_V2_PACKET_SAMPLES); /* Make pcm_pos the rtptime of the packet containing cur_pos */ pcm_pos = next_pkt; while (pcm_pos > cur_pos) pcm_pos -= AIRTUNES_V2_PACKET_SAMPLES; - pcm_start_pos = next_pkt + pcm_buf_threshold; + pcm_start_pos = next_pkt + pcm_period_size; - /* Compensate threshold, as it's taken into account by snd_pcm_delay() */ - //pcm_pos += pcm_buf_threshold; + /* Compensate period size, otherwise get_pos won't be correct */ + pcm_pos += pcm_period_size; DPRINTF(E_DBG, L_LAUDIO, "PCM pos %" PRIu64 ", start pos %" PRIu64 "\n", pcm_pos, pcm_start_pos); @@ -378,6 +377,7 @@ laudio_alsa_start(uint64_t cur_pos, uint64_t next_pkt) pcm_last_error = 0; pcm_recovery = 0; + // alsa doesn't actually seem to wait for this threshold? ret = laudio_alsa_set_start_threshold(pcm_buf_threshold); if (ret < 0) { @@ -532,6 +532,7 @@ laudio_alsa_open(void) { snd_pcm_hw_params_t *hw_params; snd_pcm_uframes_t bufsize; + snd_pcm_uframes_t period_size; int ret; hw_params = NULL; @@ -611,7 +612,24 @@ laudio_alsa_open(void) goto out_fail; } - DPRINTF(E_DBG, L_LAUDIO, "Buffer size is %lu samples\n", bufsize); + // With a small period size we seem to get underruns because the period time + // passes before we manage to feed with samples (if the player is slightly + // behind - especially critical during startup when the buffer is low) + // Internet suggests period_size should be /2 bufsize, but default seems to be + // much lower, so compromise on /4 (but not more than 65536 frames = almost 2 sec). + period_size = bufsize / 4; + if (period_size > 65536) + period_size = 65536; + + ret = snd_pcm_hw_params_set_period_size_near(hdl, hw_params, &period_size, NULL); + if (ret < 0) + { + DPRINTF(E_LOG, L_LAUDIO, "Could not set period size: %s\n", snd_strerror(ret)); + + goto out_fail; + } + + DPRINTF(E_DBG, L_LAUDIO, "Buffer size is %lu samples, period size is %lu samples\n", bufsize, period_size); ret = snd_pcm_hw_params(hdl, hw_params); if (ret < 0) @@ -627,7 +645,8 @@ laudio_alsa_open(void) pcm_pos = 0; pcm_last_error = 0; pcm_recovery = 0; - pcm_buf_threshold = (bufsize / AIRTUNES_V2_PACKET_SAMPLES) * AIRTUNES_V2_PACKET_SAMPLES; + pcm_buf_threshold = ((bufsize - period_size) / AIRTUNES_V2_PACKET_SAMPLES) * AIRTUNES_V2_PACKET_SAMPLES; + pcm_period_size = period_size; ret = mixer_open(); if (ret < 0)