diff options
Diffstat (limited to 'dom/media/ADTSDemuxer.cpp')
-rw-r--r-- | dom/media/ADTSDemuxer.cpp | 845 |
1 files changed, 845 insertions, 0 deletions
diff --git a/dom/media/ADTSDemuxer.cpp b/dom/media/ADTSDemuxer.cpp new file mode 100644 index 000000000..82075b65b --- /dev/null +++ b/dom/media/ADTSDemuxer.cpp @@ -0,0 +1,845 @@ +/* -*- 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 <inttypes.h> + +#include "nsAutoPtr.h" +#include "VideoUtils.h" +#include "TimeUnits.h" +#include "prenv.h" + +#ifdef PR_LOGGING +extern mozilla::LazyLogModule gMediaDemuxerLog; +#define ADTSLOG(msg, ...) \ + MOZ_LOG(gMediaDemuxerLog, LogLevel::Debug, ("ADTSDemuxer " msg, ##__VA_ARGS__)) +#define ADTSLOGV(msg, ...) \ + MOZ_LOG(gMediaDemuxerLog, LogLevel::Verbose, ("ADTSDemuxer " msg, ##__VA_ARGS__)) +#else +#define ADTSLOG(msg, ...) do {} while (false) +#define ADTSLOGV(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; } + + + // 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() { + 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; +}; + + +// 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::InitPromise> +ADTSDemuxer::Init() +{ + if (!InitInternal()) { + ADTSLOG("Init() failure: waiting for data"); + + return InitPromise::CreateAndReject( + NS_ERROR_DOM_MEDIA_METADATA_ERR, __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<MediaTrackDemuxer> +ADTSDemuxer::GetTrackDemuxer(TrackInfo::TrackType aType, uint32_t aTrackNumber) +{ + if (!mTrackDemuxer) { + return nullptr; + } + + return RefPtr<ADTSTrackDemuxer>(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<MediaRawData> 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<AudioInfo>(); + } + + 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; +} + +UniquePtr<TrackInfo> +ADTSTrackDemuxer::GetInfo() const +{ + return mInfo->Clone(); +} + +RefPtr<ADTSTrackDemuxer::SeekPromise> +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.ToMicroseconds(), 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.ToMicroseconds(), 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, + AverageFrameLength(), mNumParsedFrames, mFrameIndex, + mOffset, Duration(mFrameIndex + 1).ToMicroseconds()); + } + + ADTSLOG("ScanUntil End avgFrameLen=%f mNumParsedFrames=%" PRIu64 + " mFrameIndex=%" PRId64 " mOffset=%" PRIu64, + AverageFrameLength(), mNumParsedFrames, mFrameIndex, mOffset); + + return Duration(mFrameIndex); +} + +RefPtr<ADTSTrackDemuxer::SamplesPromise> +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); + + MOZ_ASSERT(aNumSamples); + + RefPtr<SamplesHolder> frames = new SamplesHolder(); + + while (aNumSamples--) { + RefPtr<MediaRawData> 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( + NS_ERROR_DOM_MEDIA_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::SkipAccessPointPromise> +ADTSTrackDemuxer::SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold) +{ + // Will not be called for audio-only resources. + return SkipAccessPointPromise::CreateAndReject( + SkipFailureHolder(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, 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<MediaResource> 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); + } + + return FramesToTimeUnit(aNumFrames * mSamplesPerFrame, mSamplesPerSecond); +} + +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<MediaRawData> 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<MediaRawData> +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<MediaRawData> frame = new MediaRawData(); + frame->mOffset = offset; + + nsAutoPtr<MediaRawDataWriter> 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<int64_t>(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<int64_t>(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) { + // Prevent blocking reads after successful initialization. + aSize = std::min<int64_t>(aSize, streamLen - aOffset); + } + + uint32_t read = 0; + ADTSLOGV("ADTSTrackDemuxer::Read -> ReadAt(%d)", aSize); + 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); +} + +double +ADTSTrackDemuxer::AverageFrameLength() const +{ + if (mNumParsedFrames) { + return static_cast<double>(mTotalFrameLen) / mNumParsedFrames; + } + + return 0.0; +} + +} // namespace mozilla |