summaryrefslogtreecommitdiffstats
path: root/audio
diff options
context:
space:
mode:
Diffstat (limited to 'audio')
-rw-r--r--audio/aframe.c13
-rw-r--r--audio/aframe.h1
-rw-r--r--audio/filter/af_drop.c114
-rw-r--r--audio/filter/af_format.c6
-rw-r--r--audio/filter/af_scaletempo.c3
-rw-r--r--audio/out/ao.c94
-rw-r--r--audio/out/ao_alsa.c7
-rw-r--r--audio/out/ao_lavc.c6
-rw-r--r--audio/out/ao_null.c9
-rw-r--r--audio/out/ao_openal.c11
-rw-r--r--audio/out/ao_pulse.c10
-rw-r--r--audio/out/buffer.c752
-rw-r--r--audio/out/internal.h11
-rw-r--r--audio/out/pull.c340
-rw-r--r--audio/out/push.c572
15 files changed, 896 insertions, 1053 deletions
diff --git a/audio/aframe.c b/audio/aframe.c
index 35259ba208..0bb2f872b4 100644
--- a/audio/aframe.c
+++ b/audio/aframe.c
@@ -100,6 +100,19 @@ void mp_aframe_unref_data(struct mp_aframe *frame)
talloc_free(tmp);
}
+// Allocate this much data. Returns false for failure (data already allocated,
+// invalid sample count or format, allocation failures).
+// Normally you're supposed to use a frame pool and mp_aframe_pool_allocate().
+bool mp_aframe_alloc_data(struct mp_aframe *frame, int samples)
+{
+ if (mp_aframe_is_allocated(frame))
+ return false;
+ struct mp_aframe_pool *p = mp_aframe_pool_create(NULL);
+ int r = mp_aframe_pool_allocate(p, frame, samples);
+ talloc_free(p);
+ return r >= 0;
+}
+
// Return a new reference to the data in av_frame. av_frame itself is not
// touched. Returns NULL if not representable, or if input is NULL.
// Does not copy the timestamps.
diff --git a/audio/aframe.h b/audio/aframe.h
index 21d4494f5f..be456a3dd1 100644
--- a/audio/aframe.h
+++ b/audio/aframe.h
@@ -20,6 +20,7 @@ struct AVFrame *mp_aframe_to_avframe_and_unref(struct mp_aframe *frame);
struct AVFrame *mp_aframe_get_raw_avframe(struct mp_aframe *frame);
bool mp_aframe_is_allocated(struct mp_aframe *frame);
+bool mp_aframe_alloc_data(struct mp_aframe *frame, int samples);
void mp_aframe_config_copy(struct mp_aframe *dst, struct mp_aframe *src);
bool mp_aframe_config_equals(struct mp_aframe *a, struct mp_aframe *b);
diff --git a/audio/filter/af_drop.c b/audio/filter/af_drop.c
new file mode 100644
index 0000000000..724c482720
--- /dev/null
+++ b/audio/filter/af_drop.c
@@ -0,0 +1,114 @@
+#include "audio/aframe.h"
+#include "audio/format.h"
+#include "common/common.h"
+#include "filters/f_autoconvert.h"
+#include "filters/filter_internal.h"
+#include "filters/user_filters.h"
+
+struct priv {
+ double speed;
+ double diff; // amount of too many additional samples in normal speed
+ struct mp_aframe *last; // for repeating
+};
+
+static void process(struct mp_filter *f)
+{
+ struct priv *p = f->priv;
+
+ if (!mp_pin_in_needs_data(f->ppins[1]))
+ return;
+
+ struct mp_frame frame = {0};
+
+ double last_dur = p->last ? mp_aframe_duration(p->last) : 0;
+ if (p->last && p->diff < 0 && -p->diff > last_dur / 2) {
+ MP_VERBOSE(f, "repeat\n");
+ frame = MAKE_FRAME(MP_FRAME_AUDIO, p->last);
+ p->last = NULL;
+ } else {
+ frame = mp_pin_out_read(f->ppins[0]);
+
+ if (frame.type == MP_FRAME_AUDIO) {
+ last_dur = mp_aframe_duration(frame.data);
+ p->diff -= last_dur;
+ if (p->diff > last_dur / 2) {
+ MP_VERBOSE(f, "drop\n");
+ mp_frame_unref(&frame);
+ mp_filter_internal_mark_progress(f);
+ }
+ }
+ }
+
+ if (frame.type == MP_FRAME_AUDIO) {
+ struct mp_aframe *fr = frame.data;
+ talloc_free(p->last);
+ p->last = mp_aframe_new_ref(fr);
+ mp_aframe_mul_speed(fr, p->speed);
+ p->diff += mp_aframe_duration(fr);
+ mp_aframe_set_pts(p->last, mp_aframe_end_pts(fr));
+ } else if (frame.type == MP_FRAME_EOF) {
+ TA_FREEP(&p->last);
+ }
+ mp_pin_in_write(f->ppins[1], frame);
+}
+
+static bool command(struct mp_filter *f, struct mp_filter_command *cmd)
+{
+ struct priv *p = f->priv;
+
+ switch (cmd->type) {
+ case MP_FILTER_COMMAND_SET_SPEED:
+ p->speed = cmd->speed;
+ return true;
+ }
+
+ return false;
+}
+
+static void reset(struct mp_filter *f)
+{
+ struct priv *p = f->priv;
+
+ TA_FREEP(&p->last);
+ p->diff = 0;
+}
+
+static void destroy(struct mp_filter *f)
+{
+ reset(f);
+}
+
+static const struct mp_filter_info af_drop_filter = {
+ .name = "drop",
+ .priv_size = sizeof(struct priv),
+ .process = process,
+ .command = command,
+ .reset = reset,
+ .destroy = destroy,
+};
+
+static struct mp_filter *af_drop_create(struct mp_filter *parent, void *options)
+{
+ struct mp_filter *f = mp_filter_create(parent, &af_drop_filter);
+ if (!f) {
+ talloc_free(options);
+ return NULL;
+ }
+
+ mp_filter_add_pin(f, MP_PIN_IN, "in");
+ mp_filter_add_pin(f, MP_PIN_OUT, "out");
+
+ struct priv *p = f->priv;
+ p->speed = 1.0;
+
+ return f;
+}
+
+const struct mp_user_filter_entry af_drop = {
+ .desc = {
+ .description = "Change audio speed by dropping/repeating frames",
+ .name = "drop",
+ .priv_size = sizeof(struct priv),
+ },
+ .create = af_drop_create,
+};
diff --git a/audio/filter/af_format.c b/audio/filter/af_format.c
index 79d78d1d96..88ae99ed56 100644
--- a/audio/filter/af_format.c
+++ b/audio/filter/af_format.c
@@ -130,9 +130,11 @@ const struct mp_user_filter_entry af_format = {
.options = (const struct m_option[]) {
{"format", OPT_AUDIOFORMAT(in_format)},
{"srate", OPT_INT(in_srate), M_RANGE(1000, 8*48000)},
- {"channels", OPT_CHANNELS(in_channels), .min = 1},
+ {"channels", OPT_CHANNELS(in_channels),
+ .flags = M_OPT_CHANNELS_LIMITED},
{"out-srate", OPT_INT(out_srate), M_RANGE(1000, 8*48000)},
- {"out-channels", OPT_CHANNELS(out_channels), .min = 1},
+ {"out-channels", OPT_CHANNELS(out_channels),
+ .flags = M_OPT_CHANNELS_LIMITED},
{"fail", OPT_FLAG(fail)},
{0}
},
diff --git a/audio/filter/af_scaletempo.c b/audio/filter/af_scaletempo.c
index 911fd8914e..b1ee790189 100644
--- a/audio/filter/af_scaletempo.c
+++ b/audio/filter/af_scaletempo.c
@@ -538,7 +538,8 @@ static void reset(struct mp_filter *f)
s->bytes_queued = 0;
s->bytes_to_slide = 0;
s->frames_stride_error = 0;
- memset(s->buf_overlap, 0, s->bytes_overlap);
+ if (s->buf_overlap && s->bytes_overlap)
+ memset(s->buf_overlap, 0, s->bytes_overlap);
TA_FREEP(&s->in);
}
diff --git a/audio/out/ao.c b/audio/out/ao.c
index 480dad69e0..7b301cd2e7 100644
--- a/audio/out/ao.c
+++ b/audio/out/ao.c
@@ -200,13 +200,10 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
af_fmt_to_str(ao->format));
ao->device = talloc_strdup(ao, dev);
-
- 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);
-
ao->stream_silence = flags & AO_INIT_STREAM_SILENCE;
+ init_buffer_pre(ao);
+
ao->period_size = 1;
int r = ao->driver->init(ao);
@@ -216,13 +213,14 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
char redirect[80], rdevice[80];
snprintf(redirect, sizeof(redirect), "%s", ao->redirect);
snprintf(rdevice, sizeof(rdevice), "%s", ao->device ? ao->device : "");
- talloc_free(ao);
+ ao_uninit(ao);
return ao_init(probing, global, wakeup_cb, wakeup_ctx,
encode_lavc_ctx, flags, samplerate, format, channels,
rdevice, redirect);
}
goto fail;
}
+ ao->driver_initialized = true;
if (ao->period_size < 1) {
MP_ERR(ao, "Invalid period size set.\n");
@@ -249,12 +247,12 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
ao->buffer = (ao->buffer + align - 1) / align * align;
MP_VERBOSE(ao, "using soft-buffer of %d samples.\n", ao->buffer);
- if (ao->api->init(ao) < 0)
+ if (!init_buffer_post(ao))
goto fail;
return ao;
fail:
- talloc_free(ao);
+ ao_uninit(ao);
return NULL;
}
@@ -348,86 +346,6 @@ struct ao *ao_init_best(struct mpv_global *global,
return ao;
}
-// Uninitialize and destroy the AO. Remaining audio must be dropped.
-void ao_uninit(struct ao *ao)
-{
- if (ao)
- ao->api->uninit(ao);
- talloc_free(ao);
-}
-
-// 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)
-{
- return ao->api->play(ao, data, samples, flags);
-}
-
-int ao_control(struct ao *ao, enum aocontrol cmd, void *arg)
-{
- return ao->api->control ? ao->api->control(ao, cmd, arg) : 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.
-double ao_get_delay(struct ao *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->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->api->reset)
- ao->api->reset(ao);
- atomic_fetch_and(&ao->events_, ~(unsigned int)AO_EVENT_UNDERRUN);
-}
-
-// 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->api->pause)
- ao->api->pause(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)
-{
- if (ao->api->resume)
- ao->api->resume(ao);
-}
-
-// Block until the current audio buffer has played completely.
-void ao_drain(struct ao *ao)
-{
- if (ao->api->drain)
- ao->api->drain(ao);
-}
-
-bool ao_eof_reached(struct ao *ao)
-{
- return ao->api->get_eof ? ao->api->get_eof(ao) : true;
-}
-
// Query the AO_EVENT_*s as requested by the events parameter, and return them.
int ao_query_and_reset_events(struct ao *ao, int events)
{
diff --git a/audio/out/ao_alsa.c b/audio/out/ao_alsa.c
index b4fa18891b..e100c0fb12 100644
--- a/audio/out/ao_alsa.c
+++ b/audio/out/ao_alsa.c
@@ -944,12 +944,6 @@ static int init(struct ao *ao)
return r;
}
-static void drain(struct ao *ao)
-{
- struct priv *p = ao->priv;
- snd_pcm_drain(p->alsa);
-}
-
static int get_space(struct ao *ao)
{
struct priv *p = ao->priv;
@@ -1260,7 +1254,6 @@ const struct ao_driver audio_out_alsa = {
.pause = audio_pause,
.resume = audio_resume,
.reset = reset,
- .drain = drain,
.wait = audio_wait,
.wakeup = ao_wakeup_poll,
.list_devs = list_devs,
diff --git a/audio/out/ao_lavc.c b/audio/out/ao_lavc.c
index a38c01e0b2..ad3865964c 100644
--- a/audio/out/ao_lavc.c
+++ b/audio/out/ao_lavc.c
@@ -345,11 +345,6 @@ static int play(struct ao *ao, void **data, int samples, int flags)
return taken;
}
-static void drain(struct ao *ao)
-{
- // pretend we support it, so generic code doesn't force a wait
-}
-
const struct ao_driver audio_out_lavc = {
.encode = true,
.description = "audio encoding using libavcodec",
@@ -361,7 +356,6 @@ const struct ao_driver audio_out_lavc = {
.uninit = uninit,
.get_space = get_space,
.play = play,
- .drain = drain,
};
// vim: sw=4 ts=4 et tw=80
diff --git a/audio/out/ao_null.c b/audio/out/ao_null.c
index 7e15b58e00..07a0d4748c 100644
--- a/audio/out/ao_null.c
+++ b/audio/out/ao_null.c
@@ -122,14 +122,6 @@ static void uninit(struct ao *ao)
{
}
-static void wait_drain(struct ao *ao)
-{
- struct priv *priv = ao->priv;
- drain(ao);
- if (!priv->paused)
- mp_sleep_us(1000000.0 * priv->buffered / ao->samplerate / priv->speed);
-}
-
// stop playing and empty buffers (for seeking/pause)
static void reset(struct ao *ao)
{
@@ -229,7 +221,6 @@ const struct ao_driver audio_out_null = {
.get_delay = get_delay,
.pause = pause,
.resume = resume,
- .drain = wait_drain,
.priv_size = sizeof(struct priv),
.priv_defaults = &(const struct priv) {
.bufferlen = 0.2,
diff --git a/audio/out/ao_openal.c b/audio/out/ao_openal.c
index 53fcaca05e..aa20fb8bed 100644
--- a/audio/out/ao_openal.c
+++ b/audio/out/ao_openal.c
@@ -274,16 +274,6 @@ err_out:
return -1;
}
-static void drain(struct ao *ao)
-{
- ALint state;
- alGetSourcei(source, AL_SOURCE_STATE, &state);
- while (state == AL_PLAYING) {
- mp_sleep_us(10000);
- alGetSourcei(source, AL_SOURCE_STATE, &state);
- }
-}
-
static void unqueue_buffers(struct ao *ao)
{
struct priv *q = ao->priv;
@@ -420,7 +410,6 @@ const struct ao_driver audio_out_openal = {
.pause = audio_pause,
.resume = audio_resume,
.reset = reset,
- .drain = drain,
.priv_size = sizeof(struct priv),
.priv_defaults = &(const struct priv) {
.num_buffers = 4,
diff --git a/audio/out/ao_pulse.c b/audio/out/ao_pulse.c
index fc5fb0caeb..c90ee255c9 100644
--- a/audio/out/ao_pulse.c
+++ b/audio/out/ao_pulse.c
@@ -278,15 +278,6 @@ static bool select_chmap(struct ao *ao, pa_channel_map *dst)
chmap_pa_from_mp(dst, &ao->channels);
}
-static void drain(struct ao *ao)
-{
- struct priv *priv = ao->priv;
- if (priv->stream) {
- pa_threaded_mainloop_lock(priv->mainloop);
- waitop(priv, pa_stream_drain(priv->stream, success_cb, ao));
- }
-}
-
static void uninit(struct ao *ao)
{
struct priv *priv = ao->priv;
@@ -826,7 +817,6 @@ const struct ao_driver audio_out_pulse = {
.get_delay = get_delay,
.pause = pause,
.resume = resume,
- .drain = drain,
.wait = wait_audio,
.wakeup = wakeup,
.hotplug_init = hotplug_init,
diff --git a/audio/out/buffer.c b/audio/out/buffer.c
new file mode 100644
index 0000000000..720cfaa58e
--- /dev/null
+++ b/audio/out/buffer.c
@@ -0,0 +1,752 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stddef.h>
+#include <pthread.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "ao.h"
+#include "internal.h"
+#include "audio/aframe.h"
+#include "audio/format.h"
+
+#include "common/msg.h"
+#include "common/common.h"
+
+#include "input/input.h"
+
+#include "osdep/io.h"
+#include "osdep/timer.h"
+#include "osdep/threads.h"
+#include "osdep/atomic.h"
+#include "misc/ring.h"
+
+struct buffer_state {
+ pthread_mutex_t lock;
+ pthread_cond_t wakeup;
+
+ // Access from AO driver's thread only.
+ char *convert_buffer;
+
+ // --- protected by lock
+
+ struct mp_ring *buffers[MP_NUM_CHANNELS];
+
+
+ bool streaming; // AO streaming active
+ bool playing; // logically playing audio from buffer
+ bool paused; // logically paused; implies playing=true
+ bool final_chunk; // if buffer contains EOF
+
+ 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).
+ bool still_playing;
+ double expected_end_time;
+ bool wait_on_ao;
+ 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];
+};
+
+static void *playthread(void *arg);
+
+// lock must be held
+static void wakeup_playthread(struct ao *ao)
+{
+ struct buffer_state *p = ao->buffer_state;
+ if (ao->driver->wakeup)
+ ao->driver->wakeup(ao);
+ p->need_wakeup = true;
+ pthread_cond_signal(&p->wakeup);
+}
+
+static int unlocked_get_space(struct ao *ao)
+{
+ struct buffer_state *p = ao->buffer_state;
+
+ int space = mp_ring_available(p->buffers[0]) / ao->sstride;
+
+ // 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) {
+ int align = af_format_sample_alignment(ao->format);
+ int device_space = ao->driver->get_space(ao);
+ 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
+ // byte based and doesn't do proper chunked processing.
+ int min_buffer = ao->buffer + 64;
+ int missing = min_buffer - device_buffered - soft_buffered;
+ missing = (missing + align - 1) / align * align;
+ // 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);
+ }
+
+ 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;
+ pthread_mutex_lock(&p->lock);
+ int space = unlocked_get_space(ao);
+ pthread_mutex_unlock(&p->lock);
+ 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;
+
+ pthread_mutex_lock(&p->lock);
+
+ int write_samples = mp_ring_available(p->buffers[0]) / ao->sstride;
+ write_samples = MPMIN(write_samples, samples);
+
+ int write_bytes = write_samples * ao->sstride;
+ for (int n = 0; n < ao->num_planes; n++) {
+ int r = mp_ring_write(p->buffers[n], data[n], write_bytes);
+ assert(r == write_bytes);
+ }
+
+ p->paused = false;
+ p->final_chunk = write_samples == samples && (flags & AOPLAY_FINAL_CHUNK);
+
+ if (p->underflow)
+ MP_DBG(ao, "Audio underrun by %lld samples.\n", (long long)p->underflow);
+ p->underflow = 0;
+
+ if (write_samples) {
+ p->playing = true;
+ p->still_playing = true;
+ p->expected_end_time = 0;
+
+ if (!ao->driver->play && !p->streaming) {
+ p->streaming = true;
+ ao->driver->resume(ao);
+ }
+
+ wakeup_playthread(ao);
+ }
+ pthread_mutex_unlock(&p->lock);
+
+ 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 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)
+{
+ struct buffer_state *p = ao->buffer_state;
+ int full_bytes = samples * ao->sstride;
+ bool need_wakeup = false;
+ int bytes = 0;
+
+ pthread_mutex_lock(&p->lock);
+
+ if (!p->playing || p->paused)
+ goto end;
+
+ int buffered_bytes = mp_ring_buffered(p->buffers[0]);
+ bytes = MPMIN(buffered_bytes, full_bytes);
+
+ if (full_bytes > bytes && !p->final_chunk) {
+ p->underflow += (full_bytes - bytes) / ao->sstride;
+ ao_underrun_event(ao);
+ }
+
+ if (bytes > 0)
+ p->end_time_us = out_time_us;
+
+ for (int n = 0; n < ao->num_planes; n++)
+ mp_ring_read(p->buffers[n], data[n], bytes);
+
+ // Half of the buffer played -> request more.
+ need_wakeup = buffered_bytes - bytes <= mp_ring_size(p->buffers[0]) / 2;
+
+end:
+
+ pthread_mutex_unlock(&p->lock);
+
+ if (need_wakeup)
+ ao->wakeup_cb(ao->wakeup_ctx);
+
+ // pad with silence (underflow/paused/eof)
+ for (int n = 0; n < ao->num_planes; n++)
+ af_fill_silence((char *)data[n] + bytes, full_bytes - bytes, ao->format);
+
+ ao_post_process_data(ao, data, samples);
+
+ return bytes / ao->sstride;
+}
+
+// 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)
+{
+ 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);
+
+ assert(ao->format == fmt->src_fmt);
+ assert(ao->channels.num == fmt->channels);
+
+ bool planar = af_fmt_is_planar(fmt->src_fmt);
+ int planes = planar ? fmt->channels : 1;
+ int plane_samples = samples * (planar ? 1: fmt->channels);
+ int src_plane_size = plane_samples * af_fmt_to_bytes(fmt->src_fmt);
+ int dst_plane_size = plane_samples * fmt->dst_bits / 8;
+
+ int needed = src_plane_size * planes;
+ if (needed > talloc_get_size(p->convert_buffer) || !p->convert_buffer) {
+ talloc_free(p->convert_buffer);
+ p->convert_buffer = talloc_size(NULL, needed);
+ }
+
+ 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);
+
+ ao_convert_inplace(fmt, ndata, samples);
+ for (int n = 0; n < planes; n++)
+ memcpy(data[n], ndata[n], dst_plane_size);
+
+ return res;
+}
+
+int ao_control(struct ao *ao, enum aocontrol cmd, void *arg)
+{
+ struct buffer_state *p = ao->buffer_state;
+ int r = CONTROL_UNKNOWN;
+ if (ao->driver->control) {
+ pthread_mutex_lock(&p->lock);
+ r = ao->driver->control(ao, cmd, arg);
+ pthread_mutex_unlock(&p->lock);
+ }
+ return r;
+}
+
+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) {
+ int64_t end = p->end_time_us;
+ int64_t now = mp_time_us();
+ driver_delay += MPMAX(0, (end - now) / (1000.0 * 1000.0));
+ }
+
+ 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;
+
+ pthread_mutex_lock(&p->lock);
+ double delay = unlocked_get_delay(ao);
+ pthread_mutex_unlock(&p->lock);
+ 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;
+
+ pthread_mutex_lock(&p->lock);
+
+ for (int n = 0; n < ao->num_planes; n++)
+ mp_ring_reset(p->buffers[n]);
+
+ if (!ao->stream_silence && ao->driver->reset) {
+ ao->driver->reset(ao); // assumes the audio callback thread is stopped
+ p->streaming = false;
+ }
+ p->paused = false;
+ p->playing = false;
+ if (p->still_playing)
+ wakeup_playthread(ao);
+ p->still_playing = false;
+ p->end_time_us = 0;
+
+ atomic_fetch_and(&ao->events_, ~(unsigned int)AO_EVENT_UNDERRUN);
+
+ pthread_mutex_unlock(&p->lock);
+}
+
+// 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;
+
+ pthread_mutex_lock(&p->lock);
+
+ if (p->playing && !p->paused) {
+ if (p->streaming && !ao->stream_silence) {
+ if (ao->driver->pause) {
+ ao->driver->pause(ao);
+ } else if (ao->driver->reset) {
+ ao->driver->reset(ao);
+ p->streaming = false;
+ }
+ }
+ p->paused = true;
+ wakeup_playthread(ao);
+ }
+
+ pthread_mutex_unlock(&p->lock);
+}
+
+// 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;
+
+ pthread_mutex_lock(&p->lock);
+
+ if (p->playing && p->paused) {
+ if (p->streaming && ao->driver->resume)
+ ao->driver->resume(ao);
+ p->paused = false;
+ p->expected_end_time = 0;
+ wakeup_playthread(ao);
+ }
+
+ pthread_mutex_unlock(&p->lock);
+}
+
+bool ao_eof_reached(struct ao *ao)
+{
+ struct buffer_state *p = ao->buffer_state;
+
+ pthread_mutex_lock(&p->lock);
+ bool eof = !p->playing;
+ if (ao->driver->play) {
+ eof |= !p->still_playing;
+ } else {
+ // For simplicity, ignore the latency. Otherwise, we would have to run
+ // an extra thread to time it.
+ eof |= mp_ring_buffered(p->buffers[0]) == 0;
+ }
+ pthread_mutex_unlock(&p->lock);
+
+ return eof;
+}
+
+// Block until the current audio buffer has played completely.
+void ao_drain(struct ao *ao)
+{
+ struct buffer_state *p = ao->buffer_state;
+
+ 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);
+
+ 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);
+ }
+
+ 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);
+ p->terminate = true;
+ wakeup_playthread(ao);
+ pthread_mutex_unlock(&p->lock);
+
+ pthread_join(p->thread, NULL);
+ p->thread_valid = false;
+ }
+
+ if (ao->driver_initialized)
+ ao->driver->uninit(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);
+
+ talloc_free(ao);
+}
+
+void init_buffer_pre(struct ao *ao)
+{
+ ao->buffer_state = talloc_zero(ao, struct buffer_state);
+}
+
+bool init_buffer_post(struct ao *ao)
+{
+ struct buffer_state *p = ao->buffer_state;
+
+ if (!ao->driver->play)
+ assert(ao->driver->resume);
+
+ 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;
+ }
+
+ p->thread_valid = true;
+ if (pthread_create(&p->thread, NULL, playthread, ao)) {
+ p->thread_valid = false;
+ return false;
+ }
+ } else {
+ if (ao->stream_silence) {
+ ao->driver->resume(ao);
+ p->streaming = true;
+ }
+ }
+
+ return true;
+}
+
+static bool realloc_buf(struct ao *ao, int samples)
+{
+ struct buffer_state *p = ao->buffer_state;
+
+ samples = MPMAX(1, samples);
+
+ if (!p->temp_buf || samples > mp_aframe_get_size(p->temp_buf)) {
+ TA_FREEP(&p->temp_buf);
+ p->temp_buf = mp_aframe_create();
+ if (!mp_aframe_set_format(p->temp_buf, ao->format) ||
+ !mp_aframe_set_chmap(p->temp_buf, &ao->channels) ||
+ !mp_aframe_set_rate(p->temp_buf, ao->samplerate) ||
+ !mp_aframe_alloc_data(p->temp_buf, samples))
+ {
+ TA_FREEP(&p->temp_buf);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// called locked
+static void ao_play_data(struct ao *ao)
+{
+ struct buffer_state *p = ao->buffer_state;
+ int space = ao->driver->get_space(ao);
+ 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;
+ }
+ void **planes = (void **)mp_aframe_get_data_rw(p->temp_buf);
+ assert(planes);
+ int samples = mp_ring_buffered(p->buffers[0]) / ao->sstride;
+ if (samples > space)
+ samples = space;
+ if (play_silence)
+ samples = space;
+ 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;
+ }
+ 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) {
+ 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 (r > 0) {
+ p->expected_end_time = 0;
+ p->streaming = true;
+ }
+ // 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;
+ // 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);
+ 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);
+}
+
+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) {
+ bool blocked = ao->driver->initially_blocked && !p->initial_unblocked;
+ bool playing = (!p->paused || ao->stream_silence) && !blocked;
+ if (playing)
+ 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;
+ }
+ }
+
+ if (was_playing && !p->still_playing)
+ ao->wakeup_cb(ao->wakeup_ctx);
+ pthread_cond_signal(&p->wakeup); // for draining
+
+ 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);
+ }
+ }
+ }
+ MP_STATS(ao, "end audio wait");
+ }
+ p->need_wakeup = false;
+ }
+ pthread_mutex_unlock(&p->lock);
+ return NULL;
+}
+
+void ao_unblock(struct ao *ao)
+{
+ if (ao->driver->play) {
+ 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);
+ }
+}
+
+// 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
diff --git a/audio/out/internal.h b/audio/out/internal.h
index 7bdd17ad8a..71f98304be 100644
--- a/audio/out/internal.h
+++ b/audio/out/internal.h
@@ -37,8 +37,8 @@ struct ao {
bool untimed; // don't assume realtime playback
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;
+ bool driver_initialized;
void *priv;
struct mpv_global *global;
struct encode_lavc_context *encode_lavc_ctx;
@@ -75,12 +75,12 @@ struct ao {
int buffer;
double def_buffer;
+ struct buffer_state *buffer_state;
void *api_priv;
};
-extern const struct ao_driver ao_api_push;
-extern const struct ao_driver ao_api_pull;
-
+void init_buffer_pre(struct ao *ao);
+bool init_buffer_post(struct ao *ao);
/* Note:
*
@@ -107,7 +107,6 @@ extern const struct ao_driver ao_api_pull;
* resume
* Optional:
* control
- * drain
* wait
* wakeup
* b) ->play must be NULL. ->resume must be provided, and should make the
@@ -162,8 +161,6 @@ struct ao_driver {
int (*play)(struct ao *ao, void **data, int samples, int flags);
// push based: see ao_get_delay()
double (*get_delay)(struct ao *ao);
- // push based: block until all queued audio is played (optional)
- void (*drain)(struct ao *ao);
// Optional. Return true if audio has stopped in any way.
bool (*get_eof)(struct ao *ao);
// Wait until the audio buffer needs to be refilled. The lock is the
diff --git a/audio/out/pull.c b/audio/out/pull.c
deleted file mode 100644
index fe8204e01b..0000000000
--- a/audio/out/pull.c
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 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 Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser 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 "input/input.h"
-
-#include "osdep/timer.h"
-#include "osdep/threads.h"
-#include "osdep/atomic.h"
-#include "misc/ring.h"
-
-/*
- * Note: there is some stupid stuff in this file in order to avoid mutexes.
- * This requirement is dictated by several audio APIs, at least jackaudio.
- */
-
-enum {
- AO_STATE_NONE, // idle (e.g. before playback started, or after playback
- // finished, but device is open)
- AO_STATE_WAIT, // wait for callback to go into AO_STATE_NONE state
- AO_STATE_PLAY, // play the buffer
- AO_STATE_BUSY, // like AO_STATE_PLAY, but ao_read_data() is being called
-};
-
-#define IS_PLAYING(st) ((st) == AO_STATE_PLAY || (st) == AO_STATE_BUSY)
-
-struct ao_pull_state {
- // Be very careful with the order when accessing planes.
- struct mp_ring *buffers[MP_NUM_CHANNELS];
-
- // AO_STATE_*
- atomic_int state;
-
- // Set when the buffer is intentionally not fed anymore in PLAY state.
- atomic_bool draining;
-
- // Set by the audio thread when an underflow was detected.
- // It adds the number of samples.
- atomic_int underflow;
-
- // Device delay of the last written sample, in realtime.
- atomic_llong end_time_us;
-
- char *convert_buffer;
-};
-
-static void set_state(struct ao *ao, int new_state)
-{
- struct ao_pull_state *p = ao->api_priv;
- while (1) {
- int old = atomic_load(&p->state);
- if (old == AO_STATE_BUSY) {
- // A spinlock, because some audio APIs don't want us to use mutexes.
- mp_sleep_us(1);
- continue;
- }
- if (atomic_compare_exchange_strong(&p->state, &old, new_state))
- break;
- }
-}
-
-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);
- }
-
- int state = atomic_load(&p->state);
- if (!IS_PLAYING(state)) {
- atomic_store(&p->draining, false);
- atomic_store(&p->underflow, 0);
- set_state(ao, AO_STATE_PLAY);
- if (!ao->stream_silence)
- ao->driver->resume(ao);
- }
-
- bool draining = write_samples == samples && (flags & AOPLAY_FINAL_CHUNK);
- atomic_store(&p->draining, draining);
-
- int underflow = atomic_fetch_and(&p->underflow, 0);
- if (underflow)
- MP_DBG(ao, "Audio underrun by %d samples.\n", underflow);
-
- 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 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)
-{
- assert(ao->api == &ao_api_pull);
-
- struct ao_pull_state *p = ao->api_priv;
- int full_bytes = samples * ao->sstride;
- bool need_wakeup = false;
- int bytes = 0;
-
- // Play silence in states other than AO_STATE_PLAY.
- if (!atomic_compare_exchange_strong(&p->state, &(int){AO_STATE_PLAY},
- AO_STATE_BUSY))
- goto end;
-
- // Since the writer will write the first plane last, its buffered amount
- // of data is the minimum amount across all planes.
- int buffered_bytes = mp_ring_buffered(p->buffers[0]);
- bytes = MPMIN(buffered_bytes, full_bytes);
-
- if (full_bytes > bytes && !atomic_load(&p->draining)) {
- atomic_fetch_add(&p->underflow, (full_bytes - bytes) / ao->sstride);
- ao_underrun_event(ao);
- }
-
- if (bytes > 0)
- atomic_store(&p->end_time_us, out_time_us);
-
- for (int n = 0; n < ao->num_planes; n++) {
- int r = mp_ring_read(p->buffers[n], data[n], bytes);
- bytes = MPMIN(bytes, r);
- }
-
- // Half of the buffer played -> request more.
- need_wakeup = buffered_bytes - bytes <= mp_ring_size(p->buffers[0]) / 2;
-
- // Should never fail.
- atomic_compare_exchange_strong(&p->state, &(int){AO_STATE_BUSY}, AO_STATE_PLAY);
-
-end:
-
- if (need_wakeup)
- ao->wakeup_cb(ao->wakeup_ctx);
-
- // pad with silence (underflow/paused/eof)
- for (int n = 0; n < ao->num_planes; n++)
- af_fill_silence((char *)data[n] + bytes, full_bytes - bytes, ao->format);
-
- ao_post_process_data(ao, data, samples);
-
- return bytes / ao->sstride;
-}
-
-// 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)
-{
- assert(ao->api == &ao_api_pull);
-
- struct ao_pull_state *p = ao->api_priv;
- void *ndata[MP_NUM_CHANNELS] = {0};
-
- if (!ao_need_conversion(fmt))
- return ao_read_data(ao, data, samples, out_time_us);
-
- assert(ao->format == fmt->src_fmt);
- assert(ao->channels.num == fmt->channels);
-
- bool planar = af_fmt_is_planar(fmt->src_fmt);
- int planes = planar ? fmt->channels : 1;
- int plane_samples = samples * (planar ? 1: fmt->channels);
- int src_plane_size = plane_samples * af_fmt_to_bytes(fmt->src_fmt);
- int dst_plane_size = plane_samples * fmt->dst_bits / 8;
-
- int needed = src_plane_size * planes;
- if (needed > talloc_get_size(p->convert_buffer) || !p->convert_buffer) {
- talloc_free(p->convert_buffer);
- p->convert_buffer = talloc_size(NULL, needed);
- }
-
- 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);
-
- ao_convert_inplace(fmt, ndata, samples);
- for (int n = 0; n < planes; n++)
- memcpy(data[n], ndata[n], dst_plane_size);
-
- return res;
-}
-
-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 double get_delay(struct ao *ao)
-{
- struct ao_pull_state *p = ao->api_priv;
-
- int64_t end = atomic_load(&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->stream_silence && ao->driver->reset)
- ao->driver->reset(ao); // assumes the audio callback thread is stopped
- set_state(ao, AO_STATE_NONE);
- for (int n = 0; n < ao->num_planes; n++)
- mp_ring_reset(p->buffers[n]);
- atomic_store(&p->end_time_us, 0);
-}
-
-static void pause(struct ao *ao)
-{
- if (!ao->stream_silence && ao->driver->reset)
- ao->driver->reset(ao);
- set_state(ao, AO_STATE_NONE);
-}
-
-static void resume(struct ao *ao)
-{
- set_state(ao, AO_STATE_PLAY);
- if (!ao->stream_silence)
- ao->driver->resume(ao);
-}
-
-static bool get_eof(struct ao *ao)
-{
- struct ao_pull_state *p = ao->api_priv;
- // For simplicity, ignore the latency. Otherwise, we would have to run an
- // extra thread to time it.
- return mp_ring_buffered(p->buffers[0]) == 0;
-}
-
-static void drain(struct ao *ao)
-{
- struct ao_pull_state *p = ao->api_priv;
- int state = atomic_load(&p->state);
- if (IS_PLAYING(state)) {
- atomic_store(&p->draining, true);
- // Wait for lower bound.
- mp_sleep_us(mp_ring_buffered(p->buffers[0]) / (double)ao->bps * 1e6);
- // 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 && !get_eof(ao))
- mp_sleep_us(1);
- }
- reset(ao);
-}
-
-static void uninit(struct ao *ao)
-{
- struct ao_pull_state *p = ao->api_priv;
-
- ao->driver->uninit(ao);
-
- talloc_free(p->convert_buffer);
-}
-
-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);
- atomic_store(&p->state, AO_STATE_NONE);
- assert(ao->driver->resume);
-
- if (ao->stream_silence)
- ao->driver->resume(ao);
-
- return 0;
-}
-
-const struct ao_driver ao_api_pull = {
- .init = init,
- .control = control,
- .uninit = uninit,
- .drain = drain,
- .reset = reset,
- .get_space = get_space,
- .play = play,
- .get_delay = get_delay,
- .get_eof = get_eof,
- .pause = pause,
- .resume = resume,
- .priv_size = sizeof(struct ao_pull_state),
-};
diff --git a/audio/out/push.c b/audio/out/push.c
deleted file mode 100644
index 92fd53631b..0000000000
--- a/audio/out/push.c
+++ /dev/null
@@ -1,572 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 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 Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stddef.h>
-#include <pthread.h>
-#include <inttypes.h>
-#include <unistd.h>
-#include <errno.h>
-#include <assert.h>
-
-#include "osdep/io.h"
-
-#include "ao.h"
-#include "internal.h"
-#include "audio/format.h"
-
-#include "common/msg.h"
-#include "common/common.h"
-
-#include "input/input.h"
-
-#include "osdep/threads.h"
-#include "osdep/timer.h"
-#include "osdep/atomic.h"
-
-#include "audio/audio_buffer.h"
-
-struct ao_push_state {
- pthread_t thread;
- pthread_mutex_t lock;
- pthread_cond_t wakeup;
-
- // --- protected by lock
-
- struct mp_audio_buffer *buffer;
-
- uint8_t *silence[MP_NUM_CHANNELS];
- int silence_samples;
-
- bool terminate;
- bool wait_on_ao;
- bool still_playing;
- bool need_wakeup;
- bool paused;
- bool initial_unblocked;
-
- // Whether the current buffer contains the complete audio.
- bool final_chunk;
- double expected_end_time;
-
- int wakeup_pipe[2];
-};
-
-// lock must be held
-static void wakeup_playthread(struct ao *ao)
-{
- struct ao_push_state *p = ao->api_priv;
- if (ao->driver->wakeup)
- ao->driver->wakeup(ao);
- p->need_wakeup = true;
- pthread_cond_signal(&p->wakeup);
-}
-
-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 double unlocked_get_delay(struct ao *ao)
-{
- struct ao_push_state *p = ao->api_priv;
- double driver_delay = 0;
- if (ao->driver->get_delay)
- driver_delay = ao->driver->get_delay(ao);
- return driver_delay + mp_audio_buffer_seconds(p->buffer);
-}
-
-static double get_delay(struct ao *ao)
-{
- struct ao_push_state *p = ao->api_priv;
- pthread_mutex_lock(&p->lock);
- double delay = unlocked_get_delay(ao);
- 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->paused = false;
- if (p->still_playing)
- wakeup_playthread(ao);
- p->still_playing = false;
- pthread_mutex_unlock(&p->lock);
-}
-
-static void audio_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->paused = true;
- wakeup_playthread(ao);
- 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->paused = false;
- p->expected_end_time = 0;
- wakeup_playthread(ao);
- pthread_mutex_unlock(&p->lock);
-}
-
-static void drain(struct ao *ao)
-{
- struct ao_push_state *p = ao->api_priv;
- double maxbuffer = ao->buffer / (double)ao->samplerate + 1;
-
- MP_VERBOSE(ao, "draining...\n");
-
- pthread_mutex_lock(&p->lock);
- if (p->paused)
- goto done;
-
- p->final_chunk = true;
- wakeup_playthread(ao);
-
- // Wait until everything is done. Since the audio API (especially ALSA)
- // can't be trusted to do this right, and we're hard-blocking here, apply
- // an upper bound timeout.
- struct timespec until = mp_rel_time_to_timespec(maxbuffer);
- while (p->still_playing && mp_audio_buffer_samples(p->buffer) > 0) {
- if (pthread_cond_timedwait(&p->wakeup, &p->lock, &until)) {
- MP_WARN(ao, "Draining is taking too long, aborting.\n");
- goto done;
- }
- }
-
- if (ao->driver->drain) {
- ao->driver->drain(ao);
- } else {
- double time = unlocked_get_delay(ao);
- mp_sleep_us(MPMIN(time, maxbuffer) * 1e6);
- }
-
-done:
- pthread_mutex_unlock(&p->lock);
-
- reset(ao);
-}
-
-static int unlocked_get_space(struct ao *ao)
-{
- struct ao_push_state *p = ao->api_priv;
- int space = mp_audio_buffer_get_write_available(p->buffer);
- if (ao->driver->get_space) {
- int align = af_format_sample_alignment(ao->format);
- // The following code attempts to keep the total buffered audio to
- // 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);
- // 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;
- missing = (missing + align - 1) / align * align;
- // 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);
- }
- return space;
-}
-
-static int get_space(struct ao *ao)
-{
- struct ao_push_state *p = ao->api_priv;
- pthread_mutex_lock(&p->lock);
- int space = unlocked_get_space(ao);
- pthread_mutex_unlock(&p->lock);
- return space;
-}
-
-static bool get_eof(struct ao *ao)
-{
- struct ao_push_state *p = ao->api_priv;
- pthread_mutex_lock(&p->lock);
- bool eof = !p->still_playing;
- pthread_mutex_unlock(&p->lock);
- return eof;
-}
-
-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);
-
- 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;
-
- mp_audio_buffer_append(p->buffer, data, samples);
-
- bool got_data = write_samples > 0 || p->paused || p->final_chunk != is_final;
-
- p->final_chunk = is_final;
- p->paused = false;
- if (got_data) {
- p->still_playing = true;
- p->expected_end_time = 0;
-
- // If we don't have new data, the decoder thread basically promises it
- // will send new data as soon as it's available.
- wakeup_playthread(ao);
- }
- pthread_mutex_unlock(&p->lock);
- return write_samples;
-}
-
-static bool realloc_silence(struct ao *ao, int samples)
-{
- struct ao_push_state *p = ao->api_priv;
-
- if (samples <= 0 || !af_fmt_is_pcm(ao->format))
- return false;
-
- if (samples > p->silence_samples) {
- talloc_free(p->silence[0]);
-
- int bytes = af_fmt_to_bytes(ao->format) * samples * ao->channels.num;
- p->silence[0] = talloc_size(p, bytes);
- for (int n = 1; n < MP_NUM_CHANNELS; n++)
- p->silence[n] = p->silence[0];
- p->silence_samples = samples;
-
- af_fill_silence(p->silence[0], bytes, ao->format);
- }
-
- return true;
-}
-
-// called locked
-static void ao_play_data(struct ao *ao)
-{
- struct ao_push_state *p = ao->api_priv;
- int space = ao->driver->get_space(ao);
- bool play_silence = p->paused || (ao->stream_silence && !p->still_playing);
- space = MPMAX(space, 0);
- if (space % ao->period_size)
- MP_ERR(ao, "Audio device reports unaligned available buffer size.\n");
- uint8_t **planes;
- int samples;
- if (play_silence) {
- planes = p->silence;
- samples = realloc_silence(ao, space) ? space : 0;
- } else {
- mp_audio_buffer_peek(p->buffer, &planes, &samples);
- }
- int max = samples;
- if (samples > space)
- samples = space;
- int flags = 0;
- if (p->final_chunk && samples == max) {
- flags |= AOPLAY_FINAL_CHUNK;
- } else {
- samples = samples / ao->period_size * ao->period_size;
- }
- MP_STATS(ao, "start ao fill");
- ao_post_process_data(ao, (void **)planes, samples);
- int r = 0;
- if (samples)
- r = ao->driver->play(ao, (void **)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) {
- 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)!\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 (!play_silence)
- mp_audio_buffer_skip(p->buffer, r);
- if (r > 0)
- p->expected_end_time = 0;
- // 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;
- // 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);
- 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);
-}
-
-static void *playthread(void *arg)
-{
- struct ao *ao = arg;
- struct ao_push_state *p = ao->api_priv;
- mpthread_set_name("ao");
- pthread_mutex_lock(&p->lock);
- while (!p->terminate) {
- bool blocked = ao->driver->initially_blocked && !p->initial_unblocked;
- bool playing = (!p->paused || ao->stream_silence) && !blocked;
- if (playing)
- 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_audio_buffer_samples(p->buffer))
- {
- 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;
- }
- }
-
- if (was_playing && !p->still_playing)
- ao->wakeup_cb(ao->wakeup_ctx);
- pthread_cond_signal(&p->wakeup); // for draining
-
- 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);
- }
- }
- }
- MP_STATS(ao, "end audio wait");
- }
- p->need_wakeup = false;
- }
- pthread_mutex_unlock(&p->lock);
- return NULL;
-}
-
-static void destroy_no_thread(struct ao *ao)
-{
- struct ao_push_state *p = ao->api_priv;
-
- ao->driver->uninit(ao);
-
- 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);
-}
-
-static void uninit(struct ao *ao)
-{
- struct ao_push_state *p = ao->api_priv;
-
- pthread_mutex_lock(&p->lock);
- p->terminate = true;
- wakeup_playthread(ao);
- pthread_mutex_unlock(&p->lock);
-
- pthread_join(p->thread, NULL);
-
- destroy_no_thread(ao);
-}
-
-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);
- mp_make_wakeup_pipe(p->wakeup_pipe);
-
- if (ao->device_buffer <= 0) {
- MP_FATAL(ao, "Couldn't probe device buffer size.\n");
- goto err;
- }
-
- 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))
- goto err;
- return 0;
-err:
- destroy_no_thread(ao);
- return -1;
-}
-
-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 = audio_pause,
- .resume = resume,
- .drain = drain,
- .get_eof = get_eof,
- .priv_size = sizeof(struct ao_push_state),
-};
-
-// Must be called locked.
-int ao_play_silence(struct ao *ao, int samples)
-{
- assert(ao->api == &ao_api_push);
-
- struct ao_push_state *p = ao->api_priv;
-
- if (!realloc_silence(ao, samples) || !ao->driver->play)
- return 0;
-
- return ao->driver->play(ao, (void **)p->silence, samples, 0);
-}
-
-void ao_unblock(struct ao *ao)
-{
- if (ao->api == &ao_api_push) {
- struct ao_push_state *p = ao->api_priv;
- 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);
- }
-}
-
-#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 ao_push_state *p = ao->api_priv;
- assert(ao->api == &ao_api_push);
- 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->api == &ao_api_push);
- struct ao_push_state *p = ao->api_priv;
-
- (void)write(p->wakeup_pipe[1], &(char){0}, 1);
-}
-
-#endif