Merge branch 'master' into 0.11
[platform/upstream/gstreamer.git] / ext / pulse / pulseprobe.c
1 /*-*- Mode: C; c-basic-offset: 2 -*-*/
2
3 /*
4  *  GStreamer pulseaudio plugin
5  *
6  *  Copyright (c) 2004-2008 Lennart Poettering
7  *
8  *  gst-pulse is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU Lesser General Public License as
10  *  published by the Free Software Foundation; either version 2.1 of the
11  *  License, or (at your option) any later version.
12  *
13  *  gst-pulse is distributed in the hope that it will be useful, but
14  *  WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  *  Lesser General Public License for more details.
17  *
18  *  You should have received a copy of the GNU Lesser General Public
19  *  License along with gst-pulse; if not, write to the Free Software
20  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21  *  USA.
22  */
23
24 /* FIXME 0.11: suppress warnings for deprecated API such as GValueArray
25  * with newer GLib versions (>= 2.31.0) */
26 #define GLIB_DISABLE_DEPRECATION_WARNINGS
27
28 #ifdef HAVE_CONFIG_H
29 #include "config.h"
30 #endif
31
32 #include "pulseprobe.h"
33 #include "pulseutil.h"
34
35 GST_DEBUG_CATEGORY_EXTERN (pulse_debug);
36 #define GST_CAT_DEFAULT pulse_debug
37
38 static void
39 gst_pulseprobe_context_state_cb (pa_context * context, void *userdata)
40 {
41   GstPulseProbe *c = (GstPulseProbe *) userdata;
42
43   /* Called from the background thread! */
44
45   switch (pa_context_get_state (context)) {
46     case PA_CONTEXT_READY:
47     case PA_CONTEXT_TERMINATED:
48     case PA_CONTEXT_FAILED:
49       pa_threaded_mainloop_signal (c->mainloop, 0);
50       break;
51
52     case PA_CONTEXT_UNCONNECTED:
53     case PA_CONTEXT_CONNECTING:
54     case PA_CONTEXT_AUTHORIZING:
55     case PA_CONTEXT_SETTING_NAME:
56       break;
57   }
58 }
59
60 static void
61 gst_pulseprobe_sink_info_cb (pa_context * context, const pa_sink_info * i,
62     int eol, void *userdata)
63 {
64   GstPulseProbe *c = (GstPulseProbe *) userdata;
65
66   /* Called from the background thread! */
67
68   if (eol || !i) {
69     c->operation_success = eol > 0;
70     pa_threaded_mainloop_signal (c->mainloop, 0);
71   }
72
73   if (i)
74     c->devices = g_list_append (c->devices, g_strdup (i->name));
75
76 }
77
78 static void
79 gst_pulseprobe_source_info_cb (pa_context * context, const pa_source_info * i,
80     int eol, void *userdata)
81 {
82   GstPulseProbe *c = (GstPulseProbe *) userdata;
83
84   /* Called from the background thread! */
85
86   if (eol || !i) {
87     c->operation_success = eol > 0;
88     pa_threaded_mainloop_signal (c->mainloop, 0);
89   }
90
91   if (i)
92     c->devices = g_list_append (c->devices, g_strdup (i->name));
93 }
94
95 static void
96 gst_pulseprobe_invalidate (GstPulseProbe * c)
97 {
98   g_list_foreach (c->devices, (GFunc) g_free, NULL);
99   g_list_free (c->devices);
100   c->devices = NULL;
101   c->devices_valid = FALSE;
102 }
103
104 static gboolean
105 gst_pulseprobe_open (GstPulseProbe * c)
106 {
107   int e;
108   gchar *name;
109
110   g_assert (c);
111
112   GST_DEBUG_OBJECT (c->object, "probe open");
113
114   c->mainloop = pa_threaded_mainloop_new ();
115   if (!c->mainloop)
116     return FALSE;
117
118   e = pa_threaded_mainloop_start (c->mainloop);
119   if (e < 0)
120     return FALSE;
121
122   name = gst_pulse_client_name ();
123
124   pa_threaded_mainloop_lock (c->mainloop);
125
126   if (!(c->context =
127           pa_context_new (pa_threaded_mainloop_get_api (c->mainloop), name))) {
128     GST_WARNING_OBJECT (c->object, "Failed to create context");
129     goto unlock_and_fail;
130   }
131
132   pa_context_set_state_callback (c->context, gst_pulseprobe_context_state_cb,
133       c);
134
135   if (pa_context_connect (c->context, c->server, 0, NULL) < 0) {
136     GST_WARNING_OBJECT (c->object, "Failed to connect context: %s",
137         pa_strerror (pa_context_errno (c->context)));
138     goto unlock_and_fail;
139   }
140
141   for (;;) {
142     pa_context_state_t state;
143
144     state = pa_context_get_state (c->context);
145
146     if (!PA_CONTEXT_IS_GOOD (state)) {
147       GST_WARNING_OBJECT (c->object, "Failed to connect context: %s",
148           pa_strerror (pa_context_errno (c->context)));
149       goto unlock_and_fail;
150     }
151
152     if (state == PA_CONTEXT_READY)
153       break;
154
155     /* Wait until the context is ready */
156     pa_threaded_mainloop_wait (c->mainloop);
157   }
158
159   pa_threaded_mainloop_unlock (c->mainloop);
160   g_free (name);
161
162   gst_pulseprobe_invalidate (c);
163
164   return TRUE;
165
166 unlock_and_fail:
167
168   if (c->mainloop)
169     pa_threaded_mainloop_unlock (c->mainloop);
170
171   g_free (name);
172
173   return FALSE;
174 }
175
176 static gboolean
177 gst_pulseprobe_is_dead (GstPulseProbe * pulseprobe)
178 {
179
180   if (!pulseprobe->context ||
181       !PA_CONTEXT_IS_GOOD (pa_context_get_state (pulseprobe->context))) {
182     const gchar *err_str =
183         pulseprobe->context ?
184         pa_strerror (pa_context_errno (pulseprobe->context)) : NULL;
185
186     GST_ELEMENT_ERROR ((pulseprobe), RESOURCE, FAILED,
187         ("Disconnected: %s", err_str), (NULL));
188     return TRUE;
189   }
190
191   return FALSE;
192 }
193
194 static gboolean
195 gst_pulseprobe_enumerate (GstPulseProbe * c)
196 {
197   pa_operation *o = NULL;
198
199   GST_DEBUG_OBJECT (c->object, "probe enumerate");
200
201   pa_threaded_mainloop_lock (c->mainloop);
202
203   if (c->enumerate_sinks) {
204     /* Get sink info */
205
206     if (!(o =
207             pa_context_get_sink_info_list (c->context,
208                 gst_pulseprobe_sink_info_cb, c))) {
209       GST_WARNING_OBJECT (c->object, "Failed to get sink info: %s",
210           pa_strerror (pa_context_errno (c->context)));
211       goto unlock_and_fail;
212     }
213
214     c->operation_success = FALSE;
215
216     while (pa_operation_get_state (o) == PA_OPERATION_RUNNING) {
217
218       if (gst_pulseprobe_is_dead (c))
219         goto unlock_and_fail;
220
221       pa_threaded_mainloop_wait (c->mainloop);
222     }
223
224     if (!c->operation_success) {
225       GST_WARNING_OBJECT (c->object, "Failed to get sink info: %s",
226           pa_strerror (pa_context_errno (c->context)));
227       goto unlock_and_fail;
228     }
229
230     pa_operation_unref (o);
231     o = NULL;
232   }
233
234   if (c->enumerate_sources) {
235     /* Get source info */
236
237     if (!(o =
238             pa_context_get_source_info_list (c->context,
239                 gst_pulseprobe_source_info_cb, c))) {
240       GST_WARNING_OBJECT (c->object, "Failed to get source info: %s",
241           pa_strerror (pa_context_errno (c->context)));
242       goto unlock_and_fail;
243     }
244
245     c->operation_success = FALSE;
246     while (pa_operation_get_state (o) == PA_OPERATION_RUNNING) {
247
248       if (gst_pulseprobe_is_dead (c))
249         goto unlock_and_fail;
250
251       pa_threaded_mainloop_wait (c->mainloop);
252     }
253
254     if (!c->operation_success) {
255       GST_WARNING_OBJECT (c->object, "Failed to get sink info: %s",
256           pa_strerror (pa_context_errno (c->context)));
257       goto unlock_and_fail;
258     }
259
260     pa_operation_unref (o);
261     o = NULL;
262   }
263
264   c->devices_valid = TRUE;
265
266   pa_threaded_mainloop_unlock (c->mainloop);
267
268   return TRUE;
269
270 unlock_and_fail:
271
272   if (o)
273     pa_operation_unref (o);
274
275   pa_threaded_mainloop_unlock (c->mainloop);
276
277   return FALSE;
278 }
279
280 static void
281 gst_pulseprobe_close (GstPulseProbe * c)
282 {
283   g_assert (c);
284
285   GST_DEBUG_OBJECT (c->object, "probe close");
286
287   if (c->mainloop)
288     pa_threaded_mainloop_stop (c->mainloop);
289
290   if (c->context) {
291     pa_context_disconnect (c->context);
292     pa_context_set_state_callback (c->context, NULL, NULL);
293     pa_context_unref (c->context);
294     c->context = NULL;
295   }
296
297   if (c->mainloop) {
298     pa_threaded_mainloop_free (c->mainloop);
299     c->mainloop = NULL;
300   }
301 }
302
303 GstPulseProbe *
304 gst_pulseprobe_new (GObject * object, GObjectClass * klass,
305     guint prop_id, const gchar * server, gboolean sinks, gboolean sources)
306 {
307   GstPulseProbe *c = NULL;
308
309   c = g_new (GstPulseProbe, 1);
310   c->object = object;           /* We don't inc the ref counter here to avoid a ref loop */
311   c->server = g_strdup (server);
312   c->enumerate_sinks = sinks;
313   c->enumerate_sources = sources;
314
315   c->mainloop = NULL;
316   c->context = NULL;
317
318   c->prop_id = prop_id;
319   c->properties =
320       g_list_append (NULL, g_object_class_find_property (klass, "device"));
321
322   c->devices = NULL;
323   c->devices_valid = FALSE;
324
325   c->operation_success = FALSE;
326
327   return c;
328 }
329
330 void
331 gst_pulseprobe_free (GstPulseProbe * c)
332 {
333   g_assert (c);
334
335   gst_pulseprobe_close (c);
336
337   g_list_free (c->properties);
338   g_free (c->server);
339
340   g_list_foreach (c->devices, (GFunc) g_free, NULL);
341   g_list_free (c->devices);
342
343   g_free (c);
344 }
345
346 const GList *
347 gst_pulseprobe_get_properties (GstPulseProbe * c)
348 {
349   return c->properties;
350 }
351
352 gboolean
353 gst_pulseprobe_needs_probe (GstPulseProbe * c, guint prop_id,
354     const GParamSpec * pspec)
355 {
356
357   if (prop_id == c->prop_id)
358     return !c->devices_valid;
359
360   G_OBJECT_WARN_INVALID_PROPERTY_ID (c->object, prop_id, pspec);
361   return FALSE;
362 }
363
364 void
365 gst_pulseprobe_probe_property (GstPulseProbe * c, guint prop_id,
366     const GParamSpec * pspec)
367 {
368
369   if (prop_id != c->prop_id) {
370     G_OBJECT_WARN_INVALID_PROPERTY_ID (c->object, prop_id, pspec);
371     return;
372   }
373
374   if (gst_pulseprobe_open (c)) {
375     gst_pulseprobe_enumerate (c);
376     gst_pulseprobe_close (c);
377   }
378 }
379
380 #if 0
381 GValueArray *
382 gst_pulseprobe_get_values (GstPulseProbe * c, guint prop_id,
383     const GParamSpec * pspec)
384 {
385   GValueArray *array;
386   GValue value = {
387     0
388   };
389   GList *item;
390
391   if (prop_id != c->prop_id) {
392     G_OBJECT_WARN_INVALID_PROPERTY_ID (c->object, prop_id, pspec);
393     return NULL;
394   }
395
396   if (!c->devices_valid)
397     return NULL;
398
399   array = g_value_array_new (g_list_length (c->devices));
400   g_value_init (&value, G_TYPE_STRING);
401   for (item = c->devices; item != NULL; item = item->next) {
402     GST_WARNING_OBJECT (c->object, "device found: %s",
403         (const gchar *) item->data);
404     g_value_set_string (&value, (const gchar *) item->data);
405     g_value_array_append (array, &value);
406   }
407   g_value_unset (&value);
408
409   return array;
410 }
411 #endif
412
413 void
414 gst_pulseprobe_set_server (GstPulseProbe * c, const gchar * server)
415 {
416   g_assert (c);
417
418   gst_pulseprobe_invalidate (c);
419
420   g_free (c->server);
421   c->server = g_strdup (server);
422 }