audio: apply an upper bound timeout when draining

This helps with shitty APIs and even shittier drivers (I'm looking at
you, ALSA). Sometimes they won't send proper wakeups. This can be fine
during playback, when for example playing video, because mpv still will
wakeup the AO outside of its own wakeup mechanisms when sending new data
to it. But when draining, it entirely relies on the driver's wakeup
mechanism. So when the driver wakeup mechanism didn't work, it could
hard freeze while waiting for the audio thread to play the rest of the
data.

Avoid this by waiting for an upper bound. We set this upper bound at the
total mpv audio buffer size plus 1 second. We don't use the get_delay
value, because the audio API could return crap for it, and we're being
paranoid here. I couldn't confirm whether this works correctly, because
my driver issue fixed itself.

(In the case that happened to me, the driver somehow stopped getting
interrupts. aplay froze instead of playing audio, and playing audio-only
files resulted in a chop party. Video worked, for reasons mentioned
above, but drainign froze hard. The driver problem was solved when
closing all audio output streams in the system. Might have been a dmix
related problem too.)
This commit is contained in:
wm4 2016-06-12 21:05:10 +02:00
parent 972ea9ca59
commit b00eab525a
1 changed files with 13 additions and 3 deletions

View File

@ -142,6 +142,7 @@ static void resume(struct ao *ao)
static void drain(struct ao *ao)
{
struct ao_push_state *p = ao->api_priv;
double maxbuffer = ao->buffer / (double)ao->samplerate + 1;
MP_VERBOSE(ao, "draining...\n");
@ -151,14 +152,23 @@ static void drain(struct ao *ao)
p->final_chunk = true;
wakeup_playthread(ao);
while (p->still_playing && mp_audio_buffer_samples(p->buffer) > 0)
pthread_cond_wait(&p->wakeup, &p->lock);
// Wait until everything is done. Since the audio API (especially ALSA)
// can't be trusted to do this right, and we're hard-blocking here, apply
// an upper bound timeout.
struct timespec until = mp_rel_time_to_timespec(maxbuffer);
while (p->still_playing && mp_audio_buffer_samples(p->buffer) > 0) {
if (pthread_cond_timedwait(&p->wakeup, &p->lock, &until)) {
MP_WARN(ao, "Draining is taking too long, aborting.\n");
goto done;
}
}
if (ao->driver->drain) {
ao->driver->drain(ao);
} else {
double time = unlocked_get_delay(ao);
mp_sleep_us(MPMIN(time, ao->buffer / (double)ao->samplerate + 1) * 1e6);
mp_sleep_us(MPMIN(time, maxbuffer) * 1e6);
}
done: