From a477481aabaa16f1ed15af456125160bc8face5a Mon Sep 17 00:00:00 2001 From: wm4 Date: Sun, 9 Mar 2014 00:04:37 +0100 Subject: 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. --- audio/mixer.c | 6 +- audio/out/ao.c | 70 +++++++++----- audio/out/internal.h | 59 +++++++++++- audio/out/pull.c | 219 ++++++++++++++++++++++++++++++++++++++++++ audio/out/push.c | 266 +++++++++++++++++++++++++++++++++++++++++++++++++++ old-makefile | 2 + wscript_build.py | 2 + 7 files changed, 593 insertions(+), 31 deletions(-) create mode 100644 audio/out/pull.c create mode 100644 audio/out/push.c 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 + +#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 . + */ + +#include +#include +#include + +#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 . + */ + +#include +#include +#include +#include +#include + +#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" ), -- cgit v1.2.3