summaryrefslogtreecommitdiffstats
path: root/audio/out/ao_pulse.c
diff options
context:
space:
mode:
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,