diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:36:56 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:36:56 +0000 |
commit | 51de1d8436100f725f3576aefa24a2bd2057bc28 (patch) | |
tree | c6d1d5264b6d40a8d7ca34129f36b7d61e188af3 /audio/out/buffer.c | |
parent | Initial commit. (diff) | |
download | mpv-51de1d8436100f725f3576aefa24a2bd2057bc28.tar.xz mpv-51de1d8436100f725f3576aefa24a2bd2057bc28.zip |
Adding upstream version 0.37.0.upstream/0.37.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'audio/out/buffer.c')
-rw-r--r-- | audio/out/buffer.c | 736 |
1 files changed, 736 insertions, 0 deletions
diff --git a/audio/out/buffer.c b/audio/out/buffer.c new file mode 100644 index 0000000..5b8b523 --- /dev/null +++ b/audio/out/buffer.c @@ -0,0 +1,736 @@ +/* + * 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 <math.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 "filters/f_async_queue.h" +#include "filters/filter_internal.h" + +#include "osdep/timer.h" +#include "osdep/threads.h" + +struct buffer_state { + // Buffer and AO + mp_mutex lock; + mp_cond wakeup; + + // Playthread sleep + mp_mutex pt_lock; + mp_cond pt_wakeup; + + // Access from AO driver's thread only. + char *convert_buffer; + + // Immutable. + struct mp_async_queue *queue; + + // --- protected by lock + + struct mp_filter *filter_root; + struct mp_filter *input; // connected to queue + struct mp_aframe *pending; // last, not fully consumed output + + bool streaming; // AO streaming active + bool playing; // logically playing audio from buffer + bool paused; // logically paused + + int64_t end_time_ns; // absolute output time of last played sample + + bool initial_unblocked; + + // "Push" AOs only (AOs with driver->write). + bool hw_paused; // driver->set_pause() was used successfully + bool recover_pause; // non-hw_paused: needs to recover delay + struct mp_pcm_state prepause_state; + mp_thread thread; // thread shoveling data to AO + bool thread_valid; // thread is running + struct mp_aframe *temp_buf; + + // --- protected by pt_lock + bool need_wakeup; + bool terminate; // exit thread +}; + +static MP_THREAD_VOID playthread(void *arg); + +void ao_wakeup_playthread(struct ao *ao) +{ + struct buffer_state *p = ao->buffer_state; + mp_mutex_lock(&p->pt_lock); + p->need_wakeup = true; + mp_cond_broadcast(&p->pt_wakeup); + mp_mutex_unlock(&p->pt_lock); +} + +// called locked +static void get_dev_state(struct ao *ao, struct mp_pcm_state *state) +{ + struct buffer_state *p = ao->buffer_state; + + if (p->paused && p->playing && !ao->stream_silence) { + *state = p->prepause_state; + return; + } + + *state = (struct mp_pcm_state){ + .free_samples = -1, + .queued_samples = -1, + .delay = -1, + }; + ao->driver->get_state(ao, state); +} + +struct mp_async_queue *ao_get_queue(struct ao *ao) +{ + struct buffer_state *p = ao->buffer_state; + return p->queue; +} + +// Special behavior with data==NULL: caller uses p->pending. +static int read_buffer(struct ao *ao, void **data, int samples, bool *eof, + bool pad_silence) +{ + struct buffer_state *p = ao->buffer_state; + int pos = 0; + *eof = false; + + while (p->playing && !p->paused && pos < samples) { + if (!p->pending || !mp_aframe_get_size(p->pending)) { + TA_FREEP(&p->pending); + struct mp_frame frame = mp_pin_out_read(p->input->pins[0]); + if (!frame.type) + break; // we can't/don't want to block + if (frame.type != MP_FRAME_AUDIO) { + if (frame.type == MP_FRAME_EOF) + *eof = true; + mp_frame_unref(&frame); + continue; + } + p->pending = frame.data; + } + + if (!data) + break; + + int copy = mp_aframe_get_size(p->pending); + uint8_t **fdata = mp_aframe_get_data_ro(p->pending); + copy = MPMIN(copy, samples - pos); + for (int n = 0; n < ao->num_planes; n++) { + memcpy((char *)data[n] + pos * ao->sstride, + fdata[n], copy * ao->sstride); + } + mp_aframe_skip_samples(p->pending, copy); + pos += copy; + *eof = false; + } + + if (!data) { + if (!p->pending) + return 0; + void **pd = (void *)mp_aframe_get_data_rw(p->pending); + if (pd) + ao_post_process_data(ao, pd, mp_aframe_get_size(p->pending)); + return 1; + } + + // pad with silence (underflow/paused/eof) + if (pad_silence) { + for (int n = 0; n < ao->num_planes; n++) { + af_fill_silence((char *)data[n] + pos * ao->sstride, + (samples - pos) * ao->sstride, + ao->format); + } + } + + ao_post_process_data(ao, data, pos); + return pos; +} + +static int ao_read_data_unlocked(struct ao *ao, void **data, int samples, + int64_t out_time_ns, bool pad_silence) +{ + struct buffer_state *p = ao->buffer_state; + assert(!ao->driver->write); + + int pos = read_buffer(ao, data, samples, &(bool){0}, pad_silence); + + if (pos > 0) + p->end_time_ns = out_time_ns; + + if (pos < samples && p->playing && !p->paused) { + p->playing = false; + ao->wakeup_cb(ao->wakeup_ctx); + // For ao_drain(). + mp_cond_broadcast(&p->wakeup); + } + + return pos; +} + +// Read the given amount of samples in the user-provided data buffer. Returns +// the number of samples copied. If there is not enough data (buffer underrun +// or EOF), return the number of samples that could be copied, and fill the +// rest of the user-provided buffer with silence. +// This basically assumes that the audio device doesn't care about underruns. +// If this is called in paused mode, it will always return 0. +// The caller should set out_time_ns to the expected delay until the last sample +// reaches the speakers, in nanoseconds, using mp_time_ns() as reference. +int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_ns) +{ + struct buffer_state *p = ao->buffer_state; + + mp_mutex_lock(&p->lock); + + int pos = ao_read_data_unlocked(ao, data, samples, out_time_ns, true); + + mp_mutex_unlock(&p->lock); + + return pos; +} + +// Like ao_read_data() but does not block and also may return partial data. +// Callers have to check the return value. +int ao_read_data_nonblocking(struct ao *ao, void **data, int samples, int64_t out_time_ns) +{ + struct buffer_state *p = ao->buffer_state; + + if (mp_mutex_trylock(&p->lock)) + return 0; + + int pos = ao_read_data_unlocked(ao, data, samples, out_time_ns, false); + + mp_mutex_unlock(&p->lock); + + return pos; +} + +// 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_ns) +{ + struct buffer_state *p = ao->buffer_state; + void *ndata[MP_NUM_CHANNELS] = {0}; + + if (!ao_need_conversion(fmt)) + return ao_read_data(ao, data, samples, out_time_ns); + + 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_ns); + + 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) { + // Only need to lock in push mode. + if (ao->driver->write) + mp_mutex_lock(&p->lock); + + r = ao->driver->control(ao, cmd, arg); + + if (ao->driver->write) + mp_mutex_unlock(&p->lock); + } + return r; +} + +double ao_get_delay(struct ao *ao) +{ + struct buffer_state *p = ao->buffer_state; + + mp_mutex_lock(&p->lock); + + double driver_delay; + if (ao->driver->write) { + struct mp_pcm_state state; + get_dev_state(ao, &state); + driver_delay = state.delay; + } else { + int64_t end = p->end_time_ns; + int64_t now = mp_time_ns(); + driver_delay = MPMAX(0, MP_TIME_NS_TO_S(end - now)); + } + + int pending = mp_async_queue_get_samples(p->queue); + if (p->pending) + pending += mp_aframe_get_size(p->pending); + + mp_mutex_unlock(&p->lock); + return driver_delay + pending / (double)ao->samplerate; +} + +// Fully stop playback; clear buffers, including queue. +void ao_reset(struct ao *ao) +{ + struct buffer_state *p = ao->buffer_state; + bool wakeup = false; + bool do_reset = false; + + mp_mutex_lock(&p->lock); + + TA_FREEP(&p->pending); + mp_async_queue_reset(p->queue); + mp_filter_reset(p->filter_root); + mp_async_queue_resume_reading(p->queue); + + if (!ao->stream_silence && ao->driver->reset) { + if (ao->driver->write) { + ao->driver->reset(ao); + } else { + // Pull AOs may wait for ao_read_data() to return. + // That would deadlock if called from within the lock. + do_reset = true; + } + p->streaming = false; + } + wakeup = p->playing; + p->playing = false; + p->recover_pause = false; + p->hw_paused = false; + p->end_time_ns = 0; + + mp_mutex_unlock(&p->lock); + + if (do_reset) + ao->driver->reset(ao); + + if (wakeup) + ao_wakeup_playthread(ao); +} + +// Initiate playback. This moves from the stop/underrun state to actually +// playing (orthogonally taking the paused state into account). Plays all +// data in the queue, and goes into underrun state if no more data available. +// No-op if already running. +void ao_start(struct ao *ao) +{ + struct buffer_state *p = ao->buffer_state; + bool do_start = false; + + mp_mutex_lock(&p->lock); + + p->playing = true; + + if (!ao->driver->write && !p->paused && !p->streaming) { + p->streaming = true; + do_start = true; + } + + mp_mutex_unlock(&p->lock); + + // Pull AOs might call ao_read_data() so do this outside the lock. + if (do_start) + ao->driver->start(ao); + + ao_wakeup_playthread(ao); +} + +void ao_set_paused(struct ao *ao, bool paused, bool eof) +{ + struct buffer_state *p = ao->buffer_state; + bool wakeup = false; + bool do_reset = false, do_start = false; + + // If we are going to pause on eof and ao is still playing, + // be sure to drain the ao first for gapless. + if (eof && paused && ao_is_playing(ao)) + ao_drain(ao); + + mp_mutex_lock(&p->lock); + + if ((p->playing || !ao->driver->write) && !p->paused && paused) { + if (p->streaming && !ao->stream_silence) { + if (ao->driver->write) { + if (!p->recover_pause) + get_dev_state(ao, &p->prepause_state); + if (ao->driver->set_pause && ao->driver->set_pause(ao, true)) { + p->hw_paused = true; + } else { + ao->driver->reset(ao); + p->streaming = false; + p->recover_pause = !ao->untimed; + } + } else if (ao->driver->reset) { + // See ao_reset() why this is done outside of the lock. + do_reset = true; + p->streaming = false; + } + } + wakeup = true; + } else if (p->playing && p->paused && !paused) { + if (ao->driver->write) { + if (p->hw_paused) + ao->driver->set_pause(ao, false); + p->hw_paused = false; + } else { + if (!p->streaming) + do_start = true; + p->streaming = true; + } + wakeup = true; + } + p->paused = paused; + + mp_mutex_unlock(&p->lock); + + if (do_reset) + ao->driver->reset(ao); + if (do_start) + ao->driver->start(ao); + + if (wakeup) + ao_wakeup_playthread(ao); +} + +// Whether audio is playing. This means that there is still data in the buffers, +// and ao_start() was called. This returns true even if playback was logically +// paused. On false, EOF was reached, or an underrun happened, or ao_reset() +// was called. +bool ao_is_playing(struct ao *ao) +{ + struct buffer_state *p = ao->buffer_state; + + mp_mutex_lock(&p->lock); + bool playing = p->playing; + mp_mutex_unlock(&p->lock); + + return playing; +} + +// Block until the current audio buffer has played completely. +void ao_drain(struct ao *ao) +{ + struct buffer_state *p = ao->buffer_state; + + mp_mutex_lock(&p->lock); + while (!p->paused && p->playing) { + mp_mutex_unlock(&p->lock); + double delay = ao_get_delay(ao); + mp_mutex_lock(&p->lock); + + // Limit to buffer + arbitrary ~250ms max. waiting for robustness. + delay += mp_async_queue_get_samples(p->queue) / (double)ao->samplerate; + + // Wait for EOF signal from AO. + if (mp_cond_timedwait(&p->wakeup, &p->lock, + MP_TIME_S_TO_NS(MPMAX(delay, 0) + 0.25))) + { + MP_VERBOSE(ao, "drain timeout\n"); + break; + } + + if (!p->playing && mp_async_queue_get_samples(p->queue)) { + MP_WARN(ao, "underrun during draining\n"); + mp_mutex_unlock(&p->lock); + ao_start(ao); + mp_mutex_lock(&p->lock); + } + } + mp_mutex_unlock(&p->lock); + + ao_reset(ao); +} + +static void wakeup_filters(void *ctx) +{ + struct ao *ao = ctx; + ao_wakeup_playthread(ao); +} + +void ao_uninit(struct ao *ao) +{ + struct buffer_state *p = ao->buffer_state; + + if (p && p->thread_valid) { + mp_mutex_lock(&p->pt_lock); + p->terminate = true; + mp_cond_broadcast(&p->pt_wakeup); + mp_mutex_unlock(&p->pt_lock); + + mp_thread_join(p->thread); + p->thread_valid = false; + } + + if (ao->driver_initialized) + ao->driver->uninit(ao); + + if (p) { + talloc_free(p->filter_root); + talloc_free(p->queue); + talloc_free(p->pending); + talloc_free(p->convert_buffer); + talloc_free(p->temp_buf); + + mp_cond_destroy(&p->wakeup); + mp_mutex_destroy(&p->lock); + + mp_cond_destroy(&p->pt_wakeup); + mp_mutex_destroy(&p->pt_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; + + assert(ao->driver->start); + if (ao->driver->write) { + assert(ao->driver->reset); + assert(ao->driver->get_state); + } + + mp_mutex_init(&p->lock); + mp_cond_init(&p->wakeup); + + mp_mutex_init(&p->pt_lock); + mp_cond_init(&p->pt_wakeup); + + p->queue = mp_async_queue_create(); + p->filter_root = mp_filter_create_root(ao->global); + p->input = mp_async_queue_create_filter(p->filter_root, MP_PIN_OUT, p->queue); + + mp_async_queue_resume_reading(p->queue); + + struct mp_async_queue_config cfg = { + .sample_unit = AQUEUE_UNIT_SAMPLES, + .max_samples = ao->buffer, + .max_bytes = INT64_MAX, + }; + mp_async_queue_set_config(p->queue, cfg); + + if (ao->driver->write) { + mp_filter_graph_set_wakeup_cb(p->filter_root, wakeup_filters, ao); + + p->thread_valid = true; + if (mp_thread_create(&p->thread, playthread, ao)) { + p->thread_valid = false; + return false; + } + } else { + if (ao->stream_silence) { + ao->driver->start(ao); + p->streaming = true; + } + } + + if (ao->stream_silence) { + MP_WARN(ao, "The --audio-stream-silence option is set. This will break " + "certain player behavior.\n"); + } + + 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 bool ao_play_data(struct ao *ao) +{ + struct buffer_state *p = ao->buffer_state; + + if ((!p->playing || p->paused) && !ao->stream_silence) + return false; + + struct mp_pcm_state state; + get_dev_state(ao, &state); + + if (p->streaming && !state.playing && !ao->untimed) + goto eof; + + void **planes = NULL; + int space = state.free_samples; + if (!space) + return false; + assert(space >= 0); + + int samples = 0; + bool got_eof = false; + if (ao->driver->write_frames) { + TA_FREEP(&p->pending); + samples = read_buffer(ao, NULL, 1, &got_eof, false); + planes = (void **)&p->pending; + } else { + if (!realloc_buf(ao, space)) { + MP_ERR(ao, "Failed to allocate buffer.\n"); + return false; + } + planes = (void **)mp_aframe_get_data_rw(p->temp_buf); + assert(planes); + + if (p->recover_pause) { + samples = MPCLAMP(p->prepause_state.delay * ao->samplerate, 0, space); + p->recover_pause = false; + mp_aframe_set_silence(p->temp_buf, 0, space); + } + + if (!samples) { + samples = read_buffer(ao, planes, space, &got_eof, true); + if (p->paused || (ao->stream_silence && !p->playing)) + samples = space; // read_buffer() sets remainder to silent + } + } + + if (samples) { + MP_STATS(ao, "start ao fill"); + if (!ao->driver->write(ao, planes, samples)) + MP_ERR(ao, "Error writing audio to device.\n"); + MP_STATS(ao, "end ao fill"); + + if (!p->streaming) { + MP_VERBOSE(ao, "starting AO\n"); + ao->driver->start(ao); + p->streaming = true; + state.playing = true; + } + } + + MP_TRACE(ao, "in=%d space=%d(%d) pl=%d, eof=%d\n", + samples, space, state.free_samples, p->playing, got_eof); + + if (got_eof) + goto eof; + + return samples > 0 && (samples < space || ao->untimed); + +eof: + MP_VERBOSE(ao, "audio end or underrun\n"); + // Normal AOs signal EOF on underrun, untimed AOs never signal underruns. + if (ao->untimed || !state.playing || ao->stream_silence) { + p->streaming = state.playing && !ao->untimed; + p->playing = false; + } + ao->wakeup_cb(ao->wakeup_ctx); + // For ao_drain(). + mp_cond_broadcast(&p->wakeup); + return true; +} + +static MP_THREAD_VOID playthread(void *arg) +{ + struct ao *ao = arg; + struct buffer_state *p = ao->buffer_state; + mp_thread_set_name("ao"); + while (1) { + mp_mutex_lock(&p->lock); + + bool retry = false; + if (!ao->driver->initially_blocked || p->initial_unblocked) + retry = ao_play_data(ao); + + // Wait until the device wants us to write more data to it. + // Fallback to guessing. + int64_t timeout = INT64_MAX; + if (p->streaming && !retry && (!p->paused || ao->stream_silence)) { + // Wake up again if half of the audio buffer has been played. + // Since audio could play at a faster or slower pace, wake up twice + // as often as ideally needed. + timeout = MP_TIME_S_TO_NS(ao->device_buffer / (double)ao->samplerate * 0.25); + } + + mp_mutex_unlock(&p->lock); + + mp_mutex_lock(&p->pt_lock); + if (p->terminate) { + mp_mutex_unlock(&p->pt_lock); + break; + } + if (!p->need_wakeup && !retry) { + MP_STATS(ao, "start audio wait"); + mp_cond_timedwait(&p->pt_wakeup, &p->pt_lock, timeout); + MP_STATS(ao, "end audio wait"); + } + p->need_wakeup = false; + mp_mutex_unlock(&p->pt_lock); + } + MP_THREAD_RETURN(); +} + +void ao_unblock(struct ao *ao) +{ + if (ao->driver->write) { + struct buffer_state *p = ao->buffer_state; + mp_mutex_lock(&p->lock); + p->initial_unblocked = true; + mp_mutex_unlock(&p->lock); + ao_wakeup_playthread(ao); + } +} |