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   if (status != noErr) {
154     return FALSE;
155   }
156   if (propertySize == 0) {
157     return FALSE;
158   }
159 
160   return TRUE;
161 }
162 
163 static inline AudioDeviceID *
_audio_system_get_devices(gint * ndevices)164 _audio_system_get_devices (gint * ndevices)
165 {
166   OSStatus status = noErr;
167   UInt32 propertySize = 0;
168   AudioDeviceID *devices = NULL;
169 
170   AudioObjectPropertyAddress audioDevicesAddress = {
171     kAudioHardwarePropertyDevices,
172     kAudioObjectPropertyScopeGlobal,
173     kAudioObjectPropertyElementMaster
174   };
175 
176   status = AudioObjectGetPropertyDataSize (kAudioObjectSystemObject,
177       &audioDevicesAddress, 0, NULL, &propertySize);
178   if (status != noErr) {
179     GST_WARNING ("failed getting number of devices: %d", (int) status);
180     return NULL;
181   }
182 
183   *ndevices = propertySize / sizeof (AudioDeviceID);
184 
185   devices = (AudioDeviceID *) g_malloc (propertySize);
186   if (devices) {
187     status = AudioObjectGetPropertyData (kAudioObjectSystemObject,
188         &audioDevicesAddress, 0, NULL, &propertySize, devices);
189     if (status != noErr) {
190       GST_WARNING ("failed getting the list of devices: %d", (int) status);
191       g_free (devices);
192       *ndevices = 0;
193       return NULL;
194     }
195   }
196 
197   return devices;
198 }
199 
200 static GList *
gst_osx_audio_device_provider_probe(GstDeviceProvider * provider)201 gst_osx_audio_device_provider_probe (GstDeviceProvider * provider)
202 {
203   GstOsxAudioDeviceProvider *self = GST_OSX_AUDIO_DEVICE_PROVIDER (provider);
204   GList *devices = NULL;
205   GstOsxAudioDevice *device = NULL;
206   AudioDeviceID *osx_devices = NULL;
207   gint i, ndevices = 0;
208 
209   osx_devices = _audio_system_get_devices (&ndevices);
210 
211   if (ndevices < 1) {
212     GST_WARNING ("no audio output devices found");
213     goto done;
214   }
215 
216   GST_INFO ("found %d audio device(s)", ndevices);
217 
218   for (i = 0; i < ndevices; i++) {
219     gchar *device_name;
220     GstOsxAudioDeviceType type = GST_OSX_AUDIO_DEVICE_TYPE_INVALID;
221 
222     if ((device_name = _audio_device_get_name (osx_devices[i], FALSE))) {
223       if (!_audio_device_has_output (osx_devices[i])) {
224         GST_DEBUG ("Input Device ID: %u Name: %s",
225             (unsigned) osx_devices[i], device_name);
226         type = GST_OSX_AUDIO_DEVICE_TYPE_SOURCE;
227 
228       } else {
229         GST_DEBUG ("Output Device ID: %u Name: %s",
230             (unsigned) osx_devices[i], device_name);
231         type = GST_OSX_AUDIO_DEVICE_TYPE_SINK;
232       }
233 
234       device =
235           gst_osx_audio_device_provider_probe_device (self, osx_devices[i],
236           device_name, type);
237       if (device) {
238         gst_object_ref_sink (device);
239         devices = g_list_prepend (devices, device);
240       }
241 
242       g_free (device_name);
243     }
244   }
245 
246 done:
247   g_free (osx_devices);
248 
249   return devices;
250 }
251 
252 enum
253 {
254   PROP_DEVICE_ID = 1,
255 };
256 
257 G_DEFINE_TYPE (GstOsxAudioDevice, gst_osx_audio_device, GST_TYPE_DEVICE);
258 
259 static void gst_osx_audio_device_get_property (GObject * object, guint prop_id,
260     GValue * value, GParamSpec * pspec);
261 static void gst_osx_audio_device_set_property (GObject * object, guint prop_id,
262     const GValue * value, GParamSpec * pspec);
263 static GstElement *gst_osx_audio_device_create_element (GstDevice * device,
264     const gchar * name);
265 
266 static void
gst_osx_audio_device_class_init(GstOsxAudioDeviceClass * klass)267 gst_osx_audio_device_class_init (GstOsxAudioDeviceClass * klass)
268 {
269   GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass);
270   GObjectClass *object_class = G_OBJECT_CLASS (klass);
271 
272   dev_class->create_element = gst_osx_audio_device_create_element;
273 
274   object_class->get_property = gst_osx_audio_device_get_property;
275   object_class->set_property = gst_osx_audio_device_set_property;
276 
277   g_object_class_install_property (object_class, PROP_DEVICE_ID,
278       g_param_spec_int ("device-id", "Device ID", "Device ID of input device",
279           0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
280 }
281 
282 static void
gst_osx_audio_device_init(GstOsxAudioDevice * device)283 gst_osx_audio_device_init (GstOsxAudioDevice * device)
284 {
285 }
286 
287 static GstElement *
gst_osx_audio_device_create_element(GstDevice * device,const gchar * name)288 gst_osx_audio_device_create_element (GstDevice * device, const gchar * name)
289 {
290   GstOsxAudioDevice *osxdev = GST_OSX_AUDIO_DEVICE (device);
291   GstElement *elem;
292 
293   elem = gst_element_factory_make (osxdev->element, name);
294   g_object_set (elem, "device", osxdev->device_id, NULL);
295 
296   return elem;
297 }
298 
299 static GstOsxAudioDevice *
gst_osx_audio_device_new(AudioDeviceID device_id,const gchar * device_name,GstOsxAudioDeviceType type,GstCoreAudio * core_audio)300 gst_osx_audio_device_new (AudioDeviceID device_id, const gchar * device_name,
301     GstOsxAudioDeviceType type, GstCoreAudio * core_audio)
302 {
303   GstOsxAudioDevice *gstdev;
304   const gchar *element_name = NULL;
305   const gchar *klass = NULL;
306   GstCaps *template_caps, *caps;
307 
308   g_return_val_if_fail (device_id > 0, NULL);
309   g_return_val_if_fail (device_name, NULL);
310 
311   switch (type) {
312     case GST_OSX_AUDIO_DEVICE_TYPE_SOURCE:
313       element_name = "osxaudiosrc";
314       klass = "Audio/Source";
315 
316       template_caps = gst_static_pad_template_get_caps (&src_factory);
317       caps = gst_core_audio_probe_caps (core_audio, template_caps);
318       gst_caps_unref (template_caps);
319 
320       break;
321     case GST_OSX_AUDIO_DEVICE_TYPE_SINK:
322       element_name = "osxaudiosink";
323       klass = "Audio/Sink";
324 
325       template_caps = gst_static_pad_template_get_caps (&sink_factory);
326       caps = gst_core_audio_probe_caps (core_audio, template_caps);
327       gst_caps_unref (template_caps);
328 
329       break;
330     default:
331       g_assert_not_reached ();
332       break;
333   }
334 
335   gstdev = g_object_new (GST_TYPE_OSX_AUDIO_DEVICE, "device-id",
336       device_id, "display-name", device_name, "caps", caps, "device-class",
337       klass, NULL);
338 
339   gstdev->element = element_name;
340 
341 
342   return gstdev;
343 }
344 
345 static void
gst_osx_audio_device_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)346 gst_osx_audio_device_get_property (GObject * object, guint prop_id,
347     GValue * value, GParamSpec * pspec)
348 {
349   GstOsxAudioDevice *device;
350 
351   device = GST_OSX_AUDIO_DEVICE_CAST (object);
352 
353   switch (prop_id) {
354     case PROP_DEVICE_ID:
355       g_value_set_int (value, device->device_id);
356       break;
357     default:
358       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
359       break;
360   }
361 }
362 
363 
364 static void
gst_osx_audio_device_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)365 gst_osx_audio_device_set_property (GObject * object, guint prop_id,
366     const GValue * value, GParamSpec * pspec)
367 {
368   GstOsxAudioDevice *device;
369 
370   device = GST_OSX_AUDIO_DEVICE_CAST (object);
371 
372   switch (prop_id) {
373     case PROP_DEVICE_ID:
374       device->device_id = g_value_get_int (value);
375       break;
376     default:
377       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
378       break;
379   }
380 }
381