audio/out: make draining a separate operation

Until now, this was always conflated with uninit. This was ugly, and
also many AOs emulated this manually (or just ignored it). Make draining
an explicit operation, so AOs which support it can provide it, and for
all others generic code will emulate it.

For ao_wasapi, we keep it simple and basically disable the internal
draining implementation (maybe it should be restored later).

Tested on Linux only.
This commit is contained in:
wm4 2014-03-09 00:49:39 +01:00
parent 2f03dc2599
commit e16c91d07a
21 changed files with 116 additions and 82 deletions

View File

@ -235,11 +235,10 @@ done:
return ao;
}
// Uninitialize and destroy the AO.
// cut_audio: if false, block until all remaining audio was played.
void ao_uninit(struct ao *ao, bool cut_audio)
// Uninitialize and destroy the AO. Remaining audio must be dropped.
void ao_uninit(struct ao *ao)
{
ao->api->uninit(ao, cut_audio);
ao->api->uninit(ao);
talloc_free(ao);
}
@ -315,13 +314,22 @@ void ao_resume(struct ao *ao)
ao->api->resume(ao);
}
// 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).
// Be careful with locking
void ao_wait_drain(struct ao *ao)
{
// This is probably not entirely accurate, but good enough.
mp_sleep_us(ao_get_delay(ao) * 1000000);
ao_reset(ao);
}
// Block until the current audio buffer has played completely.
void ao_drain(struct ao *ao)
{
if (ao->api->drain) {
ao->api->drain(ao);
} else {
ao_wait_drain(ao);
}
}
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,

View File

@ -59,7 +59,7 @@ struct ao *ao_init_best(struct mpv_global *global,
struct input_ctx *input_ctx,
struct encode_lavc_context *encode_lavc_ctx,
int samplerate, int format, struct mp_chmap channels);
void ao_uninit(struct ao *ao, bool cut_audio);
void ao_uninit(struct ao *ao);
void ao_get_format(struct ao *ao, struct mp_audio *format);
const char *ao_get_name(struct ao *ao);
const char *ao_get_description(struct ao *ao);
@ -71,5 +71,6 @@ int ao_get_space(struct ao *ao);
void ao_reset(struct ao *ao);
void ao_pause(struct ao *ao);
void ao_resume(struct ao *ao);
void ao_drain(struct ao *ao);
#endif /* MPLAYER_AUDIO_OUT_H */

View File

@ -79,7 +79,7 @@ struct priv {
} while (0)
static float get_delay(struct ao *ao);
static void uninit(struct ao *ao, bool immed);
static void uninit(struct ao *ao);
/* to set/get/query special features/parameters */
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
@ -530,22 +530,19 @@ static int init(struct ao *ao)
return 0;
alsa_error:
uninit(ao, true);
uninit(ao);
return -1;
} // end init
/* close audio device */
static void uninit(struct ao *ao, bool immed)
static void uninit(struct ao *ao)
{
struct priv *p = ao->priv;
if (p->alsa) {
int err;
if (!immed)
snd_pcm_drain(p->alsa);
err = snd_pcm_close(p->alsa);
CHECK_ALSA_ERROR("pcm close error");
@ -556,6 +553,12 @@ alsa_error:
p->alsa = NULL;
}
static void drain(struct ao *ao)
{
struct priv *p = ao->priv;
snd_pcm_drain(p->alsa);
}
static void audio_pause(struct ao *ao)
{
struct priv *p = ao->priv;
@ -712,6 +715,7 @@ const struct ao_driver audio_out_alsa = {
.pause = audio_pause,
.resume = audio_resume,
.reset = reset,
.drain = drain,
.priv_size = sizeof(struct priv),
.priv_defaults = &(const struct priv) {
.cfg_block = 1,

View File

@ -626,14 +626,11 @@ static float get_delay(struct ao *ao)
return mp_ring_buffered(p->buffer) / (float)ao->bps;
}
static void uninit(struct ao *ao, bool immed)
static void uninit(struct ao *ao)
{
struct priv *p = ao->priv;
OSStatus err = noErr;
if (!immed)
mp_sleep_us(get_delay(ao) * 1000000);
if (!p->is_digital) {
AudioOutputUnitStop(p->audio_unit);
AudioUnitUninitialize(p->audio_unit);

View File

@ -546,10 +546,8 @@ static void audio_resume(struct ao *ao)
\brief close audio device
\param immed stop playback immediately
*/
static void uninit(struct ao *ao, bool immed)
static void uninit(struct ao *ao)
{
if (!immed)
mp_sleep_us(get_delay(ao) * 1000000);
reset(ao);
DestroyBuffer(ao);

View File

@ -207,13 +207,10 @@ err_chmap:
}
// close audio device
static void uninit(struct ao *ao, bool immed)
static void uninit(struct ao *ao)
{
struct priv *p = ao->priv;
if (!immed)
ao_wait_drain(ao);
jack_client_close(p->client);
}

View File

@ -181,7 +181,7 @@ fail:
// close audio device
static int encode(struct ao *ao, double apts, void **data);
static void uninit(struct ao *ao, bool cut_audio)
static void uninit(struct ao *ao)
{
struct priv *ac = ao->priv;
struct encode_lavc_context *ectx = ao->encode_lavc_ctx;
@ -462,6 +462,11 @@ static int play(struct ao *ao, void **data, int samples, int flags)
return bufpos;
}
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",
@ -470,4 +475,5 @@ const struct ao_driver audio_out_lavc = {
.uninit = uninit,
.get_space = get_space,
.play = play,
.drain = drain,
};

View File

@ -98,10 +98,15 @@ static int init(struct ao *ao)
}
// close audio device
static void uninit(struct ao *ao, bool cut_audio)
static void uninit(struct ao *ao)
{
}
static void wait_drain(struct ao *ao)
{
struct priv *priv = ao->priv;
if (!cut_audio && !priv->paused)
drain(ao);
if (!priv->paused)
mp_sleep_us(1000000.0 * priv->buffered / ao->samplerate / priv->speed);
}
@ -185,6 +190,7 @@ 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,

View File

@ -178,18 +178,10 @@ err_out:
}
// close audio device
static void uninit(struct ao *ao, bool immed)
static void uninit(struct ao *ao)
{
ALCcontext *ctx = alcGetCurrentContext();
ALCdevice *dev = alcGetContextsDevice(ctx);
if (!immed) {
ALint state;
alGetSourcei(sources[0], AL_SOURCE_STATE, &state);
while (state == AL_PLAYING) {
mp_sleep_us(10000);
alGetSourcei(sources[0], AL_SOURCE_STATE, &state);
}
}
reset(ao);
alcMakeContextCurrent(NULL);
alcDestroyContext(ctx);
@ -197,6 +189,16 @@ static void uninit(struct ao *ao, bool immed)
ao_data = NULL;
}
static void drain(struct ao *ao)
{
ALint state;
alGetSourcei(sources[0], AL_SOURCE_STATE, &state);
while (state == AL_PLAYING) {
mp_sleep_us(10000);
alGetSourcei(sources[0], AL_SOURCE_STATE, &state);
}
}
static void unqueue_buffers(void)
{
ALint p;
@ -298,6 +300,7 @@ const struct ao_driver audio_out_openal = {
.pause = audio_pause,
.resume = audio_resume,
.reset = reset,
.drain = drain,
.priv_size = sizeof(struct priv),
.options = (const struct m_option[]) {
OPT_STRING_VALIDATE("device", cfg_device, 0, validate_device_opt),

View File

@ -427,24 +427,28 @@ ac3_retry:
}
// close audio device
static void uninit(struct ao *ao, bool immed)
static void uninit(struct ao *ao)
{
struct priv *p = ao->priv;
if (p->audio_fd == -1)
return;
#ifdef SNDCTL_DSP_SYNC
// to get the buffer played
if (!immed)
ioctl(p->audio_fd, SNDCTL_DSP_SYNC, NULL);
#endif
#ifdef SNDCTL_DSP_RESET
if (immed)
ioctl(p->audio_fd, SNDCTL_DSP_RESET, NULL);
ioctl(p->audio_fd, SNDCTL_DSP_RESET, NULL);
#endif
close(p->audio_fd);
p->audio_fd = -1;
}
static void drain(struct ao *ao)
{
#ifdef SNDCTL_DSP_SYNC
struct priv *p = ao->priv;
// to get the buffer played
if (p->audio_fd != -1)
ioctl(p->audio_fd, SNDCTL_DSP_SYNC, NULL);
#endif
}
#ifndef SNDCTL_DSP_RESET
static void close_device(struct ao *ao)
{
@ -601,6 +605,7 @@ const struct ao_driver audio_out_oss = {
.pause = audio_pause,
.resume = audio_resume,
.reset = reset,
.drain = drain,
.priv_size = sizeof(struct priv),
.priv_defaults = &(const struct priv) {
.audio_fd = -1,

View File

@ -166,7 +166,7 @@ static int init(struct ao *ao)
}
// close audio device
static void uninit(struct ao *ao, bool cut_audio)
static void uninit(struct ao *ao)
{
struct priv *priv = ao->priv;

View File

@ -146,16 +146,13 @@ static int stream_callback(const void *input,
return paContinue;
}
static void uninit(struct ao *ao, bool cut_audio)
static void uninit(struct ao *ao)
{
struct priv *priv = ao->priv;
if (priv->stream) {
if (!cut_audio && Pa_IsStreamActive(priv->stream) == 1) {
ao_wait_drain(ao);
if (Pa_IsStreamActive(priv->stream) == 1)
CHECK_PA_RET(Pa_StopStream(priv->stream));
}
CHECK_PA_RET(Pa_CloseStream(priv->stream));
}
@ -220,7 +217,7 @@ static int init(struct ao *ao)
return 0;
error_exit:
uninit(ao, true);
uninit(ao);
return -1;
}

View File

@ -205,13 +205,18 @@ static bool select_chmap(struct ao *ao, pa_channel_map *dst)
chmap_pa_from_mp(dst, &ao->channels);
}
static void uninit(struct ao *ao, bool cut_audio)
static void drain(struct ao *ao)
{
struct priv *priv = ao->priv;
if (priv->stream && !cut_audio) {
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;
if (priv->mainloop)
pa_threaded_mainloop_stop(priv->mainloop);
@ -366,7 +371,7 @@ fail:
if (proplist)
pa_proplist_free(proplist);
uninit(ao, true);
uninit(ao);
return -1;
}
@ -620,6 +625,7 @@ const struct ao_driver audio_out_pulse = {
.get_delay = get_delay,
.pause = pause,
.resume = resume,
.drain = drain,
.priv_size = sizeof(struct priv),
.priv_defaults = &(const struct priv) {
.cfg_buffer = 250,

View File

@ -128,15 +128,9 @@ static int init(struct ao *ao)
return 0;
}
static void uninit(struct ao *ao, bool cut_audio)
static void uninit(struct ao *ao)
{
struct priv *priv = ao->priv;
/* The API does not provide a direct way to explicitly wait until
* the last byte has been played server-side as this cannot be
* guaranteed by backend drivers, so we approximate this behavior.
*/
if (!cut_audio)
mp_sleep_us(rsd_delay_ms(priv->rd) * 1000);
rsd_stop(priv->rd);
rsd_free(priv->rd);

View File

@ -78,7 +78,7 @@ static void audio_callback(void *userdata, Uint8 *stream, int len)
SDL_UnlockMutex(priv->buffer_mutex);
}
static void uninit(struct ao *ao, bool cut_audio)
static void uninit(struct ao *ao)
{
struct priv *priv = ao->priv;
if (!priv)
@ -134,14 +134,14 @@ static int init(struct ao *ao)
if (SDL_InitSubSystem(SDL_INIT_AUDIO)) {
if (!ao->probing)
MP_ERR(ao, "SDL_Init failed\n");
uninit(ao, true);
uninit(ao);
return -1;
}
struct mp_chmap_sel sel = {0};
mp_chmap_sel_add_waveext_def(&sel);
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels)) {
uninit(ao, true);
uninit(ao);
return -1;
}
@ -186,7 +186,7 @@ static int init(struct ao *ao)
if (SDL_OpenAudio(&desired, &obtained)) {
if (!ao->probing)
MP_ERR(ao, "could not open audio: %s\n", SDL_GetError());
uninit(ao, true);
uninit(ao);
return -1;
}
@ -217,12 +217,12 @@ static int init(struct ao *ao)
default:
if (!ao->probing)
MP_ERR(ao, "could not find matching format\n");
uninit(ao, true);
uninit(ao);
return -1;
}
if (!ao_chmap_sel_get_def(ao, &sel, &ao->channels, obtained.channels)) {
uninit(ao, true);
uninit(ao);
return -1;
}
@ -231,13 +231,13 @@ static int init(struct ao *ao)
priv->buffer_mutex = SDL_CreateMutex();
if (!priv->buffer_mutex) {
MP_ERR(ao, "SDL_CreateMutex failed\n");
uninit(ao, true);
uninit(ao);
return -1;
}
priv->underrun_cond = SDL_CreateCond();
if (!priv->underrun_cond) {
MP_ERR(ao, "SDL_CreateCond failed\n");
uninit(ao, true);
uninit(ao);
return -1;
}

View File

@ -216,7 +216,7 @@ error:
/*
* close device
*/
static void uninit(struct ao *ao, bool immed)
static void uninit(struct ao *ao)
{
struct priv *p = ao->priv;

View File

@ -1218,11 +1218,11 @@ static int setup_buffers(struct wasapi_state *state)
return !state->ringbuff;
}
static void uninit(struct ao *ao, bool block)
static void uninit(struct ao *ao)
{
MP_VERBOSE(ao, "uninit!\n");
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
state->immed = !block;
state->immed = 1;
SetEvent(state->hUninit);
/* wait up to 10 seconds */
if (WaitForSingleObject(state->threadLoop, 10000) == WAIT_TIMEOUT)

View File

@ -100,13 +100,14 @@ struct ao_driver {
int (*init)(struct ao *ao);
// 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 (*uninit)(struct ao *ao);
void (*reset)(struct ao*ao);
int (*get_space)(struct ao *ao);
int (*play)(struct ao *ao, void **data, int samples, int flags);
float (*get_delay)(struct ao *ao);
void (*pause)(struct ao *ao);
void (*resume)(struct ao *ao);
void (*drain)(struct ao *ao);
// For option parsing (see vo.h)
int priv_size;

View File

@ -190,9 +190,9 @@ static void resume(struct ao *ao)
ao->driver->resume(ao);
}
static void uninit(struct ao *ao, bool cut_audio)
static void uninit(struct ao *ao)
{
ao->driver->uninit(ao, cut_audio);
ao->driver->uninit(ao);
}
static int init(struct ao *ao)

View File

@ -106,6 +106,17 @@ static void resume(struct ao *ao)
pthread_mutex_unlock(&p->lock);
}
static void drain(struct ao *ao)
{
if (ao->driver->drain) {
struct ao_push_state *p = ao->api_priv;
pthread_mutex_lock(&p->lock);
ao->driver->drain(ao);
pthread_mutex_unlock(&p->lock);
} else {
ao_wait_drain(ao);
}
}
static int get_space(struct ao *ao)
{
@ -202,7 +213,7 @@ static void *playthread(void *arg)
return NULL;
}
static void uninit(struct ao *ao, bool cut_audio)
static void uninit(struct ao *ao)
{
struct ao_push_state *p = ao->api_priv;
@ -213,7 +224,7 @@ static void uninit(struct ao *ao, bool cut_audio)
pthread_join(p->thread, NULL);
ao->driver->uninit(ao, cut_audio);
ao->driver->uninit(ao);
pthread_cond_destroy(&p->wakeup);
pthread_mutex_destroy(&p->lock);
@ -231,7 +242,7 @@ static int init(struct ao *ao)
&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);
ao->driver->uninit(ao);
return -1;
}
return 0;
@ -247,6 +258,7 @@ const struct ao_driver ao_api_push = {
.get_delay = get_delay,
.pause = pause,
.resume = resume,
.drain = drain,
.priv_size = sizeof(struct ao_push_state),
};

View File

@ -173,10 +173,8 @@ void uninit_player(struct MPContext *mpctx, unsigned int mask)
struct ao *ao = mpctx->ao;
mpctx->initialized_flags &= ~INITIALIZED_AO;
if (ao) {
bool drain = false;
// Note: with gapless_audio, stop_play is not correctly set
if (opts->gapless_audio || mpctx->stop_play == AT_END_OF_FILE) {
drain = true;
struct mp_audio data;
mp_audio_buffer_peek(mpctx->ao_buffer, &data);
int samples = mpctx->ao_buffer_playable_samples;
@ -187,8 +185,9 @@ void uninit_player(struct MPContext *mpctx, unsigned int mask)
if (played < samples)
MP_WARN(mpctx, "Audio output truncated at end.\n");
}
ao_drain(ao);
}
ao_uninit(ao, drain);
ao_uninit(ao);
}
mpctx->ao = NULL;
}