summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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);
}
}