Bug 1231793: Part 5 - Added Wave Data Demuxer and Decoder. r=jya
authorLouis Christie <lchristie@mozilla.com>
Mon, 15 Feb 2016 12:10:48 +1300
changeset 331294 60d3873f4ad3f9698adeff1dd57aaa389134af9d
parent 331293 789acc6a4b930f63daf58a86664924486cda09b4
child 331295 e1b3267c27648ee59ee4dd0c48f4d5670fd4140e
push id10956
push userjolesen@mozilla.com
push dateTue, 16 Feb 2016 19:12:12 +0000
reviewersjya
bugs1231793
milestone47.0a1
Bug 1231793: Part 5 - Added Wave Data Demuxer and Decoder. r=jya
dom/media/platforms/agnostic/AgnosticDecoderModule.cpp
dom/media/platforms/agnostic/WAVDecoder.cpp
dom/media/platforms/agnostic/WAVDecoder.h
dom/media/platforms/moz.build
dom/media/wave/WaveDecoder.cpp
dom/media/wave/WaveDecoder.h
dom/media/wave/WaveDemuxer.cpp
dom/media/wave/WaveDemuxer.h
dom/media/wave/moz.build
modules/libpref/init/all.js
--- a/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp
@@ -3,25 +3,27 @@
 /* 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 "AgnosticDecoderModule.h"
 #include "OpusDecoder.h"
 #include "VorbisDecoder.h"
 #include "VPXDecoder.h"
+#include "WAVDecoder.h"
 
 namespace mozilla {
 
 bool
 AgnosticDecoderModule::SupportsMimeType(const nsACString& aMimeType) const
 {
   return VPXDecoder::IsVPX(aMimeType) ||
     OpusDataDecoder::IsOpus(aMimeType) ||
-    VorbisDataDecoder::IsVorbis(aMimeType);
+    VorbisDataDecoder::IsVorbis(aMimeType) ||
+    WaveDataDecoder::IsWave(aMimeType);
 }
 
 already_AddRefed<MediaDataDecoder>
 AgnosticDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig,
                                           layers::LayersBackend aLayersBackend,
                                           layers::ImageContainer* aImageContainer,
                                           FlushableTaskQueue* aVideoTaskQueue,
                                           MediaDataDecoderCallback* aCallback)
@@ -48,14 +50,18 @@ AgnosticDecoderModule::CreateAudioDecode
   if (VorbisDataDecoder::IsVorbis(aConfig.mMimeType)) {
     m = new VorbisDataDecoder(*aConfig.GetAsAudioInfo(),
                               aAudioTaskQueue,
                               aCallback);
   } else if (OpusDataDecoder::IsOpus(aConfig.mMimeType)) {
     m = new OpusDataDecoder(*aConfig.GetAsAudioInfo(),
                             aAudioTaskQueue,
                             aCallback);
+  } else if (WaveDataDecoder::IsWave(aConfig.mMimeType)) {
+    m = new WaveDataDecoder(*aConfig.GetAsAudioInfo(),
+                            aAudioTaskQueue,
+                            aCallback);
   }
 
   return m.forget();
 }
 
 } // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/agnostic/WAVDecoder.cpp
@@ -0,0 +1,139 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "WAVDecoder.h"
+#include "AudioSampleFormat.h"
+#include "nsAutoPtr.h"
+
+using mp4_demuxer::ByteReader;
+
+namespace mozilla {
+
+WaveDataDecoder::WaveDataDecoder(const AudioInfo& aConfig,
+                                 FlushableTaskQueue* aTaskQueue,
+                                 MediaDataDecoderCallback* aCallback)
+  : mInfo(aConfig)
+  , mTaskQueue(aTaskQueue)
+  , mCallback(aCallback)
+  , mFrames(0)
+{
+}
+
+nsresult
+WaveDataDecoder::Shutdown()
+{
+  return NS_OK;
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+WaveDataDecoder::Init()
+{
+  return InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__);
+}
+
+nsresult
+WaveDataDecoder::Input(MediaRawData* aSample)
+{
+  nsCOMPtr<nsIRunnable> runnable(
+    NS_NewRunnableMethodWithArg<RefPtr<MediaRawData>>(
+      this, &WaveDataDecoder::Decode,
+      RefPtr<MediaRawData>(aSample)));
+  mTaskQueue->Dispatch(runnable.forget());
+
+  return NS_OK;
+}
+
+void
+WaveDataDecoder::Decode(MediaRawData* aSample)
+{
+  if (!DoDecode(aSample)) {
+    mCallback->Error();
+  } else if (mTaskQueue->IsEmpty()) {
+    mCallback->InputExhausted();
+  }
+}
+
+bool
+WaveDataDecoder::DoDecode(MediaRawData* aSample)
+{
+  size_t aLength = aSample->Size();
+  ByteReader aReader = ByteReader(aSample->Data(), aLength);
+  int64_t aOffset = aSample->mOffset;
+  uint64_t aTstampUsecs = aSample->mTime;
+
+  int32_t frames = aLength * 8 / mInfo.mBitDepth / mInfo.mChannels;
+
+  auto buffer = MakeUnique<AudioDataValue[]>(frames * mInfo.mChannels);
+  for (int i = 0; i < frames; ++i) {
+    for (unsigned int j = 0; j < mInfo.mChannels; ++j) {
+      if (mInfo.mBitDepth == 8) {
+        uint8_t v = aReader.ReadU8();
+        buffer[i * mInfo.mChannels + j] =
+            UInt8bitToAudioSample<AudioDataValue>(v);
+      } else if (mInfo.mBitDepth == 16) {
+        int16_t v = aReader.ReadLE16();
+        buffer[i * mInfo.mChannels + j] =
+            IntegerToAudioSample<AudioDataValue>(v);
+      } else if (mInfo.mBitDepth == 24) {
+        int32_t v = aReader.ReadLE24();
+        buffer[i * mInfo.mChannels + j] =
+            Int24bitToAudioSample<AudioDataValue>(v);
+      }
+    }
+  }
+
+  aReader.DiscardRemaining();
+
+  int64_t duration = frames / mInfo.mRate;
+
+  mCallback->Output(new AudioData(aOffset,
+                                  aTstampUsecs,
+                                  duration,
+                                  frames,
+                                  Move(buffer),
+                                  mInfo.mChannels,
+                                  mInfo.mRate));
+  mFrames += frames;
+
+  return true;
+}
+
+void
+WaveDataDecoder::DoDrain()
+{
+  mCallback->DrainComplete();
+}
+
+nsresult
+WaveDataDecoder::Drain()
+{
+  nsCOMPtr<nsIRunnable> runnable(
+    NS_NewRunnableMethod(this, &WaveDataDecoder::DoDrain));
+  mTaskQueue->Dispatch(runnable.forget());
+  return NS_OK;
+}
+
+nsresult
+WaveDataDecoder::Flush()
+{
+  mTaskQueue->Flush();
+  mFrames = 0;
+  return NS_OK;
+}
+
+/* static */
+bool
+WaveDataDecoder::IsWave(const nsACString& aMimeType)
+{
+  // Some WebAudio uses "audio/x-wav",
+  // WAVdemuxer uses "audio/wave; codecs=aNum".
+  return aMimeType.EqualsLiteral("audio/x-wav") ||
+         aMimeType.EqualsLiteral("audio/wave; codecs=1") ||
+         aMimeType.EqualsLiteral("audio/wave; codecs=65534");
+}
+
+} // namespace mozilla
+#undef LOG
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/agnostic/WAVDecoder.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#if !defined(WaveDecoder_h_)
+#define WaveDecoder_h_
+
+#include "PlatformDecoderModule.h"
+#include "mp4_demuxer/ByteReader.h"
+
+namespace mozilla {
+
+class WaveDataDecoder : public MediaDataDecoder
+{
+public:
+  WaveDataDecoder(const AudioInfo& aConfig,
+                  FlushableTaskQueue* aTaskQueue,
+                  MediaDataDecoderCallback* aCallback);
+
+  RefPtr<InitPromise> Init() override;
+  nsresult Input(MediaRawData* aSample) override;
+  nsresult Flush() override;
+  nsresult Drain() override;
+  nsresult Shutdown() override;
+  const char* GetDescriptionName() const override
+  {
+    return "wave audio decoder";
+  }
+
+  // Return true if mimetype is Wave
+  static bool IsWave(const nsACString& aMimeType);
+
+private:
+  void Decode (MediaRawData* aSample);
+  bool DoDecode (MediaRawData* aSample);
+  void DoDrain ();
+
+  const AudioInfo& mInfo;
+  RefPtr<FlushableTaskQueue> mTaskQueue;
+  MediaDataDecoderCallback* mCallback;
+
+  int64_t mFrames;
+};
+
+} // namespace mozilla
+#endif
\ No newline at end of file
--- a/dom/media/platforms/moz.build
+++ b/dom/media/platforms/moz.build
@@ -16,16 +16,17 @@ EXPORTS += [
 ]
 
 UNIFIED_SOURCES += [
     'agnostic/AgnosticDecoderModule.cpp',
     'agnostic/BlankDecoderModule.cpp',
     'agnostic/OpusDecoder.cpp',
     'agnostic/VorbisDecoder.cpp',
     'agnostic/VPXDecoder.cpp',
+    'agnostic/WAVDecoder.cpp',
     'PDMFactory.cpp',
     'PlatformDecoderModule.cpp',
     'wrappers/FuzzingWrapper.cpp',
     'wrappers/H264Converter.cpp'
 ]
 
 DIRS += [
     'agnostic/gmp',
--- a/dom/media/wave/WaveDecoder.cpp
+++ b/dom/media/wave/WaveDecoder.cpp
@@ -1,17 +1,60 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 "WaveDemuxer.h"
+#include "mozilla/Preferences.h"
 #include "MediaDecoderStateMachine.h"
 #include "WaveReader.h"
 #include "WaveDecoder.h"
+#include "MediaFormatReader.h"
+#include "PDMFactory.h"
 
 namespace mozilla {
 
-MediaDecoderStateMachine* WaveDecoder::CreateStateMachine()
+MediaDecoder*
+WaveDecoder::Clone(MediaDecoderOwner* aOwner)
+{
+  if (!IsEnabled())
+    return nullptr;
+
+  return new WaveDecoder(aOwner);
+}
+
+MediaDecoderStateMachine*
+WaveDecoder::CreateStateMachine()
 {
-  return new MediaDecoderStateMachine(this, new WaveReader(this));
+  if (Preferences::GetBool("media.wave.decoder.enabled")) {
+    RefPtr<MediaDecoderReader> reader =
+        new MediaFormatReader(this, new WAVDemuxer(GetResource()));
+    return new MediaDecoderStateMachine(this, reader);
+  } else {
+    return new MediaDecoderStateMachine(this, new WaveReader(this));
+  }
+}
+
+/* static */
+bool
+WaveDecoder::IsEnabled()
+{
+  PDMFactory::Init();
+  RefPtr<PDMFactory> platform = new PDMFactory();
+  return platform->SupportsMimeType(NS_LITERAL_CSTRING("audio/x-wav"));
+}
+
+/* static */
+bool
+WaveDecoder::CanHandleMediaType(const nsACString& aType,
+                               const nsAString& aCodecs)
+{
+  if (aType.EqualsASCII("audio/wave") || aType.EqualsASCII("audio/x-wav") ||
+      aType.EqualsASCII("audio/wav")  || aType.EqualsASCII("audio/x-pn-wav")) {
+    return IsEnabled() && (aCodecs.IsEmpty() || aCodecs.EqualsASCII("1"));
+  }
+
+  return false;
 }
 
 } // namespace mozilla
--- a/dom/media/wave/WaveDecoder.h
+++ b/dom/media/wave/WaveDecoder.h
@@ -3,37 +3,27 @@
 /* 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/. */
 #if !defined(WaveDecoder_h_)
 #define WaveDecoder_h_
 
 #include "MediaDecoder.h"
 
-/**
- * The decoder implementation is currently limited to Linear PCM encoded
- * audio data with one or two channels of 8- or 16-bit samples at sample
- * rates from 100 Hz to 96 kHz.  The number of channels is limited by what
- * the audio backend (via AudioStream) currently supports.  The supported
- * sample rate is artificially limited to arbitrarily selected sane values.
- * Support for additional channels (and other new features) would
- * require extending WaveDecoder to support parsing the newer
- * WAVE_FORMAT_EXTENSIBLE chunk format.
-**/
-
 namespace mozilla {
 
-class WaveDecoder : public MediaDecoder
-{
+class WaveDecoder : public MediaDecoder {
 public:
+  // MediaDecoder interface.
   explicit WaveDecoder(MediaDecoderOwner* aOwner) : MediaDecoder(aOwner) {}
-  MediaDecoder* Clone(MediaDecoderOwner* aOwner) override {
-    if (!IsWaveEnabled()) {
-      return nullptr;
-    }
-    return new WaveDecoder(aOwner);
-  }
+  MediaDecoder* Clone(MediaDecoderOwner* aOwner) override;
   MediaDecoderStateMachine* CreateStateMachine() override;
+
+  // Returns true if the WAV backend is pref'ed on, and we're running on a
+  // platform that is likely to have decoders for the format.
+  static bool IsEnabled();
+  static bool CanHandleMediaType(const nsACString& aType,
+                                 const nsAString& aCodecs);
 };
 
 } // namespace mozilla
 
 #endif
new file mode 100644
--- /dev/null
+++ b/dom/media/wave/WaveDemuxer.cpp
@@ -0,0 +1,889 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "WaveDemuxer.h"
+
+#include <inttypes.h>
+#include <algorithm>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Endian.h"
+#include "VideoUtils.h"
+#include "TimeUnits.h"
+#include "prenv.h"
+
+using mozilla::media::TimeUnit;
+using mozilla::media::TimeIntervals;
+using mp4_demuxer::ByteReader;
+
+namespace mozilla {
+
+// WAVDemuxer
+
+WAVDemuxer::WAVDemuxer(MediaResource* aSource)
+  : mSource(aSource)
+{
+}
+
+bool
+WAVDemuxer::InitInternal()
+{
+  if (!mTrackDemuxer) {
+    mTrackDemuxer = new WAVTrackDemuxer(mSource);
+  }
+  return mTrackDemuxer->Init();
+}
+
+RefPtr<WAVDemuxer::InitPromise>
+WAVDemuxer::Init()
+{
+  if (!InitInternal()) {
+    return InitPromise::CreateAndReject(
+      DemuxerFailureReason::DEMUXER_ERROR, __func__);
+  }
+  return InitPromise::CreateAndResolve(NS_OK, __func__);
+}
+
+bool
+WAVDemuxer::HasTrackType(TrackInfo::TrackType aType) const
+{
+  return aType == TrackInfo::kAudioTrack;
+}
+
+uint32_t
+WAVDemuxer::GetNumberTracks(TrackInfo::TrackType aType) const
+{
+  return aType == TrackInfo::kAudioTrack ? 1u : 0u;
+}
+
+already_AddRefed<MediaTrackDemuxer>
+WAVDemuxer::GetTrackDemuxer(TrackInfo::TrackType aType, uint32_t aTrackNumber)
+{
+  if (!mTrackDemuxer) {
+    return nullptr;
+  }
+  return RefPtr<WAVTrackDemuxer>(mTrackDemuxer).forget();
+}
+
+bool
+WAVDemuxer::IsSeekable() const
+{
+  return true;
+}
+
+// WAVTrackDemuxer
+
+WAVTrackDemuxer::WAVTrackDemuxer(MediaResourceIndex aSource)
+  : mSource(aSource)
+  , mOffset(0)
+  , mFirstChunkOffset(0)
+  , mNumParsedChunks(0)
+  , mChunkIndex(0)
+  , mTotalChunkLen(0)
+  , mSamplesPerChunk(0)
+  , mSamplesPerSecond(0)
+  , mChannels(0)
+{
+  Reset();
+}
+
+bool
+WAVTrackDemuxer::Init()
+{
+  Reset();
+  FastSeek(TimeUnit());
+
+  if (!mInfo) {
+    mInfo = MakeUnique<AudioInfo>();
+  }
+
+  if (!RIFFParserInit()) {
+    return false;
+  }
+
+  while (true) {
+    if (!HeaderParserInit()) {
+      return false;
+    }
+
+    uint32_t aChunkName = mHeaderParser.GiveHeader().ChunkName();
+    uint32_t aChunkSize = mHeaderParser.GiveHeader().ChunkSize();
+
+    aChunkSize += aChunkSize % 2;
+
+    if (aChunkName == FRMT_CODE) {
+      if (!FmtChunkParserInit()) {
+        return false;
+      }
+    } else if (aChunkName == LIST_CODE) {
+      mHeaderParser.Reset();
+      uint32_t endOfListChunk = mOffset + aChunkSize;
+      if (!ListChunkParserInit(aChunkSize)) {
+        mOffset = endOfListChunk;
+      }
+    } else if (aChunkName == DATA_CODE) {
+      mDataLength = aChunkSize;
+      if (mFirstChunkOffset != mOffset) {
+        mFirstChunkOffset = mOffset;
+      }
+      break;
+    } else {
+      mOffset += aChunkSize; // Skip other irrelevant chunks.
+    }
+    mHeaderParser.Reset();
+  }
+
+  if (mDataLength > StreamLength() - mFirstChunkOffset) {
+    mDataLength = StreamLength() - mFirstChunkOffset;
+  }
+
+  mSamplesPerSecond = mFmtParser.FmtChunk().SampleRate();
+  mChannels = mFmtParser.FmtChunk().Channels();
+  mSampleFormat = mFmtParser.FmtChunk().SampleFormat();
+  mSamplesPerChunk = DATA_CHUNK_SIZE * 8 / mChannels / mSampleFormat;
+
+  mInfo->mRate = mSamplesPerSecond;
+  mInfo->mChannels = mChannels;
+  mInfo->mBitDepth = mSampleFormat;
+  mInfo->mProfile = mFmtParser.FmtChunk().WaveFormat() & 0x00FF;
+  mInfo->mExtendedProfile = (mFmtParser.FmtChunk().WaveFormat() & 0xFF00) >> 8;
+  mInfo->mMimeType = "audio/wave; codecs=";
+  mInfo->mMimeType.AppendInt(mFmtParser.FmtChunk().WaveFormat());
+  mInfo->mDuration = Duration().ToMicroseconds();
+
+  return !!(mInfo->mDuration);
+}
+
+bool
+WAVTrackDemuxer::RIFFParserInit()
+{
+  RefPtr<MediaRawData> riffHeader = GetFileHeader(FindRIFFHeader());
+  if (!riffHeader) {
+    return false;
+  }
+  ByteReader RIFFReader = ByteReader(riffHeader->Data(), 12);
+  mRIFFParser.Parse(RIFFReader);
+  return mRIFFParser.RiffHeader().IsValid(11);
+}
+
+bool
+WAVTrackDemuxer::HeaderParserInit()
+{
+  RefPtr<MediaRawData> header = GetFileHeader(FindChunkHeader());
+  if (!header) {
+    return false;
+  }
+  ByteReader HeaderReader = ByteReader(header->Data(), 8);
+  mHeaderParser.Parse(HeaderReader);
+  return true;
+}
+
+bool
+WAVTrackDemuxer::FmtChunkParserInit()
+{
+  RefPtr<MediaRawData> fmtChunk = GetFileHeader(FindFmtChunk());
+  if (!fmtChunk) {
+    return false;
+  }
+  ByteReader fmtReader = ByteReader(fmtChunk->Data(),
+                                    mHeaderParser.GiveHeader().ChunkSize());
+  mFmtParser.Parse(fmtReader);
+  return true;
+}
+
+bool
+WAVTrackDemuxer::ListChunkParserInit(uint32_t aChunkSize)
+{
+  uint32_t bytesRead = 0;
+
+  RefPtr<MediaRawData> infoTag = GetFileHeader(FindInfoTag());
+  if (!infoTag) {
+    return false;
+  }
+  ByteReader infoTagReader = ByteReader(infoTag->Data(), 4);
+  if (!infoTagReader.CanRead32() || infoTagReader.ReadU32() != INFO_CODE) {
+    return false;
+  }
+
+  bytesRead += 4;
+
+  while (bytesRead < aChunkSize) {
+    if (!HeaderParserInit()) {
+      return false;
+    }
+
+    bytesRead += 8;
+
+    uint32_t id = mHeaderParser.GiveHeader().ChunkName();
+    uint32_t length = mHeaderParser.GiveHeader().ChunkSize();
+
+    // SubChunk Length Cannot Exceed List Chunk length.
+    if (length > aChunkSize - bytesRead) {
+      length = aChunkSize - bytesRead;
+    }
+
+    MediaByteRange mRange = { mOffset, mOffset + length };
+    RefPtr<MediaRawData> mChunkData = GetFileHeader(mRange);
+    if (!mChunkData) {
+      return false;
+    }
+
+    const char* mRawData = reinterpret_cast<const char*>(mChunkData->Data());
+
+    nsCString val(mRawData, length);
+    if (length > 0 && val[length - 1] == '\0') {
+      val.SetLength(length - 1);
+    }
+
+    if (length % 2) {
+      mOffset += 1;
+      length += length % 2;
+    }
+
+    bytesRead += length;
+
+    if (!IsUTF8(val)) {
+      mHeaderParser.Reset();
+      continue;
+    }
+
+    switch (id) {
+      case 0x49415254:                // IART
+        mInfo->mTags.AppendElement(MetadataTag(NS_LITERAL_CSTRING("artist"), val));
+        break;
+      case 0x49434d54:                // ICMT
+        mInfo->mTags.AppendElement(MetadataTag(NS_LITERAL_CSTRING("comments"), val));
+        break;
+      case 0x49474e52:                // IGNR
+        mInfo->mTags.AppendElement(MetadataTag(NS_LITERAL_CSTRING("genre"), val));
+        break;
+      case 0x494e414d:                // INAM
+        mInfo->mTags.AppendElement(MetadataTag(NS_LITERAL_CSTRING("name"), val));
+        break;
+    }
+
+    mHeaderParser.Reset();
+  }
+  return true;
+}
+
+media::TimeUnit
+WAVTrackDemuxer::SeekPosition() const
+{
+  TimeUnit pos = Duration(mChunkIndex);
+  if (Duration() > TimeUnit()) {
+    pos = std::min(Duration(), pos);
+  }
+  return pos;
+}
+
+RefPtr<MediaRawData>
+WAVTrackDemuxer::DemuxSample()
+{
+  return GetNextChunk(FindNextChunk());
+}
+
+UniquePtr<TrackInfo>
+WAVTrackDemuxer::GetInfo() const
+{
+  return mInfo->Clone();
+}
+
+RefPtr<WAVTrackDemuxer::SeekPromise>
+WAVTrackDemuxer::Seek(TimeUnit aTime)
+{
+  FastSeek(aTime);
+  const TimeUnit seekTime = ScanUntil(aTime);
+  return SeekPromise::CreateAndResolve(seekTime, __func__);
+}
+
+TimeUnit
+WAVTrackDemuxer::FastSeek(const TimeUnit& aTime)
+{
+  if (aTime.ToMicroseconds()) {
+    mChunkIndex = ChunkIndexFromTime(aTime);
+  } else {
+    mChunkIndex = 0;
+  }
+
+  mOffset = OffsetFromChunkIndex(mChunkIndex);
+
+  if (mOffset > mFirstChunkOffset && StreamLength() > 0) {
+    mOffset = std::min(StreamLength() - 1, mOffset);
+  }
+
+  return Duration(mChunkIndex);
+}
+
+TimeUnit
+WAVTrackDemuxer::ScanUntil(const TimeUnit& aTime)
+{
+  if (!aTime.ToMicroseconds()) {
+    return FastSeek(aTime);
+  }
+
+  if (Duration(mChunkIndex) > aTime) {
+    FastSeek(aTime);
+  }
+
+  return SeekPosition();
+}
+
+RefPtr<WAVTrackDemuxer::SamplesPromise>
+WAVTrackDemuxer::GetSamples(int32_t aNumSamples)
+{
+  if (!aNumSamples) {
+    return SamplesPromise::CreateAndReject(
+        DemuxerFailureReason::DEMUXER_ERROR, __func__);
+  }
+
+  RefPtr<SamplesHolder> datachunks = new SamplesHolder();
+
+  while (aNumSamples--) {
+    RefPtr<MediaRawData> datachunk = GetNextChunk(FindNextChunk());
+    if (!datachunk) {
+      break;
+    }
+    datachunks->mSamples.AppendElement(datachunk);
+  }
+
+  if (datachunks->mSamples.IsEmpty()) {
+    return SamplesPromise::CreateAndReject(
+        DemuxerFailureReason::END_OF_STREAM, __func__);
+  }
+
+  return SamplesPromise::CreateAndResolve(datachunks, __func__);
+}
+
+void
+WAVTrackDemuxer::Reset()
+{
+  FastSeek(TimeUnit());
+  mParser.Reset();
+  mHeaderParser.Reset();
+  mRIFFParser.Reset();
+  mFmtParser.Reset();
+}
+
+RefPtr<WAVTrackDemuxer::SkipAccessPointPromise>
+WAVTrackDemuxer::SkipToNextRandomAccessPoint(TimeUnit aTimeThreshold)
+{
+  return SkipAccessPointPromise::CreateAndReject(
+    SkipFailureHolder(DemuxerFailureReason::DEMUXER_ERROR, 0), __func__);
+}
+
+int64_t
+WAVTrackDemuxer::GetResourceOffset() const
+{
+  return mOffset;
+}
+
+TimeIntervals
+WAVTrackDemuxer::GetBuffered()
+{
+  TimeUnit duration = Duration();
+
+  if (duration <= TimeUnit()) {
+    return TimeIntervals();
+  }
+
+  AutoPinned<MediaResource> stream(mSource.GetResource());
+  return GetEstimatedBufferedTimeRanges(stream, duration.ToMicroseconds());
+}
+
+int64_t
+WAVTrackDemuxer::StreamLength() const
+{
+  return mSource.GetLength();
+}
+
+TimeUnit
+WAVTrackDemuxer::Duration() const
+{
+  if (!mDataLength) {
+    return TimeUnit();
+  }
+
+  int64_t numSamples = mDataLength * 8 / mChannels / mSampleFormat;
+
+  int64_t numUSeconds = USECS_PER_S * numSamples / mSamplesPerSecond;
+
+  if (USECS_PER_S * numSamples % mSamplesPerSecond > mSamplesPerSecond / 2) {
+    numUSeconds++;
+  }
+
+  return TimeUnit::FromMicroseconds(numUSeconds);
+}
+
+TimeUnit
+WAVTrackDemuxer::Duration(int64_t aNumDataChunks) const
+{
+  if (!mSamplesPerSecond) {
+    return TimeUnit();
+  }
+  const double usPerDataChunk = USECS_PER_S * mSamplesPerChunk /
+                                mSamplesPerSecond;
+  return TimeUnit::FromMicroseconds(aNumDataChunks * usPerDataChunk);
+}
+
+TimeUnit
+WAVTrackDemuxer::DurationFromBytes(uint32_t aNumBytes) const
+{
+  if (!mSamplesPerSecond) {
+    return TimeUnit();
+  }
+
+  int64_t numSamples = aNumBytes * 8 / mChannels / mSampleFormat;
+
+  int64_t numUSeconds = USECS_PER_S * numSamples / mSamplesPerSecond;
+
+  if (USECS_PER_S * numSamples % mSamplesPerSecond > mSamplesPerSecond / 2) {
+    numUSeconds++;
+  }
+
+  return TimeUnit::FromMicroseconds(numUSeconds);
+}
+
+MediaByteRange
+WAVTrackDemuxer::FindNextChunk()
+{
+  if (mOffset + DATA_CHUNK_SIZE < mFirstChunkOffset + mDataLength) {
+    return { mOffset, mOffset + DATA_CHUNK_SIZE };
+  } else {
+    return { mOffset, mFirstChunkOffset + mDataLength };
+  }
+}
+
+MediaByteRange
+WAVTrackDemuxer::FindChunkHeader()
+{
+  return { mOffset, mOffset + CHUNK_HEAD_SIZE };
+}
+
+MediaByteRange
+WAVTrackDemuxer::FindRIFFHeader()
+{
+  return { mOffset, mOffset + RIFF_CHUNK_SIZE };
+}
+
+MediaByteRange
+WAVTrackDemuxer::FindFmtChunk()
+{
+  return { mOffset, mOffset + mHeaderParser.GiveHeader().ChunkSize() };
+}
+
+MediaByteRange
+WAVTrackDemuxer::FindListChunk()
+{
+  return { mOffset, mOffset + mHeaderParser.GiveHeader().ChunkSize() };
+}
+
+MediaByteRange
+WAVTrackDemuxer::FindInfoTag()
+{
+  return { mOffset, mOffset + 4 };
+}
+
+bool
+WAVTrackDemuxer::SkipNextChunk(const MediaByteRange& aRange)
+{
+  UpdateState(aRange);
+  return true;
+}
+
+already_AddRefed<MediaRawData>
+WAVTrackDemuxer::GetNextChunk(const MediaByteRange& aRange)
+{
+  if (!aRange.Length()) {
+    return nullptr;
+  }
+
+  RefPtr<MediaRawData> datachunk = new MediaRawData();
+  datachunk->mOffset = aRange.mStart;
+
+  nsAutoPtr<MediaRawDataWriter> chunkWriter(datachunk->CreateWriter());
+  if (!chunkWriter->SetSize(aRange.Length())) {
+    return nullptr;
+  }
+
+  const uint32_t read = Read(chunkWriter->Data(),
+                             datachunk->mOffset,
+                             datachunk->Size());
+
+  if (read != aRange.Length()) {
+    return nullptr;
+  }
+
+  UpdateState(aRange);
+  ++mNumParsedChunks;
+  ++mChunkIndex;
+
+  datachunk->mTime = Duration(mChunkIndex - 1).ToMicroseconds();
+
+  if (static_cast<uint32_t>(mChunkIndex) * DATA_CHUNK_SIZE < mDataLength) {
+    datachunk->mDuration = Duration(1).ToMicroseconds();
+  } else {
+    uint32_t mBytesRemaining =
+      mDataLength - mChunkIndex * DATA_CHUNK_SIZE;
+    datachunk->mDuration = DurationFromBytes(mBytesRemaining).ToMicroseconds();
+  }
+  datachunk->mTimecode = datachunk->mTime;
+  datachunk->mKeyframe = true;
+
+  MOZ_ASSERT(datachunk->mTime >= 0);
+  MOZ_ASSERT(datachunk->mDuration >= 0);
+
+  return datachunk.forget();
+}
+
+already_AddRefed<MediaRawData>
+WAVTrackDemuxer::GetFileHeader(const MediaByteRange& aRange)
+{
+  if (!aRange.Length()) {
+    return nullptr;
+  }
+
+  RefPtr<MediaRawData> fileHeader = new MediaRawData();
+  fileHeader->mOffset = aRange.mStart;
+
+  nsAutoPtr<MediaRawDataWriter> headerWriter(fileHeader->CreateWriter());
+  if (!headerWriter->SetSize(aRange.Length())) {
+    return nullptr;
+  }
+
+  const uint32_t read = Read(headerWriter->Data(),
+                             fileHeader->mOffset,
+                             fileHeader->Size());
+
+  if (read != aRange.Length()) {
+    return nullptr;
+  }
+
+  UpdateState(aRange);
+
+  return fileHeader.forget();
+}
+
+int64_t
+WAVTrackDemuxer::OffsetFromChunkIndex(int64_t aChunkIndex) const
+{
+  return mFirstChunkOffset + aChunkIndex * DATA_CHUNK_SIZE;
+}
+
+int64_t
+WAVTrackDemuxer::ChunkIndexFromOffset(int64_t aOffset) const
+{
+  int64_t chunkIndex = (aOffset - mFirstChunkOffset) / DATA_CHUNK_SIZE;
+  return std::max<int64_t>(0, chunkIndex);
+}
+
+int64_t
+WAVTrackDemuxer::ChunkIndexFromTime(const media::TimeUnit& aTime) const
+{
+  int64_t chunkIndex =
+    (aTime.ToSeconds() * mSamplesPerSecond / mSamplesPerChunk) - 1;
+  return chunkIndex;
+}
+
+void
+WAVTrackDemuxer::UpdateState(const MediaByteRange& aRange)
+{
+  // Full chunk parsed, move offset to its end.
+  mOffset = aRange.mEnd;
+  mTotalChunkLen += aRange.Length();
+}
+
+int32_t
+WAVTrackDemuxer::Read(uint8_t* aBuffer, int64_t aOffset, int32_t aSize)
+{
+  const int64_t streamLen = StreamLength();
+  if (mInfo && streamLen > 0) {
+    aSize = std::min<int64_t>(aSize, streamLen - aOffset);
+  }
+  uint32_t read = 0;
+  const nsresult rv = mSource.ReadAt(aOffset,
+                                     reinterpret_cast<char*>(aBuffer),
+                                     static_cast<uint32_t>(aSize),
+                                     &read);
+  NS_ENSURE_SUCCESS(rv, 0);
+  return static_cast<int32_t>(read);
+}
+
+// RIFFParser
+
+uint32_t
+RIFFParser::Parse(ByteReader& aReader)
+{
+  MOZ_ASSERT(&aReader);
+
+  while (aReader.CanRead8() && !mRiffHeader.ParseNext(aReader.ReadU8())) { }
+
+  if (mRiffHeader.IsValid()) {
+    return RIFF_CHUNK_SIZE;
+  }
+
+  return 0;
+}
+
+void
+RIFFParser::Reset()
+{
+  mRiffHeader.Reset();
+}
+
+const RIFFParser::RIFFHeader&
+RIFFParser::RiffHeader() const
+{
+  return mRiffHeader;
+}
+
+// RIFFParser::RIFFHeader
+
+RIFFParser::RIFFHeader::RIFFHeader()
+{
+  Reset();
+}
+
+void
+RIFFParser::RIFFHeader::Reset()
+{
+  mPos = 0;
+}
+
+bool
+RIFFParser::RIFFHeader::ParseNext(uint8_t c)
+{
+  if (!Update(c)) {
+    Reset();
+    if (!Update(c)) {
+      Reset();
+    }
+  }
+  return IsValid();
+}
+
+bool
+RIFFParser::RIFFHeader::IsValid(int aPos) const
+{
+  if (aPos > -1 && aPos < 4) {
+    return RIFF[aPos] == mRaw[aPos];
+  } else if (aPos > 7 && aPos < 12) {
+    return WAVE[aPos - 8] == mRaw[aPos];
+  } else {
+    return true;
+  }
+}
+
+bool
+RIFFParser::RIFFHeader::IsValid() const
+{
+  return mPos >= RIFF_CHUNK_SIZE;
+}
+
+bool
+RIFFParser::RIFFHeader::Update(uint8_t c)
+{
+  if (mPos < RIFF_CHUNK_SIZE) {
+    mRaw[mPos] = c;
+  }
+  return IsValid(mPos++);
+}
+
+// HeaderParser
+
+uint32_t
+HeaderParser::Parse(ByteReader& aReader)
+{
+  while (aReader.CanRead8() && !mHeader.ParseNext(aReader.ReadU8())) { }
+
+  if (mHeader.IsValid()) {
+    return CHUNK_HEAD_SIZE;
+  }
+
+  return 0;
+}
+
+void
+HeaderParser::Reset()
+{
+  mHeader.Reset();
+}
+
+const HeaderParser::ChunkHeader&
+HeaderParser::GiveHeader() const
+{
+  return mHeader;
+}
+
+// HeaderParser::ChunkHeader
+
+HeaderParser::ChunkHeader::ChunkHeader()
+{
+  Reset();
+}
+
+void
+HeaderParser::ChunkHeader::Reset()
+{
+  mPos = 0;
+}
+
+bool
+HeaderParser::ChunkHeader::ParseNext(uint8_t c)
+{
+  Update(c);
+  return IsValid();
+}
+
+bool
+HeaderParser::ChunkHeader::IsValid() const
+{
+  return mPos >= CHUNK_HEAD_SIZE;
+}
+
+uint32_t
+HeaderParser::ChunkHeader::ChunkName() const
+{
+  return ((mRaw[0] << 24) | (mRaw[1] << 16) |
+          (mRaw[2] << 8 ) | (mRaw[3]));
+}
+
+uint32_t
+HeaderParser::ChunkHeader::ChunkSize() const
+{
+  return ((mRaw[7] << 24) | (mRaw[6] << 16) |
+          (mRaw[5] << 8 ) | (mRaw[4]));
+}
+
+void
+HeaderParser::ChunkHeader::Update(uint8_t c)
+{
+  if (mPos < CHUNK_HEAD_SIZE) {
+    mRaw[mPos++] = c;
+  }
+}
+
+// FormatParser
+
+uint32_t
+FormatParser::Parse(ByteReader& aReader)
+{
+  MOZ_ASSERT(&aReader);
+
+  while (aReader.CanRead8() && !mFmtChunk.ParseNext(aReader.ReadU8())) { }
+
+  if (mFmtChunk.IsValid()) {
+    return FMT_CHUNK_MIN_SIZE;
+  }
+
+  return 0;
+}
+
+void
+FormatParser::Reset()
+{
+  mFmtChunk.Reset();
+}
+
+const FormatParser::FormatChunk&
+FormatParser::FmtChunk() const
+{
+  return mFmtChunk;
+}
+
+// FormatParser::FormatChunk
+
+FormatParser::FormatChunk::FormatChunk()
+{
+  Reset();
+}
+
+void
+FormatParser::FormatChunk::Reset()
+{
+  mPos = 0;
+}
+
+uint16_t
+FormatParser::FormatChunk::WaveFormat() const
+{
+  return (mRaw[1] << 8) | (mRaw[0]);
+}
+
+uint16_t
+FormatParser::FormatChunk::Channels() const
+{
+  return (mRaw[3] << 8) | (mRaw[2]);
+}
+
+uint32_t
+FormatParser::FormatChunk::SampleRate() const
+{
+  return (mRaw[7] << 24) | (mRaw[6] << 16) |
+         (mRaw[5] << 8 ) | (mRaw[4]);
+}
+
+uint16_t
+FormatParser::FormatChunk::FrameSize() const
+{
+  return (mRaw[13] << 8) | (mRaw[12]);
+}
+
+uint16_t
+FormatParser::FormatChunk::SampleFormat() const
+{
+  return (mRaw[15] << 8) | (mRaw[14]);
+}
+
+bool
+FormatParser::FormatChunk::ParseNext(uint8_t c)
+{
+  Update(c);
+  return IsValid();
+}
+
+bool
+FormatParser::FormatChunk::IsValid() const
+{
+  return (FrameSize() == SampleRate() * Channels() / 8) &&
+         (mPos >= FMT_CHUNK_MIN_SIZE);
+}
+
+void
+FormatParser::FormatChunk::Update(uint8_t c)
+{
+  if (mPos < FMT_CHUNK_MIN_SIZE) {
+    mRaw[mPos++] = c;
+  }
+}
+
+// DataParser
+
+DataParser::DataParser()
+{
+}
+
+void
+DataParser::Reset()
+{
+  mChunk.Reset();
+}
+
+const DataParser::DataChunk&
+DataParser::CurrentChunk() const
+{
+  return mChunk;
+}
+
+// DataParser::DataChunk
+
+void
+DataParser::DataChunk::Reset()
+{
+  mPos = 0;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/wave/WaveDemuxer.h
@@ -0,0 +1,262 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * Licence, 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/. */
+
+#ifndef WAV_DEMUXER_H_
+#define WAV_DEMUXER_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "MediaDataDemuxer.h"
+#include "MediaResource.h"
+#include "mp4_demuxer/ByteReader.h"
+
+using mp4_demuxer::ByteReader;
+
+namespace mozilla {
+
+static const uint32_t FRMT_CODE = 0x666d7420;
+static const uint32_t DATA_CODE = 0x64617461;
+static const uint32_t LIST_CODE = 0x4c495354;
+static const uint32_t INFO_CODE = 0x494e464f;
+
+static const uint8_t RIFF[4] = {'R', 'I', 'F', 'F'};
+static const uint8_t WAVE[4] = {'W', 'A', 'V', 'E'};
+
+static const uint16_t RIFF_CHUNK_SIZE = 12;
+static const uint16_t CHUNK_HEAD_SIZE = 8;
+static const uint16_t FMT_CHUNK_MIN_SIZE = 16;
+static const uint16_t DATA_CHUNK_SIZE = 768;
+
+class WAVTrackDemuxer;
+
+class WAVDemuxer : public MediaDataDemuxer {
+public:
+  // MediaDataDemuxer interface.
+  explicit WAVDemuxer(MediaResource* aSource);
+  RefPtr<InitPromise> Init() override;
+  bool HasTrackType(TrackInfo::TrackType aType) const override;
+  uint32_t GetNumberTracks(TrackInfo::TrackType aType) const override;
+  already_AddRefed<MediaTrackDemuxer> GetTrackDemuxer(
+      TrackInfo::TrackType aType, uint32_t aTrackNumber) override;
+  bool IsSeekable() const override;
+
+private:
+  // Synchronous Initialization.
+  bool InitInternal();
+
+  MediaResourceIndex mSource;
+  RefPtr<WAVTrackDemuxer> mTrackDemuxer;
+};
+
+class RIFFParser {
+private:
+  class RIFFHeader;
+public:
+  const RIFFHeader& RiffHeader() const;
+
+  uint32_t Parse(ByteReader& aReader);
+
+  void Reset();
+
+private:
+  class RIFFHeader {
+  public:
+    RIFFHeader();
+    void Reset();
+
+    bool IsValid() const;
+    bool IsValid(int aPos) const;
+
+    bool ParseNext(uint8_t c);
+
+  private:
+    bool Update(uint8_t c);
+
+    uint8_t mRaw[RIFF_CHUNK_SIZE];
+
+    int mPos;
+  };
+
+  RIFFHeader mRiffHeader;
+};
+
+class HeaderParser {
+private:
+  class ChunkHeader;
+public:
+  const ChunkHeader& GiveHeader() const;
+
+  uint32_t Parse(ByteReader& aReader);
+
+  void Reset();
+
+private:
+  class ChunkHeader {
+  public:
+    ChunkHeader();
+    void Reset();
+
+    bool IsValid() const;
+
+    uint32_t ChunkName() const;
+    uint32_t ChunkSize() const;
+
+    bool ParseNext(uint8_t c);
+
+  private:
+    void Update(uint8_t c);
+
+    uint8_t mRaw[CHUNK_HEAD_SIZE];
+
+    int mPos;
+  };
+
+  ChunkHeader mHeader;
+};
+
+class FormatParser {
+private:
+  class FormatChunk;
+public:
+  const FormatChunk& FmtChunk() const;
+
+  uint32_t Parse(ByteReader& aReader);
+
+  void Reset();
+
+private:
+  class FormatChunk {
+  public:
+    FormatChunk();
+    void Reset();
+
+    uint16_t WaveFormat() const;
+    uint16_t Channels() const;
+    uint32_t SampleRate() const;
+    uint16_t FrameSize() const;
+    uint16_t SampleFormat() const;
+
+    bool IsValid() const;
+    bool ParseNext(uint8_t c);
+
+  private:
+    void Update(uint8_t c);
+
+    uint8_t mRaw[FMT_CHUNK_MIN_SIZE];
+
+    int mPos;
+  };
+
+  FormatChunk mFmtChunk;
+};
+
+class DataParser {
+private:
+  class DataChunk;
+public:
+  DataParser();
+
+  const DataChunk& CurrentChunk() const;
+
+  void Reset();
+
+private:
+  class DataChunk {
+  public:
+    void Reset();
+  private:
+    int mPos; // To Check Alignment
+  };
+
+  DataChunk mChunk;
+};
+
+class WAVTrackDemuxer : public MediaTrackDemuxer {
+public:
+  explicit WAVTrackDemuxer(MediaResourceIndex aSource);
+
+  bool Init();
+
+  int64_t StreamLength() const;
+
+  media::TimeUnit Duration() const;
+  media::TimeUnit Duration(int64_t aNumDataChunks) const;
+  media::TimeUnit DurationFromBytes(uint32_t aNumBytes) const;
+
+  media::TimeUnit SeekPosition() const;
+
+  RefPtr<MediaRawData> DemuxSample();
+
+  // MediaTrackDemuxer interface.
+  UniquePtr<TrackInfo> GetInfo() const override;
+  RefPtr<SeekPromise> Seek(media::TimeUnit aTime) override;
+  RefPtr<SamplesPromise> GetSamples(int32_t aNumSamples) override;
+  void Reset() override;
+  RefPtr<SkipAccessPointPromise> SkipToNextRandomAccessPoint(
+    media::TimeUnit aTimeThreshold) override;
+  int64_t GetResourceOffset() const override;
+  media::TimeIntervals GetBuffered() override;
+
+private:
+  ~WAVTrackDemuxer() {}
+
+  media::TimeUnit FastSeek(const media::TimeUnit& aTime);
+  media::TimeUnit ScanUntil(const media::TimeUnit& aTime);
+
+  MediaByteRange FindNextChunk();
+
+  MediaByteRange FindChunkHeader();
+  MediaByteRange FindRIFFHeader();
+  MediaByteRange FindFmtChunk();
+  MediaByteRange FindListChunk();
+  MediaByteRange FindInfoTag();
+
+  bool RIFFParserInit();
+  bool HeaderParserInit();
+  bool FmtChunkParserInit();
+  bool ListChunkParserInit(uint32_t aChunkSize);
+
+  bool SkipNextChunk(const MediaByteRange& aRange);
+
+  already_AddRefed<MediaRawData> GetNextChunk(const MediaByteRange& aRange);
+  already_AddRefed<MediaRawData> GetFileHeader(const MediaByteRange& aRange);
+
+  void UpdateState(const MediaByteRange& aRange);
+
+  int64_t OffsetFromChunkIndex(int64_t aChunkIndex) const;
+  int64_t ChunkIndexFromOffset(int64_t aOffet) const;
+  int64_t ChunkIndexFromTime(const media::TimeUnit& aTime) const;
+
+  int32_t Read(uint8_t* aBuffer, int64_t aOffset, int32_t aSize);
+
+  MediaResourceIndex mSource;
+
+  DataParser mParser;
+  RIFFParser mRIFFParser;
+  HeaderParser mHeaderParser;
+
+  FormatParser mFmtParser;
+  // ListChunkParser mListChunkParser;
+
+  int64_t mOffset;
+  int64_t mFirstChunkOffset;
+
+  uint32_t mNumParsedChunks;
+  int32_t mChunkIndex;
+
+  uint32_t mDataLength;
+  uint64_t mTotalChunkLen;
+
+  int32_t mSamplesPerChunk;
+  int32_t mSamplesPerSecond;
+
+  int32_t mChannels;
+  int32_t mSampleFormat;
+
+  UniquePtr<AudioInfo> mInfo;
+};
+
+} // namespace mozilla
+
+#endif
--- a/dom/media/wave/moz.build
+++ b/dom/media/wave/moz.build
@@ -6,12 +6,13 @@
 
 EXPORTS += [
     'WaveDecoder.h',
     'WaveReader.h',
 ]
 
 UNIFIED_SOURCES += [
     'WaveDecoder.cpp',
+    'WaveDemuxer.cpp',
     'WaveReader.cpp',
 ]
 
 FINAL_LIBRARY = 'xul'
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -344,16 +344,17 @@ pref("media.gmp.decoder.enabled", false)
 pref("media.gmp.decoder.aac", 0);
 pref("media.gmp.decoder.h264", 0);
 #ifdef MOZ_RAW
 pref("media.raw.enabled", true);
 #endif
 pref("media.ogg.enabled", true);
 pref("media.opus.enabled", true);
 pref("media.wave.enabled", true);
+pref("media.wave.decoder.enabled", false);
 pref("media.webm.enabled", true);
 #if defined(MOZ_FMP4) && defined(MOZ_WMF)
 pref("media.webm.intel_decoder.enabled", false);
 #endif
 
 #ifdef MOZ_APPLEMEDIA
 #ifdef MOZ_WIDGET_UIKIT
 pref("media.mp3.enabled", true);