summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2020-05-31 15:00:35 +0200
committerwm4 <wm4@nowhere>2020-06-01 01:08:16 +0200
commitd27ad9654218463694093697e3d09f8983b4ccf3 (patch)
tree54d889bbed1b32b2a702fee6825c66ff6fb60c33
parentd448dd5bf26408ccced1bd6df8f9eaf62a370c8e (diff)
downloadmpv-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.
-rw-r--r--DOCS/man/ao.rst2
-rw-r--r--audio/out/ao.c12
-rw-r--r--audio/out/ao.h2
-rw-r--r--audio/out/ao_alsa.c369
-rw-r--r--audio/out/ao_audiotrack.c2
-rw-r--r--audio/out/ao_audiounit.m2
-rw-r--r--audio/out/ao_coreaudio.c2
-rw-r--r--audio/out/ao_coreaudio_exclusive.c2
-rw-r--r--audio/out/ao_jack.c4
-rw-r--r--audio/out/ao_lavc.c57
-rw-r--r--audio/out/ao_null.c89
-rw-r--r--audio/out/ao_openal.c90
-rw-r--r--audio/out/ao_opensles.c2
-rw-r--r--audio/out/ao_pcm.c40
-rw-r--r--audio/out/ao_pulse.c160
-rw-r--r--audio/out/ao_sdl.c4
-rw-r--r--audio/out/ao_wasapi.c2
-rw-r--r--audio/out/buffer.c497
-rw-r--r--audio/out/internal.h118
-rw-r--r--player/audio.c2
20 files changed, 635 insertions, 823 deletions
diff --git a/DOCS/man/ao.rst b/DOCS/man/ao.rst
index 5308a7175b..0b1af44e2b 100644
--- a/DOCS/man/ao.rst
+++ b/DOCS/man/ao.rst
@@ -97,7 +97,7 @@ Available audio output drivers are:
exclusive mode (bypasses the sound server).
``openal``
- OpenAL audio output driver
+ OpenAL audio output driver. This is broken and does not work.
``--openal-num-buffers=<2-128>``
Specify the number of audio buffers to use. Lower values are better for
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 <stddef.h>
#include <pthread.h>
#include <inttypes.h>
+#include <math.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
@@ -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) {
- MP_ERR(ao, "Audio device returned broken buffer state (sent %d samples, "
- "got %d samples, %d period%s)! Discarding audio.\n", samples, r,
- ao->period_size, flags & AOPLAY_FINAL_CHUNK ? " final" : "");
- }
- r = MPMAX(r, 0);
- // Probably can't copy the rest of the buffer due to period alignment.
- bool stuck_eof = r <= 0 && space >= max && samples > 0;
- if ((flags & AOPLAY_FINAL_CHUNK) && stuck_eof) {
- MP_ERR(ao, "Audio output driver seems to ignore AOPLAY_FINAL_CHUNK.\n");
- r = max;
+
+ if (samples > 0 && ok) {
+ written = samples;
+ if (!p->streaming) {
+ MP_VERBOSE(ao, "starting AO\n");
+ ao->driver->start(ao);
+ p->streaming = true;
+ }
}
- if (r > 0) {
- p->expected_end_time = 0;
- p->streaming = true;
+
+ if (p->draining && p->still_playing && ao->untimed) {
+ p->still_playing = false;
+ pthread_cond_broadcast(&p->wakeup);
}
- // Nothing written, but more input data than space - this must mean the
- // AO's get_space() doesn't do period alignment correctly.
- bool stuck = r == 0 && max >= space && space > 0;
- if (stuck)
- MP_ERR(ao, "Audio output is reporting incorrect buffer status.\n");
+
// Wait until space becomes available. Also wait if we actually wrote data,
// so the AO wakes us up properly if it needs more data.
- p->wait_on_ao = space == 0 || r > 0 || stuck;
- p->still_playing |= r > 0 && !play_silence;
+ p->ao_wait_low_buffer = space == 0 || written > 0 || p->draining;
+ p->still_playing |= samples > 0 && !play_silence;
+
// If we just filled the AO completely (r == space), don't refill for a
// while. Prevents wakeup feedback with byte-granular AOs.
int needed = unlocked_get_space(ao);
- bool more = needed >= (r == space ? ao->device_buffer / 4 : 1) && !stuck &&
- !(flags & AOPLAY_FINAL_CHUNK);
+ bool more = needed >= (written == space ? ao->device_buffer / 4 : 1) &&
+ !p->final_chunk;
if (more)
ao->wakeup_cb(ao->wakeup_ctx); // request more data
- if (!samples && space && !ao->driver->reports_underruns && p->still_playing)
- ao_underrun_event(ao);
- MP_TRACE(ao, "in=%d flags=%d space=%d r=%d wa/pl=%d/%d needed=%d more=%d\n",
- max, flags, space, r, p->wait_on_ao, p->still_playing, needed, more);
+ MP_TRACE(ao, "in=%d eof=%d space=%d r=%d wa/pl/dr=%d/%d/%d needed=%d more=%d\n",
+ samples, is_eof, space, written, p->ao_wait_low_buffer,
+ p->still_playing, p->draining, needed, more);
}
static void *playthread(void *arg)
@@ -602,151 +669,53 @@ static void *playthread(void *arg)
struct ao *ao = arg;
struct buffer_state *p = ao->buffer_state;
mpthread_set_name("ao");
- pthread_mutex_lock(&p->lock);
- while (!p->terminate) {
+ while (1) {
+ pthread_mutex_lock(&p->lock);
+
bool blocked = ao->driver->initially_blocked && !p->initial_unblocked;
- bool playing = (!p->paused || ao->stream_silence) && !blocked;
- if (playing)
+ bool playing = !p->paused && (p->playing || ao->stream_silence);
+ if (playing && !blocked)
ao_play_data(ao);
- if (!p->need_wakeup) {
- MP_STATS(ao, "start audio wait");
- if (!p->wait_on_ao || !playing) {
- // Avoid busy waiting, because the audio API will still report
- // that it needs new data, even if we're not ready yet, or if
- // get_space() decides that the amount of audio buffered in the
- // device is enough, and p->buffer can be empty.
- // The most important part is that the decoder is woken up, so
- // that the decoder will wake up us in turn.
- MP_TRACE(ao, "buffer inactive.\n");
-
- bool was_playing = p->still_playing;
- double timeout = -1;
- if (p->still_playing && !p->paused && p->final_chunk &&
- !mp_ring_buffered(p->buffers[0]))
- {
- double now = mp_time_sec();
- if (!p->expected_end_time)
- p->expected_end_time = now + unlocked_get_delay(ao);
- if (p->expected_end_time < now) {
- p->still_playing = false;
- } else {
- timeout = p->expected_end_time - now;
- }
- }
+ // Wait until the device wants us to write more data to it.
+ // Fallback to guessing.
+ double timeout = INFINITY;
+ if (p->ao_wait_low_buffer) {
+ struct mp_pcm_state state;
+ get_dev_state(ao, &state);
+ timeout = state.delay * 0.25; // wake up if 25% played
+ p->ao_wait_low_buffer = false;
+ // If the AO doesn't tell us, we need to guess.
+ if (p->draining)
+ timeout = MPMAX(timeout, 0.1);
+ }
- if (was_playing && !p->still_playing)
- ao->wakeup_cb(ao->wakeup_ctx);
- pthread_cond_signal(&p->wakeup); // for draining
+ pthread_mutex_unlock(&p->lock);
- if (p->still_playing && timeout > 0) {
- struct timespec ts = mp_rel_time_to_timespec(timeout);
- pthread_cond_timedwait(&p->wakeup, &p->lock, &ts);
- } else {
- pthread_cond_wait(&p->wakeup, &p->lock);
- }
- } else {
- // Wait until the device wants us to write more data to it.
- if (!ao->driver->wait || ao->driver->wait(ao, &p->lock) < 0) {
- // Fallback to guessing.
- double timeout = 0;
- if (ao->driver->get_delay)
- timeout = ao->driver->get_delay(ao);
- timeout *= 0.25; // wake up if 25% played
- if (!p->need_wakeup) {
- struct timespec ts = mp_rel_time_to_timespec(timeout);
- pthread_cond_timedwait(&p->wakeup, &p->lock, &ts);
- }
- }
- }
+ pthread_mutex_lock(&p->pt_lock);
+ if (p->terminate) {
+ pthread_mutex_unlock(&p->pt_lock);
+ break;
+ }
+ if (!p->need_wakeup) {
+ MP_STATS(ao, "start audio wait");
+ struct timespec ts = mp_rel_time_to_timespec(timeout);
+ pthread_cond_timedwait(&p->pt_wakeup, &p->pt_lock, &ts);
MP_STATS(ao, "end audio wait");
}
p->need_wakeup = false;
+ pthread_mutex_unlock(&p->pt_lock);
}
- pthread_mutex_unlock(&p->lock);
return NULL;
}
void ao_unblock(struct ao *ao)
{
- if (ao->driver->play) {
+ if (ao->driver->write) {
struct buffer_state *p = ao->buffer_state;
pthread_mutex_lock(&p->lock);
- p->need_wakeup = true;
p->initial_unblocked = true;
- wakeup_playthread(ao);
- pthread_cond_signal(&p->wakeup);
pthread_mutex_unlock(&p->lock);
+ ao_wakeup_playthread(ao);
}
}
-
-// Must be called locked.
-int ao_play_silence(struct ao *ao, int samples)
-{
- assert(ao->driver->play);
-
- struct buffer_state *p = ao->buffer_state;
-
- if (!realloc_buf(ao, samples) || !ao->driver->play)
- return 0;
-
- void **planes = (void **)mp_aframe_get_data_rw(p->temp_buf);
- assert(planes);
-
- for (int n = 0; n < ao->num_planes; n++)
- af_fill_silence(planes[n], ao->sstride * samples, ao->format);
-
- return ao->driver->play(ao, planes, samples, 0);
-}
-
-#ifndef __MINGW32__
-
-#include <poll.h>
-
-#define MAX_POLL_FDS 20
-
-// Call poll() for the given fds. This will extend the given fds with the
-// wakeup pipe, so ao_wakeup_poll() will basically interrupt this function.
-// Unlocks the lock temporarily.
-// Returns <0 on error, 0 on success, 1 if the caller should return immediately.
-int ao_wait_poll(struct ao *ao, struct pollfd *fds, int num_fds,
- pthread_mutex_t *lock)
-{
- struct buffer_state *p = ao->buffer_state;
- assert(ao->driver->play);
- assert(&p->lock == lock);
-
- if (num_fds >= MAX_POLL_FDS || p->wakeup_pipe[0] < 0)
- return -1;
-
- struct pollfd p_fds[MAX_POLL_FDS];
- memcpy(p_fds, fds, num_fds * sizeof(p_fds[0]));
- p_fds[num_fds] = (struct pollfd){
- .fd = p->wakeup_pipe[0],
- .events = POLLIN,
- };
-
- pthread_mutex_unlock(&p->lock);
- int r = poll(p_fds, num_fds + 1, -1);
- r = r < 0 ? -errno : 0;
- pthread_mutex_lock(&p->lock);
-
- memcpy(fds, p_fds, num_fds * sizeof(fds[0]));
- bool wakeup = false;
- if (p_fds[num_fds].revents & POLLIN) {
- wakeup = true;
- // might "drown" some wakeups, but that's ok for our use-case
- mp_flush_wakeup_pipe(p->wakeup_pipe[0]);
- }
- return (r >= 0 || r == -EINTR) ? wakeup : -1;
-}
-
-void ao_wakeup_poll(struct ao *ao)
-{
- assert(ao->driver->play);
- struct buffer_state *p = ao->buffer_state;
-
- (void)write(p->wakeup_pipe[1], &(char){0}, 1);
-}
-
-#endif
diff --git a/audio/out/internal.h b/audio/out/internal.h
index 71f98304be..15ffd82e57 100644
--- a/audio/out/internal.h
+++ b/audio/out/internal.h
@@ -48,9 +48,7 @@ struct ao {
int init_flags; // AO_INIT_* flags
bool stream_silence; // if audio inactive, just play silence
- // Set by the driver on init. This is typically the period size, and the
- // smallest unit the driver will accept in one piece (although if
- // AOPLAY_FINAL_CHUNK is set, the driver must accept everything).
+ // Set by the driver on init.
// This value is in complete samples (i.e. 1 for stereo means 1 sample
// for both channels each).
// Used for push based API only.
@@ -82,6 +80,18 @@ struct ao {
void init_buffer_pre(struct ao *ao);
bool init_buffer_post(struct ao *ao);
+struct mp_pcm_state {
+ // Note: free_samples+queued_samples <= ao->device_buffer; the sum may be
+ // less if the audio API can report partial periods played, while
+ // free_samples should be period-size aligned.
+ int free_samples; // number of free space in ring buffer
+ int queued_samples; // number of samples to play in ring buffer
+ double delay; // total latency in seconds (includes queued_samples)
+ bool underrun; // if in underrun state (signals both accidental
+ // underruns and normal playback end); cleared by AO
+ // driver on reset() calls
+};
+
/* Note:
*
* In general, there are two types of audio drivers:
@@ -89,38 +99,32 @@ bool init_buffer_post(struct ao *ao);
* b) pull callback based (the audio API calls a callback to get audio)
*
* The ao.c code can handle both. It basically implements two audio paths
- * and provides a uniform API for them. If ao_driver->play is NULL, it assumes
+ * and provides a uniform API for them. If ao_driver->write is NULL, it assumes
* that the driver uses a callback based audio API, otherwise push based.
*
* Requirements:
- * a) ->play is called to queue audio. push.c creates a thread to regularly
- * refill audio device buffers with ->play, but all driver functions are
- * always called under an exclusive lock.
- * Mandatory:
+ * a+b) Mandatory for both types:
* init
* uninit
+ * start
+ * Optional for both types:
+ * control
+ * a) ->write is called to queue audio. push.c creates a thread to regularly
+ * refill audio device buffers with ->write, but all driver functions are
+ * always called under an exclusive lock.
+ * Mandatory:
* reset
- * get_space
- * play
- * get_delay
- * pause
- * resume
+ * write
+ * get_state
* Optional:
- * control
- * wait
- * wakeup
- * b) ->play must be NULL. ->resume must be provided, and should make the
+ * set_pause
+ * b) ->write must be NULL. ->start must be provided, and should make the
* audio API start calling the audio callback. Your audio callback should
* in turn call ao_read_data() to get audio data. Most functions are
* optional and will be emulated if missing (e.g. pausing is emulated as
- * silence). ->get_delay and ->get_space are never called.
- * Mandatory:
- * init
- * uninit
- * resume (starts the audio callback)
+ * silence).
* Also, the following optional callbacks can be provided:
- * reset (stops the audio callback, resume() restarts it)
- * control
+ * reset (stops the audio callback, start() restarts it)
*/
struct ao_driver {
// If true, use with encoding only.
@@ -130,16 +134,9 @@ struct ao_driver {
// Description shown with --ao=help.
const char *description;
// This requires waiting for a AO_EVENT_INITIAL_UNBLOCK event before the
- // first play() call is done. Encode mode uses this, and push mode
+ // first write() call is done. Encode mode uses this, and push mode
// respects it automatically (don't use with pull mode).
bool initially_blocked;
- // Whether underruns are strictly _always_ reported via ao_underrun_event().
- // Do not set this to true if underruns may be missed in some way. If the
- // AO can't guarantee to play silence after underruns, it may be better not
- // to set this.
- // If not set, the generic buffer code will report an underrun if the buffer
- // becomes empty.
- bool reports_underruns;
// Init the device using ao->format/ao->channels/ao->samplerate. If the
// device doesn't accept these parameters, you can attempt to negotiate
// fallback parameters, and set the ao format fields accordingly.
@@ -147,36 +144,29 @@ struct ao_driver {
// Optional. See ao_control() etc. in ao.c
int (*control)(struct ao *ao, enum aocontrol cmd, void *arg);
void (*uninit)(struct ao *ao);
- // push based: see ao_reset()
- // pull based: stop the audio callback
+ // Stop all audio playback, clear buffers, back to state after init().
+ // Optional for pull AOs.
void (*reset)(struct ao *ao);
- // push based: see ao_pause()
- void (*pause)(struct ao *ao);
- // push based: see ao_resume()
+ // push based: set pause state. Only called after start() and before reset().
+ // returns success (this is intended for paused=true; if it
+ // returns false, playback continues; unpausing always works)
+ bool (*set_pause)(struct ao *ao, bool paused);
// pull based: start the audio callback
- void (*resume)(struct ao *ao);
- // push based: see ao_play()
- int (*get_space)(struct ao *ao);
- // push based: see ao_play()
- int (*play)(struct ao *ao, void **data, int samples, int flags);
- // push based: see ao_get_delay()
- double (*get_delay)(struct ao *ao);
- // Optional. Return true if audio has stopped in any way.
- bool (*get_eof)(struct ao *ao);
- // Wait until the audio buffer needs to be refilled. The lock is the
- // internal mutex usually protecting the internal AO state (and used to
- // protect driver calls), and must be temporarily unlocked while waiting.
- // ->wakeup will be called (with lock held) if the wait should be canceled.
- // Returns 0 on success, -1 on error.
- // Optional; if this is not provided, generic code using audio timing is
- // used to estimate when the AO needs to be refilled.
- // Warning: it's only called if the feed thread truly needs to know when
- // the audio thread takes data again. Often, it will just copy
- // the complete soft-buffer to the AO, and then wait for the
- // decoder instead. Don't do necessary work in this callback.
- int (*wait)(struct ao *ao, pthread_mutex_t *lock);
- // In combination with wait(). Lock may or may not be held.
- void (*wakeup)(struct ao *ao);
+ // push based: start playing queued data
+ // AO should call ao_wakeup_playthread() if a period boundary
+ // is crossed, or playback stops due to external reasons
+ // (including underruns or device removal)
+ void (*start)(struct ao *ao);
+ // push based: queue new data. This won't try to write more data than the
+ // reported free space (samples <= mp_pcm_state.free_samples).
+ // This must NOT start playback. start() does that, and write() may be
+ // called multiple times before start() is called. It may also happen that
+ // reset() is called to discard the buffer. start() without write() will
+ // immediately reported an underrun.
+ // Return false on failure.
+ bool (*write)(struct ao *ao, void **data, int samples);
+ // push based: return mandatory stream information
+ void (*get_state)(struct ao *ao, struct mp_pcm_state *state);
// Return the list of devices currently available in the system. Use
// ao_device_list_add() to add entries. The selected device will be set as
@@ -204,13 +194,7 @@ struct ao_driver {
// These functions can be called by AOs.
-int ao_play_silence(struct ao *ao, int samples);
int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_us);
-struct pollfd;
-int ao_wait_poll(struct ao *ao, struct pollfd *fds, int num_fds,
- pthread_mutex_t *lock);
-void ao_wakeup_poll(struct ao *ao);
-bool ao_underrun_event(struct ao *ao);
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
struct mp_chmap *map);
@@ -238,6 +222,8 @@ bool ao_can_convert_inplace(struct ao_convert_fmt *fmt);
bool ao_need_conversion(struct ao_convert_fmt *fmt);
void ao_convert_inplace(struct ao_convert_fmt *fmt, void **data, int num_samples);
+void ao_wakeup_playthread(struct ao *ao);
+
int ao_read_data_converted(struct ao *ao, struct ao_convert_fmt *fmt,
void **data, int samples, int64_t out_time_us);
diff --git a/player/audio.c b/player/audio.c
index c6a0af4e54..e541506778 100644
--- a/player/audio.c
+++ b/player/audio.c
@@ -971,7 +971,7 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
// do it if gapless is forced, mostly for testing).
if (audio_eof && (!opts->gapless_audio ||
(opts->gapless_audio <= 0 && mpctx->video_status != STATUS_EOF)))
- playflags |= AOPLAY_FINAL_CHUNK;
+ playflags |= PLAYER_FINAL_CHUNK;
uint8_t **planes;
int samples;