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.
This commit is contained in:
wm4 2014-09-06 12:59:04 +02:00
parent 769ac6fb7b
commit 39609fc19a
1 changed files with 37 additions and 45 deletions

View File

@ -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);
}
}