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