/* -*- 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(WebMBufferedParser_h_) #define WebMBufferedParser_h_ #include "nsISupportsImpl.h" #include "nsTArray.h" #include "mozilla/ReentrantMonitor.h" #include "MediaResource.h" namespace mozilla { // Stores a stream byte offset and the scaled timecode of the block at // that offset. struct WebMTimeDataOffset { WebMTimeDataOffset(int64_t aEndOffset, uint64_t aTimecode, int64_t aInitOffset, int64_t aSyncOffset, int64_t aClusterEndOffset) : mEndOffset(aEndOffset) , mInitOffset(aInitOffset) , mSyncOffset(aSyncOffset) , mClusterEndOffset(aClusterEndOffset) , mTimecode(aTimecode) {} bool operator==(int64_t aEndOffset) const { return mEndOffset == aEndOffset; } bool operator!=(int64_t aEndOffset) const { return mEndOffset != aEndOffset; } bool operator<(int64_t aEndOffset) const { return mEndOffset < aEndOffset; } int64_t mEndOffset; int64_t mInitOffset; int64_t mSyncOffset; int64_t mClusterEndOffset; uint64_t mTimecode; }; // A simple WebM parser that produces data offset to timecode pairs as it // consumes blocks. A new parser is created for each distinct range of data // received and begins parsing from the first WebM cluster within that // range. Old parsers are destroyed when their range merges with a later // parser or an already parsed range. The parser may start at any position // within the stream. struct WebMBufferedParser { explicit WebMBufferedParser(int64_t aOffset) : mStartOffset(aOffset) , mCurrentOffset(aOffset) , mInitEndOffset(-1) , mBlockEndOffset(-1) , mState(READ_ELEMENT_ID) , mNextState(READ_ELEMENT_ID) , mVIntRaw(false) , mLastInitStartOffset(-1) , mClusterSyncPos(0) , mVIntLeft(0) , mBlockSize(0) , mClusterTimecode(0) , mClusterOffset(0) , mClusterEndOffset(-1) , mBlockOffset(0) , mBlockTimecode(0) , mBlockTimecodeLength(0) , mSkipBytes(0) , mTimecodeScale(1000000) , mGotTimecodeScale(false) { if (mStartOffset != 0) { mState = FIND_CLUSTER_SYNC; } } uint32_t GetTimecodeScale() { MOZ_ASSERT(mGotTimecodeScale); return mTimecodeScale; } // If this parser is not expected to parse a segment info, it must be told // the appropriate timecode scale to use from elsewhere. void SetTimecodeScale(uint32_t aTimecodeScale) { mTimecodeScale = aTimecodeScale; mGotTimecodeScale = true; } // Steps the parser through aLength bytes of data. Always consumes // aLength bytes. Updates mCurrentOffset before returning. Acquires // aReentrantMonitor before using aMapping. // Returns false if an error was encountered. bool Append(const unsigned char* aBuffer, uint32_t aLength, nsTArray<WebMTimeDataOffset>& aMapping, ReentrantMonitor& aReentrantMonitor); bool operator==(int64_t aOffset) const { return mCurrentOffset == aOffset; } bool operator<(int64_t aOffset) const { return mCurrentOffset < aOffset; } // Returns the start offset of the init (EBML) or media segment (Cluster) // following the aOffset position. If none were found, returns mBlockEndOffset. // This allows to determine the end of the interval containg aOffset. int64_t EndSegmentOffset(int64_t aOffset); // The offset at which this parser started parsing. Used to merge // adjacent parsers, in which case the later parser adopts the earlier // parser's mStartOffset. int64_t mStartOffset; // Current offset within the stream. Updated in chunks as Append() consumes // data. int64_t mCurrentOffset; // Tracks element's end offset. This indicates the end of the first init // segment. Will only be set if a Segment Information has been found. int64_t mInitEndOffset; // End offset of the last block parsed. // Will only be set if a complete block has been parsed. int64_t mBlockEndOffset; private: enum State { // Parser start state. Expects to begin at a valid EBML element. Move // to READ_VINT with mVIntRaw true, then return to READ_ELEMENT_SIZE. READ_ELEMENT_ID, // Store element ID read into mVInt into mElement.mID. Move to // READ_VINT with mVIntRaw false, then return to PARSE_ELEMENT. READ_ELEMENT_SIZE, // Parser start state for parsers started at an arbitrary offset. Scans // forward for the first cluster, then move to READ_ELEMENT_ID. FIND_CLUSTER_SYNC, // Simplistic core of the parser. Does not pay attention to nesting of // elements. Checks mElement for an element ID of interest, then moves // to the next state as determined by the element ID. PARSE_ELEMENT, // Read the first byte of a variable length integer. The first byte // encodes both the variable integer's length and part of the value. // The value read so far is stored in mVInt.mValue and the length is // stored in mVInt.mLength. The number of bytes left to read is stored // in mVIntLeft. READ_VINT, // Reads the remaining mVIntLeft bytes into mVInt.mValue. READ_VINT_REST, // mVInt holds the parsed timecode scale, store it in mTimecodeScale, // then return READ_ELEMENT_ID. READ_TIMECODESCALE, // mVInt holds the parsed cluster timecode, store it in // mClusterTimecode, then return to READ_ELEMENT_ID. READ_CLUSTER_TIMECODE, // mBlockTimecodeLength holds the remaining length of the block timecode // left to read. Read each byte of the timecode into mBlockTimecode. // Once complete, calculate the scaled timecode from the cluster // timecode, block timecode, and timecode scale, and insert a // WebMTimeDataOffset entry into aMapping if one is not already present // for this offset. READ_BLOCK_TIMECODE, // Will skip the current tracks element and set mInitEndOffset if an init // segment has been found. // Currently, only assumes it's the end of the tracks element. CHECK_INIT_FOUND, // Skip mSkipBytes of data before resuming parse at mNextState. SKIP_DATA, }; // Current state machine action. State mState; // Next state machine action. SKIP_DATA and READ_VINT_REST advance to // mNextState when the current action completes. State mNextState; struct VInt { VInt() : mValue(0), mLength(0) {} uint64_t mValue; uint64_t mLength; }; struct EBMLElement { uint64_t Length() { return mID.mLength + mSize.mLength; } VInt mID; VInt mSize; }; EBMLElement mElement; VInt mVInt; bool mVIntRaw; // EBML start offset. This indicates the start of the last init segment // parsed. Will only be set if an EBML element has been found. int64_t mLastInitStartOffset; // Current match position within CLUSTER_SYNC_ID. Used to find sync // within arbitrary data. uint32_t mClusterSyncPos; // Number of bytes of mVInt left to read. mVInt is complete once this // reaches 0. uint32_t mVIntLeft; // Size of the block currently being parsed. Any unused data within the // block is skipped once the block timecode has been parsed. uint64_t mBlockSize; // Cluster-level timecode. uint64_t mClusterTimecode; // Start offset of the cluster currently being parsed. Used as the sync // point offset for the offset-to-time mapping as each block timecode is // been parsed. int64_t mClusterOffset; // End offset of the cluster currently being parsed. -1 if unknown. int64_t mClusterEndOffset; // Start offset of the block currently being parsed. Used as the byte // offset for the offset-to-time mapping once the block timecode has been // parsed. int64_t mBlockOffset; // Block-level timecode. This is summed with mClusterTimecode to produce // an absolute timecode for the offset-to-time mapping. int16_t mBlockTimecode; // Number of bytes of mBlockTimecode left to read. uint32_t mBlockTimecodeLength; // Count of bytes left to skip before resuming parse at mNextState. // Mostly used to skip block payload data after reading a block timecode. uint32_t mSkipBytes; // Timecode scale read from the segment info and used to scale absolute // timecodes. uint32_t mTimecodeScale; // True if we read the timecode scale from the segment info or have // confirmed that the default value is to be used. bool mGotTimecodeScale; }; class WebMBufferedState final { NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebMBufferedState) public: WebMBufferedState() : mReentrantMonitor("WebMBufferedState") , mLastBlockOffset(-1) { MOZ_COUNT_CTOR(WebMBufferedState); } void NotifyDataArrived(const unsigned char* aBuffer, uint32_t aLength, int64_t aOffset); void Reset(); void UpdateIndex(const MediaByteRangeSet& aRanges, MediaResource* aResource); bool CalculateBufferedForRange(int64_t aStartOffset, int64_t aEndOffset, uint64_t* aStartTime, uint64_t* aEndTime); // Returns true if mTimeMapping is not empty and sets aOffset to // the latest offset for which decoding can resume without data // dependencies to arrive at aTime. aTime will be clamped to the start // of mTimeMapping if it is earlier than the first element, and to the end // if later than the last bool GetOffsetForTime(uint64_t aTime, int64_t* aOffset); // Returns end offset of init segment or -1 if none found. int64_t GetInitEndOffset(); // Returns the end offset of the last complete block or -1 if none found. int64_t GetLastBlockOffset(); // Returns start time bool GetStartTime(uint64_t *aTime); // Returns keyframe for time bool GetNextKeyframeTime(uint64_t aTime, uint64_t* aKeyframeTime); private: // Private destructor, to discourage deletion outside of Release(): ~WebMBufferedState() { MOZ_COUNT_DTOR(WebMBufferedState); } // Synchronizes access to the mTimeMapping array and mLastBlockOffset. ReentrantMonitor mReentrantMonitor; // Sorted (by offset) map of data offsets to timecodes. Populated // on the main thread as data is received and parsed by WebMBufferedParsers. nsTArray<WebMTimeDataOffset> mTimeMapping; // The last complete block parsed. -1 if not set. int64_t mLastBlockOffset; // Sorted (by offset) live parser instances. Main thread only. nsTArray<WebMBufferedParser> mRangeParsers; }; } // namespace mozilla #endif