summaryrefslogtreecommitdiffstats
path: root/dom/media/wave/WaveDemuxer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/wave/WaveDemuxer.cpp')
-rw-r--r--dom/media/wave/WaveDemuxer.cpp739
1 files changed, 739 insertions, 0 deletions
diff --git a/dom/media/wave/WaveDemuxer.cpp b/dom/media/wave/WaveDemuxer.cpp
new file mode 100644
index 0000000000..814883dc54
--- /dev/null
+++ b/dom/media/wave/WaveDemuxer.cpp
@@ -0,0 +1,739 @@
+/* -*- 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 "mozilla/Utf8.h"
+#include "BufferReader.h"
+#include "VideoUtils.h"
+#include "TimeUnits.h"
+
+using mozilla::media::TimeIntervals;
+using mozilla::media::TimeUnit;
+
+namespace mozilla {
+
+// WAVDemuxer
+
+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;
+ }
+
+ 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();
+ }
+
+ 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 = 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();
+
+ 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) {
+ return false;
+ }
+ BufferReader fmtReader(fmtChunk->Data(),
+ mHeaderParser.GiveHeader().ChunkSize());
+ Unused << mFmtParser.Parse(fmtReader);
+ 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;
+ }
+
+ 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();
+ mFmtParser.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 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();
+ }
+
+ uint64_t numSamples = aNumBytes * 8 / mChannels / mSampleFormat;
+
+ uint64_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;
+
+ 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, 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, 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;
+ }
+ uint64_t chunkIndex =
+ (aTime.ToSeconds() * mSamplesPerSecond / mSamplesPerChunk) - 1;
+ 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());
+}
+
+uint32_t WAVTrackDemuxer::Read(uint8_t* aBuffer, int64_t aOffset,
+ int32_t aSize) {
+ const int64_t streamLen = StreamLength();
+ if (mInfo && streamLen > 0) {
+ int64_t max = streamLen > aOffset ? streamLen - aOffset : 0;
+ aSize = std::min<int64_t>(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];
+ } 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
+
+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;
+ }
+}
+
+// FormatParser
+
+Result<uint32_t, nsresult> FormatParser::Parse(BufferReader& aReader) {
+ for (auto res = aReader.ReadU8();
+ res.isOk() && !mFmtChunk.ParseNext(res.unwrap());
+ res = 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 static_cast<uint32_t>((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() = default;
+
+void DataParser::Reset() { mChunk.Reset(); }
+
+const DataParser::DataChunk& DataParser::CurrentChunk() const { return mChunk; }
+
+// DataParser::DataChunk
+
+void DataParser::DataChunk::Reset() { mPos = 0; }
+
+} // namespace mozilla