Fri, 16 Jan 2015 04:50:19 +0100
Replace accessor implementation with direct member state manipulation, by
request https://trac.torproject.org/projects/tor/ticket/9701#comment:32
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 | |
michael@0 | 7 | #include "mozilla/DebugOnly.h" |
michael@0 | 8 | |
michael@0 | 9 | #include "nsError.h" |
michael@0 | 10 | #include "MediaDecoderStateMachine.h" |
michael@0 | 11 | #include "MediaDecoder.h" |
michael@0 | 12 | #include "OggReader.h" |
michael@0 | 13 | #include "VideoUtils.h" |
michael@0 | 14 | #include "theora/theoradec.h" |
michael@0 | 15 | #include <algorithm> |
michael@0 | 16 | #ifdef MOZ_OPUS |
michael@0 | 17 | #include "opus/opus.h" |
michael@0 | 18 | extern "C" { |
michael@0 | 19 | #include "opus/opus_multistream.h" |
michael@0 | 20 | } |
michael@0 | 21 | #endif |
michael@0 | 22 | #include "mozilla/dom/TimeRanges.h" |
michael@0 | 23 | #include "mozilla/TimeStamp.h" |
michael@0 | 24 | #include "VorbisUtils.h" |
michael@0 | 25 | #include "MediaMetadataManager.h" |
michael@0 | 26 | #include "nsISeekableStream.h" |
michael@0 | 27 | #include "gfx2DGlue.h" |
michael@0 | 28 | |
michael@0 | 29 | using namespace mozilla::gfx; |
michael@0 | 30 | |
michael@0 | 31 | namespace mozilla { |
michael@0 | 32 | |
michael@0 | 33 | // On B2G estimate the buffered ranges rather than calculating them explicitly. |
michael@0 | 34 | // This prevents us doing I/O on the main thread, which is prohibited in B2G. |
michael@0 | 35 | #ifdef MOZ_WIDGET_GONK |
michael@0 | 36 | #define OGG_ESTIMATE_BUFFERED 1 |
michael@0 | 37 | #endif |
michael@0 | 38 | |
michael@0 | 39 | // Un-comment to enable logging of seek bisections. |
michael@0 | 40 | //#define SEEK_LOGGING |
michael@0 | 41 | |
michael@0 | 42 | #ifdef PR_LOGGING |
michael@0 | 43 | extern PRLogModuleInfo* gMediaDecoderLog; |
michael@0 | 44 | #define LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg) |
michael@0 | 45 | #ifdef SEEK_LOGGING |
michael@0 | 46 | #define SEEK_LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg) |
michael@0 | 47 | #else |
michael@0 | 48 | #define SEEK_LOG(type, msg) |
michael@0 | 49 | #endif |
michael@0 | 50 | #else |
michael@0 | 51 | #define LOG(type, msg) |
michael@0 | 52 | #define SEEK_LOG(type, msg) |
michael@0 | 53 | #endif |
michael@0 | 54 | |
michael@0 | 55 | // The number of microseconds of "fuzz" we use in a bisection search over |
michael@0 | 56 | // HTTP. When we're seeking with fuzz, we'll stop the search if a bisection |
michael@0 | 57 | // lands between the seek target and SEEK_FUZZ_USECS microseconds before the |
michael@0 | 58 | // seek target. This is becaue it's usually quicker to just keep downloading |
michael@0 | 59 | // from an exisiting connection than to do another bisection inside that |
michael@0 | 60 | // small range, which would open a new HTTP connetion. |
michael@0 | 61 | static const uint32_t SEEK_FUZZ_USECS = 500000; |
michael@0 | 62 | |
michael@0 | 63 | // The number of microseconds of "pre-roll" we use for Opus streams. |
michael@0 | 64 | // The specification recommends 80 ms. |
michael@0 | 65 | #ifdef MOZ_OPUS |
michael@0 | 66 | static const int64_t SEEK_OPUS_PREROLL = 80 * USECS_PER_MS; |
michael@0 | 67 | #endif /* MOZ_OPUS */ |
michael@0 | 68 | |
michael@0 | 69 | enum PageSyncResult { |
michael@0 | 70 | PAGE_SYNC_ERROR = 1, |
michael@0 | 71 | PAGE_SYNC_END_OF_RANGE= 2, |
michael@0 | 72 | PAGE_SYNC_OK = 3 |
michael@0 | 73 | }; |
michael@0 | 74 | |
michael@0 | 75 | // Reads a page from the media resource. |
michael@0 | 76 | static PageSyncResult |
michael@0 | 77 | PageSync(MediaResource* aResource, |
michael@0 | 78 | ogg_sync_state* aState, |
michael@0 | 79 | bool aCachedDataOnly, |
michael@0 | 80 | int64_t aOffset, |
michael@0 | 81 | int64_t aEndOffset, |
michael@0 | 82 | ogg_page* aPage, |
michael@0 | 83 | int& aSkippedBytes); |
michael@0 | 84 | |
michael@0 | 85 | // Chunk size to read when reading Ogg files. Average Ogg page length |
michael@0 | 86 | // is about 4300 bytes, so we read the file in chunks larger than that. |
michael@0 | 87 | static const int PAGE_STEP = 8192; |
michael@0 | 88 | |
michael@0 | 89 | OggReader::OggReader(AbstractMediaDecoder* aDecoder) |
michael@0 | 90 | : MediaDecoderReader(aDecoder), |
michael@0 | 91 | mMonitor("OggReader"), |
michael@0 | 92 | mTheoraState(nullptr), |
michael@0 | 93 | mVorbisState(nullptr), |
michael@0 | 94 | #ifdef MOZ_OPUS |
michael@0 | 95 | mOpusState(nullptr), |
michael@0 | 96 | mOpusEnabled(MediaDecoder::IsOpusEnabled()), |
michael@0 | 97 | #endif /* MOZ_OPUS */ |
michael@0 | 98 | mSkeletonState(nullptr), |
michael@0 | 99 | mVorbisSerial(0), |
michael@0 | 100 | mOpusSerial(0), |
michael@0 | 101 | mTheoraSerial(0), |
michael@0 | 102 | mOpusPreSkip(0), |
michael@0 | 103 | mIsChained(false), |
michael@0 | 104 | mDecodedAudioFrames(0) |
michael@0 | 105 | { |
michael@0 | 106 | MOZ_COUNT_CTOR(OggReader); |
michael@0 | 107 | memset(&mTheoraInfo, 0, sizeof(mTheoraInfo)); |
michael@0 | 108 | } |
michael@0 | 109 | |
michael@0 | 110 | OggReader::~OggReader() |
michael@0 | 111 | { |
michael@0 | 112 | ogg_sync_clear(&mOggState); |
michael@0 | 113 | MOZ_COUNT_DTOR(OggReader); |
michael@0 | 114 | } |
michael@0 | 115 | |
michael@0 | 116 | nsresult OggReader::Init(MediaDecoderReader* aCloneDonor) { |
michael@0 | 117 | int ret = ogg_sync_init(&mOggState); |
michael@0 | 118 | NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE); |
michael@0 | 119 | return NS_OK; |
michael@0 | 120 | } |
michael@0 | 121 | |
michael@0 | 122 | nsresult OggReader::ResetDecode() |
michael@0 | 123 | { |
michael@0 | 124 | return ResetDecode(false); |
michael@0 | 125 | } |
michael@0 | 126 | |
michael@0 | 127 | nsresult OggReader::ResetDecode(bool start) |
michael@0 | 128 | { |
michael@0 | 129 | NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
michael@0 | 130 | nsresult res = NS_OK; |
michael@0 | 131 | |
michael@0 | 132 | if (NS_FAILED(MediaDecoderReader::ResetDecode())) { |
michael@0 | 133 | res = NS_ERROR_FAILURE; |
michael@0 | 134 | } |
michael@0 | 135 | |
michael@0 | 136 | // Discard any previously buffered packets/pages. |
michael@0 | 137 | ogg_sync_reset(&mOggState); |
michael@0 | 138 | if (mVorbisState && NS_FAILED(mVorbisState->Reset())) { |
michael@0 | 139 | res = NS_ERROR_FAILURE; |
michael@0 | 140 | } |
michael@0 | 141 | #ifdef MOZ_OPUS |
michael@0 | 142 | if (mOpusState && NS_FAILED(mOpusState->Reset(start))) { |
michael@0 | 143 | res = NS_ERROR_FAILURE; |
michael@0 | 144 | } |
michael@0 | 145 | #endif /* MOZ_OPUS */ |
michael@0 | 146 | if (mTheoraState && NS_FAILED(mTheoraState->Reset())) { |
michael@0 | 147 | res = NS_ERROR_FAILURE; |
michael@0 | 148 | } |
michael@0 | 149 | |
michael@0 | 150 | return res; |
michael@0 | 151 | } |
michael@0 | 152 | |
michael@0 | 153 | bool OggReader::ReadHeaders(OggCodecState* aState) |
michael@0 | 154 | { |
michael@0 | 155 | while (!aState->DoneReadingHeaders()) { |
michael@0 | 156 | ogg_packet* packet = NextOggPacket(aState); |
michael@0 | 157 | // DecodeHeader is responsible for releasing packet. |
michael@0 | 158 | if (!packet || !aState->DecodeHeader(packet)) { |
michael@0 | 159 | aState->Deactivate(); |
michael@0 | 160 | return false; |
michael@0 | 161 | } |
michael@0 | 162 | } |
michael@0 | 163 | return aState->Init(); |
michael@0 | 164 | } |
michael@0 | 165 | |
michael@0 | 166 | void OggReader::BuildSerialList(nsTArray<uint32_t>& aTracks) |
michael@0 | 167 | { |
michael@0 | 168 | if (HasVideo()) { |
michael@0 | 169 | aTracks.AppendElement(mTheoraState->mSerial); |
michael@0 | 170 | } |
michael@0 | 171 | if (HasAudio()) { |
michael@0 | 172 | if (mVorbisState) { |
michael@0 | 173 | aTracks.AppendElement(mVorbisState->mSerial); |
michael@0 | 174 | #ifdef MOZ_OPUS |
michael@0 | 175 | } else if (mOpusState) { |
michael@0 | 176 | aTracks.AppendElement(mOpusState->mSerial); |
michael@0 | 177 | #endif /* MOZ_OPUS */ |
michael@0 | 178 | } |
michael@0 | 179 | } |
michael@0 | 180 | } |
michael@0 | 181 | |
michael@0 | 182 | nsresult OggReader::ReadMetadata(MediaInfo* aInfo, |
michael@0 | 183 | MetadataTags** aTags) |
michael@0 | 184 | { |
michael@0 | 185 | NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
michael@0 | 186 | |
michael@0 | 187 | // We read packets until all bitstreams have read all their header packets. |
michael@0 | 188 | // We record the offset of the first non-header page so that we know |
michael@0 | 189 | // what page to seek to when seeking to the media start. |
michael@0 | 190 | |
michael@0 | 191 | NS_ASSERTION(aTags, "Called with null MetadataTags**."); |
michael@0 | 192 | *aTags = nullptr; |
michael@0 | 193 | |
michael@0 | 194 | ogg_page page; |
michael@0 | 195 | nsAutoTArray<OggCodecState*,4> bitstreams; |
michael@0 | 196 | bool readAllBOS = false; |
michael@0 | 197 | while (!readAllBOS) { |
michael@0 | 198 | if (!ReadOggPage(&page)) { |
michael@0 | 199 | // Some kind of error... |
michael@0 | 200 | break; |
michael@0 | 201 | } |
michael@0 | 202 | |
michael@0 | 203 | int serial = ogg_page_serialno(&page); |
michael@0 | 204 | OggCodecState* codecState = 0; |
michael@0 | 205 | |
michael@0 | 206 | if (!ogg_page_bos(&page)) { |
michael@0 | 207 | // We've encountered a non Beginning Of Stream page. No more BOS pages |
michael@0 | 208 | // can follow in this Ogg segment, so there will be no other bitstreams |
michael@0 | 209 | // in the Ogg (unless it's invalid). |
michael@0 | 210 | readAllBOS = true; |
michael@0 | 211 | } else if (!mCodecStore.Contains(serial)) { |
michael@0 | 212 | // We've not encountered a stream with this serial number before. Create |
michael@0 | 213 | // an OggCodecState to demux it, and map that to the OggCodecState |
michael@0 | 214 | // in mCodecStates. |
michael@0 | 215 | codecState = OggCodecState::Create(&page); |
michael@0 | 216 | mCodecStore.Add(serial, codecState); |
michael@0 | 217 | bitstreams.AppendElement(codecState); |
michael@0 | 218 | if (codecState && |
michael@0 | 219 | codecState->GetType() == OggCodecState::TYPE_VORBIS && |
michael@0 | 220 | !mVorbisState) |
michael@0 | 221 | { |
michael@0 | 222 | // First Vorbis bitstream, we'll play this one. Subsequent Vorbis |
michael@0 | 223 | // bitstreams will be ignored. |
michael@0 | 224 | mVorbisState = static_cast<VorbisState*>(codecState); |
michael@0 | 225 | } |
michael@0 | 226 | if (codecState && |
michael@0 | 227 | codecState->GetType() == OggCodecState::TYPE_THEORA && |
michael@0 | 228 | !mTheoraState) |
michael@0 | 229 | { |
michael@0 | 230 | // First Theora bitstream, we'll play this one. Subsequent Theora |
michael@0 | 231 | // bitstreams will be ignored. |
michael@0 | 232 | mTheoraState = static_cast<TheoraState*>(codecState); |
michael@0 | 233 | } |
michael@0 | 234 | #ifdef MOZ_OPUS |
michael@0 | 235 | if (codecState && |
michael@0 | 236 | codecState->GetType() == OggCodecState::TYPE_OPUS && |
michael@0 | 237 | !mOpusState) |
michael@0 | 238 | { |
michael@0 | 239 | if (mOpusEnabled) { |
michael@0 | 240 | mOpusState = static_cast<OpusState*>(codecState); |
michael@0 | 241 | } else { |
michael@0 | 242 | NS_WARNING("Opus decoding disabled." |
michael@0 | 243 | " See media.opus.enabled in about:config"); |
michael@0 | 244 | } |
michael@0 | 245 | } |
michael@0 | 246 | #endif /* MOZ_OPUS */ |
michael@0 | 247 | if (codecState && |
michael@0 | 248 | codecState->GetType() == OggCodecState::TYPE_SKELETON && |
michael@0 | 249 | !mSkeletonState) |
michael@0 | 250 | { |
michael@0 | 251 | mSkeletonState = static_cast<SkeletonState*>(codecState); |
michael@0 | 252 | } |
michael@0 | 253 | } |
michael@0 | 254 | |
michael@0 | 255 | codecState = mCodecStore.Get(serial); |
michael@0 | 256 | NS_ENSURE_TRUE(codecState != nullptr, NS_ERROR_FAILURE); |
michael@0 | 257 | |
michael@0 | 258 | if (NS_FAILED(codecState->PageIn(&page))) { |
michael@0 | 259 | return NS_ERROR_FAILURE; |
michael@0 | 260 | } |
michael@0 | 261 | } |
michael@0 | 262 | |
michael@0 | 263 | // We've read all BOS pages, so we know the streams contained in the media. |
michael@0 | 264 | // Now process all available header packets in the active Theora, Vorbis and |
michael@0 | 265 | // Skeleton streams. |
michael@0 | 266 | |
michael@0 | 267 | // Deactivate any non-primary bitstreams. |
michael@0 | 268 | for (uint32_t i = 0; i < bitstreams.Length(); i++) { |
michael@0 | 269 | OggCodecState* s = bitstreams[i]; |
michael@0 | 270 | if (s != mVorbisState && |
michael@0 | 271 | #ifdef MOZ_OPUS |
michael@0 | 272 | s != mOpusState && |
michael@0 | 273 | #endif /* MOZ_OPUS */ |
michael@0 | 274 | s != mTheoraState && s != mSkeletonState) { |
michael@0 | 275 | s->Deactivate(); |
michael@0 | 276 | } |
michael@0 | 277 | } |
michael@0 | 278 | |
michael@0 | 279 | if (mTheoraState && ReadHeaders(mTheoraState)) { |
michael@0 | 280 | nsIntRect picture = nsIntRect(mTheoraState->mInfo.pic_x, |
michael@0 | 281 | mTheoraState->mInfo.pic_y, |
michael@0 | 282 | mTheoraState->mInfo.pic_width, |
michael@0 | 283 | mTheoraState->mInfo.pic_height); |
michael@0 | 284 | |
michael@0 | 285 | nsIntSize displaySize = nsIntSize(mTheoraState->mInfo.pic_width, |
michael@0 | 286 | mTheoraState->mInfo.pic_height); |
michael@0 | 287 | |
michael@0 | 288 | // Apply the aspect ratio to produce the intrinsic display size we report |
michael@0 | 289 | // to the element. |
michael@0 | 290 | ScaleDisplayByAspectRatio(displaySize, mTheoraState->mPixelAspectRatio); |
michael@0 | 291 | |
michael@0 | 292 | nsIntSize frameSize(mTheoraState->mInfo.frame_width, |
michael@0 | 293 | mTheoraState->mInfo.frame_height); |
michael@0 | 294 | if (IsValidVideoRegion(frameSize, picture, displaySize)) { |
michael@0 | 295 | // Video track's frame sizes will not overflow. Activate the video track. |
michael@0 | 296 | mInfo.mVideo.mHasVideo = true; |
michael@0 | 297 | mInfo.mVideo.mDisplay = displaySize; |
michael@0 | 298 | mPicture = picture; |
michael@0 | 299 | |
michael@0 | 300 | VideoFrameContainer* container = mDecoder->GetVideoFrameContainer(); |
michael@0 | 301 | if (container) { |
michael@0 | 302 | container->SetCurrentFrame(gfxIntSize(displaySize.width, displaySize.height), |
michael@0 | 303 | nullptr, |
michael@0 | 304 | TimeStamp::Now()); |
michael@0 | 305 | } |
michael@0 | 306 | |
michael@0 | 307 | // Copy Theora info data for time computations on other threads. |
michael@0 | 308 | memcpy(&mTheoraInfo, &mTheoraState->mInfo, sizeof(mTheoraInfo)); |
michael@0 | 309 | mTheoraSerial = mTheoraState->mSerial; |
michael@0 | 310 | } |
michael@0 | 311 | } |
michael@0 | 312 | |
michael@0 | 313 | if (mVorbisState && ReadHeaders(mVorbisState)) { |
michael@0 | 314 | mInfo.mAudio.mHasAudio = true; |
michael@0 | 315 | mInfo.mAudio.mRate = mVorbisState->mInfo.rate; |
michael@0 | 316 | mInfo.mAudio.mChannels = mVorbisState->mInfo.channels; |
michael@0 | 317 | // Copy Vorbis info data for time computations on other threads. |
michael@0 | 318 | memcpy(&mVorbisInfo, &mVorbisState->mInfo, sizeof(mVorbisInfo)); |
michael@0 | 319 | mVorbisInfo.codec_setup = nullptr; |
michael@0 | 320 | mVorbisSerial = mVorbisState->mSerial; |
michael@0 | 321 | *aTags = mVorbisState->GetTags(); |
michael@0 | 322 | } else { |
michael@0 | 323 | memset(&mVorbisInfo, 0, sizeof(mVorbisInfo)); |
michael@0 | 324 | } |
michael@0 | 325 | #ifdef MOZ_OPUS |
michael@0 | 326 | if (mOpusState && ReadHeaders(mOpusState)) { |
michael@0 | 327 | mInfo.mAudio.mHasAudio = true; |
michael@0 | 328 | mInfo.mAudio.mRate = mOpusState->mRate; |
michael@0 | 329 | mInfo.mAudio.mChannels = mOpusState->mChannels; |
michael@0 | 330 | mOpusSerial = mOpusState->mSerial; |
michael@0 | 331 | mOpusPreSkip = mOpusState->mPreSkip; |
michael@0 | 332 | |
michael@0 | 333 | *aTags = mOpusState->GetTags(); |
michael@0 | 334 | } |
michael@0 | 335 | #endif |
michael@0 | 336 | if (mSkeletonState) { |
michael@0 | 337 | if (!HasAudio() && !HasVideo()) { |
michael@0 | 338 | // We have a skeleton track, but no audio or video, may as well disable |
michael@0 | 339 | // the skeleton, we can't do anything useful with this media. |
michael@0 | 340 | mSkeletonState->Deactivate(); |
michael@0 | 341 | } else if (ReadHeaders(mSkeletonState) && mSkeletonState->HasIndex()) { |
michael@0 | 342 | // Extract the duration info out of the index, so we don't need to seek to |
michael@0 | 343 | // the end of resource to get it. |
michael@0 | 344 | nsAutoTArray<uint32_t, 2> tracks; |
michael@0 | 345 | BuildSerialList(tracks); |
michael@0 | 346 | int64_t duration = 0; |
michael@0 | 347 | if (NS_SUCCEEDED(mSkeletonState->GetDuration(tracks, duration))) { |
michael@0 | 348 | ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); |
michael@0 | 349 | mDecoder->SetMediaDuration(duration); |
michael@0 | 350 | LOG(PR_LOG_DEBUG, ("Got duration from Skeleton index %lld", duration)); |
michael@0 | 351 | } |
michael@0 | 352 | } |
michael@0 | 353 | } |
michael@0 | 354 | |
michael@0 | 355 | if (HasAudio() || HasVideo()) { |
michael@0 | 356 | ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); |
michael@0 | 357 | |
michael@0 | 358 | MediaResource* resource = mDecoder->GetResource(); |
michael@0 | 359 | if (mDecoder->GetMediaDuration() == -1 && |
michael@0 | 360 | !mDecoder->IsShutdown() && |
michael@0 | 361 | resource->GetLength() >= 0 && |
michael@0 | 362 | mDecoder->IsMediaSeekable()) |
michael@0 | 363 | { |
michael@0 | 364 | // We didn't get a duration from the index or a Content-Duration header. |
michael@0 | 365 | // Seek to the end of file to find the end time. |
michael@0 | 366 | mDecoder->GetResource()->StartSeekingForMetadata(); |
michael@0 | 367 | int64_t length = resource->GetLength(); |
michael@0 | 368 | |
michael@0 | 369 | NS_ASSERTION(length > 0, "Must have a content length to get end time"); |
michael@0 | 370 | |
michael@0 | 371 | int64_t endTime = 0; |
michael@0 | 372 | { |
michael@0 | 373 | ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor()); |
michael@0 | 374 | endTime = RangeEndTime(length); |
michael@0 | 375 | } |
michael@0 | 376 | if (endTime != -1) { |
michael@0 | 377 | mDecoder->SetMediaEndTime(endTime); |
michael@0 | 378 | LOG(PR_LOG_DEBUG, ("Got Ogg duration from seeking to end %lld", endTime)); |
michael@0 | 379 | } |
michael@0 | 380 | mDecoder->GetResource()->EndSeekingForMetadata(); |
michael@0 | 381 | } else if (mDecoder->GetMediaDuration() == -1) { |
michael@0 | 382 | // We don't have a duration, and we don't know enough about the resource |
michael@0 | 383 | // to try a seek. Abort trying to get a duration. This happens for example |
michael@0 | 384 | // when the server says it accepts range requests, but does not give us a |
michael@0 | 385 | // Content-Length. |
michael@0 | 386 | mDecoder->SetTransportSeekable(false); |
michael@0 | 387 | } |
michael@0 | 388 | } else { |
michael@0 | 389 | return NS_ERROR_FAILURE; |
michael@0 | 390 | } |
michael@0 | 391 | *aInfo = mInfo; |
michael@0 | 392 | |
michael@0 | 393 | return NS_OK; |
michael@0 | 394 | } |
michael@0 | 395 | |
michael@0 | 396 | nsresult OggReader::DecodeVorbis(ogg_packet* aPacket) { |
michael@0 | 397 | NS_ASSERTION(aPacket->granulepos != -1, "Must know vorbis granulepos!"); |
michael@0 | 398 | |
michael@0 | 399 | if (vorbis_synthesis(&mVorbisState->mBlock, aPacket) != 0) { |
michael@0 | 400 | return NS_ERROR_FAILURE; |
michael@0 | 401 | } |
michael@0 | 402 | if (vorbis_synthesis_blockin(&mVorbisState->mDsp, |
michael@0 | 403 | &mVorbisState->mBlock) != 0) |
michael@0 | 404 | { |
michael@0 | 405 | return NS_ERROR_FAILURE; |
michael@0 | 406 | } |
michael@0 | 407 | |
michael@0 | 408 | VorbisPCMValue** pcm = 0; |
michael@0 | 409 | int32_t frames = 0; |
michael@0 | 410 | uint32_t channels = mVorbisState->mInfo.channels; |
michael@0 | 411 | ogg_int64_t endFrame = aPacket->granulepos; |
michael@0 | 412 | while ((frames = vorbis_synthesis_pcmout(&mVorbisState->mDsp, &pcm)) > 0) { |
michael@0 | 413 | mVorbisState->ValidateVorbisPacketSamples(aPacket, frames); |
michael@0 | 414 | nsAutoArrayPtr<AudioDataValue> buffer(new AudioDataValue[frames * channels]); |
michael@0 | 415 | for (uint32_t j = 0; j < channels; ++j) { |
michael@0 | 416 | VorbisPCMValue* channel = pcm[j]; |
michael@0 | 417 | for (uint32_t i = 0; i < uint32_t(frames); ++i) { |
michael@0 | 418 | buffer[i*channels + j] = MOZ_CONVERT_VORBIS_SAMPLE(channel[i]); |
michael@0 | 419 | } |
michael@0 | 420 | } |
michael@0 | 421 | |
michael@0 | 422 | // No channel mapping for more than 8 channels. |
michael@0 | 423 | if (channels > 8) { |
michael@0 | 424 | return NS_ERROR_FAILURE; |
michael@0 | 425 | } |
michael@0 | 426 | |
michael@0 | 427 | int64_t duration = mVorbisState->Time((int64_t)frames); |
michael@0 | 428 | int64_t startTime = mVorbisState->Time(endFrame - frames); |
michael@0 | 429 | mAudioQueue.Push(new AudioData(mDecoder->GetResource()->Tell(), |
michael@0 | 430 | startTime, |
michael@0 | 431 | duration, |
michael@0 | 432 | frames, |
michael@0 | 433 | buffer.forget(), |
michael@0 | 434 | channels)); |
michael@0 | 435 | |
michael@0 | 436 | mDecodedAudioFrames += frames; |
michael@0 | 437 | |
michael@0 | 438 | endFrame -= frames; |
michael@0 | 439 | if (vorbis_synthesis_read(&mVorbisState->mDsp, frames) != 0) { |
michael@0 | 440 | return NS_ERROR_FAILURE; |
michael@0 | 441 | } |
michael@0 | 442 | } |
michael@0 | 443 | return NS_OK; |
michael@0 | 444 | } |
michael@0 | 445 | #ifdef MOZ_OPUS |
michael@0 | 446 | nsresult OggReader::DecodeOpus(ogg_packet* aPacket) { |
michael@0 | 447 | NS_ASSERTION(aPacket->granulepos != -1, "Must know opus granulepos!"); |
michael@0 | 448 | |
michael@0 | 449 | // Maximum value is 63*2880, so there's no chance of overflow. |
michael@0 | 450 | int32_t frames_number = opus_packet_get_nb_frames(aPacket->packet, |
michael@0 | 451 | aPacket->bytes); |
michael@0 | 452 | if (frames_number <= 0) |
michael@0 | 453 | return NS_ERROR_FAILURE; // Invalid packet header. |
michael@0 | 454 | int32_t samples = opus_packet_get_samples_per_frame(aPacket->packet, |
michael@0 | 455 | (opus_int32) mOpusState->mRate); |
michael@0 | 456 | int32_t frames = frames_number*samples; |
michael@0 | 457 | |
michael@0 | 458 | // A valid Opus packet must be between 2.5 and 120 ms long. |
michael@0 | 459 | if (frames < 120 || frames > 5760) |
michael@0 | 460 | return NS_ERROR_FAILURE; |
michael@0 | 461 | uint32_t channels = mOpusState->mChannels; |
michael@0 | 462 | nsAutoArrayPtr<AudioDataValue> buffer(new AudioDataValue[frames * channels]); |
michael@0 | 463 | |
michael@0 | 464 | // Decode to the appropriate sample type. |
michael@0 | 465 | #ifdef MOZ_SAMPLE_TYPE_FLOAT32 |
michael@0 | 466 | int ret = opus_multistream_decode_float(mOpusState->mDecoder, |
michael@0 | 467 | aPacket->packet, aPacket->bytes, |
michael@0 | 468 | buffer, frames, false); |
michael@0 | 469 | #else |
michael@0 | 470 | int ret = opus_multistream_decode(mOpusState->mDecoder, |
michael@0 | 471 | aPacket->packet, aPacket->bytes, |
michael@0 | 472 | buffer, frames, false); |
michael@0 | 473 | #endif |
michael@0 | 474 | if (ret < 0) |
michael@0 | 475 | return NS_ERROR_FAILURE; |
michael@0 | 476 | NS_ASSERTION(ret == frames, "Opus decoded too few audio samples"); |
michael@0 | 477 | |
michael@0 | 478 | int64_t endFrame = aPacket->granulepos; |
michael@0 | 479 | int64_t startFrame; |
michael@0 | 480 | // If this is the last packet, perform end trimming. |
michael@0 | 481 | if (aPacket->e_o_s && mOpusState->mPrevPacketGranulepos != -1) { |
michael@0 | 482 | startFrame = mOpusState->mPrevPacketGranulepos; |
michael@0 | 483 | frames = static_cast<int32_t>(std::max(static_cast<int64_t>(0), |
michael@0 | 484 | std::min(endFrame - startFrame, |
michael@0 | 485 | static_cast<int64_t>(frames)))); |
michael@0 | 486 | } else { |
michael@0 | 487 | startFrame = endFrame - frames; |
michael@0 | 488 | } |
michael@0 | 489 | |
michael@0 | 490 | // Trim the initial frames while the decoder is settling. |
michael@0 | 491 | if (mOpusState->mSkip > 0) { |
michael@0 | 492 | int32_t skipFrames = std::min(mOpusState->mSkip, frames); |
michael@0 | 493 | if (skipFrames == frames) { |
michael@0 | 494 | // discard the whole packet |
michael@0 | 495 | mOpusState->mSkip -= frames; |
michael@0 | 496 | LOG(PR_LOG_DEBUG, ("Opus decoder skipping %d frames" |
michael@0 | 497 | " (whole packet)", frames)); |
michael@0 | 498 | return NS_OK; |
michael@0 | 499 | } |
michael@0 | 500 | int32_t keepFrames = frames - skipFrames; |
michael@0 | 501 | int samples = keepFrames * channels; |
michael@0 | 502 | nsAutoArrayPtr<AudioDataValue> trimBuffer(new AudioDataValue[samples]); |
michael@0 | 503 | for (int i = 0; i < samples; i++) |
michael@0 | 504 | trimBuffer[i] = buffer[skipFrames*channels + i]; |
michael@0 | 505 | |
michael@0 | 506 | startFrame = endFrame - keepFrames; |
michael@0 | 507 | frames = keepFrames; |
michael@0 | 508 | buffer = trimBuffer; |
michael@0 | 509 | |
michael@0 | 510 | mOpusState->mSkip -= skipFrames; |
michael@0 | 511 | LOG(PR_LOG_DEBUG, ("Opus decoder skipping %d frames", skipFrames)); |
michael@0 | 512 | } |
michael@0 | 513 | // Save this packet's granule position in case we need to perform end |
michael@0 | 514 | // trimming on the next packet. |
michael@0 | 515 | mOpusState->mPrevPacketGranulepos = endFrame; |
michael@0 | 516 | |
michael@0 | 517 | // Apply the header gain if one was specified. |
michael@0 | 518 | #ifdef MOZ_SAMPLE_TYPE_FLOAT32 |
michael@0 | 519 | if (mOpusState->mGain != 1.0f) { |
michael@0 | 520 | float gain = mOpusState->mGain; |
michael@0 | 521 | int samples = frames * channels; |
michael@0 | 522 | for (int i = 0; i < samples; i++) { |
michael@0 | 523 | buffer[i] *= gain; |
michael@0 | 524 | } |
michael@0 | 525 | } |
michael@0 | 526 | #else |
michael@0 | 527 | if (mOpusState->mGain_Q16 != 65536) { |
michael@0 | 528 | int64_t gain_Q16 = mOpusState->mGain_Q16; |
michael@0 | 529 | int samples = frames * channels; |
michael@0 | 530 | for (int i = 0; i < samples; i++) { |
michael@0 | 531 | int32_t val = static_cast<int32_t>((gain_Q16*buffer[i] + 32768)>>16); |
michael@0 | 532 | buffer[i] = static_cast<AudioDataValue>(MOZ_CLIP_TO_15(val)); |
michael@0 | 533 | } |
michael@0 | 534 | } |
michael@0 | 535 | #endif |
michael@0 | 536 | |
michael@0 | 537 | // No channel mapping for more than 8 channels. |
michael@0 | 538 | if (channels > 8) { |
michael@0 | 539 | return NS_ERROR_FAILURE; |
michael@0 | 540 | } |
michael@0 | 541 | |
michael@0 | 542 | LOG(PR_LOG_DEBUG, ("Opus decoder pushing %d frames", frames)); |
michael@0 | 543 | int64_t startTime = mOpusState->Time(startFrame); |
michael@0 | 544 | int64_t endTime = mOpusState->Time(endFrame); |
michael@0 | 545 | mAudioQueue.Push(new AudioData(mDecoder->GetResource()->Tell(), |
michael@0 | 546 | startTime, |
michael@0 | 547 | endTime - startTime, |
michael@0 | 548 | frames, |
michael@0 | 549 | buffer.forget(), |
michael@0 | 550 | channels)); |
michael@0 | 551 | |
michael@0 | 552 | mDecodedAudioFrames += frames; |
michael@0 | 553 | |
michael@0 | 554 | return NS_OK; |
michael@0 | 555 | } |
michael@0 | 556 | #endif /* MOZ_OPUS */ |
michael@0 | 557 | |
michael@0 | 558 | bool OggReader::DecodeAudioData() |
michael@0 | 559 | { |
michael@0 | 560 | NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
michael@0 | 561 | DebugOnly<bool> haveCodecState = mVorbisState != nullptr |
michael@0 | 562 | #ifdef MOZ_OPUS |
michael@0 | 563 | || mOpusState != nullptr |
michael@0 | 564 | #endif /* MOZ_OPUS */ |
michael@0 | 565 | ; |
michael@0 | 566 | NS_ASSERTION(haveCodecState, "Need audio codec state to decode audio"); |
michael@0 | 567 | |
michael@0 | 568 | // Read the next data packet. Skip any non-data packets we encounter. |
michael@0 | 569 | ogg_packet* packet = 0; |
michael@0 | 570 | OggCodecState* codecState; |
michael@0 | 571 | if (mVorbisState) |
michael@0 | 572 | codecState = static_cast<OggCodecState*>(mVorbisState); |
michael@0 | 573 | #ifdef MOZ_OPUS |
michael@0 | 574 | else |
michael@0 | 575 | codecState = static_cast<OggCodecState*>(mOpusState); |
michael@0 | 576 | #endif /* MOZ_OPUS */ |
michael@0 | 577 | do { |
michael@0 | 578 | if (packet) { |
michael@0 | 579 | OggCodecState::ReleasePacket(packet); |
michael@0 | 580 | } |
michael@0 | 581 | packet = NextOggPacket(codecState); |
michael@0 | 582 | } while (packet && codecState->IsHeader(packet)); |
michael@0 | 583 | |
michael@0 | 584 | if (!packet) { |
michael@0 | 585 | return false; |
michael@0 | 586 | } |
michael@0 | 587 | |
michael@0 | 588 | NS_ASSERTION(packet && packet->granulepos != -1, |
michael@0 | 589 | "Must have packet with known granulepos"); |
michael@0 | 590 | nsAutoRef<ogg_packet> autoRelease(packet); |
michael@0 | 591 | if (mVorbisState) { |
michael@0 | 592 | DecodeVorbis(packet); |
michael@0 | 593 | #ifdef MOZ_OPUS |
michael@0 | 594 | } else if (mOpusState) { |
michael@0 | 595 | DecodeOpus(packet); |
michael@0 | 596 | #endif |
michael@0 | 597 | } |
michael@0 | 598 | |
michael@0 | 599 | if ((packet->e_o_s) && (!ReadOggChain())) { |
michael@0 | 600 | // We've encountered an end of bitstream packet, or we've hit the end of |
michael@0 | 601 | // file while trying to decode, so inform the audio queue that there'll |
michael@0 | 602 | // be no more samples. |
michael@0 | 603 | return false; |
michael@0 | 604 | } |
michael@0 | 605 | |
michael@0 | 606 | return true; |
michael@0 | 607 | } |
michael@0 | 608 | |
michael@0 | 609 | void OggReader::SetChained(bool aIsChained) { |
michael@0 | 610 | { |
michael@0 | 611 | ReentrantMonitorAutoEnter mon(mMonitor); |
michael@0 | 612 | mIsChained = aIsChained; |
michael@0 | 613 | } |
michael@0 | 614 | { |
michael@0 | 615 | ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); |
michael@0 | 616 | mDecoder->SetMediaSeekable(false); |
michael@0 | 617 | } |
michael@0 | 618 | } |
michael@0 | 619 | |
michael@0 | 620 | bool OggReader::ReadOggChain() |
michael@0 | 621 | { |
michael@0 | 622 | bool chained = false; |
michael@0 | 623 | #ifdef MOZ_OPUS |
michael@0 | 624 | OpusState* newOpusState = nullptr; |
michael@0 | 625 | #endif /* MOZ_OPUS */ |
michael@0 | 626 | VorbisState* newVorbisState = nullptr; |
michael@0 | 627 | int channels = 0; |
michael@0 | 628 | long rate = 0; |
michael@0 | 629 | MetadataTags* tags = nullptr; |
michael@0 | 630 | |
michael@0 | 631 | if (HasVideo() || HasSkeleton() || !HasAudio()) { |
michael@0 | 632 | return false; |
michael@0 | 633 | } |
michael@0 | 634 | |
michael@0 | 635 | ogg_page page; |
michael@0 | 636 | if (!ReadOggPage(&page) || !ogg_page_bos(&page)) { |
michael@0 | 637 | return false; |
michael@0 | 638 | } |
michael@0 | 639 | |
michael@0 | 640 | int serial = ogg_page_serialno(&page); |
michael@0 | 641 | if (mCodecStore.Contains(serial)) { |
michael@0 | 642 | return false; |
michael@0 | 643 | } |
michael@0 | 644 | |
michael@0 | 645 | nsAutoPtr<OggCodecState> codecState; |
michael@0 | 646 | codecState = OggCodecState::Create(&page); |
michael@0 | 647 | if (!codecState) { |
michael@0 | 648 | return false; |
michael@0 | 649 | } |
michael@0 | 650 | |
michael@0 | 651 | if (mVorbisState && (codecState->GetType() == OggCodecState::TYPE_VORBIS)) { |
michael@0 | 652 | newVorbisState = static_cast<VorbisState*>(codecState.get()); |
michael@0 | 653 | } |
michael@0 | 654 | #ifdef MOZ_OPUS |
michael@0 | 655 | else if (mOpusState && (codecState->GetType() == OggCodecState::TYPE_OPUS)) { |
michael@0 | 656 | newOpusState = static_cast<OpusState*>(codecState.get()); |
michael@0 | 657 | } |
michael@0 | 658 | #endif |
michael@0 | 659 | else { |
michael@0 | 660 | return false; |
michael@0 | 661 | } |
michael@0 | 662 | OggCodecState* state; |
michael@0 | 663 | |
michael@0 | 664 | mCodecStore.Add(serial, codecState.forget()); |
michael@0 | 665 | state = mCodecStore.Get(serial); |
michael@0 | 666 | |
michael@0 | 667 | NS_ENSURE_TRUE(state != nullptr, false); |
michael@0 | 668 | |
michael@0 | 669 | if (NS_FAILED(state->PageIn(&page))) { |
michael@0 | 670 | return false; |
michael@0 | 671 | } |
michael@0 | 672 | |
michael@0 | 673 | if ((newVorbisState && ReadHeaders(newVorbisState)) && |
michael@0 | 674 | (mVorbisState->mInfo.rate == newVorbisState->mInfo.rate) && |
michael@0 | 675 | (mVorbisState->mInfo.channels == newVorbisState->mInfo.channels)) { |
michael@0 | 676 | mVorbisState->Reset(); |
michael@0 | 677 | mVorbisState = newVorbisState; |
michael@0 | 678 | mVorbisSerial = mVorbisState->mSerial; |
michael@0 | 679 | LOG(PR_LOG_DEBUG, ("New vorbis ogg link, serial=%d\n", mVorbisSerial)); |
michael@0 | 680 | chained = true; |
michael@0 | 681 | rate = mVorbisState->mInfo.rate; |
michael@0 | 682 | channels = mVorbisState->mInfo.channels; |
michael@0 | 683 | tags = mVorbisState->GetTags(); |
michael@0 | 684 | } |
michael@0 | 685 | |
michael@0 | 686 | #ifdef MOZ_OPUS |
michael@0 | 687 | if ((newOpusState && ReadHeaders(newOpusState)) && |
michael@0 | 688 | (mOpusState->mRate == newOpusState->mRate) && |
michael@0 | 689 | (mOpusState->mChannels == newOpusState->mChannels)) { |
michael@0 | 690 | mOpusState->Reset(); |
michael@0 | 691 | mOpusState = newOpusState; |
michael@0 | 692 | mOpusSerial = mOpusState->mSerial; |
michael@0 | 693 | chained = true; |
michael@0 | 694 | rate = mOpusState->mRate; |
michael@0 | 695 | channels = mOpusState->mChannels; |
michael@0 | 696 | tags = mOpusState->GetTags(); |
michael@0 | 697 | } |
michael@0 | 698 | #endif |
michael@0 | 699 | |
michael@0 | 700 | if (chained) { |
michael@0 | 701 | SetChained(true); |
michael@0 | 702 | { |
michael@0 | 703 | ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); |
michael@0 | 704 | mDecoder->QueueMetadata((mDecodedAudioFrames * USECS_PER_S) / rate, |
michael@0 | 705 | channels, |
michael@0 | 706 | rate, |
michael@0 | 707 | HasAudio(), |
michael@0 | 708 | HasVideo(), |
michael@0 | 709 | tags); |
michael@0 | 710 | } |
michael@0 | 711 | return true; |
michael@0 | 712 | } |
michael@0 | 713 | |
michael@0 | 714 | return false; |
michael@0 | 715 | } |
michael@0 | 716 | |
michael@0 | 717 | nsresult OggReader::DecodeTheora(ogg_packet* aPacket, int64_t aTimeThreshold) |
michael@0 | 718 | { |
michael@0 | 719 | NS_ASSERTION(aPacket->granulepos >= TheoraVersion(&mTheoraState->mInfo,3,2,1), |
michael@0 | 720 | "Packets must have valid granulepos and packetno"); |
michael@0 | 721 | |
michael@0 | 722 | int ret = th_decode_packetin(mTheoraState->mCtx, aPacket, 0); |
michael@0 | 723 | if (ret != 0 && ret != TH_DUPFRAME) { |
michael@0 | 724 | return NS_ERROR_FAILURE; |
michael@0 | 725 | } |
michael@0 | 726 | int64_t time = mTheoraState->StartTime(aPacket->granulepos); |
michael@0 | 727 | |
michael@0 | 728 | // Don't use the frame if it's outside the bounds of the presentation |
michael@0 | 729 | // start time in the skeleton track. Note we still must submit the frame |
michael@0 | 730 | // to the decoder (via th_decode_packetin), as the frames which are |
michael@0 | 731 | // presentable may depend on this frame's data. |
michael@0 | 732 | if (mSkeletonState && !mSkeletonState->IsPresentable(time)) { |
michael@0 | 733 | return NS_OK; |
michael@0 | 734 | } |
michael@0 | 735 | |
michael@0 | 736 | int64_t endTime = mTheoraState->Time(aPacket->granulepos); |
michael@0 | 737 | if (endTime < aTimeThreshold) { |
michael@0 | 738 | // The end time of this frame is already before the current playback |
michael@0 | 739 | // position. It will never be displayed, don't bother enqueing it. |
michael@0 | 740 | return NS_OK; |
michael@0 | 741 | } |
michael@0 | 742 | |
michael@0 | 743 | if (ret == TH_DUPFRAME) { |
michael@0 | 744 | VideoData* v = VideoData::CreateDuplicate(mDecoder->GetResource()->Tell(), |
michael@0 | 745 | time, |
michael@0 | 746 | endTime - time, |
michael@0 | 747 | aPacket->granulepos); |
michael@0 | 748 | mVideoQueue.Push(v); |
michael@0 | 749 | } else if (ret == 0) { |
michael@0 | 750 | th_ycbcr_buffer buffer; |
michael@0 | 751 | ret = th_decode_ycbcr_out(mTheoraState->mCtx, buffer); |
michael@0 | 752 | NS_ASSERTION(ret == 0, "th_decode_ycbcr_out failed"); |
michael@0 | 753 | bool isKeyframe = th_packet_iskeyframe(aPacket) == 1; |
michael@0 | 754 | VideoData::YCbCrBuffer b; |
michael@0 | 755 | for (uint32_t i=0; i < 3; ++i) { |
michael@0 | 756 | b.mPlanes[i].mData = buffer[i].data; |
michael@0 | 757 | b.mPlanes[i].mHeight = buffer[i].height; |
michael@0 | 758 | b.mPlanes[i].mWidth = buffer[i].width; |
michael@0 | 759 | b.mPlanes[i].mStride = buffer[i].stride; |
michael@0 | 760 | b.mPlanes[i].mOffset = b.mPlanes[i].mSkip = 0; |
michael@0 | 761 | } |
michael@0 | 762 | |
michael@0 | 763 | VideoData *v = VideoData::Create(mInfo.mVideo, |
michael@0 | 764 | mDecoder->GetImageContainer(), |
michael@0 | 765 | mDecoder->GetResource()->Tell(), |
michael@0 | 766 | time, |
michael@0 | 767 | endTime - time, |
michael@0 | 768 | b, |
michael@0 | 769 | isKeyframe, |
michael@0 | 770 | aPacket->granulepos, |
michael@0 | 771 | ToIntRect(mPicture)); |
michael@0 | 772 | if (!v) { |
michael@0 | 773 | // There may be other reasons for this error, but for |
michael@0 | 774 | // simplicity just assume the worst case: out of memory. |
michael@0 | 775 | NS_WARNING("Failed to allocate memory for video frame"); |
michael@0 | 776 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 777 | } |
michael@0 | 778 | mVideoQueue.Push(v); |
michael@0 | 779 | } |
michael@0 | 780 | return NS_OK; |
michael@0 | 781 | } |
michael@0 | 782 | |
michael@0 | 783 | bool OggReader::DecodeVideoFrame(bool &aKeyframeSkip, |
michael@0 | 784 | int64_t aTimeThreshold) |
michael@0 | 785 | { |
michael@0 | 786 | NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
michael@0 | 787 | |
michael@0 | 788 | // Record number of frames decoded and parsed. Automatically update the |
michael@0 | 789 | // stats counters using the AutoNotifyDecoded stack-based class. |
michael@0 | 790 | uint32_t parsed = 0, decoded = 0; |
michael@0 | 791 | AbstractMediaDecoder::AutoNotifyDecoded autoNotify(mDecoder, parsed, decoded); |
michael@0 | 792 | |
michael@0 | 793 | // Read the next data packet. Skip any non-data packets we encounter. |
michael@0 | 794 | ogg_packet* packet = 0; |
michael@0 | 795 | do { |
michael@0 | 796 | if (packet) { |
michael@0 | 797 | OggCodecState::ReleasePacket(packet); |
michael@0 | 798 | } |
michael@0 | 799 | packet = NextOggPacket(mTheoraState); |
michael@0 | 800 | } while (packet && mTheoraState->IsHeader(packet)); |
michael@0 | 801 | if (!packet) { |
michael@0 | 802 | return false; |
michael@0 | 803 | } |
michael@0 | 804 | nsAutoRef<ogg_packet> autoRelease(packet); |
michael@0 | 805 | |
michael@0 | 806 | parsed++; |
michael@0 | 807 | NS_ASSERTION(packet && packet->granulepos != -1, |
michael@0 | 808 | "Must know first packet's granulepos"); |
michael@0 | 809 | bool eos = packet->e_o_s; |
michael@0 | 810 | int64_t frameEndTime = mTheoraState->Time(packet->granulepos); |
michael@0 | 811 | if (!aKeyframeSkip || |
michael@0 | 812 | (th_packet_iskeyframe(packet) && frameEndTime >= aTimeThreshold)) |
michael@0 | 813 | { |
michael@0 | 814 | aKeyframeSkip = false; |
michael@0 | 815 | nsresult res = DecodeTheora(packet, aTimeThreshold); |
michael@0 | 816 | decoded++; |
michael@0 | 817 | if (NS_FAILED(res)) { |
michael@0 | 818 | return false; |
michael@0 | 819 | } |
michael@0 | 820 | } |
michael@0 | 821 | |
michael@0 | 822 | if (eos) { |
michael@0 | 823 | // We've encountered an end of bitstream packet. Inform the queue that |
michael@0 | 824 | // there will be no more frames. |
michael@0 | 825 | return false; |
michael@0 | 826 | } |
michael@0 | 827 | |
michael@0 | 828 | return true; |
michael@0 | 829 | } |
michael@0 | 830 | |
michael@0 | 831 | bool OggReader::ReadOggPage(ogg_page* aPage) |
michael@0 | 832 | { |
michael@0 | 833 | NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
michael@0 | 834 | |
michael@0 | 835 | int ret = 0; |
michael@0 | 836 | while((ret = ogg_sync_pageseek(&mOggState, aPage)) <= 0) { |
michael@0 | 837 | if (ret < 0) { |
michael@0 | 838 | // Lost page sync, have to skip up to next page. |
michael@0 | 839 | continue; |
michael@0 | 840 | } |
michael@0 | 841 | // Returns a buffer that can be written too |
michael@0 | 842 | // with the given size. This buffer is stored |
michael@0 | 843 | // in the ogg synchronisation structure. |
michael@0 | 844 | char* buffer = ogg_sync_buffer(&mOggState, 4096); |
michael@0 | 845 | NS_ASSERTION(buffer, "ogg_sync_buffer failed"); |
michael@0 | 846 | |
michael@0 | 847 | // Read from the resource into the buffer |
michael@0 | 848 | uint32_t bytesRead = 0; |
michael@0 | 849 | |
michael@0 | 850 | nsresult rv = mDecoder->GetResource()->Read(buffer, 4096, &bytesRead); |
michael@0 | 851 | if (NS_FAILED(rv) || (bytesRead == 0 && ret == 0)) { |
michael@0 | 852 | // End of file. |
michael@0 | 853 | return false; |
michael@0 | 854 | } |
michael@0 | 855 | |
michael@0 | 856 | // Update the synchronisation layer with the number |
michael@0 | 857 | // of bytes written to the buffer |
michael@0 | 858 | ret = ogg_sync_wrote(&mOggState, bytesRead); |
michael@0 | 859 | NS_ENSURE_TRUE(ret == 0, false); |
michael@0 | 860 | } |
michael@0 | 861 | |
michael@0 | 862 | return true; |
michael@0 | 863 | } |
michael@0 | 864 | |
michael@0 | 865 | ogg_packet* OggReader::NextOggPacket(OggCodecState* aCodecState) |
michael@0 | 866 | { |
michael@0 | 867 | NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
michael@0 | 868 | |
michael@0 | 869 | if (!aCodecState || !aCodecState->mActive) { |
michael@0 | 870 | return nullptr; |
michael@0 | 871 | } |
michael@0 | 872 | |
michael@0 | 873 | ogg_packet* packet; |
michael@0 | 874 | while ((packet = aCodecState->PacketOut()) == nullptr) { |
michael@0 | 875 | // The codec state does not have any buffered pages, so try to read another |
michael@0 | 876 | // page from the channel. |
michael@0 | 877 | ogg_page page; |
michael@0 | 878 | if (!ReadOggPage(&page)) { |
michael@0 | 879 | return nullptr; |
michael@0 | 880 | } |
michael@0 | 881 | |
michael@0 | 882 | uint32_t serial = ogg_page_serialno(&page); |
michael@0 | 883 | OggCodecState* codecState = nullptr; |
michael@0 | 884 | codecState = mCodecStore.Get(serial); |
michael@0 | 885 | if (codecState && NS_FAILED(codecState->PageIn(&page))) { |
michael@0 | 886 | return nullptr; |
michael@0 | 887 | } |
michael@0 | 888 | } |
michael@0 | 889 | |
michael@0 | 890 | return packet; |
michael@0 | 891 | } |
michael@0 | 892 | |
michael@0 | 893 | // Returns an ogg page's checksum. |
michael@0 | 894 | static ogg_uint32_t |
michael@0 | 895 | GetChecksum(ogg_page* page) |
michael@0 | 896 | { |
michael@0 | 897 | if (page == 0 || page->header == 0 || page->header_len < 25) { |
michael@0 | 898 | return 0; |
michael@0 | 899 | } |
michael@0 | 900 | const unsigned char* p = page->header + 22; |
michael@0 | 901 | uint32_t c = p[0] + |
michael@0 | 902 | (p[1] << 8) + |
michael@0 | 903 | (p[2] << 16) + |
michael@0 | 904 | (p[3] << 24); |
michael@0 | 905 | return c; |
michael@0 | 906 | } |
michael@0 | 907 | |
michael@0 | 908 | int64_t OggReader::RangeStartTime(int64_t aOffset) |
michael@0 | 909 | { |
michael@0 | 910 | NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
michael@0 | 911 | MediaResource* resource = mDecoder->GetResource(); |
michael@0 | 912 | NS_ENSURE_TRUE(resource != nullptr, 0); |
michael@0 | 913 | nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET, aOffset); |
michael@0 | 914 | NS_ENSURE_SUCCESS(res, 0); |
michael@0 | 915 | int64_t startTime = 0; |
michael@0 | 916 | MediaDecoderReader::FindStartTime(startTime); |
michael@0 | 917 | return startTime; |
michael@0 | 918 | } |
michael@0 | 919 | |
michael@0 | 920 | struct nsAutoOggSyncState { |
michael@0 | 921 | nsAutoOggSyncState() { |
michael@0 | 922 | ogg_sync_init(&mState); |
michael@0 | 923 | } |
michael@0 | 924 | ~nsAutoOggSyncState() { |
michael@0 | 925 | ogg_sync_clear(&mState); |
michael@0 | 926 | } |
michael@0 | 927 | ogg_sync_state mState; |
michael@0 | 928 | }; |
michael@0 | 929 | |
michael@0 | 930 | int64_t OggReader::RangeEndTime(int64_t aEndOffset) |
michael@0 | 931 | { |
michael@0 | 932 | NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(), |
michael@0 | 933 | "Should be on state machine or decode thread."); |
michael@0 | 934 | |
michael@0 | 935 | MediaResource* resource = mDecoder->GetResource(); |
michael@0 | 936 | NS_ENSURE_TRUE(resource != nullptr, -1); |
michael@0 | 937 | int64_t position = resource->Tell(); |
michael@0 | 938 | int64_t endTime = RangeEndTime(0, aEndOffset, false); |
michael@0 | 939 | nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET, position); |
michael@0 | 940 | NS_ENSURE_SUCCESS(res, -1); |
michael@0 | 941 | return endTime; |
michael@0 | 942 | } |
michael@0 | 943 | |
michael@0 | 944 | int64_t OggReader::RangeEndTime(int64_t aStartOffset, |
michael@0 | 945 | int64_t aEndOffset, |
michael@0 | 946 | bool aCachedDataOnly) |
michael@0 | 947 | { |
michael@0 | 948 | MediaResource* resource = mDecoder->GetResource(); |
michael@0 | 949 | nsAutoOggSyncState sync; |
michael@0 | 950 | |
michael@0 | 951 | // We need to find the last page which ends before aEndOffset that |
michael@0 | 952 | // has a granulepos that we can convert to a timestamp. We do this by |
michael@0 | 953 | // backing off from aEndOffset until we encounter a page on which we can |
michael@0 | 954 | // interpret the granulepos. If while backing off we encounter a page which |
michael@0 | 955 | // we've previously encountered before, we'll either backoff again if we |
michael@0 | 956 | // haven't found an end time yet, or return the last end time found. |
michael@0 | 957 | const int step = 5000; |
michael@0 | 958 | const int maxOggPageSize = 65306; |
michael@0 | 959 | int64_t readStartOffset = aEndOffset; |
michael@0 | 960 | int64_t readLimitOffset = aEndOffset; |
michael@0 | 961 | int64_t readHead = aEndOffset; |
michael@0 | 962 | int64_t endTime = -1; |
michael@0 | 963 | uint32_t checksumAfterSeek = 0; |
michael@0 | 964 | uint32_t prevChecksumAfterSeek = 0; |
michael@0 | 965 | bool mustBackOff = false; |
michael@0 | 966 | while (true) { |
michael@0 | 967 | ogg_page page; |
michael@0 | 968 | int ret = ogg_sync_pageseek(&sync.mState, &page); |
michael@0 | 969 | if (ret == 0) { |
michael@0 | 970 | // We need more data if we've not encountered a page we've seen before, |
michael@0 | 971 | // or we've read to the end of file. |
michael@0 | 972 | if (mustBackOff || readHead == aEndOffset || readHead == aStartOffset) { |
michael@0 | 973 | if (endTime != -1 || readStartOffset == 0) { |
michael@0 | 974 | // We have encountered a page before, or we're at the end of file. |
michael@0 | 975 | break; |
michael@0 | 976 | } |
michael@0 | 977 | mustBackOff = false; |
michael@0 | 978 | prevChecksumAfterSeek = checksumAfterSeek; |
michael@0 | 979 | checksumAfterSeek = 0; |
michael@0 | 980 | ogg_sync_reset(&sync.mState); |
michael@0 | 981 | readStartOffset = std::max(static_cast<int64_t>(0), readStartOffset - step); |
michael@0 | 982 | // There's no point reading more than the maximum size of |
michael@0 | 983 | // an Ogg page into data we've previously scanned. Any data |
michael@0 | 984 | // between readLimitOffset and aEndOffset must be garbage |
michael@0 | 985 | // and we can ignore it thereafter. |
michael@0 | 986 | readLimitOffset = std::min(readLimitOffset, |
michael@0 | 987 | readStartOffset + maxOggPageSize); |
michael@0 | 988 | readHead = std::max(aStartOffset, readStartOffset); |
michael@0 | 989 | } |
michael@0 | 990 | |
michael@0 | 991 | int64_t limit = std::min(static_cast<int64_t>(UINT32_MAX), |
michael@0 | 992 | aEndOffset - readHead); |
michael@0 | 993 | limit = std::max(static_cast<int64_t>(0), limit); |
michael@0 | 994 | limit = std::min(limit, static_cast<int64_t>(step)); |
michael@0 | 995 | uint32_t bytesToRead = static_cast<uint32_t>(limit); |
michael@0 | 996 | uint32_t bytesRead = 0; |
michael@0 | 997 | char* buffer = ogg_sync_buffer(&sync.mState, bytesToRead); |
michael@0 | 998 | NS_ASSERTION(buffer, "Must have buffer"); |
michael@0 | 999 | nsresult res; |
michael@0 | 1000 | if (aCachedDataOnly) { |
michael@0 | 1001 | res = resource->ReadFromCache(buffer, readHead, bytesToRead); |
michael@0 | 1002 | NS_ENSURE_SUCCESS(res, -1); |
michael@0 | 1003 | bytesRead = bytesToRead; |
michael@0 | 1004 | } else { |
michael@0 | 1005 | NS_ASSERTION(readHead < aEndOffset, |
michael@0 | 1006 | "resource pos must be before range end"); |
michael@0 | 1007 | res = resource->Seek(nsISeekableStream::NS_SEEK_SET, readHead); |
michael@0 | 1008 | NS_ENSURE_SUCCESS(res, -1); |
michael@0 | 1009 | res = resource->Read(buffer, bytesToRead, &bytesRead); |
michael@0 | 1010 | NS_ENSURE_SUCCESS(res, -1); |
michael@0 | 1011 | } |
michael@0 | 1012 | readHead += bytesRead; |
michael@0 | 1013 | if (readHead > readLimitOffset) { |
michael@0 | 1014 | mustBackOff = true; |
michael@0 | 1015 | } |
michael@0 | 1016 | |
michael@0 | 1017 | // Update the synchronisation layer with the number |
michael@0 | 1018 | // of bytes written to the buffer |
michael@0 | 1019 | ret = ogg_sync_wrote(&sync.mState, bytesRead); |
michael@0 | 1020 | if (ret != 0) { |
michael@0 | 1021 | endTime = -1; |
michael@0 | 1022 | break; |
michael@0 | 1023 | } |
michael@0 | 1024 | |
michael@0 | 1025 | continue; |
michael@0 | 1026 | } |
michael@0 | 1027 | |
michael@0 | 1028 | if (ret < 0 || ogg_page_granulepos(&page) < 0) { |
michael@0 | 1029 | continue; |
michael@0 | 1030 | } |
michael@0 | 1031 | |
michael@0 | 1032 | uint32_t checksum = GetChecksum(&page); |
michael@0 | 1033 | if (checksumAfterSeek == 0) { |
michael@0 | 1034 | // This is the first page we've decoded after a backoff/seek. Remember |
michael@0 | 1035 | // the page checksum. If we backoff further and encounter this page |
michael@0 | 1036 | // again, we'll know that we won't find a page with an end time after |
michael@0 | 1037 | // this one, so we'll know to back off again. |
michael@0 | 1038 | checksumAfterSeek = checksum; |
michael@0 | 1039 | } |
michael@0 | 1040 | if (checksum == prevChecksumAfterSeek) { |
michael@0 | 1041 | // This page has the same checksum as the first page we encountered |
michael@0 | 1042 | // after the last backoff/seek. Since we've already scanned after this |
michael@0 | 1043 | // page and failed to find an end time, we may as well backoff again and |
michael@0 | 1044 | // try to find an end time from an earlier page. |
michael@0 | 1045 | mustBackOff = true; |
michael@0 | 1046 | continue; |
michael@0 | 1047 | } |
michael@0 | 1048 | |
michael@0 | 1049 | int64_t granulepos = ogg_page_granulepos(&page); |
michael@0 | 1050 | int serial = ogg_page_serialno(&page); |
michael@0 | 1051 | |
michael@0 | 1052 | OggCodecState* codecState = nullptr; |
michael@0 | 1053 | codecState = mCodecStore.Get(serial); |
michael@0 | 1054 | |
michael@0 | 1055 | if (!codecState) { |
michael@0 | 1056 | // This page is from a bitstream which we haven't encountered yet. |
michael@0 | 1057 | // It's probably from a new "link" in a "chained" ogg. Don't |
michael@0 | 1058 | // bother even trying to find a duration... |
michael@0 | 1059 | SetChained(true); |
michael@0 | 1060 | endTime = -1; |
michael@0 | 1061 | break; |
michael@0 | 1062 | } |
michael@0 | 1063 | |
michael@0 | 1064 | int64_t t = codecState->Time(granulepos); |
michael@0 | 1065 | if (t != -1) { |
michael@0 | 1066 | endTime = t; |
michael@0 | 1067 | } |
michael@0 | 1068 | } |
michael@0 | 1069 | |
michael@0 | 1070 | return endTime; |
michael@0 | 1071 | } |
michael@0 | 1072 | |
michael@0 | 1073 | nsresult OggReader::GetSeekRanges(nsTArray<SeekRange>& aRanges) |
michael@0 | 1074 | { |
michael@0 | 1075 | NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
michael@0 | 1076 | nsTArray<MediaByteRange> cached; |
michael@0 | 1077 | nsresult res = mDecoder->GetResource()->GetCachedRanges(cached); |
michael@0 | 1078 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 1079 | |
michael@0 | 1080 | for (uint32_t index = 0; index < cached.Length(); index++) { |
michael@0 | 1081 | MediaByteRange& range = cached[index]; |
michael@0 | 1082 | int64_t startTime = -1; |
michael@0 | 1083 | int64_t endTime = -1; |
michael@0 | 1084 | if (NS_FAILED(ResetDecode())) { |
michael@0 | 1085 | return NS_ERROR_FAILURE; |
michael@0 | 1086 | } |
michael@0 | 1087 | int64_t startOffset = range.mStart; |
michael@0 | 1088 | int64_t endOffset = range.mEnd; |
michael@0 | 1089 | startTime = RangeStartTime(startOffset); |
michael@0 | 1090 | if (startTime != -1 && |
michael@0 | 1091 | ((endTime = RangeEndTime(endOffset)) != -1)) |
michael@0 | 1092 | { |
michael@0 | 1093 | NS_WARN_IF_FALSE(startTime < endTime, |
michael@0 | 1094 | "Start time must be before end time"); |
michael@0 | 1095 | aRanges.AppendElement(SeekRange(startOffset, |
michael@0 | 1096 | endOffset, |
michael@0 | 1097 | startTime, |
michael@0 | 1098 | endTime)); |
michael@0 | 1099 | } |
michael@0 | 1100 | } |
michael@0 | 1101 | if (NS_FAILED(ResetDecode())) { |
michael@0 | 1102 | return NS_ERROR_FAILURE; |
michael@0 | 1103 | } |
michael@0 | 1104 | return NS_OK; |
michael@0 | 1105 | } |
michael@0 | 1106 | |
michael@0 | 1107 | OggReader::SeekRange |
michael@0 | 1108 | OggReader::SelectSeekRange(const nsTArray<SeekRange>& ranges, |
michael@0 | 1109 | int64_t aTarget, |
michael@0 | 1110 | int64_t aStartTime, |
michael@0 | 1111 | int64_t aEndTime, |
michael@0 | 1112 | bool aExact) |
michael@0 | 1113 | { |
michael@0 | 1114 | NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
michael@0 | 1115 | int64_t so = 0; |
michael@0 | 1116 | int64_t eo = mDecoder->GetResource()->GetLength(); |
michael@0 | 1117 | int64_t st = aStartTime; |
michael@0 | 1118 | int64_t et = aEndTime; |
michael@0 | 1119 | for (uint32_t i = 0; i < ranges.Length(); i++) { |
michael@0 | 1120 | const SeekRange &r = ranges[i]; |
michael@0 | 1121 | if (r.mTimeStart < aTarget) { |
michael@0 | 1122 | so = r.mOffsetStart; |
michael@0 | 1123 | st = r.mTimeStart; |
michael@0 | 1124 | } |
michael@0 | 1125 | if (r.mTimeEnd >= aTarget && r.mTimeEnd < et) { |
michael@0 | 1126 | eo = r.mOffsetEnd; |
michael@0 | 1127 | et = r.mTimeEnd; |
michael@0 | 1128 | } |
michael@0 | 1129 | |
michael@0 | 1130 | if (r.mTimeStart < aTarget && aTarget <= r.mTimeEnd) { |
michael@0 | 1131 | // Target lies exactly in this range. |
michael@0 | 1132 | return ranges[i]; |
michael@0 | 1133 | } |
michael@0 | 1134 | } |
michael@0 | 1135 | if (aExact || eo == -1) { |
michael@0 | 1136 | return SeekRange(); |
michael@0 | 1137 | } |
michael@0 | 1138 | return SeekRange(so, eo, st, et); |
michael@0 | 1139 | } |
michael@0 | 1140 | |
michael@0 | 1141 | OggReader::IndexedSeekResult OggReader::RollbackIndexedSeek(int64_t aOffset) |
michael@0 | 1142 | { |
michael@0 | 1143 | mSkeletonState->Deactivate(); |
michael@0 | 1144 | MediaResource* resource = mDecoder->GetResource(); |
michael@0 | 1145 | NS_ENSURE_TRUE(resource != nullptr, SEEK_FATAL_ERROR); |
michael@0 | 1146 | nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET, aOffset); |
michael@0 | 1147 | NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR); |
michael@0 | 1148 | return SEEK_INDEX_FAIL; |
michael@0 | 1149 | } |
michael@0 | 1150 | |
michael@0 | 1151 | OggReader::IndexedSeekResult OggReader::SeekToKeyframeUsingIndex(int64_t aTarget) |
michael@0 | 1152 | { |
michael@0 | 1153 | MediaResource* resource = mDecoder->GetResource(); |
michael@0 | 1154 | NS_ENSURE_TRUE(resource != nullptr, SEEK_FATAL_ERROR); |
michael@0 | 1155 | if (!HasSkeleton() || !mSkeletonState->HasIndex()) { |
michael@0 | 1156 | return SEEK_INDEX_FAIL; |
michael@0 | 1157 | } |
michael@0 | 1158 | // We have an index from the Skeleton track, try to use it to seek. |
michael@0 | 1159 | nsAutoTArray<uint32_t, 2> tracks; |
michael@0 | 1160 | BuildSerialList(tracks); |
michael@0 | 1161 | SkeletonState::nsSeekTarget keyframe; |
michael@0 | 1162 | if (NS_FAILED(mSkeletonState->IndexedSeekTarget(aTarget, |
michael@0 | 1163 | tracks, |
michael@0 | 1164 | keyframe))) |
michael@0 | 1165 | { |
michael@0 | 1166 | // Could not locate a keypoint for the target in the index. |
michael@0 | 1167 | return SEEK_INDEX_FAIL; |
michael@0 | 1168 | } |
michael@0 | 1169 | |
michael@0 | 1170 | // Remember original resource read cursor position so we can rollback on failure. |
michael@0 | 1171 | int64_t tell = resource->Tell(); |
michael@0 | 1172 | |
michael@0 | 1173 | // Seek to the keypoint returned by the index. |
michael@0 | 1174 | if (keyframe.mKeyPoint.mOffset > resource->GetLength() || |
michael@0 | 1175 | keyframe.mKeyPoint.mOffset < 0) |
michael@0 | 1176 | { |
michael@0 | 1177 | // Index must be invalid. |
michael@0 | 1178 | return RollbackIndexedSeek(tell); |
michael@0 | 1179 | } |
michael@0 | 1180 | LOG(PR_LOG_DEBUG, ("Seeking using index to keyframe at offset %lld\n", |
michael@0 | 1181 | keyframe.mKeyPoint.mOffset)); |
michael@0 | 1182 | nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET, |
michael@0 | 1183 | keyframe.mKeyPoint.mOffset); |
michael@0 | 1184 | NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR); |
michael@0 | 1185 | |
michael@0 | 1186 | // We've moved the read set, so reset decode. |
michael@0 | 1187 | res = ResetDecode(); |
michael@0 | 1188 | NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR); |
michael@0 | 1189 | |
michael@0 | 1190 | // Check that the page the index thinks is exactly here is actually exactly |
michael@0 | 1191 | // here. If not, the index is invalid. |
michael@0 | 1192 | ogg_page page; |
michael@0 | 1193 | int skippedBytes = 0; |
michael@0 | 1194 | PageSyncResult syncres = PageSync(resource, |
michael@0 | 1195 | &mOggState, |
michael@0 | 1196 | false, |
michael@0 | 1197 | keyframe.mKeyPoint.mOffset, |
michael@0 | 1198 | resource->GetLength(), |
michael@0 | 1199 | &page, |
michael@0 | 1200 | skippedBytes); |
michael@0 | 1201 | NS_ENSURE_TRUE(syncres != PAGE_SYNC_ERROR, SEEK_FATAL_ERROR); |
michael@0 | 1202 | if (syncres != PAGE_SYNC_OK || skippedBytes != 0) { |
michael@0 | 1203 | LOG(PR_LOG_DEBUG, ("Indexed-seek failure: Ogg Skeleton Index is invalid " |
michael@0 | 1204 | "or sync error after seek")); |
michael@0 | 1205 | return RollbackIndexedSeek(tell); |
michael@0 | 1206 | } |
michael@0 | 1207 | uint32_t serial = ogg_page_serialno(&page); |
michael@0 | 1208 | if (serial != keyframe.mSerial) { |
michael@0 | 1209 | // Serialno of page at offset isn't what the index told us to expect. |
michael@0 | 1210 | // Assume the index is invalid. |
michael@0 | 1211 | return RollbackIndexedSeek(tell); |
michael@0 | 1212 | } |
michael@0 | 1213 | OggCodecState* codecState = mCodecStore.Get(serial); |
michael@0 | 1214 | if (codecState && |
michael@0 | 1215 | codecState->mActive && |
michael@0 | 1216 | ogg_stream_pagein(&codecState->mState, &page) != 0) |
michael@0 | 1217 | { |
michael@0 | 1218 | // Couldn't insert page into the ogg resource, or somehow the resource |
michael@0 | 1219 | // is no longer active. |
michael@0 | 1220 | return RollbackIndexedSeek(tell); |
michael@0 | 1221 | } |
michael@0 | 1222 | return SEEK_OK; |
michael@0 | 1223 | } |
michael@0 | 1224 | |
michael@0 | 1225 | nsresult OggReader::SeekInBufferedRange(int64_t aTarget, |
michael@0 | 1226 | int64_t aAdjustedTarget, |
michael@0 | 1227 | int64_t aStartTime, |
michael@0 | 1228 | int64_t aEndTime, |
michael@0 | 1229 | const nsTArray<SeekRange>& aRanges, |
michael@0 | 1230 | const SeekRange& aRange) |
michael@0 | 1231 | { |
michael@0 | 1232 | LOG(PR_LOG_DEBUG, ("%p Seeking in buffered data to %lld using bisection search", mDecoder, aTarget)); |
michael@0 | 1233 | nsresult res = NS_OK; |
michael@0 | 1234 | if (HasVideo() || aAdjustedTarget >= aTarget) { |
michael@0 | 1235 | // We know the exact byte range in which the target must lie. It must |
michael@0 | 1236 | // be buffered in the media cache. Seek there. |
michael@0 | 1237 | nsresult res = SeekBisection(aTarget, aRange, 0); |
michael@0 | 1238 | if (NS_FAILED(res) || !HasVideo()) { |
michael@0 | 1239 | return res; |
michael@0 | 1240 | } |
michael@0 | 1241 | |
michael@0 | 1242 | // We have an active Theora bitstream. Decode the next Theora frame, and |
michael@0 | 1243 | // extract its keyframe's time. |
michael@0 | 1244 | bool eof; |
michael@0 | 1245 | do { |
michael@0 | 1246 | bool skip = false; |
michael@0 | 1247 | eof = !DecodeVideoFrame(skip, 0); |
michael@0 | 1248 | { |
michael@0 | 1249 | ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); |
michael@0 | 1250 | if (mDecoder->IsShutdown()) { |
michael@0 | 1251 | return NS_ERROR_FAILURE; |
michael@0 | 1252 | } |
michael@0 | 1253 | } |
michael@0 | 1254 | } while (!eof && |
michael@0 | 1255 | mVideoQueue.GetSize() == 0); |
michael@0 | 1256 | |
michael@0 | 1257 | VideoData* video = mVideoQueue.PeekFront(); |
michael@0 | 1258 | if (video && !video->mKeyframe) { |
michael@0 | 1259 | // First decoded frame isn't a keyframe, seek back to previous keyframe, |
michael@0 | 1260 | // otherwise we'll get visual artifacts. |
michael@0 | 1261 | NS_ASSERTION(video->mTimecode != -1, "Must have a granulepos"); |
michael@0 | 1262 | int shift = mTheoraState->mInfo.keyframe_granule_shift; |
michael@0 | 1263 | int64_t keyframeGranulepos = (video->mTimecode >> shift) << shift; |
michael@0 | 1264 | int64_t keyframeTime = mTheoraState->StartTime(keyframeGranulepos); |
michael@0 | 1265 | SEEK_LOG(PR_LOG_DEBUG, ("Keyframe for %lld is at %lld, seeking back to it", |
michael@0 | 1266 | video->mTime, keyframeTime)); |
michael@0 | 1267 | aAdjustedTarget = std::min(aAdjustedTarget, keyframeTime); |
michael@0 | 1268 | } |
michael@0 | 1269 | } |
michael@0 | 1270 | if (aAdjustedTarget < aTarget) { |
michael@0 | 1271 | SeekRange k = SelectSeekRange(aRanges, |
michael@0 | 1272 | aAdjustedTarget, |
michael@0 | 1273 | aStartTime, |
michael@0 | 1274 | aEndTime, |
michael@0 | 1275 | false); |
michael@0 | 1276 | res = SeekBisection(aAdjustedTarget, k, SEEK_FUZZ_USECS); |
michael@0 | 1277 | } |
michael@0 | 1278 | return res; |
michael@0 | 1279 | } |
michael@0 | 1280 | |
michael@0 | 1281 | nsresult OggReader::SeekInUnbuffered(int64_t aTarget, |
michael@0 | 1282 | int64_t aStartTime, |
michael@0 | 1283 | int64_t aEndTime, |
michael@0 | 1284 | const nsTArray<SeekRange>& aRanges) |
michael@0 | 1285 | { |
michael@0 | 1286 | LOG(PR_LOG_DEBUG, ("%p Seeking in unbuffered data to %lld using bisection search", mDecoder, aTarget)); |
michael@0 | 1287 | |
michael@0 | 1288 | // If we've got an active Theora bitstream, determine the maximum possible |
michael@0 | 1289 | // time in usecs which a keyframe could be before a given interframe. We |
michael@0 | 1290 | // subtract this from our seek target, seek to the new target, and then |
michael@0 | 1291 | // will decode forward to the original seek target. We should encounter a |
michael@0 | 1292 | // keyframe in that interval. This prevents us from needing to run two |
michael@0 | 1293 | // bisections; one for the seek target frame, and another to find its |
michael@0 | 1294 | // keyframe. It's usually faster to just download this extra data, rather |
michael@0 | 1295 | // tham perform two bisections to find the seek target's keyframe. We |
michael@0 | 1296 | // don't do this offsetting when seeking in a buffered range, |
michael@0 | 1297 | // as the extra decoding causes a noticeable speed hit when all the data |
michael@0 | 1298 | // is buffered (compared to just doing a bisection to exactly find the |
michael@0 | 1299 | // keyframe). |
michael@0 | 1300 | int64_t keyframeOffsetMs = 0; |
michael@0 | 1301 | if (HasVideo() && mTheoraState) { |
michael@0 | 1302 | keyframeOffsetMs = mTheoraState->MaxKeyframeOffset(); |
michael@0 | 1303 | } |
michael@0 | 1304 | #ifdef MOZ_OPUS |
michael@0 | 1305 | // Add in the Opus pre-roll if necessary, as well. |
michael@0 | 1306 | if (HasAudio() && mOpusState) { |
michael@0 | 1307 | keyframeOffsetMs = std::max(keyframeOffsetMs, SEEK_OPUS_PREROLL); |
michael@0 | 1308 | } |
michael@0 | 1309 | #endif /* MOZ_OPUS */ |
michael@0 | 1310 | int64_t seekTarget = std::max(aStartTime, aTarget - keyframeOffsetMs); |
michael@0 | 1311 | // Minimize the bisection search space using the known timestamps from the |
michael@0 | 1312 | // buffered ranges. |
michael@0 | 1313 | SeekRange k = SelectSeekRange(aRanges, seekTarget, aStartTime, aEndTime, false); |
michael@0 | 1314 | return SeekBisection(seekTarget, k, SEEK_FUZZ_USECS); |
michael@0 | 1315 | } |
michael@0 | 1316 | |
michael@0 | 1317 | nsresult OggReader::Seek(int64_t aTarget, |
michael@0 | 1318 | int64_t aStartTime, |
michael@0 | 1319 | int64_t aEndTime, |
michael@0 | 1320 | int64_t aCurrentTime) |
michael@0 | 1321 | { |
michael@0 | 1322 | NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
michael@0 | 1323 | if (mIsChained) |
michael@0 | 1324 | return NS_ERROR_FAILURE; |
michael@0 | 1325 | LOG(PR_LOG_DEBUG, ("%p About to seek to %lld", mDecoder, aTarget)); |
michael@0 | 1326 | nsresult res; |
michael@0 | 1327 | MediaResource* resource = mDecoder->GetResource(); |
michael@0 | 1328 | NS_ENSURE_TRUE(resource != nullptr, NS_ERROR_FAILURE); |
michael@0 | 1329 | int64_t adjustedTarget = aTarget; |
michael@0 | 1330 | #ifdef MOZ_OPUS |
michael@0 | 1331 | if (HasAudio() && mOpusState){ |
michael@0 | 1332 | adjustedTarget = std::max(aStartTime, aTarget - SEEK_OPUS_PREROLL); |
michael@0 | 1333 | } |
michael@0 | 1334 | #endif /* MOZ_OPUS */ |
michael@0 | 1335 | |
michael@0 | 1336 | if (adjustedTarget == aStartTime) { |
michael@0 | 1337 | // We've seeked to the media start. Just seek to the offset of the first |
michael@0 | 1338 | // content page. |
michael@0 | 1339 | res = resource->Seek(nsISeekableStream::NS_SEEK_SET, 0); |
michael@0 | 1340 | NS_ENSURE_SUCCESS(res,res); |
michael@0 | 1341 | |
michael@0 | 1342 | res = ResetDecode(true); |
michael@0 | 1343 | NS_ENSURE_SUCCESS(res,res); |
michael@0 | 1344 | |
michael@0 | 1345 | NS_ASSERTION(aStartTime != -1, "mStartTime should be known"); |
michael@0 | 1346 | { |
michael@0 | 1347 | ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); |
michael@0 | 1348 | mDecoder->UpdatePlaybackPosition(aStartTime); |
michael@0 | 1349 | } |
michael@0 | 1350 | } else { |
michael@0 | 1351 | // TODO: This may seek back unnecessarily far in the video, but we don't |
michael@0 | 1352 | // have a way of asking Skeleton to seek to a different target for each |
michael@0 | 1353 | // stream yet. Using adjustedTarget here is at least correct, if slow. |
michael@0 | 1354 | IndexedSeekResult sres = SeekToKeyframeUsingIndex(adjustedTarget); |
michael@0 | 1355 | NS_ENSURE_TRUE(sres != SEEK_FATAL_ERROR, NS_ERROR_FAILURE); |
michael@0 | 1356 | if (sres == SEEK_INDEX_FAIL) { |
michael@0 | 1357 | // No index or other non-fatal index-related failure. Try to seek |
michael@0 | 1358 | // using a bisection search. Determine the already downloaded data |
michael@0 | 1359 | // in the media cache, so we can try to seek in the cached data first. |
michael@0 | 1360 | nsAutoTArray<SeekRange, 16> ranges; |
michael@0 | 1361 | res = GetSeekRanges(ranges); |
michael@0 | 1362 | NS_ENSURE_SUCCESS(res,res); |
michael@0 | 1363 | |
michael@0 | 1364 | // Figure out if the seek target lies in a buffered range. |
michael@0 | 1365 | SeekRange r = SelectSeekRange(ranges, aTarget, aStartTime, aEndTime, true); |
michael@0 | 1366 | |
michael@0 | 1367 | if (!r.IsNull()) { |
michael@0 | 1368 | // We know the buffered range in which the seek target lies, do a |
michael@0 | 1369 | // bisection search in that buffered range. |
michael@0 | 1370 | res = SeekInBufferedRange(aTarget, adjustedTarget, aStartTime, aEndTime, ranges, r); |
michael@0 | 1371 | NS_ENSURE_SUCCESS(res,res); |
michael@0 | 1372 | } else { |
michael@0 | 1373 | // The target doesn't lie in a buffered range. Perform a bisection |
michael@0 | 1374 | // search over the whole media, using the known buffered ranges to |
michael@0 | 1375 | // reduce the search space. |
michael@0 | 1376 | res = SeekInUnbuffered(aTarget, aStartTime, aEndTime, ranges); |
michael@0 | 1377 | NS_ENSURE_SUCCESS(res,res); |
michael@0 | 1378 | } |
michael@0 | 1379 | } |
michael@0 | 1380 | } |
michael@0 | 1381 | |
michael@0 | 1382 | if (HasVideo()) { |
michael@0 | 1383 | // Decode forwards until we find the next keyframe. This is required, |
michael@0 | 1384 | // as although the seek should finish on a page containing a keyframe, |
michael@0 | 1385 | // there may be non-keyframes in the page before the keyframe. |
michael@0 | 1386 | // When doing fastSeek we display the first frame after the seek, so |
michael@0 | 1387 | // we need to advance the decode to the keyframe otherwise we'll get |
michael@0 | 1388 | // visual artifacts in the first frame output after the seek. |
michael@0 | 1389 | bool skip = true; |
michael@0 | 1390 | while (DecodeVideoFrame(skip, 0) && skip) { |
michael@0 | 1391 | ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); |
michael@0 | 1392 | if (mDecoder->IsShutdown()) { |
michael@0 | 1393 | return NS_ERROR_FAILURE; |
michael@0 | 1394 | } |
michael@0 | 1395 | } |
michael@0 | 1396 | |
michael@0 | 1397 | #ifdef DEBUG |
michael@0 | 1398 | const VideoData* v = mVideoQueue.PeekFront(); |
michael@0 | 1399 | if (!v || !v->mKeyframe) { |
michael@0 | 1400 | NS_WARNING("Ogg seek didn't end up before a key frame!"); |
michael@0 | 1401 | } |
michael@0 | 1402 | #endif |
michael@0 | 1403 | } |
michael@0 | 1404 | return NS_OK; |
michael@0 | 1405 | } |
michael@0 | 1406 | |
michael@0 | 1407 | // Reads a page from the media resource. |
michael@0 | 1408 | static PageSyncResult |
michael@0 | 1409 | PageSync(MediaResource* aResource, |
michael@0 | 1410 | ogg_sync_state* aState, |
michael@0 | 1411 | bool aCachedDataOnly, |
michael@0 | 1412 | int64_t aOffset, |
michael@0 | 1413 | int64_t aEndOffset, |
michael@0 | 1414 | ogg_page* aPage, |
michael@0 | 1415 | int& aSkippedBytes) |
michael@0 | 1416 | { |
michael@0 | 1417 | aSkippedBytes = 0; |
michael@0 | 1418 | // Sync to the next page. |
michael@0 | 1419 | int ret = 0; |
michael@0 | 1420 | uint32_t bytesRead = 0; |
michael@0 | 1421 | int64_t readHead = aOffset; |
michael@0 | 1422 | while (ret <= 0) { |
michael@0 | 1423 | ret = ogg_sync_pageseek(aState, aPage); |
michael@0 | 1424 | if (ret == 0) { |
michael@0 | 1425 | char* buffer = ogg_sync_buffer(aState, PAGE_STEP); |
michael@0 | 1426 | NS_ASSERTION(buffer, "Must have a buffer"); |
michael@0 | 1427 | |
michael@0 | 1428 | // Read from the file into the buffer |
michael@0 | 1429 | int64_t bytesToRead = std::min(static_cast<int64_t>(PAGE_STEP), |
michael@0 | 1430 | aEndOffset - readHead); |
michael@0 | 1431 | NS_ASSERTION(bytesToRead <= UINT32_MAX, "bytesToRead range check"); |
michael@0 | 1432 | if (bytesToRead <= 0) { |
michael@0 | 1433 | return PAGE_SYNC_END_OF_RANGE; |
michael@0 | 1434 | } |
michael@0 | 1435 | nsresult rv = NS_OK; |
michael@0 | 1436 | if (aCachedDataOnly) { |
michael@0 | 1437 | rv = aResource->ReadFromCache(buffer, readHead, |
michael@0 | 1438 | static_cast<uint32_t>(bytesToRead)); |
michael@0 | 1439 | NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR); |
michael@0 | 1440 | bytesRead = static_cast<uint32_t>(bytesToRead); |
michael@0 | 1441 | } else { |
michael@0 | 1442 | rv = aResource->Seek(nsISeekableStream::NS_SEEK_SET, readHead); |
michael@0 | 1443 | NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR); |
michael@0 | 1444 | rv = aResource->Read(buffer, |
michael@0 | 1445 | static_cast<uint32_t>(bytesToRead), |
michael@0 | 1446 | &bytesRead); |
michael@0 | 1447 | NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR); |
michael@0 | 1448 | } |
michael@0 | 1449 | if (bytesRead == 0 && NS_SUCCEEDED(rv)) { |
michael@0 | 1450 | // End of file. |
michael@0 | 1451 | return PAGE_SYNC_END_OF_RANGE; |
michael@0 | 1452 | } |
michael@0 | 1453 | readHead += bytesRead; |
michael@0 | 1454 | |
michael@0 | 1455 | // Update the synchronisation layer with the number |
michael@0 | 1456 | // of bytes written to the buffer |
michael@0 | 1457 | ret = ogg_sync_wrote(aState, bytesRead); |
michael@0 | 1458 | NS_ENSURE_TRUE(ret == 0, PAGE_SYNC_ERROR); |
michael@0 | 1459 | continue; |
michael@0 | 1460 | } |
michael@0 | 1461 | |
michael@0 | 1462 | if (ret < 0) { |
michael@0 | 1463 | NS_ASSERTION(aSkippedBytes >= 0, "Offset >= 0"); |
michael@0 | 1464 | aSkippedBytes += -ret; |
michael@0 | 1465 | NS_ASSERTION(aSkippedBytes >= 0, "Offset >= 0"); |
michael@0 | 1466 | continue; |
michael@0 | 1467 | } |
michael@0 | 1468 | } |
michael@0 | 1469 | |
michael@0 | 1470 | return PAGE_SYNC_OK; |
michael@0 | 1471 | } |
michael@0 | 1472 | |
michael@0 | 1473 | nsresult OggReader::SeekBisection(int64_t aTarget, |
michael@0 | 1474 | const SeekRange& aRange, |
michael@0 | 1475 | uint32_t aFuzz) |
michael@0 | 1476 | { |
michael@0 | 1477 | NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); |
michael@0 | 1478 | nsresult res; |
michael@0 | 1479 | MediaResource* resource = mDecoder->GetResource(); |
michael@0 | 1480 | |
michael@0 | 1481 | if (aTarget == aRange.mTimeStart) { |
michael@0 | 1482 | if (NS_FAILED(ResetDecode())) { |
michael@0 | 1483 | return NS_ERROR_FAILURE; |
michael@0 | 1484 | } |
michael@0 | 1485 | res = resource->Seek(nsISeekableStream::NS_SEEK_SET, 0); |
michael@0 | 1486 | NS_ENSURE_SUCCESS(res,res); |
michael@0 | 1487 | return NS_OK; |
michael@0 | 1488 | } |
michael@0 | 1489 | |
michael@0 | 1490 | // Bisection search, find start offset of last page with end time less than |
michael@0 | 1491 | // the seek target. |
michael@0 | 1492 | ogg_int64_t startOffset = aRange.mOffsetStart; |
michael@0 | 1493 | ogg_int64_t startTime = aRange.mTimeStart; |
michael@0 | 1494 | ogg_int64_t startLength = 0; // Length of the page at startOffset. |
michael@0 | 1495 | ogg_int64_t endOffset = aRange.mOffsetEnd; |
michael@0 | 1496 | ogg_int64_t endTime = aRange.mTimeEnd; |
michael@0 | 1497 | |
michael@0 | 1498 | ogg_int64_t seekTarget = aTarget; |
michael@0 | 1499 | int64_t seekLowerBound = std::max(static_cast<int64_t>(0), aTarget - aFuzz); |
michael@0 | 1500 | int hops = 0; |
michael@0 | 1501 | DebugOnly<ogg_int64_t> previousGuess = -1; |
michael@0 | 1502 | int backsteps = 0; |
michael@0 | 1503 | const int maxBackStep = 10; |
michael@0 | 1504 | NS_ASSERTION(static_cast<uint64_t>(PAGE_STEP) * pow(2.0, maxBackStep) < INT32_MAX, |
michael@0 | 1505 | "Backstep calculation must not overflow"); |
michael@0 | 1506 | |
michael@0 | 1507 | // Seek via bisection search. Loop until we find the offset where the page |
michael@0 | 1508 | // before the offset is before the seek target, and the page after the offset |
michael@0 | 1509 | // is after the seek target. |
michael@0 | 1510 | while (true) { |
michael@0 | 1511 | ogg_int64_t duration = 0; |
michael@0 | 1512 | double target = 0; |
michael@0 | 1513 | ogg_int64_t interval = 0; |
michael@0 | 1514 | ogg_int64_t guess = 0; |
michael@0 | 1515 | ogg_page page; |
michael@0 | 1516 | int skippedBytes = 0; |
michael@0 | 1517 | ogg_int64_t pageOffset = 0; |
michael@0 | 1518 | ogg_int64_t pageLength = 0; |
michael@0 | 1519 | ogg_int64_t granuleTime = -1; |
michael@0 | 1520 | bool mustBackoff = false; |
michael@0 | 1521 | |
michael@0 | 1522 | // Guess where we should bisect to, based on the bit rate and the time |
michael@0 | 1523 | // remaining in the interval. Loop until we can determine the time at |
michael@0 | 1524 | // the guess offset. |
michael@0 | 1525 | while (true) { |
michael@0 | 1526 | |
michael@0 | 1527 | // Discard any previously buffered packets/pages. |
michael@0 | 1528 | if (NS_FAILED(ResetDecode())) { |
michael@0 | 1529 | return NS_ERROR_FAILURE; |
michael@0 | 1530 | } |
michael@0 | 1531 | |
michael@0 | 1532 | interval = endOffset - startOffset - startLength; |
michael@0 | 1533 | if (interval == 0) { |
michael@0 | 1534 | // Our interval is empty, we've found the optimal seek point, as the |
michael@0 | 1535 | // page at the start offset is before the seek target, and the page |
michael@0 | 1536 | // at the end offset is after the seek target. |
michael@0 | 1537 | SEEK_LOG(PR_LOG_DEBUG, ("Interval narrowed, terminating bisection.")); |
michael@0 | 1538 | break; |
michael@0 | 1539 | } |
michael@0 | 1540 | |
michael@0 | 1541 | // Guess bisection point. |
michael@0 | 1542 | duration = endTime - startTime; |
michael@0 | 1543 | target = (double)(seekTarget - startTime) / (double)duration; |
michael@0 | 1544 | guess = startOffset + startLength + |
michael@0 | 1545 | static_cast<ogg_int64_t>((double)interval * target); |
michael@0 | 1546 | guess = std::min(guess, endOffset - PAGE_STEP); |
michael@0 | 1547 | if (mustBackoff) { |
michael@0 | 1548 | // We previously failed to determine the time at the guess offset, |
michael@0 | 1549 | // probably because we ran out of data to decode. This usually happens |
michael@0 | 1550 | // when we guess very close to the end offset. So reduce the guess |
michael@0 | 1551 | // offset using an exponential backoff until we determine the time. |
michael@0 | 1552 | SEEK_LOG(PR_LOG_DEBUG, ("Backing off %d bytes, backsteps=%d", |
michael@0 | 1553 | static_cast<int32_t>(PAGE_STEP * pow(2.0, backsteps)), backsteps)); |
michael@0 | 1554 | guess -= PAGE_STEP * static_cast<ogg_int64_t>(pow(2.0, backsteps)); |
michael@0 | 1555 | |
michael@0 | 1556 | if (guess <= startOffset) { |
michael@0 | 1557 | // We've tried to backoff to before the start offset of our seek |
michael@0 | 1558 | // range. This means we couldn't find a seek termination position |
michael@0 | 1559 | // near the end of the seek range, so just set the seek termination |
michael@0 | 1560 | // condition, and break out of the bisection loop. We'll begin |
michael@0 | 1561 | // decoding from the start of the seek range. |
michael@0 | 1562 | interval = 0; |
michael@0 | 1563 | break; |
michael@0 | 1564 | } |
michael@0 | 1565 | |
michael@0 | 1566 | backsteps = std::min(backsteps + 1, maxBackStep); |
michael@0 | 1567 | // We reset mustBackoff. If we still need to backoff further, it will |
michael@0 | 1568 | // be set to true again. |
michael@0 | 1569 | mustBackoff = false; |
michael@0 | 1570 | } else { |
michael@0 | 1571 | backsteps = 0; |
michael@0 | 1572 | } |
michael@0 | 1573 | guess = std::max(guess, startOffset + startLength); |
michael@0 | 1574 | |
michael@0 | 1575 | SEEK_LOG(PR_LOG_DEBUG, ("Seek loop start[o=%lld..%lld t=%lld] " |
michael@0 | 1576 | "end[o=%lld t=%lld] " |
michael@0 | 1577 | "interval=%lld target=%lf guess=%lld", |
michael@0 | 1578 | startOffset, (startOffset+startLength), startTime, |
michael@0 | 1579 | endOffset, endTime, interval, target, guess)); |
michael@0 | 1580 | |
michael@0 | 1581 | NS_ASSERTION(guess >= startOffset + startLength, "Guess must be after range start"); |
michael@0 | 1582 | NS_ASSERTION(guess < endOffset, "Guess must be before range end"); |
michael@0 | 1583 | NS_ASSERTION(guess != previousGuess, "Guess should be different to previous"); |
michael@0 | 1584 | previousGuess = guess; |
michael@0 | 1585 | |
michael@0 | 1586 | hops++; |
michael@0 | 1587 | |
michael@0 | 1588 | // Locate the next page after our seek guess, and then figure out the |
michael@0 | 1589 | // granule time of the audio and video bitstreams there. We can then |
michael@0 | 1590 | // make a bisection decision based on our location in the media. |
michael@0 | 1591 | PageSyncResult res = PageSync(resource, |
michael@0 | 1592 | &mOggState, |
michael@0 | 1593 | false, |
michael@0 | 1594 | guess, |
michael@0 | 1595 | endOffset, |
michael@0 | 1596 | &page, |
michael@0 | 1597 | skippedBytes); |
michael@0 | 1598 | NS_ENSURE_TRUE(res != PAGE_SYNC_ERROR, NS_ERROR_FAILURE); |
michael@0 | 1599 | |
michael@0 | 1600 | if (res == PAGE_SYNC_END_OF_RANGE) { |
michael@0 | 1601 | // Our guess was too close to the end, we've ended up reading the end |
michael@0 | 1602 | // page. Backoff exponentially from the end point, in case the last |
michael@0 | 1603 | // page/frame/sample is huge. |
michael@0 | 1604 | mustBackoff = true; |
michael@0 | 1605 | SEEK_LOG(PR_LOG_DEBUG, ("Hit the end of range, backing off")); |
michael@0 | 1606 | continue; |
michael@0 | 1607 | } |
michael@0 | 1608 | |
michael@0 | 1609 | // We've located a page of length |ret| at |guess + skippedBytes|. |
michael@0 | 1610 | // Remember where the page is located. |
michael@0 | 1611 | pageOffset = guess + skippedBytes; |
michael@0 | 1612 | pageLength = page.header_len + page.body_len; |
michael@0 | 1613 | |
michael@0 | 1614 | // Read pages until we can determine the granule time of the audio and |
michael@0 | 1615 | // video bitstream. |
michael@0 | 1616 | ogg_int64_t audioTime = -1; |
michael@0 | 1617 | ogg_int64_t videoTime = -1; |
michael@0 | 1618 | do { |
michael@0 | 1619 | // Add the page to its codec state, determine its granule time. |
michael@0 | 1620 | uint32_t serial = ogg_page_serialno(&page); |
michael@0 | 1621 | OggCodecState* codecState = mCodecStore.Get(serial); |
michael@0 | 1622 | if (codecState && codecState->mActive) { |
michael@0 | 1623 | int ret = ogg_stream_pagein(&codecState->mState, &page); |
michael@0 | 1624 | NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE); |
michael@0 | 1625 | } |
michael@0 | 1626 | |
michael@0 | 1627 | ogg_int64_t granulepos = ogg_page_granulepos(&page); |
michael@0 | 1628 | |
michael@0 | 1629 | if (HasAudio() && granulepos > 0 && audioTime == -1) { |
michael@0 | 1630 | if (mVorbisState && serial == mVorbisState->mSerial) { |
michael@0 | 1631 | audioTime = mVorbisState->Time(granulepos); |
michael@0 | 1632 | #ifdef MOZ_OPUS |
michael@0 | 1633 | } else if (mOpusState && serial == mOpusState->mSerial) { |
michael@0 | 1634 | audioTime = mOpusState->Time(granulepos); |
michael@0 | 1635 | #endif |
michael@0 | 1636 | } |
michael@0 | 1637 | } |
michael@0 | 1638 | |
michael@0 | 1639 | if (HasVideo() && |
michael@0 | 1640 | granulepos > 0 && |
michael@0 | 1641 | serial == mTheoraState->mSerial && |
michael@0 | 1642 | videoTime == -1) { |
michael@0 | 1643 | videoTime = mTheoraState->Time(granulepos); |
michael@0 | 1644 | } |
michael@0 | 1645 | |
michael@0 | 1646 | if (pageOffset + pageLength >= endOffset) { |
michael@0 | 1647 | // Hit end of readable data. |
michael@0 | 1648 | break; |
michael@0 | 1649 | } |
michael@0 | 1650 | |
michael@0 | 1651 | if (!ReadOggPage(&page)) { |
michael@0 | 1652 | break; |
michael@0 | 1653 | } |
michael@0 | 1654 | |
michael@0 | 1655 | } while ((HasAudio() && audioTime == -1) || |
michael@0 | 1656 | (HasVideo() && videoTime == -1)); |
michael@0 | 1657 | |
michael@0 | 1658 | |
michael@0 | 1659 | if ((HasAudio() && audioTime == -1) || |
michael@0 | 1660 | (HasVideo() && videoTime == -1)) |
michael@0 | 1661 | { |
michael@0 | 1662 | // We don't have timestamps for all active tracks... |
michael@0 | 1663 | if (pageOffset == startOffset + startLength && |
michael@0 | 1664 | pageOffset + pageLength >= endOffset) { |
michael@0 | 1665 | // We read the entire interval without finding timestamps for all |
michael@0 | 1666 | // active tracks. We know the interval start offset is before the seek |
michael@0 | 1667 | // target, and the interval end is after the seek target, and we can't |
michael@0 | 1668 | // terminate inside the interval, so we terminate the seek at the |
michael@0 | 1669 | // start of the interval. |
michael@0 | 1670 | interval = 0; |
michael@0 | 1671 | break; |
michael@0 | 1672 | } |
michael@0 | 1673 | |
michael@0 | 1674 | // We should backoff; cause the guess to back off from the end, so |
michael@0 | 1675 | // that we've got more room to capture. |
michael@0 | 1676 | mustBackoff = true; |
michael@0 | 1677 | continue; |
michael@0 | 1678 | } |
michael@0 | 1679 | |
michael@0 | 1680 | // We've found appropriate time stamps here. Proceed to bisect |
michael@0 | 1681 | // the search space. |
michael@0 | 1682 | granuleTime = std::max(audioTime, videoTime); |
michael@0 | 1683 | NS_ASSERTION(granuleTime > 0, "Must get a granuletime"); |
michael@0 | 1684 | break; |
michael@0 | 1685 | } // End of "until we determine time at guess offset" loop. |
michael@0 | 1686 | |
michael@0 | 1687 | if (interval == 0) { |
michael@0 | 1688 | // Seek termination condition; we've found the page boundary of the |
michael@0 | 1689 | // last page before the target, and the first page after the target. |
michael@0 | 1690 | SEEK_LOG(PR_LOG_DEBUG, ("Terminating seek at offset=%lld", startOffset)); |
michael@0 | 1691 | NS_ASSERTION(startTime < aTarget, "Start time must always be less than target"); |
michael@0 | 1692 | res = resource->Seek(nsISeekableStream::NS_SEEK_SET, startOffset); |
michael@0 | 1693 | NS_ENSURE_SUCCESS(res,res); |
michael@0 | 1694 | if (NS_FAILED(ResetDecode())) { |
michael@0 | 1695 | return NS_ERROR_FAILURE; |
michael@0 | 1696 | } |
michael@0 | 1697 | break; |
michael@0 | 1698 | } |
michael@0 | 1699 | |
michael@0 | 1700 | SEEK_LOG(PR_LOG_DEBUG, ("Time at offset %lld is %lld", guess, granuleTime)); |
michael@0 | 1701 | if (granuleTime < seekTarget && granuleTime > seekLowerBound) { |
michael@0 | 1702 | // We're within the fuzzy region in which we want to terminate the search. |
michael@0 | 1703 | res = resource->Seek(nsISeekableStream::NS_SEEK_SET, pageOffset); |
michael@0 | 1704 | NS_ENSURE_SUCCESS(res,res); |
michael@0 | 1705 | if (NS_FAILED(ResetDecode())) { |
michael@0 | 1706 | return NS_ERROR_FAILURE; |
michael@0 | 1707 | } |
michael@0 | 1708 | SEEK_LOG(PR_LOG_DEBUG, ("Terminating seek at offset=%lld", pageOffset)); |
michael@0 | 1709 | break; |
michael@0 | 1710 | } |
michael@0 | 1711 | |
michael@0 | 1712 | if (granuleTime >= seekTarget) { |
michael@0 | 1713 | // We've landed after the seek target. |
michael@0 | 1714 | NS_ASSERTION(pageOffset < endOffset, "offset_end must decrease"); |
michael@0 | 1715 | endOffset = pageOffset; |
michael@0 | 1716 | endTime = granuleTime; |
michael@0 | 1717 | } else if (granuleTime < seekTarget) { |
michael@0 | 1718 | // Landed before seek target. |
michael@0 | 1719 | NS_ASSERTION(pageOffset >= startOffset + startLength, |
michael@0 | 1720 | "Bisection point should be at or after end of first page in interval"); |
michael@0 | 1721 | startOffset = pageOffset; |
michael@0 | 1722 | startLength = pageLength; |
michael@0 | 1723 | startTime = granuleTime; |
michael@0 | 1724 | } |
michael@0 | 1725 | NS_ASSERTION(startTime < seekTarget, "Must be before seek target"); |
michael@0 | 1726 | NS_ASSERTION(endTime >= seekTarget, "End must be after seek target"); |
michael@0 | 1727 | } |
michael@0 | 1728 | |
michael@0 | 1729 | SEEK_LOG(PR_LOG_DEBUG, ("Seek complete in %d bisections.", hops)); |
michael@0 | 1730 | |
michael@0 | 1731 | return NS_OK; |
michael@0 | 1732 | } |
michael@0 | 1733 | |
michael@0 | 1734 | nsresult OggReader::GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime) |
michael@0 | 1735 | { |
michael@0 | 1736 | { |
michael@0 | 1737 | mozilla::ReentrantMonitorAutoEnter mon(mMonitor); |
michael@0 | 1738 | if (mIsChained) |
michael@0 | 1739 | return NS_ERROR_FAILURE; |
michael@0 | 1740 | } |
michael@0 | 1741 | #ifdef OGG_ESTIMATE_BUFFERED |
michael@0 | 1742 | return MediaDecoderReader::GetBuffered(aBuffered, aStartTime); |
michael@0 | 1743 | #else |
michael@0 | 1744 | // HasAudio and HasVideo are not used here as they take a lock and cause |
michael@0 | 1745 | // a deadlock. Accessing mInfo doesn't require a lock - it doesn't change |
michael@0 | 1746 | // after metadata is read. |
michael@0 | 1747 | if (!mInfo.HasValidMedia()) { |
michael@0 | 1748 | // No need to search through the file if there are no audio or video tracks |
michael@0 | 1749 | return NS_OK; |
michael@0 | 1750 | } |
michael@0 | 1751 | |
michael@0 | 1752 | MediaResource* resource = mDecoder->GetResource(); |
michael@0 | 1753 | nsTArray<MediaByteRange> ranges; |
michael@0 | 1754 | nsresult res = resource->GetCachedRanges(ranges); |
michael@0 | 1755 | NS_ENSURE_SUCCESS(res, res); |
michael@0 | 1756 | |
michael@0 | 1757 | // Traverse across the buffered byte ranges, determining the time ranges |
michael@0 | 1758 | // they contain. MediaResource::GetNextCachedData(offset) returns -1 when |
michael@0 | 1759 | // offset is after the end of the media resource, or there's no more cached |
michael@0 | 1760 | // data after the offset. This loop will run until we've checked every |
michael@0 | 1761 | // buffered range in the media, in increasing order of offset. |
michael@0 | 1762 | nsAutoOggSyncState sync; |
michael@0 | 1763 | for (uint32_t index = 0; index < ranges.Length(); index++) { |
michael@0 | 1764 | // Ensure the offsets are after the header pages. |
michael@0 | 1765 | int64_t startOffset = ranges[index].mStart; |
michael@0 | 1766 | int64_t endOffset = ranges[index].mEnd; |
michael@0 | 1767 | |
michael@0 | 1768 | // Because the granulepos time is actually the end time of the page, |
michael@0 | 1769 | // we special-case (startOffset == 0) so that the first |
michael@0 | 1770 | // buffered range always appears to be buffered from the media start |
michael@0 | 1771 | // time, rather than from the end-time of the first page. |
michael@0 | 1772 | int64_t startTime = (startOffset == 0) ? aStartTime : -1; |
michael@0 | 1773 | |
michael@0 | 1774 | // Find the start time of the range. Read pages until we find one with a |
michael@0 | 1775 | // granulepos which we can convert into a timestamp to use as the time of |
michael@0 | 1776 | // the start of the buffered range. |
michael@0 | 1777 | ogg_sync_reset(&sync.mState); |
michael@0 | 1778 | while (startTime == -1) { |
michael@0 | 1779 | ogg_page page; |
michael@0 | 1780 | int32_t discard; |
michael@0 | 1781 | PageSyncResult res = PageSync(resource, |
michael@0 | 1782 | &sync.mState, |
michael@0 | 1783 | true, |
michael@0 | 1784 | startOffset, |
michael@0 | 1785 | endOffset, |
michael@0 | 1786 | &page, |
michael@0 | 1787 | discard); |
michael@0 | 1788 | if (res == PAGE_SYNC_ERROR) { |
michael@0 | 1789 | return NS_ERROR_FAILURE; |
michael@0 | 1790 | } else if (res == PAGE_SYNC_END_OF_RANGE) { |
michael@0 | 1791 | // Hit the end of range without reading a page, give up trying to |
michael@0 | 1792 | // find a start time for this buffered range, skip onto the next one. |
michael@0 | 1793 | break; |
michael@0 | 1794 | } |
michael@0 | 1795 | |
michael@0 | 1796 | int64_t granulepos = ogg_page_granulepos(&page); |
michael@0 | 1797 | if (granulepos == -1) { |
michael@0 | 1798 | // Page doesn't have an end time, advance to the next page |
michael@0 | 1799 | // until we find one. |
michael@0 | 1800 | startOffset += page.header_len + page.body_len; |
michael@0 | 1801 | continue; |
michael@0 | 1802 | } |
michael@0 | 1803 | |
michael@0 | 1804 | uint32_t serial = ogg_page_serialno(&page); |
michael@0 | 1805 | if (mVorbisState && serial == mVorbisSerial) { |
michael@0 | 1806 | startTime = VorbisState::Time(&mVorbisInfo, granulepos); |
michael@0 | 1807 | NS_ASSERTION(startTime > 0, "Must have positive start time"); |
michael@0 | 1808 | } |
michael@0 | 1809 | #ifdef MOZ_OPUS |
michael@0 | 1810 | else if (mOpusState && serial == mOpusSerial) { |
michael@0 | 1811 | startTime = OpusState::Time(mOpusPreSkip, granulepos); |
michael@0 | 1812 | NS_ASSERTION(startTime > 0, "Must have positive start time"); |
michael@0 | 1813 | } |
michael@0 | 1814 | #endif /* MOZ_OPUS */ |
michael@0 | 1815 | else if (mTheoraState && serial == mTheoraSerial) { |
michael@0 | 1816 | startTime = TheoraState::Time(&mTheoraInfo, granulepos); |
michael@0 | 1817 | NS_ASSERTION(startTime > 0, "Must have positive start time"); |
michael@0 | 1818 | } |
michael@0 | 1819 | else if (mCodecStore.Contains(serial)) { |
michael@0 | 1820 | // Stream is not the theora or vorbis stream we're playing, |
michael@0 | 1821 | // but is one that we have header data for. |
michael@0 | 1822 | startOffset += page.header_len + page.body_len; |
michael@0 | 1823 | continue; |
michael@0 | 1824 | } |
michael@0 | 1825 | else { |
michael@0 | 1826 | // Page is for a stream we don't know about (possibly a chained |
michael@0 | 1827 | // ogg), return OK to abort the finding any further ranges. This |
michael@0 | 1828 | // prevents us searching through the rest of the media when we |
michael@0 | 1829 | // may not be able to extract timestamps from it. |
michael@0 | 1830 | SetChained(true); |
michael@0 | 1831 | return NS_OK; |
michael@0 | 1832 | } |
michael@0 | 1833 | } |
michael@0 | 1834 | |
michael@0 | 1835 | if (startTime != -1) { |
michael@0 | 1836 | // We were able to find a start time for that range, see if we can |
michael@0 | 1837 | // find an end time. |
michael@0 | 1838 | int64_t endTime = RangeEndTime(startOffset, endOffset, true); |
michael@0 | 1839 | if (endTime != -1) { |
michael@0 | 1840 | aBuffered->Add((startTime - aStartTime) / static_cast<double>(USECS_PER_S), |
michael@0 | 1841 | (endTime - aStartTime) / static_cast<double>(USECS_PER_S)); |
michael@0 | 1842 | } |
michael@0 | 1843 | } |
michael@0 | 1844 | } |
michael@0 | 1845 | |
michael@0 | 1846 | return NS_OK; |
michael@0 | 1847 | #endif |
michael@0 | 1848 | } |
michael@0 | 1849 | |
michael@0 | 1850 | OggCodecStore::OggCodecStore() |
michael@0 | 1851 | : mMonitor("CodecStore") |
michael@0 | 1852 | { |
michael@0 | 1853 | } |
michael@0 | 1854 | |
michael@0 | 1855 | void OggCodecStore::Add(uint32_t serial, OggCodecState* codecState) |
michael@0 | 1856 | { |
michael@0 | 1857 | MonitorAutoLock mon(mMonitor); |
michael@0 | 1858 | mCodecStates.Put(serial, codecState); |
michael@0 | 1859 | } |
michael@0 | 1860 | |
michael@0 | 1861 | bool OggCodecStore::Contains(uint32_t serial) |
michael@0 | 1862 | { |
michael@0 | 1863 | MonitorAutoLock mon(mMonitor); |
michael@0 | 1864 | return mCodecStates.Get(serial, nullptr); |
michael@0 | 1865 | } |
michael@0 | 1866 | |
michael@0 | 1867 | OggCodecState* OggCodecStore::Get(uint32_t serial) |
michael@0 | 1868 | { |
michael@0 | 1869 | MonitorAutoLock mon(mMonitor); |
michael@0 | 1870 | return mCodecStates.Get(serial); |
michael@0 | 1871 | } |
michael@0 | 1872 | |
michael@0 | 1873 | } // namespace mozilla |
michael@0 | 1874 |