summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2014-09-06 12:59:04 +0200
committerwm4 <wm4@nowhere>2014-09-06 12:59:04 +0200
commit39609fc19acb8ebe04e476ca7ad964c3f15903e7 (patch)
tree4ccede07615ad547ac2d09342fb28811885df7a1
parent769ac6fb7bb39cb04933e1dede32cdbec4f74098 (diff)
downloadmpv-39609fc19acb8ebe04e476ca7ad964c3f15903e7.tar.bz2
mpv-39609fc19acb8ebe04e476ca7ad964c3f15903e7.tar.xz
audio/out/push: redo audio waiting
Improve the logic how the audio thread decides how to wait until the AO is ready for new data. The previous commit makes some of this easier, although it turned out that it wasn't required, and we still can handle AOs with bad get_space implementation (although the new code prints an error message, and it might fail in obscure situations). The new code is pretty similar to the old one, and the main thing that changes is that complicated conditions are tweaked. AO waiting is now used better (mainly instead of max>0, r>0 is used). Whether to wakeup is reevaluated every time, instead of somehow doing the wrong thing and compensating for it with a flag. This fixes the specific situation when the device buffer is full, and we don't want to buffer more data. In the old code, this wasn't handled correctly: the AO went to sleep forever, because it prevented proper wakeup by the AO driver, and as consequence never asked the core for new data. Commit 4fa3ffeb was a hack-fix against this, and now that we have a proper solution, this hack is removed as well. Also make the refill threshold consistent and always use 1/4 of the buffer. (The threshold is used for situations when an AO doesn't support proper waiting or chunked processing.) This commit will probably cause a bunch of regressions again.
-rw-r--r--audio/out/push.c82
1 files changed, 37 insertions, 45 deletions
diff --git a/audio/out/push.c b/audio/out/push.c
index 5e49595978..85abb6de85 100644
--- a/audio/out/push.c
+++ b/audio/out/push.c
@@ -52,11 +52,9 @@ struct ao_push_state {
bool terminate;
bool drain;
- bool buffers_full;
- bool avoid_ao_wait;
+ bool wait_on_ao;
bool still_playing;
bool need_wakeup;
- bool requested_data;
bool paused;
// Whether the current buffer contains the complete audio.
@@ -177,13 +175,17 @@ static int unlocked_get_space(struct ao *ao)
int space = mp_audio_buffer_get_write_available(p->buffer);
if (ao->driver->get_space) {
// The following code attempts to keep the total buffered audio to
- // def_buffer/2+device_buffer in order to improve latency.
+ // ao->buffer in order to improve latency.
int device_space = ao->driver->get_space(ao);
int device_buffered = ao->device_buffer - device_space;
int soft_buffered = mp_audio_buffer_samples(p->buffer);
- int min_buffer = ao->def_buffer / 2 * ao->samplerate + ao->device_buffer;
- int total_buffer = device_buffered + soft_buffered;
- int missing = min_buffer - total_buffer;
+ // The extra margin helps avoiding too many wakeups if the AO is fully
+ // byte based and doesn't do proper chunked processing.
+ int min_buffer = ao->buffer + 64;
+ int missing = min_buffer - device_buffered - soft_buffered;
+ // But always keep the device's buffer filled as much as we can.
+ int device_missing = device_space - soft_buffered;
+ missing = MPMAX(missing, device_missing);
space = MPMIN(space, missing);
space = MPMAX(0, space);
}
@@ -217,6 +219,8 @@ static int play(struct ao *ao, void **data, int samples, int flags)
int write_samples = mp_audio_buffer_get_write_available(p->buffer);
write_samples = MPMIN(write_samples, samples);
+ MP_TRACE(ao, "samples=%d flags=%d r=%d\n", samples, flags, write_samples);
+
if (write_samples < samples)
flags = flags & ~AOPLAY_FINAL_CHUNK;
bool is_final = flags & AOPLAY_FINAL_CHUNK;
@@ -237,10 +241,8 @@ static int play(struct ao *ao, void **data, int samples, int flags)
// If we don't have new data, the decoder thread basically promises it
// will send new data as soon as it's available.
- if (got_data) {
- p->requested_data = false;
+ if (got_data)
wakeup_playthread(ao);
- }
pthread_mutex_unlock(&p->lock);
return write_samples;
}
@@ -270,8 +272,8 @@ static void ao_play_data(struct ao *ao)
}
r = MPMAX(r, 0);
// Probably can't copy the rest of the buffer due to period alignment.
- bool stuck = r <= 0 && space >= max && data.samples > 0;
- if ((flags & AOPLAY_FINAL_CHUNK) && stuck) {
+ bool stuck_eof = r <= 0 && space >= max && data.samples > 0;
+ if ((flags & AOPLAY_FINAL_CHUNK) && stuck_eof) {
MP_ERR(ao, "Audio output driver seems to ignore AOPLAY_FINAL_CHUNK.\n");
r = max;
}
@@ -281,27 +283,23 @@ static void ao_play_data(struct ao *ao)
if (ao->driver->get_delay)
p->expected_end_time += ao->driver->get_delay(ao);
}
- // In both cases, we have to account for space!=0, but the AO not accepting
- // any new data (due to rounding to period boundaries).
- p->buffers_full = max >= space && r <= 0;
- p->avoid_ao_wait = (max == 0 && space > 0) || p->paused || stuck;
+ // Nothing written, but more input data than space - this must mean the
+ // AO's get_space() doesn't do period alignment correctly.
+ bool stuck = r == 0 && max >= space && space > 0;
+ if (stuck)
+ MP_ERR(ao, "Audio output is reporting incorrect buffer status.\n");
+ // Wait until space becomes available. Also wait if we actually wrote data,
+ // so the AO wakes us up properly if it needs more data.
+ p->wait_on_ao = space == 0 || r > 0 || stuck;
p->still_playing |= r > 0;
- MP_TRACE(ao, "in=%d, space=%d r=%d flags=%d aw=%d full=%d f=%d\n", max,
- space, r, flags, p->avoid_ao_wait, p->buffers_full, p->final_chunk);
-}
-
-// Estimate when the AO needs data again.
-static double ao_estimate_timeout(struct ao *ao)
-{
- struct ao_push_state *p = ao->api_priv;
- double timeout = 0;
- if (p->buffers_full && ao->driver->get_delay) {
- timeout = ao->driver->get_delay(ao) - 0.050;
- // Keep extra safety margin if the buffers are large
- if (timeout > 0.100)
- timeout = MPMAX(timeout - 0.200, 0.100);
- }
- return MPMAX(timeout, ao->device_buffer * 0.25 / ao->samplerate);
+ // If we just filled the AO completely (r == space), don't refill for a
+ // while. Prevents wakeup feedback with byte-granular AOs.
+ int needed = unlocked_get_space(ao);
+ bool more = needed >= (r == space ? ao->device_buffer / 4 : 1) && !stuck;
+ if (more)
+ mp_input_wakeup(ao->input_ctx); // request more data
+ MP_TRACE(ao, "in=%d flags=%d space=%d r=%d wa=%d needed=%d more=%d\n",
+ max, flags, space, r, p->wait_on_ao, needed, more);
}
static void *playthread(void *arg)
@@ -313,16 +311,7 @@ static void *playthread(void *arg)
if (!p->paused)
ao_play_data(ao);
- // Request new data from decoder if buffer goes below "full".
- // Allow a small margin of missing data for AOs that use timeouts.
- double margin = ao->driver->wait ? 0 : ao->device_buffer / 8;
- if (!p->buffers_full && unlocked_get_space(ao) > margin) {
- if (!p->requested_data)
- mp_input_wakeup(ao->input_ctx);
- p->requested_data = true;
- }
-
- if (p->drain && (p->avoid_ao_wait || p->paused)) {
+ if (p->drain && (!p->wait_on_ao || p->paused)) {
if (ao->driver->drain)
ao->driver->drain(ao);
p->drain = false;
@@ -331,7 +320,7 @@ static void *playthread(void *arg)
if (!p->need_wakeup) {
MP_STATS(ao, "start audio wait");
- if (p->avoid_ao_wait || p->paused) {
+ if (!p->wait_on_ao || p->paused) {
// Avoid busy waiting, because the audio API will still report
// that it needs new data, even if we're not ready yet, or if
// get_space() decides that the amount of audio buffered in the
@@ -348,7 +337,7 @@ static void *playthread(void *arg)
p->still_playing = false;
}
- if (!p->requested_data || (was_playing && !p->still_playing))
+ if (was_playing && !p->still_playing)
mp_input_wakeup(ao->input_ctx);
if (p->still_playing && timeout > 0) {
@@ -359,7 +348,10 @@ static void *playthread(void *arg)
} else {
if (!ao->driver->wait || ao->driver->wait(ao, &p->lock) < 0) {
// Fallback to guessing.
- double timeout = ao_estimate_timeout(ao);
+ double timeout = 0;
+ if (ao->driver->get_delay)
+ timeout = ao->driver->get_delay(ao);
+ timeout *= 0.25; // wake up if 25% played
mpthread_cond_timedwait_rel(&p->wakeup, &p->lock, timeout);
}
}