diff options
author | wm4 <wm4@nowhere> | 2016-06-12 21:05:10 +0200 |
---|---|---|
committer | wm4 <wm4@nowhere> | 2016-06-12 21:05:10 +0200 |
commit | b00eab525afb65626b454557824f3f5c9a22b369 (patch) | |
tree | 5d589fff29fd405d040f96edd76dd2764587f559 | |
parent | 972ea9ca5913a175e50eb36944ca0aa7224d9361 (diff) | |
download | mpv-b00eab525afb65626b454557824f3f5c9a22b369.tar.bz2 mpv-b00eab525afb65626b454557824f3f5c9a22b369.tar.xz |
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.)
-rw-r--r-- | audio/out/push.c | 16 |
1 files changed, 13 insertions, 3 deletions
diff --git a/audio/out/push.c b/audio/out/push.c index 8071ad0f8d..ac87c62a96 100644 --- a/audio/out/push.c +++ b/audio/out/push.c @@ -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: |