path: root/audio
diff options
authorwm4 <wm4@nowhere>2016-06-12 21:05:10 +0200
committerwm4 <wm4@nowhere>2016-06-12 21:05:10 +0200
commitb00eab525afb65626b454557824f3f5c9a22b369 (patch)
tree5d589fff29fd405d040f96edd76dd2764587f559 /audio
parent972ea9ca5913a175e50eb36944ca0aa7224d9361 (diff)
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.)
Diffstat (limited to 'audio')
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;
- 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) {
} 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);