[Spice-devel] [spice-gtk PATCH v4 1/6] audio: spice-pulse aware of app changes

Victor Toso victortoso at redhat.com
Mon Mar 30 04:09:10 PDT 2015


If there are changes in the audio stream like mute or volume,
spice-pulse should be aware of this changes in case it is
necessary to sync this values with guest.

This patch subscribe a callback for changes in sink-input and
source-output of pulse and keep track of volume and mute changes.
---
 gtk/spice-pulse.c | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 136 insertions(+)

diff --git a/gtk/spice-pulse.c b/gtk/spice-pulse.c
index dd7f309..6b27c97 100644
--- a/gtk/spice-pulse.c
+++ b/gtk/spice-pulse.c
@@ -37,6 +37,9 @@ struct stream {
     pa_operation            *cork_op;
     gboolean                started;
     guint                   num_underflow;
+    gboolean                mute;
+    guint8                  nchannels;
+    guint16                 *volume;
 };
 
 struct _SpicePulsePrivate {
@@ -50,6 +53,7 @@ struct _SpicePulsePrivate {
     struct stream           record;
     guint                   last_delay;
     guint                   target_delay;
+    gboolean                context_subscribed;
 };
 
 G_DEFINE_TYPE(SpicePulse, spice_pulse, SPICE_TYPE_AUDIO)
@@ -110,6 +114,8 @@ static void spice_pulse_dispose(GObject *obj)
         pa_operation_unref(p->playback.cork_op);
     p->playback.cork_op = NULL;
 
+    g_clear_pointer(&p->playback.volume, g_free);
+
     if (p->record.uncork_op)
         pa_operation_unref(p->record.uncork_op);
     p->record.uncork_op = NULL;
@@ -118,6 +124,8 @@ static void spice_pulse_dispose(GObject *obj)
         pa_operation_unref(p->record.cork_op);
     p->record.cork_op = NULL;
 
+    g_clear_pointer(&p->record.volume, g_free);
+
     if (p->pchannel)
         g_object_weak_unref(G_OBJECT(p->pchannel), channel_weak_notified, pulse);
     p->pchannel = NULL;
@@ -780,6 +788,116 @@ static gboolean connect_channel(SpiceAudio *audio, SpiceChannel *channel)
     return FALSE;
 }
 
+static void sink_input_info_cb(pa_context *context,
+                               const pa_sink_input_info *info,
+                               int eol,
+                               void *userdata)
+{
+    SpicePulsePrivate *p = SPICE_PULSE(userdata)->priv;
+    gint i;
+
+    if (eol)
+        return;
+
+    if (p->playback.nchannels != info->volume.channels) {
+        spice_warning ("Number of channels changed from %d to %d",
+                       p->playback.nchannels, info->volume.channels);
+        g_clear_pointer (&p->playback.volume, g_free);
+        p->playback.nchannels = info->volume.channels;
+        p->playback.volume = g_new(guint16, p->playback.nchannels);
+    }
+
+    for (i = 0; i < p->playback.nchannels; i++) {
+        p->playback.volume[i] = (guint16) info->volume.values[i];
+        SPICE_DEBUG("playback volume changed (client-side) %u", p->playback.volume[i]);
+    }
+
+    p->playback.mute = (info->mute) ? TRUE : FALSE;
+}
+
+static void source_output_info_cb(pa_context *context,
+                                  const pa_source_output_info *info,
+                                  int eol,
+                                  void *userdata)
+{
+    SpicePulsePrivate *p = SPICE_PULSE(userdata)->priv;
+    gint i;
+
+    if (eol)
+        return;
+
+    if (p->record.nchannels != info->volume.channels) {
+        spice_warning ("Number of channels changed from %d to %d",
+                       p->record.nchannels, info->volume.channels);
+        g_clear_pointer (&p->record.volume, g_free);
+        p->record.nchannels = info->volume.channels;
+        p->record.volume = g_new(guint16, p->record.nchannels);
+    }
+
+    for (i = 0; i < p->record.nchannels; i++) {
+        p->record.volume[i] = (guint16) info->volume.values[i];
+        SPICE_DEBUG("record volume changed (client-side) %u", p->record.volume[i]);
+    }
+
+    p->record.mute = (info->mute) ? TRUE : FALSE;
+}
+
+static void context_subscribe_callback(pa_context *c,
+                                       pa_subscription_event_type_t event,
+                                       uint32_t index,
+                                       void *userdata)
+{
+    SpicePulse *pulse = userdata;
+    SpicePulsePrivate *p = pulse->priv;
+    pa_subscription_event_type_t type, facility;
+
+    type = event & PA_SUBSCRIPTION_EVENT_TYPE_MASK;
+    if (type != PA_SUBSCRIPTION_EVENT_CHANGE &&
+        type != PA_SUBSCRIPTION_EVENT_NEW)
+        return;
+
+    facility = event & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
+    if (p->playback.stream != NULL &&
+        facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT) {
+        pa_operation *op;
+        guint32 stream_index;
+
+        stream_index = pa_stream_get_index(p->playback.stream);
+        if (stream_index != index) {
+            SPICE_DEBUG ("Playback stream %d differs from sink-input %d",
+                         stream_index, index);
+            return;
+        }
+        op = pa_context_get_sink_input_info(p->context, stream_index,
+                                            sink_input_info_cb, pulse);
+        if (!op)
+            spice_warning("get_sink_input_info failed: %s",
+                          pa_strerror(pa_context_errno(p->context)));
+        else
+            pa_operation_unref(op);
+    }
+
+    if (p->record.stream != NULL &&
+        facility == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT) {
+        pa_operation *op;
+        guint32 stream_index;
+
+        stream_index = pa_stream_get_index(p->record.stream);
+        if (stream_index != index) {
+            SPICE_DEBUG ("Record stream %d differs from sink-input %d",
+                         stream_index, index);
+            return;
+        }
+        op = pa_context_get_source_output_info(p->context, stream_index,
+                                               source_output_info_cb, pulse);
+        if (!op)
+            spice_warning("get_source_output_info failed: %s",
+                          pa_strerror(pa_context_errno(p->context)));
+        else
+            pa_operation_unref(op);
+    }
+}
+
 static void context_state_callback(pa_context *c, void *userdata)
 {
     SpicePulse *pulse = userdata;
@@ -802,6 +920,24 @@ static void context_state_callback(pa_context *c, void *userdata)
 
         if (!p->playback.stream && p->playback.started)
             create_playback(SPICE_PULSE(userdata));
+
+        if (p->context_subscribed == FALSE) {
+            pa_operation *op;
+            pa_context_set_subscribe_callback(p->context,
+                                              context_subscribe_callback,
+                                              pulse);
+            op = pa_context_subscribe(p->context,
+                                      PA_SUBSCRIPTION_MASK_SINK_INPUT|
+                                        PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT,
+                                      NULL, NULL);
+            if (op) {
+                pa_operation_unref(op);
+                p->context_subscribed = TRUE;
+            } else {
+                spice_warning("context_subscribe failed: %s",
+                              pa_strerror(pa_context_errno(p->context)));
+            }
+        }
         break;
     }
 
-- 
2.1.0



More information about the Spice-devel mailing list