Bug 1264199: P4. Add mono to stereo upmix to AudioConverter. r=rillian draft
authorJean-Yves Avenard <jyavenard@mozilla.com>
Wed, 13 Apr 2016 19:50:54 +1000
changeset 356283 33e4f515f3888f221848da0eb2db213789e86a09
parent 356282 89565a98b33c81c3a268524075c289dfe388eea2
child 356284 ce935ab7fa71a8c0df449d68137dfda58681eb8c
push id16486
push userbmo:jyavenard@mozilla.com
push dateTue, 26 Apr 2016 02:36:37 +0000
reviewersrillian
bugs1264199
milestone49.0a1
Bug 1264199: P4. Add mono to stereo upmix to AudioConverter. r=rillian MozReview-Commit-ID: 4sCvNWKEMZS
dom/media/AudioConverter.cpp
dom/media/AudioConverter.h
--- a/dom/media/AudioConverter.cpp
+++ b/dom/media/AudioConverter.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AudioConverter.h"
 #include <string.h>
 #include <speex/speex_resampler.h>
+#include <cmath>
 
 /*
  *  Parts derived from MythTV AudioConvert Class
  *  Created by Jean-Yves Avenard.
  *
  *  Copyright (C) Bubblestuff Pty Ltd 2013
  *  Copyright (C) foobum@gmail.com 2010
  */
@@ -21,19 +22,19 @@ namespace mozilla {
 AudioConverter::AudioConverter(const AudioConfig& aIn, const AudioConfig& aOut)
   : mIn(aIn)
   , mOut(aOut)
   , mResampler(nullptr)
 {
   MOZ_DIAGNOSTIC_ASSERT(aIn.Format() == aOut.Format() &&
                         aIn.Interleaved() == aOut.Interleaved(),
                         "No format or rate conversion is supported at this stage");
-  MOZ_DIAGNOSTIC_ASSERT((aIn.Channels() > aOut.Channels() && aOut.Channels() <= 2) ||
+  MOZ_DIAGNOSTIC_ASSERT(aOut.Channels() <= 2 ||
                         aIn.Channels() == aOut.Channels(),
-                        "Only downmixing to mono or stereo is supported at this stage");
+                        "Only down/upmixing 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);
   if (aIn.Rate() != aOut.Rate()) {
     int error;
     mResampler = speex_resampler_init(aOut.Channels(),
                                       aIn.Rate(),
                                       aOut.Rate(),
                                       SPEEX_RESAMPLER_QUALITY_DEFAULT,
@@ -55,35 +56,37 @@ AudioConverter::~AudioConverter()
     mResampler = nullptr;
   }
 }
 
 bool
 AudioConverter::CanWorkInPlace() const
 {
   bool needDownmix = mIn.Channels() > mOut.Channels();
+  bool needUpmix = mIn.Channels() < mOut.Channels();
   bool canDownmixInPlace =
     mIn.Channels() * AudioConfig::SampleSize(mIn.Format()) >=
     mOut.Channels() * AudioConfig::SampleSize(mOut.Format());
   bool needResample = mIn.Rate() != mOut.Rate();
   bool canResampleInPlace = mIn.Rate() >= mOut.Rate();
   // We should be able to work in place if 1s of audio input takes less space
   // than 1s of audio output. However, as we downmix before resampling we can't
   // perform any upsampling in place (e.g. if incoming rate >= outgoing rate)
-  return (!needDownmix || canDownmixInPlace) &&
+  return !needUpmix && (!needDownmix || canDownmixInPlace) &&
          (!needResample || canResampleInPlace);
 }
 
 size_t
 AudioConverter::ProcessInternal(void* aOut, const void* aIn, size_t aFrames)
 {
   if (mIn.Channels() > mOut.Channels()) {
     return DownmixAudio(aOut, aIn, aFrames);
-  } else if (mIn.Layout() != mOut.Layout() &&
-      CanReorderAudio()) {
+  } else if (mIn.Channels() < mOut.Channels()) {
+    return UpmixAudio(aOut, aIn, aFrames);
+  } else if (mIn.Layout() != mOut.Layout() && CanReorderAudio()) {
     ReOrderInterleavedChannels(aOut, aIn, aFrames);
   } else if (aIn != aOut) {
     memmove(aOut, aIn, FramesOutToBytes(aFrames));
   }
   return aFrames;
 }
 
 // Reorder interleaved channels.
@@ -226,26 +229,26 @@ AudioConverter::DownmixAudio(void* aOut,
     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 < aFrames; ++fIdx) {
+      for (size_t fIdx = 0; fIdx < aFrames; ++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 < aFrames; ++fIdx) {
+      for (size_t fIdx = 0; fIdx < aFrames; ++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");
     }
@@ -275,16 +278,58 @@ AudioConverter::ResampleAudio(void* aOut
   } else {
     MOZ_DIAGNOSTIC_ASSERT(false, "Unsupported data type");
   }
   MOZ_ASSERT(inframes == aFrames, "Some frames will be dropped");
   return outframes;
 }
 
 size_t
+AudioConverter::UpmixAudio(void* aOut, const void* aIn, size_t aFrames) const
+{
+  MOZ_ASSERT(mIn.Format() == AudioConfig::FORMAT_S16 ||
+             mIn.Format() == AudioConfig::FORMAT_FLT);
+  MOZ_ASSERT(mIn.Channels() < mOut.Channels());
+  MOZ_ASSERT(mIn.Channels() == 1, "Can only upmix mono for now");
+  MOZ_ASSERT(mOut.Channels() == 2, "Can only upmix to stereo for now");
+
+  if (mOut.Channels() != 2) {
+    return 0;
+  }
+
+  // Upmix mono to stereo.
+  // This is a very dumb mono to stereo upmixing, power levels are preserved
+  // following the calculation: left = right = -3dB*mono.
+  if (mIn.Format() == AudioConfig::FORMAT_FLT) {
+    const float m3db = std::sqrt(0.5); // -3dB = sqrt(1/2)
+    const float* in = static_cast<const float*>(aIn);
+    float* out = static_cast<float*>(aOut);
+    for (size_t fIdx = 0; fIdx < aFrames; ++fIdx) {
+      float sample = in[fIdx] * m3db;
+      // The samples of the buffer would be interleaved.
+      *out++ = sample;
+      *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 (size_t fIdx = 0; fIdx < aFrames; ++fIdx) {
+      int16_t sample = ((int32_t)in[fIdx] * 11585) >> 14; // close enough to i*sqrt(0.5)
+      // The samples of the buffer would be interleaved.
+      *out++ = sample;
+      *out++ = sample;
+    }
+  } else {
+    MOZ_DIAGNOSTIC_ASSERT(false, "Unsupported data type");
+  }
+
+  return aFrames;
+}
+
+size_t
 AudioConverter::ResampleRecipientFrames(size_t aFrames) const
 {
   return (uint64_t)aFrames * mOut.Rate() / mIn.Rate() + 1;
 }
 
 size_t
 AudioConverter::FramesOutToSamples(size_t aFrames) const
 {
--- a/dom/media/AudioConverter.h
+++ b/dom/media/AudioConverter.h
@@ -208,16 +208,17 @@ private:
    * aIn   : source buffer
    * aSamples: number of frames in source buffer
    *
    * Return Value: number of frames converted or 0 if error
    */
   size_t ProcessInternal(void* aOut, const void* aIn, size_t aFrames);
   void ReOrderInterleavedChannels(void* aOut, const void* aIn, size_t aFrames) const;
   size_t DownmixAudio(void* aOut, const void* aIn, size_t aFrames) const;
+  size_t UpmixAudio(void* aOut, const void* aIn, size_t aFrames) const;
 
   size_t FramesOutToSamples(size_t aFrames) const;
   size_t SamplesInToFrames(size_t aSamples) const;
   size_t FramesOutToBytes(size_t aFrames) const;
 
   // Resampler context.
   SpeexResamplerState* mResampler;
   size_t ResampleAudio(void* aOut, const void* aIn, size_t aFrames);