Bug 1321502 - part 2: Use preferred layout for initializing cubeb when audio queue is empty; r=jya draft
authorChun-Min Chang <chun.m.chang@gmail.com>
Sat, 04 Feb 2017 08:22:15 +0800
changeset 478913 97b0b46e660f77307da357416c00042c0df6de63
parent 478912 e86bc2a6e4cfe23eec1772de9822b7c1b5dcc5ff
child 544530 c809ff7179127821a5581e7ad19798ce84bd95c1
push id44086
push userbmo:cchang@mozilla.com
push dateSat, 04 Feb 2017 00:49:35 +0000
reviewersjya
bugs1321502
milestone54.0a1
Bug 1321502 - part 2: Use preferred layout for initializing cubeb when audio queue is empty; r=jya MozReview-Commit-ID: BDEb8IxuJRn
dom/media/AudioStream.h
dom/media/CubebUtils.cpp
dom/media/CubebUtils.h
dom/media/mediasink/DecodedAudioDataSink.cpp
--- a/dom/media/AudioStream.h
+++ b/dom/media/AudioStream.h
@@ -220,16 +220,21 @@ public:
   // was opened, of the audio hardware.  Thread-safe.
   int64_t GetPositionInFrames();
 
   static uint32_t GetPreferredRate()
   {
     return CubebUtils::PreferredSampleRate();
   }
 
+  static uint32_t GetPreferredChannelMap(uint32_t aChannels)
+  {
+    return CubebUtils::PreferredChannelMap(aChannels);
+  }
+
   uint32_t GetOutChannels() { return mOutChannels; }
 
   // Set playback rate as a multiple of the intrinsic playback rate. This is to
   // be called only with aPlaybackRate > 0.0.
   nsresult SetPlaybackRate(double aPlaybackRate);
   // Switch between resampling (if false) and time stretching (if true, default).
   nsresult SetPreservesPitch(bool aPreservesPitch);
 
--- a/dom/media/CubebUtils.cpp
+++ b/dom/media/CubebUtils.cpp
@@ -106,16 +106,21 @@ const int CUBEB_BACKEND_UNKNOWN = CUBEB_
 // thread before fetching, after which it is safe to fetch without holding the
 // mutex because it is only written once per process execution (by the first
 // initialization to complete).  Since the init must have been called on a
 // given thread before fetching the value, it's guaranteed (via the mutex) that
 // sufficient memory barriers have occurred to ensure the correct value is
 // visible on the querying thread/CPU.
 uint32_t sPreferredSampleRate;
 
+// We only support SMPTE layout in cubeb for now. If the value is
+// CUBEB_LAYOUT_UNDEFINED, then it implies that the preferred layout is
+// non-SMPTE format.
+cubeb_channel_layout sPreferredChannelLayout;
+
 } // namespace
 
 extern LazyLogModule gAudioStreamLog;
 
 static const uint32_t CUBEB_NORMAL_LATENCY_MS = 100;
 // Consevative default that can work on all platforms.
 static const uint32_t CUBEB_NORMAL_LATENCY_FRAMES = 1024;
 
@@ -211,16 +216,71 @@ uint32_t PreferredSampleRate()
 {
   if (!InitPreferredSampleRate()) {
     return 44100;
   }
   MOZ_ASSERT(sPreferredSampleRate);
   return sPreferredSampleRate;
 }
 
+bool InitPreferredChannelLayout()
+{
+  StaticMutexAutoLock lock(sMutex);
+  if (sPreferredChannelLayout != 0) {
+    return true;
+  }
+  cubeb* context = GetCubebContextUnlocked();
+  if (!context) {
+    return false;
+  }
+  return cubeb_get_preferred_channel_layout(context,
+                                            &sPreferredChannelLayout) == CUBEB_OK
+         ? true : false;
+}
+
+uint32_t PreferredChannelMap(uint32_t aChannels)
+{
+  // The first element of the following mapping table is channel counts,
+  // and the second one is its bit mask. It will be used in many times,
+  // so we shoule avoid to allocate it in stack, or it will be created
+  // and removed repeatedly. Use static to allocate this local variable
+  // in data space instead of stack.
+  static uint32_t layoutInfo[CUBEB_LAYOUT_MAX][2] = {
+    { 0, 0 },               // CUBEB_LAYOUT_UNDEFINED
+    { 2, MASK_STEREO },     // CUBEB_LAYOUT_DUAL_MONO
+    { 3, MASK_STEREO_LFE }, // CUBEB_LAYOUT_DUAL_MONO_LFE
+    { 1, MASK_MONO },       // CUBEB_LAYOUT_MONO
+    { 2, MASK_MONO_LFE },   // CUBEB_LAYOUT_MONO_LFE
+    { 2, MASK_STEREO },     // CUBEB_LAYOUT_STEREO
+    { 3, MASK_STEREO_LFE }, // CUBEB_LAYOUT_STEREO_LFE
+    { 3, MASK_3F },         // CUBEB_LAYOUT_3F
+    { 4, MASK_3F_LFE },     // CUBEB_LAYOUT_3F_LFE
+    { 3, MASK_2F1 },        // CUBEB_LAYOUT_2F1
+    { 4, MASK_2F1_LFE },    // CUBEB_LAYOUT_2F1_LFE
+    { 4, MASK_3F1 },        // CUBEB_LAYOUT_3F1
+    { 5, MASK_3F1_LFE },    // CUBEB_LAYOUT_3F1_LFE
+    { 4, MASK_2F2 },        // CUBEB_LAYOUT_2F2
+    { 5, MASK_2F2_LFE },    // CUBEB_LAYOUT_2F2_LFE
+    { 5, MASK_3F2 },        // CUBEB_LAYOUT_3F2
+    { 6, MASK_3F2_LFE },    // CUBEB_LAYOUT_3F2_LFE
+    { 7, MASK_3F3R_LFE },   // CUBEB_LAYOUT_3F3R_LFE
+    { 8, MASK_3F4_LFE },    // CUBEB_LAYOUT_3F4_LFE
+  };
+
+  // Use SMPTE default channel map if we can't get preferred layout
+  // or the channel counts of preferred layout is different from input's one
+  if (!InitPreferredChannelLayout()
+      || layoutInfo[sPreferredChannelLayout][0] != aChannels) {
+    AudioConfig::ChannelLayout smpteLayout(aChannels);
+    return smpteLayout.Map();
+  }
+
+  return layoutInfo[sPreferredChannelLayout][1];
+}
+
 void InitBrandName()
 {
   if (sBrandName) {
     return;
   }
   nsXPIDLString brandName;
   nsCOMPtr<nsIStringBundleService> stringBundleService =
     mozilla::services::GetStringBundleService();
--- a/dom/media/CubebUtils.h
+++ b/dom/media/CubebUtils.h
@@ -25,16 +25,19 @@ void InitLibrary();
 void ShutdownLibrary();
 
 // Returns the maximum number of channels supported by the audio hardware.
 uint32_t MaxNumberOfChannels();
 
 // Get the sample rate the hardware/mixer runs at. Thread safe.
 uint32_t PreferredSampleRate();
 
+// Get the bit mask of the connected audio device's preferred layout.
+uint32_t PreferredChannelMap(uint32_t aChannels);
+
 void PrefChanged(const char* aPref, void* aClosure);
 double GetVolumeScale();
 bool GetFirstStream();
 cubeb* GetCubebContext();
 cubeb* GetCubebContextUnlocked();
 void ReportCubebStreamInitFailure(bool aIsFirstStream);
 void ReportCubebBackendUsed();
 uint32_t GetCubebPlaybackLatencyInMilliseconds();
--- a/dom/media/mediasink/DecodedAudioDataSink.cpp
+++ b/dom/media/mediasink/DecodedAudioDataSink.cpp
@@ -190,23 +190,25 @@ DecodedAudioDataSink::SetPlaying(bool aP
   }
   mPlaying = aPlaying;
 }
 
 nsresult
 DecodedAudioDataSink::InitializeAudioStream(const PlaybackParams& aParams)
 {
   mAudioStream = new AudioStream(*this);
+  // When AudioQueue is empty, there is no way to know the channel layout of
+  // the coming audio data, so we use the predefined channel map instead.
+  uint32_t channelMap = mConverter
+                        ? mConverter->OutputConfig().Layout().Map()
+                        : AudioStream::GetPreferredChannelMap(mOutputChannels);
   // The layout map used here is already processed by mConverter with
   // mOutputChannels into SMPTE format, so there is no need to worry if
   // MediaPrefs::MonoAudio() or MediaPrefs::AudioSinkForceStereo() is applied.
-  nsresult rv = mAudioStream->Init(mOutputChannels,
-                                   mConverter->OutputConfig().Layout().Map(),
-                                   mOutputRate,
-                                   mChannel);
+  nsresult rv = mAudioStream->Init(mOutputChannels, channelMap, mOutputRate, mChannel);
   if (NS_FAILED(rv)) {
     mAudioStream->Shutdown();
     mAudioStream = nullptr;
     return rv;
   }
 
   // Set playback params before calling Start() so they can take effect
   // as soon as the 1st DataCallback of the AudioStream fires.