[Spice-devel] [spice-gtk PATCH 3/5] audio: spice-pulse aware of app changes
Victor Toso
victortoso at redhat.com
Wed Mar 18 10:17:15 PDT 2015
If there are changes in the audio stream like mute or volume,
spice-pulse should be aware of this changes and propagate this
changes to channel-playback and channel-record.
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 | 156 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 156 insertions(+)
diff --git a/gtk/spice-pulse.c b/gtk/spice-pulse.c
index dd7f309..b9ced66 100644
--- a/gtk/spice-pulse.c
+++ b/gtk/spice-pulse.c
@@ -37,6 +37,7 @@ struct stream {
pa_operation *cork_op;
gboolean started;
guint num_underflow;
+ gboolean client_volume_change;
};
struct _SpicePulsePrivate {
@@ -50,6 +51,7 @@ struct _SpicePulsePrivate {
struct stream record;
guint last_delay;
guint target_delay;
+ gboolean context_subscribed;
};
G_DEFINE_TYPE(SpicePulse, spice_pulse, SPICE_TYPE_AUDIO)
@@ -589,6 +591,14 @@ static void playback_volume_changed(GObject *object, GParamSpec *pspec, gpointer
pa_cvolume v;
guint i;
+ if (p->playback.client_volume_change) {
+ /* signal volume-changed emitted by client changes (not guest).
+ * this avoid infinite volume changes as the volume in the guest is
+ * already updated */
+ p->playback.client_volume_change = FALSE;
+ return;
+ }
+
g_object_get(object,
"volume", &volume,
"nchannels", &nchannels,
@@ -692,6 +702,14 @@ static void record_volume_changed(GObject *object, GParamSpec *pspec, gpointer d
pa_cvolume v;
guint i;
+ if (p->record.client_volume_change) {
+ /* signal volume-changed emitted by client changes (not guest).
+ * this avoid infinite volume changes as the volume in the guest is
+ * already updated */
+ p->record.client_volume_change = FALSE;
+ return;
+ }
+
g_object_get(object,
"volume", &volume,
"nchannels", &nchannels,
@@ -780,6 +798,126 @@ 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)
+{
+ SpicePulse *pulse = userdata;
+ SpicePulsePrivate *p = pulse->priv;
+ gboolean sink_mute;
+ guint16 *volume;
+ gint i;
+
+ if (eol)
+ return;
+
+ /* volume in pa_cvolume is _stored_ as guint32 */
+ volume = g_new(guint16, info->volume.channels);
+ for (i = 0; i < info->volume.channels; i++) {
+ volume[i] = (guint16) info->volume.values[i];
+ SPICE_DEBUG("playback volume changed (client-side) %u", volume[i]);
+ }
+
+ sink_mute = (info->mute) ? TRUE : FALSE;
+ SPICE_DEBUG("playback mute changed (client-side) %u", sink_mute);
+
+ p->playback.client_volume_change = TRUE;
+ g_object_set(p->pchannel,
+ "mute", sink_mute,
+ "volume", volume,
+ NULL);
+ g_free (volume);
+}
+
+static void source_output_info_cb(pa_context *context,
+ const pa_source_output_info *info,
+ int eol,
+ void *userdata)
+{
+ SpicePulse *pulse = userdata;
+ SpicePulsePrivate *p = pulse->priv;
+ gboolean source_mute;
+ guint16 *volume;
+ gint i;
+
+ if (eol)
+ return;
+
+ /* volume in pa_cvolume is _stored_ as guint32 */
+ volume = g_new(guint16, info->volume.channels);
+ for (i = 0; i < info->volume.channels; i++) {
+ volume[i] = (guint16) info->volume.values[i];
+ SPICE_DEBUG("record volume changed (client-side) %u", volume[i]);
+ }
+
+ source_mute = (info->mute) ? TRUE : FALSE;
+ SPICE_DEBUG("record mute changed (client-side) %u", source_mute);
+
+ p->record.client_volume_change = TRUE;
+ g_object_set(p->rchannel,
+ "mute", source_mute,
+ "volume", volume,
+ NULL);
+ g_free (volume);
+}
+
+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 +940,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