1 /* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3    Copyright (C) 2010 Red Hat, Inc.
4 
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9 
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14 
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 */
18 /*
19  * simple audio init dispatcher
20  */
21 
22 /**
23  * SECTION:spice-audio
24  * @short_description: a helper to play and to record audio channels
25  * @title: Spice Audio
26  * @section_id:
27  * @see_also: #SpiceRecordChannel, and #SpicePlaybackChannel
28  * @stability: Stable
29  * @include: spice-client.h
30  *
31  * A class that handles the playback and record channels for your
32  * application, and connect them to the default sound system.
33  */
34 
35 #include "config.h"
36 
37 #include "spice-client.h"
38 #include "spice-common.h"
39 
40 #include "spice-audio.h"
41 #include "spice-session-priv.h"
42 #include "spice-channel-priv.h"
43 #include "spice-audio-priv.h"
44 
45 #ifdef HAVE_PULSE
46 #include "spice-pulse.h"
47 #endif
48 #include "spice-gstaudio.h"
49 
50 G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE(SpiceAudio, spice_audio, G_TYPE_OBJECT)
51 
52 enum {
53     PROP_0,
54     PROP_SESSION,
55     PROP_MAIN_CONTEXT,
56 };
57 
spice_audio_finalize(GObject * gobject)58 static void spice_audio_finalize(GObject *gobject)
59 {
60     SpiceAudio *self = SPICE_AUDIO(gobject);
61     SpiceAudioPrivate *priv = self->priv;
62 
63     g_clear_pointer(&priv->main_context, g_main_context_unref);
64 
65     if (G_OBJECT_CLASS(spice_audio_parent_class)->finalize)
66         G_OBJECT_CLASS(spice_audio_parent_class)->finalize(gobject);
67 }
68 
spice_audio_get_property(GObject * gobject,guint prop_id,GValue * value,GParamSpec * pspec)69 static void spice_audio_get_property(GObject *gobject,
70                                      guint prop_id,
71                                      GValue *value,
72                                      GParamSpec *pspec)
73 {
74     SpiceAudio *self = SPICE_AUDIO(gobject);
75     SpiceAudioPrivate *priv = self->priv;
76 
77     switch (prop_id) {
78     case PROP_SESSION:
79         g_value_set_object(value, priv->session);
80         break;
81     case PROP_MAIN_CONTEXT:
82         g_value_set_boxed(value, priv->main_context);
83         break;
84     default:
85         G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
86         break;
87     }
88 }
89 
spice_audio_set_property(GObject * gobject,guint prop_id,const GValue * value,GParamSpec * pspec)90 static void spice_audio_set_property(GObject *gobject,
91                                      guint prop_id,
92                                      const GValue *value,
93                                      GParamSpec *pspec)
94 {
95     SpiceAudio *self = SPICE_AUDIO(gobject);
96     SpiceAudioPrivate *priv = self->priv;
97 
98     switch (prop_id) {
99     case PROP_SESSION:
100         priv->session = g_value_get_object(value);
101         break;
102     case PROP_MAIN_CONTEXT:
103         priv->main_context = g_value_dup_boxed(value);
104         break;
105     default:
106         G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
107         break;
108     }
109 }
110 
spice_audio_class_init(SpiceAudioClass * klass)111 static void spice_audio_class_init(SpiceAudioClass *klass)
112 {
113     GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
114     GParamSpec *pspec;
115 
116     gobject_class->finalize     = spice_audio_finalize;
117     gobject_class->get_property = spice_audio_get_property;
118     gobject_class->set_property = spice_audio_set_property;
119 
120     /**
121      * SpiceAudio:session:
122      *
123      * #SpiceSession this #SpiceAudio is associated with
124      *
125      **/
126     pspec = g_param_spec_object("session", "Session", "SpiceSession",
127                                 SPICE_TYPE_SESSION,
128                                 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
129     g_object_class_install_property(gobject_class, PROP_SESSION, pspec);
130 
131     /**
132      * SpiceAudio:main-context:
133      */
134     pspec = g_param_spec_boxed("main-context", "Main Context",
135                                "GMainContext to use for the event source",
136                                G_TYPE_MAIN_CONTEXT,
137                                G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
138     g_object_class_install_property(gobject_class, PROP_MAIN_CONTEXT, pspec);
139 }
140 
spice_audio_init(SpiceAudio * self)141 static void spice_audio_init(SpiceAudio *self)
142 {
143     self->priv = spice_audio_get_instance_private(self);
144 }
145 
connect_channel(SpiceAudio * self,SpiceChannel * channel)146 static void connect_channel(SpiceAudio *self, SpiceChannel *channel)
147 {
148     if (channel->priv->state != SPICE_CHANNEL_STATE_UNCONNECTED)
149         return;
150 
151     if (SPICE_AUDIO_GET_CLASS(self)->connect_channel(self, channel))
152         spice_channel_connect(channel);
153 }
154 
update_audio_channels(SpiceAudio * self,SpiceSession * session)155 static void update_audio_channels(SpiceAudio *self, SpiceSession *session)
156 {
157     GList *list, *tmp;
158 
159     if (!spice_session_get_audio_enabled(session)) {
160         SPICE_DEBUG("FIXME: disconnect audio channels");
161         return;
162     }
163 
164     list = spice_session_get_channels(session);
165     for (tmp = g_list_first(list); tmp != NULL; tmp = g_list_next(tmp)) {
166         connect_channel(self, tmp->data);
167     }
168     g_list_free(list);
169 }
170 
channel_new(SpiceSession * session,SpiceChannel * channel,SpiceAudio * self)171 static void channel_new(SpiceSession *session, SpiceChannel *channel, SpiceAudio *self)
172 {
173     connect_channel(self, channel);
174 }
175 
session_enable_audio(GObject * gobject,GParamSpec * pspec,gpointer user_data)176 static void session_enable_audio(GObject *gobject, GParamSpec *pspec,
177                                  gpointer user_data)
178 {
179     update_audio_channels(SPICE_AUDIO(user_data), SPICE_SESSION(gobject));
180 }
181 
spice_audio_get_playback_volume_info_async(SpiceAudio * audio,GCancellable * cancellable,SpiceMainChannel * main_channel,GAsyncReadyCallback callback,gpointer user_data)182 void spice_audio_get_playback_volume_info_async(SpiceAudio *audio,
183                                                 GCancellable *cancellable,
184                                                 SpiceMainChannel *main_channel,
185                                                 GAsyncReadyCallback callback,
186                                                 gpointer user_data)
187 {
188     g_return_if_fail(audio != NULL);
189     SPICE_AUDIO_GET_CLASS(audio)->get_playback_volume_info_async(audio,
190             cancellable, main_channel, callback, user_data);
191 }
192 
spice_audio_get_playback_volume_info_finish(SpiceAudio * audio,GAsyncResult * res,gboolean * mute,guint8 * nchannels,guint16 ** volume,GError ** error)193 gboolean spice_audio_get_playback_volume_info_finish(SpiceAudio *audio,
194                                                      GAsyncResult *res,
195                                                      gboolean *mute,
196                                                      guint8 *nchannels,
197                                                      guint16 **volume,
198                                                      GError **error)
199 {
200     g_return_val_if_fail(audio != NULL, FALSE);
201     return SPICE_AUDIO_GET_CLASS(audio)->get_playback_volume_info_finish(audio,
202             res, mute, nchannels, volume, error);
203 }
204 
spice_audio_get_record_volume_info_async(SpiceAudio * audio,GCancellable * cancellable,SpiceMainChannel * main_channel,GAsyncReadyCallback callback,gpointer user_data)205 void spice_audio_get_record_volume_info_async(SpiceAudio *audio,
206                                               GCancellable *cancellable,
207                                               SpiceMainChannel *main_channel,
208                                               GAsyncReadyCallback callback,
209                                               gpointer user_data)
210 {
211     g_return_if_fail(audio != NULL);
212     SPICE_AUDIO_GET_CLASS(audio)->get_record_volume_info_async(audio,
213             cancellable, main_channel, callback, user_data);
214 }
215 
spice_audio_get_record_volume_info_finish(SpiceAudio * audio,GAsyncResult * res,gboolean * mute,guint8 * nchannels,guint16 ** volume,GError ** error)216 gboolean spice_audio_get_record_volume_info_finish(SpiceAudio *audio,
217                                                    GAsyncResult *res,
218                                                    gboolean *mute,
219                                                    guint8 *nchannels,
220                                                    guint16 **volume,
221                                                    GError **error)
222 {
223     g_return_val_if_fail(audio != NULL, FALSE);
224     return SPICE_AUDIO_GET_CLASS(audio)->get_record_volume_info_finish(audio,
225             res, mute, nchannels, volume, error);
226 }
227 
228 G_GNUC_INTERNAL
spice_audio_new_priv(SpiceSession * session,GMainContext * context,const char * name)229 SpiceAudio *spice_audio_new_priv(SpiceSession *session, GMainContext *context,
230                                  const char *name)
231 {
232     SpiceAudio *self = NULL;
233 
234     if (context == NULL)
235         context = g_main_context_default();
236     if (name == NULL)
237         name = g_get_application_name();
238 
239 #ifdef HAVE_PULSE
240     self = SPICE_AUDIO(spice_pulse_new(session, context, name));
241 #endif
242     if (!self)
243         self = SPICE_AUDIO(spice_gstaudio_new(session, context, name));
244     if (!self)
245         return NULL;
246 
247     spice_g_signal_connect_object(session, "notify::enable-audio", G_CALLBACK(session_enable_audio), self, 0);
248     spice_g_signal_connect_object(session, "channel-new", G_CALLBACK(channel_new), self, G_CONNECT_AFTER);
249     update_audio_channels(self, session);
250 
251     return self;
252 }
253 
254 /**
255  * spice_audio_new:
256  * @session: the #SpiceSession to connect to
257  * @context: (allow-none): a #GMainContext to attach to (or %NULL for
258  * default).
259  * @name: (allow-none): a name for the audio channels (or %NULL for
260  * application name).
261  *
262  * Once instantiated, #SpiceAudio will handle the playback and record
263  * channels to stream to your local audio system.
264  *
265  * Returns: a new #SpiceAudio instance or %NULL if no backend or failed.
266  * Deprecated: 0.8: Use spice_audio_get() instead
267  **/
spice_audio_new(SpiceSession * session,GMainContext * context,const char * name)268 SpiceAudio *spice_audio_new(SpiceSession *session, GMainContext *context,
269                             const char *name)
270 {
271     return spice_audio_new_priv(session, context, name);
272 }
273