[Spice-devel] [PATCH spice-gtk 6/8] spice-pulse: adjust the playback latency when the min-latency property changes

Yonit Halperin yhalperi at redhat.com
Tue Feb 26 10:04:56 PST 2013


If the current latency is smaller than the new min-latency
value, we cork the playback till the target latency is achieved.

Note: I didn't modify the prebuf configuration and used
pa_stream_prebuf, because pulse updated the prebuf only if
I set both prebuf and tlength to be target_latency*2. Then,
due to the tlength growth, each time the playback latency deviated
from the target latency, an underflow occurred. Since the latency
that is computed by the server is not exact and is based on its
current evaluation of the bit-rate, which is dynamic, it is better not
to change the tlength (in order to avoid unnecessary underflows).
---
 gtk/spice-pulse.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 59 insertions(+), 8 deletions(-)

diff --git a/gtk/spice-pulse.c b/gtk/spice-pulse.c
index 107ce7c..7a4e2c6 100644
--- a/gtk/spice-pulse.c
+++ b/gtk/spice-pulse.c
@@ -46,6 +46,8 @@ struct _SpicePulsePrivate {
     int                     state;
     struct stream           playback;
     struct stream           record;
+    guint                   last_delay;
+    guint                   target_delay;
 };
 
 G_DEFINE_TYPE(SpicePulse, spice_pulse, SPICE_TYPE_AUDIO)
@@ -185,7 +187,7 @@ static void pulse_flush_cb(pa_stream *pastream, int success, void *data)
     s->cork_op = NULL;
 }
 
-static void pulse_cork_cb(pa_stream *pastream, int success, void *data)
+static void pulse_cork_flush_cb(pa_stream *pastream, int success, void *data)
 {
     struct stream *s = data;
 
@@ -199,7 +201,19 @@ static void pulse_cork_cb(pa_stream *pastream, int success, void *data)
     }
 }
 
-static void stream_cork(SpicePulse *pulse, struct stream *s)
+static void pulse_cork_cb(pa_stream *pastream, int success, void *data)
+{
+    struct stream *s = data;
+
+    SPICE_DEBUG("%s: cork started", __FUNCTION__);
+    if (!success)
+        g_warning("pulseaudio cork operation failed");
+
+    pa_operation_unref(s->cork_op);
+    s->cork_op = NULL;
+}
+
+static void stream_cork(SpicePulse *pulse, struct stream *s, gboolean with_flush)
 {
     SpicePulsePrivate *p = SPICE_PULSE_GET_PRIVATE(pulse);
     pa_operation *o = NULL;
@@ -211,7 +225,10 @@ static void stream_cork(SpicePulse *pulse, struct stream *s)
     }
 
     if (!pa_stream_is_corked(s->stream) && !s->cork_op) {
-        if (!(o = pa_stream_cork(s->stream, 1, pulse_cork_cb, s))) {
+        if (!(o = pa_stream_cork(s->stream, 1,
+                                 with_flush ? pulse_cork_flush_cb :
+                                              pulse_cork_cb,
+                                 s))) {
             g_warning("pa_stream_cork() failed: %s",
                       pa_strerror(pa_context_errno(p->context)));
         }
@@ -277,11 +294,12 @@ static void stream_underflow_cb(pa_stream *s, void *userdata)
 
 static void stream_update_latency_callback(pa_stream *s, void *userdata)
 {
+    SpicePulse *pulse = userdata;
     pa_usec_t usec;
     int negative = 0;
     SpicePulsePrivate *p;
 
-    p = SPICE_PULSE_GET_PRIVATE(userdata);
+    p = SPICE_PULSE_GET_PRIVATE(pulse);
 
     g_return_if_fail(s != NULL);
     g_return_if_fail(p != NULL);
@@ -295,8 +313,16 @@ static void stream_update_latency_callback(pa_stream *s, void *userdata)
     }
 
     g_return_if_fail(negative == FALSE);
-
+    p->last_delay = usec / PA_USEC_PER_MSEC;
     spice_playback_channel_set_delay(SPICE_PLAYBACK_CHANNEL(p->pchannel), usec / 1000);
+    if (pa_stream_is_corked(p->playback.stream)) {
+        if (p->last_delay >= p->target_delay) {
+            SPICE_DEBUG("%s: uncork playback. delay %u target %u",  __FUNCTION__, p->last_delay, p->target_delay);
+            stream_uncork(pulse, &p->playback);
+        } else {
+            SPICE_DEBUG("%s: still corked. delay %u target %u",  __FUNCTION__, p->last_delay, p->target_delay);
+        }
+    }
 }
 
 static void create_playback(SpicePulse *pulse)
@@ -319,7 +345,7 @@ static void create_playback(SpicePulse *pulse)
 
     /* FIXME: we might want customizable latency */
     buffer_attr.maxlength = -1;
-    buffer_attr.tlength = pa_usec_to_bytes(100 * PA_USEC_PER_MSEC, &p->playback.spec);
+    buffer_attr.tlength = pa_usec_to_bytes(p->target_delay * PA_USEC_PER_MSEC, &p->playback.spec);
     buffer_attr.prebuf = -1;
     buffer_attr.minreq = -1;
     flags = PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE;
@@ -337,15 +363,18 @@ static void playback_start(SpicePlaybackChannel *channel, gint format, gint chan
     SpicePulse *pulse = data;
     SpicePulsePrivate *p = SPICE_PULSE_GET_PRIVATE(pulse);
     pa_context_state_t state;
+    guint latency;
 
     g_return_if_fail(p != NULL);
 
     p->playback.started = TRUE;
     p->playback.num_underflow = 0;
+    g_object_get(p->pchannel, "min-latency", &latency, NULL);
 
     if (p->playback.stream &&
         (p->playback.spec.rate != frequency ||
-         p->playback.spec.channels != channels)) {
+         p->playback.spec.channels != channels ||
+         p->target_delay != latency)) {
         stream_stop(pulse, &p->playback);
     }
 
@@ -353,6 +382,8 @@ static void playback_start(SpicePlaybackChannel *channel, gint format, gint chan
     p->playback.spec.format   = PA_SAMPLE_S16LE;
     p->playback.spec.rate     = frequency;
     p->playback.spec.channels = channels;
+    p->target_delay = latency;
+    p->last_delay = 0;
 
     state = pa_context_get_state(p->context);
     switch (state) {
@@ -421,7 +452,7 @@ static void playback_stop(SpicePlaybackChannel *channel, gpointer data)
     if (!p->playback.stream)
         return;
 
-    stream_cork(pulse, &p->playback);
+    stream_cork(pulse, &p->playback, TRUE);
 }
 
 static void stream_read_callback(pa_stream *s, size_t length, void *data)
@@ -629,6 +660,24 @@ static void playback_mute_changed(GObject *object, GParamSpec *pspec, gpointer d
         pa_operation_unref(op);
 }
 
+static void playback_min_latency_changed(GObject *object, GParamSpec *pspec, gpointer data)
+{
+
+    SpicePulse *pulse = data;
+    SpicePulsePrivate *p = pulse->priv;
+    guint min_latency;
+
+    g_object_get(object, "min-latency", &min_latency, NULL);
+    p->target_delay = min_latency;
+
+    if (p->last_delay < p->target_delay) {
+        spice_debug("%s: corking", __FUNCTION__);
+        stream_cork(pulse, &p->playback, FALSE);
+    } else {
+        spice_debug("%s: not corking. The current delay satisfies the requirement", __FUNCTION__);
+    }
+}
+
 static void record_mute_changed(GObject *object, GParamSpec *pspec, gpointer data)
 {
     SpicePulse *pulse = data;
@@ -711,6 +760,8 @@ static gboolean connect_channel(SpiceAudio *audio, SpiceChannel *channel)
                                       G_CALLBACK(playback_volume_changed), pulse, 0);
         spice_g_signal_connect_object(channel, "notify::mute",
                                       G_CALLBACK(playback_mute_changed), pulse, 0);
+        spice_g_signal_connect_object(channel, "notify::min-latency",
+                                      G_CALLBACK(playback_min_latency_changed), pulse, 0);
 
         return TRUE;
     }
-- 
1.8.1



More information about the Spice-devel mailing list