summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2014-03-09 00:04:37 +0100
committerwm4 <wm4@nowhere>2014-03-09 01:27:41 +0100
commita477481aabaa16f1ed15af456125160bc8face5a (patch)
treec99949f3e13e7c7ba498c56edcbcfa433e487cd3
parent5ffd6a9e9b7a0d894d7513ad20c24c2727426ecd (diff)
downloadmpv-a477481aabaa16f1ed15af456125160bc8face5a.tar.bz2
mpv-a477481aabaa16f1ed15af456125160bc8face5a.tar.xz
audio/out: feed AOs from a separate thread
This has 2 goals: - Ensure that AOs have always enough data, even if the device buffers are very small. - Reduce complexity in some AOs, which do their own buffering. One disadvantage is that performance is slightly reduced due to more copying. Implementation-wise, we don't change ao.c much, and instead "redirect" the driver's callback to an API wrapper in push.c. Additionally, we add code for dealing with AOs that have a pull API. These AOs usually do their own buffering (jack, coreaudio, portaudio), and adding a thread is basically a waste. The code in pull.c manages a ringbuffer, and allows callback-based AOs to read data directly.
-rw-r--r--audio/mixer.c6
-rw-r--r--audio/out/ao.c70
-rw-r--r--audio/out/internal.h59
-rw-r--r--audio/out/pull.c219
-rw-r--r--audio/out/push.c266
-rw-r--r--old-makefile2
-rw-r--r--wscript_build.py2
7 files changed, 593 insertions, 31 deletions
diff --git a/audio/mixer.c b/audio/mixer.c
index 34a1722c80..bf4db223b8 100644
--- a/audio/mixer.c
+++ b/audio/mixer.c
@@ -244,7 +244,7 @@ static void probe_softvol(struct mixer *mixer)
if (mixer->opts->softvol == SOFTVOL_AUTO) {
// No system-wide volume => fine with AO volume control.
mixer->softvol =
- ao_control(mixer->ao, AOCONTROL_HAS_TEMP_VOLUME, 0) != 1 &&
+ ao_control(mixer->ao, AOCONTROL_HAS_TEMP_VOLUME, 0) != 1 ||
ao_control(mixer->ao, AOCONTROL_HAS_PER_APP_VOLUME, 0) != 1;
} else {
mixer->softvol = mixer->opts->softvol == SOFTVOL_YES;
@@ -278,8 +278,8 @@ static void restore_volume(struct mixer *mixer)
const char *prev_driver = mixer->driver;
mixer->driver = mixer->softvol ? "softvol" : ao_get_name(ao);
- bool restore
- = mixer->softvol || ao_control(ao, AOCONTROL_HAS_TEMP_VOLUME, 0) == 1;
+ bool restore =
+ mixer->softvol || ao_control(ao, AOCONTROL_HAS_TEMP_VOLUME, 0) == 1;
// Restore old parameters if volume won't survive reinitialization.
// But not if volume scale is possibly different.
diff --git a/audio/out/ao.c b/audio/out/ao.c
index 7a973fef2d..f1e88d2a10 100644
--- a/audio/out/ao.c
+++ b/audio/out/ao.c
@@ -31,9 +31,14 @@
#include "options/options.h"
#include "options/m_config.h"
+#include "osdep/timer.h"
#include "common/msg.h"
+#include "common/common.h"
#include "common/global.h"
+// Minimum buffer size in seconds.
+#define MIN_BUFFER 0.2
+
extern const struct ao_driver audio_out_oss;
extern const struct ao_driver audio_out_coreaudio;
extern const struct ao_driver audio_out_rsound;
@@ -156,16 +161,36 @@ static struct ao *ao_create(bool probing, struct mpv_global *global,
if (m_config_set_obj_params(config, args) < 0)
goto error;
ao->priv = config->optstruct;
+
char *chmap = mp_chmap_to_str(&ao->channels);
MP_VERBOSE(ao, "requested format: %d Hz, %s channels, %s\n",
ao->samplerate, chmap, af_fmt_to_str(ao->format));
talloc_free(chmap);
+
+ ao->api = ao->driver->play ? &ao_api_push : &ao_api_pull;
+ ao->api_priv = talloc_zero_size(ao, ao->api->priv_size);
+ assert(!ao->api->priv_defaults && !ao->api->options);
+
if (ao->driver->init(ao) < 0)
goto error;
+
ao->sstride = af_fmt2bits(ao->format) / 8;
- if (!af_fmt_is_planar(ao->format))
+ ao->num_planes = 1;
+ if (af_fmt_is_planar(ao->format)) {
+ ao->num_planes = ao->channels.num;
+ } else {
ao->sstride *= ao->channels.num;
+ }
ao->bps = ao->samplerate * ao->sstride;
+
+ if (!ao->device_buffer && ao->driver->get_space)
+ ao->device_buffer = ao->driver->get_space(ao);
+ ao->buffer = MPMAX(ao->device_buffer, MIN_BUFFER * ao->samplerate);
+ MP_VERBOSE(ao, "using soft-buffer of %d samples.\n", ao->buffer);
+
+ if (ao->api->init(ao) < 0)
+ goto error;
+
return ao;
error:
talloc_free(ao);
@@ -214,7 +239,7 @@ done:
// cut_audio: if false, block until all remaining audio was played.
void ao_uninit(struct ao *ao, bool cut_audio)
{
- ao->driver->uninit(ao, cut_audio);
+ ao->api->uninit(ao, cut_audio);
talloc_free(ao);
}
@@ -227,7 +252,7 @@ void ao_uninit(struct ao *ao, bool cut_audio)
// flags: currently AOPLAY_FINAL_CHUNK can be set
int ao_play(struct ao *ao, void **data, int samples, int flags)
{
- return ao->driver->play(ao, data, samples, flags);
+ return ao->api->play(ao, data, samples, flags);
}
int ao_control(struct ao *ao, enum aocontrol cmd, void *arg)
@@ -238,8 +263,8 @@ int ao_control(struct ao *ao, enum aocontrol cmd, void *arg)
case AOCONTROL_HAS_PER_APP_VOLUME:
return !!ao->per_application_mixer;
default:
- if (ao->driver->control)
- return ao->driver->control(ao, cmd, arg);
+ if (ao->api->control)
+ return ao->api->control(ao, cmd, arg);
}
return CONTROL_UNKNOWN;
}
@@ -251,34 +276,34 @@ int ao_control(struct ao *ao, enum aocontrol cmd, void *arg)
// this correctly.
double ao_get_delay(struct ao *ao)
{
- if (!ao->driver->get_delay) {
+ if (!ao->api->get_delay) {
assert(ao->untimed);
return 0;
}
- return ao->driver->get_delay(ao);
+ return ao->api->get_delay(ao);
}
// 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)
{
- return ao->driver->get_space(ao);
+ return ao->api->get_space(ao);
}
// Stop playback and empty buffers. Essentially go back to the state after
// ao->init().
void ao_reset(struct ao *ao)
{
- if (ao->driver->reset)
- ao->driver->reset(ao);
+ if (ao->api->reset)
+ ao->api->reset(ao);
}
// Pause playback. Keep the current buffer. ao_get_delay() must return the
// same value as before pausing.
void ao_pause(struct ao *ao)
{
- if (ao->driver->pause)
- ao->driver->pause(ao);
+ if (ao->api->pause)
+ ao->api->pause(ao);
}
// Resume playback. Play the remaining buffer. If the driver doesn't support
@@ -286,22 +311,17 @@ void ao_pause(struct ao *ao)
// the lost audio.
void ao_resume(struct ao *ao)
{
- if (ao->driver->resume)
- ao->driver->resume(ao);
+ if (ao->api->resume)
+ ao->api->resume(ao);
}
-int ao_play_silence(struct ao *ao, int samples)
+// Wait until the audio buffer is drained. This can be used to emulate draining
+// if native functionality is not available.
+// Only call this on uninit (otherwise, deadlock trouble ahead).
+void ao_wait_drain(struct ao *ao)
{
- if (samples <= 0 || AF_FORMAT_IS_SPECIAL(ao->format))
- return 0;
- char *p = talloc_size(NULL, samples * ao->sstride);
- af_fill_silence(p, samples * ao->sstride, ao->format);
- void *tmp[MP_NUM_CHANNELS];
- for (int n = 0; n < MP_NUM_CHANNELS; n++)
- tmp[n] = p;
- int r = ao_play(ao, tmp, samples, 0);
- talloc_free(p);
- return r;
+ // This is probably not entirely accurate, but good enough.
+ mp_sleep_us(ao_get_delay(ao) * 1000000);
}
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
diff --git a/audio/out/internal.h b/audio/out/internal.h
index e996f8afeb..98de7e08b3 100644
--- a/audio/out/internal.h
+++ b/audio/out/internal.h
@@ -19,25 +19,74 @@
#ifndef MP_AO_INTERNAL_H_
#define MP_AO_INTERNAL_H_
+#include <stdbool.h>
+
+#include "audio/chmap.h"
+#include "audio/chmap_sel.h"
+
/* global data used by ao.c and ao drivers */
struct ao {
int samplerate;
struct mp_chmap channels;
int format; // one of AF_FORMAT_...
- int bps; // bytes per second
+ int bps; // bytes per second (per plane)
int sstride; // size of a sample on each plane
// (format_size*num_channels/num_planes)
+ int num_planes;
bool probing; // if true, don't fail loudly on init
bool untimed; // don't assume realtime playback
bool no_persistent_volume; // the AO does the equivalent of af_volume
bool per_application_mixer; // like above, but volume persists (per app)
+ int device_buffer; // device buffer in samples (guessed by
+ // common init code if not set by driver)
+ const struct ao_driver *api; // entrypoints to the wrapper (push.c/pull.c)
const struct ao_driver *driver;
void *priv;
struct encode_lavc_context *encode_lavc_ctx;
struct input_ctx *input_ctx;
struct mp_log *log; // Using e.g. "[ao/coreaudio]" as prefix
+
+ int buffer;
+ void *api_priv;
};
+extern const struct ao_driver ao_api_push;
+extern const struct ao_driver ao_api_pull;
+
+
+/* Note:
+ *
+ * In general, there are two types of audio drivers:
+ * a) push based (the user queues data that should be played)
+ * b) pull callback based (the audio API calls a callback to get audio)
+ *
+ * The ao.c code can handle both. It basically implements two audio paths
+ * and provides a uniform API for them. If ao_driver->play is NULL, it assumes
+ * that the driver uses a callback based audio API, otherwise push based.
+ *
+ * Requirements:
+ * a) Most functions (except ->control) must be provided. ->play is called to
+ * queue audio. ao.c creates a thread to regularly refill audio device
+ * buffers with ->play, but all driver functions are always called under
+ * an exclusive lock.
+ * Mandatory:
+ * init
+ * uninit
+ * reset
+ * get_space
+ * play
+ * get_delay
+ * pause
+ * resume
+ * b) ->play must be NULL. The driver can start the audio API in init(). The
+ * audio API in turn will start a thread and call a callback provided by the
+ * driver. That callback calls ao_read_data() to get audio data. Most
+ * functions are optional and will be emulated if missing (e.g. pausing
+ * is emulated as silence). ->get_delay and ->get_space are never called.
+ * Mandatory:
+ * init
+ * uninit
+ */
struct ao_driver {
// If true, use with encoding only.
bool encode;
@@ -49,7 +98,7 @@ struct ao_driver {
// device doesn't accept these parameters, you can attempt to negotiate
// fallback parameters, and set the ao format fields accordingly.
int (*init)(struct ao *ao);
- // See ao_control() etc. in ao.c
+ // Optional. See ao_control() etc. in ao.c
int (*control)(struct ao *ao, enum aocontrol cmd, void *arg);
void (*uninit)(struct ao *ao, bool cut_audio);
void (*reset)(struct ao*ao);
@@ -65,8 +114,12 @@ struct ao_driver {
const struct m_option *options;
};
-// These functions can be called by AOs. They don't lock the AO.
+// These functions can be called by AOs.
+
int ao_play_silence(struct ao *ao, int samples);
+void ao_wait_drain(struct ao *ao);
+int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_us);
+
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
struct mp_chmap *map);
bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s,
diff --git a/audio/out/pull.c b/audio/out/pull.c
new file mode 100644
index 0000000000..a41ac714ba
--- /dev/null
+++ b/audio/out/pull.c
@@ -0,0 +1,219 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stddef.h>
+#include <inttypes.h>
+#include <assert.h>
+
+#include "ao.h"
+#include "internal.h"
+#include "audio/format.h"
+
+#include "common/msg.h"
+#include "common/common.h"
+
+#include "osdep/timer.h"
+#include "osdep/threads.h"
+#include "compat/atomics.h"
+#include "misc/ring.h"
+
+enum {
+ AO_STATE_NONE, // idle (e.g. before playback started, or after playback
+ // finished, but device is open)
+ AO_STATE_PLAY, // play the buffer
+ AO_STATE_PAUSE, // pause playback
+};
+
+struct ao_pull_state {
+ // Be very careful with the order when accessing planes.
+ struct mp_ring *buffers[MP_NUM_CHANNELS];
+
+ // AO_STATE_*
+ int state;
+
+ // Whether buffers[] can be accessed.
+ int ready;
+
+ // Device delay of the last written sample, in realtime.
+ int64_t end_time_us;
+};
+
+static int get_space(struct ao *ao)
+{
+ struct ao_pull_state *p = ao->api_priv;
+ // Since the reader will read the last plane last, its free space is the
+ // minimum free space across all planes.
+ return mp_ring_available(p->buffers[ao->num_planes - 1]) / ao->sstride;
+}
+
+static int play(struct ao *ao, void **data, int samples, int flags)
+{
+ struct ao_pull_state *p = ao->api_priv;
+
+ int write_samples = get_space(ao);
+ write_samples = MPMIN(write_samples, samples);
+
+ // Write starting from the last plane - this way, the first plane will
+ // always contain the minimum amount of data readable across all planes
+ // (assumes the reader starts with the first plane).
+ int write_bytes = write_samples * ao->sstride;
+ for (int n = ao->num_planes - 1; n >= 0; n--) {
+ int r = mp_ring_write(p->buffers[n], data[n], write_bytes);
+ assert(r == write_bytes);
+ }
+ if (p->state != AO_STATE_PLAY) {
+ p->end_time_us = 0;
+ p->state = AO_STATE_PLAY;
+ mp_memory_barrier();
+ if (ao->driver->resume)
+ ao->driver->resume(ao);
+ }
+
+ return write_samples;
+}
+
+// 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 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)
+{
+ assert(ao->api == &ao_api_pull);
+
+ struct ao_pull_state *p = ao->api_priv;
+ int full_bytes = samples * ao->sstride;
+
+ mp_memory_barrier();
+ if (!p->ready) {
+ for (int n = 0; n < ao->num_planes; n++)
+ af_fill_silence(data[n], full_bytes, ao->format);
+ return 0;
+ }
+
+ // Since the writer will write the first plane last, its buffered amount
+ // of data is the minimum amount across all planes.
+ int bytes = mp_ring_buffered(p->buffers[0]);
+ bytes = MPMIN(bytes, full_bytes);
+
+ if (bytes > 0)
+ p->end_time_us = out_time_us;
+
+ mp_memory_barrier();
+ if (p->state == AO_STATE_PAUSE)
+ bytes = 0;
+
+ for (int n = 0; n < ao->num_planes; n++) {
+ int r = mp_ring_read(p->buffers[n], data[n], bytes);
+ assert(r == bytes);
+ // pad with silence (underflow/paused/eof)
+ int silence = full_bytes - bytes;
+ if (silence)
+ af_fill_silence((char *)data[n] + bytes, silence, ao->format);
+ }
+ return bytes / ao->sstride;
+}
+
+static int control(struct ao *ao, enum aocontrol cmd, void *arg)
+{
+ if (ao->driver->control)
+ return ao->driver->control(ao, cmd, arg);
+ return CONTROL_UNKNOWN;
+}
+
+// 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.
+static float get_delay(struct ao *ao)
+{
+ struct ao_pull_state *p = ao->api_priv;
+
+ mp_memory_barrier();
+ int64_t end = p->end_time_us;
+ int64_t now = mp_time_us();
+ double driver_delay = MPMAX(0, (end - now) / (1000.0 * 1000.0));
+ return mp_ring_buffered(p->buffers[0]) / (double)ao->bps + driver_delay;
+}
+
+static void reset(struct ao *ao)
+{
+ struct ao_pull_state *p = ao->api_priv;
+ if (ao->driver->reset)
+ ao->driver->reset(ao);
+ // Not like this is race-condition free. It will work if ->reset
+ // stops the audio callback, though.
+ p->ready = 0;
+ p->state = AO_STATE_NONE;
+ mp_memory_barrier();
+ for (int n = 0; n < ao->num_planes; n++)
+ mp_ring_reset(p->buffers[n]);
+ p->end_time_us = 0;
+ mp_memory_barrier();
+ p->ready = 1;
+ mp_memory_barrier();
+}
+
+static void pause(struct ao *ao)
+{
+ struct ao_pull_state *p = ao->api_priv;
+ if (ao->driver->pause)
+ ao->driver->pause(ao);
+ p->state = AO_STATE_PAUSE;
+ mp_memory_barrier();
+}
+
+static void resume(struct ao *ao)
+{
+ struct ao_pull_state *p = ao->api_priv;
+ p->state = AO_STATE_PLAY;
+ mp_memory_barrier();
+ if (ao->driver->resume)
+ ao->driver->resume(ao);
+}
+
+static void uninit(struct ao *ao, bool cut_audio)
+{
+ ao->driver->uninit(ao, cut_audio);
+}
+
+static int init(struct ao *ao)
+{
+ struct ao_pull_state *p = ao->api_priv;
+ for (int n = 0; n < ao->num_planes; n++)
+ p->buffers[n] = mp_ring_new(ao, ao->buffer * ao->sstride);
+ p->ready = 1;
+ mp_memory_barrier();
+ return 0;
+}
+
+const struct ao_driver ao_api_pull = {
+ .init = init,
+ .control = control,
+ .uninit = uninit,
+ .reset = reset,
+ .get_space = get_space,
+ .play = play,
+ .get_delay = get_delay,
+ .pause = pause,
+ .resume = resume,
+ .priv_size = sizeof(struct ao_pull_state),
+};
diff --git a/audio/out/push.c b/audio/out/push.c
new file mode 100644
index 0000000000..de093988bd
--- /dev/null
+++ b/audio/out/push.c
@@ -0,0 +1,266 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * mpv is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stddef.h>
+#include <pthread.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <assert.h>
+
+#include "ao.h"
+#include "internal.h"
+#include "audio/format.h"
+
+#include "common/msg.h"
+#include "common/common.h"
+
+#include "osdep/threads.h"
+#include "compat/atomics.h"
+
+#include "audio/audio.h"
+#include "audio/audio_buffer.h"
+
+struct ao_push_state {
+ pthread_t thread;
+ pthread_mutex_t lock;
+ pthread_cond_t wakeup;
+
+ struct mp_audio_buffer *buffer;
+
+ bool terminate;
+ bool playing;
+
+ // Whether the current buffer contains the complete audio.
+ bool final_chunk;
+};
+
+static int control(struct ao *ao, enum aocontrol cmd, void *arg)
+{
+ int r = CONTROL_UNKNOWN;
+ if (ao->driver->control) {
+ struct ao_push_state *p = ao->api_priv;
+ pthread_mutex_lock(&p->lock);
+ r = ao->driver->control(ao, cmd, arg);
+ pthread_mutex_unlock(&p->lock);
+ }
+ return r;
+}
+
+static float get_delay(struct ao *ao)
+{
+ struct ao_push_state *p = ao->api_priv;
+ pthread_mutex_lock(&p->lock);
+ double driver_delay = 0;
+ if (ao->driver->get_delay)
+ driver_delay = ao->driver->get_delay(ao);
+ double delay = driver_delay + mp_audio_buffer_seconds(p->buffer);
+ pthread_mutex_unlock(&p->lock);
+ return delay;
+}
+
+static void reset(struct ao *ao)
+{
+ struct ao_push_state *p = ao->api_priv;
+ pthread_mutex_lock(&p->lock);
+ if (ao->driver->reset)
+ ao->driver->reset(ao);
+ mp_audio_buffer_clear(p->buffer);
+ p->playing = false;
+ pthread_cond_signal(&p->wakeup);
+ pthread_mutex_unlock(&p->lock);
+}
+
+static void pause(struct ao *ao)
+{
+ struct ao_push_state *p = ao->api_priv;
+ pthread_mutex_lock(&p->lock);
+ if (ao->driver->pause)
+ ao->driver->pause(ao);
+ p->playing = false;
+ pthread_cond_signal(&p->wakeup);
+ pthread_mutex_unlock(&p->lock);
+}
+
+static void resume(struct ao *ao)
+{
+ struct ao_push_state *p = ao->api_priv;
+ pthread_mutex_lock(&p->lock);
+ if (ao->driver->resume)
+ ao->driver->resume(ao);
+ p->playing = true; // tentatively
+ pthread_cond_signal(&p->wakeup);
+ pthread_mutex_unlock(&p->lock);
+}
+
+
+static int get_space(struct ao *ao)
+{
+ struct ao_push_state *p = ao->api_priv;
+ pthread_mutex_lock(&p->lock);
+ int s = mp_audio_buffer_get_write_available(p->buffer);
+ pthread_mutex_unlock(&p->lock);
+ return s;
+}
+
+static int play(struct ao *ao, void **data, int samples, int flags)
+{
+ struct ao_push_state *p = ao->api_priv;
+
+ pthread_mutex_lock(&p->lock);
+
+ int write_samples = mp_audio_buffer_get_write_available(p->buffer);
+ write_samples = MPMIN(write_samples, samples);
+
+ struct mp_audio audio;
+ mp_audio_buffer_get_format(p->buffer, &audio);
+ for (int n = 0; n < ao->num_planes; n++)
+ audio.planes[n] = data[n];
+ audio.samples = write_samples;
+ mp_audio_buffer_append(p->buffer, &audio);
+
+ p->final_chunk = !!(flags & AOPLAY_FINAL_CHUNK);
+ p->playing = true;
+
+ pthread_cond_signal(&p->wakeup);
+ pthread_mutex_unlock(&p->lock);
+ return write_samples;
+}
+
+// called locked
+static int ao_play_data(struct ao *ao)
+{
+ struct ao_push_state *p = ao->api_priv;
+ struct mp_audio data;
+ mp_audio_buffer_peek(p->buffer, &data);
+ int max = data.samples;
+ int space = ao->driver->get_space ? ao->driver->get_space(ao) : INT_MAX;
+ if (data.samples > space)
+ data.samples = space;
+ if (data.samples <= 0)
+ return 0;
+ int flags = 0;
+ if (p->final_chunk && data.samples == max)
+ flags |= AOPLAY_FINAL_CHUNK;
+ int r = ao->driver->play(ao, data.planes, data.samples, flags);
+ if (r > data.samples) {
+ MP_WARN(ao, "Audio device returned non-sense value.");
+ r = data.samples;
+ }
+ if (r > 0)
+ mp_audio_buffer_skip(p->buffer, r);
+ if (p->final_chunk && mp_audio_buffer_samples(p->buffer) == 0)
+ p->playing = false;
+ return r;
+}
+
+static void *playthread(void *arg)
+{
+ struct ao *ao = arg;
+ struct ao_push_state *p = ao->api_priv;
+ pthread_mutex_lock(&p->lock);
+ while (!p->terminate) {
+ double timeout = 2.0;
+ if (p->playing) {
+ double min_wait = ao->device_buffer / (double)ao->samplerate;
+ min_wait *= 0.75;
+ int r = ao_play_data(ao);
+ // The device buffers are not necessarily full, but writing to the
+ // AO buffer will wake up this thread anyway.
+ bool buffers_full = r <= 0;
+ // We have to estimate when the AO needs data again.
+ if (buffers_full && ao->driver->get_delay) {
+ float buffered_audio = ao->driver->get_delay(ao);
+ timeout = buffered_audio - 0.050;
+ if (timeout > 0.100) {
+ // Keep extra safety margin if the buffers are large
+ timeout = MPMAX(timeout - 0.200, 0.100);
+ } else {
+ timeout = MPMAX(timeout, min_wait);
+ }
+ } else {
+ timeout = min_wait;
+ }
+ }
+ struct timespec deadline = mpthread_get_deadline(timeout);
+ pthread_cond_timedwait(&p->wakeup, &p->lock, &deadline);
+ }
+ pthread_mutex_unlock(&p->lock);
+ return NULL;
+}
+
+static void uninit(struct ao *ao, bool cut_audio)
+{
+ struct ao_push_state *p = ao->api_priv;
+
+ pthread_mutex_lock(&p->lock);
+ p->terminate = true;
+ pthread_cond_signal(&p->wakeup);
+ pthread_mutex_unlock(&p->lock);
+
+ pthread_join(p->thread, NULL);
+
+ ao->driver->uninit(ao, cut_audio);
+
+ pthread_cond_destroy(&p->wakeup);
+ pthread_mutex_destroy(&p->lock);
+}
+
+static int init(struct ao *ao)
+{
+ struct ao_push_state *p = ao->api_priv;
+
+ pthread_mutex_init(&p->lock, NULL);
+ pthread_cond_init(&p->wakeup, NULL);
+
+ p->buffer = mp_audio_buffer_create(ao);
+ mp_audio_buffer_reinit_fmt(p->buffer, ao->format,
+ &ao->channels, ao->samplerate);
+ mp_audio_buffer_preallocate_min(p->buffer, ao->buffer);
+ if (pthread_create(&p->thread, NULL, playthread, ao)) {
+ ao->driver->uninit(ao, true);
+ return -1;
+ }
+ return 0;
+}
+
+const struct ao_driver ao_api_push = {
+ .init = init,
+ .control = control,
+ .uninit = uninit,
+ .reset = reset,
+ .get_space = get_space,
+ .play = play,
+ .get_delay = get_delay,
+ .pause = pause,
+ .resume = resume,
+ .priv_size = sizeof(struct ao_push_state),
+};
+
+int ao_play_silence(struct ao *ao, int samples)
+{
+ assert(ao->api == &ao_api_push);
+ if (samples <= 0 || AF_FORMAT_IS_SPECIAL(ao->format) || !ao->driver->play)
+ return 0;
+ char *p = talloc_size(NULL, samples * ao->sstride);
+ af_fill_silence(p, samples * ao->sstride, ao->format);
+ void *tmp[MP_NUM_CHANNELS];
+ for (int n = 0; n < MP_NUM_CHANNELS; n++)
+ tmp[n] = p;
+ int r = ao->driver->play(ao, tmp, samples, 0);
+ talloc_free(p);
+ return r;
+}
diff --git a/old-makefile b/old-makefile
index 0614829e6f..c2ca755335 100644
--- a/old-makefile
+++ b/old-makefile
@@ -181,6 +181,8 @@ SOURCES = audio/audio.c \
audio/out/ao.c \
audio/out/ao_null.c \
audio/out/ao_pcm.c \
+ audio/out/pull.c \
+ audio/out/push.c \
bstr/bstr.c \
common/asxparser.c \
common/av_common.c \
diff --git a/wscript_build.py b/wscript_build.py
index 2d24a4c685..999a14495a 100644
--- a/wscript_build.py
+++ b/wscript_build.py
@@ -145,6 +145,8 @@ def build(ctx):
( "audio/out/ao_sdl.c", "sdl2" ),
( "audio/out/ao_sndio.c", "sndio" ),
( "audio/out/ao_wasapi.c", "wasapi" ),
+ ( "audio/out/pull.c" ),
+ ( "audio/out/push.c" ),
## Bstr
( "bstr/bstr.c" ),