mirror of
https://github.com/mpv-player/mpv.git
synced 2024-09-20 03:52:22 +02:00
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.
This commit is contained in:
parent
5ffd6a9e9b
commit
a477481aab
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -19,25 +19,74 @@
|
||||
#ifndef MP_AO_INTERNAL_H_
|
||||
#define MP_AO_INTERNAL_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "audio/chmap.h"
|
||||
#include "audio/chmap_sel.h"
|
||||
|
||||
/* global data used by ao.c and ao drivers */
|
||||
struct ao {
|
||||
int samplerate;
|
||||
struct mp_chmap channels;
|
||||
int format; // one of AF_FORMAT_...
|
||||
int bps; // bytes per second
|
||||
int bps; // bytes per second (per plane)
|
||||
int sstride; // size of a sample on each plane
|
||||
// (format_size*num_channels/num_planes)
|
||||
int num_planes;
|
||||
bool probing; // if true, don't fail loudly on init
|
||||
bool untimed; // don't assume realtime playback
|
||||
bool no_persistent_volume; // the AO does the equivalent of af_volume
|
||||
bool per_application_mixer; // like above, but volume persists (per app)
|
||||
int device_buffer; // device buffer in samples (guessed by
|
||||
// common init code if not set by driver)
|
||||
const struct ao_driver *api; // entrypoints to the wrapper (push.c/pull.c)
|
||||
const struct ao_driver *driver;
|
||||
void *priv;
|
||||
struct encode_lavc_context *encode_lavc_ctx;
|
||||
struct input_ctx *input_ctx;
|
||||
struct mp_log *log; // Using e.g. "[ao/coreaudio]" as prefix
|
||||
|
||||
int buffer;
|
||||
void *api_priv;
|
||||
};
|
||||
|
||||
extern const struct ao_driver ao_api_push;
|
||||
extern const struct ao_driver ao_api_pull;
|
||||
|
||||
|
||||
/* Note:
|
||||
*
|
||||
* In general, there are two types of audio drivers:
|
||||
* a) push based (the user queues data that should be played)
|
||||
* b) pull callback based (the audio API calls a callback to get audio)
|
||||
*
|
||||
* The ao.c code can handle both. It basically implements two audio paths
|
||||
* and provides a uniform API for them. If ao_driver->play is NULL, it assumes
|
||||
* that the driver uses a callback based audio API, otherwise push based.
|
||||
*
|
||||
* Requirements:
|
||||
* a) Most functions (except ->control) must be provided. ->play is called to
|
||||
* queue audio. ao.c creates a thread to regularly refill audio device
|
||||
* buffers with ->play, but all driver functions are always called under
|
||||
* an exclusive lock.
|
||||
* Mandatory:
|
||||
* init
|
||||
* uninit
|
||||
* reset
|
||||
* get_space
|
||||
* play
|
||||
* get_delay
|
||||
* pause
|
||||
* resume
|
||||
* b) ->play must be NULL. The driver can start the audio API in init(). The
|
||||
* audio API in turn will start a thread and call a callback provided by the
|
||||
* driver. That callback calls ao_read_data() to get audio data. Most
|
||||
* functions are optional and will be emulated if missing (e.g. pausing
|
||||
* is emulated as silence). ->get_delay and ->get_space are never called.
|
||||
* Mandatory:
|
||||
* init
|
||||
* uninit
|
||||
*/
|
||||
struct ao_driver {
|
||||
// If true, use with encoding only.
|
||||
bool encode;
|
||||
@ -49,7 +98,7 @@ struct ao_driver {
|
||||
// device doesn't accept these parameters, you can attempt to negotiate
|
||||
// fallback parameters, and set the ao format fields accordingly.
|
||||
int (*init)(struct ao *ao);
|
||||
// See ao_control() etc. in ao.c
|
||||
// Optional. See ao_control() etc. in ao.c
|
||||
int (*control)(struct ao *ao, enum aocontrol cmd, void *arg);
|
||||
void (*uninit)(struct ao *ao, bool cut_audio);
|
||||
void (*reset)(struct ao*ao);
|
||||
@ -65,8 +114,12 @@ struct ao_driver {
|
||||
const struct m_option *options;
|
||||
};
|
||||
|
||||
// These functions can be called by AOs. They don't lock the AO.
|
||||
// These functions can be called by AOs.
|
||||
|
||||
int ao_play_silence(struct ao *ao, int samples);
|
||||
void ao_wait_drain(struct ao *ao);
|
||||
int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_us);
|
||||
|
||||
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
struct mp_chmap *map);
|
||||
bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
|
219
audio/out/pull.c
Normal file
219
audio/out/pull.c
Normal file
@ -0,0 +1,219 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
#include "audio/format.h"
|
||||
|
||||
#include "common/msg.h"
|
||||
#include "common/common.h"
|
||||
|
||||
#include "osdep/timer.h"
|
||||
#include "osdep/threads.h"
|
||||
#include "compat/atomics.h"
|
||||
#include "misc/ring.h"
|
||||
|
||||
enum {
|
||||
AO_STATE_NONE, // idle (e.g. before playback started, or after playback
|
||||
// finished, but device is open)
|
||||
AO_STATE_PLAY, // play the buffer
|
||||
AO_STATE_PAUSE, // pause playback
|
||||
};
|
||||
|
||||
struct ao_pull_state {
|
||||
// Be very careful with the order when accessing planes.
|
||||
struct mp_ring *buffers[MP_NUM_CHANNELS];
|
||||
|
||||
// AO_STATE_*
|
||||
int state;
|
||||
|
||||
// Whether buffers[] can be accessed.
|
||||
int ready;
|
||||
|
||||
// Device delay of the last written sample, in realtime.
|
||||
int64_t end_time_us;
|
||||
};
|
||||
|
||||
static int get_space(struct ao *ao)
|
||||
{
|
||||
struct ao_pull_state *p = ao->api_priv;
|
||||
// Since the reader will read the last plane last, its free space is the
|
||||
// minimum free space across all planes.
|
||||
return mp_ring_available(p->buffers[ao->num_planes - 1]) / ao->sstride;
|
||||
}
|
||||
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct ao_pull_state *p = ao->api_priv;
|
||||
|
||||
int write_samples = get_space(ao);
|
||||
write_samples = MPMIN(write_samples, samples);
|
||||
|
||||
// Write starting from the last plane - this way, the first plane will
|
||||
// always contain the minimum amount of data readable across all planes
|
||||
// (assumes the reader starts with the first plane).
|
||||
int write_bytes = write_samples * ao->sstride;
|
||||
for (int n = ao->num_planes - 1; n >= 0; n--) {
|
||||
int r = mp_ring_write(p->buffers[n], data[n], write_bytes);
|
||||
assert(r == write_bytes);
|
||||
}
|
||||
if (p->state != AO_STATE_PLAY) {
|
||||
p->end_time_us = 0;
|
||||
p->state = AO_STATE_PLAY;
|
||||
mp_memory_barrier();
|
||||
if (ao->driver->resume)
|
||||
ao->driver->resume(ao);
|
||||
}
|
||||
|
||||
return write_samples;
|
||||
}
|
||||
|
||||
// Read the given amount of samples in the user-provided data buffer. Returns
|
||||
// the number of samples copied. If there is not enough data (buffer underrun
|
||||
// or EOF), return the number of samples that could be copied, and fill the
|
||||
// rest of the user-provided buffer with silence.
|
||||
// This basically assumes that the audio device doesn't care about underruns.
|
||||
// If this is called in paused mode, it will always return 0.
|
||||
// The caller should set out_time_us to the expected delay the last sample
|
||||
// reaches the speakers, in microseconds, using mp_time_us() as reference.
|
||||
int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_us)
|
||||
{
|
||||
assert(ao->api == &ao_api_pull);
|
||||
|
||||
struct ao_pull_state *p = ao->api_priv;
|
||||
int full_bytes = samples * ao->sstride;
|
||||
|
||||
mp_memory_barrier();
|
||||
if (!p->ready) {
|
||||
for (int n = 0; n < ao->num_planes; n++)
|
||||
af_fill_silence(data[n], full_bytes, ao->format);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Since the writer will write the first plane last, its buffered amount
|
||||
// of data is the minimum amount across all planes.
|
||||
int bytes = mp_ring_buffered(p->buffers[0]);
|
||||
bytes = MPMIN(bytes, full_bytes);
|
||||
|
||||
if (bytes > 0)
|
||||
p->end_time_us = out_time_us;
|
||||
|
||||
mp_memory_barrier();
|
||||
if (p->state == AO_STATE_PAUSE)
|
||||
bytes = 0;
|
||||
|
||||
for (int n = 0; n < ao->num_planes; n++) {
|
||||
int r = mp_ring_read(p->buffers[n], data[n], bytes);
|
||||
assert(r == bytes);
|
||||
// pad with silence (underflow/paused/eof)
|
||||
int silence = full_bytes - bytes;
|
||||
if (silence)
|
||||
af_fill_silence((char *)data[n] + bytes, silence, ao->format);
|
||||
}
|
||||
return bytes / ao->sstride;
|
||||
}
|
||||
|
||||
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
if (ao->driver->control)
|
||||
return ao->driver->control(ao, cmd, arg);
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
// Return size of the buffered data in seconds. Can include the device latency.
|
||||
// Basically, this returns how much data there is still to play, and how long
|
||||
// it takes until the last sample in the buffer reaches the speakers. This is
|
||||
// used for audio/video synchronization, so it's very important to implement
|
||||
// this correctly.
|
||||
static float get_delay(struct ao *ao)
|
||||
{
|
||||
struct ao_pull_state *p = ao->api_priv;
|
||||
|
||||
mp_memory_barrier();
|
||||
int64_t end = p->end_time_us;
|
||||
int64_t now = mp_time_us();
|
||||
double driver_delay = MPMAX(0, (end - now) / (1000.0 * 1000.0));
|
||||
return mp_ring_buffered(p->buffers[0]) / (double)ao->bps + driver_delay;
|
||||
}
|
||||
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
struct ao_pull_state *p = ao->api_priv;
|
||||
if (ao->driver->reset)
|
||||
ao->driver->reset(ao);
|
||||
// Not like this is race-condition free. It will work if ->reset
|
||||
// stops the audio callback, though.
|
||||
p->ready = 0;
|
||||
p->state = AO_STATE_NONE;
|
||||
mp_memory_barrier();
|
||||
for (int n = 0; n < ao->num_planes; n++)
|
||||
mp_ring_reset(p->buffers[n]);
|
||||
p->end_time_us = 0;
|
||||
mp_memory_barrier();
|
||||
p->ready = 1;
|
||||
mp_memory_barrier();
|
||||
}
|
||||
|
||||
static void pause(struct ao *ao)
|
||||
{
|
||||
struct ao_pull_state *p = ao->api_priv;
|
||||
if (ao->driver->pause)
|
||||
ao->driver->pause(ao);
|
||||
p->state = AO_STATE_PAUSE;
|
||||
mp_memory_barrier();
|
||||
}
|
||||
|
||||
static void resume(struct ao *ao)
|
||||
{
|
||||
struct ao_pull_state *p = ao->api_priv;
|
||||
p->state = AO_STATE_PLAY;
|
||||
mp_memory_barrier();
|
||||
if (ao->driver->resume)
|
||||
ao->driver->resume(ao);
|
||||
}
|
||||
|
||||
static void uninit(struct ao *ao, bool cut_audio)
|
||||
{
|
||||
ao->driver->uninit(ao, cut_audio);
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
struct ao_pull_state *p = ao->api_priv;
|
||||
for (int n = 0; n < ao->num_planes; n++)
|
||||
p->buffers[n] = mp_ring_new(ao, ao->buffer * ao->sstride);
|
||||
p->ready = 1;
|
||||
mp_memory_barrier();
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct ao_driver ao_api_pull = {
|
||||
.init = init,
|
||||
.control = control,
|
||||
.uninit = uninit,
|
||||
.reset = reset,
|
||||
.get_space = get_space,
|
||||
.play = play,
|
||||
.get_delay = get_delay,
|
||||
.pause = pause,
|
||||
.resume = resume,
|
||||
.priv_size = sizeof(struct ao_pull_state),
|
||||
};
|
266
audio/out/push.c
Normal file
266
audio/out/push.c
Normal file
@ -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;
|
||||
}
|
@ -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 \
|
||||
|
@ -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" ),
|
||||
|
Loading…
Reference in New Issue
Block a user