• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer
2  * Copyright (C) 2016 Hyunjun Ko <zzoon@igalia.com>
3  *
4  * gstosxaudiodeviceprovider.c: OSX audio 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 <gst/gst.h>
27 #include <gst/audio/audio.h>
28 #include "gstosxaudiosrc.h"
29 #include "gstosxaudiosink.h"
30 #include "gstosxaudiodeviceprovider.h"
31 
32 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
33     GST_PAD_SRC,
34     GST_PAD_ALWAYS,
35     GST_STATIC_CAPS (GST_OSX_AUDIO_SRC_CAPS)
36     );
37 
38 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
39     GST_PAD_SINK,
40     GST_PAD_ALWAYS,
41     GST_STATIC_CAPS (GST_OSX_AUDIO_SINK_CAPS)
42     );
43 
44 static GstOsxAudioDevice *gst_osx_audio_device_new (AudioDeviceID device_id,
45     const gchar * device_name, GstOsxAudioDeviceType type,
46     GstCoreAudio * core_audio);
47 
48 G_DEFINE_TYPE (GstOsxAudioDeviceProvider, gst_osx_audio_device_provider,
49     GST_TYPE_DEVICE_PROVIDER);
50 
51 static GList *gst_osx_audio_device_provider_probe (GstDeviceProvider *
52     provider);
53 
54 static void
gst_osx_audio_device_provider_class_init(GstOsxAudioDeviceProviderClass * klass)55 gst_osx_audio_device_provider_class_init (GstOsxAudioDeviceProviderClass *
56     klass)
57 {
58   GstDeviceProviderClass *dm_class = GST_DEVICE_PROVIDER_CLASS (klass);
59 
60   dm_class->probe = gst_osx_audio_device_provider_probe;
61 
62   gst_device_provider_class_set_static_metadata (dm_class,
63       "OSX Audio Device Provider", "Source/Sink/Audio",
64       "List and monitor OSX audio source and sink devices",
65       "Hyunjun Ko <zzoon@igalia.com>");
66 }
67 
68 static void
gst_osx_audio_device_provider_init(GstOsxAudioDeviceProvider * provider)69 gst_osx_audio_device_provider_init (GstOsxAudioDeviceProvider * provider)
70 {
71 }
72 
73 static GstOsxAudioDevice *
gst_osx_audio_device_provider_probe_device(GstOsxAudioDeviceProvider * provider,AudioDeviceID device_id,const gchar * device_name,GstOsxAudioDeviceType type)74 gst_osx_audio_device_provider_probe_device (GstOsxAudioDeviceProvider *
75     provider, AudioDeviceID device_id, const gchar * device_name,
76     GstOsxAudioDeviceType type)
77 {
78   GstOsxAudioDevice *device = NULL;
79   GstCoreAudio *core_audio;
80 
81   core_audio = gst_core_audio_new (NULL);
82   core_audio->is_src = type == GST_OSX_AUDIO_DEVICE_TYPE_SOURCE ? TRUE : FALSE;
83   core_audio->device_id = device_id;
84 
85   if (!gst_core_audio_open (core_audio)) {
86     GST_ERROR ("CoreAudio device could not be opened");
87     goto done;
88   }
89 
90   device = gst_osx_audio_device_new (device_id, device_name, type, core_audio);
91 
92   gst_core_audio_close (core_audio);
93 
94 done:
95   g_object_unref (core_audio);
96 
97   return device;
98 }
99 
100 static inline gchar *
_audio_device_get_name(AudioDeviceID device_id,gboolean output)101 _audio_device_get_name (AudioDeviceID device_id, gboolean output)
102 {
103   OSStatus status = noErr;
104   UInt32 propertySize = 0;
105   gchar *device_name = NULL;
106   AudioObjectPropertyScope prop_scope;
107 
108   AudioObjectPropertyAddress deviceNameAddress = {
109     kAudioDevicePropertyDeviceName,
110     kAudioDevicePropertyScopeOutput,
111     kAudioObjectPropertyElementMaster
112   };
113 
114   prop_scope = output ? kAudioDevicePropertyScopeOutput :
115       kAudioDevicePropertyScopeInput;
116 
117   deviceNameAddress.mScope = prop_scope;
118 
119   /* Get the length of the device name */
120   status = AudioObjectGetPropertyDataSize (device_id,
121       &deviceNameAddress, 0, NULL, &propertySize);
122   if (status != noErr) {
123     goto beach;
124   }
125 
126   /* Get the name of the device */
127   device_name = (gchar *) g_malloc (propertySize);
128   status = AudioObjectGetPropertyData (device_id,
129       &deviceNameAddress, 0, NULL, &propertySize, device_name);
130   if (status != noErr) {
131     g_free (device_name);
132     device_name = NULL;
133   }
134 
135 beach:
136   return device_name;
137 }
138 
139 static inline gboolean
_audio_device_has_output(AudioDeviceID device_id)140 _audio_device_has_output (AudioDeviceID device_id)
141 {
142   OSStatus status = noErr;
143   UInt32 propertySize;
144 
145   AudioObjectPropertyAddress streamsAddress = {
146     kAudioDevicePropertyStreams,
147     kAudioDevicePropertyScopeOutput,
148     kAudioObjectPropertyElementMaster
149   };
150 
151   status = AudioObjectGetPropertyDataSize (device_id,
152       &streamsAddress, 0, NULL, &propertySize);
153 
154   if (status != noErr) {
155     GST_WARNING ("failed getting device property: %d", (int) status);
156     return FALSE;
157   }
158   if (propertySize == 0) {
159     GST_DEBUG ("property size was 0; device has no output channels");
160     return FALSE;
161   }
162 
163   return TRUE;
164 }
165 
166 static inline gboolean
_audio_device_has_input(AudioDeviceID device_id)167 _audio_device_has_input (AudioDeviceID device_id)
168 {
169   OSStatus status = noErr;
170   UInt32 propertySize;
171 
172   AudioObjectPropertyAddress streamsAddress = {
173     kAudioDevicePropertyStreams,
174     kAudioDevicePropertyScopeInput,
175     kAudioObjectPropertyElementMaster
176   };
177 
178   status = AudioObjectGetPropertyDataSize (device_id,
179       &streamsAddress, 0, NULL, &propertySize);
180 
181   if (status != noErr) {
182     GST_WARNING ("failed getting device property: %d", (int) status);
183     return FALSE;
184   }
185   if (propertySize == 0) {
186     GST_DEBUG ("property size was 0; device has no input channels");
187     return FALSE;
188   }
189 
190   return TRUE;
191 }
192 
193 static inline AudioDeviceID *
_audio_system_get_devices(gint * ndevices)194 _audio_system_get_devices (gint * ndevices)
195 {
196   OSStatus status = noErr;
197   UInt32 propertySize = 0;
198   AudioDeviceID *devices = NULL;
199 
200   AudioObjectPropertyAddress audioDevicesAddress = {
201     kAudioHardwarePropertyDevices,
202     kAudioObjectPropertyScopeGlobal,
203     kAudioObjectPropertyElementMaster
204   };
205 
206   status = AudioObjectGetPropertyDataSize (kAudioObjectSystemObject,
207       &audioDevicesAddress, 0, NULL, &propertySize);
208   if (status != noErr) {
209     GST_WARNING ("failed getting number of devices: %d", (int) status);
210     return NULL;
211   }
212 
213   *ndevices = propertySize / sizeof (AudioDeviceID);
214 
215   devices = (AudioDeviceID *) g_malloc (propertySize);
216   if (devices) {
217     status = AudioObjectGetPropertyData (kAudioObjectSystemObject,
218         &audioDevicesAddress, 0, NULL, &propertySize, devices);
219     if (status != noErr) {
220       GST_WARNING ("failed getting the list of devices: %d", (int) status);
221       g_free (devices);
222       *ndevices = 0;
223       return NULL;
224     }
225   }
226 
227   return devices;
228 }
229 
230 static void
gst_osx_audio_device_provider_probe_internal(GstOsxAudioDeviceProvider * self,gboolean is_src,AudioDeviceID * osx_devices,gint ndevices,GList ** devices)231 gst_osx_audio_device_provider_probe_internal (GstOsxAudioDeviceProvider * self,
232     gboolean is_src, AudioDeviceID * osx_devices, gint ndevices,
233     GList ** devices)
234 {
235 
236   gint i = 0;
237   GstOsxAudioDeviceType type = GST_OSX_AUDIO_DEVICE_TYPE_INVALID;
238   GstOsxAudioDevice *device = NULL;
239 
240   if (is_src) {
241     type = GST_OSX_AUDIO_DEVICE_TYPE_SOURCE;
242   } else {
243     type = GST_OSX_AUDIO_DEVICE_TYPE_SINK;
244   }
245 
246   for (i = 0; i < ndevices; i++) {
247     gchar *device_name;
248 
249     if ((device_name = _audio_device_get_name (osx_devices[i], FALSE))) {
250       gboolean has_output = _audio_device_has_output (osx_devices[i]);
251       gboolean has_input = _audio_device_has_input (osx_devices[i]);
252 
253       if (is_src && !has_input) {
254         goto cleanup;
255       } else if (!is_src && !has_output) {
256         goto cleanup;
257       }
258 
259       device =
260           gst_osx_audio_device_provider_probe_device (self, osx_devices[i],
261           device_name, type);
262       if (device) {
263         if (is_src) {
264           GST_DEBUG ("Input Device ID: %u Name: %s",
265               (unsigned) osx_devices[i], device_name);
266         } else {
267           GST_DEBUG ("Output Device ID: %u Name: %s",
268               (unsigned) osx_devices[i], device_name);
269         }
270         gst_object_ref_sink (device);
271         *devices = g_list_prepend (*devices, device);
272       }
273 
274     cleanup:
275       g_free (device_name);
276     }
277   }
278 }
279 
280 static GList *
gst_osx_audio_device_provider_probe(GstDeviceProvider * provider)281 gst_osx_audio_device_provider_probe (GstDeviceProvider * provider)
282 {
283   GstOsxAudioDeviceProvider *self = GST_OSX_AUDIO_DEVICE_PROVIDER (provider);
284   GList *devices = NULL;
285   AudioDeviceID *osx_devices = NULL;
286   gint ndevices = 0;
287 
288   osx_devices = _audio_system_get_devices (&ndevices);
289 
290   if (ndevices < 1) {
291     GST_WARNING ("no audio output devices found");
292     goto done;
293   }
294 
295   GST_INFO ("found %d audio device(s)", ndevices);
296 
297   gst_osx_audio_device_provider_probe_internal (self, TRUE, osx_devices,
298       ndevices, &devices);
299   gst_osx_audio_device_provider_probe_internal (self, FALSE, osx_devices,
300       ndevices, &devices);
301 
302 done:
303   g_free (osx_devices);
304 
305   return devices;
306 }
307 
308 enum
309 {
310   PROP_DEVICE_ID = 1,
311 };
312 
313 G_DEFINE_TYPE (GstOsxAudioDevice, gst_osx_audio_device, GST_TYPE_DEVICE);
314 
315 static void gst_osx_audio_device_get_property (GObject * object, guint prop_id,
316     GValue * value, GParamSpec * pspec);
317 static void gst_osx_audio_device_set_property (GObject * object, guint prop_id,
318     const GValue * value, GParamSpec * pspec);
319 static GstElement *gst_osx_audio_device_create_element (GstDevice * device,
320     const gchar * name);
321 
322 static void
gst_osx_audio_device_class_init(GstOsxAudioDeviceClass * klass)323 gst_osx_audio_device_class_init (GstOsxAudioDeviceClass * klass)
324 {
325   GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass);
326   GObjectClass *object_class = G_OBJECT_CLASS (klass);
327 
328   dev_class->create_element = gst_osx_audio_device_create_element;
329 
330   object_class->get_property = gst_osx_audio_device_get_property;
331   object_class->set_property = gst_osx_audio_device_set_property;
332 
333   g_object_class_install_property (object_class, PROP_DEVICE_ID,
334       g_param_spec_int ("device-id", "Device ID", "Device ID of input device",
335           0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
336 }
337 
338 static void
gst_osx_audio_device_init(GstOsxAudioDevice * device)339 gst_osx_audio_device_init (GstOsxAudioDevice * device)
340 {
341 }
342 
343 static GstElement *
gst_osx_audio_device_create_element(GstDevice * device,const gchar * name)344 gst_osx_audio_device_create_element (GstDevice * device, const gchar * name)
345 {
346   GstOsxAudioDevice *osxdev = GST_OSX_AUDIO_DEVICE (device);
347   GstElement *elem;
348 
349   elem = gst_element_factory_make (osxdev->element, name);
350   g_object_set (elem, "device", osxdev->device_id, NULL);
351 
352   return elem;
353 }
354 
355 static GstOsxAudioDevice *
gst_osx_audio_device_new(AudioDeviceID device_id,const gchar * device_name,GstOsxAudioDeviceType type,GstCoreAudio * core_audio)356 gst_osx_audio_device_new (AudioDeviceID device_id, const gchar * device_name,
357     GstOsxAudioDeviceType type, GstCoreAudio * core_audio)
358 {
359   GstOsxAudioDevice *gstdev;
360   const gchar *element_name = NULL;
361   const gchar *klass = NULL;
362   GstCaps *template_caps, *caps;
363 
364   g_return_val_if_fail (device_id > 0, NULL);
365   g_return_val_if_fail (device_name, NULL);
366 
367   switch (type) {
368     case GST_OSX_AUDIO_DEVICE_TYPE_SOURCE:
369       element_name = "osxaudiosrc";
370       klass = "Audio/Source";
371 
372       template_caps = gst_static_pad_template_get_caps (&src_factory);
373       caps = gst_core_audio_probe_caps (core_audio, template_caps);
374       gst_caps_unref (template_caps);
375 
376       break;
377     case GST_OSX_AUDIO_DEVICE_TYPE_SINK:
378       element_name = "osxaudiosink";
379       klass = "Audio/Sink";
380 
381       template_caps = gst_static_pad_template_get_caps (&sink_factory);
382       caps = gst_core_audio_probe_caps (core_audio, template_caps);
383       gst_caps_unref (template_caps);
384 
385       break;
386     default:
387       g_assert_not_reached ();
388       break;
389   }
390 
391   gstdev = g_object_new (GST_TYPE_OSX_AUDIO_DEVICE, "device-id",
392       device_id, "display-name", device_name, "caps", caps, "device-class",
393       klass, NULL);
394 
395   gstdev->element = element_name;
396 
397 
398   return gstdev;
399 }
400 
401 static void
gst_osx_audio_device_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)402 gst_osx_audio_device_get_property (GObject * object, guint prop_id,
403     GValue * value, GParamSpec * pspec)
404 {
405   GstOsxAudioDevice *device;
406 
407   device = GST_OSX_AUDIO_DEVICE_CAST (object);
408 
409   switch (prop_id) {
410     case PROP_DEVICE_ID:
411       g_value_set_int (value, device->device_id);
412       break;
413     default:
414       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
415       break;
416   }
417 }
418 
419 
420 static void
gst_osx_audio_device_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)421 gst_osx_audio_device_set_property (GObject * object, guint prop_id,
422     const GValue * value, GParamSpec * pspec)
423 {
424   GstOsxAudioDevice *device;
425 
426   device = GST_OSX_AUDIO_DEVICE_CAST (object);
427 
428   switch (prop_id) {
429     case PROP_DEVICE_ID:
430       device->device_id = g_value_get_int (value);
431       break;
432     default:
433       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
434       break;
435   }
436 }
437