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