summaryrefslogtreecommitdiffstats
path: root/audio/out/buffer.c
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2020-05-31 15:00:35 +0200
committerwm4 <wm4@nowhere>2020-06-01 01:08:16 +0200
commitd27ad9654218463694093697e3d09f8983b4ccf3 (patch)
tree54d889bbed1b32b2a702fee6825c66ff6fb60c33 /audio/out/buffer.c
parentd448dd5bf26408ccced1bd6df8f9eaf62a370c8e (diff)
downloadmpv-d27ad9654218463694093697e3d09f8983b4ccf3.tar.bz2
mpv-d27ad9654218463694093697e3d09f8983b4ccf3.tar.xz
audio: redo internal AO API
This affects "pull" AOs only: ao_alsa, ao_pulse, ao_openal, ao_pcm, ao_lavc. There are changes to the other AOs too, but that's only about renaming ao_driver.resume to ao_driver.start. ao_openal is broken because I didn't manage to fix it, so it exits with an error message. If you want it, why don't _you_ put effort into it? I see no reason to waste my own precious lifetime over this (I realize the irony). ao_alsa loses the poll() mechanism, but it was mostly broken and didn't really do what it was supposed to. There doesn't seem to be anything in the ALSA API to watch the playback status without polling (unless you want to use raw UNIX signals). No idea if ao_pulse is correct, or whether it's subtly broken now. There is no documentation, so I can't tell what is correct, without reverse engineering the whole project. I recommend using ALSA. This was supposed to be just a simple fix, but somehow it expanded scope like a train wreck. Very high chance of regressions, but probably only for the AOs listed above. The rest you can figure out from reading the diff.
Diffstat (limited to 'audio/out/buffer.c')
-rw-r--r--audio/out/buffer.c497
1 files changed, 233 insertions, 264 deletions
diff --git a/audio/out/buffer.c b/audio/out/buffer.c
index d1563452d1..0e92ef5911 100644
--- a/audio/out/buffer.c
+++ b/audio/out/buffer.c
@@ -18,6 +18,7 @@
#include <stddef.h>
#include <pthread.h>
#include <inttypes.h>
+#include <math.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
@@ -39,9 +40,14 @@
#include "misc/ring.h"
struct buffer_state {
+ // Buffer and AO
pthread_mutex_t lock;
pthread_cond_t wakeup;
+ // Playthread sleep
+ pthread_mutex_t pt_lock;
+ pthread_cond_t pt_wakeup;
+
// Access from AO driver's thread only.
char *convert_buffer;
@@ -58,31 +64,64 @@ struct buffer_state {
int64_t end_time_us; // absolute output time of last played sample
int64_t underflow; // number of samples missing since last check
- bool need_wakeup;
bool initial_unblocked;
- // "Push" AOs only (AOs with driver->play).
+ // "Push" AOs only (AOs with driver->write).
bool still_playing;
- double expected_end_time;
- bool wait_on_ao;
+ bool hw_paused; // driver->set_pause() was used successfully
+ bool recover_pause; // non-hw_paused: needs to recover delay
+ bool draining;
+ bool had_underrun;
+ bool ao_wait_low_buffer;
+ struct mp_pcm_state prepause_state;
pthread_t thread; // thread shoveling data to AO
bool thread_valid; // thread is running
- bool terminate; // exit thread
struct mp_aframe *temp_buf;
- int wakeup_pipe[2];
+ // --- protected by pt_lock
+ bool need_wakeup;
+ bool terminate; // exit thread
};
static void *playthread(void *arg);
-// lock must be held
-static void wakeup_playthread(struct ao *ao)
+void ao_wakeup_playthread(struct ao *ao)
{
struct buffer_state *p = ao->buffer_state;
- if (ao->driver->wakeup)
- ao->driver->wakeup(ao);
+ pthread_mutex_lock(&p->pt_lock);
p->need_wakeup = true;
- pthread_cond_signal(&p->wakeup);
+ pthread_cond_broadcast(&p->pt_wakeup);
+ pthread_mutex_unlock(&p->pt_lock);
+}
+
+// called locked
+static void get_dev_state(struct ao *ao, struct mp_pcm_state *state)
+{
+ struct buffer_state *p = ao->buffer_state;
+
+ if (p->paused) {
+ *state = p->prepause_state;
+ return;
+ }
+
+ *state = (struct mp_pcm_state){
+ .free_samples = -1,
+ .queued_samples = -1,
+ .delay = -1,
+ .underrun = false,
+ };
+ ao->driver->get_state(ao, state);
+
+ if (state->underrun) {
+ p->had_underrun = true;
+ if (p->draining) {
+ MP_VERBOSE(ao, "underrun signaled for audio end\n");
+ p->still_playing = false;
+ pthread_cond_broadcast(&p->wakeup);
+ } else {
+ ao_add_events(ao, AO_EVENT_UNDERRUN);
+ }
+ }
}
static int unlocked_get_space(struct ao *ao)
@@ -93,9 +132,11 @@ static int unlocked_get_space(struct ao *ao)
// The following code attempts to keep the total buffered audio at
// ao->buffer in order to improve latency.
- if (ao->driver->play && ao->driver->get_space) {
+ if (ao->driver->write) {
+ struct mp_pcm_state state;
+ get_dev_state(ao, &state);
int align = af_format_sample_alignment(ao->format);
- int device_space = ao->driver->get_space(ao);
+ int device_space = MPMAX(state.free_samples, 0);
int device_buffered = ao->device_buffer - device_space;
int soft_buffered = mp_ring_size(p->buffers[0]) / ao->sstride - space;
// The extra margin helps avoiding too many wakeups if the AO is fully
@@ -113,8 +154,6 @@ static int unlocked_get_space(struct ao *ao)
return space;
}
-// Return free size of the internal audio buffer. This controls how much audio
-// the core should decode and try to queue with ao_play().
int ao_get_space(struct ao *ao)
{
struct buffer_state *p = ao->buffer_state;
@@ -124,13 +163,6 @@ int ao_get_space(struct ao *ao)
return space;
}
-// Queue the given audio data. Start playback if it hasn't started yet. Return
-// the number of samples that was accepted (the core will try to queue the rest
-// again later). Should never block.
-// data: start pointer for each plane. If the audio data is packed, only
-// data[0] is valid, otherwise there is a plane for each channel.
-// samples: size of the audio data (see ao->sstride)
-// flags: currently AOPLAY_FINAL_CHUNK can be set
int ao_play(struct ao *ao, void **data, int samples, int flags)
{
struct buffer_state *p = ao->buffer_state;
@@ -147,7 +179,7 @@ int ao_play(struct ao *ao, void **data, int samples, int flags)
}
p->paused = false;
- p->final_chunk = write_samples == samples && (flags & AOPLAY_FINAL_CHUNK);
+ p->final_chunk = write_samples == samples && (flags & PLAYER_FINAL_CHUNK);
if (p->underflow)
MP_DBG(ao, "Audio underrun by %lld samples.\n", (long long)p->underflow);
@@ -156,17 +188,19 @@ int ao_play(struct ao *ao, void **data, int samples, int flags)
if (write_samples) {
p->playing = true;
p->still_playing = true;
- p->expected_end_time = 0;
+ p->draining = false;
- if (!ao->driver->play && !p->streaming) {
+ if (!ao->driver->write && !p->streaming) {
p->streaming = true;
- ao->driver->resume(ao);
+ ao->driver->start(ao);
}
- wakeup_playthread(ao);
}
pthread_mutex_unlock(&p->lock);
+ if (write_samples)
+ ao_wakeup_playthread(ao);
+
return write_samples;
}
@@ -195,7 +229,7 @@ int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_us)
if (full_bytes > bytes && !p->final_chunk) {
p->underflow += (full_bytes - bytes) / ao->sstride;
- ao_underrun_event(ao);
+ ao_add_events(ao, AO_EVENT_UNDERRUN);
}
if (bytes > 0)
@@ -278,10 +312,11 @@ static double unlocked_get_delay(struct ao *ao)
struct buffer_state *p = ao->buffer_state;
double driver_delay = 0;
- if (ao->driver->get_delay)
- driver_delay = ao->driver->get_delay(ao);
-
- if (!ao->driver->play) {
+ if (ao->driver->write) {
+ struct mp_pcm_state state;
+ get_dev_state(ao, &state);
+ driver_delay = state.delay;
+ } else {
int64_t end = p->end_time_us;
int64_t now = mp_time_us();
driver_delay += MPMAX(0, (end - now) / (1000.0 * 1000.0));
@@ -290,11 +325,6 @@ static double unlocked_get_delay(struct ao *ao)
return mp_ring_buffered(p->buffers[0]) / (double)ao->bps + driver_delay;
}
-// Return size of the buffered data in seconds. Can include the device latency.
-// Basically, this returns how much data there is still to play, and how long
-// it takes until the last sample in the buffer reaches the speakers. This is
-// used for audio/video synchronization, so it's very important to implement
-// this correctly.
double ao_get_delay(struct ao *ao)
{
struct buffer_state *p = ao->buffer_state;
@@ -305,11 +335,10 @@ double ao_get_delay(struct ao *ao)
return delay;
}
-// Stop playback and empty buffers. Essentially go back to the state after
-// ao->init().
void ao_reset(struct ao *ao)
{
struct buffer_state *p = ao->buffer_state;
+ bool wakeup = false;
pthread_mutex_lock(&p->lock);
@@ -322,58 +351,82 @@ void ao_reset(struct ao *ao)
}
p->paused = false;
p->playing = false;
- if (p->still_playing)
- wakeup_playthread(ao);
+ p->recover_pause = false;
+ p->hw_paused = false;
+ wakeup = p->still_playing || p->draining;
+ p->draining = false;
p->still_playing = false;
p->end_time_us = 0;
atomic_fetch_and(&ao->events_, ~(unsigned int)AO_EVENT_UNDERRUN);
pthread_mutex_unlock(&p->lock);
+
+ if (wakeup)
+ ao_wakeup_playthread(ao);
}
-// Pause playback. Keep the current buffer. ao_get_delay() must return the
-// same value as before pausing.
void ao_pause(struct ao *ao)
{
struct buffer_state *p = ao->buffer_state;
+ bool wakeup = false;
pthread_mutex_lock(&p->lock);
if (p->playing && !p->paused) {
if (p->streaming && !ao->stream_silence) {
- if (ao->driver->pause) {
- ao->driver->pause(ao);
+ if (ao->driver->write) {
+ if (!p->recover_pause)
+ get_dev_state(ao, &p->prepause_state);
+ if (ao->driver->set_pause && ao->driver->set_pause(ao, true)) {
+ p->hw_paused = true;
+ } else {
+ ao->driver->reset(ao);
+ p->streaming = false;
+ }
} else if (ao->driver->reset) {
ao->driver->reset(ao);
p->streaming = false;
}
}
p->paused = true;
- wakeup_playthread(ao);
+ wakeup = true;
}
pthread_mutex_unlock(&p->lock);
+
+ if (wakeup)
+ ao_wakeup_playthread(ao);
}
-// Resume playback. Play the remaining buffer. If the driver doesn't support
-// pausing, it has to work around this and e.g. use ao_play_silence() to fill
-// the lost audio.
void ao_resume(struct ao *ao)
{
struct buffer_state *p = ao->buffer_state;
+ bool wakeup = false;
pthread_mutex_lock(&p->lock);
if (p->playing && p->paused) {
- if (ao->driver->resume && (!p->streaming || ao->driver->play))
- ao->driver->resume(ao);
+ if (ao->driver->write) {
+ if (p->streaming && p->hw_paused) {
+ ao->driver->set_pause(ao, false);
+ } else {
+ p->recover_pause = true;
+ }
+ p->hw_paused = false;
+ } else {
+ if (!p->streaming)
+ ao->driver->start(ao);
+ p->streaming = true;
+ }
p->paused = false;
- p->expected_end_time = 0;
- wakeup_playthread(ao);
+ wakeup = true;
}
pthread_mutex_unlock(&p->lock);
+
+ if (wakeup)
+ ao_wakeup_playthread(ao);
}
bool ao_eof_reached(struct ao *ao)
@@ -382,7 +435,7 @@ bool ao_eof_reached(struct ao *ao)
pthread_mutex_lock(&p->lock);
bool eof = !p->playing;
- if (ao->driver->play) {
+ if (ao->driver->write) {
eof |= !p->still_playing;
} else {
// For simplicity, ignore the latency. Otherwise, we would have to run
@@ -401,37 +454,51 @@ void ao_drain(struct ao *ao)
pthread_mutex_lock(&p->lock);
p->final_chunk = true;
- wakeup_playthread(ao);
- double left = 0;
- if (p->playing && !p->paused && !ao->driver->encode)
- left = mp_ring_buffered(p->buffers[0]) / (double)ao->bps * 1e6;
- pthread_mutex_unlock(&p->lock);
+ while (!p->paused && p->still_playing && !p->had_underrun) {
+ if (ao->driver->write) {
+ if (p->draining) {
+ // Wait for EOF signal from AO.
+ pthread_cond_wait(&p->wakeup, &p->lock);
+ } else {
+ p->draining = true;
+ MP_VERBOSE(ao, "waiting for draining...\n");
+ pthread_mutex_unlock(&p->lock);
+ ao_wakeup_playthread(ao);
+ pthread_mutex_lock(&p->lock);
+ }
+ } else {
+ double left = mp_ring_buffered(p->buffers[0]) / (double)ao->bps * 1e6;
+ pthread_mutex_unlock(&p->lock);
+
+ if (left > 0) {
+ // Wait for lower bound.
+ mp_sleep_us(left);
+ // And then poll for actual end. No other way.
+ // Limit to arbitrary ~250ms max. waiting for robustness.
+ int64_t max = mp_time_us() + 250000;
+ while (mp_time_us() < max && !ao_eof_reached(ao))
+ mp_sleep_us(1);
+ } else {
+ p->still_playing = false;
+ }
- if (left > 0) {
- // Wait for lower bound.
- mp_sleep_us(left);
- // And then poll for actual end. (Unfortunately, this code considers
- // audio APIs which do not want you to use mutexes in the audio
- // callback, and an extra semaphore would require slightly more effort.)
- // Limit to arbitrary ~250ms max. waiting for robustness.
- int64_t max = mp_time_us() + 250000;
- while (mp_time_us() < max && !ao_eof_reached(ao))
- mp_sleep_us(1);
+ pthread_mutex_lock(&p->lock);
+ }
}
+ pthread_mutex_unlock(&p->lock);
ao_reset(ao);
}
-// Uninitialize and destroy the AO. Remaining audio must be dropped.
void ao_uninit(struct ao *ao)
{
struct buffer_state *p = ao->buffer_state;
if (p->thread_valid) {
- pthread_mutex_lock(&p->lock);
+ pthread_mutex_lock(&p->pt_lock);
p->terminate = true;
- wakeup_playthread(ao);
- pthread_mutex_unlock(&p->lock);
+ pthread_cond_broadcast(&p->pt_wakeup);
+ pthread_mutex_unlock(&p->pt_lock);
pthread_join(p->thread, NULL);
p->thread_valid = false;
@@ -443,15 +510,12 @@ void ao_uninit(struct ao *ao)
talloc_free(p->convert_buffer);
talloc_free(p->temp_buf);
- for (int n = 0; n < 2; n++) {
- int h = p->wakeup_pipe[n];
- if (h >= 0)
- close(h);
- }
-
pthread_cond_destroy(&p->wakeup);
pthread_mutex_destroy(&p->lock);
+ pthread_cond_destroy(&p->pt_wakeup);
+ pthread_mutex_destroy(&p->pt_lock);
+
talloc_free(ao);
}
@@ -464,22 +528,22 @@ bool init_buffer_post(struct ao *ao)
{
struct buffer_state *p = ao->buffer_state;
- if (!ao->driver->play)
- assert(ao->driver->resume);
+ assert(ao->driver->start);
+ if (ao->driver->write) {
+ assert(ao->driver->reset);
+ assert(ao->driver->get_state);
+ }
for (int n = 0; n < ao->num_planes; n++)
p->buffers[n] = mp_ring_new(ao, ao->buffer * ao->sstride);
mpthread_mutex_init_recursive(&p->lock);
pthread_cond_init(&p->wakeup, NULL);
- mp_make_wakeup_pipe(p->wakeup_pipe);
- if (ao->driver->play) {
- if (ao->device_buffer <= 0) {
- MP_FATAL(ao, "Couldn't probe device buffer size.\n");
- return false;
- }
+ pthread_mutex_init(&p->pt_lock, NULL);
+ pthread_cond_init(&p->pt_wakeup, NULL);
+ if (ao->driver->write) {
p->thread_valid = true;
if (pthread_create(&p->thread, NULL, playthread, ao)) {
p->thread_valid = false;
@@ -487,7 +551,7 @@ bool init_buffer_post(struct ao *ao)
}
} else {
if (ao->stream_silence) {
- ao->driver->resume(ao);
+ ao->driver->start(ao);
p->streaming = true;
}
}
@@ -521,13 +585,20 @@ static bool realloc_buf(struct ao *ao, int samples)
static void ao_play_data(struct ao *ao)
{
struct buffer_state *p = ao->buffer_state;
- int space = ao->driver->get_space(ao);
+
+ if (p->had_underrun) {
+ MP_VERBOSE(ao, "recover underrun\n");
+ ao->driver->reset(ao);
+ p->streaming = false;
+ p->had_underrun = false;
+ }
+
+ struct mp_pcm_state state;
+ get_dev_state(ao, &state);
+ // Round free space to period sizes to reduce number of write() calls.
+ int space = state.free_samples / ao->period_size * ao->period_size;
bool play_silence = p->paused || (ao->stream_silence && !p->still_playing);
space = MPMAX(space, 0);
- // Most AOs want period-size aligned audio, and preferably as much as
- // possible in one go, so the audio data is "linearized" into this buffer.
- if (space % ao->period_size)
- MP_ERR(ao, "Audio device reports unaligned available buffer size.\n");
if (!realloc_buf(ao, space)) {
MP_ERR(ao, "Failed to allocate buffer.\n");
return;
@@ -539,62 +610,58 @@ static void ao_play_data(struct ao *ao)
samples = space;
if (play_silence)
samples = space;
- samples = ao_read_data(ao, planes, samples, 0);
+ if (p->recover_pause) {
+ samples = MPCLAMP(p->prepause_state.delay * ao->samplerate, 0, space);
+ p->recover_pause = false;
+ mp_aframe_set_silence(p->temp_buf, 0, space);
+ } else {
+ samples = ao_read_data(ao, planes, samples, 0);
+ }
if (play_silence)
samples = space; // ao_read_data() sets remainder to silent
- int max = samples;
- int flags = 0;
- if (p->final_chunk && samples < space) {
- flags |= AOPLAY_FINAL_CHUNK;
- } else {
- samples = samples / ao->period_size * ao->period_size;
+
+ bool is_eof = p->final_chunk && samples < space;
+ bool ok = true;
+ int written = 0;
+ if (samples) {
+ p->draining = is_eof;
+ MP_STATS(ao, "start ao fill");
+ ok = ao->driver->write(ao, planes, samples);
+ MP_STATS(ao, "end ao fill");
}
- MP_STATS(ao, "start ao fill");
- int r = 0;
- if (samples)
- r = ao->driver->play(ao, planes, samples, flags);
- MP_STATS(ao, "end ao fill");
- if (r > samples) {
- MP_ERR(ao, "Audio device returned nonsense value.\n");
- r = samples;
- } else if (r < 0) {
+
+ if (!ok)
MP_ERR(ao, "Error writing audio to device.\n");
- } else if (r != samples) {
- MP_ERR(ao, "Audio device returned broken buffer state (sent %d samples, "
- "got %d samples, %d period%s)! Discarding audio.\n", samples, r,
- ao->period_size, flags & AOPLAY_FINAL_CHUNK ? " final" : "");
- }
- r = MPMAX(r, 0);
- // Probably can't copy the rest of the buffer due to period alignment.
- bool stuck_eof = r <= 0 && space >= max && samples > 0;
- if ((flags & AOPLAY_FINAL_CHUNK) && stuck_eof) {
- MP_ERR(ao, "Audio output driver seems to ignore AOPLAY_FINAL_CHUNK.\n");
- r = max;
+
+ if (samples > 0 && ok) {
+ written = samples;
+ if (!p->streaming) {
+ MP_VERBOSE(ao, "starting AO\n");
+ ao->driver->start(ao);
+ p->streaming = true;
+ }
}
- if (r > 0) {
- p->expected_end_time = 0;
- p->streaming = true;
+
+ if (p->draining && p->still_playing && ao->untimed) {
+ p->still_playing = false;
+ pthread_cond_broadcast(&p->wakeup);
}
- // 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 && !play_silence;
+ p->ao_wait_low_buffer = space == 0 || written > 0 || p->draining;
+ p->still_playing |= samples > 0 && !play_silence;
+
// 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 &&
- !(flags & AOPLAY_FINAL_CHUNK);
+ bool more = needed >= (written == space ? ao->device_buffer / 4 : 1) &&
+ !p->final_chunk;
if (more)
ao->wakeup_cb(ao->wakeup_ctx); // request more data
- if (!samples && space && !ao->driver->reports_underruns && p->still_playing)
- ao_underrun_event(ao);
- MP_TRACE(ao, "in=%d flags=%d space=%d r=%d wa/pl=%d/%d needed=%d more=%d\n",
- max, flags, space, r, p->wait_on_ao, p->still_playing, needed, more);
+ MP_TRACE(ao, "in=%d eof=%d space=%d r=%d wa/pl/dr=%d/%d/%d needed=%d more=%d\n",
+ samples, is_eof, space, written, p->ao_wait_low_buffer,
+ p->still_playing, p->draining, needed, more);
}
static void *playthread(void *arg)
@@ -602,151 +669,53 @@ static void *playthread(void *arg)
struct ao *ao = arg;
struct buffer_state *p = ao->buffer_state;
mpthread_set_name("ao");
- pthread_mutex_lock(&p->lock);
- while (!p->terminate) {
+ while (1) {
+ pthread_mutex_lock(&p->lock);
+
bool blocked = ao->driver->initially_blocked && !p->initial_unblocked;
- bool playing = (!p->paused || ao->stream_silence) && !blocked;
- if (playing)
+ bool playing = !p->paused && (p->playing || ao->stream_silence);
+ if (playing && !blocked)
ao_play_data(ao);
- if (!p->need_wakeup) {
- MP_STATS(ao, "start audio wait");
- if (!p->wait_on_ao || !playing) {
- // 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
- // device is enough, and p->buffer can be empty.
- // The most important part is that the decoder is woken up, so
- // that the decoder will wake up us in turn.
- MP_TRACE(ao, "buffer inactive.\n");
-
- bool was_playing = p->still_playing;
- double timeout = -1;
- if (p->still_playing && !p->paused && p->final_chunk &&
- !mp_ring_buffered(p->buffers[0]))
- {
- double now = mp_time_sec();
- if (!p->expected_end_time)
- p->expected_end_time = now + unlocked_get_delay(ao);
- if (p->expected_end_time < now) {
- p->still_playing = false;
- } else {
- timeout = p->expected_end_time - now;
- }
- }
+ // Wait until the device wants us to write more data to it.
+ // Fallback to guessing.
+ double timeout = INFINITY;
+ if (p->ao_wait_low_buffer) {
+ struct mp_pcm_state state;
+ get_dev_state(ao, &state);
+ timeout = state.delay * 0.25; // wake up if 25% played
+ p->ao_wait_low_buffer = false;
+ // If the AO doesn't tell us, we need to guess.
+ if (p->draining)
+ timeout = MPMAX(timeout, 0.1);
+ }
- if (was_playing && !p->still_playing)
- ao->wakeup_cb(ao->wakeup_ctx);
- pthread_cond_signal(&p->wakeup); // for draining
+ pthread_mutex_unlock(&p->lock);
- if (p->still_playing && timeout > 0) {
- struct timespec ts = mp_rel_time_to_timespec(timeout);
- pthread_cond_timedwait(&p->wakeup, &p->lock, &ts);
- } else {
- pthread_cond_wait(&p->wakeup, &p->lock);
- }
- } else {
- // Wait until the device wants us to write more data to it.
- if (!ao->driver->wait || ao->driver->wait(ao, &p->lock) < 0) {
- // Fallback to guessing.
- double timeout = 0;
- if (ao->driver->get_delay)
- timeout = ao->driver->get_delay(ao);
- timeout *= 0.25; // wake up if 25% played
- if (!p->need_wakeup) {
- struct timespec ts = mp_rel_time_to_timespec(timeout);
- pthread_cond_timedwait(&p->wakeup, &p->lock, &ts);
- }
- }
- }
+ pthread_mutex_lock(&p->pt_lock);
+ if (p->terminate) {
+ pthread_mutex_unlock(&p->pt_lock);
+ break;
+ }
+ if (!p->need_wakeup) {
+ MP_STATS(ao, "start audio wait");
+ struct timespec ts = mp_rel_time_to_timespec(timeout);
+ pthread_cond_timedwait(&p->pt_wakeup, &p->pt_lock, &ts);
MP_STATS(ao, "end audio wait");
}
p->need_wakeup = false;
+ pthread_mutex_unlock(&p->pt_lock);
}
- pthread_mutex_unlock(&p->lock);
return NULL;
}
void ao_unblock(struct ao *ao)
{
- if (ao->driver->play) {
+ if (ao->driver->write) {
struct buffer_state *p = ao->buffer_state;
pthread_mutex_lock(&p->lock);
- p->need_wakeup = true;
p->initial_unblocked = true;
- wakeup_playthread(ao);
- pthread_cond_signal(&p->wakeup);
pthread_mutex_unlock(&p->lock);
+ ao_wakeup_playthread(ao);
}
}
-
-// Must be called locked.
-int ao_play_silence(struct ao *ao, int samples)
-{
- assert(ao->driver->play);
-
- struct buffer_state *p = ao->buffer_state;
-
- if (!realloc_buf(ao, samples) || !ao->driver->play)
- return 0;
-
- void **planes = (void **)mp_aframe_get_data_rw(p->temp_buf);
- assert(planes);
-
- for (int n = 0; n < ao->num_planes; n++)
- af_fill_silence(planes[n], ao->sstride * samples, ao->format);
-
- return ao->driver->play(ao, planes, samples, 0);
-}
-
-#ifndef __MINGW32__
-
-#include <poll.h>
-
-#define MAX_POLL_FDS 20
-
-// Call poll() for the given fds. This will extend the given fds with the
-// wakeup pipe, so ao_wakeup_poll() will basically interrupt this function.
-// Unlocks the lock temporarily.
-// Returns <0 on error, 0 on success, 1 if the caller should return immediately.
-int ao_wait_poll(struct ao *ao, struct pollfd *fds, int num_fds,
- pthread_mutex_t *lock)
-{
- struct buffer_state *p = ao->buffer_state;
- assert(ao->driver->play);
- assert(&p->lock == lock);
-
- if (num_fds >= MAX_POLL_FDS || p->wakeup_pipe[0] < 0)
- return -1;
-
- struct pollfd p_fds[MAX_POLL_FDS];
- memcpy(p_fds, fds, num_fds * sizeof(p_fds[0]));
- p_fds[num_fds] = (struct pollfd){
- .fd = p->wakeup_pipe[0],
- .events = POLLIN,
- };
-
- pthread_mutex_unlock(&p->lock);
- int r = poll(p_fds, num_fds + 1, -1);
- r = r < 0 ? -errno : 0;
- pthread_mutex_lock(&p->lock);
-
- memcpy(fds, p_fds, num_fds * sizeof(fds[0]));
- bool wakeup = false;
- if (p_fds[num_fds].revents & POLLIN) {
- wakeup = true;
- // might "drown" some wakeups, but that's ok for our use-case
- mp_flush_wakeup_pipe(p->wakeup_pipe[0]);
- }
- return (r >= 0 || r == -EINTR) ? wakeup : -1;
-}
-
-void ao_wakeup_poll(struct ao *ao)
-{
- assert(ao->driver->play);
- struct buffer_state *p = ao->buffer_state;
-
- (void)write(p->wakeup_pipe[1], &(char){0}, 1);
-}
-
-#endif