summaryrefslogtreecommitdiffstats
path: root/dom/media/wave
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/wave')
-rw-r--r--dom/media/wave/WaveDecoder.cpp45
-rw-r--r--dom/media/wave/WaveDecoder.h26
-rw-r--r--dom/media/wave/WaveDemuxer.cpp898
-rw-r--r--dom/media/wave/WaveDemuxer.h262
-rw-r--r--dom/media/wave/moz.build20
5 files changed, 1251 insertions, 0 deletions
diff --git a/dom/media/wave/WaveDecoder.cpp b/dom/media/wave/WaveDecoder.cpp
new file mode 100644
index 000000000..b31191baa
--- /dev/null
+++ b/dom/media/wave/WaveDecoder.cpp
@@ -0,0 +1,45 @@
+/* -*- 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 "MediaDecoderStateMachine.h"
+#include "WaveDecoder.h"
+#include "MediaFormatReader.h"
+#include "PDMFactory.h"
+
+namespace mozilla {
+
+MediaDecoder*
+WaveDecoder::Clone(MediaDecoderOwner* aOwner)
+{
+ return new WaveDecoder(aOwner);
+}
+
+MediaDecoderStateMachine*
+WaveDecoder::CreateStateMachine()
+{
+ return new MediaDecoderStateMachine(
+ this, new MediaFormatReader(this, new WAVDemuxer(GetResource())));
+}
+
+/* static */
+bool
+WaveDecoder::CanHandleMediaType(const nsACString& aType,
+ const nsAString& aCodecs)
+{
+ if (!IsWaveEnabled()) {
+ return false;
+ }
+ if (aType.EqualsASCII("audio/wave") || aType.EqualsASCII("audio/x-wav") ||
+ aType.EqualsASCII("audio/wav") || aType.EqualsASCII("audio/x-pn-wav")) {
+ return (aCodecs.IsEmpty() || aCodecs.EqualsASCII("1") ||
+ aCodecs.EqualsASCII("6") || aCodecs.EqualsASCII("7"));
+ }
+
+ return false;
+}
+
+} // namespace mozilla
diff --git a/dom/media/wave/WaveDecoder.h b/dom/media/wave/WaveDecoder.h
new file mode 100644
index 000000000..4ed61ce85
--- /dev/null
+++ b/dom/media/wave/WaveDecoder.h
@@ -0,0 +1,26 @@
+/* -*- 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 "MediaDecoder.h"
+
+namespace mozilla {
+
+class WaveDecoder : public MediaDecoder {
+public:
+ // MediaDecoder interface.
+ explicit WaveDecoder(MediaDecoderOwner* aOwner) : MediaDecoder(aOwner) {}
+ MediaDecoder* Clone(MediaDecoderOwner* aOwner) override;
+ MediaDecoderStateMachine* CreateStateMachine() override;
+
+ static bool CanHandleMediaType(const nsACString& aType,
+ const nsAString& aCodecs);
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/wave/WaveDemuxer.cpp b/dom/media/wave/WaveDemuxer.cpp
new file mode 100644
index 000000000..387a75675
--- /dev/null
+++ b/dom/media/wave/WaveDemuxer.cpp
@@ -0,0 +1,898 @@
+/* -*- 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/EndianUtils.h"
+#include "nsAutoPtr.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(
+ NS_ERROR_DOM_MEDIA_METADATA_ERR, __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();
+
+ if (aChunkName == FRMT_CODE) {
+ if (!FmtChunkParserInit()) {
+ return false;
+ }
+ } else if (aChunkName == LIST_CODE) {
+ mHeaderParser.Reset();
+ uint64_t endOfListChunk = static_cast<uint64_t>(mOffset) + aChunkSize;
+ if (endOfListChunk > UINT32_MAX) {
+ return false;
+ }
+ 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.
+ }
+ if (mOffset & 1) {
+ // Wave files are 2-byte aligned so we need to round up
+ mOffset += 1;
+ }
+ mHeaderParser.Reset();
+ }
+
+ if (mDataLength > StreamLength() - mFirstChunkOffset) {
+ mDataLength = StreamLength() - mFirstChunkOffset;
+ }
+
+ mSamplesPerSecond = mFmtParser.FmtChunk().SampleRate();
+ mChannels = mFmtParser.FmtChunk().Channels();
+ mSampleFormat = mFmtParser.FmtChunk().SampleFormat();
+ if (!mSamplesPerSecond || !mChannels || !mSampleFormat) {
+ return false;
+ }
+ 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(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(header->Data(), 8);
+ mHeaderParser.Parse(HeaderReader);
+ return true;
+}
+
+bool
+WAVTrackDemuxer::FmtChunkParserInit()
+{
+ RefPtr<MediaRawData> fmtChunk = GetFileHeader(FindFmtChunk());
+ if (!fmtChunk) {
+ return false;
+ }
+ ByteReader fmtReader(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(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* rawData = reinterpret_cast<const char*>(mChunkData->Data());
+
+ nsCString val(rawData, 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)
+{
+ MOZ_ASSERT(aNumSamples);
+
+ 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(
+ NS_ERROR_DOM_MEDIA_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(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, 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 ||!mChannels || !mSampleFormat) {
+ return TimeUnit();
+ }
+
+ int64_t numSamples =
+ static_cast<int64_t>(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 || !mSamplesPerChunk) {
+ return TimeUnit();
+ }
+ const double usPerDataChunk = USECS_PER_S *
+ static_cast<double>(mSamplesPerChunk) /
+ mSamplesPerSecond;
+ return TimeUnit::FromMicroseconds(aNumDataChunks * usPerDataChunk);
+}
+
+TimeUnit
+WAVTrackDemuxer::DurationFromBytes(uint32_t aNumBytes) const
+{
+ if (!mSamplesPerSecond || !mChannels || !mSampleFormat) {
+ 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
+{
+ if (!mSamplesPerChunk || !mSamplesPerSecond) {
+ return 0;
+ }
+ 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)
+{
+ 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()
+{
+ memset(mRaw, 0, sizeof(mRaw));
+ 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()
+{
+ memset(mRaw, 0, sizeof(mRaw));
+ 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)
+{
+ 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()
+{
+ memset(mRaw, 0, sizeof(mRaw));
+ 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
diff --git a/dom/media/wave/WaveDemuxer.h b/dom/media/wave/WaveDemuxer.h
new file mode 100644
index 000000000..3424e1630
--- /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
diff --git a/dom/media/wave/moz.build b/dom/media/wave/moz.build
new file mode 100644
index 000000000..f91e8eaeb
--- /dev/null
+++ b/dom/media/wave/moz.build
@@ -0,0 +1,20 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS += [
+ 'WaveDecoder.h',
+ 'WaveDemuxer.h',
+]
+
+UNIFIED_SOURCES += [
+ 'WaveDecoder.cpp',
+ 'WaveDemuxer.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']