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