1 /* GStreamer
2  * Copyright (C) 2012 Olivier Crete <olivier.crete@collabora.com>
3  *
4  * pulsedeviceprovider.c: pulseaudio device probing and monitoring
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 
26 #include "pulsedeviceprovider.h"
27 
28 #include <string.h>
29 
30 #include <gst/gst.h>
31 
32 #include "pulsesrc.h"
33 #include "pulsesink.h"
34 #include "pulseutil.h"
35 
36 
37 GST_DEBUG_CATEGORY_EXTERN (pulse_debug);
38 #define GST_CAT_DEFAULT pulse_debug
39 
40 
41 static GstDevice *gst_pulse_device_new (guint id,
42     const gchar * device_name, GstCaps * caps, const gchar * internal_name,
43     GstPulseDeviceType type, GstStructure * properties, gboolean is_default);
44 
45 G_DEFINE_TYPE (GstPulseDeviceProvider, gst_pulse_device_provider,
46     GST_TYPE_DEVICE_PROVIDER);
47 
48 static void gst_pulse_device_provider_finalize (GObject * object);
49 static void gst_pulse_device_provider_set_property (GObject * object,
50     guint prop_id, const GValue * value, GParamSpec * pspec);
51 static void gst_pulse_device_provider_get_property (GObject * object,
52     guint prop_id, GValue * value, GParamSpec * pspec);
53 
54 
55 static GList *gst_pulse_device_provider_probe (GstDeviceProvider * provider);
56 static gboolean gst_pulse_device_provider_start (GstDeviceProvider * provider);
57 static void gst_pulse_device_provider_stop (GstDeviceProvider * provider);
58 
59 enum
60 {
61   PROP_0,
62   PROP_SERVER,
63   PROP_CLIENT_NAME,
64   PROP_LAST
65 };
66 
67 
68 typedef struct
69 {
70   GList *devices;
71   GstPulseDeviceProvider *self;
72 } ListDevicesData;
73 
74 static void
gst_pulse_device_provider_class_init(GstPulseDeviceProviderClass * klass)75 gst_pulse_device_provider_class_init (GstPulseDeviceProviderClass * klass)
76 {
77   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
78   GstDeviceProviderClass *dm_class = GST_DEVICE_PROVIDER_CLASS (klass);
79   gchar *client_name;
80 
81   gobject_class->set_property = gst_pulse_device_provider_set_property;
82   gobject_class->get_property = gst_pulse_device_provider_get_property;
83   gobject_class->finalize = gst_pulse_device_provider_finalize;
84 
85   dm_class->probe = gst_pulse_device_provider_probe;
86   dm_class->start = gst_pulse_device_provider_start;
87   dm_class->stop = gst_pulse_device_provider_stop;
88 
89   g_object_class_install_property (gobject_class,
90       PROP_SERVER,
91       g_param_spec_string ("server", "Server",
92           "The PulseAudio server to connect to", NULL,
93           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
94 
95   client_name = gst_pulse_client_name ();
96   g_object_class_install_property (gobject_class,
97       PROP_CLIENT_NAME,
98       g_param_spec_string ("client-name", "Client Name",
99           "The PulseAudio client_name_to_use", client_name,
100           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
101           GST_PARAM_MUTABLE_READY));
102   g_free (client_name);
103 
104   gst_device_provider_class_set_static_metadata (dm_class,
105       "PulseAudio Device Provider", "Sink/Source/Audio",
106       "List and provider PulseAudio source and sink devices",
107       "Olivier Crete <olivier.crete@collabora.com>");
108 }
109 
110 static void
gst_pulse_device_provider_init(GstPulseDeviceProvider * self)111 gst_pulse_device_provider_init (GstPulseDeviceProvider * self)
112 {
113   self->client_name = gst_pulse_client_name ();
114 }
115 
116 static void
gst_pulse_device_provider_finalize(GObject * object)117 gst_pulse_device_provider_finalize (GObject * object)
118 {
119   GstPulseDeviceProvider *self = GST_PULSE_DEVICE_PROVIDER (object);
120 
121   g_free (self->client_name);
122   g_free (self->server);
123   g_free (self->default_sink_name);
124   g_free (self->default_source_name);
125 
126   G_OBJECT_CLASS (gst_pulse_device_provider_parent_class)->finalize (object);
127 }
128 
129 
130 static void
gst_pulse_device_provider_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)131 gst_pulse_device_provider_set_property (GObject * object,
132     guint prop_id, const GValue * value, GParamSpec * pspec)
133 {
134   GstPulseDeviceProvider *self = GST_PULSE_DEVICE_PROVIDER (object);
135 
136   switch (prop_id) {
137     case PROP_SERVER:
138       g_free (self->server);
139       self->server = g_value_dup_string (value);
140       break;
141     case PROP_CLIENT_NAME:
142       g_free (self->client_name);
143       if (!g_value_get_string (value)) {
144         GST_WARNING_OBJECT (self,
145             "Empty PulseAudio client name not allowed. "
146             "Resetting to default value");
147         self->client_name = gst_pulse_client_name ();
148       } else
149         self->client_name = g_value_dup_string (value);
150       break;
151     default:
152       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
153       break;
154   }
155 }
156 
157 static void
gst_pulse_device_provider_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)158 gst_pulse_device_provider_get_property (GObject * object,
159     guint prop_id, GValue * value, GParamSpec * pspec)
160 {
161   GstPulseDeviceProvider *self = GST_PULSE_DEVICE_PROVIDER (object);
162 
163   switch (prop_id) {
164     case PROP_SERVER:
165       g_value_set_string (value, self->server);
166       break;
167     case PROP_CLIENT_NAME:
168       g_value_set_string (value, self->client_name);
169       break;
170     default:
171       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
172       break;
173   }
174 }
175 
176 static void
context_state_cb(pa_context * c,void * userdata)177 context_state_cb (pa_context * c, void *userdata)
178 {
179   GstPulseDeviceProvider *self = userdata;
180 
181   switch (pa_context_get_state (c)) {
182     case PA_CONTEXT_READY:
183     case PA_CONTEXT_TERMINATED:
184     case PA_CONTEXT_FAILED:
185       pa_threaded_mainloop_signal (self->mainloop, 0);
186       break;
187 
188     case PA_CONTEXT_UNCONNECTED:
189     case PA_CONTEXT_CONNECTING:
190     case PA_CONTEXT_AUTHORIZING:
191     case PA_CONTEXT_SETTING_NAME:
192       break;
193   }
194 }
195 
196 static GstDevice *
new_source(GstPulseDeviceProvider * self,const pa_source_info * info)197 new_source (GstPulseDeviceProvider * self, const pa_source_info * info)
198 {
199   GstCaps *caps;
200   GstStructure *props;
201   guint i;
202 
203   caps = gst_caps_new_empty ();
204 
205   for (i = 0; i < info->n_formats; i++)
206     gst_caps_append (caps, gst_pulse_format_info_to_caps (info->formats[i]));
207 
208   props = gst_pulse_make_structure (info->proplist);
209 
210   return gst_pulse_device_new (info->index, info->description,
211       caps, info->name, GST_PULSE_DEVICE_TYPE_SOURCE, props,
212       !g_strcmp0 (info->name, self->default_source_name));
213 }
214 
215 static GstDevice *
new_sink(GstPulseDeviceProvider * self,const pa_sink_info * info)216 new_sink (GstPulseDeviceProvider * self, const pa_sink_info * info)
217 {
218   GstCaps *caps;
219   GstStructure *props;
220   guint i;
221 
222   caps = gst_caps_new_empty ();
223 
224   for (i = 0; i < info->n_formats; i++)
225     gst_caps_append (caps, gst_pulse_format_info_to_caps (info->formats[i]));
226 
227   props = gst_pulse_make_structure (info->proplist);
228 
229   return gst_pulse_device_new (info->index, info->description,
230       caps, info->name, GST_PULSE_DEVICE_TYPE_SINK, props,
231       !g_strcmp0 (info->name, self->default_sink_name));
232 }
233 
234 static void
get_source_info_cb(pa_context * context,const pa_source_info * info,int eol,void * userdata)235 get_source_info_cb (pa_context * context,
236     const pa_source_info * info, int eol, void *userdata)
237 {
238   GstPulseDeviceProvider *self = userdata;
239   GstDevice *dev;
240 
241   if (eol) {
242     pa_threaded_mainloop_signal (self->mainloop, 0);
243     return;
244   }
245 
246   dev = new_source (self, info);
247 
248   if (dev)
249     gst_device_provider_device_add (GST_DEVICE_PROVIDER (self), dev);
250 }
251 
252 static void
get_server_info_cb(pa_context * context,const pa_server_info * info,void * userdata)253 get_server_info_cb (pa_context * context, const pa_server_info * info,
254     void *userdata)
255 {
256   GList *tmp, *devices = NULL;
257   GstPulseDeviceProvider *self = userdata;
258 
259   GST_OBJECT_LOCK (self);
260   g_free (self->default_sink_name);
261   g_free (self->default_source_name);
262   self->default_sink_name = g_strdup (info->default_sink_name);
263   self->default_source_name = g_strdup (info->default_source_name);
264   GST_DEBUG_OBJECT (self, "Default sink name: %s", self->default_sink_name);
265 
266   for (tmp = GST_DEVICE_PROVIDER (self)->devices; tmp; tmp = tmp->next)
267     devices = g_list_prepend (devices, gst_object_ref (tmp->data));
268   GST_OBJECT_UNLOCK (self);
269 
270   for (tmp = devices; tmp; tmp = tmp->next) {
271     GstPulseDevice *dev = tmp->data;
272     GstStructure *props = gst_device_get_properties (GST_DEVICE (dev));
273     gboolean was_default = FALSE, is_default = FALSE;
274 
275     g_assert (props);
276     gst_structure_get_boolean (props, "is-default", &was_default);
277     switch (dev->type) {
278       case GST_PULSE_DEVICE_TYPE_SINK:
279         is_default = !g_strcmp0 (dev->internal_name, self->default_sink_name);
280         break;
281       case GST_PULSE_DEVICE_TYPE_SOURCE:
282         is_default = !g_strcmp0 (dev->internal_name, self->default_source_name);
283         break;
284     }
285 
286     if (was_default != is_default) {
287       GstDevice *updated_device;
288       gchar *name = gst_device_get_display_name (GST_DEVICE (dev));
289 
290       gst_structure_set (props, "is-default", G_TYPE_BOOLEAN, is_default, NULL);
291       updated_device = gst_pulse_device_new (dev->device_index,
292           name, gst_device_get_caps (GST_DEVICE (dev)), dev->internal_name,
293           dev->type, props, is_default);
294 
295       gst_device_provider_device_changed (GST_DEVICE_PROVIDER (self),
296           updated_device, GST_DEVICE (dev));
297 
298       g_free (name);
299     } else {
300       gst_structure_free (props);
301     }
302   }
303   g_list_free_full (devices, gst_object_unref);
304 
305   pa_threaded_mainloop_signal (self->mainloop, 0);
306 }
307 
308 static void
get_sink_info_cb(pa_context * context,const pa_sink_info * info,int eol,void * userdata)309 get_sink_info_cb (pa_context * context,
310     const pa_sink_info * info, int eol, void *userdata)
311 {
312   GstPulseDeviceProvider *self = userdata;
313   GstDevice *dev;
314 
315   if (eol) {
316     pa_threaded_mainloop_signal (self->mainloop, 0);
317     return;
318   }
319 
320   dev = new_sink (self, info);
321 
322   if (dev)
323     gst_device_provider_device_add (GST_DEVICE_PROVIDER (self), dev);
324 }
325 
326 static void
context_subscribe_cb(pa_context * context,pa_subscription_event_type_t type,uint32_t idx,void * userdata)327 context_subscribe_cb (pa_context * context, pa_subscription_event_type_t type,
328     uint32_t idx, void *userdata)
329 {
330   GstPulseDeviceProvider *self = userdata;
331   GstDeviceProvider *provider = userdata;
332   pa_subscription_event_type_t facility =
333       type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
334   pa_subscription_event_type_t event_type =
335       type & PA_SUBSCRIPTION_EVENT_TYPE_MASK;
336 
337   if (facility == PA_SUBSCRIPTION_EVENT_SERVER ||
338       facility != PA_SUBSCRIPTION_EVENT_CHANGE) {
339     pa_context_get_server_info (self->context, get_server_info_cb, self);
340 
341     return;
342   }
343 
344   if (facility != PA_SUBSCRIPTION_EVENT_SOURCE &&
345       facility != PA_SUBSCRIPTION_EVENT_SINK)
346     return;
347 
348   if (event_type == PA_SUBSCRIPTION_EVENT_NEW) {
349     /* Microphone in the source output has changed */
350 
351     if (facility == PA_SUBSCRIPTION_EVENT_SOURCE)
352       pa_context_get_source_info_by_index (context, idx, get_source_info_cb,
353           self);
354     else if (facility == PA_SUBSCRIPTION_EVENT_SINK)
355       pa_context_get_sink_info_by_index (context, idx, get_sink_info_cb, self);
356   } else if (event_type == PA_SUBSCRIPTION_EVENT_REMOVE) {
357     GstPulseDevice *dev = NULL;
358     GList *item;
359 
360     GST_OBJECT_LOCK (self);
361     for (item = provider->devices; item; item = item->next) {
362       dev = item->data;
363 
364       if (((facility == PA_SUBSCRIPTION_EVENT_SOURCE &&
365                   dev->type == GST_PULSE_DEVICE_TYPE_SOURCE) ||
366               (facility == PA_SUBSCRIPTION_EVENT_SINK &&
367                   dev->type == GST_PULSE_DEVICE_TYPE_SINK)) &&
368           dev->device_index == idx) {
369         gst_object_ref (dev);
370         break;
371       }
372       dev = NULL;
373     }
374     GST_OBJECT_UNLOCK (self);
375 
376     if (dev) {
377       gst_device_provider_device_remove (GST_DEVICE_PROVIDER (self),
378           GST_DEVICE (dev));
379       gst_object_unref (dev);
380     }
381   }
382 }
383 
384 static void
get_source_info_list_cb(pa_context * context,const pa_source_info * info,int eol,void * userdata)385 get_source_info_list_cb (pa_context * context, const pa_source_info * info,
386     int eol, void *userdata)
387 {
388   ListDevicesData *data = userdata;
389 
390   if (eol)
391     return;
392 
393   data->devices =
394       g_list_prepend (data->devices,
395       gst_object_ref_sink (new_source (data->self, info)));
396 }
397 
398 static void
get_sink_info_list_cb(pa_context * context,const pa_sink_info * info,int eol,void * userdata)399 get_sink_info_list_cb (pa_context * context, const pa_sink_info * info,
400     int eol, void *userdata)
401 {
402   ListDevicesData *data = userdata;
403 
404   if (eol)
405     return;
406 
407   data->devices =
408       g_list_prepend (data->devices, gst_object_ref_sink (new_sink (data->self,
409               info)));
410 }
411 
412 static GList *
gst_pulse_device_provider_probe(GstDeviceProvider * provider)413 gst_pulse_device_provider_probe (GstDeviceProvider * provider)
414 {
415   GstPulseDeviceProvider *self = GST_PULSE_DEVICE_PROVIDER (provider);
416   pa_mainloop *m = NULL;
417   pa_context *c = NULL;
418   pa_operation *o;
419   ListDevicesData data = { NULL, self };
420 
421   if (!(m = pa_mainloop_new ()))
422     return NULL;
423 
424   if (!(c = pa_context_new (pa_mainloop_get_api (m), self->client_name))) {
425     GST_ERROR_OBJECT (self, "Failed to create context");
426     goto failed;
427   }
428 
429   if (pa_context_connect (c, self->server, 0, NULL) < 0) {
430     GST_ERROR_OBJECT (self, "Failed to connect: %s",
431         pa_strerror (pa_context_errno (self->context)));
432     goto failed;
433   }
434 
435   for (;;) {
436     pa_context_state_t state;
437 
438     state = pa_context_get_state (c);
439 
440     if (!PA_CONTEXT_IS_GOOD (state)) {
441       GST_ELEMENT_ERROR (self, RESOURCE, FAILED, ("Failed to connect: %s",
442               pa_strerror (pa_context_errno (c))), (NULL));
443       goto failed;
444     }
445 
446     if (state == PA_CONTEXT_READY)
447       break;
448 
449     /* Wait until the context is ready */
450     if (pa_mainloop_iterate (m, TRUE, NULL) < 0)
451       goto failed;
452 
453   }
454   GST_DEBUG_OBJECT (self, "connected");
455 
456   o = pa_context_get_sink_info_list (c, get_sink_info_list_cb, &data);
457   while (pa_operation_get_state (o) == PA_OPERATION_RUNNING &&
458       pa_operation_get_state (o) == PA_OPERATION_RUNNING) {
459     if (pa_mainloop_iterate (m, TRUE, NULL) < 0)
460       break;
461   }
462   pa_operation_unref (o);
463 
464   o = pa_context_get_source_info_list (c, get_source_info_list_cb, &data);
465   while (pa_operation_get_state (o) == PA_OPERATION_RUNNING &&
466       pa_operation_get_state (o) == PA_OPERATION_RUNNING) {
467     if (pa_mainloop_iterate (m, TRUE, NULL) < 0)
468       break;
469   }
470   pa_operation_unref (o);
471 
472   pa_context_disconnect (c);
473   pa_mainloop_free (m);
474 
475   return data.devices;
476 
477 failed:
478 
479   return NULL;
480 }
481 
482 static gboolean
run_pulse_operation(GstPulseDeviceProvider * self,pa_operation * operation)483 run_pulse_operation (GstPulseDeviceProvider * self, pa_operation * operation)
484 {
485   if (!operation)
486     return FALSE;
487 
488   while (pa_operation_get_state (operation) == PA_OPERATION_RUNNING) {
489     if (!PA_CONTEXT_IS_GOOD (pa_context_get_state ((self->context)))) {
490       pa_operation_cancel (operation);
491       pa_operation_unref (operation);
492       return FALSE;
493     }
494 
495     pa_threaded_mainloop_wait (self->mainloop);
496   }
497 
498   pa_operation_unref (operation);
499 
500   return TRUE;
501 }
502 
503 static gboolean
gst_pulse_device_provider_start(GstDeviceProvider * provider)504 gst_pulse_device_provider_start (GstDeviceProvider * provider)
505 {
506   GstPulseDeviceProvider *self = GST_PULSE_DEVICE_PROVIDER (provider);
507 
508   if (!(self->mainloop = pa_threaded_mainloop_new ())) {
509     GST_ERROR_OBJECT (self, "Could not create pulseaudio mainloop");
510     goto mainloop_failed;
511   }
512   if (pa_threaded_mainloop_start (self->mainloop) < 0) {
513     GST_ERROR_OBJECT (self, "Could not start pulseaudio mainloop");
514     pa_threaded_mainloop_free (self->mainloop);
515     self->mainloop = NULL;
516     goto mainloop_failed;
517   }
518 
519   pa_threaded_mainloop_lock (self->mainloop);
520 
521   if (!(self->context =
522           pa_context_new (pa_threaded_mainloop_get_api (self->mainloop),
523               self->client_name))) {
524     GST_ERROR_OBJECT (self, "Failed to create context");
525     goto unlock_and_fail;
526   }
527 
528   pa_context_set_state_callback (self->context, context_state_cb, self);
529   pa_context_set_subscribe_callback (self->context, context_subscribe_cb, self);
530 
531 
532   GST_DEBUG_OBJECT (self, "connect to server %s", GST_STR_NULL (self->server));
533 
534   if (pa_context_connect (self->context, self->server, 0, NULL) < 0) {
535     GST_ERROR_OBJECT (self, "Failed to connect: %s",
536         pa_strerror (pa_context_errno (self->context)));
537     goto unlock_and_fail;
538   }
539 
540   for (;;) {
541     pa_context_state_t state;
542 
543     state = pa_context_get_state (self->context);
544 
545     if (!PA_CONTEXT_IS_GOOD (state)) {
546       GST_ERROR_OBJECT (self, "Failed to connect: %s",
547           pa_strerror (pa_context_errno (self->context)));
548       goto unlock_and_fail;
549     }
550 
551     if (state == PA_CONTEXT_READY)
552       break;
553 
554     /* Wait until the context is ready */
555     pa_threaded_mainloop_wait (self->mainloop);
556   }
557   GST_DEBUG_OBJECT (self, "connected");
558 
559   pa_context_subscribe (self->context,
560       PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_SINK |
561       PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, NULL, NULL);
562 
563   if (!run_pulse_operation (self, pa_context_get_server_info (self->context,
564               get_server_info_cb, self)))
565     goto unlock_and_fail;
566 
567   if (!run_pulse_operation (self,
568           pa_context_get_source_info_list (self->context, get_source_info_cb,
569               self)))
570     goto unlock_and_fail;
571 
572   if (!run_pulse_operation (self, pa_context_get_sink_info_list (self->context,
573               get_sink_info_cb, self)))
574     goto unlock_and_fail;
575 
576   pa_threaded_mainloop_unlock (self->mainloop);
577 
578   return TRUE;
579 
580 unlock_and_fail:
581   pa_threaded_mainloop_unlock (self->mainloop);
582   gst_pulse_device_provider_stop (provider);
583   return FALSE;
584 
585 mainloop_failed:
586   return FALSE;
587 }
588 
589 static void
gst_pulse_device_provider_stop(GstDeviceProvider * provider)590 gst_pulse_device_provider_stop (GstDeviceProvider * provider)
591 {
592   GstPulseDeviceProvider *self = GST_PULSE_DEVICE_PROVIDER (provider);
593 
594   pa_threaded_mainloop_stop (self->mainloop);
595 
596   if (self->context) {
597     pa_context_disconnect (self->context);
598 
599     /* Make sure we don't get any further callbacks */
600     pa_context_set_state_callback (self->context, NULL, NULL);
601     pa_context_set_subscribe_callback (self->context, NULL, NULL);
602 
603     pa_context_unref (self->context);
604     self->context = NULL;
605   }
606 
607   pa_threaded_mainloop_free (self->mainloop);
608   self->mainloop = NULL;
609 }
610 
611 enum
612 {
613   PROP_INTERNAL_NAME = 1,
614 };
615 
616 G_DEFINE_TYPE (GstPulseDevice, gst_pulse_device, GST_TYPE_DEVICE);
617 
618 static void gst_pulse_device_get_property (GObject * object, guint prop_id,
619     GValue * value, GParamSpec * pspec);
620 static void gst_pulse_device_set_property (GObject * object, guint prop_id,
621     const GValue * value, GParamSpec * pspec);
622 static void gst_pulse_device_finalize (GObject * object);
623 static GstElement *gst_pulse_device_create_element (GstDevice * device,
624     const gchar * name);
625 static gboolean gst_pulse_device_reconfigure_element (GstDevice * device,
626     GstElement * element);
627 
628 static void
gst_pulse_device_class_init(GstPulseDeviceClass * klass)629 gst_pulse_device_class_init (GstPulseDeviceClass * klass)
630 {
631   GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass);
632   GObjectClass *object_class = G_OBJECT_CLASS (klass);
633 
634   dev_class->create_element = gst_pulse_device_create_element;
635   dev_class->reconfigure_element = gst_pulse_device_reconfigure_element;
636 
637   object_class->get_property = gst_pulse_device_get_property;
638   object_class->set_property = gst_pulse_device_set_property;
639   object_class->finalize = gst_pulse_device_finalize;
640 
641   g_object_class_install_property (object_class, PROP_INTERNAL_NAME,
642       g_param_spec_string ("internal-name", "Internal PulseAudio device name",
643           "The internal name of the PulseAudio device", "",
644           G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
645 }
646 
647 static void
gst_pulse_device_init(GstPulseDevice * device)648 gst_pulse_device_init (GstPulseDevice * device)
649 {
650 }
651 
652 static void
gst_pulse_device_finalize(GObject * object)653 gst_pulse_device_finalize (GObject * object)
654 {
655   GstPulseDevice *device = GST_PULSE_DEVICE (object);
656 
657   g_free (device->internal_name);
658 
659   G_OBJECT_CLASS (gst_pulse_device_parent_class)->finalize (object);
660 }
661 
662 static GstElement *
gst_pulse_device_create_element(GstDevice * device,const gchar * name)663 gst_pulse_device_create_element (GstDevice * device, const gchar * name)
664 {
665   GstPulseDevice *pulse_dev = GST_PULSE_DEVICE (device);
666   GstElement *elem;
667 
668   elem = gst_element_factory_make (pulse_dev->element, name);
669   g_object_set (elem, "device", pulse_dev->internal_name, NULL);
670 
671   return elem;
672 }
673 
674 static gboolean
gst_pulse_device_reconfigure_element(GstDevice * device,GstElement * element)675 gst_pulse_device_reconfigure_element (GstDevice * device, GstElement * element)
676 {
677   GstPulseDevice *pulse_dev = GST_PULSE_DEVICE (device);
678 
679   if (!strcmp (pulse_dev->element, "pulsesrc")) {
680     if (!GST_IS_PULSESRC (element))
681       return FALSE;
682   } else if (!strcmp (pulse_dev->element, "pulsesink")) {
683     if (!GST_IS_PULSESINK (element))
684       return FALSE;
685   } else {
686     g_assert_not_reached ();
687   }
688 
689   g_object_set (element, "device", pulse_dev->internal_name, NULL);
690 
691   return TRUE;
692 }
693 
694 /* Takes ownership of @caps and @props */
695 static GstDevice *
gst_pulse_device_new(guint device_index,const gchar * device_name,GstCaps * caps,const gchar * internal_name,GstPulseDeviceType type,GstStructure * props,gboolean is_default)696 gst_pulse_device_new (guint device_index, const gchar * device_name,
697     GstCaps * caps, const gchar * internal_name, GstPulseDeviceType type,
698     GstStructure * props, gboolean is_default)
699 {
700   GstPulseDevice *gstdev;
701   const gchar *element = NULL;
702   const gchar *klass = NULL;
703 
704   g_return_val_if_fail (device_name, NULL);
705   g_return_val_if_fail (internal_name, NULL);
706   g_return_val_if_fail (caps, NULL);
707 
708 
709   switch (type) {
710     case GST_PULSE_DEVICE_TYPE_SOURCE:
711       element = "pulsesrc";
712       klass = "Audio/Source";
713       break;
714     case GST_PULSE_DEVICE_TYPE_SINK:
715       element = "pulsesink";
716       klass = "Audio/Sink";
717       break;
718     default:
719       g_assert_not_reached ();
720       break;
721   }
722 
723   gst_structure_set (props, "is-default", G_TYPE_BOOLEAN, is_default, NULL);
724   gstdev = g_object_new (GST_TYPE_PULSE_DEVICE,
725       "display-name", device_name, "caps", caps, "device-class", klass,
726       "internal-name", internal_name, "properties", props, NULL);
727 
728   gstdev->type = type;
729   gstdev->device_index = device_index;
730   gstdev->element = element;
731   gstdev->is_default = is_default;
732 
733   gst_structure_free (props);
734   gst_caps_unref (caps);
735 
736   return GST_DEVICE (gstdev);
737 }
738 
739 
740 static void
gst_pulse_device_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)741 gst_pulse_device_get_property (GObject * object, guint prop_id,
742     GValue * value, GParamSpec * pspec)
743 {
744   GstPulseDevice *device;
745 
746   device = GST_PULSE_DEVICE_CAST (object);
747 
748   switch (prop_id) {
749     case PROP_INTERNAL_NAME:
750       g_value_set_string (value, device->internal_name);
751       break;
752     default:
753       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
754       break;
755   }
756 }
757 
758 
759 static void
gst_pulse_device_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)760 gst_pulse_device_set_property (GObject * object, guint prop_id,
761     const GValue * value, GParamSpec * pspec)
762 {
763   GstPulseDevice *device;
764 
765   device = GST_PULSE_DEVICE_CAST (object);
766 
767   switch (prop_id) {
768     case PROP_INTERNAL_NAME:
769       device->internal_name = g_value_dup_string (value);
770       break;
771     default:
772       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
773       break;
774   }
775 }
776