/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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 "ADTSDemuxer.h" #include #include "VideoUtils.h" #include "TimeUnits.h" #include "prenv.h" #ifdef PR_LOGGING mozilla::LazyLogModule gADTSDemuxerLog("ADTSDemuxer"); #define ADTSLOG(msg, ...) \ MOZ_LOG(gADTSDemuxerLog, LogLevel::Debug, ("ADTSDemuxer " msg, ##__VA_ARGS__)) #define ADTSLOGV(msg, ...) \ MOZ_LOG(gADTSDemuxerLog, LogLevel::Verbose, ("ADTSDemuxer " msg, ##__VA_ARGS__)) #else #define ADTSLOG(msg, ...) do {} while (false) #define ADTSLOG(msg, ...) do {} while (false) #endif namespace mozilla { namespace adts { // adts::FrameHeader - Holds the ADTS frame header and its parsing // state. // // ADTS Frame Structure // // 11111111 1111BCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP(QQQQQQQQ QQQQQQQQ) // // Header consists of 7 or 9 bytes(without or with CRC). // Letter Length(bits) Description // { sync } 12 syncword 0xFFF, all bits must be 1 // B 1 MPEG Version: 0 for MPEG-4, 1 for MPEG-2 // C 2 Layer: always 0 // D 1 protection absent, Warning, set to 1 if there is no // CRC and 0 if there is CRC // E 2 profile, the MPEG-4 Audio Object Type minus 1 // F 4 MPEG-4 Sampling Frequency Index (15 is forbidden) // H 3 MPEG-4 Channel Configuration (in the case of 0, the // channel configuration is sent via an in-band PCE) // M 13 frame length, this value must include 7 or 9 bytes of // header length: FrameLength = // (ProtectionAbsent == 1 ? 7 : 9) + size(AACFrame) // O 11 Buffer fullness // P 2 Number of AAC frames(RDBs) in ADTS frame minus 1, for // maximum compatibility always use 1 AAC frame per ADTS // frame // Q 16 CRC if protection absent is 0 class FrameHeader { public: uint32_t mFrameLength; uint32_t mSampleRate; uint32_t mSamples; uint32_t mChannels; uint8_t mObjectType; uint8_t mSamplingIndex; uint8_t mChannelConfig; uint8_t mNumAACFrames; bool mHaveCrc; // Returns whether aPtr matches a valid ADTS header sync marker static bool MatchesSync(const uint8_t* aPtr) { return aPtr[0] == 0xFF && (aPtr[1] & 0xF6) == 0xF0; } FrameHeader() { Reset(); } // Header size size_t HeaderSize() const { return (mHaveCrc) ? 9 : 7; } bool IsValid() const { return mFrameLength > 0; } // Resets the state to allow for a new parsing session. void Reset() { PodZero(this); } // Returns whether the byte creates a valid sequence up to this point. bool Parse(const uint8_t* aPtr) { const uint8_t* p = aPtr; if (!MatchesSync(p)) { return false; } // AAC has 1024 samples per frame per channel. mSamples = 1024; mHaveCrc = !(p[1] & 0x01); mObjectType = ((p[2] & 0xC0) >> 6) + 1; mSamplingIndex = (p[2] & 0x3C) >> 2; mChannelConfig = (p[2] & 0x01) << 2 | (p[3] & 0xC0) >> 6; mFrameLength = (p[3] & 0x03) << 11 | (p[4] & 0xFF) << 3 | (p[5] & 0xE0) >> 5; mNumAACFrames = (p[6] & 0x03) + 1; static const int32_t SAMPLE_RATES[16] = { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350 }; mSampleRate = SAMPLE_RATES[mSamplingIndex]; MOZ_ASSERT(mChannelConfig < 8); mChannels = (mChannelConfig == 7) ? 8 : mChannelConfig; return true; } }; // adts::Frame - Frame meta container used to parse and hold a frame // header and side info. class Frame { public: Frame() : mOffset(0), mHeader() {} int64_t Offset() const { return mOffset; } size_t Length() const { // TODO: If fields are zero'd when invalid, this check wouldn't be necessary. if (!mHeader.IsValid()) { return 0; } return mHeader.mFrameLength; } // Returns the offset to the start of frame's raw data. int64_t PayloadOffset() const { return mOffset + mHeader.HeaderSize(); } // Returns the length of the frame's raw data (excluding the header) in bytes. size_t PayloadLength() const { // TODO: If fields are zero'd when invalid, this check wouldn't be necessary. if (!mHeader.IsValid()) { return 0; } return mHeader.mFrameLength - mHeader.HeaderSize(); } // Returns the parsed frame header. const FrameHeader& Header() const { return mHeader; } bool IsValid() const { return mHeader.IsValid(); } // Resets the frame header and data. void Reset() { mHeader.Reset(); mOffset = 0; } // Returns whether the valid bool Parse(int64_t aOffset, uint8_t* aStart, uint8_t* aEnd) { MOZ_ASSERT(aStart && aEnd); bool found = false; uint8_t* ptr = aStart; // Require at least 7 bytes of data at the end of the buffer for the minimum // ADTS frame header. while (ptr < aEnd - 7 && !found) { found = mHeader.Parse(ptr); ptr++; } mOffset = aOffset + (ptr - aStart) - 1; return found; } private: // The offset to the start of the header. int64_t mOffset; // The currently parsed frame header. FrameHeader mHeader; }; class FrameParser { public: // Returns the currently parsed frame. Reset via Reset or EndFrameSession. const Frame& CurrentFrame() const { return mFrame; } #ifdef ENABLE_TESTS // Returns the previously parsed frame. Reset via Reset. const Frame& PrevFrame() const { return mPrevFrame; } #endif // Returns the first parsed frame. Reset via Reset. const Frame& FirstFrame() const { return mFirstFrame; } // Resets the parser. Don't use between frames as first frame data is reset. void Reset() { EndFrameSession(); mFirstFrame.Reset(); } // Clear the last parsed frame to allow for next frame parsing, i.e.: // - sets PrevFrame to CurrentFrame // - resets the CurrentFrame // - resets ID3Header if no valid header was parsed yet void EndFrameSession() { #ifdef ENABLE_TESTS mPrevFrame = mFrame; #endif mFrame.Reset(); } // Parses contents of given ByteReader for a valid frame header and returns true // if one was found. After returning, the variable passed to 'aBytesToSkip' holds // the amount of bytes to be skipped (if any) in order to jump across a large // ID3v2 tag spanning multiple buffers. bool Parse(int64_t aOffset, uint8_t* aStart, uint8_t* aEnd) { const bool found = mFrame.Parse(aOffset, aStart, aEnd); if (mFrame.Length() && !mFirstFrame.Length()) { mFirstFrame = mFrame; } return found; } private: // We keep the first parsed frame around for static info access, the // previously parsed frame for debugging and the currently parsed frame. Frame mFirstFrame; Frame mFrame; #ifdef ENABLE_TESTS Frame mPrevFrame; #endif }; // Return the AAC Profile Level Indication based upon sample rate and channels // Information based upon table 1.10 from ISO/IEC 14496-3:2005(E) static int8_t ProfileLevelIndication(const Frame& frame) { const FrameHeader& header = frame.Header(); MOZ_ASSERT(header.IsValid()); if (!header.IsValid()) { return 0; } const int channels = header.mChannels; const int sampleRate = header.mSampleRate; if (channels <= 2) { if (sampleRate <= 24000) { // AAC Profile L1 return 0x28; } else if (sampleRate <= 48000) { // AAC Profile L2 return 0x29; } } else if (channels <= 5) { if (sampleRate <= 48000) { // AAC Profile L4 return 0x2A; } else if (sampleRate <= 96000) { // AAC Profile L5 return 0x2B; } } // TODO: Should this be 0xFE for 'no audio profile specified'? return 0; } // Initialize the AAC AudioSpecificConfig. // Only handles two-byte version for AAC-LC. static void InitAudioSpecificConfig(const Frame& frame, MediaByteBuffer* aBuffer) { const FrameHeader& header = frame.Header(); MOZ_ASSERT(header.IsValid()); int audioObjectType = header.mObjectType; int samplingFrequencyIndex = header.mSamplingIndex; int channelConfig = header.mChannelConfig; uint8_t asc[2]; asc[0] = (audioObjectType & 0x1F) << 3 | (samplingFrequencyIndex & 0x0E) >> 1; asc[1] = (samplingFrequencyIndex & 0x01) << 7 | (channelConfig & 0x0F) << 3; aBuffer->AppendElements(asc, 2); } } // namespace adts // ADTSDemuxer ADTSDemuxer::ADTSDemuxer(MediaResource* aSource) : mSource(aSource) {} bool ADTSDemuxer::InitInternal() { if (!mTrackDemuxer) { mTrackDemuxer = new ADTSTrackDemuxer(mSource); } return mTrackDemuxer->Init(); } RefPtr ADTSDemuxer::Init() { if (!InitInternal()) { ADTSLOG("Init() failure: waiting for data"); return InitPromise::CreateAndReject( DemuxerFailureReason::DEMUXER_ERROR, __func__); } ADTSLOG("Init() successful"); return InitPromise::CreateAndResolve(NS_OK, __func__); } bool ADTSDemuxer::HasTrackType(TrackInfo::TrackType aType) const { return aType == TrackInfo::kAudioTrack; } uint32_t ADTSDemuxer::GetNumberTracks(TrackInfo::TrackType aType) const { return (aType == TrackInfo::kAudioTrack) ? 1 : 0; } already_AddRefed ADTSDemuxer::GetTrackDemuxer(TrackInfo::TrackType aType, uint32_t aTrackNumber) { if (!mTrackDemuxer) { return nullptr; } return RefPtr(mTrackDemuxer).forget(); } bool ADTSDemuxer::IsSeekable() const { int64_t length = mSource->GetLength(); if (length > -1) return true; return false; } // ADTSTrackDemuxer ADTSTrackDemuxer::ADTSTrackDemuxer(MediaResource* aSource) : mSource(aSource) , mParser(new adts::FrameParser()) , mOffset(0) , mNumParsedFrames(0) , mFrameIndex(0) , mTotalFrameLen(0) , mSamplesPerFrame(0) , mSamplesPerSecond(0) , mChannels(0) { Reset(); } ADTSTrackDemuxer::~ADTSTrackDemuxer() { delete mParser; mParser = nullptr; } bool ADTSTrackDemuxer::Init() { FastSeek(media::TimeUnit()); // Read the first frame to fetch sample rate and other meta data. RefPtr frame(GetNextFrame(FindNextFrame(true))); ADTSLOG("Init StreamLength()=%" PRId64 " first-frame-found=%d", StreamLength(), !!frame); if (!frame) { return false; } // Rewind back to the stream begin to avoid dropping the first frame. FastSeek(media::TimeUnit()); if (!mInfo) { mInfo = MakeUnique(); } mInfo->mRate = mSamplesPerSecond; mInfo->mChannels = mChannels; mInfo->mBitDepth = 16; mInfo->mDuration = Duration().ToMicroseconds(); // AAC Specific information mInfo->mMimeType = "audio/mp4a-latm"; // Configure AAC codec-specific values. // According to // https://msdn.microsoft.com/en-us/library/windows/desktop/dd742784%28v=vs.85%29.aspx, // wAudioProfileLevelIndication, which is passed mInfo->mProfile, is // a value from Table 1.12 -- audioProfileLevelIndication values, ISO/IEC 14496-3. mInfo->mProfile = ProfileLevelIndication(mParser->FirstFrame()); // For AAC, mExtendedProfile contains the audioObjectType from Table // 1.3 -- Audio Profile definition, ISO/IEC 14496-3. Eg. 2 == AAC LC mInfo->mExtendedProfile = mParser->FirstFrame().Header().mObjectType; InitAudioSpecificConfig(mParser->FirstFrame(), mInfo->mCodecSpecificConfig); ADTSLOG("Init mInfo={mRate=%u mChannels=%u mBitDepth=%u mDuration=%" PRId64 "}", mInfo->mRate, mInfo->mChannels, mInfo->mBitDepth, mInfo->mDuration); return mSamplesPerSecond && mChannels; } #ifdef ENABLE_TESTS const adts::Frame& ADTSTrackDemuxer::LastFrame() const { return mParser->PrevFrame(); } RefPtr ADTSTrackDemuxer::DemuxSample() { return GetNextFrame(FindNextFrame()); } media::TimeUnit ADTSTrackDemuxer::SeekPosition() const { return Duration(mFrameIndex); } #endif UniquePtr ADTSTrackDemuxer::GetInfo() const { return mInfo->Clone(); } RefPtr ADTSTrackDemuxer::Seek(media::TimeUnit aTime) { // Efficiently seek to the position. FastSeek(aTime); // Correct seek position by scanning the next frames. const media::TimeUnit seekTime = ScanUntil(aTime); return SeekPromise::CreateAndResolve(seekTime, __func__); } media::TimeUnit ADTSTrackDemuxer::FastSeek(const media::TimeUnit& aTime) { ADTSLOG("FastSeek(%" PRId64 ") avgFrameLen=%f mNumParsedFrames=%" PRIu64 " mFrameIndex=%" PRId64 " mOffset=%" PRIu64, aTime, AverageFrameLength(), mNumParsedFrames, mFrameIndex, mOffset); const int64_t firstFrameOffset = mParser->FirstFrame().Offset(); if (!aTime.ToMicroseconds()) { // Quick seek to the beginning of the stream. mOffset = firstFrameOffset; } else if (AverageFrameLength() > 0) { mOffset = firstFrameOffset + FrameIndexFromTime(aTime) * AverageFrameLength(); } if (mOffset > firstFrameOffset && StreamLength() > 0) { mOffset = std::min(StreamLength() - 1, mOffset); } mFrameIndex = FrameIndexFromOffset(mOffset); mParser->EndFrameSession(); ADTSLOG("FastSeek End avgFrameLen=%f mNumParsedFrames=%" PRIu64 " mFrameIndex=%" PRId64 " mFirstFrameOffset=%llu mOffset=%" PRIu64 " SL=%llu", AverageFrameLength(), mNumParsedFrames, mFrameIndex, firstFrameOffset, mOffset, StreamLength()); return Duration(mFrameIndex); } media::TimeUnit ADTSTrackDemuxer::ScanUntil(const media::TimeUnit& aTime) { ADTSLOG("ScanUntil(%" PRId64 ") avgFrameLen=%f mNumParsedFrames=%" PRIu64 " mFrameIndex=%" PRId64 " mOffset=%" PRIu64, aTime, AverageFrameLength(), mNumParsedFrames, mFrameIndex, mOffset); if (!aTime.ToMicroseconds()) { return FastSeek(aTime); } if (Duration(mFrameIndex) > aTime) { FastSeek(aTime); } while (SkipNextFrame(FindNextFrame()) && Duration(mFrameIndex + 1) < aTime) { ADTSLOGV("ScanUntil* avgFrameLen=%f mNumParsedFrames=%" PRIu64 " mFrameIndex=%" PRId64 " mOffset=%" PRIu64 " Duration=%" PRId64, aTime, AverageFrameLength(), mNumParsedFrames, mFrameIndex, mOffset, Duration(mFrameIndex + 1)); } ADTSLOG("ScanUntil End avgFrameLen=%f mNumParsedFrames=%" PRIu64 " mFrameIndex=%" PRId64 " mOffset=%" PRIu64, aTime, AverageFrameLength(), mNumParsedFrames, mFrameIndex, mOffset); return Duration(mFrameIndex); } RefPtr ADTSTrackDemuxer::GetSamples(int32_t aNumSamples) { ADTSLOGV("GetSamples(%d) Begin mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64 " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d " "mSamplesPerSecond=%d mChannels=%d", aNumSamples, mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen, mSamplesPerFrame, mSamplesPerSecond, mChannels); if (!aNumSamples) { return SamplesPromise::CreateAndReject( DemuxerFailureReason::DEMUXER_ERROR, __func__); } RefPtr frames = new SamplesHolder(); while (aNumSamples--) { RefPtr frame(GetNextFrame(FindNextFrame())); if (!frame) break; frames->mSamples.AppendElement(frame); } ADTSLOGV("GetSamples() End mSamples.Size()=%d aNumSamples=%d mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64 " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d mSamplesPerSecond=%d " "mChannels=%d", frames->mSamples.Length(), aNumSamples, mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen, mSamplesPerFrame, mSamplesPerSecond, mChannels); if (frames->mSamples.IsEmpty()) { return SamplesPromise::CreateAndReject( DemuxerFailureReason::END_OF_STREAM, __func__); } return SamplesPromise::CreateAndResolve(frames, __func__); } void ADTSTrackDemuxer::Reset() { ADTSLOG("Reset()"); MOZ_ASSERT(mParser); if (mParser) { mParser->Reset(); } FastSeek(media::TimeUnit()); } RefPtr ADTSTrackDemuxer::SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold) { // Will not be called for audio-only resources. return SkipAccessPointPromise::CreateAndReject( SkipFailureHolder(DemuxerFailureReason::DEMUXER_ERROR, 0), __func__); } int64_t ADTSTrackDemuxer::GetResourceOffset() const { return mOffset; } media::TimeIntervals ADTSTrackDemuxer::GetBuffered() { media::TimeUnit duration = Duration(); if (duration <= media::TimeUnit()) { return media::TimeIntervals(); } AutoPinned stream(mSource.GetResource()); return GetEstimatedBufferedTimeRanges(stream, duration.ToMicroseconds()); } int64_t ADTSTrackDemuxer::StreamLength() const { return mSource.GetLength(); } media::TimeUnit ADTSTrackDemuxer::Duration() const { if (!mNumParsedFrames) { return media::TimeUnit::FromMicroseconds(-1); } const int64_t streamLen = StreamLength(); if (streamLen < 0) { // Unknown length, we can't estimate duration. return media::TimeUnit::FromMicroseconds(-1); } const int64_t firstFrameOffset = mParser->FirstFrame().Offset(); int64_t numFrames = (streamLen - firstFrameOffset) / AverageFrameLength(); return Duration(numFrames); } media::TimeUnit ADTSTrackDemuxer::Duration(int64_t aNumFrames) const { if (!mSamplesPerSecond) { return media::TimeUnit::FromMicroseconds(-1); } const double usPerFrame = USECS_PER_S * mSamplesPerFrame / mSamplesPerSecond; return media::TimeUnit::FromMicroseconds(aNumFrames * usPerFrame); } const adts::Frame& ADTSTrackDemuxer::FindNextFrame(bool findFirstFrame /*= false*/) { static const int BUFFER_SIZE = 4096; static const int MAX_SKIPPED_BYTES = 10 * BUFFER_SIZE; ADTSLOGV("FindNext() Begin mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64 " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d", mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen, mSamplesPerFrame, mSamplesPerSecond, mChannels); uint8_t buffer[BUFFER_SIZE]; int32_t read = 0; bool foundFrame = false; int64_t frameHeaderOffset = mOffset; // Prepare the parser for the next frame parsing session. mParser->EndFrameSession(); // Check whether we've found a valid ADTS frame. while (!foundFrame) { if ((read = Read(buffer, frameHeaderOffset, BUFFER_SIZE)) == 0) { ADTSLOG("FindNext() EOS without a frame"); break; } if (frameHeaderOffset - mOffset > MAX_SKIPPED_BYTES) { ADTSLOG("FindNext() exceeded MAX_SKIPPED_BYTES without a frame"); break; } const adts::Frame& currentFrame = mParser->CurrentFrame(); foundFrame = mParser->Parse(frameHeaderOffset, buffer, buffer + read); if (findFirstFrame && foundFrame) { // Check for sync marker after the found frame, since it's // possible to find sync marker in AAC data. If sync marker // exists after the current frame then we've found a frame // header. int64_t nextFrameHeaderOffset = currentFrame.Offset() + currentFrame.Length(); int32_t read = Read(buffer, nextFrameHeaderOffset, 2); if (read != 2 || !adts::FrameHeader::MatchesSync(buffer)) { frameHeaderOffset = currentFrame.Offset() + 1; mParser->Reset(); foundFrame = false; continue; } } if (foundFrame) { break; } // Minimum header size is 7 bytes. int64_t advance = read - 7; // Check for offset overflow. if (frameHeaderOffset + advance <= frameHeaderOffset) { break; } frameHeaderOffset += advance; } if (!foundFrame || !mParser->CurrentFrame().Length()) { ADTSLOG("FindNext() Exit foundFrame=%d mParser->CurrentFrame().Length()=%d ", foundFrame, mParser->CurrentFrame().Length()); mParser->Reset(); return mParser->CurrentFrame(); } ADTSLOGV("FindNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64 " mFrameIndex=%" PRId64 " frameHeaderOffset=%d" " mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d mSamplesPerSecond=%d" " mChannels=%d", mOffset, mNumParsedFrames, mFrameIndex, frameHeaderOffset, mTotalFrameLen, mSamplesPerFrame, mSamplesPerSecond, mChannels); return mParser->CurrentFrame(); } bool ADTSTrackDemuxer::SkipNextFrame(const adts::Frame& aFrame) { if (!mNumParsedFrames || !aFrame.Length()) { RefPtr frame(GetNextFrame(aFrame)); return frame; } UpdateState(aFrame); ADTSLOGV("SkipNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64 " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d", mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen, mSamplesPerFrame, mSamplesPerSecond, mChannels); return true; } already_AddRefed ADTSTrackDemuxer::GetNextFrame(const adts::Frame& aFrame) { ADTSLOG("GetNext() Begin({mOffset=%" PRId64 " HeaderSize()=%d Length()=%d})", aFrame.Offset(), aFrame.Header().HeaderSize(), aFrame.PayloadLength()); if (!aFrame.IsValid()) return nullptr; const int64_t offset = aFrame.PayloadOffset(); const uint32_t length = aFrame.PayloadLength(); RefPtr frame = new MediaRawData(); frame->mOffset = offset; nsAutoPtr frameWriter(frame->CreateWriter()); if (!frameWriter->SetSize(length)) { ADTSLOG("GetNext() Exit failed to allocated media buffer"); return nullptr; } const uint32_t read = Read(frameWriter->Data(), offset, length); if (read != length) { ADTSLOG("GetNext() Exit read=%u frame->Size()=%u", read, frame->Size()); return nullptr; } UpdateState(aFrame); frame->mTime = Duration(mFrameIndex - 1).ToMicroseconds(); frame->mDuration = Duration(1).ToMicroseconds(); frame->mTimecode = frame->mTime; frame->mKeyframe = true; MOZ_ASSERT(frame->mTime >= 0); MOZ_ASSERT(frame->mDuration > 0); ADTSLOGV("GetNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64 " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d", mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen, mSamplesPerFrame, mSamplesPerSecond, mChannels); return frame.forget(); } int64_t ADTSTrackDemuxer::FrameIndexFromOffset(int64_t aOffset) const { int64_t frameIndex = 0; if (AverageFrameLength() > 0) { frameIndex = (aOffset - mParser->FirstFrame().Offset()) / AverageFrameLength(); } ADTSLOGV("FrameIndexFromOffset(%" PRId64 ") -> %" PRId64, aOffset, frameIndex); return std::max(0, frameIndex); } int64_t ADTSTrackDemuxer::FrameIndexFromTime(const media::TimeUnit& aTime) const { int64_t frameIndex = 0; if (mSamplesPerSecond > 0 && mSamplesPerFrame > 0) { frameIndex = aTime.ToSeconds() * mSamplesPerSecond / mSamplesPerFrame - 1; } ADTSLOGV("FrameIndexFromOffset(%fs) -> %" PRId64, aTime.ToSeconds(), frameIndex); return std::max(0, frameIndex); } void ADTSTrackDemuxer::UpdateState(const adts::Frame& aFrame) { int32_t frameLength = aFrame.Length(); // Prevent overflow. if (mTotalFrameLen + frameLength < mTotalFrameLen) { // These variables have a linear dependency and are only used to derive the // average frame length. mTotalFrameLen /= 2; mNumParsedFrames /= 2; } // Full frame parsed, move offset to its end. mOffset = aFrame.Offset() + frameLength; mTotalFrameLen += frameLength; if (!mSamplesPerFrame) { const adts::FrameHeader& header = aFrame.Header(); mSamplesPerFrame = header.mSamples; mSamplesPerSecond = header.mSampleRate; mChannels = header.mChannels; } ++mNumParsedFrames; ++mFrameIndex; MOZ_ASSERT(mFrameIndex > 0); } int32_t ADTSTrackDemuxer::Read(uint8_t* aBuffer, int64_t aOffset, int32_t aSize) { ADTSLOGV("ADTSTrackDemuxer::Read(%p %" PRId64 " %d)", aBuffer, aOffset, aSize); const int64_t streamLen = StreamLength(); if (mInfo && streamLen > 0) { int64_t max = streamLen > aOffset ? streamLen - aOffset : 0; // Prevent blocking reads after successful initialization. aSize = std::min(aSize, max); } uint32_t read = 0; ADTSLOGV("ADTSTrackDemuxer::Read -> ReadAt(%d)", aSize); const nsresult rv = mSource.ReadAt(aOffset, reinterpret_cast(aBuffer), static_cast(aSize), &read); NS_ENSURE_SUCCESS(rv, 0); return static_cast(read); } double ADTSTrackDemuxer::AverageFrameLength() const { if (mNumParsedFrames) { return static_cast(mTotalFrameLen) / mNumParsedFrames; } return 0.0; } } // namespace mozilla