summaryrefslogtreecommitdiffstats
path: root/audio/out/push.c
diff options
context:
space:
mode:
Diffstat (limited to 'audio/out/push.c')
-rw-r--r--audio/out/push.c266
1 files changed, 266 insertions, 0 deletions
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;
+}