Bug 1432779 - P11. Make cubeb_mixer creation infallible. r?padenot draft
authorJean-Yves Avenard <jyavenard@mozilla.com>
Fri, 09 Mar 2018 22:10:25 +0100
changeset 768208 65cd9e6674430b3ddaa32688357b63ed15142d39
parent 768207 a26e00c6102edda8db60dcfaa651a50cabb7edec
child 768209 cdd5ee6e882c512f4d30ef296124c9fc38b65ff3
push id102819
push userbmo:jyavenard@mozilla.com
push dateThu, 15 Mar 2018 19:46:59 +0000
reviewerspadenot
bugs1432779
milestone61.0a1
Bug 1432779 - P11. Make cubeb_mixer creation infallible. r?padenot Rather than ignore nonsensical layouts, we attempt to play it according to the stream channels count instead. The audio data will be played as-is, dropping the extra channels or inserting silence where needed. MozReview-Commit-ID: 7bygAJMu93Z
media/libcubeb/src/cubeb_audiounit.cpp
media/libcubeb/src/cubeb_mixer.cpp
media/libcubeb/src/cubeb_mixer.h
media/libcubeb/src/cubeb_wasapi.cpp
--- a/media/libcubeb/src/cubeb_audiounit.cpp
+++ b/media/libcubeb/src/cubeb_audiounit.cpp
@@ -82,16 +82,17 @@ struct cubeb {
   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;
   // 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)>
 make_sized_audio_channel_layout(size_t sz)
 {
     assert(sz >= sizeof(AudioChannelLayout));
     AudioChannelLayout * acl = reinterpret_cast<AudioChannelLayout *>(calloc(1, sz));
     assert(acl); // Assert the allocation works.
@@ -1311,26 +1312,28 @@ audio_stream_desc_init(AudioStreamBasicD
   ss->mFramesPerPacket = 1;
   ss->mBytesPerPacket = ss->mBytesPerFrame * ss->mFramesPerPacket;
 
   ss->mReserved = 0;
 
   return CUBEB_OK;
 }
 
-bool
+void
 audiounit_init_mixer(cubeb_stream * stm)
 {
   // We can't rely on macOS' AudioUnit to properly downmix (or upmix) the audio
   // data, it silently drop the channels so we need to remix the
   // audio data by ourselves to keep all the information.
   stm->mixer.reset(cubeb_mixer_create(stm->output_stream_params.format,
+                                      stm->output_stream_params.channels,
                                       stm->output_stream_params.layout,
+                                      stm->context->channels,
                                       stm->context->layout));
-  return static_cast<bool>(stm->mixer);
+  assert(stm->mixer);
 }
 
 static int
 audiounit_set_channel_layout(AudioUnit unit,
                              io_side side,
                              cubeb_channel_layout layout)
 {
   if (side != OUTPUT) {
@@ -2280,38 +2283,32 @@ audiounit_configure_output(cubeb_stream 
                            &output_hw_desc,
                            &size);
   if (r != noErr) {
     LOG("AudioUnitGetProperty/output/kAudioUnitProperty_StreamFormat rv=%d", r);
     return CUBEB_ERROR;
   }
   stm->output_hw_rate = output_hw_desc.mSampleRate;
   LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate);
-
-  if (stm->output_stream_params.layout != CUBEB_LAYOUT_UNDEFINED) {
-    audiounit_layout_init(stm, OUTPUT);
-    if (stm->context->layout != CUBEB_LAYOUT_UNDEFINED &&
-        stm->context->layout != stm->output_stream_params.layout) {
-      LOG("Incompatible channel layouts detected, setting up remixer");
-      if (audiounit_init_mixer(stm)) {
-        // We will be remixing the data before it reaches the output device.
-        // We need to adjust the number of channels and other
-        // AudioStreamDescription details.
-        stm->output_desc.mChannelsPerFrame =
-          cubeb_channel_layout_nb_channels(stm->context->layout);
-        stm->output_desc.mBytesPerFrame =
-          (stm->output_desc.mBitsPerChannel / 8) *
-          stm->output_desc.mChannelsPerFrame;
-        stm->output_desc.mBytesPerPacket =
-          stm->output_desc.mBytesPerFrame * stm->output_desc.mFramesPerPacket;
-      }
-    }
-    if (!stm->mixer) {
-      LOG("Failed to setup remixer for layout %u", stm->output_stream_params.layout);
-    }
+  stm->context->channels = output_hw_desc.mChannelsPerFrame;
+
+  // Set the input layout to match the output device layout.
+  audiounit_layout_init(stm, OUTPUT);
+  if (stm->context->channels != stm->output_stream_params.channels ||
+      stm->context->layout != stm->output_stream_params.layout) {
+    LOG("Incompatible channel layouts detected, setting up remixer");
+    audiounit_init_mixer(stm);
+    // We will be remixing the data before it reaches the output device.
+    // We need to adjust the number of channels and other
+    // AudioStreamDescription details.
+    stm->output_desc.mChannelsPerFrame = stm->context->channels;
+    stm->output_desc.mBytesPerFrame = (stm->output_desc.mBitsPerChannel / 8) *
+                                      stm->output_desc.mChannelsPerFrame;
+    stm->output_desc.mBytesPerPacket =
+      stm->output_desc.mBytesPerFrame * stm->output_desc.mFramesPerPacket;
   } else {
     stm->mixer = nullptr;
   }
 
   r = AudioUnitSetProperty(stm->output_unit,
                            kAudioUnitProperty_StreamFormat,
                            kAudioUnitScope_Input,
                            AU_OUT_BUS,
--- a/media/libcubeb/src/cubeb_mixer.cpp
+++ b/media/libcubeb/src/cubeb_mixer.cpp
@@ -73,24 +73,31 @@ unsigned int cubeb_channel_layout_nb_cha
   x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
   x = (x + (x >> 4)) & 0x0F0F0F0F;
   x += x >> 8;
   return (x + (x >> 16)) & 0x3F;
 #endif
 }
 struct MixerContext {
   MixerContext(cubeb_sample_format f,
+               uint32_t in_channels,
                cubeb_channel_layout in,
+               uint32_t out_channels,
                cubeb_channel_layout out)
     : _format(f)
     , _in_ch_layout(in)
     , _out_ch_layout(out)
-    , _in_ch_count(cubeb_channel_layout_nb_channels(in))
-    , _out_ch_count(cubeb_channel_layout_nb_channels(out))
+    , _in_ch_count(in_channels)
+    , _out_ch_count(out_channels)
   {
+    if (in_channels != cubeb_channel_layout_nb_channels(in) ||
+        out_channels != cubeb_channel_layout_nb_channels(out)) {
+      // Mismatch between channels and layout, aborting.
+      return;
+    }
     _valid = init() >= 0;
   }
 
   static bool even(cubeb_channel_layout layout)
   {
     if (!layout) {
       return true;
     }
@@ -488,46 +495,85 @@ static int rematrix(const MixerContext *
     }
   }
   return 0;
 }
 
 struct cubeb_mixer
 {
   cubeb_mixer(cubeb_sample_format format,
+              uint32_t in_channels,
               cubeb_channel_layout in_layout,
+              uint32_t out_channels,
               cubeb_channel_layout out_layout)
-    : _context(format, in_layout, out_layout)
+    : _context(format, in_channels, in_layout, out_channels, out_layout)
+  {
+  }
+
+  template<typename T>
+  void copy_and_trunc(unsigned long frames,
+                      const T* input_buffer,
+                      T* output_buffer) const
   {
+    if (_context._in_ch_count <= _context._out_ch_count) {
+      // Not enough channels to copy, fill the gaps with silence.
+      for (uint32_t i = 0; i < frames; i++) {
+        PodCopy(output_buffer, input_buffer, _context._in_ch_count);
+        output_buffer += _context._in_ch_count;
+        input_buffer += _context._in_ch_count;
+        PodZero(output_buffer, _context._out_ch_count - _context._in_ch_count);
+        output_buffer += _context._out_ch_count - _context._in_ch_count;
+      }
+    } else {
+      for (uint32_t i = 0; i < frames; i++) {
+        PodCopy(output_buffer, input_buffer, _context._out_ch_count);
+        output_buffer += _context._out_ch_count;
+        input_buffer += _context._in_ch_count;
+      }
+    }
   }
 
   int mix(unsigned long frames,
           void * input_buffer,
           unsigned long input_buffer_size,
           void * output_buffer,
           unsigned long output_buffer_size) const
   {
-    if (!valid()) {
-      return -1;
-    }
-    if (frames <= 0) {
+    if (frames <= 0 || _context._out_ch_count == 0) {
       return 0;
     }
 
     // Check if output buffer is of sufficient size.
     size_t size_read_needed =
       frames * _context._in_ch_count * cubeb_sample_size(_context._format);
     if (input_buffer_size < size_read_needed) {
       // We don't have enough data to read!
       return -1;
     }
     if (output_buffer_size * _context._in_ch_count <
         size_read_needed * _context._out_ch_count) {
       return -1;
     }
+
+    if (!valid()) {
+      // The channel layouts were invalid or unsupported, instead we will simply
+      // either drop the extra channels, or fill with silence the missing ones
+      if (_context._format == CUBEB_SAMPLE_FLOAT32NE) {
+        copy_and_trunc(frames,
+                       static_cast<const float*>(input_buffer),
+                       static_cast<float*>(output_buffer));
+      } else {
+        assert(_context._format == CUBEB_SAMPLE_S16NE);
+        copy_and_trunc(frames,
+                       static_cast<const int16_t*>(input_buffer),
+                       static_cast<int16_t*>(output_buffer));
+      }
+      return 0;
+    }
+
     switch (_context._format)
     {
       case CUBEB_SAMPLE_FLOAT32NE: {
         auto f = [](float x) { return x; };
         return rematrix(&_context,
                         static_cast<float*>(output_buffer),
                         static_cast<const float*>(input_buffer),
                         _context._matrix_flt,
@@ -559,34 +605,36 @@ struct cubeb_mixer
                           f,
                           frames);
         }
         break;
       default:
         assert(false);
         break;
     }
+
+    return -1;
   }
 
+  // Return false if any of the input or ouput layout were invalid.
   bool valid() const { return _context._valid; }
 
   virtual ~cubeb_mixer(){};
 
   MixerContext _context;
 };
 
 cubeb_mixer* cubeb_mixer_create(cubeb_sample_format format,
+                                uint32_t in_channels,
                                 cubeb_channel_layout in_layout,
+                                uint32_t out_channels,
                                 cubeb_channel_layout out_layout)
 {
-  if (!MixerContext::sane_layout(in_layout) ||
-      !MixerContext::sane_layout(out_layout)) {
-    return nullptr;
-  }
-  return new cubeb_mixer(format, in_layout, out_layout);
+  return new cubeb_mixer(
+    format, in_channels, in_layout, out_channels, out_layout);
 }
 
 void cubeb_mixer_destroy(cubeb_mixer * mixer)
 {
   delete mixer;
 }
 
 int cubeb_mixer_mix(cubeb_mixer * mixer,
--- a/media/libcubeb/src/cubeb_mixer.h
+++ b/media/libcubeb/src/cubeb_mixer.h
@@ -11,17 +11,19 @@
 #include "cubeb/cubeb.h" // for cubeb_channel_layout and cubeb_stream_params.
 
 #if defined(__cplusplus)
 extern "C" {
 #endif
 
 typedef struct cubeb_mixer cubeb_mixer;
 cubeb_mixer * cubeb_mixer_create(cubeb_sample_format format,
+                                 uint32_t in_channels,
                                  cubeb_channel_layout in_layout,
+                                 uint32_t out_channels,
                                  cubeb_channel_layout out_layout);
 void cubeb_mixer_destroy(cubeb_mixer * mixer);
 int cubeb_mixer_mix(cubeb_mixer * mixer,
                     unsigned long frames,
                     void * input_buffer,
                     unsigned long input_buffer_size,
                     void * output_buffer,
                     unsigned long output_buffer_size);
--- a/media/libcubeb/src/cubeb_wasapi.cpp
+++ b/media/libcubeb/src/cubeb_wasapi.cpp
@@ -1745,24 +1745,24 @@ int setup_wasapi_stream(cubeb_stream * s
   }
 
   // Create mixer.
   if (has_output(stm) && stm->output_mix_params.layout != stm->output_stream_params.layout) {
     if (stm->output_mix_params.layout == CUBEB_LAYOUT_UNDEFINED) {
       LOG("Stream using undefined layout! Any mixing may be unpredictable!\n");
     }
     stm->mixer.reset(cubeb_mixer_create(stm->output_stream_params.format,
+                                        stm->output_stream_params.channels,
                                         stm->output_stream_params.layout,
+                                        stm->output_mix_params.channels,
                                         stm->output_mix_params.layout));
-
+    assert(stm->mixer);
     // Input is up/down mixed when depacketized in get_input_buffer.
-    if (stm->mixer) {
-      stm->mix_buffer.resize(
-        frames_to_bytes_before_mix(stm, stm->output_buffer_frame_count));
-    }
+    stm->mix_buffer.resize(
+      frames_to_bytes_before_mix(stm, stm->output_buffer_frame_count));
   }
 
   return CUBEB_OK;
 }
 
 int
 wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
                    char const * stream_name,
@@ -1795,19 +1795,17 @@ wasapi_stream_init(cubeb * context, cube
   stm->user_ptr = user_ptr;
   if (input_stream_params) {
     stm->input_stream_params = *input_stream_params;
     stm->input_device = utf8_to_wstr(reinterpret_cast<char const *>(input_device));
   }
   if (output_stream_params) {
     stm->output_stream_params = *output_stream_params;
     stm->output_device = utf8_to_wstr(reinterpret_cast<char const *>(output_device));
-    // Make sure the layout matches the channel count.
-    XASSERT(stm->output_stream_params.layout == CUBEB_LAYOUT_UNDEFINED ||
-            stm->output_stream_params.channels == cubeb_channel_layout_nb_channels(stm->output_stream_params.layout));
+    XASSERT(stm->output_stream_params.layout == CUBEB_LAYOUT_UNDEFINED);
   }
 
   switch (output_stream_params ? output_stream_params->format : input_stream_params->format) {
     case CUBEB_SAMPLE_S16NE:
       stm->bytes_per_sample = sizeof(short);
       stm->waveformatextensible_sub_format = KSDATAFORMAT_SUBTYPE_PCM;
       stm->linear_input_buffer.reset(new auto_array_wrapper_impl<short>);
       break;