summaryrefslogtreecommitdiffstats
path: root/dom/media/wave
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/wave')
-rw-r--r--dom/media/wave/WaveDecoder.cpp56
-rw-r--r--dom/media/wave/WaveDecoder.h28
-rw-r--r--dom/media/wave/WaveDemuxer.cpp760
-rw-r--r--dom/media/wave/WaveDemuxer.h248
-rw-r--r--dom/media/wave/moz.build20
5 files changed, 1112 insertions, 0 deletions
diff --git a/dom/media/wave/WaveDecoder.cpp b/dom/media/wave/WaveDecoder.cpp
new file mode 100644
index 0000000000..2aea6b4178
--- /dev/null
+++ b/dom/media/wave/WaveDecoder.cpp
@@ -0,0 +1,56 @@
+/* -*- 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 "WaveDecoder.h"
+#include "MediaContainerType.h"
+#include "MediaDecoder.h"
+
+namespace mozilla {
+
+/* static */
+bool WaveDecoder::IsSupportedType(const MediaContainerType& aContainerType) {
+ if (!MediaDecoder::IsWaveEnabled()) {
+ return false;
+ }
+ if (aContainerType.Type() == MEDIAMIMETYPE("audio/wave") ||
+ aContainerType.Type() == MEDIAMIMETYPE("audio/x-wav") ||
+ aContainerType.Type() == MEDIAMIMETYPE("audio/wav") ||
+ aContainerType.Type() == MEDIAMIMETYPE("audio/x-pn-wav")) {
+ return (aContainerType.ExtendedType().Codecs().IsEmpty() ||
+ aContainerType.ExtendedType().Codecs() == "1" ||
+ aContainerType.ExtendedType().Codecs() == "3" ||
+ aContainerType.ExtendedType().Codecs() == "6" ||
+ aContainerType.ExtendedType().Codecs() == "7");
+ }
+
+ return false;
+}
+
+/* static */
+nsTArray<UniquePtr<TrackInfo>> WaveDecoder::GetTracksInfo(
+ const MediaContainerType& aType) {
+ nsTArray<UniquePtr<TrackInfo>> tracks;
+ if (!IsSupportedType(aType)) {
+ return tracks;
+ }
+
+ const MediaCodecs& codecs = aType.ExtendedType().Codecs();
+ if (codecs.IsEmpty()) {
+ tracks.AppendElement(
+ CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
+ "audio/x-wav"_ns, aType));
+ return tracks;
+ }
+
+ for (const auto& codec : codecs.Range()) {
+ tracks.AppendElement(
+ CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
+ "audio/wave; codecs="_ns + NS_ConvertUTF16toUTF8(codec), aType));
+ }
+ return tracks;
+}
+
+} // namespace mozilla
diff --git a/dom/media/wave/WaveDecoder.h b/dom/media/wave/WaveDecoder.h
new file mode 100644
index 0000000000..448602de9a
--- /dev/null
+++ b/dom/media/wave/WaveDecoder.h
@@ -0,0 +1,28 @@
+/* -*- 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 "mozilla/UniquePtr.h"
+# include "nsTArray.h"
+
+namespace mozilla {
+
+class MediaContainerType;
+class TrackInfo;
+
+class WaveDecoder {
+ public:
+ // Returns true if the Wave backend is pref'ed on, and we're running on a
+ // platform that is likely to have decoders for the format.
+ static bool IsSupportedType(const MediaContainerType& aContainerType);
+ static nsTArray<UniquePtr<TrackInfo>> GetTracksInfo(
+ const MediaContainerType& aType);
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/wave/WaveDemuxer.cpp b/dom/media/wave/WaveDemuxer.cpp
new file mode 100644
index 0000000000..198834f5e5
--- /dev/null
+++ b/dom/media/wave/WaveDemuxer.cpp
@@ -0,0 +1,760 @@
+/* -*- 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/Utf8.h"
+#include "BufferReader.h"
+#include "VideoUtils.h"
+#include "TimeUnits.h"
+#include "mozilla/Logging.h"
+
+using mozilla::media::TimeIntervals;
+using mozilla::media::TimeUnit;
+
+extern mozilla::LazyLogModule gMediaDemuxerLog;
+
+namespace mozilla {
+
+#define LOG(msg, ...) \
+ MOZ_LOG(gMediaDemuxerLog, LogLevel::Debug, msg, ##__VA_ARGS__)
+
+WAVDemuxer::WAVDemuxer(MediaResource* aSource) : mSource(aSource) {
+ DDLINKCHILD("source", aSource);
+}
+
+bool WAVDemuxer::InitInternal() {
+ if (!mTrackDemuxer) {
+ mTrackDemuxer = new WAVTrackDemuxer(mSource.GetResource());
+ DDLINKCHILD("track demuxer", mTrackDemuxer.get());
+ }
+ 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__);
+}
+
+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(MediaResource* aSource)
+ : mSource(aSource),
+ mOffset(0),
+ mFirstChunkOffset(0),
+ mNumParsedChunks(0),
+ mChunkIndex(0),
+ mDataLength(0),
+ mTotalChunkLen(0),
+ mSamplesPerChunk(0),
+ mSamplesPerSecond(0),
+ mChannels(0),
+ mSampleFormat(0) {
+ DDLINKCHILD("source", aSource);
+ Reset();
+}
+
+bool WAVTrackDemuxer::Init() {
+ Reset();
+ FastSeek(TimeUnit());
+
+ if (!mInfo) {
+ mInfo = MakeUnique<AudioInfo>();
+ mInfo->mCodecSpecificConfig =
+ AudioCodecSpecificVariant{WaveCodecSpecificData{}};
+ }
+
+ if (!RIFFParserInit()) {
+ return false;
+ }
+
+ bool hasValidFmt = false;
+
+ while (true) {
+ if (!HeaderParserInit()) {
+ return false;
+ }
+
+ uint32_t chunkName = mHeaderParser.GiveHeader().ChunkName();
+ uint32_t chunkSize = mHeaderParser.GiveHeader().ChunkSize();
+
+ if (chunkName == FRMT_CODE) {
+ hasValidFmt = FmtChunkParserInit();
+ } else if (chunkName == LIST_CODE) {
+ mHeaderParser.Reset();
+ uint64_t endOfListChunk = static_cast<uint64_t>(mOffset) + chunkSize;
+ if (endOfListChunk > UINT32_MAX) {
+ return false;
+ }
+ if (!ListChunkParserInit(chunkSize)) {
+ mOffset = endOfListChunk;
+ }
+ } else if (chunkName == DATA_CODE) {
+ mDataLength = chunkSize;
+ if (mFirstChunkOffset != mOffset) {
+ mFirstChunkOffset = mOffset;
+ }
+ break;
+ } else {
+ mOffset += chunkSize; // Skip other irrelevant chunks.
+ }
+ if (mOffset & 1) {
+ // Wave files are 2-byte aligned so we need to round up
+ mOffset += 1;
+ }
+ mHeaderParser.Reset();
+ }
+
+ if (!hasValidFmt) {
+ return false;
+ }
+
+ int64_t streamLength = StreamLength();
+ // If the chunk length and the resource length are not equal, use the
+ // resource length as the "real" data chunk length, if it's longer than the
+ // chunk size.
+ if (streamLength != -1) {
+ uint64_t streamLengthPositive = static_cast<uint64_t>(streamLength);
+ if (streamLengthPositive > mFirstChunkOffset &&
+ mDataLength > streamLengthPositive - mFirstChunkOffset) {
+ mDataLength = streamLengthPositive - mFirstChunkOffset;
+ }
+ }
+
+ mSamplesPerSecond = mFmtChunk.SampleRate();
+ mChannels = mFmtChunk.Channels();
+ if (!mSamplesPerSecond || !mChannels || !mFmtChunk.ValidBitsPerSamples()) {
+ return false;
+ }
+ mSamplesPerChunk =
+ DATA_CHUNK_SIZE * 8 / mChannels / mFmtChunk.ValidBitsPerSamples();
+ mSampleFormat = mFmtChunk.ValidBitsPerSamples();
+
+ mInfo->mRate = mSamplesPerSecond;
+ mInfo->mChannels = mChannels;
+ mInfo->mBitDepth = mFmtChunk.ValidBitsPerSamples();
+ mInfo->mProfile = AssertedCast<uint8_t>(mFmtChunk.WaveFormat() & 0x00FF);
+ mInfo->mExtendedProfile =
+ AssertedCast<uint8_t>(mFmtChunk.WaveFormat() & 0xFF00 >> 8);
+ mInfo->mMimeType = "audio/wave; codecs=";
+ mInfo->mMimeType.AppendInt(mFmtChunk.WaveFormat());
+ mInfo->mDuration = Duration();
+ mInfo->mChannelMap = mFmtChunk.ChannelMap();
+
+ if (AudioConfig::ChannelLayout::Channels(mInfo->mChannelMap) !=
+ mInfo->mChannels) {
+ AudioConfig::ChannelLayout::ChannelMap defaultForChannelCount =
+ AudioConfig::ChannelLayout(mInfo->mChannels).Map();
+ LOG(("Channel count of %" PRIu32
+ " and channel layout disagree, overriding channel map from %s to %s",
+ mInfo->mChannels,
+ AudioConfig::ChannelLayout::ChannelMapToString(mInfo->mChannelMap)
+ .get(),
+ AudioConfig::ChannelLayout::ChannelMapToString(defaultForChannelCount)
+ .get()));
+ mInfo->mChannelMap = defaultForChannelCount;
+ }
+ LOG(("WavDemuxer initialized: %s", mInfo->ToString().get()));
+
+ return mInfo->mDuration.IsPositive();
+}
+
+bool WAVTrackDemuxer::RIFFParserInit() {
+ RefPtr<MediaRawData> riffHeader = GetFileHeader(FindRIFFHeader());
+ if (!riffHeader) {
+ return false;
+ }
+ BufferReader RIFFReader(riffHeader->Data(), 12);
+ Unused << mRIFFParser.Parse(RIFFReader);
+ return mRIFFParser.RiffHeader().IsValid(11);
+}
+
+bool WAVTrackDemuxer::HeaderParserInit() {
+ RefPtr<MediaRawData> header = GetFileHeader(FindChunkHeader());
+ if (!header) {
+ return false;
+ }
+ BufferReader headerReader(header->Data(), 8);
+ Unused << mHeaderParser.Parse(headerReader);
+ return true;
+}
+
+bool WAVTrackDemuxer::FmtChunkParserInit() {
+ RefPtr<MediaRawData> fmtChunk = GetFileHeader(FindFmtChunk());
+ if (!fmtChunk || fmtChunk->Size() < 16) {
+ return false;
+ }
+ nsTArray<uint8_t> fmtChunkData(fmtChunk->Data(), fmtChunk->Size());
+ mFmtChunk.Init(std::move(fmtChunkData));
+ return true;
+}
+
+bool WAVTrackDemuxer::ListChunkParserInit(uint32_t aChunkSize) {
+ uint32_t bytesRead = 0;
+
+ RefPtr<MediaRawData> infoTag = GetFileHeader(FindInfoTag());
+ if (!infoTag) {
+ return false;
+ }
+
+ BufferReader infoTagReader(infoTag->Data(), 4);
+ auto res = infoTagReader.ReadU32();
+ if (res.isErr() || (res.isOk() && res.unwrap() != 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("artist"_ns, val));
+ break;
+ case 0x49434d54: // ICMT
+ mInfo->mTags.AppendElement(MetadataTag("comments"_ns, val));
+ break;
+ case 0x49474e52: // IGNR
+ mInfo->mTags.AppendElement(MetadataTag("genre"_ns, val));
+ break;
+ case 0x494e414d: // INAM
+ mInfo->mTags.AppendElement(MetadataTag("name"_ns, val));
+ break;
+ default:
+ LOG(("Metadata key %08x not handled", id));
+ }
+
+ 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(
+ const 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(static_cast<uint64_t>(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;
+ }
+ if (!datachunk->HasValidTime()) {
+ return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR,
+ __func__);
+ }
+ datachunks->AppendSample(datachunk);
+ }
+
+ if (datachunks->GetSamples().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();
+}
+
+RefPtr<WAVTrackDemuxer::SkipAccessPointPromise>
+WAVTrackDemuxer::SkipToNextRandomAccessPoint(const TimeUnit& aTimeThreshold) {
+ return SkipAccessPointPromise::CreateAndReject(
+ SkipFailureHolder(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, 0), __func__);
+}
+
+int64_t WAVTrackDemuxer::GetResourceOffset() const {
+ return AssertedCast<int64_t>(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 int64_t frames = mSamplesPerChunk * aNumDataChunks;
+ return TimeUnit(frames, mSamplesPerSecond);
+}
+
+TimeUnit WAVTrackDemuxer::DurationFromBytes(uint32_t aNumBytes) const {
+ if (!mSamplesPerSecond || !mChannels || !mSampleFormat) {
+ return TimeUnit();
+ }
+
+ uint64_t numSamples = aNumBytes * 8 / mChannels / mSampleFormat;
+
+ return TimeUnit(numSamples, mSamplesPerSecond);
+}
+
+MediaByteRange WAVTrackDemuxer::FindNextChunk() {
+ if (mOffset + DATA_CHUNK_SIZE < mFirstChunkOffset + mDataLength) {
+ return {mOffset, mOffset + DATA_CHUNK_SIZE};
+ }
+ 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;
+
+ UniquePtr<MediaRawDataWriter> chunkWriter(datachunk->CreateWriter());
+ if (!chunkWriter->SetSize(static_cast<uint32_t>(aRange.Length()))) {
+ return nullptr;
+ }
+
+ const uint32_t read = Read(chunkWriter->Data(), datachunk->mOffset,
+ AssertedCast<int64_t>(datachunk->Size()));
+
+ if (read != aRange.Length()) {
+ return nullptr;
+ }
+
+ UpdateState(aRange);
+ ++mNumParsedChunks;
+ ++mChunkIndex;
+
+ datachunk->mTime = Duration(mChunkIndex - 1);
+
+ if (static_cast<uint32_t>(mChunkIndex) * DATA_CHUNK_SIZE < mDataLength) {
+ datachunk->mDuration = Duration(1);
+ } else {
+ uint32_t mBytesRemaining = mDataLength - mChunkIndex * DATA_CHUNK_SIZE;
+ datachunk->mDuration = DurationFromBytes(mBytesRemaining);
+ }
+ datachunk->mTimecode = datachunk->mTime;
+ datachunk->mKeyframe = true;
+
+ MOZ_ASSERT(!datachunk->mTime.IsNegative());
+ MOZ_ASSERT(!datachunk->mDuration.IsNegative());
+
+ 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;
+
+ UniquePtr<MediaRawDataWriter> headerWriter(fileHeader->CreateWriter());
+ if (!headerWriter->SetSize(static_cast<uint32_t>(aRange.Length()))) {
+ return nullptr;
+ }
+
+ const uint32_t read = Read(headerWriter->Data(), fileHeader->mOffset,
+ AssertedCast<int64_t>(fileHeader->Size()));
+
+ if (read != aRange.Length()) {
+ return nullptr;
+ }
+
+ UpdateState(aRange);
+
+ return fileHeader.forget();
+}
+
+uint64_t WAVTrackDemuxer::OffsetFromChunkIndex(uint32_t aChunkIndex) const {
+ return mFirstChunkOffset + aChunkIndex * DATA_CHUNK_SIZE;
+}
+
+uint64_t WAVTrackDemuxer::ChunkIndexFromTime(
+ const media::TimeUnit& aTime) const {
+ if (!mSamplesPerChunk || !mSamplesPerSecond) {
+ return 0;
+ }
+ double chunkDurationS =
+ mSamplesPerChunk / static_cast<double>(mSamplesPerSecond);
+ int64_t chunkIndex = std::floor(aTime.ToSeconds() / chunkDurationS);
+ return chunkIndex;
+}
+
+void WAVTrackDemuxer::UpdateState(const MediaByteRange& aRange) {
+ // Full chunk parsed, move offset to its end.
+ mOffset = static_cast<uint32_t>(aRange.mEnd);
+ mTotalChunkLen += static_cast<uint64_t>(aRange.Length());
+}
+
+int64_t WAVTrackDemuxer::Read(uint8_t* aBuffer, int64_t aOffset,
+ int64_t aSize) {
+ const int64_t streamLen = StreamLength();
+ if (mInfo && streamLen > 0) {
+ int64_t max = streamLen > aOffset ? streamLen - aOffset : 0;
+ aSize = std::min(aSize, max);
+ }
+ 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 read;
+}
+
+// RIFFParser
+
+Result<uint32_t, nsresult> RIFFParser::Parse(BufferReader& aReader) {
+ for (auto res = aReader.ReadU8();
+ res.isOk() && !mRiffHeader.ParseNext(res.unwrap());
+ res = 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];
+ }
+ if (aPos > 7 && aPos < 12) {
+ return WAVE[aPos - 8] == mRaw[aPos];
+ }
+ 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
+
+Result<uint32_t, nsresult> HeaderParser::Parse(BufferReader& aReader) {
+ for (auto res = aReader.ReadU8();
+ res.isOk() && !mHeader.ParseNext(res.unwrap()); res = 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 static_cast<uint32_t>(
+ ((mRaw[0] << 24) | (mRaw[1] << 16) | (mRaw[2] << 8) | (mRaw[3])));
+}
+
+uint32_t HeaderParser::ChunkHeader::ChunkSize() const {
+ return static_cast<uint32_t>(
+ ((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;
+ }
+}
+
+// FormatChunk
+
+void FormatChunk::Init(nsTArray<uint8_t>&& aData) { mRaw = std::move(aData); }
+
+uint16_t FormatChunk::WaveFormat() const { return (mRaw[1] << 8) | (mRaw[0]); }
+
+uint16_t FormatChunk::Channels() const { return (mRaw[3] << 8) | (mRaw[2]); }
+
+uint32_t FormatChunk::SampleRate() const {
+ return static_cast<uint32_t>((mRaw[7] << 24) | (mRaw[6] << 16) |
+ (mRaw[5] << 8) | (mRaw[4]));
+}
+
+uint16_t FormatChunk::AverageBytesPerSec() const {
+ return static_cast<uint16_t>((mRaw[11] << 24) | (mRaw[10] << 16) |
+ (mRaw[9] << 8) | (mRaw[8]));
+}
+
+uint16_t FormatChunk::BlockAlign() const {
+ return static_cast<uint16_t>(mRaw[13] << 8) | (mRaw[12]);
+}
+
+uint16_t FormatChunk::ValidBitsPerSamples() const {
+ return (mRaw[15] << 8) | (mRaw[14]);
+}
+
+uint16_t FormatChunk::ExtraFormatInfoSize() const {
+ uint16_t value = static_cast<uint16_t>(mRaw[17] << 8) | (mRaw[16]);
+ if (WaveFormat() != 0xFFFE && value != 0) {
+ NS_WARNING(
+ "Found non-zero extra format info length and the wave format"
+ " isn't WAVEFORMATEXTENSIBLE.");
+ return 0;
+ }
+ if (WaveFormat() == 0xFFFE && value < 22) {
+ NS_WARNING(
+ "Wave format is WAVEFORMATEXTENSIBLE and extra data size isn't at"
+ " least 22 bytes");
+ return 0;
+ }
+ return value;
+}
+
+AudioConfig::ChannelLayout::ChannelMap FormatChunk::ChannelMap() const {
+ // Regular mapping if file doesn't have channel mapping info. Alternatively,
+ // if the chunk size doesn't have the field for the size of the extension
+ // data, return a regular mapping.
+ constexpr size_t SIZE_WAVEFORMATEX = 18;
+ constexpr size_t MIN_SIZE_WAVEFORMATEXTENSIBLE = 22;
+ constexpr size_t OFFSET_CHANNEL_MAP = 20;
+ if (WaveFormat() != 0xFFFE || mRaw.Length() <= SIZE_WAVEFORMATEX) {
+ return AudioConfig::ChannelLayout(Channels()).Map();
+ }
+ // The length of this chunk is at least 18, check if it's long enough to
+ // hold the WAVE_FORMAT_EXTENSIBLE struct, that is 22 more than the
+ // WAVEFORMATEX. If not, fall back to a common mapping. The channel
+ // mapping is four bytes, starting at offset 20.
+ if (ExtraFormatInfoSize() < MIN_SIZE_WAVEFORMATEXTENSIBLE ||
+ mRaw.Length() < SIZE_WAVEFORMATEX + MIN_SIZE_WAVEFORMATEXTENSIBLE) {
+ return AudioConfig::ChannelLayout(Channels()).Map();
+ }
+ // ChannelLayout::ChannelMap is by design bit-per-bit compatible with
+ // WAVEFORMATEXTENSIBLE's dwChannelMask attribute.
+ BufferReader reader(mRaw.Elements() + OFFSET_CHANNEL_MAP, sizeof(uint32_t));
+ auto channelMap = reader.ReadLEU32();
+ return channelMap.unwrapOr(AudioConfig::ChannelLayout::UNKNOWN_MAP);
+}
+
+// DataParser
+
+DataParser::DataParser() = default;
+
+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 0000000000..cfde360374
--- /dev/null
+++ b/dom/media/wave/WaveDemuxer.h
@@ -0,0 +1,248 @@
+/* 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 "MediaDataDemuxer.h"
+#include "MediaResource.h"
+
+namespace mozilla {
+class BufferReader;
+
+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;
+
+DDLoggedTypeDeclNameAndBase(WAVDemuxer, MediaDataDemuxer);
+DDLoggedTypeNameAndBase(WAVTrackDemuxer, MediaTrackDemuxer);
+
+class WAVDemuxer : public MediaDataDemuxer,
+ public DecoderDoctorLifeLogger<WAVDemuxer> {
+ public:
+ // MediaDataDemuxer interface.
+ explicit WAVDemuxer(MediaResource* aSource);
+ RefPtr<InitPromise> Init() 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;
+
+ Result<uint32_t, nsresult> Parse(BufferReader& 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 = 0;
+ };
+
+ RIFFHeader mRiffHeader;
+};
+
+class HeaderParser {
+ private:
+ class ChunkHeader;
+
+ public:
+ const ChunkHeader& GiveHeader() const;
+
+ Result<uint32_t, nsresult> Parse(BufferReader& 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 = 0;
+ };
+
+ ChunkHeader mHeader;
+};
+
+class FormatChunk {
+ public:
+ FormatChunk() = default;
+ void Init(nsTArray<uint8_t>&& aData);
+
+ uint16_t WaveFormat() const;
+ uint16_t Channels() const;
+ uint32_t SampleRate() const;
+ uint16_t ExtraFormatInfoSize() const;
+ uint16_t SampleFormat() const;
+ uint16_t AverageBytesPerSec() const;
+ uint16_t BlockAlign() const;
+ uint16_t ValidBitsPerSamples() const;
+ AudioConfig::ChannelLayout::ChannelMap ChannelMap() const;
+
+ private:
+ nsTArray<uint8_t> mRaw;
+};
+
+class DataParser {
+ private:
+ class DataChunk;
+
+ public:
+ DataParser();
+
+ const DataChunk& CurrentChunk() const;
+
+ void Reset();
+
+ private:
+ class DataChunk {
+ public:
+ void Reset();
+
+ private:
+ int mPos = 0; // To Check Alignment
+ };
+
+ DataChunk mChunk;
+};
+
+class WAVTrackDemuxer : public MediaTrackDemuxer,
+ public DecoderDoctorLifeLogger<WAVTrackDemuxer> {
+ public:
+ explicit WAVTrackDemuxer(MediaResource* 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(const media::TimeUnit& aTime) override;
+ RefPtr<SamplesPromise> GetSamples(int32_t aNumSamples) override;
+ void Reset() override;
+ RefPtr<SkipAccessPointPromise> SkipToNextRandomAccessPoint(
+ const media::TimeUnit& aTimeThreshold) override;
+ int64_t GetResourceOffset() const override;
+ media::TimeIntervals GetBuffered() override;
+
+ private:
+ ~WAVTrackDemuxer() = default;
+
+ 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);
+
+ uint64_t OffsetFromChunkIndex(uint32_t aChunkIndex) const;
+ uint64_t ChunkIndexFromTime(const media::TimeUnit& aTime) const;
+
+ int64_t Read(uint8_t* aBuffer, int64_t aOffset, int64_t aSize);
+
+ MediaResourceIndex mSource;
+
+ DataParser mParser;
+ RIFFParser mRIFFParser;
+ HeaderParser mHeaderParser;
+
+ FormatChunk mFmtChunk;
+ // ListChunkParser mListChunkParser;
+
+ uint64_t mOffset;
+ uint64_t mFirstChunkOffset;
+
+ uint32_t mNumParsedChunks;
+ uint32_t mChunkIndex;
+
+ uint32_t mDataLength;
+ uint64_t mTotalChunkLen;
+
+ uint32_t mSamplesPerChunk;
+ uint32_t mSamplesPerSecond;
+
+ uint32_t mChannels;
+ uint32_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 0000000000..04eeba1b77
--- /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"
+
+# Add libFuzzer configuration directives
+include("/tools/fuzzing/libfuzzer-config.mozbuild")