summaryrefslogtreecommitdiffstats
path: root/audio/out/ao_pulse.c
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 /audio/out/ao_pulse.c
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.
Diffstat (limited to 'audio/out/ao_pulse.c')
-rw-r--r--audio/out/ao_pulse.c160
1 files changed, 67 insertions, 93 deletions
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,