summaryrefslogtreecommitdiffstats
path: root/audio/out/buffer.c
diff options
context:
space:
mode:
Diffstat (limited to 'audio/out/buffer.c')
-rw-r--r--audio/out/buffer.c272
1 files changed, 163 insertions, 109 deletions
diff --git a/audio/out/buffer.c b/audio/out/buffer.c
index 9ac410ff9e..97f7ea147e 100644
--- a/audio/out/buffer.c
+++ b/audio/out/buffer.c
@@ -16,7 +16,6 @@
*/
#include <stddef.h>
-#include <pthread.h>
#include <inttypes.h>
#include <math.h>
#include <unistd.h>
@@ -39,12 +38,12 @@
struct buffer_state {
// Buffer and AO
- pthread_mutex_t lock;
- pthread_cond_t wakeup;
+ mp_mutex lock;
+ mp_cond wakeup;
- // Playthread sleep
- pthread_mutex_t pt_lock;
- pthread_cond_t pt_wakeup;
+ // AO thread sleep
+ mp_mutex pt_lock;
+ mp_cond pt_wakeup;
// Access from AO driver's thread only.
char *convert_buffer;
@@ -62,7 +61,12 @@ struct buffer_state {
bool playing; // logically playing audio from buffer
bool paused; // logically paused
- int64_t end_time_us; // absolute output time of last played sample
+ int64_t end_time_ns; // absolute output time of last played sample
+ int64_t queued_time_ns; // duration of samples that have been queued to
+ // the device but have not been played.
+ // This field is only set in ao_set_paused(),
+ // and is considered as a temporary solution;
+ // DO NOT USE IT IN OTHER PLACES.
bool initial_unblocked;
@@ -70,7 +74,7 @@ struct buffer_state {
bool hw_paused; // driver->set_pause() was used successfully
bool recover_pause; // non-hw_paused: needs to recover delay
struct mp_pcm_state prepause_state;
- pthread_t thread; // thread shoveling data to AO
+ mp_thread thread; // thread shoveling data to AO
bool thread_valid; // thread is running
struct mp_aframe *temp_buf;
@@ -79,15 +83,15 @@ struct buffer_state {
bool terminate; // exit thread
};
-static void *playthread(void *arg);
+static MP_THREAD_VOID ao_thread(void *arg);
-void ao_wakeup_playthread(struct ao *ao)
+void ao_wakeup(struct ao *ao)
{
struct buffer_state *p = ao->buffer_state;
- pthread_mutex_lock(&p->pt_lock);
+ mp_mutex_lock(&p->pt_lock);
p->need_wakeup = true;
- pthread_cond_broadcast(&p->pt_wakeup);
- pthread_mutex_unlock(&p->pt_lock);
+ mp_cond_broadcast(&p->pt_wakeup);
+ mp_mutex_unlock(&p->pt_lock);
}
// called locked
@@ -115,7 +119,8 @@ struct mp_async_queue *ao_get_queue(struct ao *ao)
}
// Special behavior with data==NULL: caller uses p->pending.
-static int read_buffer(struct ao *ao, void **data, int samples, bool *eof)
+static int read_buffer(struct ao *ao, void **data, int samples, bool *eof,
+ bool pad_silence)
{
struct buffer_state *p = ao->buffer_state;
int pos = 0;
@@ -161,42 +166,72 @@ static int read_buffer(struct ao *ao, void **data, int samples, bool *eof)
}
// pad with silence (underflow/paused/eof)
- for (int n = 0; n < ao->num_planes; n++) {
- af_fill_silence((char *)data[n] + pos, (samples - pos) * ao->sstride,
- ao->format);
+ if (pad_silence) {
+ for (int n = 0; n < ao->num_planes; n++) {
+ af_fill_silence((char *)data[n] + pos * ao->sstride,
+ (samples - pos) * ao->sstride,
+ ao->format);
+ }
}
ao_post_process_data(ao, data, pos);
return pos;
}
+static int ao_read_data_locked(struct ao *ao, void **data, int samples,
+ int64_t out_time_ns, bool pad_silence)
+{
+ struct buffer_state *p = ao->buffer_state;
+ assert(!ao->driver->write);
+
+ int pos = read_buffer(ao, data, samples, &(bool){0}, pad_silence);
+
+ if (pos > 0)
+ p->end_time_ns = out_time_ns;
+
+ if (pos < samples && p->playing && !p->paused) {
+ p->playing = false;
+ ao->wakeup_cb(ao->wakeup_ctx);
+ // For ao_drain().
+ mp_cond_broadcast(&p->wakeup);
+ }
+
+ return pos;
+}
+
// Read the given amount of samples in the user-provided data buffer. Returns
// the number of samples copied. If there is not enough data (buffer underrun
// or EOF), return the number of samples that could be copied, and fill the
// rest of the user-provided buffer with silence.
// This basically assumes that the audio device doesn't care about underruns.
// If this is called in paused mode, it will always return 0.
-// The caller should set out_time_us to the expected delay until the last sample
-// reaches the speakers, in microseconds, using mp_time_us() as reference.
-int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_us)
+// The caller should set out_time_ns to the expected delay until the last sample
+// reaches the speakers, in nanoseconds, using mp_time_ns() as reference.
+int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_ns)
{
struct buffer_state *p = ao->buffer_state;
- assert(!ao->driver->write);
- pthread_mutex_lock(&p->lock);
+ mp_mutex_lock(&p->lock);
- int pos = read_buffer(ao, data, samples, &(bool){0});
+ int pos = ao_read_data_locked(ao, data, samples, out_time_ns, true);
- if (pos > 0)
- p->end_time_us = out_time_us;
+ mp_mutex_unlock(&p->lock);
- if (pos < samples && p->playing && !p->paused) {
- p->playing = false;
- // For ao_drain().
- pthread_cond_broadcast(&p->wakeup);
- }
+ return pos;
+}
- pthread_mutex_unlock(&p->lock);
+// Like ao_read_data() but does not block and also may return partial data.
+// Callers have to check the return value.
+int ao_read_data_nonblocking(struct ao *ao, void **data, int samples, int64_t out_time_ns)
+{
+ struct buffer_state *p = ao->buffer_state;
+
+ if (mp_mutex_trylock(&p->lock))
+ return 0;
+
+ int pos = ao_read_data_locked(ao, data, samples, out_time_ns, false);
+
+ mp_mutex_unlock(&p->lock);
return pos;
}
@@ -204,13 +239,13 @@ int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_us)
// Same as ao_read_data(), but convert data according to *fmt.
// fmt->src_fmt and fmt->channels must be the same as the AO parameters.
int ao_read_data_converted(struct ao *ao, struct ao_convert_fmt *fmt,
- void **data, int samples, int64_t out_time_us)
+ void **data, int samples, int64_t out_time_ns)
{
struct buffer_state *p = ao->buffer_state;
void *ndata[MP_NUM_CHANNELS] = {0};
if (!ao_need_conversion(fmt))
- return ao_read_data(ao, data, samples, out_time_us);
+ return ao_read_data(ao, data, samples, out_time_ns);
assert(ao->format == fmt->src_fmt);
assert(ao->channels.num == fmt->channels);
@@ -230,7 +265,7 @@ int ao_read_data_converted(struct ao *ao, struct ao_convert_fmt *fmt,
for (int n = 0; n < planes; n++)
ndata[n] = p->convert_buffer + n * src_plane_size;
- int res = ao_read_data(ao, ndata, samples, out_time_us);
+ int res = ao_read_data(ao, ndata, samples, out_time_ns);
ao_convert_inplace(fmt, ndata, samples);
for (int n = 0; n < planes; n++)
@@ -246,12 +281,12 @@ int ao_control(struct ao *ao, enum aocontrol cmd, void *arg)
if (ao->driver->control) {
// Only need to lock in push mode.
if (ao->driver->write)
- pthread_mutex_lock(&p->lock);
+ mp_mutex_lock(&p->lock);
r = ao->driver->control(ao, cmd, arg);
if (ao->driver->write)
- pthread_mutex_unlock(&p->lock);
+ mp_mutex_unlock(&p->lock);
}
return r;
}
@@ -260,7 +295,7 @@ double ao_get_delay(struct ao *ao)
{
struct buffer_state *p = ao->buffer_state;
- pthread_mutex_lock(&p->lock);
+ mp_mutex_lock(&p->lock);
double driver_delay;
if (ao->driver->write) {
@@ -268,16 +303,16 @@ double ao_get_delay(struct ao *ao)
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));
+ int64_t end = p->end_time_ns;
+ int64_t now = mp_time_ns();
+ driver_delay = MPMAX(0, MP_TIME_NS_TO_S(end - now));
}
int pending = mp_async_queue_get_samples(p->queue);
if (p->pending)
pending += mp_aframe_get_size(p->pending);
- pthread_mutex_unlock(&p->lock);
+ mp_mutex_unlock(&p->lock);
return driver_delay + pending / (double)ao->samplerate;
}
@@ -288,7 +323,7 @@ void ao_reset(struct ao *ao)
bool wakeup = false;
bool do_reset = false;
- pthread_mutex_lock(&p->lock);
+ mp_mutex_lock(&p->lock);
TA_FREEP(&p->pending);
mp_async_queue_reset(p->queue);
@@ -309,15 +344,15 @@ void ao_reset(struct ao *ao)
p->playing = false;
p->recover_pause = false;
p->hw_paused = false;
- p->end_time_us = 0;
+ p->end_time_ns = 0;
- pthread_mutex_unlock(&p->lock);
+ mp_mutex_unlock(&p->lock);
if (do_reset)
ao->driver->reset(ao);
if (wakeup)
- ao_wakeup_playthread(ao);
+ ao_wakeup(ao);
}
// Initiate playback. This moves from the stop/underrun state to actually
@@ -329,7 +364,7 @@ void ao_start(struct ao *ao)
struct buffer_state *p = ao->buffer_state;
bool do_start = false;
- pthread_mutex_lock(&p->lock);
+ mp_mutex_lock(&p->lock);
p->playing = true;
@@ -338,24 +373,29 @@ void ao_start(struct ao *ao)
do_start = true;
}
- pthread_mutex_unlock(&p->lock);
+ mp_mutex_unlock(&p->lock);
// Pull AOs might call ao_read_data() so do this outside the lock.
if (do_start)
ao->driver->start(ao);
- ao_wakeup_playthread(ao);
+ ao_wakeup(ao);
}
-void ao_set_paused(struct ao *ao, bool paused)
+void ao_set_paused(struct ao *ao, bool paused, bool eof)
{
struct buffer_state *p = ao->buffer_state;
bool wakeup = false;
- bool do_reset = false, do_start = false;
+ bool do_change_state = false;
- pthread_mutex_lock(&p->lock);
+ // If we are going to pause on eof and ao is still playing,
+ // be sure to drain the ao first for gapless.
+ if (eof && paused && ao_is_playing(ao))
+ ao_drain(ao);
- if (p->playing && !p->paused && paused) {
+ mp_mutex_lock(&p->lock);
+
+ if ((p->playing || !ao->driver->write) && !p->paused && paused) {
if (p->streaming && !ao->stream_silence) {
if (ao->driver->write) {
if (!p->recover_pause)
@@ -367,9 +407,9 @@ void ao_set_paused(struct ao *ao, bool paused)
p->streaming = false;
p->recover_pause = !ao->untimed;
}
- } else if (ao->driver->reset) {
+ } else if (ao->driver->reset || ao->driver->set_pause) {
// See ao_reset() why this is done outside of the lock.
- do_reset = true;
+ do_change_state = true;
p->streaming = false;
}
}
@@ -381,22 +421,34 @@ void ao_set_paused(struct ao *ao, bool paused)
p->hw_paused = false;
} else {
if (!p->streaming)
- do_start = true;
+ do_change_state = true;
p->streaming = true;
}
wakeup = true;
}
p->paused = paused;
- pthread_mutex_unlock(&p->lock);
+ mp_mutex_unlock(&p->lock);
- if (do_reset)
- ao->driver->reset(ao);
- if (do_start)
- ao->driver->start(ao);
+ if (do_change_state) {
+ if (ao->driver->set_pause) {
+ if (paused) {
+ ao->driver->set_pause(ao, true);
+ p->queued_time_ns = p->end_time_ns - mp_time_ns();
+ } else {
+ p->end_time_ns = p->queued_time_ns + mp_time_ns();
+ ao->driver->set_pause(ao, false);
+ }
+ } else {
+ if (paused)
+ ao->driver->reset(ao);
+ else
+ ao->driver->start(ao);
+ }
+ }
if (wakeup)
- ao_wakeup_playthread(ao);
+ ao_wakeup(ao);
}
// Whether audio is playing. This means that there is still data in the buffers,
@@ -407,9 +459,9 @@ bool ao_is_playing(struct ao *ao)
{
struct buffer_state *p = ao->buffer_state;
- pthread_mutex_lock(&p->lock);
+ mp_mutex_lock(&p->lock);
bool playing = p->playing;
- pthread_mutex_unlock(&p->lock);
+ mp_mutex_unlock(&p->lock);
return playing;
}
@@ -419,30 +471,31 @@ void ao_drain(struct ao *ao)
{
struct buffer_state *p = ao->buffer_state;
- pthread_mutex_lock(&p->lock);
+ mp_mutex_lock(&p->lock);
while (!p->paused && p->playing) {
- pthread_mutex_unlock(&p->lock);
+ mp_mutex_unlock(&p->lock);
double delay = ao_get_delay(ao);
- pthread_mutex_lock(&p->lock);
+ mp_mutex_lock(&p->lock);
// Limit to buffer + arbitrary ~250ms max. waiting for robustness.
delay += mp_async_queue_get_samples(p->queue) / (double)ao->samplerate;
- struct timespec ts = mp_rel_time_to_timespec(MPMAX(delay, 0) + 0.25);
// Wait for EOF signal from AO.
- if (pthread_cond_timedwait(&p->wakeup, &p->lock, &ts)) {
+ if (mp_cond_timedwait(&p->wakeup, &p->lock,
+ MP_TIME_S_TO_NS(MPMAX(delay, 0) + 0.25)))
+ {
MP_VERBOSE(ao, "drain timeout\n");
break;
}
if (!p->playing && mp_async_queue_get_samples(p->queue)) {
MP_WARN(ao, "underrun during draining\n");
- pthread_mutex_unlock(&p->lock);
+ mp_mutex_unlock(&p->lock);
ao_start(ao);
- pthread_mutex_lock(&p->lock);
+ mp_mutex_lock(&p->lock);
}
}
- pthread_mutex_unlock(&p->lock);
+ mp_mutex_unlock(&p->lock);
ao_reset(ao);
}
@@ -450,37 +503,39 @@ void ao_drain(struct ao *ao)
static void wakeup_filters(void *ctx)
{
struct ao *ao = ctx;
- ao_wakeup_playthread(ao);
+ ao_wakeup(ao);
}
void ao_uninit(struct ao *ao)
{
struct buffer_state *p = ao->buffer_state;
- if (p->thread_valid) {
- pthread_mutex_lock(&p->pt_lock);
+ if (p && p->thread_valid) {
+ mp_mutex_lock(&p->pt_lock);
p->terminate = true;
- pthread_cond_broadcast(&p->pt_wakeup);
- pthread_mutex_unlock(&p->pt_lock);
+ mp_cond_broadcast(&p->pt_wakeup);
+ mp_mutex_unlock(&p->pt_lock);
- pthread_join(p->thread, NULL);
+ mp_thread_join(p->thread);
p->thread_valid = false;
}
if (ao->driver_initialized)
ao->driver->uninit(ao);
- talloc_free(p->filter_root);
- talloc_free(p->queue);
- talloc_free(p->pending);
- talloc_free(p->convert_buffer);
- talloc_free(p->temp_buf);
+ if (p) {
+ talloc_free(p->filter_root);
+ talloc_free(p->queue);
+ talloc_free(p->pending);
+ talloc_free(p->convert_buffer);
+ talloc_free(p->temp_buf);
- pthread_cond_destroy(&p->wakeup);
- pthread_mutex_destroy(&p->lock);
+ mp_cond_destroy(&p->wakeup);
+ mp_mutex_destroy(&p->lock);
- pthread_cond_destroy(&p->pt_wakeup);
- pthread_mutex_destroy(&p->pt_lock);
+ mp_cond_destroy(&p->pt_wakeup);
+ mp_mutex_destroy(&p->pt_lock);
+ }
talloc_free(ao);
}
@@ -500,11 +555,11 @@ bool init_buffer_post(struct ao *ao)
assert(ao->driver->get_state);
}
- pthread_mutex_init(&p->lock, NULL);
- pthread_cond_init(&p->wakeup, NULL);
+ mp_mutex_init(&p->lock);
+ mp_cond_init(&p->wakeup);
- pthread_mutex_init(&p->pt_lock, NULL);
- pthread_cond_init(&p->pt_wakeup, NULL);
+ mp_mutex_init(&p->pt_lock);
+ mp_cond_init(&p->pt_wakeup);
p->queue = mp_async_queue_create();
p->filter_root = mp_filter_create_root(ao->global);
@@ -523,7 +578,7 @@ bool init_buffer_post(struct ao *ao)
mp_filter_graph_set_wakeup_cb(p->filter_root, wakeup_filters, ao);
p->thread_valid = true;
- if (pthread_create(&p->thread, NULL, playthread, ao)) {
+ if (mp_thread_create(&p->thread, ao_thread, ao)) {
p->thread_valid = false;
return false;
}
@@ -588,7 +643,7 @@ static bool ao_play_data(struct ao *ao)
bool got_eof = false;
if (ao->driver->write_frames) {
TA_FREEP(&p->pending);
- samples = read_buffer(ao, NULL, 1, &got_eof);
+ samples = read_buffer(ao, NULL, 1, &got_eof, false);
planes = (void **)&p->pending;
} else {
if (!realloc_buf(ao, space)) {
@@ -605,7 +660,7 @@ static bool ao_play_data(struct ao *ao)
}
if (!samples) {
- samples = read_buffer(ao, planes, space, &got_eof);
+ samples = read_buffer(ao, planes, space, &got_eof, true);
if (p->paused || (ao->stream_silence && !p->playing))
samples = space; // read_buffer() sets remainder to silent
}
@@ -642,17 +697,17 @@ eof:
}
ao->wakeup_cb(ao->wakeup_ctx);
// For ao_drain().
- pthread_cond_broadcast(&p->wakeup);
+ mp_cond_broadcast(&p->wakeup);
return true;
}
-static void *playthread(void *arg)
+static MP_THREAD_VOID ao_thread(void *arg)
{
struct ao *ao = arg;
struct buffer_state *p = ao->buffer_state;
- mpthread_set_name("ao");
+ mp_thread_set_name("ao");
while (1) {
- pthread_mutex_lock(&p->lock);
+ mp_mutex_lock(&p->lock);
bool retry = false;
if (!ao->driver->initially_blocked || p->initial_unblocked)
@@ -660,40 +715,39 @@ static void *playthread(void *arg)
// Wait until the device wants us to write more data to it.
// Fallback to guessing.
- double timeout = INFINITY;
+ int64_t timeout = INT64_MAX;
if (p->streaming && !retry && (!p->paused || ao->stream_silence)) {
// Wake up again if half of the audio buffer has been played.
// Since audio could play at a faster or slower pace, wake up twice
// as often as ideally needed.
- timeout = ao->device_buffer / (double)ao->samplerate * 0.25;
+ timeout = MP_TIME_S_TO_NS(ao->device_buffer / (double)ao->samplerate * 0.25);
}
- pthread_mutex_unlock(&p->lock);
+ mp_mutex_unlock(&p->lock);
- pthread_mutex_lock(&p->pt_lock);
+ mp_mutex_lock(&p->pt_lock);
if (p->terminate) {
- pthread_mutex_unlock(&p->pt_lock);
+ mp_mutex_unlock(&p->pt_lock);
break;
}
if (!p->need_wakeup && !retry) {
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_cond_timedwait(&p->pt_wakeup, &p->pt_lock, timeout);
MP_STATS(ao, "end audio wait");
}
p->need_wakeup = false;
- pthread_mutex_unlock(&p->pt_lock);
+ mp_mutex_unlock(&p->pt_lock);
}
- return NULL;
+ MP_THREAD_RETURN();
}
void ao_unblock(struct ao *ao)
{
if (ao->driver->write) {
struct buffer_state *p = ao->buffer_state;
- pthread_mutex_lock(&p->lock);
+ mp_mutex_lock(&p->lock);
p->initial_unblocked = true;
- pthread_mutex_unlock(&p->lock);
- ao_wakeup_playthread(ao);
+ mp_mutex_unlock(&p->lock);
+ ao_wakeup(ao);
}
}