Bug 1500377 - Update cubeb to upstream commit a68892d. r=kinetik
authorAlex Chronopoulos <achronop@gmail.com>
Fri, 19 Oct 2018 22:00:20 +0000
changeset 490581 37df3d44e6882b69f9b8eda9ca67b5d322272fa7
parent 490569 d0f1450799b56b9960ad6df2da432a5fd5849417
child 490582 abbf8a8eed9da1931dbbcdf71810d21ccd3ef333
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewerskinetik
bugs1500377
milestone64.0a1
Bug 1500377 - Update cubeb to upstream commit a68892d. r=kinetik Differential Revision: https://phabricator.services.mozilla.com/D9241
media/libcubeb/README_MOZILLA
media/libcubeb/gtest/common.h
media/libcubeb/gtest/test_audio.cpp
media/libcubeb/gtest/test_devices.cpp
media/libcubeb/gtest/test_duplex.cpp
media/libcubeb/gtest/test_latency.cpp
media/libcubeb/gtest/test_loopback.cpp
media/libcubeb/gtest/test_overload_callback.cpp
media/libcubeb/gtest/test_record.cpp
media/libcubeb/gtest/test_sanity.cpp
media/libcubeb/gtest/test_tone.cpp
media/libcubeb/prefer-pulse-rust.patch
media/libcubeb/src/cubeb_audiounit.cpp
media/libcubeb/src/cubeb_pulse.c
media/libcubeb/update.sh
--- a/media/libcubeb/README_MOZILLA
+++ b/media/libcubeb/README_MOZILLA
@@ -1,8 +1,8 @@
 The source from this directory was copied from the cubeb
 git repository using the update.sh script.  The only changes
 made were those applied by update.sh and the addition of
 Makefile.in build files for the Mozilla build system.
 
 The cubeb git repository is: git://github.com/kinetiknz/cubeb.git
 
-The git commit ID used was 455981555e37a0153e5dd4d4517f073262946519 (2018-10-11 18:48:08 +0300)
+The git commit ID used was a68892dff73bad5ca2b25008b07d0b06fc850391 (2018-10-19 12:17:40 +0200)
--- a/media/libcubeb/gtest/common.h
+++ b/media/libcubeb/gtest/common.h
@@ -88,16 +88,28 @@ void print_log(const char * msg, ...)
   va_end(args);
 }
 
 /** Initialize cubeb with backend override.
  *  Create call cubeb_init passing value for CUBEB_BACKEND env var as
  *  override. */
 int common_init(cubeb ** ctx, char const * ctx_name)
 {
+#ifdef ENABLE_NORMAL_LOG
+  if (cubeb_set_log_callback(CUBEB_LOG_NORMAL, print_log) != CUBEB_OK) {
+    fprintf(stderr, "Set normal log callback failed\n");
+  }
+#endif
+
+#ifdef ENABLE_VERBOSE_LOG
+  if (cubeb_set_log_callback(CUBEB_LOG_VERBOSE, print_log) != CUBEB_OK) {
+    fprintf(stderr, "Set verbose log callback failed\n");
+  }
+#endif
+
   int r;
   char const * backend;
   char const * ctx_backend;
 
   backend = getenv("CUBEB_BACKEND");
   r = cubeb_init(ctx, ctx_name, backend);
   if (r == CUBEB_OK && backend) {
     ctx_backend = cubeb_get_backend_id(*ctx);
--- a/media/libcubeb/gtest/test_audio.cpp
+++ b/media/libcubeb/gtest/test_audio.cpp
@@ -12,18 +12,21 @@
 #define _XOPEN_SOURCE 600
 #endif
 #include <stdio.h>
 #include <stdlib.h>
 #include <math.h>
 #include <memory>
 #include <string.h>
 #include "cubeb/cubeb.h"
+#include <string>
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
 #include "common.h"
-#include <string>
 
 using namespace std;
 
 #define MAX_NUM_CHANNELS 32
 
 #if !defined(M_PI)
 #define M_PI 3.14159265358979323846
 #endif
--- a/media/libcubeb/gtest/test_devices.cpp
+++ b/media/libcubeb/gtest/test_devices.cpp
@@ -8,16 +8,19 @@
 /* libcubeb enumerate device test/example.
  * Prints out a list of devices enumerated. */
 #include "gtest/gtest.h"
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <memory>
 #include "cubeb/cubeb.h"
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
 #include "common.h"
 
 long data_cb_duplex(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
 {
   // noop, unused
   return 0;
 }
 
--- a/media/libcubeb/gtest/test_duplex.cpp
+++ b/media/libcubeb/gtest/test_duplex.cpp
@@ -11,52 +11,56 @@
 #if !defined(_XOPEN_SOURCE)
 #define _XOPEN_SOURCE 600
 #endif
 #include <stdio.h>
 #include <stdlib.h>
 #include <math.h>
 #include <memory>
 #include "cubeb/cubeb.h"
+#include <atomic>
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
 #include "common.h"
-#include <atomic>
 
 #define SAMPLE_FREQUENCY 48000
 #define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
+#define INPUT_CHANNELS 1
+#define INPUT_LAYOUT CUBEB_LAYOUT_MONO
+#define OUTPUT_CHANNELS 2
+#define OUTPUT_LAYOUT CUBEB_LAYOUT_STEREO
 
 struct user_state_duplex
 {
-  std::atomic<int> seen_audio{ 0 };
+  std::atomic<int> invalid_audio_value{ 0 };
 };
 
 long data_cb_duplex(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
 {
   user_state_duplex * u = reinterpret_cast<user_state_duplex*>(user);
   float *ib = (float *)inputbuffer;
   float *ob = (float *)outputbuffer;
-  bool seen_audio = 1;
 
   if (stream == NULL || inputbuffer == NULL || outputbuffer == NULL) {
     return CUBEB_ERROR;
   }
 
   // Loop back: upmix the single input channel to the two output channels,
   // checking if there is noise in the process.
   long output_index = 0;
   for (long i = 0; i < nframes; i++) {
-    if (ib[i] <= -1.0 && ib[i] >= 1.0) {
-      seen_audio = 0;
+    if (ib[i] <= -1.0 || ib[i] >= 1.0) {
+      u->invalid_audio_value = 1;
       break;
     }
     ob[output_index] = ob[output_index + 1] = ib[i];
     output_index += 2;
   }
 
-  u->seen_audio |= seen_audio;
-
   return nframes;
 }
 
 void state_cb_duplex(cubeb_stream * stream, void * /*user*/, cubeb_state state)
 {
   if (stream == NULL)
     return;
 
@@ -93,24 +97,24 @@ TEST(cubeb, duplex)
   /* This test needs an available input device, skip it if this host does not
    * have one. */
   if (!has_available_input_device(ctx)) {
     return;
   }
 
   /* typical user-case: mono input, stereo output, low latency. */
   input_params.format = STREAM_FORMAT;
-  input_params.rate = 48000;
-  input_params.channels = 1;
-  input_params.layout = CUBEB_LAYOUT_MONO;
+  input_params.rate = SAMPLE_FREQUENCY;
+  input_params.channels = INPUT_CHANNELS;
+  input_params.layout = INPUT_LAYOUT;
   input_params.prefs = CUBEB_STREAM_PREF_NONE;
   output_params.format = STREAM_FORMAT;
-  output_params.rate = 48000;
-  output_params.channels = 2;
-  output_params.layout = CUBEB_LAYOUT_STEREO;
+  output_params.rate = SAMPLE_FREQUENCY;
+  output_params.channels = OUTPUT_CHANNELS;
+  output_params.layout = OUTPUT_LAYOUT;
   output_params.prefs = CUBEB_STREAM_PREF_NONE;
 
   r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
   ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
 
   r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
                         NULL, &input_params, NULL, &output_params,
                         latency_frames, data_cb_duplex, state_cb_duplex, &stream_state);
@@ -118,17 +122,17 @@ TEST(cubeb, duplex)
 
   std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
     cleanup_stream_at_exit(stream, cubeb_stream_destroy);
 
   cubeb_stream_start(stream);
   delay(500);
   cubeb_stream_stop(stream);
 
-  ASSERT_TRUE(stream_state.seen_audio.load());
+  ASSERT_FALSE(stream_state.invalid_audio_value.load());
 }
 
 void device_collection_changed_callback(cubeb * context, void * user)
 {
   fprintf(stderr, "collection changed callback\n");
   ASSERT_TRUE(false) << "Error: device collection changed callback"
                         " called when opening a stream";
 }
@@ -151,24 +155,24 @@ TEST(cubeb, duplex_collection_change)
                                                nullptr);
   ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
 
   std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
     cleanup_cubeb_at_exit(ctx, cubeb_destroy);
 
   /* typical user-case: mono input, stereo output, low latency. */
   input_params.format = STREAM_FORMAT;
-  input_params.rate = 48000;
-  input_params.channels = 1;
-  input_params.layout = CUBEB_LAYOUT_MONO;
+  input_params.rate = SAMPLE_FREQUENCY;
+  input_params.channels = INPUT_CHANNELS;
+  input_params.layout = INPUT_LAYOUT;
   input_params.prefs = CUBEB_STREAM_PREF_NONE;
   output_params.format = STREAM_FORMAT;
-  output_params.rate = 48000;
-  output_params.channels = 2;
-  output_params.layout = CUBEB_LAYOUT_STEREO;
+  output_params.rate = SAMPLE_FREQUENCY;
+  output_params.channels = OUTPUT_CHANNELS;
+  output_params.layout = OUTPUT_LAYOUT;
   output_params.prefs = CUBEB_STREAM_PREF_NONE;
 
   r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
   ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
 
   r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
                         NULL, &input_params, NULL, &output_params,
                         latency_frames, data_cb_duplex, state_cb_duplex, nullptr);
--- a/media/libcubeb/gtest/test_latency.cpp
+++ b/media/libcubeb/gtest/test_latency.cpp
@@ -1,12 +1,14 @@
 #include "gtest/gtest.h"
 #include <stdlib.h>
 #include <memory>
 #include "cubeb/cubeb.h"
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
 #include "common.h"
 
 TEST(cubeb, latency)
 {
   cubeb * ctx = NULL;
   int r;
   uint32_t max_channels;
   uint32_t preferred_rate;
--- a/media/libcubeb/gtest/test_loopback.cpp
+++ b/media/libcubeb/gtest/test_loopback.cpp
@@ -15,18 +15,19 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <math.h>
 #include <algorithm>
 #include <memory>
 #include <mutex>
 #include <string>
 #include "cubeb/cubeb.h"
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
 #include "common.h"
-
 const uint32_t SAMPLE_FREQUENCY = 48000;
 const uint32_t TONE_FREQUENCY = 440;
 const double OUTPUT_AMPLITUDE = 0.25;
 const uint32_t NUM_FRAMES_TO_OUTPUT = SAMPLE_FREQUENCY / 20; /* play ~50ms of samples */
 
 template<typename T> T ConvertSampleToOutput(double input);
 template<> float ConvertSampleToOutput(double input) { return float(input); }
 template<> short ConvertSampleToOutput(double input) { return short(input * 32767.0f); }
--- a/media/libcubeb/gtest/test_overload_callback.cpp
+++ b/media/libcubeb/gtest/test_overload_callback.cpp
@@ -10,16 +10,18 @@
 #define _XOPEN_SOURCE 600
 #endif
 #include <stdio.h>
 #include <stdlib.h>
 #include <math.h>
 #include <memory>
 #include <atomic>
 #include "cubeb/cubeb.h"
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
 #include "common.h"
 
 #define SAMPLE_FREQUENCY 48000
 #define STREAM_FORMAT CUBEB_SAMPLE_S16LE
 
 std::atomic<bool> load_callback{ false };
 
 long data_cb(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
--- a/media/libcubeb/gtest/test_record.cpp
+++ b/media/libcubeb/gtest/test_record.cpp
@@ -10,46 +10,46 @@
 #if !defined(_XOPEN_SOURCE)
 #define _XOPEN_SOURCE 600
 #endif
 #include <stdio.h>
 #include <stdlib.h>
 #include <math.h>
 #include <memory>
 #include "cubeb/cubeb.h"
+#include <atomic>
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
 #include "common.h"
-#include <atomic>
 
 #define SAMPLE_FREQUENCY 48000
 #define STREAM_FORMAT CUBEB_SAMPLE_FLOAT32LE
 
 struct user_state_record
 {
-  std::atomic<int> seen_audio{ 0 };
+  std::atomic<int> invalid_audio_value{ 0 };
 };
 
 long data_cb_record(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
 {
   user_state_record * u = reinterpret_cast<user_state_record*>(user);
   float *b = (float *)inputbuffer;
 
   if (stream == NULL  || inputbuffer == NULL || outputbuffer != NULL) {
     return CUBEB_ERROR;
   }
 
-  bool seen_audio = 1;
   for (long i = 0; i < nframes; i++) {
-    if (b[i] <= -1.0 && b[i] >= 1.0) {
-      seen_audio = 0;
+    if (b[i] <= -1.0 || b[i] >= 1.0) {
+      u->invalid_audio_value = 1;
       break;
     }
   }
 
-  u->seen_audio |= seen_audio;
-
   return nframes;
 }
 
 void state_cb_record(cubeb_stream * stream, void * /*user*/, cubeb_state state)
 {
   if (stream == NULL)
     return;
 
@@ -106,11 +106,11 @@ TEST(cubeb, record)
   cubeb_stream_start(stream);
   delay(500);
   cubeb_stream_stop(stream);
 
 #ifdef __linux__
   // user callback does not arrive in Linux, silence the error
   fprintf(stderr, "Check is disabled in Linux\n");
 #else
-  ASSERT_TRUE(stream_state.seen_audio.load());
+  ASSERT_FALSE(stream_state.invalid_audio_value.load());
 #endif
 }
--- a/media/libcubeb/gtest/test_sanity.cpp
+++ b/media/libcubeb/gtest/test_sanity.cpp
@@ -8,16 +8,19 @@
 #if !defined(_XOPEN_SOURCE)
 #define _XOPEN_SOURCE 600
 #endif
 #include "cubeb/cubeb.h"
 #include <atomic>
 #include <stdio.h>
 #include <string.h>
 #include <math.h>
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
 #include "common.h"
 
 #define STREAM_RATE 44100
 #define STREAM_LATENCY 100 * STREAM_RATE / 1000
 #define STREAM_CHANNELS 1
 #define STREAM_LAYOUT CUBEB_LAYOUT_MONO
 #define STREAM_FORMAT CUBEB_SAMPLE_S16LE
 
--- a/media/libcubeb/gtest/test_tone.cpp
+++ b/media/libcubeb/gtest/test_tone.cpp
@@ -11,18 +11,22 @@
 #define _XOPEN_SOURCE 600
 #endif
 #include <stdio.h>
 #include <stdlib.h>
 #include <math.h>
 #include <memory>
 #include <limits.h>
 #include "cubeb/cubeb.h"
+#include <atomic>
+
+//#define ENABLE_NORMAL_LOG
+//#define ENABLE_VERBOSE_LOG
 #include "common.h"
-#include <atomic>
+
 
 #define SAMPLE_FREQUENCY 48000
 #define STREAM_FORMAT CUBEB_SAMPLE_S16LE
 
 /* store the phase of the generated waveform */
 struct cb_user_data {
   std::atomic<long> position;
 };
deleted file mode 100644
--- a/media/libcubeb/prefer-pulse-rust.patch
+++ /dev/null
@@ -1,14 +0,0 @@
-diff --git a/media/libcubeb/src/cubeb.c b/media/libcubeb/src/cubeb.c
-index 1db59240e335..241f3cfed041 100644
---- a/media/libcubeb/src/cubeb.c
-+++ b/media/libcubeb/src/cubeb.c
-@@ -166,6 +166,9 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam
-      * to override all other choices
-      */
-     init_oneshot,
-+#if defined(USE_PULSE_RUST)
-+    pulse_rust_init,
-+#endif
- #if defined(USE_PULSE)
-     pulse_init,
- #endif
--- a/media/libcubeb/src/cubeb_audiounit.cpp
+++ b/media/libcubeb/src/cubeb_audiounit.cpp
@@ -127,21 +127,23 @@ static void audiounit_reinit_stream_asyn
 
 extern cubeb_ops const audiounit_ops;
 
 struct cubeb {
   cubeb_ops const * ops = &audiounit_ops;
   owned_critical_section mutex;
   int active_streams = 0;
   uint32_t global_latency_frames = 0;
-  cubeb_device_collection_changed_callback collection_changed_callback = nullptr;
-  void * collection_changed_user_ptr = nullptr;
-  /* Differentiate input from output devices. */
-  cubeb_device_type collection_changed_devtype = CUBEB_DEVICE_TYPE_UNKNOWN;
-  vector<AudioObjectID> devtype_device_array;
+  cubeb_device_collection_changed_callback input_collection_changed_callback = nullptr;
+  void * input_collection_changed_user_ptr = nullptr;
+  cubeb_device_collection_changed_callback output_collection_changed_callback = nullptr;
+  void * output_collection_changed_user_ptr = nullptr;
+  // Store list of devices to detect changes
+  vector<AudioObjectID> input_device_array;
+  vector<AudioObjectID> output_device_array;
   // The queue is asynchronously deallocated once all references to it are released
   dispatch_queue_t serial_queue = dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL);
   // Current used channel layout
   atomic<cubeb_channel_layout> layout{ CUBEB_LAYOUT_UNDEFINED };
   uint32_t channels = 0;
 };
 
 static unique_ptr<AudioChannelLayout, decltype(&free)>
@@ -1401,33 +1403,36 @@ audiounit_get_current_channel_layout(Aud
     return CUBEB_LAYOUT_UNDEFINED;
   }
 
   return audiounit_convert_channel_layout(layout.get());
 }
 
 static int audiounit_create_unit(AudioUnit * unit, device_info * device);
 
-static OSStatus audiounit_remove_device_listener(cubeb * context);
+static OSStatus audiounit_remove_device_listener(cubeb * context, cubeb_device_type devtype);
 
 static void
 audiounit_destroy(cubeb * ctx)
 {
   {
     auto_lock lock(ctx->mutex);
 
     // Disabling this assert for bug 1083664 -- we seem to leak a stream
     // assert(ctx->active_streams == 0);
     if (audiounit_active_streams(ctx) > 0) {
       LOG("(%p) API misuse, %d streams active when context destroyed!", ctx, audiounit_active_streams(ctx));
     }
 
     /* Unregister the callback if necessary. */
-    if (ctx->collection_changed_callback) {
-      audiounit_remove_device_listener(ctx);
+    if (ctx->input_collection_changed_callback) {
+      audiounit_remove_device_listener(ctx, CUBEB_DEVICE_TYPE_INPUT);
+    }
+    if (ctx->output_collection_changed_callback) {
+      audiounit_remove_device_listener(ctx, CUBEB_DEVICE_TYPE_OUTPUT);
     }
   }
 
   delete ctx;
 }
 
 static void audiounit_stream_destroy(cubeb_stream * stm);
 
@@ -1836,18 +1841,18 @@ audiounit_workaround_for_airpod(cubeb_st
   audiounit_create_device_from_hwdev(&input_device_info, stm->input_device.id, CUBEB_DEVICE_TYPE_INPUT);
 
   cubeb_device_info output_device_info;
   audiounit_create_device_from_hwdev(&output_device_info, stm->output_device.id, CUBEB_DEVICE_TYPE_OUTPUT);
 
   std::string input_name_str(input_device_info.friendly_name);
   std::string output_name_str(output_device_info.friendly_name);
 
-  if( input_name_str.find("AirPods") != std::string::npos
-    && output_name_str.find("AirPods") != std::string::npos ) {
+  if(input_name_str.find("AirPods") != std::string::npos &&
+     output_name_str.find("AirPods") != std::string::npos) {
     uint32_t input_min_rate = 0;
     uint32_t input_max_rate = 0;
     uint32_t input_nominal_rate = 0;
     audiounit_get_available_samplerate(stm->input_device.id, kAudioObjectPropertyScopeGlobal,
                                        &input_min_rate, &input_max_rate, &input_nominal_rate);
     LOG("(%p) Input device %u, name: %s, min: %u, max: %u, nominal rate: %u", stm, stm->input_device.id
     , input_device_info.friendly_name, input_min_rate, input_max_rate, input_nominal_rate);
     uint32_t output_min_rate = 0;
@@ -1975,18 +1980,18 @@ audiounit_new_unit_instance(AudioUnit * 
   desc.componentType = kAudioUnitType_Output;
 #if TARGET_OS_IPHONE
   desc.componentSubType = kAudioUnitSubType_RemoteIO;
 #else
   // Use the DefaultOutputUnit for output when no device is specified
   // so we retain automatic output device switching when the default
   // changes.  Once we have complete support for device notifications
   // and switching, we can use the AUHAL for everything.
-  if ((device->flags & DEV_SYSTEM_DEFAULT)
-      && (device->flags & DEV_OUTPUT)) {
+  if ((device->flags & DEV_SYSTEM_DEFAULT) &&
+      (device->flags & DEV_OUTPUT)) {
     desc.componentSubType = kAudioUnitSubType_DefaultOutput;
   } else {
     desc.componentSubType = kAudioUnitSubType_HALOutput;
   }
 #endif
   desc.componentManufacturer = kAudioUnitManufacturer_Apple;
   desc.componentFlags = 0;
   desc.componentFlagsMask = 0;
@@ -2036,18 +2041,18 @@ audiounit_create_unit(AudioUnit * unit, 
   int r;
 
   r = audiounit_new_unit_instance(unit, device);
   if (r != CUBEB_OK) {
     return r;
   }
   assert(*unit);
 
-  if ((device->flags & DEV_SYSTEM_DEFAULT)
-      && (device->flags & DEV_OUTPUT)) {
+  if ((device->flags & DEV_SYSTEM_DEFAULT) &&
+      (device->flags & DEV_OUTPUT)) {
     return CUBEB_OK;
   }
 
 
   if (device->flags & DEV_INPUT) {
     r = audiounit_enable_unit_scope(unit, io_side::INPUT, ENABLE);
     if (r != CUBEB_OK) {
       LOG("Failed to enable audiounit input scope ");
@@ -3445,97 +3450,125 @@ audiounit_collection_changed_callback(Au
                                       const AudioObjectPropertyAddress * /* inAddresses */,
                                       void * inClientData)
 {
   cubeb * context = static_cast<cubeb *>(inClientData);
 
   // This can be called from inside an AudioUnit function, dispatch to another queue.
   dispatch_async(context->serial_queue, ^() {
     auto_lock lock(context->mutex);
-    if (context->collection_changed_callback == NULL) {
+    if (!context->input_collection_changed_callback &&
+      !context->output_collection_changed_callback) {
       /* Listener removed while waiting in mutex, abort. */
       return;
     }
-
-    assert(context->collection_changed_devtype &
-           (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT));
-
-    vector<AudioObjectID> devices = audiounit_get_devices_of_type(context->collection_changed_devtype);
-    /* The elements in the vector are sorted. */
-    if (context->devtype_device_array == devices) {
-      /* Device changed for the other scope, ignore. */
-      return;
+    if (context->input_collection_changed_callback) {
+      vector<AudioObjectID> devices = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT);
+      /* Elements in the vector expected sorted. */
+      if (context->input_device_array != devices) {
+        context->input_device_array = devices;
+        context->input_collection_changed_callback(context, context->input_collection_changed_user_ptr);
+      }
     }
-    /* Device on desired scope has changed. */
-    context->devtype_device_array = devices;
-    context->collection_changed_callback(context, context->collection_changed_user_ptr);
+    if (context->output_collection_changed_callback) {
+      vector<AudioObjectID> devices = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT);
+      /* Elements in the vector expected sorted. */
+      if (context->output_device_array != devices) {
+        context->output_device_array = devices;
+        context->output_collection_changed_callback(context, context->output_collection_changed_user_ptr);
+      }
+    }
   });
   return noErr;
 }
 
 static OSStatus
 audiounit_add_device_listener(cubeb * context,
                               cubeb_device_type devtype,
                               cubeb_device_collection_changed_callback collection_changed_callback,
                               void * user_ptr)
 {
+  context->mutex.assert_current_thread_owns();
+  assert(devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT));
   /* Note: second register without unregister first causes 'nope' error.
    * Current implementation requires unregister before register a new cb. */
-  assert(context->collection_changed_callback == NULL);
-
-  OSStatus ret = AudioObjectAddPropertyListener(kAudioObjectSystemObject,
-                                                &DEVICES_PROPERTY_ADDRESS,
-                                                audiounit_collection_changed_callback,
-                                                context);
-  if (ret == noErr) {
+  assert((devtype & CUBEB_DEVICE_TYPE_INPUT) && !context->input_collection_changed_callback ||
+         (devtype & CUBEB_DEVICE_TYPE_OUTPUT) && !context->output_collection_changed_callback);
+
+  if (!context->input_collection_changed_callback &&
+      !context->output_collection_changed_callback) {
+    OSStatus ret = AudioObjectAddPropertyListener(kAudioObjectSystemObject,
+                                                  &DEVICES_PROPERTY_ADDRESS,
+                                                  audiounit_collection_changed_callback,
+                                                  context);
+    if (ret != noErr) {
+      return ret;
+    }
+  }
+  if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
     /* Expected empty after unregister. */
-    assert(context->devtype_device_array.empty());
-    /* Listener works for input and output.
-     * When requested one of them we need to differentiate. */
-    assert(devtype &
-           (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT));
-    context->devtype_device_array = audiounit_get_devices_of_type(devtype);
-    context->collection_changed_devtype = devtype;
-    context->collection_changed_callback = collection_changed_callback;
-    context->collection_changed_user_ptr = user_ptr;
+    assert(context->input_device_array.empty());
+    context->input_device_array = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT);
+    context->input_collection_changed_callback = collection_changed_callback;
+    context->input_collection_changed_user_ptr = user_ptr;
   }
-  return ret;
+  if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
+    /* Expected empty after unregister. */
+    assert(context->output_device_array.empty());
+    context->output_device_array = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT);
+    context->output_collection_changed_callback = collection_changed_callback;
+    context->output_collection_changed_user_ptr = user_ptr;
+  }
+  return noErr;
 }
 
 static OSStatus
-audiounit_remove_device_listener(cubeb * context)
+audiounit_remove_device_listener(cubeb * context, cubeb_device_type devtype)
 {
+  context->mutex.assert_current_thread_owns();
+
+  if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
+    context->input_collection_changed_callback = nullptr;
+    context->input_collection_changed_user_ptr = nullptr;
+    context->input_device_array.clear();
+  }
+  if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
+    context->output_collection_changed_callback = nullptr;
+    context->output_collection_changed_user_ptr = nullptr;
+    context->output_device_array.clear();
+  }
+
+  if (context->input_collection_changed_callback ||
+      context->output_collection_changed_callback) {
+    return noErr;
+  }
   /* Note: unregister a non registered cb is not a problem, not checking. */
-  OSStatus ret = AudioObjectRemovePropertyListener(kAudioObjectSystemObject,
-                                                   &DEVICES_PROPERTY_ADDRESS,
-                                                   audiounit_collection_changed_callback,
-                                                   context);
-  if (ret == noErr) {
-    /* Reset all values. */
-    context->collection_changed_devtype = CUBEB_DEVICE_TYPE_UNKNOWN;
-    context->collection_changed_callback = NULL;
-    context->collection_changed_user_ptr = NULL;
-    context->devtype_device_array.clear();
-  }
-  return ret;
+  return AudioObjectRemovePropertyListener(kAudioObjectSystemObject,
+                                           &DEVICES_PROPERTY_ADDRESS,
+                                           audiounit_collection_changed_callback,
+                                           context);
 }
 
 int audiounit_register_device_collection_changed(cubeb * context,
                                                  cubeb_device_type devtype,
                                                  cubeb_device_collection_changed_callback collection_changed_callback,
                                                  void * user_ptr)
 {
+  if (devtype == CUBEB_DEVICE_TYPE_UNKNOWN) {
+    return CUBEB_ERROR_INVALID_PARAMETER;
+  }
   OSStatus ret;
   auto_lock lock(context->mutex);
   if (collection_changed_callback) {
-    ret = audiounit_add_device_listener(context, devtype,
+    ret = audiounit_add_device_listener(context,
+                                        devtype,
                                         collection_changed_callback,
                                         user_ptr);
   } else {
-    ret = audiounit_remove_device_listener(context);
+    ret = audiounit_remove_device_listener(context, devtype);
   }
   return (ret == noErr) ? CUBEB_OK : CUBEB_ERROR;
 }
 
 cubeb_ops const audiounit_ops = {
   /*.init =*/ audiounit_init,
   /*.get_backend_id =*/ audiounit_get_backend_id,
   /*.get_max_channel_count =*/ audiounit_get_max_channel_count,
--- a/media/libcubeb/src/cubeb_pulse.c
+++ b/media/libcubeb/src/cubeb_pulse.c
@@ -106,18 +106,20 @@ struct cubeb_default_sink_info {
 struct cubeb {
   struct cubeb_ops const * ops;
   void * libpulse;
   pa_threaded_mainloop * mainloop;
   pa_context * context;
   struct cubeb_default_sink_info * default_sink_info;
   char * context_name;
   int error;
-  cubeb_device_collection_changed_callback collection_changed_callback;
-  void * collection_changed_user_ptr;
+  cubeb_device_collection_changed_callback output_collection_changed_callback;
+  void * output_collection_changed_user_ptr;
+  cubeb_device_collection_changed_callback input_collection_changed_callback;
+  void * input_collection_changed_user_ptr;
   cubeb_strings * device_ids;
 };
 
 struct cubeb_stream {
   /* Note: Must match cubeb_stream layout in cubeb.c. */
   cubeb * context;
   void * user_ptr;
   /**/
@@ -1491,33 +1493,38 @@ pulse_subscribe_callback(pa_context * ct
 
   switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
   case PA_SUBSCRIPTION_EVENT_SOURCE:
   case PA_SUBSCRIPTION_EVENT_SINK:
 
     if (g_cubeb_log_level) {
       if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE &&
           (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
-        LOG("Removing sink index %d", index);
+        LOG("Removing source index %d", index);
       } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE &&
           (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
-        LOG("Adding sink index %d", index);
+        LOG("Adding source index %d", index);
       }
       if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK &&
           (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
-        LOG("Removing source index %d", index);
+        LOG("Removing sink index %d", index);
       } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK &&
           (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
-        LOG("Adding source index %d", index);
+        LOG("Adding sink index %d", index);
       }
     }
 
     if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE ||
         (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
-      context->collection_changed_callback(context, context->collection_changed_user_ptr);
+      if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE) {
+        context->input_collection_changed_callback(context, context->input_collection_changed_user_ptr);
+      }
+      if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) {
+        context->output_collection_changed_callback(context, context->output_collection_changed_user_ptr);
+      }
     }
     break;
   }
 }
 
 static void
 subscribe_success(pa_context *c, int success, void *userdata)
 {
@@ -1528,34 +1535,42 @@ subscribe_success(pa_context *c, int suc
 }
 
 static int
 pulse_register_device_collection_changed(cubeb * context,
                                          cubeb_device_type devtype,
                                          cubeb_device_collection_changed_callback collection_changed_callback,
                                          void * user_ptr)
 {
-  context->collection_changed_callback = collection_changed_callback;
-  context->collection_changed_user_ptr = user_ptr;
+  if (devtype & CUBEB_DEVICE_TYPE_INPUT) {
+    context->input_collection_changed_callback = collection_changed_callback;
+    context->input_collection_changed_user_ptr = user_ptr;
+  }
+  if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {
+    context->output_collection_changed_callback = collection_changed_callback;
+    context->output_collection_changed_user_ptr = user_ptr;
+  }
 
   WRAP(pa_threaded_mainloop_lock)(context->mainloop);
 
-  pa_subscription_mask_t mask;
-  if (context->collection_changed_callback == NULL) {
-    // Unregister subscription
-    WRAP(pa_context_set_subscribe_callback)(context->context, NULL, NULL);
-    mask = PA_SUBSCRIPTION_MASK_NULL;
+  pa_subscription_mask_t mask = PA_SUBSCRIPTION_MASK_NULL;
+  if (context->input_collection_changed_callback) {
+    mask |= PA_SUBSCRIPTION_MASK_SOURCE;
+  }
+  if (context->output_collection_changed_callback) {
+    mask |= PA_SUBSCRIPTION_MASK_SINK;
+  }
+
+  if (collection_changed_callback == NULL) {
+    // Unregister subscription.
+    if (mask == PA_SUBSCRIPTION_MASK_NULL) {
+      WRAP(pa_context_set_subscribe_callback)(context->context, NULL, NULL);
+    }
   } else {
     WRAP(pa_context_set_subscribe_callback)(context->context, pulse_subscribe_callback, context);
-    if (devtype == CUBEB_DEVICE_TYPE_INPUT)
-      mask = PA_SUBSCRIPTION_MASK_SOURCE;
-    else if (devtype == CUBEB_DEVICE_TYPE_OUTPUT)
-      mask = PA_SUBSCRIPTION_MASK_SINK;
-    else
-      mask = PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE;
   }
 
   pa_operation * o;
   o = WRAP(pa_context_subscribe)(context->context, mask, subscribe_success, context);
   if (o == NULL) {
     WRAP(pa_threaded_mainloop_unlock)(context->mainloop);
     LOG("Context subscribe failed");
     return CUBEB_ERROR;
--- a/media/libcubeb/update.sh
+++ b/media/libcubeb/update.sh
@@ -72,13 +72,10 @@ if [ -n "$rev" ]; then
   rm README_MOZILLA.bak
 else
   echo "Remember to update README_MOZILLA with the version details."
 fi
 
 echo "Applying disable-assert.patch on top of $rev"
 patch -p3 < disable-assert.patch
 
-echo "Applying prefer-pulse-rust.patch on top of $rev"
-patch -p3 < prefer-pulse-rust.patch
-
 echo "Applying disable-device-switching.patch on top of $rev"
 patch -p3 < disable-device-switching.patch