michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: #if !defined(OggReader_h_) michael@0: #define OggReader_h_ michael@0: michael@0: #include michael@0: #include michael@0: #ifdef MOZ_TREMOR michael@0: #include michael@0: #else michael@0: #include michael@0: #endif michael@0: #include "MediaDecoderReader.h" michael@0: #include "OggCodecState.h" michael@0: #include "VideoUtils.h" michael@0: #include "mozilla/Monitor.h" michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: class TimeRanges; michael@0: } michael@0: } michael@0: michael@0: namespace mozilla { michael@0: michael@0: // Thread safe container to store the codec information and the serial for each michael@0: // streams. michael@0: class OggCodecStore michael@0: { michael@0: public: michael@0: OggCodecStore(); michael@0: void Add(uint32_t serial, OggCodecState* codecState); michael@0: bool Contains(uint32_t serial); michael@0: OggCodecState* Get(uint32_t serial); michael@0: bool IsKnownStream(uint32_t aSerial); michael@0: michael@0: private: michael@0: // Maps Ogg serialnos to OggStreams. michael@0: nsClassHashtable mCodecStates; michael@0: michael@0: // Protects the |mCodecStates| and the |mKnownStreams| members. michael@0: Monitor mMonitor; michael@0: }; michael@0: michael@0: class OggReader : public MediaDecoderReader michael@0: { michael@0: public: michael@0: OggReader(AbstractMediaDecoder* aDecoder); michael@0: ~OggReader(); michael@0: michael@0: virtual nsresult Init(MediaDecoderReader* aCloneDonor); michael@0: virtual nsresult ResetDecode(); michael@0: virtual bool DecodeAudioData(); michael@0: michael@0: // If the Theora granulepos has not been captured, it may read several packets michael@0: // until one with a granulepos has been captured, to ensure that all packets michael@0: // read have valid time info. michael@0: virtual bool DecodeVideoFrame(bool &aKeyframeSkip, michael@0: int64_t aTimeThreshold); michael@0: michael@0: virtual bool HasAudio() { michael@0: return (mVorbisState != 0 && mVorbisState->mActive) michael@0: #ifdef MOZ_OPUS michael@0: || (mOpusState != 0 && mOpusState->mActive) michael@0: #endif /* MOZ_OPUS */ michael@0: ; michael@0: } michael@0: michael@0: virtual bool HasVideo() { michael@0: return mTheoraState != 0 && mTheoraState->mActive; michael@0: } michael@0: michael@0: virtual nsresult ReadMetadata(MediaInfo* aInfo, michael@0: MetadataTags** aTags); michael@0: virtual nsresult Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime); michael@0: virtual nsresult GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime); michael@0: michael@0: private: michael@0: // This monitor should be taken when reading or writing to mIsChained. michael@0: ReentrantMonitor mMonitor; michael@0: michael@0: // Specialized Reset() method to signal if the seek is michael@0: // to the start of the stream. michael@0: nsresult ResetDecode(bool start); michael@0: michael@0: bool HasSkeleton() { michael@0: return mSkeletonState != 0 && mSkeletonState->mActive; michael@0: } michael@0: michael@0: // Seeks to the keyframe preceeding the target time using available michael@0: // keyframe indexes. michael@0: enum IndexedSeekResult { michael@0: SEEK_OK, // Success. michael@0: SEEK_INDEX_FAIL, // Failure due to no index, or invalid index. michael@0: SEEK_FATAL_ERROR // Error returned by a stream operation. michael@0: }; michael@0: IndexedSeekResult SeekToKeyframeUsingIndex(int64_t aTarget); michael@0: michael@0: // Rolls back a seek-using-index attempt, returning a failure error code. michael@0: IndexedSeekResult RollbackIndexedSeek(int64_t aOffset); michael@0: michael@0: // Represents a section of contiguous media, with a start and end offset, michael@0: // and the timestamps of the start and end of that range, that is cached. michael@0: // Used to denote the extremities of a range in which we can seek quickly michael@0: // (because it's cached). michael@0: class SeekRange { michael@0: public: michael@0: SeekRange() michael@0: : mOffsetStart(0), michael@0: mOffsetEnd(0), michael@0: mTimeStart(0), michael@0: mTimeEnd(0) michael@0: {} michael@0: michael@0: SeekRange(int64_t aOffsetStart, michael@0: int64_t aOffsetEnd, michael@0: int64_t aTimeStart, michael@0: int64_t aTimeEnd) michael@0: : mOffsetStart(aOffsetStart), michael@0: mOffsetEnd(aOffsetEnd), michael@0: mTimeStart(aTimeStart), michael@0: mTimeEnd(aTimeEnd) michael@0: {} michael@0: michael@0: bool IsNull() const { michael@0: return mOffsetStart == 0 && michael@0: mOffsetEnd == 0 && michael@0: mTimeStart == 0 && michael@0: mTimeEnd == 0; michael@0: } michael@0: michael@0: int64_t mOffsetStart, mOffsetEnd; // in bytes. michael@0: int64_t mTimeStart, mTimeEnd; // in usecs. michael@0: }; michael@0: michael@0: // Seeks to aTarget usecs in the buffered range aRange using bisection search, michael@0: // or to the keyframe prior to aTarget if we have video. aAdjustedTarget is michael@0: // an adjusted version of the target used to account for Opus pre-roll, if michael@0: // necessary. aStartTime must be the presentation time at the start of media, michael@0: // and aEndTime the time at end of media. aRanges must be the time/byte ranges michael@0: // buffered in the media cache as per GetSeekRanges(). michael@0: nsresult SeekInBufferedRange(int64_t aTarget, michael@0: int64_t aAdjustedTarget, michael@0: int64_t aStartTime, michael@0: int64_t aEndTime, michael@0: const nsTArray& aRanges, michael@0: const SeekRange& aRange); michael@0: michael@0: // Seeks to before aTarget usecs in media using bisection search. If the media michael@0: // has video, this will seek to before the keyframe required to render the michael@0: // media at aTarget. Will use aRanges in order to narrow the bisection michael@0: // search space. aStartTime must be the presentation time at the start of michael@0: // media, and aEndTime the time at end of media. aRanges must be the time/byte michael@0: // ranges buffered in the media cache as per GetSeekRanges(). michael@0: nsresult SeekInUnbuffered(int64_t aTarget, michael@0: int64_t aStartTime, michael@0: int64_t aEndTime, michael@0: const nsTArray& aRanges); michael@0: michael@0: // Get the end time of aEndOffset. This is the playback position we'd reach michael@0: // after playback finished at aEndOffset. michael@0: int64_t RangeEndTime(int64_t aEndOffset); michael@0: michael@0: // Get the end time of aEndOffset, without reading before aStartOffset. michael@0: // This is the playback position we'd reach after playback finished at michael@0: // aEndOffset. If bool aCachedDataOnly is true, then we'll only read michael@0: // from data which is cached in the media cached, otherwise we'll do michael@0: // regular blocking reads from the media stream. If bool aCachedDataOnly michael@0: // is true, this can safely be called on the main thread, otherwise it michael@0: // must be called on the state machine thread. michael@0: int64_t RangeEndTime(int64_t aStartOffset, michael@0: int64_t aEndOffset, michael@0: bool aCachedDataOnly); michael@0: michael@0: // Get the start time of the range beginning at aOffset. This is the start michael@0: // time of the first frame and or audio sample we'd be able to play if we michael@0: // started playback at aOffset. michael@0: int64_t RangeStartTime(int64_t aOffset); michael@0: michael@0: // Performs a seek bisection to move the media stream's read cursor to the michael@0: // last ogg page boundary which has end time before aTarget usecs on both the michael@0: // Theora and Vorbis bitstreams. Limits its search to data inside aRange; michael@0: // i.e. it will only read inside of the aRange's start and end offsets. michael@0: // aFuzz is the number of usecs of leniency we'll allow; we'll terminate the michael@0: // seek when we land in the range (aTime - aFuzz, aTime) usecs. michael@0: nsresult SeekBisection(int64_t aTarget, michael@0: const SeekRange& aRange, michael@0: uint32_t aFuzz); michael@0: michael@0: // Returns true if the serial number is for a stream we encountered michael@0: // while reading metadata. Call on the main thread only. michael@0: bool IsKnownStream(uint32_t aSerial); michael@0: michael@0: // Fills aRanges with SeekRanges denoting the sections of the media which michael@0: // have been downloaded and are stored in the media cache. The reader michael@0: // monitor must must be held with exactly one lock count. The MediaResource michael@0: // must be pinned while calling this. michael@0: nsresult GetSeekRanges(nsTArray& aRanges); michael@0: michael@0: // Returns the range in which you should perform a seek bisection if michael@0: // you wish to seek to aTarget usecs, given the known (buffered) byte ranges michael@0: // in aRanges. If aExact is true, we only return an exact copy of a michael@0: // range in which aTarget lies, or a null range if aTarget isn't contained michael@0: // in any of the (buffered) ranges. Otherwise, when aExact is false, michael@0: // we'll construct the smallest possible range we can, based on the times michael@0: // and byte offsets known in aRanges. We can then use this to minimize our michael@0: // bisection's search space when the target isn't in a known buffered range. michael@0: SeekRange SelectSeekRange(const nsTArray& aRanges, michael@0: int64_t aTarget, michael@0: int64_t aStartTime, michael@0: int64_t aEndTime, michael@0: bool aExact); michael@0: private: michael@0: michael@0: // Decodes a packet of Vorbis data, and inserts its samples into the michael@0: // audio queue. michael@0: nsresult DecodeVorbis(ogg_packet* aPacket); michael@0: michael@0: // Decodes a packet of Opus data, and inserts its samples into the michael@0: // audio queue. michael@0: nsresult DecodeOpus(ogg_packet* aPacket); michael@0: michael@0: // Decodes a packet of Theora data, and inserts its frame into the michael@0: // video queue. May return NS_ERROR_OUT_OF_MEMORY. Caller must have obtained michael@0: // the reader's monitor. aTimeThreshold is the current playback position michael@0: // in media time in microseconds. Frames with an end time before this will michael@0: // not be enqueued. michael@0: nsresult DecodeTheora(ogg_packet* aPacket, int64_t aTimeThreshold); michael@0: michael@0: // Read a page of data from the Ogg file. Returns true if a page has been michael@0: // read, false if the page read failed or end of file reached. michael@0: bool ReadOggPage(ogg_page* aPage); michael@0: michael@0: // Reads and decodes header packets for aState, until either header decode michael@0: // fails, or is complete. Initializes the codec state before returning. michael@0: // Returns true if reading headers and initializtion of the stream michael@0: // succeeds. michael@0: bool ReadHeaders(OggCodecState* aState); michael@0: michael@0: // Reads the next link in the chain. michael@0: bool ReadOggChain(); michael@0: michael@0: // Set this media as being a chain and notifies the state machine that the michael@0: // media is no longer seekable. michael@0: void SetChained(bool aIsChained); michael@0: michael@0: // Returns the next Ogg packet for an bitstream/codec state. Returns a michael@0: // pointer to an ogg_packet on success, or nullptr if the read failed. michael@0: // The caller is responsible for deleting the packet and its |packet| field. michael@0: ogg_packet* NextOggPacket(OggCodecState* aCodecState); michael@0: michael@0: // Fills aTracks with the serial numbers of each active stream, for use by michael@0: // various SkeletonState functions. michael@0: void BuildSerialList(nsTArray& aTracks); michael@0: michael@0: OggCodecStore mCodecStore; michael@0: michael@0: // Decode state of the Theora bitstream we're decoding, if we have video. michael@0: TheoraState* mTheoraState; michael@0: michael@0: // Decode state of the Vorbis bitstream we're decoding, if we have audio. michael@0: VorbisState* mVorbisState; michael@0: michael@0: #ifdef MOZ_OPUS michael@0: // Decode state of the Opus bitstream we're decoding, if we have one. michael@0: OpusState *mOpusState; michael@0: michael@0: // Represents the user pref media.opus.enabled at the time our michael@0: // contructor was called. We can't check it dynamically because michael@0: // we're not on the main thread; michael@0: bool mOpusEnabled; michael@0: #endif /* MOZ_OPUS */ michael@0: michael@0: // Decode state of the Skeleton bitstream. michael@0: SkeletonState* mSkeletonState; michael@0: michael@0: // Ogg decoding state. michael@0: ogg_sync_state mOggState; michael@0: michael@0: // Vorbis/Opus/Theora data used to compute timestamps. This is written on the michael@0: // decoder thread and read on the main thread. All reading on the main michael@0: // thread must be done after metadataloaded. We can't use the existing michael@0: // data in the codec states due to threading issues. You must check the michael@0: // associated mTheoraState or mVorbisState pointer is non-null before michael@0: // using this codec data. michael@0: uint32_t mVorbisSerial; michael@0: uint32_t mOpusSerial; michael@0: uint32_t mTheoraSerial; michael@0: vorbis_info mVorbisInfo; michael@0: int mOpusPreSkip; michael@0: th_info mTheoraInfo; michael@0: michael@0: // The picture region inside Theora frame to be displayed, if we have michael@0: // a Theora video track. michael@0: nsIntRect mPicture; michael@0: michael@0: // True if we are decoding a chained ogg. Reading or writing to this member michael@0: // should be done with |mMonitor| acquired. michael@0: bool mIsChained; michael@0: michael@0: // Number of audio frames decoded so far. michael@0: int64_t mDecodedAudioFrames; michael@0: }; michael@0: michael@0: } // namespace mozilla michael@0: michael@0: #endif