From d27ad9654218463694093697e3d09f8983b4ccf3 Mon Sep 17 00:00:00 2001 From: wm4 Date: Sun, 31 May 2020 15:00:35 +0200 Subject: audio: redo internal AO API This affects "pull" AOs only: ao_alsa, ao_pulse, ao_openal, ao_pcm, ao_lavc. There are changes to the other AOs too, but that's only about renaming ao_driver.resume to ao_driver.start. ao_openal is broken because I didn't manage to fix it, so it exits with an error message. If you want it, why don't _you_ put effort into it? I see no reason to waste my own precious lifetime over this (I realize the irony). ao_alsa loses the poll() mechanism, but it was mostly broken and didn't really do what it was supposed to. There doesn't seem to be anything in the ALSA API to watch the playback status without polling (unless you want to use raw UNIX signals). No idea if ao_pulse is correct, or whether it's subtly broken now. There is no documentation, so I can't tell what is correct, without reverse engineering the whole project. I recommend using ALSA. This was supposed to be just a simple fix, but somehow it expanded scope like a train wreck. Very high chance of regressions, but probably only for the AOs listed above. The rest you can figure out from reading the diff. --- audio/out/ao.c | 12 +- audio/out/ao.h | 2 +- audio/out/ao_alsa.c | 369 ++++++++++----------------- audio/out/ao_audiotrack.c | 2 +- audio/out/ao_audiounit.m | 2 +- audio/out/ao_coreaudio.c | 2 +- audio/out/ao_coreaudio_exclusive.c | 2 +- audio/out/ao_jack.c | 4 +- audio/out/ao_lavc.c | 57 +++-- audio/out/ao_null.c | 89 ++++--- audio/out/ao_openal.c | 90 +++---- audio/out/ao_opensles.c | 2 +- audio/out/ao_pcm.c | 40 ++- audio/out/ao_pulse.c | 160 +++++------- audio/out/ao_sdl.c | 4 +- audio/out/ao_wasapi.c | 2 +- audio/out/buffer.c | 497 +++++++++++++++++-------------------- audio/out/internal.h | 118 ++++----- 18 files changed, 633 insertions(+), 821 deletions(-) (limited to 'audio/out') diff --git a/audio/out/ao.c b/audio/out/ao.c index 7b301cd2e7..9ac42806e4 100644 --- a/audio/out/ao.c +++ b/audio/out/ao.c @@ -236,8 +236,10 @@ static struct ao *ao_init(bool probing, struct mpv_global *global, } ao->bps = ao->samplerate * ao->sstride; - if (!ao->device_buffer && ao->driver->get_space) - ao->device_buffer = ao->driver->get_space(ao); + if (ao->device_buffer < 0 && ao->driver->write) { + MP_ERR(ao, "Device buffer size not set.\n"); + goto fail; + } if (ao->device_buffer) MP_VERBOSE(ao, "device buffer: %d samples.\n", ao->device_buffer); ao->buffer = MPMAX(ao->device_buffer, ao->def_buffer * ao->samplerate); @@ -374,12 +376,6 @@ void ao_hotplug_event(struct ao *ao) ao_add_events(ao, AO_EVENT_HOTPLUG); } -// Returns whether this call actually set a new underrun flag. -bool ao_underrun_event(struct ao *ao) -{ - return ao_add_events(ao, AO_EVENT_UNDERRUN); -} - bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s, struct mp_chmap *map) { diff --git a/audio/out/ao.h b/audio/out/ao.h index da81be103c..0207e5a8bf 100644 --- a/audio/out/ao.h +++ b/audio/out/ao.h @@ -39,7 +39,7 @@ enum aocontrol { // If set, then the queued audio data is the last. Note that after a while, new // data might be written again, instead of closing the AO. -#define AOPLAY_FINAL_CHUNK 1 +#define PLAYER_FINAL_CHUNK 1 enum { AO_EVENT_RELOAD = 1, diff --git a/audio/out/ao_alsa.c b/audio/out/ao_alsa.c index e100c0fb12..0f375133e1 100644 --- a/audio/out/ao_alsa.c +++ b/audio/out/ao_alsa.c @@ -91,12 +91,9 @@ static const struct m_sub_options ao_alsa_conf = { struct priv { snd_pcm_t *alsa; bool device_lost; + bool underrun; snd_pcm_format_t alsa_fmt; bool can_pause; - bool paused; - bool final_chunk_written; - snd_pcm_sframes_t prepause_frames; - double delay_before_pause; snd_pcm_uframes_t buffersize; snd_pcm_uframes_t outburst; @@ -121,34 +118,6 @@ struct priv { MP_WARN(ao, "%s: %s\n", (message), snd_strerror(err)); \ } while (0) -// Common code for handling ENODEV, which happens if a device gets "lost", and -// can't be used anymore. Returns true if alsa_err is not ENODEV. -static bool check_device_present(struct ao *ao, int alsa_err) -{ - struct priv *p = ao->priv; - if (alsa_err != -ENODEV) - return true; - if (!p->device_lost) { - MP_WARN(ao, "Device lost, trying to recover...\n"); - ao_request_reload(ao); - p->device_lost = true; - } - return false; -} - -static void handle_underrun(struct ao *ao) -{ - struct priv *p = ao->priv; - - if (!p->final_chunk_written) { - ao_underrun_event(ao); - - int err = snd_pcm_prepare(p->alsa); - CHECK_ALSA_ERROR("pcm prepare error"); - alsa_error: ; - } -} - static int control(struct ao *ao, enum aocontrol cmd, void *arg) { struct priv *p = ao->priv; @@ -865,9 +834,8 @@ static int init_device(struct ao *ao, int mode) err = snd_pcm_sw_params_get_boundary(alsa_swparams, &boundary); CHECK_ALSA_ERROR("Unable to get boundary"); - /* start playing when one period has been written */ - err = snd_pcm_sw_params_set_start_threshold - (p->alsa, alsa_swparams, p->outburst); + // Manual trigger; INT_MAX as suggested by ALSA doxygen (they call it MAXINT). + err = snd_pcm_sw_params_set_start_threshold(p->alsa, alsa_swparams, INT_MAX); CHECK_ALSA_ERROR("Unable to set start threshold"); /* play silence when there is an underrun */ @@ -887,6 +855,9 @@ static int init_device(struct ao *ao, int mode) p->convert.channels = ao->channels.num; + err = snd_pcm_prepare(p->alsa); + CHECK_ALSA_ERROR("pcm prepare error"); + return 0; alsa_error: @@ -944,258 +915,184 @@ static int init(struct ao *ao) return r; } -static int get_space(struct ao *ao) +// Function for dealing with playback state. This attempts to recover the ALSA +// state (bring it into SND_PCM_STATE_{PREPARED,RUNNING,PAUSED}). If state!=NULL, +// fill it after recovery. +// Returns true if PCM is in one the expected states. +static bool recover_and_get_state(struct ao *ao, struct mp_pcm_state *state) { struct priv *p = ao->priv; + int err; - // in case of pausing or the device still being configured, - // just return our buffer size. - if (p->paused || snd_pcm_state(p->alsa) == SND_PCM_STATE_SETUP) - return p->buffersize; - - snd_pcm_sframes_t space = snd_pcm_avail(p->alsa); - if (space < 0 && space != -EPIPE) { - MP_ERR(ao, "Error received from snd_pcm_avail " - "(%ld, %s with ALSA state %s)!\n", - space, snd_strerror(space), - snd_pcm_state_name(snd_pcm_state(p->alsa))); - - // request a reload of the AO if device is not present, - // then error out. - check_device_present(ao, space); - goto alsa_error; - } - if (space == -EPIPE) - handle_underrun(ao); - - if (space > p->buffersize || space < 0) // Buffer underrun? - space = p->buffersize; - return space / p->outburst * p->outburst; - -alsa_error: - return 0; -} + snd_pcm_status_t *st; + snd_pcm_status_alloca(&st); -/* delay in seconds between first and last sample in buffer */ -static double get_delay(struct ao *ao) -{ - struct priv *p = ao->priv; - snd_pcm_sframes_t delay; + bool state_ok = false; - if (p->paused) - return p->delay_before_pause; + // Give it a number of chances to recover. This tries to deal with the fact + // that the API is asynchronous, and to account for some past cargo-cult + // (where things were retried in a loop). + for (int n = 0; n < 10; n++) { + err = snd_pcm_status(p->alsa, st); + CHECK_ALSA_ERROR("snd_pcm_status"); - int err = snd_pcm_delay(p->alsa, &delay); - if (err < 0) { - if (err == -EPIPE) - handle_underrun(ao); - return 0; - } + snd_pcm_state_t pcmst = snd_pcm_status_get_state(st); + if (pcmst == SND_PCM_STATE_PREPARED || + pcmst == SND_PCM_STATE_RUNNING || + pcmst == SND_PCM_STATE_PAUSED) + { + state_ok = true; + break; + } - if (delay < 0) { - /* underrun - move the application pointer forward to catch up */ - snd_pcm_forward(p->alsa, -delay); - delay = 0; - } - return delay / (double)ao->samplerate; -} + MP_VERBOSE(ao, "attempt %d to recover from state '%s'...\n", + n + 1, snd_pcm_state_name(pcmst)); -// For stream-silence mode: replace remaining buffer with silence. -// Tries to cause an instant buffer underrun. -static void soft_reset(struct ao *ao) -{ - struct priv *p = ao->priv; - snd_pcm_sframes_t frames = snd_pcm_rewindable(p->alsa); - if (frames > 0 && snd_pcm_state(p->alsa) == SND_PCM_STATE_RUNNING) { - frames = snd_pcm_rewind(p->alsa, frames); - if (frames < 0) { - int err = frames; - CHECK_ALSA_WARN("pcm rewind error"); + switch (pcmst) { + // Underrun; note and recover. We never use draining, + case SND_PCM_STATE_XRUN: + case SND_PCM_STATE_DRAINING: + p->underrun = true; + err = snd_pcm_prepare(p->alsa); + CHECK_ALSA_ERROR("pcm prepare error"); + continue; + // Hardware suspend. + case SND_PCM_STATE_SUSPENDED: + MP_INFO(ao, "PCM in suspend mode, trying to resume.\n"); + err = snd_pcm_resume(p->alsa); + if (err == -EAGAIN) { + // Cargo-cult from decades ago, with a cargo cult timeout. + MP_INFO(ao, "PCM resume EAGAIN - retrying.\n"); + sleep(1); + continue; + } + if (err == -ENOSYS) { + // As suggested by ALSA doxygen. + MP_VERBOSE(ao, "ENOSYS, retrying with snd_pcm_prepare().\n"); + err = snd_pcm_prepare(p->alsa); + } + if (err < 0) + MP_ERR(ao, "resuming from SUSPENDED: %s\n", snd_strerror(err)); + continue; + // Device lost. OPEN/SETUP are states we never enter after init, so + // treat them like DISCONNECTED. + case SND_PCM_STATE_DISCONNECTED: + case SND_PCM_STATE_OPEN: + case SND_PCM_STATE_SETUP: + default: + if (!p->device_lost) { + MP_WARN(ao, "Device lost, trying to recover...\n"); + ao_request_reload(ao); + p->device_lost = true; + } + return false; } } -} -static void audio_pause(struct ao *ao) -{ - struct priv *p = ao->priv; - int err; - - if (p->paused) - return; - - p->delay_before_pause = get_delay(ao); - p->prepause_frames = p->delay_before_pause * ao->samplerate; + if (!state_ok) { + MP_ERR(ao, "could not recover\n"); + return false; + } - if (ao->stream_silence) { - soft_reset(ao); - } else if (p->can_pause) { - if (snd_pcm_state(p->alsa) == SND_PCM_STATE_RUNNING) { - err = snd_pcm_pause(p->alsa, 1); - CHECK_ALSA_ERROR("pcm pause error"); - p->prepause_frames = 0; - } - } else { - err = snd_pcm_drop(p->alsa); - CHECK_ALSA_ERROR("pcm drop error"); + if (state) { + snd_pcm_sframes_t del = snd_pcm_status_get_delay(st); + state->delay = MPMAX(del, 0) / (double)ao->samplerate; + state->free_samples = snd_pcm_status_get_avail(st); + state->free_samples = MPCLAMP(state->free_samples, 0, ao->device_buffer); + state->queued_samples = ao->device_buffer - state->free_samples; + state->underrun = p->underrun; } - p->paused = true; + return true; -alsa_error: ; +alsa_error: + return false; } -static void resume_device(struct ao *ao) +static void audio_get_state(struct ao *ao, struct mp_pcm_state *state) { struct priv *p = ao->priv; - int err; - - if (snd_pcm_state(p->alsa) == SND_PCM_STATE_SUSPENDED) { - MP_INFO(ao, "PCM in suspend mode, trying to resume.\n"); - - while ((err = snd_pcm_resume(p->alsa)) == -EAGAIN) - sleep(1); - } + recover_and_get_state(ao, state); + p->underrun = false; } -static void audio_resume(struct ao *ao) +static void audio_start(struct ao *ao) { struct priv *p = ao->priv; int err; - if (!p->paused) - return; - - resume_device(ao); - - if (ao->stream_silence) { - p->paused = false; - get_delay(ao); // recovers from underrun (as a side-effect) - } else if (p->can_pause) { - if (snd_pcm_state(p->alsa) == SND_PCM_STATE_PAUSED) { - err = snd_pcm_pause(p->alsa, 0); - CHECK_ALSA_ERROR("pcm resume error"); - } - } else { - MP_VERBOSE(ao, "resume not supported by hardware\n"); - err = snd_pcm_prepare(p->alsa); - CHECK_ALSA_ERROR("pcm prepare error"); - } + recover_and_get_state(ao, NULL); - if (p->prepause_frames) - ao_play_silence(ao, p->prepause_frames); + err = snd_pcm_start(p->alsa); + CHECK_ALSA_ERROR("pcm start error"); alsa_error: ; - p->paused = false; } -static void reset(struct ao *ao) +static void audio_reset(struct ao *ao) { struct priv *p = ao->priv; int err; - p->paused = false; - p->prepause_frames = 0; - p->delay_before_pause = 0; - p->final_chunk_written = false; + err = snd_pcm_drop(p->alsa); + CHECK_ALSA_ERROR("pcm drop error"); + err = snd_pcm_prepare(p->alsa); + CHECK_ALSA_ERROR("pcm prepare error"); - if (ao->stream_silence) { - soft_reset(ao); - } else { - err = snd_pcm_drop(p->alsa); - CHECK_ALSA_ERROR("pcm prepare error"); - err = snd_pcm_prepare(p->alsa); - CHECK_ALSA_ERROR("pcm prepare error"); - } + recover_and_get_state(ao, NULL); alsa_error: ; } -static int play(struct ao *ao, void **data, int samples, int flags) +static bool audio_set_paused(struct ao *ao, bool paused) { struct priv *p = ao->priv; - snd_pcm_sframes_t res = 0; - bool final_chunk = flags & AOPLAY_FINAL_CHUNK; - - if (!final_chunk) - samples = samples / p->outburst * p->outburst; - - if (samples == 0) - goto done; - ao_convert_inplace(&p->convert, data, samples); + int err; - do { - if (af_fmt_is_planar(ao->format)) { - res = snd_pcm_writen(p->alsa, data, samples); - } else { - res = snd_pcm_writei(p->alsa, data[0], samples); - } + recover_and_get_state(ao, NULL); - if (res == -EINTR || res == -EAGAIN) { /* retry */ - res = 0; - } else if (!check_device_present(ao, res)) { - goto alsa_error; - } else if (res < 0) { - if (res == -ESTRPIPE) { /* suspend */ - resume_device(ao); - } else if (res == -EPIPE) { - handle_underrun(ao); - } else { - MP_ERR(ao, "Write error: %s\n", snd_strerror(res)); - } - int err = snd_pcm_prepare(p->alsa); - CHECK_ALSA_ERROR("pcm prepare error"); - res = 0; - } - } while (res == 0); + if (!p->can_pause) + return false; - p->paused = false; + snd_pcm_state_t pcmst = snd_pcm_state(p->alsa); + if (pcmst == SND_PCM_STATE_RUNNING && paused) { + err = snd_pcm_pause(p->alsa, 1); + CHECK_ALSA_ERROR("pcm pause error"); + } else if (pcmst == SND_PCM_STATE_PAUSED && !paused) { + err = snd_pcm_pause(p->alsa, 0); + CHECK_ALSA_ERROR("pcm resume error"); + } -done: - p->final_chunk_written = res == samples && final_chunk; - return res < 0 ? -1 : res; + return true; alsa_error: - return -1; + return false; } -#define MAX_POLL_FDS 20 -static int audio_wait(struct ao *ao, pthread_mutex_t *lock) +static bool audio_write(struct ao *ao, void **data, int samples) { struct priv *p = ao->priv; - int err; - - int num_fds = snd_pcm_poll_descriptors_count(p->alsa); - if (num_fds <= 0 || num_fds >= MAX_POLL_FDS) - goto alsa_error; - struct pollfd fds[MAX_POLL_FDS]; - err = snd_pcm_poll_descriptors(p->alsa, fds, num_fds); - CHECK_ALSA_ERROR("cannot get pollfds"); + ao_convert_inplace(&p->convert, data, samples); - while (1) { - int r = ao_wait_poll(ao, fds, num_fds, lock); - if (r) - return r; + if (!recover_and_get_state(ao, NULL)) + return false; - unsigned short revents; - err = snd_pcm_poll_descriptors_revents(p->alsa, fds, num_fds, &revents); - CHECK_ALSA_ERROR("cannot read poll events"); + snd_pcm_sframes_t err = 0; + if (af_fmt_is_planar(ao->format)) { + err = snd_pcm_writen(p->alsa, data, samples); + } else { + err = snd_pcm_writei(p->alsa, data[0], samples); + } - if (revents & POLLERR) { - snd_pcm_status_t *status; - snd_pcm_status_alloca(&status); + CHECK_ALSA_ERROR("pcm write error"); + if (err != samples) + MP_WARN(ao, "unexpected short write\n"); - err = snd_pcm_status(p->alsa, status); - check_device_present(ao, err); - return -1; - } - if (revents & POLLOUT) - return 0; - } - return 0; + return true; alsa_error: - return -1; + return false; } static bool is_useless_device(char *name) @@ -1248,16 +1145,12 @@ const struct ao_driver audio_out_alsa = { .init = init, .uninit = uninit, .control = control, - .get_space = get_space, - .play = play, - .get_delay = get_delay, - .pause = audio_pause, - .resume = audio_resume, - .reset = reset, - .wait = audio_wait, - .wakeup = ao_wakeup_poll, + .get_state = audio_get_state, + .write = audio_write, + .start = audio_start, + .set_pause = audio_set_paused, + .reset = audio_reset, .list_devs = list_devs, - .reports_underruns = true, .priv_size = sizeof(struct priv), .global_opts = &ao_alsa_conf, }; diff --git a/audio/out/ao_audiotrack.c b/audio/out/ao_audiotrack.c index b3be357c1b..7a1715d373 100644 --- a/audio/out/ao_audiotrack.c +++ b/audio/out/ao_audiotrack.c @@ -706,7 +706,7 @@ const struct ao_driver audio_out_audiotrack = { .init = init, .uninit = uninit, .reset = stop, - .resume = start, + .start = start, .priv_size = sizeof(struct priv), .options = (const struct m_option[]) { {"pcm-float", OPT_FLAG(cfg_pcm_float)}, diff --git a/audio/out/ao_audiounit.m b/audio/out/ao_audiounit.m index dd77464edb..0dfcb1d82b 100644 --- a/audio/out/ao_audiounit.m +++ b/audio/out/ao_audiounit.m @@ -193,6 +193,6 @@ const struct ao_driver audio_out_audiounit = { .uninit = uninit, .init = init, .reset = stop, - .resume = start, + .start = start, .priv_size = sizeof(struct priv), }; diff --git a/audio/out/ao_coreaudio.c b/audio/out/ao_coreaudio.c index 533d102d32..99007ef018 100644 --- a/audio/out/ao_coreaudio.c +++ b/audio/out/ao_coreaudio.c @@ -417,7 +417,7 @@ const struct ao_driver audio_out_coreaudio = { .init = init, .control = control, .reset = stop, - .resume = start, + .start = start, .hotplug_init = hotplug_init, .hotplug_uninit = hotplug_uninit, .list_devs = ca_get_device_list, diff --git a/audio/out/ao_coreaudio_exclusive.c b/audio/out/ao_coreaudio_exclusive.c index 19721e0951..b1d7c57017 100644 --- a/audio/out/ao_coreaudio_exclusive.c +++ b/audio/out/ao_coreaudio_exclusive.c @@ -455,7 +455,7 @@ const struct ao_driver audio_out_coreaudio_exclusive = { .uninit = uninit, .init = init, .reset = audio_pause, - .resume = audio_resume, + .start = audio_resume, .list_devs = ca_get_device_list, .priv_size = sizeof(struct priv), .priv_defaults = &(const struct priv){ diff --git a/audio/out/ao_jack.c b/audio/out/ao_jack.c index 249c314f9d..767f437895 100644 --- a/audio/out/ao_jack.c +++ b/audio/out/ao_jack.c @@ -194,7 +194,7 @@ err_port_register: return -1; } -static void resume(struct ao *ao) +static void start(struct ao *ao) { struct priv *p = ao->priv; if (!p->activated) { @@ -276,7 +276,7 @@ const struct ao_driver audio_out_jack = { .name = "jack", .init = init, .uninit = uninit, - .resume = resume, + .start = start, .priv_size = sizeof(struct priv), .global_opts = &ao_jack_conf, }; diff --git a/audio/out/ao_lavc.c b/audio/out/ao_lavc.c index ad3865964c..33e82219b0 100644 --- a/audio/out/ao_lavc.c +++ b/audio/out/ao_lavc.c @@ -156,7 +156,8 @@ static int init(struct ao *ao) ao->untimed = true; - ao->period_size = ac->aframesize * ac->framecount; + ao->device_buffer = ac->aframesize * ac->framecount; + ao->period_size = ao->device_buffer; if (ao->channels.num > AV_NUM_DATA_POINTERS) goto fail; @@ -188,14 +189,6 @@ static void uninit(struct ao *ao) } } -// return: how many samples can be played without blocking -static int get_space(struct ao *ao) -{ - struct priv *ac = ao->priv; - - return ac->aframesize * ac->framecount; -} - // must get exactly ac->aframesize amount of data static void encode(struct ao *ao, double apts, void **data) { @@ -249,9 +242,9 @@ static void encode(struct ao *ao, double apts, void **data) } } -// this should round samples down to frame sizes -// return: number of samples played -static int play(struct ao *ao, void **data, int samples, int flags) +// Note: currently relies on samples aligned to period sizes - will not work +// in the future. +static bool audio_write(struct ao *ao, void **data, int samples) { struct priv *ac = ao->priv; struct encoder_context *enc = ac->enc; @@ -271,7 +264,7 @@ static int play(struct ao *ao, void **data, int samples, int flags) void *tempdata = NULL; void *padded[MP_NUM_CHANNELS]; - if ((flags & AOPLAY_FINAL_CHUNK) && (samples % ac->aframesize)) { + if (samples % ac->aframesize) { tempdata = talloc_new(NULL); size_t bytelen = samples * ao->sstride; size_t extralen = (ac->aframesize - 1) * ao->sstride; @@ -282,6 +275,7 @@ static int play(struct ao *ao, void **data, int samples, int flags) } data = padded; samples = (bytelen + extralen) / ao->sstride; + MP_VERBOSE(ao, "padding final frame with silence\n"); } double outpts = pts; @@ -334,15 +328,28 @@ static int play(struct ao *ao, void **data, int samples, int flags) pthread_mutex_unlock(&ectx->lock); - if (flags & AOPLAY_FINAL_CHUNK) { - if (bufpos < orig_samples) - MP_ERR(ao, "did not write enough data at the end\n"); - } else { - if (bufpos > orig_samples) - MP_ERR(ao, "audio buffer overflow (should never happen)\n"); - } + return true; +} + +static void get_state(struct ao *ao, struct mp_pcm_state *state) +{ + state->free_samples = ao->device_buffer; + state->queued_samples = 0; + state->delay = 0; +} + +static bool set_pause(struct ao *ao, bool paused) +{ + return true; // signal support so common code doesn't write silence +} - return taken; +static void start(struct ao *ao) +{ + // we use data immediately +} + +static void reset(struct ao *ao) +{ } const struct ao_driver audio_out_lavc = { @@ -350,12 +357,14 @@ const struct ao_driver audio_out_lavc = { .description = "audio encoding using libavcodec", .name = "lavc", .initially_blocked = true, - .reports_underruns = true, // not a thing .priv_size = sizeof(struct priv), .init = init, .uninit = uninit, - .get_space = get_space, - .play = play, + .get_state = get_state, + .set_pause = set_pause, + .write = audio_write, + .start = start, + .reset = reset, }; // vim: sw=4 ts=4 et tw=80 diff --git a/audio/out/ao_null.c b/audio/out/ao_null.c index 07a0d4748c..107dc7e35a 100644 --- a/audio/out/ao_null.c +++ b/audio/out/ao_null.c @@ -40,9 +40,10 @@ struct priv { bool paused; double last_time; - bool playing_final; float buffered; // samples int buffersize; // samples + bool playing; + bool underrun; int untimed; float bufferlen; // seconds @@ -77,8 +78,7 @@ static void drain(struct ao *ao) if (priv->buffered > 0) { priv->buffered -= (now - priv->last_time) * ao->samplerate * priv->speed; if (priv->buffered < 0) { - if (!priv->playing_final) - MP_ERR(ao, "buffer underrun\n"); + priv->underrun = true; priv->buffered = 0; } } @@ -127,85 +127,81 @@ static void reset(struct ao *ao) { struct priv *priv = ao->priv; priv->buffered = 0; - priv->playing_final = false; + priv->underrun = false; + priv->playing = false; } -// stop playing, keep buffers (for pause) -static void pause(struct ao *ao) +static void start(struct ao *ao) { struct priv *priv = ao->priv; - drain(ao); - priv->paused = true; -} - -// resume playing, after pause() -static void resume(struct ao *ao) -{ - struct priv *priv = ao->priv; + if (priv->paused) + MP_ERR(ao, "illegal state: start() while paused\n"); drain(ao); priv->paused = false; priv->last_time = mp_time_sec(); + priv->playing = true; } -static int get_space(struct ao *ao) +static bool set_pause(struct ao *ao, bool paused) { struct priv *priv = ao->priv; - drain(ao); - int samples = priv->buffersize - priv->latency - priv->buffered; - return samples / priv->outburst * priv->outburst; + if (!priv->playing) + MP_ERR(ao, "illegal state: set_pause() while not playing\n"); + + if (priv->paused != paused) { + + drain(ao); + priv->paused = paused; + if (!priv->paused) + priv->last_time = mp_time_sec(); + } + + return true; } -static int play(struct ao *ao, void **data, int samples, int flags) +static bool audio_write(struct ao *ao, void **data, int samples) { struct priv *priv = ao->priv; - int accepted; - - resume(ao); if (priv->buffered <= 0) priv->buffered = priv->latency; // emulate fixed latency - priv->playing_final = flags & AOPLAY_FINAL_CHUNK; - if (priv->playing_final) { - // Last audio chunk - don't round to outburst. - accepted = MPMIN(priv->buffersize - priv->buffered, samples); - } else { - int maxbursts = (priv->buffersize - priv->buffered) / priv->outburst; - int playbursts = samples / priv->outburst; - int bursts = playbursts > maxbursts ? maxbursts : playbursts; - accepted = bursts * priv->outburst; - } - priv->buffered += accepted; - return accepted; + priv->buffered += samples; + return true; } -static double get_delay(struct ao *ao) +static void get_state(struct ao *ao, struct mp_pcm_state *state) { struct priv *priv = ao->priv; drain(ao); - // Note how get_delay returns the delay in audio device time (instead of + state->free_samples = priv->buffersize - priv->latency - priv->buffered; + state->free_samples = state->free_samples / priv->outburst * priv->outburst; + state->queued_samples = priv->buffered; + + // Note how get_state returns the delay in audio device time (instead of // adjusting for speed), since most AOs seem to also do that. - double delay = priv->buffered; + state->delay = priv->buffered; // Drivers with broken EOF handling usually always report the same device- // level delay that is additional to the buffer time. if (priv->broken_eof && priv->buffered < priv->latency) - delay = priv->latency; + state->delay = priv->latency; - delay /= ao->samplerate; + state->delay /= ao->samplerate; if (priv->broken_delay) { // Report only multiples of outburst double q = priv->outburst / (double)ao->samplerate; - if (delay > 0) - delay = (int)(delay / q) * q; + if (state->delay > 0) + state->delay = (int)(state->delay / q) * q; } - return delay; + state->underrun = priv->underrun; + priv->underrun = false; } #define OPT_BASE_STRUCT struct priv @@ -216,11 +212,10 @@ const struct ao_driver audio_out_null = { .init = init, .uninit = uninit, .reset = reset, - .get_space = get_space, - .play = play, - .get_delay = get_delay, - .pause = pause, - .resume = resume, + .get_state = get_state, + .set_pause = set_pause, + .write = audio_write, + .start = start, .priv_size = sizeof(struct priv), .priv_defaults = &(const struct priv) { .bufferlen = 0.2, diff --git a/audio/out/ao_openal.c b/audio/out/ao_openal.c index aa20fb8bed..64d957e893 100644 --- a/audio/out/ao_openal.c +++ b/audio/out/ao_openal.c @@ -61,8 +61,6 @@ struct priv { int direct_channels; }; -static void reset(struct ao *ao); - static int control(struct ao *ao, enum aocontrol cmd, void *arg) { switch (cmd) { @@ -177,6 +175,9 @@ static void uninit(struct ao *ao) static int init(struct ao *ao) { + MP_FATAL(ao, "broken.\n"); + return -1; + float position[3] = {0, 0, 0}; float direction[6] = {0, 0, -1, 0, 1, 0}; ALCdevice *dev = NULL; @@ -291,67 +292,32 @@ static void unqueue_buffers(struct ao *ao) } } -/** - * \brief stop playing and empty buffers (for seeking/pause) - */ static void reset(struct ao *ao) { alSourceStop(source); unqueue_buffers(ao); } -/** - * \brief stop playing, keep buffers (for pause) - */ -static void audio_pause(struct ao *ao) -{ - alSourcePause(source); -} - -/** - * \brief resume playing, after audio_pause() - */ -static void audio_resume(struct ao *ao) -{ - alSourcePlay(source); -} - -static int get_space(struct ao *ao) +static bool audio_set_pause(struct ao *ao, bool pause) { - struct priv *p = ao->priv; - ALint queued; - unqueue_buffers(ao); - alGetSourcei(source, AL_BUFFERS_QUEUED, &queued); - queued = p->num_buffers - queued; - if (queued < 0) - return 0; - return p->num_samples * queued; + if (pause) { + alSourcePause(source); + } else { + alSourcePlay(source); + } + return true; } -/** - * \brief write data into buffer and reset underrun flag - */ -static int play(struct ao *ao, void **data, int samples, int flags) +static bool audio_write(struct ao *ao, void **data, int samples) { struct priv *p = ao->priv; - int buffered_samples = 0; - int num = 0; - if (flags & AOPLAY_FINAL_CHUNK) { - num = 1; - buffered_samples = samples; - } else { - num = samples / p->num_samples; - buffered_samples = num * p->num_samples; - } + int num = (samples + p->num_samples - 1) / p->num_samples; for (int i = 0; i < num; i++) { char *d = *data; - if (flags & AOPLAY_FINAL_CHUNK) { - buffer_size[cur_buf] = samples; - } else { - buffer_size[cur_buf] = p->num_samples; - } + buffer_size[cur_buf] = + MPMIN(samples - num * p->num_samples, p->num_samples); d += i * buffer_size[cur_buf] * ao->sstride; alBufferData(buffers[cur_buf], p->al_format, d, buffer_size[cur_buf] * ao->sstride, ao->samplerate); @@ -359,17 +325,18 @@ static int play(struct ao *ao, void **data, int samples, int flags) cur_buf = (cur_buf + 1) % p->num_buffers; } - ALint state; - alGetSourcei(source, AL_SOURCE_STATE, &state); - if (state != AL_PLAYING) // checked here in case of an underrun - alSourcePlay(source); + return true; +} - return buffered_samples; +static void audio_start(struct ao *ao) +{ + alSourcePlay(source); } -static double get_delay(struct ao *ao) +static void get_state(struct ao *ao, struct mp_pcm_state *state) { struct priv *p = ao->priv; + ALint queued; unqueue_buffers(ao); alGetSourcei(source, AL_BUFFERS_QUEUED, &queued); @@ -393,7 +360,11 @@ static double get_delay(struct ao *ao) queued_samples += buffer_size[index]; index = (index + 1) % p->num_buffers; } - return (queued_samples / (double)ao->samplerate) + soft_source_latency; + + state->delay = queued_samples / (double)ao->samplerate + soft_source_latency; + + state->queued_samples = queued_samples; + state->free_samples = MPMAX(p->num_buffers - queued, 0) * p->num_samples; } #define OPT_BASE_STRUCT struct priv @@ -404,11 +375,10 @@ const struct ao_driver audio_out_openal = { .init = init, .uninit = uninit, .control = control, - .get_space = get_space, - .play = play, - .get_delay = get_delay, - .pause = audio_pause, - .resume = audio_resume, + .get_state = get_state, + .write = audio_write, + .start = audio_start, + .set_pause = audio_set_pause, .reset = reset, .priv_size = sizeof(struct priv), .priv_defaults = &(const struct priv) { diff --git a/audio/out/ao_opensles.c b/audio/out/ao_opensles.c index 67ebd46aff..40ab5324b5 100644 --- a/audio/out/ao_opensles.c +++ b/audio/out/ao_opensles.c @@ -247,7 +247,7 @@ const struct ao_driver audio_out_opensles = { .init = init, .uninit = uninit, .reset = reset, - .resume = resume, + .start = resume, .priv_size = sizeof(struct priv), .priv_defaults = &(const struct priv) { diff --git a/audio/out/ao_pcm.c b/audio/out/ao_pcm.c index 7d2656be49..3e23920bd6 100644 --- a/audio/out/ao_pcm.c +++ b/audio/out/ao_pcm.c @@ -162,6 +162,7 @@ static int init(struct ao *ao) if (priv->waveheader) // Reserve space for wave header write_wave_header(ao, priv->fp, 0x7ffff000); ao->untimed = true; + ao->device_buffer = 1 << 16; return 0; } @@ -193,19 +194,36 @@ static void uninit(struct ao *ao) fclose(priv->fp); } -static int get_space(struct ao *ao) -{ - return 65536; -} - -static int play(struct ao *ao, void **data, int samples, int flags) +static bool audio_write(struct ao *ao, void **data, int samples) { struct priv *priv = ao->priv; int len = samples * ao->sstride; fwrite(data[0], len, 1, priv->fp); priv->data_length += len; - return samples; + + return true; +} + +static void get_state(struct ao *ao, struct mp_pcm_state *state) +{ + state->free_samples = ao->device_buffer; + state->queued_samples = 0; + state->delay = 0; +} + +static bool set_pause(struct ao *ao, bool paused) +{ + return true; // signal support so common code doesn't write silence +} + +static void start(struct ao *ao) +{ + // we use data immediately +} + +static void reset(struct ao *ao) +{ } #define OPT_BASE_STRUCT struct priv @@ -215,9 +233,11 @@ const struct ao_driver audio_out_pcm = { .name = "pcm", .init = init, .uninit = uninit, - .get_space = get_space, - .play = play, - .reports_underruns = true, // not a thing + .get_state = get_state, + .set_pause = set_pause, + .write = audio_write, + .start = start, + .reset = reset, .priv_size = sizeof(struct priv), .priv_defaults = &(const struct priv) { .waveheader = 1 }, .options = (const struct m_option[]) { diff --git a/audio/out/ao_pulse.c b/audio/out/ao_pulse.c index c90ee255c9..563848b523 100644 --- a/audio/out/ao_pulse.c +++ b/audio/out/ao_pulse.c @@ -52,11 +52,7 @@ struct priv { struct pa_sink_input_info pi; int retval; - - // for wakeup handling - pthread_mutex_t wakeup_lock; - pthread_cond_t wakeup; - int wakeup_status; + bool underrun; char *cfg_host; int cfg_buffer; @@ -119,42 +115,27 @@ static void stream_state_cb(pa_stream *s, void *userdata) } } -static void wakeup(struct ao *ao) -{ - struct priv *priv = ao->priv; - pthread_mutex_lock(&priv->wakeup_lock); - priv->wakeup_status = 1; - pthread_cond_signal(&priv->wakeup); - pthread_mutex_unlock(&priv->wakeup_lock); -} - static void stream_request_cb(pa_stream *s, size_t length, void *userdata) { struct ao *ao = userdata; struct priv *priv = ao->priv; - wakeup(ao); + ao_wakeup_playthread(ao); pa_threaded_mainloop_signal(priv->mainloop, 0); } -static int wait_audio(struct ao *ao, pthread_mutex_t *lock) +static void stream_latency_update_cb(pa_stream *s, void *userdata) { + struct ao *ao = userdata; struct priv *priv = ao->priv; - // We don't use this mutex, because pulse like to call stream_request_cb - // while we have the central mutex held. - pthread_mutex_unlock(lock); - pthread_mutex_lock(&priv->wakeup_lock); - while (!priv->wakeup_status) - pthread_cond_wait(&priv->wakeup, &priv->wakeup_lock); - priv->wakeup_status = 0; - pthread_mutex_unlock(&priv->wakeup_lock); - pthread_mutex_lock(lock); - return 0; + pa_threaded_mainloop_signal(priv->mainloop, 0); } -static void stream_latency_update_cb(pa_stream *s, void *userdata) +static void underflow_cb(pa_stream *s, void *userdata) { struct ao *ao = userdata; struct priv *priv = ao->priv; + priv->underrun = true; + ao_wakeup_playthread(ao); pa_threaded_mainloop_signal(priv->mainloop, 0); } @@ -166,28 +147,33 @@ static void success_cb(pa_stream *s, int success, void *userdata) pa_threaded_mainloop_signal(priv->mainloop, 0); } -/** - * \brief waits for a pulseaudio operation to finish, frees it and - * unlocks the mainloop - * \param op operation to wait for - * \return 1 if operation has finished normally (DONE state), 0 otherwise - */ -static int waitop(struct priv *priv, pa_operation *op) +// Like waitop(), but keep the lock (even if it may unlock temporarily). +static bool waitop_no_unlock(struct priv *priv, pa_operation *op) { - if (!op) { - pa_threaded_mainloop_unlock(priv->mainloop); - return 0; - } + if (!op) + return false; pa_operation_state_t state = pa_operation_get_state(op); while (state == PA_OPERATION_RUNNING) { pa_threaded_mainloop_wait(priv->mainloop); state = pa_operation_get_state(op); } pa_operation_unref(op); - pa_threaded_mainloop_unlock(priv->mainloop); return state == PA_OPERATION_DONE; } +/** + * \brief waits for a pulseaudio operation to finish, frees it and + * unlocks the mainloop + * \param op operation to wait for + * \return 1 if operation has finished normally (DONE state), 0 otherwise + */ +static bool waitop(struct priv *priv, pa_operation *op) +{ + bool r = waitop_no_unlock(priv, op); + pa_threaded_mainloop_unlock(priv->mainloop); + return r; +} + static const struct format_map { int mp_format; pa_sample_format_t pa_format; @@ -301,9 +287,6 @@ static void uninit(struct ao *ao) pa_threaded_mainloop_free(priv->mainloop); priv->mainloop = NULL; } - - pthread_cond_destroy(&priv->wakeup); - pthread_mutex_destroy(&priv->wakeup_lock); } static int pa_init_boilerplate(struct ao *ao) @@ -312,9 +295,6 @@ static int pa_init_boilerplate(struct ao *ao) char *host = priv->cfg_host && priv->cfg_host[0] ? priv->cfg_host : NULL; bool locked = false; - pthread_mutex_init(&priv->wakeup_lock, NULL); - pthread_cond_init(&priv->wakeup, NULL); - if (!(priv->mainloop = pa_threaded_mainloop_new())) { MP_ERR(ao, "Failed to allocate main loop\n"); goto fail; @@ -451,12 +431,13 @@ static int init(struct ao *ao) pa_stream_set_write_callback(priv->stream, stream_request_cb, ao); pa_stream_set_latency_update_callback(priv->stream, stream_latency_update_cb, ao); + pa_stream_set_underflow_callback(priv->stream, underflow_cb, ao); uint32_t buf_size = ao->samplerate * (priv->cfg_buffer / 1000.0) * af_fmt_to_bytes(ao->format) * ao->channels.num; pa_buffer_attr bufattr = { .maxlength = -1, .tlength = buf_size > 0 ? buf_size : -1, - .prebuf = -1, + .prebuf = 0, .minreq = -1, .fragsize = -1, }; @@ -511,22 +492,23 @@ static void cork(struct ao *ao, bool pause) } // Play the specified data to the pulseaudio server -static int play(struct ao *ao, void **data, int samples, int flags) +static bool audio_write(struct ao *ao, void **data, int samples) { struct priv *priv = ao->priv; + bool res = true; pa_threaded_mainloop_lock(priv->mainloop); if (pa_stream_write(priv->stream, data[0], samples * ao->sstride, NULL, 0, PA_SEEK_RELATIVE) < 0) { GENERIC_ERR_MSG("pa_stream_write() failed"); - samples = -1; - } - if (flags & AOPLAY_FINAL_CHUNK) { - // Force start in case the stream was too short for prebuf - pa_operation *op = pa_stream_trigger(priv->stream, NULL, NULL); - pa_operation_unref(op); + res = false; } pa_threaded_mainloop_unlock(priv->mainloop); - return samples; + return res; +} + +static void start(struct ao *ao) +{ + cork(ao, false); } // Reset the audio stream, i.e. flush the playback buffer on the server side @@ -540,29 +522,12 @@ static void reset(struct ao *ao) if (!waitop(priv, pa_stream_flush(priv->stream, success_cb, ao)) || !priv->retval) GENERIC_ERR_MSG("pa_stream_flush() failed"); - cork(ao, false); -} - -// Pause the audio stream by corking it on the server -static void pause(struct ao *ao) -{ - cork(ao, true); -} - -// Resume the audio stream by uncorking it on the server -static void resume(struct ao *ao) -{ - cork(ao, false); } -// Return number of samples that may be written to the server without blocking -static int get_space(struct ao *ao) +static bool set_pause(struct ao *ao, bool paused) { - struct priv *priv = ao->priv; - pa_threaded_mainloop_lock(priv->mainloop); - size_t space = pa_stream_writable_size(priv->stream); - pa_threaded_mainloop_unlock(priv->mainloop); - return space / ao->sstride; + cork(ao, paused); + return true; } static double get_delay_hackfixed(struct ao *ao) @@ -581,21 +546,19 @@ static double get_delay_hackfixed(struct ao *ao) * this should be enough to fix the normal local playback case. */ struct priv *priv = ao->priv; - pa_threaded_mainloop_lock(priv->mainloop); - if (!waitop(priv, pa_stream_update_timing_info(priv->stream, NULL, NULL))) { + if (!waitop_no_unlock(priv, pa_stream_update_timing_info(priv->stream, + NULL, NULL))) + { GENERIC_ERR_MSG("pa_stream_update_timing_info() failed"); return 0; } - pa_threaded_mainloop_lock(priv->mainloop); const pa_timing_info *ti = pa_stream_get_timing_info(priv->stream); if (!ti) { - pa_threaded_mainloop_unlock(priv->mainloop); GENERIC_ERR_MSG("pa_stream_get_timing_info() failed"); return 0; } const struct pa_sample_spec *ss = pa_stream_get_sample_spec(priv->stream); if (!ss) { - pa_threaded_mainloop_unlock(priv->mainloop); GENERIC_ERR_MSG("pa_stream_get_sample_spec() failed"); return 0; } @@ -616,7 +579,6 @@ static double get_delay_hackfixed(struct ao *ao) latency += sink_latency; if (latency < 0) latency = 0; - pa_threaded_mainloop_unlock(priv->mainloop); return latency / 1e6; } @@ -624,7 +586,6 @@ static double get_delay_pulse(struct ao *ao) { struct priv *priv = ao->priv; pa_usec_t latency = (pa_usec_t) -1; - pa_threaded_mainloop_lock(priv->mainloop); while (pa_stream_get_latency(priv->stream, &latency, NULL) < 0) { if (pa_context_errno(priv->context) != PA_ERR_NODATA) { GENERIC_ERR_MSG("pa_stream_get_latency() failed"); @@ -633,19 +594,35 @@ static double get_delay_pulse(struct ao *ao) /* Wait until latency data is available again */ pa_threaded_mainloop_wait(priv->mainloop); } - pa_threaded_mainloop_unlock(priv->mainloop); return latency == (pa_usec_t) -1 ? 0 : latency / 1000000.0; } -// Return the current latency in seconds -static double get_delay(struct ao *ao) +static void audio_get_state(struct ao *ao, struct mp_pcm_state *state) { struct priv *priv = ao->priv; + + pa_threaded_mainloop_lock(priv->mainloop); + + size_t space = pa_stream_writable_size(priv->stream); + state->free_samples = space == (size_t)-1 ? 0 : space / ao->sstride; + + state->queued_samples = ao->device_buffer - state->free_samples; // dunno + if (priv->cfg_latency_hacks) { - return get_delay_hackfixed(ao); + state->delay = get_delay_hackfixed(ao); } else { - return get_delay_pulse(ao); + state->delay = get_delay_pulse(ao); } + + state->underrun = priv->underrun; + priv->underrun = false; + + pa_threaded_mainloop_unlock(priv->mainloop); + + // Otherwise, PA will keep hammering us for underruns (which it does instead + // of stopping the stream automatically). + if (state->underrun) + cork(ao, true); } /* A callback function that is called when the @@ -812,13 +789,10 @@ const struct ao_driver audio_out_pulse = { .init = init, .uninit = uninit, .reset = reset, - .get_space = get_space, - .play = play, - .get_delay = get_delay, - .pause = pause, - .resume = resume, - .wait = wait_audio, - .wakeup = wakeup, + .get_state = audio_get_state, + .write = audio_write, + .start = start, + .set_pause = set_pause, .hotplug_init = hotplug_init, .hotplug_uninit = hotplug_uninit, .list_devs = list_devs, diff --git a/audio/out/ao_sdl.c b/audio/out/ao_sdl.c index c1c09b8c92..20458d0b2f 100644 --- a/audio/out/ao_sdl.c +++ b/audio/out/ao_sdl.c @@ -188,7 +188,7 @@ static void reset(struct ao *ao) priv->paused = 1; } -static void resume(struct ao *ao) +static void start(struct ao *ao) { struct priv *priv = ao->priv; if (priv->paused) @@ -204,7 +204,7 @@ const struct ao_driver audio_out_sdl = { .init = init, .uninit = uninit, .reset = reset, - .resume = resume, + .start = start, .priv_size = sizeof(struct priv), .priv_defaults = &(const struct priv) { .buflen = 0, // use SDL default diff --git a/audio/out/ao_wasapi.c b/audio/out/ao_wasapi.c index 1187ab3140..da4a937c7d 100644 --- a/audio/out/ao_wasapi.c +++ b/audio/out/ao_wasapi.c @@ -493,7 +493,7 @@ const struct ao_driver audio_out_wasapi = { .uninit = uninit, .control = control, .reset = audio_reset, - .resume = audio_resume, + .start = audio_resume, .list_devs = wasapi_list_devs, .hotplug_init = hotplug_init, .hotplug_uninit = hotplug_uninit, diff --git a/audio/out/buffer.c b/audio/out/buffer.c index d1563452d1..0e92ef5911 100644 --- a/audio/out/buffer.c +++ b/audio/out/buffer.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -39,9 +40,14 @@ #include "misc/ring.h" struct buffer_state { + // Buffer and AO pthread_mutex_t lock; pthread_cond_t wakeup; + // Playthread sleep + pthread_mutex_t pt_lock; + pthread_cond_t pt_wakeup; + // Access from AO driver's thread only. char *convert_buffer; @@ -58,31 +64,64 @@ struct buffer_state { int64_t end_time_us; // absolute output time of last played sample int64_t underflow; // number of samples missing since last check - bool need_wakeup; bool initial_unblocked; - // "Push" AOs only (AOs with driver->play). + // "Push" AOs only (AOs with driver->write). bool still_playing; - double expected_end_time; - bool wait_on_ao; + bool hw_paused; // driver->set_pause() was used successfully + bool recover_pause; // non-hw_paused: needs to recover delay + bool draining; + bool had_underrun; + bool ao_wait_low_buffer; + struct mp_pcm_state prepause_state; pthread_t thread; // thread shoveling data to AO bool thread_valid; // thread is running - bool terminate; // exit thread struct mp_aframe *temp_buf; - int wakeup_pipe[2]; + // --- protected by pt_lock + bool need_wakeup; + bool terminate; // exit thread }; static void *playthread(void *arg); -// lock must be held -static void wakeup_playthread(struct ao *ao) +void ao_wakeup_playthread(struct ao *ao) { struct buffer_state *p = ao->buffer_state; - if (ao->driver->wakeup) - ao->driver->wakeup(ao); + pthread_mutex_lock(&p->pt_lock); p->need_wakeup = true; - pthread_cond_signal(&p->wakeup); + pthread_cond_broadcast(&p->pt_wakeup); + pthread_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) { + *state = p->prepause_state; + return; + } + + *state = (struct mp_pcm_state){ + .free_samples = -1, + .queued_samples = -1, + .delay = -1, + .underrun = false, + }; + ao->driver->get_state(ao, state); + + if (state->underrun) { + p->had_underrun = true; + if (p->draining) { + MP_VERBOSE(ao, "underrun signaled for audio end\n"); + p->still_playing = false; + pthread_cond_broadcast(&p->wakeup); + } else { + ao_add_events(ao, AO_EVENT_UNDERRUN); + } + } } static int unlocked_get_space(struct ao *ao) @@ -93,9 +132,11 @@ static int unlocked_get_space(struct ao *ao) // The following code attempts to keep the total buffered audio at // ao->buffer in order to improve latency. - if (ao->driver->play && ao->driver->get_space) { + if (ao->driver->write) { + struct mp_pcm_state state; + get_dev_state(ao, &state); int align = af_format_sample_alignment(ao->format); - int device_space = ao->driver->get_space(ao); + int device_space = MPMAX(state.free_samples, 0); int device_buffered = ao->device_buffer - device_space; int soft_buffered = mp_ring_size(p->buffers[0]) / ao->sstride - space; // The extra margin helps avoiding too many wakeups if the AO is fully @@ -113,8 +154,6 @@ static int unlocked_get_space(struct ao *ao) return space; } -// 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) { struct buffer_state *p = ao->buffer_state; @@ -124,13 +163,6 @@ int ao_get_space(struct ao *ao) return space; } -// Queue the given audio data. Start playback if it hasn't started yet. Return -// the number of samples that was accepted (the core will try to queue the rest -// again later). Should never block. -// data: start pointer for each plane. If the audio data is packed, only -// data[0] is valid, otherwise there is a plane for each channel. -// samples: size of the audio data (see ao->sstride) -// flags: currently AOPLAY_FINAL_CHUNK can be set int ao_play(struct ao *ao, void **data, int samples, int flags) { struct buffer_state *p = ao->buffer_state; @@ -147,7 +179,7 @@ int ao_play(struct ao *ao, void **data, int samples, int flags) } p->paused = false; - p->final_chunk = write_samples == samples && (flags & AOPLAY_FINAL_CHUNK); + p->final_chunk = write_samples == samples && (flags & PLAYER_FINAL_CHUNK); if (p->underflow) MP_DBG(ao, "Audio underrun by %lld samples.\n", (long long)p->underflow); @@ -156,17 +188,19 @@ int ao_play(struct ao *ao, void **data, int samples, int flags) if (write_samples) { p->playing = true; p->still_playing = true; - p->expected_end_time = 0; + p->draining = false; - if (!ao->driver->play && !p->streaming) { + if (!ao->driver->write && !p->streaming) { p->streaming = true; - ao->driver->resume(ao); + ao->driver->start(ao); } - wakeup_playthread(ao); } pthread_mutex_unlock(&p->lock); + if (write_samples) + ao_wakeup_playthread(ao); + return write_samples; } @@ -195,7 +229,7 @@ int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_us) if (full_bytes > bytes && !p->final_chunk) { p->underflow += (full_bytes - bytes) / ao->sstride; - ao_underrun_event(ao); + ao_add_events(ao, AO_EVENT_UNDERRUN); } if (bytes > 0) @@ -278,10 +312,11 @@ static double unlocked_get_delay(struct ao *ao) struct buffer_state *p = ao->buffer_state; double driver_delay = 0; - if (ao->driver->get_delay) - driver_delay = ao->driver->get_delay(ao); - - if (!ao->driver->play) { + 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_us; int64_t now = mp_time_us(); driver_delay += MPMAX(0, (end - now) / (1000.0 * 1000.0)); @@ -290,11 +325,6 @@ static double unlocked_get_delay(struct ao *ao) return mp_ring_buffered(p->buffers[0]) / (double)ao->bps + driver_delay; } -// 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. double ao_get_delay(struct ao *ao) { struct buffer_state *p = ao->buffer_state; @@ -305,11 +335,10 @@ double ao_get_delay(struct ao *ao) return delay; } -// Stop playback and empty buffers. Essentially go back to the state after -// ao->init(). void ao_reset(struct ao *ao) { struct buffer_state *p = ao->buffer_state; + bool wakeup = false; pthread_mutex_lock(&p->lock); @@ -322,58 +351,82 @@ void ao_reset(struct ao *ao) } p->paused = false; p->playing = false; - if (p->still_playing) - wakeup_playthread(ao); + p->recover_pause = false; + p->hw_paused = false; + wakeup = p->still_playing || p->draining; + p->draining = false; p->still_playing = false; p->end_time_us = 0; atomic_fetch_and(&ao->events_, ~(unsigned int)AO_EVENT_UNDERRUN); pthread_mutex_unlock(&p->lock); + + if (wakeup) + ao_wakeup_playthread(ao); } -// Pause playback. Keep the current buffer. ao_get_delay() must return the -// same value as before pausing. void ao_pause(struct ao *ao) { struct buffer_state *p = ao->buffer_state; + bool wakeup = false; pthread_mutex_lock(&p->lock); if (p->playing && !p->paused) { if (p->streaming && !ao->stream_silence) { - if (ao->driver->pause) { - ao->driver->pause(ao); + 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; + } } else if (ao->driver->reset) { ao->driver->reset(ao); p->streaming = false; } } p->paused = true; - wakeup_playthread(ao); + wakeup = true; } pthread_mutex_unlock(&p->lock); + + if (wakeup) + ao_wakeup_playthread(ao); } -// Resume playback. Play the remaining buffer. If the driver doesn't support -// pausing, it has to work around this and e.g. use ao_play_silence() to fill -// the lost audio. void ao_resume(struct ao *ao) { struct buffer_state *p = ao->buffer_state; + bool wakeup = false; pthread_mutex_lock(&p->lock); if (p->playing && p->paused) { - if (ao->driver->resume && (!p->streaming || ao->driver->play)) - ao->driver->resume(ao); + if (ao->driver->write) { + if (p->streaming && p->hw_paused) { + ao->driver->set_pause(ao, false); + } else { + p->recover_pause = true; + } + p->hw_paused = false; + } else { + if (!p->streaming) + ao->driver->start(ao); + p->streaming = true; + } p->paused = false; - p->expected_end_time = 0; - wakeup_playthread(ao); + wakeup = true; } pthread_mutex_unlock(&p->lock); + + if (wakeup) + ao_wakeup_playthread(ao); } bool ao_eof_reached(struct ao *ao) @@ -382,7 +435,7 @@ bool ao_eof_reached(struct ao *ao) pthread_mutex_lock(&p->lock); bool eof = !p->playing; - if (ao->driver->play) { + if (ao->driver->write) { eof |= !p->still_playing; } else { // For simplicity, ignore the latency. Otherwise, we would have to run @@ -401,37 +454,51 @@ void ao_drain(struct ao *ao) pthread_mutex_lock(&p->lock); p->final_chunk = true; - wakeup_playthread(ao); - double left = 0; - if (p->playing && !p->paused && !ao->driver->encode) - left = mp_ring_buffered(p->buffers[0]) / (double)ao->bps * 1e6; - pthread_mutex_unlock(&p->lock); + while (!p->paused && p->still_playing && !p->had_underrun) { + if (ao->driver->write) { + if (p->draining) { + // Wait for EOF signal from AO. + pthread_cond_wait(&p->wakeup, &p->lock); + } else { + p->draining = true; + MP_VERBOSE(ao, "waiting for draining...\n"); + pthread_mutex_unlock(&p->lock); + ao_wakeup_playthread(ao); + pthread_mutex_lock(&p->lock); + } + } else { + double left = mp_ring_buffered(p->buffers[0]) / (double)ao->bps * 1e6; + pthread_mutex_unlock(&p->lock); + + if (left > 0) { + // Wait for lower bound. + mp_sleep_us(left); + // And then poll for actual end. No other way. + // Limit to arbitrary ~250ms max. waiting for robustness. + int64_t max = mp_time_us() + 250000; + while (mp_time_us() < max && !ao_eof_reached(ao)) + mp_sleep_us(1); + } else { + p->still_playing = false; + } - if (left > 0) { - // Wait for lower bound. - mp_sleep_us(left); - // And then poll for actual end. (Unfortunately, this code considers - // audio APIs which do not want you to use mutexes in the audio - // callback, and an extra semaphore would require slightly more effort.) - // Limit to arbitrary ~250ms max. waiting for robustness. - int64_t max = mp_time_us() + 250000; - while (mp_time_us() < max && !ao_eof_reached(ao)) - mp_sleep_us(1); + pthread_mutex_lock(&p->lock); + } } + pthread_mutex_unlock(&p->lock); ao_reset(ao); } -// Uninitialize and destroy the AO. Remaining audio must be dropped. void ao_uninit(struct ao *ao) { struct buffer_state *p = ao->buffer_state; if (p->thread_valid) { - pthread_mutex_lock(&p->lock); + pthread_mutex_lock(&p->pt_lock); p->terminate = true; - wakeup_playthread(ao); - pthread_mutex_unlock(&p->lock); + pthread_cond_broadcast(&p->pt_wakeup); + pthread_mutex_unlock(&p->pt_lock); pthread_join(p->thread, NULL); p->thread_valid = false; @@ -443,15 +510,12 @@ void ao_uninit(struct ao *ao) talloc_free(p->convert_buffer); talloc_free(p->temp_buf); - for (int n = 0; n < 2; n++) { - int h = p->wakeup_pipe[n]; - if (h >= 0) - close(h); - } - pthread_cond_destroy(&p->wakeup); pthread_mutex_destroy(&p->lock); + pthread_cond_destroy(&p->pt_wakeup); + pthread_mutex_destroy(&p->pt_lock); + talloc_free(ao); } @@ -464,22 +528,22 @@ bool init_buffer_post(struct ao *ao) { struct buffer_state *p = ao->buffer_state; - if (!ao->driver->play) - assert(ao->driver->resume); + assert(ao->driver->start); + if (ao->driver->write) { + assert(ao->driver->reset); + assert(ao->driver->get_state); + } for (int n = 0; n < ao->num_planes; n++) p->buffers[n] = mp_ring_new(ao, ao->buffer * ao->sstride); mpthread_mutex_init_recursive(&p->lock); pthread_cond_init(&p->wakeup, NULL); - mp_make_wakeup_pipe(p->wakeup_pipe); - if (ao->driver->play) { - if (ao->device_buffer <= 0) { - MP_FATAL(ao, "Couldn't probe device buffer size.\n"); - return false; - } + pthread_mutex_init(&p->pt_lock, NULL); + pthread_cond_init(&p->pt_wakeup, NULL); + if (ao->driver->write) { p->thread_valid = true; if (pthread_create(&p->thread, NULL, playthread, ao)) { p->thread_valid = false; @@ -487,7 +551,7 @@ bool init_buffer_post(struct ao *ao) } } else { if (ao->stream_silence) { - ao->driver->resume(ao); + ao->driver->start(ao); p->streaming = true; } } @@ -521,13 +585,20 @@ static bool realloc_buf(struct ao *ao, int samples) static void ao_play_data(struct ao *ao) { struct buffer_state *p = ao->buffer_state; - int space = ao->driver->get_space(ao); + + if (p->had_underrun) { + MP_VERBOSE(ao, "recover underrun\n"); + ao->driver->reset(ao); + p->streaming = false; + p->had_underrun = false; + } + + struct mp_pcm_state state; + get_dev_state(ao, &state); + // Round free space to period sizes to reduce number of write() calls. + int space = state.free_samples / ao->period_size * ao->period_size; bool play_silence = p->paused || (ao->stream_silence && !p->still_playing); space = MPMAX(space, 0); - // Most AOs want period-size aligned audio, and preferably as much as - // possible in one go, so the audio data is "linearized" into this buffer. - if (space % ao->period_size) - MP_ERR(ao, "Audio device reports unaligned available buffer size.\n"); if (!realloc_buf(ao, space)) { MP_ERR(ao, "Failed to allocate buffer.\n"); return; @@ -539,62 +610,58 @@ static void ao_play_data(struct ao *ao) samples = space; if (play_silence) samples = space; - samples = ao_read_data(ao, planes, samples, 0); + 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); + } else { + samples = ao_read_data(ao, planes, samples, 0); + } if (play_silence) samples = space; // ao_read_data() sets remainder to silent - int max = samples; - int flags = 0; - if (p->final_chunk && samples < space) { - flags |= AOPLAY_FINAL_CHUNK; - } else { - samples = samples / ao->period_size * ao->period_size; + + bool is_eof = p->final_chunk && samples < space; + bool ok = true; + int written = 0; + if (samples) { + p->draining = is_eof; + MP_STATS(ao, "start ao fill"); + ok = ao->driver->write(ao, planes, samples); + MP_STATS(ao, "end ao fill"); } - MP_STATS(ao, "start ao fill"); - int r = 0; - if (samples) - r = ao->driver->play(ao, planes, samples, flags); - MP_STATS(ao, "end ao fill"); - if (r > samples) { - MP_ERR(ao, "Audio device returned nonsense value.\n"); - r = samples; - } else if (r < 0) { + + if (!ok) MP_ERR(ao, "Error writing audio to device.\n"); - } else if (r != samples) { -