@@ -54,6 +54,14 @@ G_DEFINE_TYPE (GstOsxAudioDeviceProvider, gst_osx_audio_device_provider,
5454
5555static GList * gst_osx_audio_device_provider_probe (GstDeviceProvider *
5656 provider );
57+ static gboolean gst_osx_audio_device_provider_start (GstDeviceProvider * provider );
58+ static void gst_osx_audio_device_provider_stop (GstDeviceProvider * provider );
59+ static OSStatus gst_osx_audio_device_change_cb (AudioObjectID inObjectID ,
60+ UInt32 inNumberAddresses ,
61+ const AudioObjectPropertyAddress * inAddresses ,
62+ void * inClientData );
63+ static void
64+ gst_osx_audio_device_provider_update_devices (GstOsxAudioDeviceProvider * provider );
5765
5866static void
5967gst_osx_audio_device_provider_class_init (GstOsxAudioDeviceProviderClass *
@@ -62,18 +70,112 @@ gst_osx_audio_device_provider_class_init (GstOsxAudioDeviceProviderClass *
6270 GstDeviceProviderClass * dm_class = GST_DEVICE_PROVIDER_CLASS (klass );
6371
6472 dm_class -> probe = gst_osx_audio_device_provider_probe ;
73+ dm_class -> start = gst_osx_audio_device_provider_start ;
74+ dm_class -> stop = gst_osx_audio_device_provider_stop ;
6575
6676 gst_device_provider_class_set_static_metadata (dm_class ,
6777 "OSX Audio Device Provider" , "Source/Sink/Audio" ,
6878 "List and monitor OSX audio source and sink devices" ,
6979 "Hyunjun Ko <[email protected] >" );
7080}
7181
82+ static OSStatus gst_osx_audio_device_change_cb (AudioObjectID inObjectID ,
83+ guint32 inNumberAddresses ,
84+ const AudioObjectPropertyAddress * inAddresses ,
85+ void * userdata ) {
86+ GstOsxAudioDeviceProvider * provider = (GstOsxAudioDeviceProvider * ) userdata ;
87+
88+ for (guint32 i = 0 ; i < inNumberAddresses ; i ++ ) {
89+ switch (inAddresses [i ].mSelector ) {
90+ case kAudioHardwarePropertyDefaultInputDevice :
91+ gst_osx_audio_device_provider_update_devices (provider );
92+ break ;
93+ case kAudioHardwarePropertyDefaultOutputDevice :
94+ gst_osx_audio_device_provider_update_devices (provider );
95+ break ;
96+ case kAudioHardwarePropertyDevices :
97+ gst_osx_audio_device_provider_update_devices (provider );
98+ break ;
99+ default :
100+ break ;
101+ }
102+ }
103+ return noErr ;
104+ }
105+
72106static void
73107gst_osx_audio_device_provider_init (GstOsxAudioDeviceProvider * provider )
74108{
75109}
76110
111+ static gboolean
112+ gst_osx_audio_device_provider_start (GstDeviceProvider * provider )
113+ {
114+ GstOsxAudioDeviceProvider * self = GST_OSX_AUDIO_DEVICE_PROVIDER (provider );
115+
116+ // Register callbacks for the following AudioObjectIDs
117+ AudioObjectID event_ids [] = {kAudioHardwarePropertyDevices , kAudioHardwarePropertyDefaultInputDevice , kAudioHardwarePropertyDefaultOutputDevice };
118+
119+ for (size_t i = 0 ; i < sizeof (event_ids ) / sizeof (event_ids [0 ]); i ++ ){
120+ AudioObjectPropertyAddress deviceListAddr = {
121+ .mSelector = event_ids [i ],
122+ .mScope = kAudioObjectPropertyScopeGlobal ,
123+ .mElement = kAudioObjectPropertyElementMain
124+ };
125+
126+ OSStatus err =
127+ AudioObjectAddPropertyListener (kAudioObjectSystemObject ,
128+ & deviceListAddr ,
129+ gst_osx_audio_device_change_cb ,
130+ (void * )self );
131+
132+ if (err != noErr ) {
133+ GST_ERROR ("Failed to register AudioObjectAddPropertyListener(%u) %d" , event_ids [i ], err );
134+ return FALSE;
135+ }
136+ }
137+
138+ /* baseclass will not call probe() once it's started, but we can get
139+ * notification only add/remove or change case. To this manually */
140+ GList * devices = gst_osx_audio_device_provider_probe (provider );
141+ if (devices ) {
142+ GList * iter ;
143+ for (iter = devices ; iter ; iter = g_list_next (iter )) {
144+ gst_device_provider_device_add (provider , GST_DEVICE (iter -> data ));
145+ }
146+ g_list_free (devices );
147+ }
148+
149+ return TRUE;
150+ }
151+
152+ static void
153+ gst_osx_audio_device_provider_stop (GstDeviceProvider * provider )
154+ {
155+ GstOsxAudioDeviceProvider * self = GST_OSX_AUDIO_DEVICE_PROVIDER (provider );
156+
157+ // De-register callbacks for the following AudioObjectIDs
158+ AudioObjectID event_ids [] = {kAudioHardwarePropertyDevices , kAudioHardwarePropertyDefaultInputDevice , kAudioHardwarePropertyDefaultOutputDevice };
159+
160+ for (size_t i = 0 ; i < sizeof (event_ids ) / sizeof (event_ids [0 ]); i ++ ){
161+ AudioObjectPropertyAddress deviceListAddr = {
162+ .mSelector = event_ids [i ],
163+ .mScope = kAudioObjectPropertyScopeGlobal ,
164+ .mElement = kAudioObjectPropertyElementMain
165+ };
166+
167+ OSStatus err =
168+ AudioObjectRemovePropertyListener (kAudioObjectSystemObject ,
169+ & deviceListAddr ,
170+ gst_osx_audio_device_change_cb ,
171+ (void * )self );
172+
173+ if (err != noErr ) {
174+ GST_ERROR ("Failed to de-register AudioObjectAddPropertyListener(%u) %d" , event_ids [i ], err );
175+ }
176+ }
177+ }
178+
77179static GstOsxAudioDevice *
78180gst_osx_audio_device_provider_probe_device (GstOsxAudioDeviceProvider *
79181 provider , AudioDeviceID device_id , const gchar * device_name ,
@@ -344,6 +446,99 @@ gst_osx_audio_device_provider_probe (GstDeviceProvider * provider)
344446 return devices ;
345447}
346448
449+ static gboolean
450+ gst_osx_audio_device_is_in_list (GList * list , GstDevice * device )
451+ {
452+ GList * iter ;
453+ GstStructure * s ;
454+ AudioDeviceID device_id ;
455+ gboolean device_is_default ;
456+ gboolean found = FALSE;
457+
458+ s = gst_device_get_properties (device );
459+ g_assert (s );
460+ g_assert (gst_structure_get_int (s , "device-id" , & device_id ) == TRUE);
461+ g_assert (gst_structure_get_boolean (s , "is-default" , & device_is_default ) == TRUE);
462+
463+ for (iter = list ; iter ; iter = g_list_next (iter )) {
464+ GstStructure * other_s ;
465+ AudioDeviceID other_device_id ;
466+ gboolean other_device_is_default ;
467+
468+ other_s = gst_device_get_properties (GST_DEVICE (iter -> data ));
469+ g_assert (other_s );
470+
471+ g_assert (gst_structure_get_int (other_s , "device-id" , & other_device_id ) == TRUE);
472+ g_assert (gst_structure_get_boolean (other_s , "is-default" , & other_device_is_default ) == TRUE);
473+
474+ if (device_id == other_device_id && device_is_default == other_device_is_default ) {
475+ found = TRUE;
476+ }
477+
478+ gst_structure_free (other_s );
479+ if (found )
480+ break ;
481+ }
482+
483+ gst_structure_free (s );
484+
485+ return found ;
486+ }
487+
488+ static void
489+ gst_osx_audio_device_provider_update_devices (GstOsxAudioDeviceProvider * self )
490+ {
491+ GstDeviceProvider * provider = GST_DEVICE_PROVIDER_CAST (self );
492+ GList * prev_devices = NULL ;
493+ GList * new_devices = NULL ;
494+ GList * to_add = NULL ;
495+ GList * to_remove = NULL ;
496+ GList * iter ;
497+
498+ GST_OBJECT_LOCK (self );
499+ prev_devices = g_list_copy_deep (provider -> devices ,
500+ (GCopyFunc ) gst_object_ref , NULL );
501+ GST_OBJECT_UNLOCK (self );
502+
503+ new_devices = gst_osx_audio_device_provider_probe (provider );
504+
505+ /* Ownership of GstDevice for gst_device_provider_device_add()
506+ * and gst_device_provider_device_remove() is a bit complicated.
507+ * Remove floating reference here for things to be clear */
508+ for (iter = new_devices ; iter ; iter = g_list_next (iter ))
509+ gst_object_ref_sink (iter -> data );
510+
511+ /* Check newly added devices */
512+ for (iter = new_devices ; iter ; iter = g_list_next (iter )) {
513+ if (!gst_osx_audio_device_is_in_list (prev_devices , GST_DEVICE (iter -> data ))) {
514+ to_add = g_list_prepend (to_add , gst_object_ref (iter -> data ));
515+ }
516+ }
517+
518+ /* Check removed device */
519+ for (iter = prev_devices ; iter ; iter = g_list_next (iter )) {
520+ if (!gst_osx_audio_device_is_in_list (new_devices , GST_DEVICE (iter -> data ))) {
521+ to_remove = g_list_prepend (to_remove , gst_object_ref (iter -> data ));
522+ }
523+ }
524+
525+ for (iter = to_remove ; iter ; iter = g_list_next (iter ))
526+ gst_device_provider_device_remove (provider , GST_DEVICE (iter -> data ));
527+
528+ for (iter = to_add ; iter ; iter = g_list_next (iter ))
529+ gst_device_provider_device_add (provider , GST_DEVICE (iter -> data ));
530+
531+ if (prev_devices )
532+ g_list_free_full (prev_devices , (GDestroyNotify ) gst_object_unref );
533+
534+ if (to_add )
535+ g_list_free_full (to_add , (GDestroyNotify ) gst_object_unref );
536+
537+ if (to_remove )
538+ g_list_free_full (to_remove , (GDestroyNotify ) gst_object_unref );
539+ }
540+
541+
347542enum
348543{
349544 PROP_DEVICE_ID = 1 ,
@@ -430,6 +625,7 @@ gst_osx_audio_device_new (AudioDeviceID device_id, const gchar * device_name,
430625 }
431626
432627 GstStructure * props = gst_structure_new ("osxaudiodevice-proplist" ,
628+ "device-id" , G_TYPE_INT , device_id ,
433629 "is-default" , G_TYPE_BOOLEAN , is_default , NULL );
434630 gstdev = g_object_new (GST_TYPE_OSX_AUDIO_DEVICE , "device-id" ,
435631 device_id , "display-name" , device_name , "caps" , caps , "device-class" ,
0 commit comments