Bug 1262746: P1. Add downmixing capabilities to AudioConverter. r=rillian
authorJean-Yves Avenard <jyavenard@mozilla.com>
Fri, 08 Apr 2016 17:32:57 +1000
changeset 330648 b77cdb6c0b6cf5b556139a13e34c6198f4faf462
parent 330647 46a0648db82f51e4c09eebb96fe8df9f872f4f0d
child 330649 5b3a0fdc1e6162c834403f720e402e104deffb80
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrillian
bugs1262746
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1262746: P1. Add downmixing capabilities to AudioConverter. r=rillian This is using the same downmixer algorithm as found in the ogg reader. MozReview-Commit-ID: 5KwVLPOg7Tt
dom/media/AudioConverter.cpp
dom/media/AudioConverter.h
--- a/dom/media/AudioConverter.cpp
+++ b/dom/media/AudioConverter.cpp
@@ -16,21 +16,23 @@
  */
 
 namespace mozilla {
 
 AudioConverter::AudioConverter(const AudioConfig& aIn, const AudioConfig& aOut)
   : mIn(aIn)
   , mOut(aOut)
 {
-  MOZ_DIAGNOSTIC_ASSERT(aIn.Channels() == aOut.Channels() &&
-                        aIn.Rate() == aOut.Rate() &&
+  MOZ_DIAGNOSTIC_ASSERT(aIn.Rate() == aOut.Rate() &&
                         aIn.Format() == aOut.Format() &&
                         aIn.Interleaved() == aOut.Interleaved(),
-                        "Only channel reordering is supported at this stage");
+                        "No format or rate conversion is supported at this stage");
+  MOZ_DIAGNOSTIC_ASSERT((aIn.Channels() > aOut.Channels() && aOut.Channels() <= 2) ||
+                        aIn.Channels() == aOut.Channels(),
+                        "Only downmixing to mono or stereo is supported at this stage");
   MOZ_DIAGNOSTIC_ASSERT(aOut.Interleaved(), "planar audio format not supported");
   mIn.Layout().MappingTable(mOut.Layout(), mChannelOrderMap);
 }
 
 bool
 AudioConverter::CanWorkInPlace() const
 {
   return mIn.Channels() * mIn.Rate() * AudioConfig::SampleSize(mIn.Format()) >=
@@ -38,17 +40,19 @@ AudioConverter::CanWorkInPlace() const
 }
 
 size_t
 AudioConverter::Process(void* aOut, const void* aIn, size_t aBytes)
 {
   if (!CanWorkInPlace()) {
     return 0;
   }
-  if (mIn.Layout() != mOut.Layout() &&
+  if (mIn.Channels() > mOut.Channels()) {
+    return DownmixAudio(aOut, aIn, aBytes);
+  } else if (mIn.Layout() != mOut.Layout() &&
       CanReorderAudio()) {
     ReOrderInterleavedChannels(aOut, aIn, aBytes);
   }
   return aBytes;
 }
 
 // Reorder interleaved channels.
 // Can work in place (e.g aOut == aIn).
@@ -106,9 +110,120 @@ AudioConverter::ReOrderInterleavedChanne
       MOZ_DIAGNOSTIC_ASSERT(AudioConfig::SampleSize(mOut.Format()) == 4);
       _ReOrderInterleavedChannels((int32_t*)aOut,(const int32_t*)aIn,
                                   aDataSize/sizeof(int32_t)/mIn.Channels(),
                                   mIn.Channels(), mChannelOrderMap);
       break;
   }
 }
 
+static inline int16_t clipTo15(int32_t aX)
+{
+  return aX < -32768 ? -32768 : aX <= 32767 ? aX : 32767;
+}
+
+size_t
+AudioConverter::DownmixAudio(void* aOut, const void* aIn, size_t aDataSize) const
+{
+  MOZ_ASSERT(mIn.Format() == AudioConfig::FORMAT_S16 ||
+             mIn.Format() == AudioConfig::FORMAT_FLT);
+  MOZ_ASSERT(mIn.Channels() >= mOut.Channels());
+  MOZ_ASSERT(mIn.Layout() == AudioConfig::ChannelLayout(mIn.Channels()),
+             "Can only downmix input data in SMPTE layout");
+  MOZ_ASSERT(mOut.Layout() == AudioConfig::ChannelLayout(2) ||
+             mOut.Layout() == AudioConfig::ChannelLayout(1));
+
+  uint32_t channels = mIn.Channels();
+  uint32_t frames =
+    aDataSize / AudioConfig::SampleSize(mOut.Format()) / channels;
+
+  if (channels == 1 && mOut.Channels() == 1) {
+    if (aOut != aIn) {
+      memmove(aOut, aIn, aDataSize);
+    }
+    return aDataSize;
+  }
+
+  if (channels > 2) {
+    if (mIn.Format() == AudioConfig::FORMAT_FLT) {
+      // Downmix matrix. Per-row normalization 1 for rows 3,4 and 2 for rows 5-8.
+      static const float dmatrix[6][8][2]= {
+          /*3*/{{0.5858f,0},{0,0.5858f},{0.4142f,0.4142f}},
+          /*4*/{{0.4226f,0},{0,0.4226f},{0.366f, 0.2114f},{0.2114f,0.366f}},
+          /*5*/{{0.6510f,0},{0,0.6510f},{0.4600f,0.4600f},{0.5636f,0.3254f},{0.3254f,0.5636f}},
+          /*6*/{{0.5290f,0},{0,0.5290f},{0.3741f,0.3741f},{0.3741f,0.3741f},{0.4582f,0.2645f},{0.2645f,0.4582f}},
+          /*7*/{{0.4553f,0},{0,0.4553f},{0.3220f,0.3220f},{0.3220f,0.3220f},{0.2788f,0.2788f},{0.3943f,0.2277f},{0.2277f,0.3943f}},
+          /*8*/{{0.3886f,0},{0,0.3886f},{0.2748f,0.2748f},{0.2748f,0.2748f},{0.3366f,0.1943f},{0.1943f,0.3366f},{0.3366f,0.1943f},{0.1943f,0.3366f}},
+      };
+      // Re-write the buffer with downmixed data
+      const float* in = static_cast<const float*>(aIn);
+      float* out = static_cast<float*>(aOut);
+      for (uint32_t i = 0; i < frames; i++) {
+        float sampL = 0.0;
+        float sampR = 0.0;
+        for (uint32_t j = 0; j < channels; j++) {
+          sampL += in[i*mIn.Channels()+j]*dmatrix[mIn.Channels()-3][j][0];
+          sampR += in[i*mIn.Channels()+j]*dmatrix[mIn.Channels()-3][j][1];
+        }
+        *out++ = sampL;
+        *out++ = sampR;
+      }
+    } else if (mIn.Format() == AudioConfig::FORMAT_S16) {
+      // Downmix matrix. Per-row normalization 1 for rows 3,4 and 2 for rows 5-8.
+      // Coefficients in Q14.
+      static const int16_t dmatrix[6][8][2]= {
+          /*3*/{{9598, 0},{0,   9598},{6786,6786}},
+          /*4*/{{6925, 0},{0,   6925},{5997,3462},{3462,5997}},
+          /*5*/{{10663,0},{0,  10663},{7540,7540},{9234,5331},{5331,9234}},
+          /*6*/{{8668, 0},{0,   8668},{6129,6129},{6129,6129},{7507,4335},{4335,7507}},
+          /*7*/{{7459, 0},{0,   7459},{5275,5275},{5275,5275},{4568,4568},{6460,3731},{3731,6460}},
+          /*8*/{{6368, 0},{0,   6368},{4502,4502},{4502,4502},{5514,3184},{3184,5514},{5514,3184},{3184,5514}}
+      };
+      // Re-write the buffer with downmixed data
+      const int16_t* in = static_cast<const int16_t*>(aIn);
+      int16_t* out = static_cast<int16_t*>(aOut);
+      for (uint32_t i = 0; i < frames; i++) {
+        int32_t sampL = 0;
+        int32_t sampR = 0;
+        for (uint32_t j = 0; j < channels; j++) {
+          sampL+=in[i*channels+j]*dmatrix[channels-3][j][0];
+          sampR+=in[i*channels+j]*dmatrix[channels-3][j][1];
+        }
+        *out++ = clipTo15((sampL + 8192)>>14);
+        *out++ = clipTo15((sampR + 8192)>>14);
+      }
+    } else {
+      MOZ_DIAGNOSTIC_ASSERT(false, "Unsupported data type");
+    }
+
+    // If we are to continue downmixing to mono, start working on the output
+    // buffer.
+    aIn = aOut;
+    channels = 2;
+  }
+
+  if (mOut.Channels() == 1) {
+    if (mIn.Format() == AudioConfig::FORMAT_FLT) {
+      const float* in = static_cast<const float*>(aIn);
+      float* out = static_cast<float*>(aOut);
+      for (uint32_t fIdx = 0; fIdx < frames; ++fIdx) {
+        float sample = 0.0;
+        // The sample of the buffer would be interleaved.
+        sample = (in[fIdx*channels] + in[fIdx*channels + 1]) * 0.5;
+        *out++ = sample;
+      }
+    } else if (mIn.Format() == AudioConfig::FORMAT_S16) {
+      const int16_t* in = static_cast<const int16_t*>(aIn);
+      int16_t* out = static_cast<int16_t*>(aOut);
+      for (uint32_t fIdx = 0; fIdx < frames; ++fIdx) {
+        int32_t sample = 0.0;
+        // The sample of the buffer would be interleaved.
+        sample = (in[fIdx*channels] + in[fIdx*channels + 1]) * 0.5;
+        *out++ = sample;
+      }
+    } else {
+      MOZ_DIAGNOSTIC_ASSERT(false, "Unsupported data type");
+    }
+  }
+  return frames * AudioConfig::SampleSize(mOut.Format()) * mOut.Channels();
+}
+
 } // namespace mozilla
\ No newline at end of file
--- a/dom/media/AudioConverter.h
+++ b/dom/media/AudioConverter.h
@@ -111,16 +111,22 @@ public:
   // Process may allocate memory internally should intermediary steps be
   // required.
   template <AudioConfig::SampleFormat Type, typename Value>
   size_t Process(AudioDataBuffer<Type, Value>& aBuffer)
   {
     MOZ_DIAGNOSTIC_ASSERT(mIn.Format() == mOut.Format() && mIn.Format() == Type);
     return Process(aBuffer.Data(), aBuffer.Data(), aBuffer.Size());
   }
+  template <typename Value>
+  size_t Process(Value* aBuffer, size_t aSamples)
+  {
+    MOZ_DIAGNOSTIC_ASSERT(mIn.Format() == mOut.Format());
+    return Process(aBuffer, aBuffer, aSamples * AudioConfig::SampleSize(mIn.Format()));
+  }
   bool CanWorkInPlace() const;
   bool CanReorderAudio() const
   {
     return mIn.Layout().MappingTable(mOut.Layout());
   }
 
 private:
   const AudioConfig mIn;
@@ -132,13 +138,14 @@ private:
    * aOut  : destination buffer where converted samples will be copied
    * aIn   : source buffer
    * aBytes: size in bytes of source buffer
    *
    * Return Value: size in bytes of samples converted or 0 if error
    */
   size_t Process(void* aOut, const void* aIn, size_t aBytes);
   void ReOrderInterleavedChannels(void* aOut, const void* aIn, size_t aDataSize) const;
+  size_t DownmixAudio(void* aOut, const void* aIn, size_t aDataSize) const;
 };
 
 } // namespace mozilla
 
 #endif /* AudioConverter_h */