Bug 1444479 - P6. Make Opus and Vorbis decoder deal with more channels than 8. r?padenot draft
authorJean-Yves Avenard <jyavenard@mozilla.com>
Fri, 16 Mar 2018 16:54:12 +0100
changeset 769399 3e58f138c164179245e47e3f15f7d9750c521e67
parent 769398 82589900face8537d3912e3aa792088cc37eb4ce
child 769400 b7ea9357d3853921f47e3dd2c5ea680e7b3a17b2
push id103110
push userbmo:jyavenard@mozilla.com
push dateMon, 19 Mar 2018 14:36:56 +0000
reviewerspadenot
bugs1444479
milestone61.0a1
Bug 1444479 - P6. Make Opus and Vorbis decoder deal with more channels than 8. r?padenot Under 8 channels, the audio will be reordered so it can be playable on any platforms. Over 8 channels, the channels will be as output by the decoder. Playing of such stream will be platform dependent as neither Opus nor Vorbis define a channel layout with more than 8 channels. With WebAudio however, the result will be platform independent (as long as you don't attempt to play it) MozReview-Commit-ID: 93ATiKm9y20
dom/media/ogg/OpusParser.cpp
dom/media/platforms/agnostic/OpusDecoder.cpp
dom/media/platforms/agnostic/OpusDecoder.h
dom/media/platforms/agnostic/VorbisDecoder.cpp
--- a/dom/media/ogg/OpusParser.cpp
+++ b/dom/media/ogg/OpusParser.cpp
@@ -75,24 +75,25 @@ bool OpusParser::DecodeHeader(unsigned c
                            " mapping family 0.", mChannels));
         return false;
       }
       mStreams = 1;
       mCoupledStreams = mChannels - 1;
       mMappingTable[0] = 0;
       mMappingTable[1] = 1;
     } else if (mChannelMapping == 1 || mChannelMapping == 255) {
-      // Currently only up to 8 channels are defined for mapping family 1 and we
-      // only supports only up to 8 channels for mapping family 255.
-      if (mChannels>8) {
-        OPUS_LOG(LogLevel::Debug, ("Invalid Opus file: too many channels (%d) for"
-                           " mapping family 1.", mChannels));
+      // Currently only up to 8 channels are defined for mapping family 1
+      if (mChannelMapping == 1 && mChannels > 8) {
+        OPUS_LOG(LogLevel::Debug,
+                 ("Invalid Opus file: too many channels (%d) for"
+                  " mapping family 1.",
+                  mChannels));
         return false;
       }
-      if (aLength>static_cast<unsigned>(20+mChannels)) {
+      if (aLength > static_cast<unsigned>(20 + mChannels)) {
         mStreams = aData[19];
         mCoupledStreams = aData[20];
         int i;
         for (i=0; i<mChannels; i++)
           mMappingTable[i] = aData[21+i];
       } else {
         OPUS_LOG(LogLevel::Debug, ("Invalid Opus file: channel mapping %d,"
                            " but no channel mapping table", mChannelMapping));
--- a/dom/media/platforms/agnostic/OpusDecoder.cpp
+++ b/dom/media/platforms/agnostic/OpusDecoder.cpp
@@ -82,22 +82,23 @@ OpusDataDecoder::Init()
   if (NS_FAILED(DecodeHeader(p, length))) {
     OPUS_DEBUG("Error decoding header!");
     return InitPromise::CreateAndReject(
       MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                   RESULT_DETAIL("Error decoding header!")),
       __func__);
   }
 
+  MOZ_ASSERT(mMappingTable.Length() >= mOpusParser->mChannels);
   int r;
   mOpusDecoder = opus_multistream_decoder_create(mOpusParser->mRate,
                                                  mOpusParser->mChannels,
                                                  mOpusParser->mStreams,
                                                  mOpusParser->mCoupledStreams,
-                                                 mMappingTable,
+                                                 mMappingTable.Elements(),
                                                  &r);
   mSkip = mOpusParser->mPreSkip;
   mPaddingDiscarded = false;
 
   if (codecDelay != FramesToUsecs(mOpusParser->mPreSkip,
                                   mOpusParser->mRate).value()) {
     NS_WARNING("Invalid Opus header: CodecDelay and pre-skip do not match!");
     return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
@@ -126,41 +127,42 @@ OpusDataDecoder::DecodeHeader(const unsi
   mDecodedHeader = true;
 
   mOpusParser = new OpusParser;
   if (!mOpusParser->DecodeHeader(const_cast<unsigned char*>(aData), aLength)) {
     return NS_ERROR_FAILURE;
   }
   int channels = mOpusParser->mChannels;
 
+  mMappingTable.SetLength(channels);
   AudioConfig::ChannelLayout vorbisLayout(
     channels, VorbisDataDecoder::VorbisLayout(channels));
-  if (!vorbisLayout.IsValid()) {
-    OPUS_DEBUG("Invalid channel mapping. Source is %d channels", channels);
-    return NS_ERROR_FAILURE;
-  }
-  mChannelMap = vorbisLayout.Map();
+  if (vorbisLayout.IsValid()) {
+    mChannelMap = vorbisLayout.Map();
 
-  AudioConfig::ChannelLayout smpteLayout(
-    AudioConfig::ChannelLayout::SMPTEDefault(vorbisLayout));
+    AudioConfig::ChannelLayout smpteLayout(
+      AudioConfig::ChannelLayout::SMPTEDefault(vorbisLayout));
 
-  static_assert(sizeof(mOpusParser->mMappingTable) /
-                sizeof(mOpusParser->mMappingTable[0]) >=
-                MAX_AUDIO_CHANNELS,
-                "Invalid size set");
-  uint8_t map[sizeof(mOpusParser->mMappingTable) /
-              sizeof(mOpusParser->mMappingTable[0])];
-  if (vorbisLayout.MappingTable(smpteLayout, map)) {
-    for (int i = 0; i < channels; i++) {
-      mMappingTable[i] = mOpusParser->mMappingTable[map[i]];
+    AutoTArray<uint8_t, 8> map;
+    map.SetLength(channels);
+    if (vorbisLayout.MappingTable(smpteLayout, &map)) {
+      for (int i = 0; i < channels; i++) {
+        mMappingTable[i] = mOpusParser->mMappingTable[map[i]];
+      }
+    } else {
+      // Should never get here as vorbis layout is always convertible to SMPTE
+      // default layout.
+      PodCopy(mMappingTable.Elements(), mOpusParser->mMappingTable, channels);
     }
   } else {
-    // Should never get here as vorbis layout is always convertible to SMPTE
-    // default layout.
-    PodCopy(mMappingTable, mOpusParser->mMappingTable, MAX_AUDIO_CHANNELS);
+    // Create a dummy mapping table so that channel ordering stay the same
+    // during decoding.
+    for (uint32_t i = 0; i < channels; i++) {
+      mMappingTable[i] = i;
+    }
   }
 
   return NS_OK;
 }
 
 RefPtr<MediaDataDecoder::DecodePromise>
 OpusDataDecoder::Decode(MediaRawData* aSample)
 {
--- a/dom/media/platforms/agnostic/OpusDecoder.h
+++ b/dom/media/platforms/agnostic/OpusDecoder.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #if !defined(OpusDecoder_h_)
 #define OpusDecoder_h_
 
 #include "PlatformDecoderModule.h"
 
 #include "mozilla/Maybe.h"
 #include "nsAutoPtr.h"
+#include "nsTArray.h"
 
 struct OpusMSDecoder;
 
 namespace mozilla {
 
 class OpusParser;
 
 DDLoggedTypeDeclNameAndBase(OpusDataDecoder, MediaDataDecoder);
@@ -63,14 +64,14 @@ private:
   bool mDecodedHeader;
 
   // Opus padding should only be discarded on the final packet.  Once this
   // is set to true, if the reader attempts to decode any further packets it
   // will raise an error so we can indicate that the file is invalid.
   bool mPaddingDiscarded;
   int64_t mFrames;
   Maybe<int64_t> mLastFrameTime;
-  uint8_t mMappingTable[MAX_AUDIO_CHANNELS]; // Channel mapping table.
+  AutoTArray<uint8_t, 8> mMappingTable;
   AudioConfig::ChannelLayout::ChannelMap mChannelMap;
 };
 
 } // namespace mozilla
 #endif
--- a/dom/media/platforms/agnostic/VorbisDecoder.cpp
+++ b/dom/media/platforms/agnostic/VorbisDecoder.cpp
@@ -230,24 +230,19 @@ VorbisDataDecoder::ProcessDecode(MediaRa
           NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
           RESULT_DETAIL("Overflow adding total_duration and aSample->mTime")),
         __func__);
     };
 
     if (!mAudioConverter) {
       const AudioConfig::ChannelLayout layout =
         AudioConfig::ChannelLayout(channels, VorbisLayout(channels));
-      AudioConfig in(layout, rate);
-      AudioConfig out(AudioConfig::ChannelLayout::SMPTEDefault(layout), rate);
-      if (!in.IsValid() || !out.IsValid()) {
-        return DecodePromise::CreateAndReject(
-          MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
-                      RESULT_DETAIL("Invalid channel layout:%u", channels)),
-          __func__);
-      }
+      AudioConfig in(layout, channels, rate);
+      AudioConfig out(
+        AudioConfig::ChannelLayout::SMPTEDefault(layout), channels, rate);
       mAudioConverter = MakeUnique<AudioConverter>(in, out);
     }
     MOZ_ASSERT(mAudioConverter->CanWorkInPlace());
     AudioSampleBuffer data(Move(buffer));
     data = mAudioConverter->Process(Move(data));
 
     results.AppendElement(
       new AudioData(aOffset,