1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "WaveDemuxer.h"
12 #include "mozilla/Assertions.h"
13 #include "mozilla/EndianUtils.h"
14 #include "mozilla/Utf8.h"
15 #include "BufferReader.h"
16 #include "VideoUtils.h"
17 #include "TimeUnits.h"
19 using mozilla::media::TimeIntervals
;
20 using mozilla::media::TimeUnit
;
26 WAVDemuxer::WAVDemuxer(MediaResource
* aSource
) : mSource(aSource
) {
27 DDLINKCHILD("source", aSource
);
30 bool WAVDemuxer::InitInternal() {
32 mTrackDemuxer
= new WAVTrackDemuxer(mSource
.GetResource());
33 DDLINKCHILD("track demuxer", mTrackDemuxer
.get());
35 return mTrackDemuxer
->Init();
38 RefPtr
<WAVDemuxer::InitPromise
> WAVDemuxer::Init() {
39 if (!InitInternal()) {
40 return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_METADATA_ERR
,
43 return InitPromise::CreateAndResolve(NS_OK
, __func__
);
46 uint32_t WAVDemuxer::GetNumberTracks(TrackInfo::TrackType aType
) const {
47 return aType
== TrackInfo::kAudioTrack
? 1u : 0u;
50 already_AddRefed
<MediaTrackDemuxer
> WAVDemuxer::GetTrackDemuxer(
51 TrackInfo::TrackType aType
, uint32_t aTrackNumber
) {
55 return RefPtr
<WAVTrackDemuxer
>(mTrackDemuxer
).forget();
58 bool WAVDemuxer::IsSeekable() const { return true; }
62 WAVTrackDemuxer::WAVTrackDemuxer(MediaResource
* aSource
)
74 DDLINKCHILD("source", aSource
);
78 bool WAVTrackDemuxer::Init() {
83 mInfo
= MakeUnique
<AudioInfo
>();
84 mInfo
->mCodecSpecificConfig
=
85 AudioCodecSpecificVariant
{WaveCodecSpecificData
{}};
88 if (!RIFFParserInit()) {
92 bool hasValidFmt
= false;
95 if (!HeaderParserInit()) {
99 uint32_t chunkName
= mHeaderParser
.GiveHeader().ChunkName();
100 uint32_t chunkSize
= mHeaderParser
.GiveHeader().ChunkSize();
102 if (chunkName
== FRMT_CODE
) {
103 hasValidFmt
= FmtChunkParserInit();
104 } else if (chunkName
== LIST_CODE
) {
105 mHeaderParser
.Reset();
106 uint64_t endOfListChunk
= static_cast<uint64_t>(mOffset
) + chunkSize
;
107 if (endOfListChunk
> UINT32_MAX
) {
110 if (!ListChunkParserInit(chunkSize
)) {
111 mOffset
= endOfListChunk
;
113 } else if (chunkName
== DATA_CODE
) {
114 mDataLength
= chunkSize
;
115 if (mFirstChunkOffset
!= mOffset
) {
116 mFirstChunkOffset
= mOffset
;
120 mOffset
+= chunkSize
; // Skip other irrelevant chunks.
123 // Wave files are 2-byte aligned so we need to round up
126 mHeaderParser
.Reset();
133 int64_t streamLength
= StreamLength();
134 // If the chunk length and the resource length are not equal, use the
135 // resource length as the "real" data chunk length, if it's longer than the
137 if (streamLength
!= -1) {
138 uint64_t streamLengthPositive
= static_cast<uint64_t>(streamLength
);
139 if (streamLengthPositive
> mFirstChunkOffset
&&
140 mDataLength
> streamLengthPositive
- mFirstChunkOffset
) {
141 mDataLength
= streamLengthPositive
- mFirstChunkOffset
;
145 mSamplesPerSecond
= mFmtChunk
.SampleRate();
146 mChannels
= mFmtChunk
.Channels();
147 if (!mSamplesPerSecond
|| !mChannels
|| !mFmtChunk
.ValidBitsPerSamples()) {
151 DATA_CHUNK_SIZE
* 8 / mChannels
/ mFmtChunk
.ValidBitsPerSamples();
152 mSampleFormat
= mFmtChunk
.ValidBitsPerSamples();
154 mInfo
->mRate
= mSamplesPerSecond
;
155 mInfo
->mChannels
= mChannels
;
156 mInfo
->mBitDepth
= mFmtChunk
.ValidBitsPerSamples();
157 mInfo
->mProfile
= AssertedCast
<uint8_t>(mFmtChunk
.WaveFormat() & 0x00FF);
158 mInfo
->mExtendedProfile
=
159 AssertedCast
<uint8_t>(mFmtChunk
.WaveFormat() & 0xFF00 >> 8);
160 mInfo
->mMimeType
= "audio/wave; codecs=";
161 mInfo
->mMimeType
.AppendInt(mFmtChunk
.WaveFormat());
162 mInfo
->mDuration
= Duration();
163 mInfo
->mChannelMap
= mFmtChunk
.ChannelMap();
165 return mInfo
->mDuration
.IsPositive();
168 bool WAVTrackDemuxer::RIFFParserInit() {
169 RefPtr
<MediaRawData
> riffHeader
= GetFileHeader(FindRIFFHeader());
173 BufferReader
RIFFReader(riffHeader
->Data(), 12);
174 Unused
<< mRIFFParser
.Parse(RIFFReader
);
175 return mRIFFParser
.RiffHeader().IsValid(11);
178 bool WAVTrackDemuxer::HeaderParserInit() {
179 RefPtr
<MediaRawData
> header
= GetFileHeader(FindChunkHeader());
183 BufferReader
headerReader(header
->Data(), 8);
184 Unused
<< mHeaderParser
.Parse(headerReader
);
188 bool WAVTrackDemuxer::FmtChunkParserInit() {
189 RefPtr
<MediaRawData
> fmtChunk
= GetFileHeader(FindFmtChunk());
190 if (!fmtChunk
|| fmtChunk
->Size() < 16) {
193 nsTArray
<uint8_t> fmtChunkData(fmtChunk
->Data(), fmtChunk
->Size());
194 mFmtChunk
.Init(std::move(fmtChunkData
));
198 bool WAVTrackDemuxer::ListChunkParserInit(uint32_t aChunkSize
) {
199 uint32_t bytesRead
= 0;
201 RefPtr
<MediaRawData
> infoTag
= GetFileHeader(FindInfoTag());
206 BufferReader
infoTagReader(infoTag
->Data(), 4);
207 auto res
= infoTagReader
.ReadU32();
208 if (res
.isErr() || (res
.isOk() && res
.unwrap() != INFO_CODE
)) {
214 while (bytesRead
< aChunkSize
) {
215 if (!HeaderParserInit()) {
221 uint32_t id
= mHeaderParser
.GiveHeader().ChunkName();
222 uint32_t length
= mHeaderParser
.GiveHeader().ChunkSize();
224 // SubChunk Length Cannot Exceed List Chunk length.
225 if (length
> aChunkSize
- bytesRead
) {
226 length
= aChunkSize
- bytesRead
;
229 MediaByteRange mRange
= {mOffset
, mOffset
+ length
};
230 RefPtr
<MediaRawData
> mChunkData
= GetFileHeader(mRange
);
235 const char* rawData
= reinterpret_cast<const char*>(mChunkData
->Data());
237 nsCString
val(rawData
, length
);
238 if (length
> 0 && val
[length
- 1] == '\0') {
239 val
.SetLength(length
- 1);
244 length
+= length
% 2;
250 mHeaderParser
.Reset();
255 case 0x49415254: // IART
256 mInfo
->mTags
.AppendElement(MetadataTag("artist"_ns
, val
));
258 case 0x49434d54: // ICMT
259 mInfo
->mTags
.AppendElement(MetadataTag("comments"_ns
, val
));
261 case 0x49474e52: // IGNR
262 mInfo
->mTags
.AppendElement(MetadataTag("genre"_ns
, val
));
264 case 0x494e414d: // INAM
265 mInfo
->mTags
.AppendElement(MetadataTag("name"_ns
, val
));
269 mHeaderParser
.Reset();
274 media::TimeUnit
WAVTrackDemuxer::SeekPosition() const {
275 TimeUnit pos
= Duration(mChunkIndex
);
276 if (Duration() > TimeUnit()) {
277 pos
= std::min(Duration(), pos
);
282 RefPtr
<MediaRawData
> WAVTrackDemuxer::DemuxSample() {
283 return GetNextChunk(FindNextChunk());
286 UniquePtr
<TrackInfo
> WAVTrackDemuxer::GetInfo() const { return mInfo
->Clone(); }
288 RefPtr
<WAVTrackDemuxer::SeekPromise
> WAVTrackDemuxer::Seek(
289 const TimeUnit
& aTime
) {
291 const TimeUnit seekTime
= ScanUntil(aTime
);
292 return SeekPromise::CreateAndResolve(seekTime
, __func__
);
295 TimeUnit
WAVTrackDemuxer::FastSeek(const TimeUnit
& aTime
) {
296 if (aTime
.ToMicroseconds()) {
297 mChunkIndex
= ChunkIndexFromTime(aTime
);
302 mOffset
= OffsetFromChunkIndex(mChunkIndex
);
304 if (mOffset
> mFirstChunkOffset
&& StreamLength() > 0) {
305 mOffset
= std::min(static_cast<uint64_t>(StreamLength() - 1), mOffset
);
308 return Duration(mChunkIndex
);
311 TimeUnit
WAVTrackDemuxer::ScanUntil(const TimeUnit
& aTime
) {
312 if (!aTime
.ToMicroseconds()) {
313 return FastSeek(aTime
);
316 if (Duration(mChunkIndex
) > aTime
) {
320 return SeekPosition();
323 RefPtr
<WAVTrackDemuxer::SamplesPromise
> WAVTrackDemuxer::GetSamples(
324 int32_t aNumSamples
) {
325 MOZ_ASSERT(aNumSamples
);
327 RefPtr
<SamplesHolder
> datachunks
= new SamplesHolder();
329 while (aNumSamples
--) {
330 RefPtr
<MediaRawData
> datachunk
= GetNextChunk(FindNextChunk());
334 if (!datachunk
->HasValidTime()) {
335 return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR
,
338 datachunks
->AppendSample(datachunk
);
341 if (datachunks
->GetSamples().IsEmpty()) {
342 return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_END_OF_STREAM
,
346 return SamplesPromise::CreateAndResolve(datachunks
, __func__
);
349 void WAVTrackDemuxer::Reset() {
350 FastSeek(TimeUnit());
352 mHeaderParser
.Reset();
356 RefPtr
<WAVTrackDemuxer::SkipAccessPointPromise
>
357 WAVTrackDemuxer::SkipToNextRandomAccessPoint(const TimeUnit
& aTimeThreshold
) {
358 return SkipAccessPointPromise::CreateAndReject(
359 SkipFailureHolder(NS_ERROR_DOM_MEDIA_DEMUXER_ERR
, 0), __func__
);
362 int64_t WAVTrackDemuxer::GetResourceOffset() const {
363 return AssertedCast
<int64_t>(mOffset
);
366 TimeIntervals
WAVTrackDemuxer::GetBuffered() {
367 TimeUnit duration
= Duration();
369 if (duration
<= TimeUnit()) {
370 return TimeIntervals();
373 AutoPinned
<MediaResource
> stream(mSource
.GetResource());
374 return GetEstimatedBufferedTimeRanges(stream
, duration
.ToMicroseconds());
377 int64_t WAVTrackDemuxer::StreamLength() const { return mSource
.GetLength(); }
379 TimeUnit
WAVTrackDemuxer::Duration() const {
380 if (!mDataLength
|| !mChannels
|| !mSampleFormat
) {
385 static_cast<int64_t>(mDataLength
) * 8 / mChannels
/ mSampleFormat
;
387 int64_t numUSeconds
= USECS_PER_S
* numSamples
/ mSamplesPerSecond
;
389 if (USECS_PER_S
* numSamples
% mSamplesPerSecond
> mSamplesPerSecond
/ 2) {
393 return TimeUnit::FromMicroseconds(numUSeconds
);
396 TimeUnit
WAVTrackDemuxer::Duration(int64_t aNumDataChunks
) const {
397 if (!mSamplesPerSecond
|| !mSamplesPerChunk
) {
400 const int64_t frames
= mSamplesPerChunk
* aNumDataChunks
;
401 return TimeUnit(frames
, mSamplesPerSecond
);
404 TimeUnit
WAVTrackDemuxer::DurationFromBytes(uint32_t aNumBytes
) const {
405 if (!mSamplesPerSecond
|| !mChannels
|| !mSampleFormat
) {
409 uint64_t numSamples
= aNumBytes
* 8 / mChannels
/ mSampleFormat
;
411 return TimeUnit(numSamples
, mSamplesPerSecond
);
414 MediaByteRange
WAVTrackDemuxer::FindNextChunk() {
415 if (mOffset
+ DATA_CHUNK_SIZE
< mFirstChunkOffset
+ mDataLength
) {
416 return {mOffset
, mOffset
+ DATA_CHUNK_SIZE
};
418 return {mOffset
, mFirstChunkOffset
+ mDataLength
};
421 MediaByteRange
WAVTrackDemuxer::FindChunkHeader() {
422 return {mOffset
, mOffset
+ CHUNK_HEAD_SIZE
};
425 MediaByteRange
WAVTrackDemuxer::FindRIFFHeader() {
426 return {mOffset
, mOffset
+ RIFF_CHUNK_SIZE
};
429 MediaByteRange
WAVTrackDemuxer::FindFmtChunk() {
430 return {mOffset
, mOffset
+ mHeaderParser
.GiveHeader().ChunkSize()};
433 MediaByteRange
WAVTrackDemuxer::FindListChunk() {
434 return {mOffset
, mOffset
+ mHeaderParser
.GiveHeader().ChunkSize()};
437 MediaByteRange
WAVTrackDemuxer::FindInfoTag() { return {mOffset
, mOffset
+ 4}; }
439 bool WAVTrackDemuxer::SkipNextChunk(const MediaByteRange
& aRange
) {
444 already_AddRefed
<MediaRawData
> WAVTrackDemuxer::GetNextChunk(
445 const MediaByteRange
& aRange
) {
446 if (!aRange
.Length()) {
450 RefPtr
<MediaRawData
> datachunk
= new MediaRawData();
451 datachunk
->mOffset
= aRange
.mStart
;
453 UniquePtr
<MediaRawDataWriter
> chunkWriter(datachunk
->CreateWriter());
454 if (!chunkWriter
->SetSize(static_cast<uint32_t>(aRange
.Length()))) {
458 const uint32_t read
= Read(chunkWriter
->Data(), datachunk
->mOffset
,
459 AssertedCast
<int64_t>(datachunk
->Size()));
461 if (read
!= aRange
.Length()) {
469 datachunk
->mTime
= Duration(mChunkIndex
- 1);
471 if (static_cast<uint32_t>(mChunkIndex
) * DATA_CHUNK_SIZE
< mDataLength
) {
472 datachunk
->mDuration
= Duration(1);
474 uint32_t mBytesRemaining
= mDataLength
- mChunkIndex
* DATA_CHUNK_SIZE
;
475 datachunk
->mDuration
= DurationFromBytes(mBytesRemaining
);
477 datachunk
->mTimecode
= datachunk
->mTime
;
478 datachunk
->mKeyframe
= true;
480 MOZ_ASSERT(!datachunk
->mTime
.IsNegative());
481 MOZ_ASSERT(!datachunk
->mDuration
.IsNegative());
483 return datachunk
.forget();
486 already_AddRefed
<MediaRawData
> WAVTrackDemuxer::GetFileHeader(
487 const MediaByteRange
& aRange
) {
488 if (!aRange
.Length()) {
492 RefPtr
<MediaRawData
> fileHeader
= new MediaRawData();
493 fileHeader
->mOffset
= aRange
.mStart
;
495 UniquePtr
<MediaRawDataWriter
> headerWriter(fileHeader
->CreateWriter());
496 if (!headerWriter
->SetSize(static_cast<uint32_t>(aRange
.Length()))) {
500 const uint32_t read
= Read(headerWriter
->Data(), fileHeader
->mOffset
,
501 AssertedCast
<int64_t>(fileHeader
->Size()));
503 if (read
!= aRange
.Length()) {
509 return fileHeader
.forget();
512 uint64_t WAVTrackDemuxer::OffsetFromChunkIndex(uint32_t aChunkIndex
) const {
513 return mFirstChunkOffset
+ aChunkIndex
* DATA_CHUNK_SIZE
;
516 uint64_t WAVTrackDemuxer::ChunkIndexFromTime(
517 const media::TimeUnit
& aTime
) const {
518 if (!mSamplesPerChunk
|| !mSamplesPerSecond
) {
521 double chunkDurationS
=
522 mSamplesPerChunk
/ static_cast<double>(mSamplesPerSecond
);
523 int64_t chunkIndex
= std::floor(aTime
.ToSeconds() / chunkDurationS
);
527 void WAVTrackDemuxer::UpdateState(const MediaByteRange
& aRange
) {
528 // Full chunk parsed, move offset to its end.
529 mOffset
= static_cast<uint32_t>(aRange
.mEnd
);
530 mTotalChunkLen
+= static_cast<uint64_t>(aRange
.Length());
533 int64_t WAVTrackDemuxer::Read(uint8_t* aBuffer
, int64_t aOffset
,
535 const int64_t streamLen
= StreamLength();
536 if (mInfo
&& streamLen
> 0) {
537 int64_t max
= streamLen
> aOffset
? streamLen
- aOffset
: 0;
538 aSize
= std::min(aSize
, max
);
541 const nsresult rv
= mSource
.ReadAt(aOffset
, reinterpret_cast<char*>(aBuffer
),
542 static_cast<uint32_t>(aSize
), &read
);
543 NS_ENSURE_SUCCESS(rv
, 0);
549 Result
<uint32_t, nsresult
> RIFFParser::Parse(BufferReader
& aReader
) {
550 for (auto res
= aReader
.ReadU8();
551 res
.isOk() && !mRiffHeader
.ParseNext(res
.unwrap());
552 res
= aReader
.ReadU8()) {
555 if (mRiffHeader
.IsValid()) {
556 return RIFF_CHUNK_SIZE
;
562 void RIFFParser::Reset() { mRiffHeader
.Reset(); }
564 const RIFFParser::RIFFHeader
& RIFFParser::RiffHeader() const {
568 // RIFFParser::RIFFHeader
570 RIFFParser::RIFFHeader::RIFFHeader() { Reset(); }
572 void RIFFParser::RIFFHeader::Reset() {
573 memset(mRaw
, 0, sizeof(mRaw
));
577 bool RIFFParser::RIFFHeader::ParseNext(uint8_t c
) {
587 bool RIFFParser::RIFFHeader::IsValid(int aPos
) const {
588 if (aPos
> -1 && aPos
< 4) {
589 return RIFF
[aPos
] == mRaw
[aPos
];
591 if (aPos
> 7 && aPos
< 12) {
592 return WAVE
[aPos
- 8] == mRaw
[aPos
];
597 bool RIFFParser::RIFFHeader::IsValid() const { return mPos
>= RIFF_CHUNK_SIZE
; }
599 bool RIFFParser::RIFFHeader::Update(uint8_t c
) {
600 if (mPos
< RIFF_CHUNK_SIZE
) {
603 return IsValid(mPos
++);
608 Result
<uint32_t, nsresult
> HeaderParser::Parse(BufferReader
& aReader
) {
609 for (auto res
= aReader
.ReadU8();
610 res
.isOk() && !mHeader
.ParseNext(res
.unwrap()); res
= aReader
.ReadU8()) {
613 if (mHeader
.IsValid()) {
614 return CHUNK_HEAD_SIZE
;
620 void HeaderParser::Reset() { mHeader
.Reset(); }
622 const HeaderParser::ChunkHeader
& HeaderParser::GiveHeader() const {
626 // HeaderParser::ChunkHeader
628 HeaderParser::ChunkHeader::ChunkHeader() { Reset(); }
630 void HeaderParser::ChunkHeader::Reset() {
631 memset(mRaw
, 0, sizeof(mRaw
));
635 bool HeaderParser::ChunkHeader::ParseNext(uint8_t c
) {
640 bool HeaderParser::ChunkHeader::IsValid() const {
641 return mPos
>= CHUNK_HEAD_SIZE
;
644 uint32_t HeaderParser::ChunkHeader::ChunkName() const {
645 return static_cast<uint32_t>(
646 ((mRaw
[0] << 24) | (mRaw
[1] << 16) | (mRaw
[2] << 8) | (mRaw
[3])));
649 uint32_t HeaderParser::ChunkHeader::ChunkSize() const {
650 return static_cast<uint32_t>(
651 ((mRaw
[7] << 24) | (mRaw
[6] << 16) | (mRaw
[5] << 8) | (mRaw
[4])));
654 void HeaderParser::ChunkHeader::Update(uint8_t c
) {
655 if (mPos
< CHUNK_HEAD_SIZE
) {
662 void FormatChunk::Init(nsTArray
<uint8_t>&& aData
) { mRaw
= std::move(aData
); }
664 uint16_t FormatChunk::WaveFormat() const { return (mRaw
[1] << 8) | (mRaw
[0]); }
666 uint16_t FormatChunk::Channels() const { return (mRaw
[3] << 8) | (mRaw
[2]); }
668 uint32_t FormatChunk::SampleRate() const {
669 return static_cast<uint32_t>((mRaw
[7] << 24) | (mRaw
[6] << 16) |
670 (mRaw
[5] << 8) | (mRaw
[4]));
673 uint16_t FormatChunk::AverageBytesPerSec() const {
674 return static_cast<uint16_t>((mRaw
[11] << 24) | (mRaw
[10] << 16) |
675 (mRaw
[9] << 8) | (mRaw
[8]));
678 uint16_t FormatChunk::BlockAlign() const {
679 return static_cast<uint16_t>(mRaw
[13] << 8) | (mRaw
[12]);
682 uint16_t FormatChunk::ValidBitsPerSamples() const {
683 return (mRaw
[15] << 8) | (mRaw
[14]);
686 uint16_t FormatChunk::ExtraFormatInfoSize() const {
687 uint16_t value
= static_cast<uint16_t>(mRaw
[17] << 8) | (mRaw
[16]);
688 if (WaveFormat() != 0xFFFE && value
!= 0) {
690 "Found non-zero extra format info length and the wave format"
691 " isn't WAVEFORMATEXTENSIBLE.");
694 if (WaveFormat() == 0xFFFE && value
< 22) {
696 "Wave format is WAVEFORMATEXTENSIBLE and extra data size isn't at"
703 AudioConfig::ChannelLayout::ChannelMap
FormatChunk::ChannelMap() const {
704 // Regular mapping if file doesn't have channel mapping info. Alternatively,
705 // if the chunk size doesn't have the field for the size of the extension
706 // data, return a regular mapping.
707 if (WaveFormat() != 0xFFFE || mRaw
.Length() < 18) {
708 return AudioConfig::ChannelLayout(Channels()).Map();
710 // The length of this chunk is at least 18, check if it's long enough to
711 // hold the WAVE_FORMAT_EXTENSIBLE struct, that is 22 bytes. If not, fall
712 // back to a common mapping. The channel mapping is four bytes, starting at
714 if (ExtraFormatInfoSize() < 22 || mRaw
.Length() < 22) {
715 return AudioConfig::ChannelLayout(Channels()).Map();
717 // ChannelLayout::ChannelMap is by design bit-per-bit compatible with
718 // WAVEFORMATEXTENSIBLE's dwChannelMask attribute, we can just cast here.
719 auto channelMap
= static_cast<AudioConfig::ChannelLayout::ChannelMap
>(
720 mRaw
[21] | mRaw
[20] | mRaw
[19] | mRaw
[18]);
726 DataParser::DataParser() = default;
728 void DataParser::Reset() { mChunk
.Reset(); }
730 const DataParser::DataChunk
& DataParser::CurrentChunk() const { return mChunk
; }
732 // DataParser::DataChunk
734 void DataParser::DataChunk::Reset() { mPos
= 0; }
736 } // namespace mozilla