diff options
author | wm4 <wm4@nowhere> | 2020-05-31 15:00:35 +0200 |
---|---|---|
committer | wm4 <wm4@nowhere> | 2020-06-01 01:08:16 +0200 |
commit | d27ad9654218463694093697e3d09f8983b4ccf3 (patch) | |
tree | 54d889bbed1b32b2a702fee6825c66ff6fb60c33 /audio/out | |
parent | d448dd5bf26408ccced1bd6df8f9eaf62a370c8e (diff) | |
download | mpv-d27ad9654218463694093697e3d09f8983b4ccf3.tar.bz2 mpv-d27ad9654218463694093697e3d09f8983b4ccf3.tar.xz |
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.
Diffstat (limited to 'audio/out')
-rw-r--r-- | audio/out/ao.c | 12 | ||||
-rw-r--r-- | audio/out/ao.h | 2 | ||||
-rw-r--r-- | audio/out/ao_alsa.c | 369 | ||||
-rw-r--r-- | audio/out/ao_audiotrack.c | 2 | ||||
-rw-r--r-- | audio/out/ao_audiounit.m | 2 | ||||
-rw-r--r-- | audio/out/ao_coreaudio.c | 2 | ||||
-rw-r--r-- | audio/out/ao_coreaudio_exclusive.c | 2 | ||||
-rw-r--r-- | audio/out/ao_jack.c | 4 | ||||
-rw-r--r-- | audio/out/ao_lavc.c | 57 | ||||
-rw-r--r-- | audio/out/ao_null.c | 89 | ||||
-rw-r--r-- | audio/out/ao_openal.c | 90 | ||||
-rw-r--r-- | audio/out/ao_opensles.c | 2 | ||||
-rw-r--r-- | audio/out/ao_pcm.c | 40 | ||||
-rw-r--r-- | audio/out/ao_pulse.c | 160 | ||||
-rw-r--r-- | audio/out/ao_sdl.c | 4 | ||||
-rw-r--r-- | audio/out/ao_wasapi.c | 2 | ||||
-rw-r--r-- | audio/out/buffer.c | 497 | ||||
-rw-r--r-- | audio/out/internal.h | 118 |
18 files changed, 633 insertions, 821 deletions
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 = u |