Bug 1027713 - Part 3 - Add a cubeb API to signal that the output device changed. r=kinetik
☠☠ backed out by 5dc0231d0153 ☠ ☠
authorPaul Adenot <paul@paul.cx>
Thu, 24 Jul 2014 17:05:23 +0200
changeset 196016 c0045bb1849ed7cbe7e06c92318a8e7b01314428
parent 196015 274b2b25d167f8fccfd0d4dc034e5392f09cec9f
child 196017 b6bb388463335a6129c386b3a3d0cc82a2725a00
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewerskinetik
bugs1027713
milestone34.0a1
Bug 1027713 - Part 3 - Add a cubeb API to signal that the output device changed. r=kinetik The reentrant mutex is needed so that users can call back into cubeb's API from the callback.
layout/media/symbols.def.in
media/libcubeb/include/cubeb.h
media/libcubeb/src/cubeb-internal.h
media/libcubeb/src/cubeb.c
media/libcubeb/src/cubeb_alsa.c
media/libcubeb/src/cubeb_audiotrack.c
media/libcubeb/src/cubeb_audiounit.c
media/libcubeb/src/cubeb_opensl.c
media/libcubeb/src/cubeb_pulse.c
media/libcubeb/src/cubeb_sndio.c
media/libcubeb/src/cubeb_wasapi.cpp
media/libcubeb/src/cubeb_winmm.c
--- a/layout/media/symbols.def.in
+++ b/layout/media/symbols.def.in
@@ -134,16 +134,17 @@ cubeb_stream_get_position
 cubeb_stream_init
 cubeb_stream_start
 cubeb_stream_stop
 cubeb_stream_get_latency
 cubeb_stream_set_volume
 cubeb_stream_set_panning
 cubeb_stream_get_current_output_device
 cubeb_stream_output_device_destroy
+cubeb_stream_register_device_changed_callback
 th_comment_clear
 th_comment_init
 th_decode_alloc
 th_decode_free
 th_decode_headerin
 th_decode_packetin
 th_decode_ycbcr_out
 th_granule_frame
--- a/media/libcubeb/include/cubeb.h
+++ b/media/libcubeb/include/cubeb.h
@@ -166,16 +166,21 @@ typedef long (* cubeb_data_callback)(cub
 /** User supplied state callback.
     @param stream
     @param user_ptr
     @param state */
 typedef void (* cubeb_state_callback)(cubeb_stream * stream,
                                       void * user_ptr,
                                       cubeb_state state);
 
+/**
+ * User supplied callback called when the underlying device changed.
+ * @param user */
+typedef void (* cubeb_device_changed_callback)(void * user_ptr);
+
 /** Initialize an application context.  This will perform any library or
     application scoped initialization.
     @param context
     @param context_name
     @retval CUBEB_OK
     @retval CUBEB_ERROR */
 int cubeb_init(cubeb ** context, char const * context_name);
 
@@ -306,13 +311,25 @@ int cubeb_stream_get_current_output_devi
  * @param stream the stream passed in cubeb_stream_get_current_output_device
  * @param devices the devices to destroy
  * @return CUBEB_OK in case of success
  * @return CUBEB_ERROR_INVALID_PARAMETER if devices is an invalid pointer
  */
 int cubeb_stream_output_device_destroy(cubeb_stream * stream,
                                        cubeb_output_device * devices);
 
+/**
+ * Set a callback to be notified when the output device changes.
+ * @param stream the stream for which to set the callback.
+ * @param device_changed_callback a function called whenever the device has
+ *        changed. Passing NULL allow to unregister a function
+ * @return CUBEB_ERROR_INVALID_PARAMETER if either stream or
+ *         device_changed_callback are invalid pointers.
+ * @return CUBEB_OK
+ */
+int cubeb_stream_register_device_changed_callback(cubeb_stream * stream,
+                                                  cubeb_device_changed_callback  device_changed_callback);
+
 #if defined(__cplusplus)
 }
 #endif
 
 #endif /* CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382 */
--- a/media/libcubeb/src/cubeb-internal.h
+++ b/media/libcubeb/src/cubeb-internal.h
@@ -29,12 +29,15 @@ struct cubeb_ops {
   int (* stream_get_position)(cubeb_stream * stream, uint64_t * position);
   int (* stream_get_latency)(cubeb_stream * stream, uint32_t * latency);
   int (* stream_set_volume)(cubeb_stream * stream, float volumes);
   int (* stream_set_panning)(cubeb_stream * stream, float panning);
   int (* stream_get_current_output_device)(cubeb_stream * stream,
                                            cubeb_output_device ** const device);
   int (* stream_output_device_destroy)(cubeb_stream * stream,
                                        cubeb_output_device * device);
+  int (*stream_register_device_changed_callback)(cubeb_stream * stream,
+                                                 cubeb_device_changed_callback device_changed_callback);
+
 };
 
 #endif /* CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5 */
 
--- a/media/libcubeb/src/cubeb.c
+++ b/media/libcubeb/src/cubeb.c
@@ -305,8 +305,24 @@ int cubeb_stream_output_device_destroy(c
   // If we find an implementation, call the function, it might not be available
   // on some platforms.
   if (stream->context->ops->stream_output_device_destroy) {
     return stream->context->ops->stream_output_device_destroy(stream, device);
   }
 
   return CUBEB_ERROR;
 }
+
+int cubeb_stream_register_device_changed_callback(cubeb_stream * stream,
+                                                  cubeb_device_changed_callback device_changed_callback)
+{
+  if (!stream || !device_changed_callback) {
+    return CUBEB_ERROR_INVALID_PARAMETER;
+  }
+
+  if (stream->context->ops->stream_register_device_changed_callback) {
+    return stream->context->ops->
+      stream_register_device_changed_callback(stream,
+                                              device_changed_callback);
+  }
+
+  return CUBEB_ERROR;
+}
--- a/media/libcubeb/src/cubeb_alsa.c
+++ b/media/libcubeb/src/cubeb_alsa.c
@@ -1127,10 +1127,11 @@ static struct cubeb_ops const alsa_ops =
   .stream_destroy = alsa_stream_destroy,
   .stream_start = alsa_stream_start,
   .stream_stop = alsa_stream_stop,
   .stream_get_position = alsa_stream_get_position,
   .stream_get_latency = alsa_stream_get_latency,
   .stream_set_volume = alsa_stream_set_volume,
   .stream_set_panning = alsa_stream_set_panning,
   .stream_get_current_output_device = NULL,
-  .stream_output_device_destroy = NULL
+  .stream_output_device_destroy = NULL,
+  .stream_register_device_changed_callback = NULL
 };
--- a/media/libcubeb/src/cubeb_audiotrack.c
+++ b/media/libcubeb/src/cubeb_audiotrack.c
@@ -501,10 +501,11 @@ static struct cubeb_ops const audiotrack
   .stream_destroy = audiotrack_stream_destroy,
   .stream_start = audiotrack_stream_start,
   .stream_stop = audiotrack_stream_stop,
   .stream_get_position = audiotrack_stream_get_position,
   .stream_get_latency = audiotrack_stream_get_latency,
   .stream_set_volume = audiotrack_stream_set_volume,
   .stream_set_panning = audiotrack_stream_set_panning,
   .stream_get_current_output_device = NULL,
-  .stream_output_device_destroy = NULL
+  .stream_output_device_destroy = NULL,
+  .stream_register_device_changed_callback = NULL
 };
--- a/media/libcubeb/src/cubeb_audiounit.c
+++ b/media/libcubeb/src/cubeb_audiounit.c
@@ -33,16 +33,17 @@ struct cubeb {
   int limit_streams;
 };
 
 struct cubeb_stream {
   cubeb * context;
   AudioUnit unit;
   cubeb_data_callback data_callback;
   cubeb_state_callback state_callback;
+  cubeb_device_changed_callback device_changed_callback;
   void * user_ptr;
   AudioStreamBasicDescription sample_spec;
   pthread_mutex_t mutex;
   uint64_t frames_played;
   uint64_t frames_queued;
   int shutdown;
   int draining;
   uint64_t current_latency_frames;
@@ -176,16 +177,130 @@ audiounit_get_output_device_id(AudioDevi
                                  device_id);
   if (r != noErr) {
     return CUBEB_ERROR;
   }
 
   return CUBEB_OK;
 }
 
+static int audiounit_install_device_changed_callback(cubeb_stream * stm);
+static int audiounit_uninstall_device_changed_callback();
+
+static OSStatus
+audiounit_property_listener_callback(AudioObjectID id, UInt32 address_count,
+                                     const AudioObjectPropertyAddress * addresses,
+                                     void * user)
+{
+  cubeb_stream * stm = (cubeb_stream*) user;
+
+  for (UInt32 i = 0; i < address_count; i++) {
+    switch(addresses[i].mSelector) {
+      case kAudioHardwarePropertyDefaultOutputDevice:
+        /* fall through */
+      case kAudioDevicePropertyDataSource:
+        pthread_mutex_lock(&stm->mutex);
+        if (stm->device_changed_callback) {
+          stm->device_changed_callback(stm->user_ptr);
+        }
+        pthread_mutex_unlock(&stm->mutex);
+        break;
+    }
+  }
+
+  return noErr;
+}
+
+static int
+audiounit_install_device_changed_callback(cubeb_stream * stm)
+{
+  OSStatus r;
+  AudioDeviceID id;
+
+  /* This event will notify us when the data source on the same device changes,
+   * for example when the user plugs in a normal (non-usb) headset in the
+   * headphone jack. */
+  AudioObjectPropertyAddress alive_address = {
+    kAudioDevicePropertyDataSource,
+    kAudioObjectPropertyScopeGlobal,
+    kAudioObjectPropertyElementMaster
+  };
+
+  if (audiounit_get_output_device_id(&id) != noErr) {
+    return CUBEB_ERROR;
+  }
+
+  r = AudioObjectAddPropertyListener(id, &alive_address,
+                                     &audiounit_property_listener_callback,
+                                     stm);
+  if (r != noErr) {
+    return CUBEB_ERROR;
+  }
+
+  /* This event will notify us when the default audio device changes,
+   * for example when the user plugs in a USB headset and the system chooses it
+   * automatically as the default, or when another device is chosen in the
+   * dropdown list. */
+  AudioObjectPropertyAddress default_device_address = {
+    kAudioHardwarePropertyDefaultOutputDevice,
+    kAudioObjectPropertyScopeGlobal,
+    kAudioObjectPropertyElementMaster
+  };
+
+  r = AudioObjectAddPropertyListener(kAudioObjectSystemObject,
+                                     &default_device_address,
+                                     &audiounit_property_listener_callback,
+                                     stm);
+  if (r != noErr) {
+    return CUBEB_ERROR;
+  }
+
+  return CUBEB_OK;
+}
+
+static int
+audiounit_uninstall_device_changed_callback()
+{
+  OSStatus r;
+  AudioDeviceID id;
+
+  AudioObjectPropertyAddress datasource_address = {
+    kAudioDevicePropertyDataSource,
+    kAudioObjectPropertyScopeGlobal,
+    kAudioObjectPropertyElementMaster
+  };
+
+  if (audiounit_get_output_device_id(&id) != noErr) {
+    return CUBEB_ERROR;
+  }
+
+  r = AudioObjectRemovePropertyListener(id, &datasource_address,
+                                        &audiounit_property_listener_callback,
+                                        NULL);
+  if (r != noErr) {
+    return CUBEB_ERROR;
+  }
+
+  AudioObjectPropertyAddress default_device_address = {
+    kAudioHardwarePropertyDefaultOutputDevice,
+    kAudioObjectPropertyScopeGlobal,
+    kAudioObjectPropertyElementMaster
+  };
+
+  r = AudioObjectRemovePropertyListener(kAudioObjectSystemObject,
+                                        &default_device_address,
+                                        &audiounit_property_listener_callback,
+                                        NULL);
+  if (r != noErr) {
+    return CUBEB_ERROR;
+  }
+
+  return CUBEB_OK;
+}
+
 /* Get the acceptable buffer size (in frames) that this device can work with. */
 static int
 audiounit_get_acceptable_latency_range(AudioValueRange * latency_range)
 {
   UInt32 size;
   OSStatus r;
   AudioDeviceID output_device_id;
   AudioObjectPropertyAddress output_device_buffer_size_range = {
@@ -395,20 +510,25 @@ audiounit_stream_init(cubeb * context, c
 
   stm = calloc(1, sizeof(*stm));
   assert(stm);
 
   stm->context = context;
   stm->data_callback = data_callback;
   stm->state_callback = state_callback;
   stm->user_ptr = user_ptr;
+  stm->device_changed_callback = NULL;
 
   stm->sample_spec = ss;
 
-  r = pthread_mutex_init(&stm->mutex, NULL);
+  pthread_mutexattr_t attr;
+  pthread_mutexattr_init(&attr);
+  pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+  r = pthread_mutex_init(&stm->mutex, &attr);
+  pthread_mutexattr_destroy(&attr);
   assert(r == 0);
 
   stm->frames_played = 0;
   stm->frames_queued = 0;
   stm->current_latency_frames = 0;
   stm->hw_latency_frames = UINT64_MAX;
 
 #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060
@@ -483,16 +603,34 @@ audiounit_stream_init(cubeb * context, c
   r = AudioUnitInitialize(stm->unit);
   if (r != 0) {
     audiounit_stream_destroy(stm);
     return CUBEB_ERROR;
   }
 
   *stream = stm;
 
+  /* This is needed so that AudioUnit listeners get called on this thread, and
+   * not the main thread. If we don't do that, they are not called, or a crash
+   * occur, depending on the OSX version. */
+  AudioObjectPropertyAddress runloop_address = {
+    kAudioHardwarePropertyRunLoop,
+    kAudioObjectPropertyScopeGlobal,
+    kAudioObjectPropertyElementMaster
+  };
+
+  CFRunLoopRef run_loop = NULL;
+  r = AudioObjectSetPropertyData(kAudioObjectSystemObject,
+                                 &runloop_address,
+                                 0, NULL, sizeof(CFRunLoopRef), &run_loop);
+
+  /* we dont' check the return value here, because we want to be able to play
+   * even if we can't detect device changes. */
+  audiounit_install_device_changed_callback(stm);
+
   return CUBEB_OK;
 }
 
 static void
 audiounit_stream_destroy(cubeb_stream * stm)
 {
   int r;
 
@@ -503,16 +641,18 @@ audiounit_stream_destroy(cubeb_stream * 
     AudioUnitUninitialize(stm->unit);
 #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060
     CloseComponent(stm->unit);
 #else
     AudioComponentInstanceDispose(stm->unit);
 #endif
   }
 
+  audiounit_uninstall_device_changed_callback();
+
   r = pthread_mutex_destroy(&stm->mutex);
   assert(r == 0);
 
   pthread_mutex_lock(&stm->context->mutex);
   assert(stm->context->active_streams >= 1);
   stm->context->active_streams -= 1;
   pthread_mutex_unlock(&stm->context->mutex);
 
@@ -653,17 +793,17 @@ int audiounit_stream_set_panning(cubeb_s
 
   return CUBEB_OK;
 }
 
 int audiounit_stream_get_current_output_device(cubeb_stream * stm,
                                                cubeb_output_device ** const  device)
 {
   OSStatus r;
-  uint32_t size;
+  UInt32 size;
   UInt32 data;
   char strdata[4];
   AudioDeviceID output_device_id;
 
   AudioObjectPropertyAddress datasource_address = {
     kAudioDevicePropertyDataSource,
     kAudioDevicePropertyScopeOutput,
     kAudioObjectPropertyElementMaster
@@ -672,17 +812,19 @@ int audiounit_stream_get_current_output_
   *device = NULL;
 
   if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) {
     return CUBEB_ERROR;
   }
 
   size = sizeof(UInt32);
   /* This fails with some USB headset, so simply return an empty string. */
-  r = AudioObjectGetPropertyData(output_device_id, &datasource_address, 0, NULL, &size, &data);
+  r = AudioObjectGetPropertyData(output_device_id,
+                                 &datasource_address,
+                                 0, NULL, &size, &data);
   if (r != noErr) {
     size = 0;
     data = 0;
   }
 
   *device = malloc(sizeof(cubeb_output_device));
   if (!*device) {
     return CUBEB_ERROR;
@@ -708,26 +850,37 @@ int audiounit_stream_get_current_output_
 int audiounit_stream_output_device_destroy(cubeb_stream * stream,
                                            cubeb_output_device * device)
 {
   free(device->name);
   free(device);
   return CUBEB_OK;
 }
 
+int audiounit_stream_register_device_changed_callback(cubeb_stream * stream,
+                                                  cubeb_device_changed_callback  device_changed_callback)
+{
+  pthread_mutex_lock(&stream->mutex);
+  stream->device_changed_callback = device_changed_callback;
+  pthread_mutex_unlock(&stream->mutex);
+
+  return CUBEB_OK;
+}
+
 static struct cubeb_ops const audiounit_ops = {
   .init = audiounit_init,
   .get_backend_id = audiounit_get_backend_id,
   .get_max_channel_count = audiounit_get_max_channel_count,
   .get_min_latency = audiounit_get_min_latency,
   .get_preferred_sample_rate = audiounit_get_preferred_sample_rate,
   .destroy = audiounit_destroy,
   .stream_init = audiounit_stream_init,
   .stream_destroy = audiounit_stream_destroy,
   .stream_start = audiounit_stream_start,
   .stream_stop = audiounit_stream_stop,
   .stream_get_position = audiounit_stream_get_position,
   .stream_get_latency = audiounit_stream_get_latency,
   .stream_set_volume = audiounit_stream_set_volume,
   .stream_set_panning = audiounit_stream_set_panning,
   .stream_get_current_output_device = audiounit_stream_get_current_output_device,
-  .stream_output_device_destroy = audiounit_stream_output_device_destroy
+  .stream_output_device_destroy = audiounit_stream_output_device_destroy,
+  .stream_register_device_changed_callback = audiounit_stream_register_device_changed_callback
 };
--- a/media/libcubeb/src/cubeb_opensl.c
+++ b/media/libcubeb/src/cubeb_opensl.c
@@ -784,10 +784,11 @@ static struct cubeb_ops const opensl_ops
   .stream_destroy = opensl_stream_destroy,
   .stream_start = opensl_stream_start,
   .stream_stop = opensl_stream_stop,
   .stream_get_position = opensl_stream_get_position,
   .stream_get_latency = opensl_stream_get_latency,
   .stream_set_volume = opensl_stream_set_volume,
   .stream_set_panning = opensl_stream_set_panning,
   .stream_get_current_output_device = NULL,
-  .stream_output_device_destroy = NULL
+  .stream_output_device_destroy = NULL,
+  .stream_register_device_changed_callback = NULL
 };
--- a/media/libcubeb/src/cubeb_pulse.c
+++ b/media/libcubeb/src/cubeb_pulse.c
@@ -745,10 +745,11 @@ static struct cubeb_ops const pulse_ops 
   .stream_destroy = pulse_stream_destroy,
   .stream_start = pulse_stream_start,
   .stream_stop = pulse_stream_stop,
   .stream_get_position = pulse_stream_get_position,
   .stream_get_latency = pulse_stream_get_latency,
   .stream_set_volume = pulse_stream_set_volume,
   .stream_set_panning = pulse_stream_set_panning,
   .stream_get_current_output_device = NULL,
-  .stream_output_device_destroy = NULL
+  .stream_output_device_destroy = NULL,
+  .stream_register_device_changed_callback = NULL
 };
--- a/media/libcubeb/src/cubeb_sndio.c
+++ b/media/libcubeb/src/cubeb_sndio.c
@@ -358,10 +358,11 @@ static struct cubeb_ops const sndio_ops 
   .stream_destroy = sndio_stream_destroy,
   .stream_start = sndio_stream_start,
   .stream_stop = sndio_stream_stop,
   .stream_get_position = sndio_stream_get_position,
   .stream_get_latency = sndio_stream_get_latency,
   .stream_set_volume = sndio_stream_set_volume,
   .stream_set_panning = sndio_stream_set_panning,
   .stream_get_current_output_device = NULL,
-  .stream_output_device_destroy = NULL
+  .stream_output_device_destroy = NULL,
+  .stream_register_device_changed_callback = NULL
 };
--- a/media/libcubeb/src/cubeb_wasapi.cpp
+++ b/media/libcubeb/src/cubeb_wasapi.cpp
@@ -16,17 +16,17 @@
 #include <process.h>
 #include <avrt.h>
 #include "cubeb/cubeb.h"
 #include "cubeb-internal.h"
 #include "cubeb/cubeb-stdint.h"
 #include "cubeb_resampler.h"
 #include <stdio.h>
 
-#if 0
+#if 1
 #  define LOG(...) do {         \
   fprintf(stderr, __VA_ARGS__); \
   fprintf(stderr, "\n");        \
 } while(0);
 #else
 #  define LOG(...)
 #endif
 
@@ -936,12 +936,13 @@ cubeb_ops const wasapi_ops = {
   /*.stream_destroy =*/ wasapi_stream_destroy,
   /*.stream_start =*/ wasapi_stream_start,
   /*.stream_stop =*/ wasapi_stream_stop,
   /*.stream_get_position =*/ wasapi_stream_get_position,
   /*.stream_get_latency =*/ wasapi_stream_get_latency,
   /*.stream_set_volume =*/ wasapi_stream_set_volume,
   /*.stream_set_panning =*/ wasapi_stream_set_panning,
   /*.stream_get_current_output_device =*/ NULL,
-  /*.stream_output_device_destroy =*/ NULL
+  /*.stream_output_device_destroy =*/ NULL,
+  /*.stream_register_device_changed_callback =*/ NULL
  };
 } // namespace anonymous
 
--- a/media/libcubeb/src/cubeb_winmm.c
+++ b/media/libcubeb/src/cubeb_winmm.c
@@ -711,10 +711,11 @@ static struct cubeb_ops const winmm_ops 
   /*.stream_destroy =*/ winmm_stream_destroy,
   /*.stream_start =*/ winmm_stream_start,
   /*.stream_stop =*/ winmm_stream_stop,
   /*.stream_get_position =*/ winmm_stream_get_position,
   /*.stream_get_latency = */ winmm_stream_get_latency,
   /*.stream_set_volume =*/ winmm_stream_set_volume,
   /*.stream_set_panning =*/ winmm_stream_set_panning,
   /*.stream_get_current_output_device =*/ NULL,
-  /*.stream_output_device_destroy =*/ NULL
+  /*.stream_output_device_destroy =*/ NULL,
+  /*.stream_register_device_changed_callback=*/ NULL
 };