Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | #if !defined(OggReader_h_) |
michael@0 | 7 | #define OggReader_h_ |
michael@0 | 8 | |
michael@0 | 9 | #include <ogg/ogg.h> |
michael@0 | 10 | #include <theora/theoradec.h> |
michael@0 | 11 | #ifdef MOZ_TREMOR |
michael@0 | 12 | #include <tremor/ivorbiscodec.h> |
michael@0 | 13 | #else |
michael@0 | 14 | #include <vorbis/codec.h> |
michael@0 | 15 | #endif |
michael@0 | 16 | #include "MediaDecoderReader.h" |
michael@0 | 17 | #include "OggCodecState.h" |
michael@0 | 18 | #include "VideoUtils.h" |
michael@0 | 19 | #include "mozilla/Monitor.h" |
michael@0 | 20 | |
michael@0 | 21 | namespace mozilla { |
michael@0 | 22 | namespace dom { |
michael@0 | 23 | class TimeRanges; |
michael@0 | 24 | } |
michael@0 | 25 | } |
michael@0 | 26 | |
michael@0 | 27 | namespace mozilla { |
michael@0 | 28 | |
michael@0 | 29 | // Thread safe container to store the codec information and the serial for each |
michael@0 | 30 | // streams. |
michael@0 | 31 | class OggCodecStore |
michael@0 | 32 | { |
michael@0 | 33 | public: |
michael@0 | 34 | OggCodecStore(); |
michael@0 | 35 | void Add(uint32_t serial, OggCodecState* codecState); |
michael@0 | 36 | bool Contains(uint32_t serial); |
michael@0 | 37 | OggCodecState* Get(uint32_t serial); |
michael@0 | 38 | bool IsKnownStream(uint32_t aSerial); |
michael@0 | 39 | |
michael@0 | 40 | private: |
michael@0 | 41 | // Maps Ogg serialnos to OggStreams. |
michael@0 | 42 | nsClassHashtable<nsUint32HashKey, OggCodecState> mCodecStates; |
michael@0 | 43 | |
michael@0 | 44 | // Protects the |mCodecStates| and the |mKnownStreams| members. |
michael@0 | 45 | Monitor mMonitor; |
michael@0 | 46 | }; |
michael@0 | 47 | |
michael@0 | 48 | class OggReader : public MediaDecoderReader |
michael@0 | 49 | { |
michael@0 | 50 | public: |
michael@0 | 51 | OggReader(AbstractMediaDecoder* aDecoder); |
michael@0 | 52 | ~OggReader(); |
michael@0 | 53 | |
michael@0 | 54 | virtual nsresult Init(MediaDecoderReader* aCloneDonor); |
michael@0 | 55 | virtual nsresult ResetDecode(); |
michael@0 | 56 | virtual bool DecodeAudioData(); |
michael@0 | 57 | |
michael@0 | 58 | // If the Theora granulepos has not been captured, it may read several packets |
michael@0 | 59 | // until one with a granulepos has been captured, to ensure that all packets |
michael@0 | 60 | // read have valid time info. |
michael@0 | 61 | virtual bool DecodeVideoFrame(bool &aKeyframeSkip, |
michael@0 | 62 | int64_t aTimeThreshold); |
michael@0 | 63 | |
michael@0 | 64 | virtual bool HasAudio() { |
michael@0 | 65 | return (mVorbisState != 0 && mVorbisState->mActive) |
michael@0 | 66 | #ifdef MOZ_OPUS |
michael@0 | 67 | || (mOpusState != 0 && mOpusState->mActive) |
michael@0 | 68 | #endif /* MOZ_OPUS */ |
michael@0 | 69 | ; |
michael@0 | 70 | } |
michael@0 | 71 | |
michael@0 | 72 | virtual bool HasVideo() { |
michael@0 | 73 | return mTheoraState != 0 && mTheoraState->mActive; |
michael@0 | 74 | } |
michael@0 | 75 | |
michael@0 | 76 | virtual nsresult ReadMetadata(MediaInfo* aInfo, |
michael@0 | 77 | MetadataTags** aTags); |
michael@0 | 78 | virtual nsresult Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime); |
michael@0 | 79 | virtual nsresult GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime); |
michael@0 | 80 | |
michael@0 | 81 | private: |
michael@0 | 82 | // This monitor should be taken when reading or writing to mIsChained. |
michael@0 | 83 | ReentrantMonitor mMonitor; |
michael@0 | 84 | |
michael@0 | 85 | // Specialized Reset() method to signal if the seek is |
michael@0 | 86 | // to the start of the stream. |
michael@0 | 87 | nsresult ResetDecode(bool start); |
michael@0 | 88 | |
michael@0 | 89 | bool HasSkeleton() { |
michael@0 | 90 | return mSkeletonState != 0 && mSkeletonState->mActive; |
michael@0 | 91 | } |
michael@0 | 92 | |
michael@0 | 93 | // Seeks to the keyframe preceeding the target time using available |
michael@0 | 94 | // keyframe indexes. |
michael@0 | 95 | enum IndexedSeekResult { |
michael@0 | 96 | SEEK_OK, // Success. |
michael@0 | 97 | SEEK_INDEX_FAIL, // Failure due to no index, or invalid index. |
michael@0 | 98 | SEEK_FATAL_ERROR // Error returned by a stream operation. |
michael@0 | 99 | }; |
michael@0 | 100 | IndexedSeekResult SeekToKeyframeUsingIndex(int64_t aTarget); |
michael@0 | 101 | |
michael@0 | 102 | // Rolls back a seek-using-index attempt, returning a failure error code. |
michael@0 | 103 | IndexedSeekResult RollbackIndexedSeek(int64_t aOffset); |
michael@0 | 104 | |
michael@0 | 105 | // Represents a section of contiguous media, with a start and end offset, |
michael@0 | 106 | // and the timestamps of the start and end of that range, that is cached. |
michael@0 | 107 | // Used to denote the extremities of a range in which we can seek quickly |
michael@0 | 108 | // (because it's cached). |
michael@0 | 109 | class SeekRange { |
michael@0 | 110 | public: |
michael@0 | 111 | SeekRange() |
michael@0 | 112 | : mOffsetStart(0), |
michael@0 | 113 | mOffsetEnd(0), |
michael@0 | 114 | mTimeStart(0), |
michael@0 | 115 | mTimeEnd(0) |
michael@0 | 116 | {} |
michael@0 | 117 | |
michael@0 | 118 | SeekRange(int64_t aOffsetStart, |
michael@0 | 119 | int64_t aOffsetEnd, |
michael@0 | 120 | int64_t aTimeStart, |
michael@0 | 121 | int64_t aTimeEnd) |
michael@0 | 122 | : mOffsetStart(aOffsetStart), |
michael@0 | 123 | mOffsetEnd(aOffsetEnd), |
michael@0 | 124 | mTimeStart(aTimeStart), |
michael@0 | 125 | mTimeEnd(aTimeEnd) |
michael@0 | 126 | {} |
michael@0 | 127 | |
michael@0 | 128 | bool IsNull() const { |
michael@0 | 129 | return mOffsetStart == 0 && |
michael@0 | 130 | mOffsetEnd == 0 && |
michael@0 | 131 | mTimeStart == 0 && |
michael@0 | 132 | mTimeEnd == 0; |
michael@0 | 133 | } |
michael@0 | 134 | |
michael@0 | 135 | int64_t mOffsetStart, mOffsetEnd; // in bytes. |
michael@0 | 136 | int64_t mTimeStart, mTimeEnd; // in usecs. |
michael@0 | 137 | }; |
michael@0 | 138 | |
michael@0 | 139 | // Seeks to aTarget usecs in the buffered range aRange using bisection search, |
michael@0 | 140 | // or to the keyframe prior to aTarget if we have video. aAdjustedTarget is |
michael@0 | 141 | // an adjusted version of the target used to account for Opus pre-roll, if |
michael@0 | 142 | // necessary. aStartTime must be the presentation time at the start of media, |
michael@0 | 143 | // and aEndTime the time at end of media. aRanges must be the time/byte ranges |
michael@0 | 144 | // buffered in the media cache as per GetSeekRanges(). |
michael@0 | 145 | nsresult SeekInBufferedRange(int64_t aTarget, |
michael@0 | 146 | int64_t aAdjustedTarget, |
michael@0 | 147 | int64_t aStartTime, |
michael@0 | 148 | int64_t aEndTime, |
michael@0 | 149 | const nsTArray<SeekRange>& aRanges, |
michael@0 | 150 | const SeekRange& aRange); |
michael@0 | 151 | |
michael@0 | 152 | // Seeks to before aTarget usecs in media using bisection search. If the media |
michael@0 | 153 | // has video, this will seek to before the keyframe required to render the |
michael@0 | 154 | // media at aTarget. Will use aRanges in order to narrow the bisection |
michael@0 | 155 | // search space. aStartTime must be the presentation time at the start of |
michael@0 | 156 | // media, and aEndTime the time at end of media. aRanges must be the time/byte |
michael@0 | 157 | // ranges buffered in the media cache as per GetSeekRanges(). |
michael@0 | 158 | nsresult SeekInUnbuffered(int64_t aTarget, |
michael@0 | 159 | int64_t aStartTime, |
michael@0 | 160 | int64_t aEndTime, |
michael@0 | 161 | const nsTArray<SeekRange>& aRanges); |
michael@0 | 162 | |
michael@0 | 163 | // Get the end time of aEndOffset. This is the playback position we'd reach |
michael@0 | 164 | // after playback finished at aEndOffset. |
michael@0 | 165 | int64_t RangeEndTime(int64_t aEndOffset); |
michael@0 | 166 | |
michael@0 | 167 | // Get the end time of aEndOffset, without reading before aStartOffset. |
michael@0 | 168 | // This is the playback position we'd reach after playback finished at |
michael@0 | 169 | // aEndOffset. If bool aCachedDataOnly is true, then we'll only read |
michael@0 | 170 | // from data which is cached in the media cached, otherwise we'll do |
michael@0 | 171 | // regular blocking reads from the media stream. If bool aCachedDataOnly |
michael@0 | 172 | // is true, this can safely be called on the main thread, otherwise it |
michael@0 | 173 | // must be called on the state machine thread. |
michael@0 | 174 | int64_t RangeEndTime(int64_t aStartOffset, |
michael@0 | 175 | int64_t aEndOffset, |
michael@0 | 176 | bool aCachedDataOnly); |
michael@0 | 177 | |
michael@0 | 178 | // Get the start time of the range beginning at aOffset. This is the start |
michael@0 | 179 | // time of the first frame and or audio sample we'd be able to play if we |
michael@0 | 180 | // started playback at aOffset. |
michael@0 | 181 | int64_t RangeStartTime(int64_t aOffset); |
michael@0 | 182 | |
michael@0 | 183 | // Performs a seek bisection to move the media stream's read cursor to the |
michael@0 | 184 | // last ogg page boundary which has end time before aTarget usecs on both the |
michael@0 | 185 | // Theora and Vorbis bitstreams. Limits its search to data inside aRange; |
michael@0 | 186 | // i.e. it will only read inside of the aRange's start and end offsets. |
michael@0 | 187 | // aFuzz is the number of usecs of leniency we'll allow; we'll terminate the |
michael@0 | 188 | // seek when we land in the range (aTime - aFuzz, aTime) usecs. |
michael@0 | 189 | nsresult SeekBisection(int64_t aTarget, |
michael@0 | 190 | const SeekRange& aRange, |
michael@0 | 191 | uint32_t aFuzz); |
michael@0 | 192 | |
michael@0 | 193 | // Returns true if the serial number is for a stream we encountered |
michael@0 | 194 | // while reading metadata. Call on the main thread only. |
michael@0 | 195 | bool IsKnownStream(uint32_t aSerial); |
michael@0 | 196 | |
michael@0 | 197 | // Fills aRanges with SeekRanges denoting the sections of the media which |
michael@0 | 198 | // have been downloaded and are stored in the media cache. The reader |
michael@0 | 199 | // monitor must must be held with exactly one lock count. The MediaResource |
michael@0 | 200 | // must be pinned while calling this. |
michael@0 | 201 | nsresult GetSeekRanges(nsTArray<SeekRange>& aRanges); |
michael@0 | 202 | |
michael@0 | 203 | // Returns the range in which you should perform a seek bisection if |
michael@0 | 204 | // you wish to seek to aTarget usecs, given the known (buffered) byte ranges |
michael@0 | 205 | // in aRanges. If aExact is true, we only return an exact copy of a |
michael@0 | 206 | // range in which aTarget lies, or a null range if aTarget isn't contained |
michael@0 | 207 | // in any of the (buffered) ranges. Otherwise, when aExact is false, |
michael@0 | 208 | // we'll construct the smallest possible range we can, based on the times |
michael@0 | 209 | // and byte offsets known in aRanges. We can then use this to minimize our |
michael@0 | 210 | // bisection's search space when the target isn't in a known buffered range. |
michael@0 | 211 | SeekRange SelectSeekRange(const nsTArray<SeekRange>& aRanges, |
michael@0 | 212 | int64_t aTarget, |
michael@0 | 213 | int64_t aStartTime, |
michael@0 | 214 | int64_t aEndTime, |
michael@0 | 215 | bool aExact); |
michael@0 | 216 | private: |
michael@0 | 217 | |
michael@0 | 218 | // Decodes a packet of Vorbis data, and inserts its samples into the |
michael@0 | 219 | // audio queue. |
michael@0 | 220 | nsresult DecodeVorbis(ogg_packet* aPacket); |
michael@0 | 221 | |
michael@0 | 222 | // Decodes a packet of Opus data, and inserts its samples into the |
michael@0 | 223 | // audio queue. |
michael@0 | 224 | nsresult DecodeOpus(ogg_packet* aPacket); |
michael@0 | 225 | |
michael@0 | 226 | // Decodes a packet of Theora data, and inserts its frame into the |
michael@0 | 227 | // video queue. May return NS_ERROR_OUT_OF_MEMORY. Caller must have obtained |
michael@0 | 228 | // the reader's monitor. aTimeThreshold is the current playback position |
michael@0 | 229 | // in media time in microseconds. Frames with an end time before this will |
michael@0 | 230 | // not be enqueued. |
michael@0 | 231 | nsresult DecodeTheora(ogg_packet* aPacket, int64_t aTimeThreshold); |
michael@0 | 232 | |
michael@0 | 233 | // Read a page of data from the Ogg file. Returns true if a page has been |
michael@0 | 234 | // read, false if the page read failed or end of file reached. |
michael@0 | 235 | bool ReadOggPage(ogg_page* aPage); |
michael@0 | 236 | |
michael@0 | 237 | // Reads and decodes header packets for aState, until either header decode |
michael@0 | 238 | // fails, or is complete. Initializes the codec state before returning. |
michael@0 | 239 | // Returns true if reading headers and initializtion of the stream |
michael@0 | 240 | // succeeds. |
michael@0 | 241 | bool ReadHeaders(OggCodecState* aState); |
michael@0 | 242 | |
michael@0 | 243 | // Reads the next link in the chain. |
michael@0 | 244 | bool ReadOggChain(); |
michael@0 | 245 | |
michael@0 | 246 | // Set this media as being a chain and notifies the state machine that the |
michael@0 | 247 | // media is no longer seekable. |
michael@0 | 248 | void SetChained(bool aIsChained); |
michael@0 | 249 | |
michael@0 | 250 | // Returns the next Ogg packet for an bitstream/codec state. Returns a |
michael@0 | 251 | // pointer to an ogg_packet on success, or nullptr if the read failed. |
michael@0 | 252 | // The caller is responsible for deleting the packet and its |packet| field. |
michael@0 | 253 | ogg_packet* NextOggPacket(OggCodecState* aCodecState); |
michael@0 | 254 | |
michael@0 | 255 | // Fills aTracks with the serial numbers of each active stream, for use by |
michael@0 | 256 | // various SkeletonState functions. |
michael@0 | 257 | void BuildSerialList(nsTArray<uint32_t>& aTracks); |
michael@0 | 258 | |
michael@0 | 259 | OggCodecStore mCodecStore; |
michael@0 | 260 | |
michael@0 | 261 | // Decode state of the Theora bitstream we're decoding, if we have video. |
michael@0 | 262 | TheoraState* mTheoraState; |
michael@0 | 263 | |
michael@0 | 264 | // Decode state of the Vorbis bitstream we're decoding, if we have audio. |
michael@0 | 265 | VorbisState* mVorbisState; |
michael@0 | 266 | |
michael@0 | 267 | #ifdef MOZ_OPUS |
michael@0 | 268 | // Decode state of the Opus bitstream we're decoding, if we have one. |
michael@0 | 269 | OpusState *mOpusState; |
michael@0 | 270 | |
michael@0 | 271 | // Represents the user pref media.opus.enabled at the time our |
michael@0 | 272 | // contructor was called. We can't check it dynamically because |
michael@0 | 273 | // we're not on the main thread; |
michael@0 | 274 | bool mOpusEnabled; |
michael@0 | 275 | #endif /* MOZ_OPUS */ |
michael@0 | 276 | |
michael@0 | 277 | // Decode state of the Skeleton bitstream. |
michael@0 | 278 | SkeletonState* mSkeletonState; |
michael@0 | 279 | |
michael@0 | 280 | // Ogg decoding state. |
michael@0 | 281 | ogg_sync_state mOggState; |
michael@0 | 282 | |
michael@0 | 283 | // Vorbis/Opus/Theora data used to compute timestamps. This is written on the |
michael@0 | 284 | // decoder thread and read on the main thread. All reading on the main |
michael@0 | 285 | // thread must be done after metadataloaded. We can't use the existing |
michael@0 | 286 | // data in the codec states due to threading issues. You must check the |
michael@0 | 287 | // associated mTheoraState or mVorbisState pointer is non-null before |
michael@0 | 288 | // using this codec data. |
michael@0 | 289 | uint32_t mVorbisSerial; |
michael@0 | 290 | uint32_t mOpusSerial; |
michael@0 | 291 | uint32_t mTheoraSerial; |
michael@0 | 292 | vorbis_info mVorbisInfo; |
michael@0 | 293 | int mOpusPreSkip; |
michael@0 | 294 | th_info mTheoraInfo; |
michael@0 | 295 | |
michael@0 | 296 | // The picture region inside Theora frame to be displayed, if we have |
michael@0 | 297 | // a Theora video track. |
michael@0 | 298 | nsIntRect mPicture; |
michael@0 | 299 | |
michael@0 | 300 | // True if we are decoding a chained ogg. Reading or writing to this member |
michael@0 | 301 | // should be done with |mMonitor| acquired. |
michael@0 | 302 | bool mIsChained; |
michael@0 | 303 | |
michael@0 | 304 | // Number of audio frames decoded so far. |
michael@0 | 305 | int64_t mDecodedAudioFrames; |
michael@0 | 306 | }; |
michael@0 | 307 | |
michael@0 | 308 | } // namespace mozilla |
michael@0 | 309 | |
michael@0 | 310 | #endif |