1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/media/ogg/OggReader.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1874 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim:set ts=2 sw=2 sts=2 et cindent: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "mozilla/DebugOnly.h" 1.11 + 1.12 +#include "nsError.h" 1.13 +#include "MediaDecoderStateMachine.h" 1.14 +#include "MediaDecoder.h" 1.15 +#include "OggReader.h" 1.16 +#include "VideoUtils.h" 1.17 +#include "theora/theoradec.h" 1.18 +#include <algorithm> 1.19 +#ifdef MOZ_OPUS 1.20 +#include "opus/opus.h" 1.21 +extern "C" { 1.22 +#include "opus/opus_multistream.h" 1.23 +} 1.24 +#endif 1.25 +#include "mozilla/dom/TimeRanges.h" 1.26 +#include "mozilla/TimeStamp.h" 1.27 +#include "VorbisUtils.h" 1.28 +#include "MediaMetadataManager.h" 1.29 +#include "nsISeekableStream.h" 1.30 +#include "gfx2DGlue.h" 1.31 + 1.32 +using namespace mozilla::gfx; 1.33 + 1.34 +namespace mozilla { 1.35 + 1.36 +// On B2G estimate the buffered ranges rather than calculating them explicitly. 1.37 +// This prevents us doing I/O on the main thread, which is prohibited in B2G. 1.38 +#ifdef MOZ_WIDGET_GONK 1.39 +#define OGG_ESTIMATE_BUFFERED 1 1.40 +#endif 1.41 + 1.42 +// Un-comment to enable logging of seek bisections. 1.43 +//#define SEEK_LOGGING 1.44 + 1.45 +#ifdef PR_LOGGING 1.46 +extern PRLogModuleInfo* gMediaDecoderLog; 1.47 +#define LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg) 1.48 +#ifdef SEEK_LOGGING 1.49 +#define SEEK_LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg) 1.50 +#else 1.51 +#define SEEK_LOG(type, msg) 1.52 +#endif 1.53 +#else 1.54 +#define LOG(type, msg) 1.55 +#define SEEK_LOG(type, msg) 1.56 +#endif 1.57 + 1.58 +// The number of microseconds of "fuzz" we use in a bisection search over 1.59 +// HTTP. When we're seeking with fuzz, we'll stop the search if a bisection 1.60 +// lands between the seek target and SEEK_FUZZ_USECS microseconds before the 1.61 +// seek target. This is becaue it's usually quicker to just keep downloading 1.62 +// from an exisiting connection than to do another bisection inside that 1.63 +// small range, which would open a new HTTP connetion. 1.64 +static const uint32_t SEEK_FUZZ_USECS = 500000; 1.65 + 1.66 +// The number of microseconds of "pre-roll" we use for Opus streams. 1.67 +// The specification recommends 80 ms. 1.68 +#ifdef MOZ_OPUS 1.69 +static const int64_t SEEK_OPUS_PREROLL = 80 * USECS_PER_MS; 1.70 +#endif /* MOZ_OPUS */ 1.71 + 1.72 +enum PageSyncResult { 1.73 + PAGE_SYNC_ERROR = 1, 1.74 + PAGE_SYNC_END_OF_RANGE= 2, 1.75 + PAGE_SYNC_OK = 3 1.76 +}; 1.77 + 1.78 +// Reads a page from the media resource. 1.79 +static PageSyncResult 1.80 +PageSync(MediaResource* aResource, 1.81 + ogg_sync_state* aState, 1.82 + bool aCachedDataOnly, 1.83 + int64_t aOffset, 1.84 + int64_t aEndOffset, 1.85 + ogg_page* aPage, 1.86 + int& aSkippedBytes); 1.87 + 1.88 +// Chunk size to read when reading Ogg files. Average Ogg page length 1.89 +// is about 4300 bytes, so we read the file in chunks larger than that. 1.90 +static const int PAGE_STEP = 8192; 1.91 + 1.92 +OggReader::OggReader(AbstractMediaDecoder* aDecoder) 1.93 + : MediaDecoderReader(aDecoder), 1.94 + mMonitor("OggReader"), 1.95 + mTheoraState(nullptr), 1.96 + mVorbisState(nullptr), 1.97 +#ifdef MOZ_OPUS 1.98 + mOpusState(nullptr), 1.99 + mOpusEnabled(MediaDecoder::IsOpusEnabled()), 1.100 +#endif /* MOZ_OPUS */ 1.101 + mSkeletonState(nullptr), 1.102 + mVorbisSerial(0), 1.103 + mOpusSerial(0), 1.104 + mTheoraSerial(0), 1.105 + mOpusPreSkip(0), 1.106 + mIsChained(false), 1.107 + mDecodedAudioFrames(0) 1.108 +{ 1.109 + MOZ_COUNT_CTOR(OggReader); 1.110 + memset(&mTheoraInfo, 0, sizeof(mTheoraInfo)); 1.111 +} 1.112 + 1.113 +OggReader::~OggReader() 1.114 +{ 1.115 + ogg_sync_clear(&mOggState); 1.116 + MOZ_COUNT_DTOR(OggReader); 1.117 +} 1.118 + 1.119 +nsresult OggReader::Init(MediaDecoderReader* aCloneDonor) { 1.120 + int ret = ogg_sync_init(&mOggState); 1.121 + NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE); 1.122 + return NS_OK; 1.123 +} 1.124 + 1.125 +nsresult OggReader::ResetDecode() 1.126 +{ 1.127 + return ResetDecode(false); 1.128 +} 1.129 + 1.130 +nsresult OggReader::ResetDecode(bool start) 1.131 +{ 1.132 + NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); 1.133 + nsresult res = NS_OK; 1.134 + 1.135 + if (NS_FAILED(MediaDecoderReader::ResetDecode())) { 1.136 + res = NS_ERROR_FAILURE; 1.137 + } 1.138 + 1.139 + // Discard any previously buffered packets/pages. 1.140 + ogg_sync_reset(&mOggState); 1.141 + if (mVorbisState && NS_FAILED(mVorbisState->Reset())) { 1.142 + res = NS_ERROR_FAILURE; 1.143 + } 1.144 +#ifdef MOZ_OPUS 1.145 + if (mOpusState && NS_FAILED(mOpusState->Reset(start))) { 1.146 + res = NS_ERROR_FAILURE; 1.147 + } 1.148 +#endif /* MOZ_OPUS */ 1.149 + if (mTheoraState && NS_FAILED(mTheoraState->Reset())) { 1.150 + res = NS_ERROR_FAILURE; 1.151 + } 1.152 + 1.153 + return res; 1.154 +} 1.155 + 1.156 +bool OggReader::ReadHeaders(OggCodecState* aState) 1.157 +{ 1.158 + while (!aState->DoneReadingHeaders()) { 1.159 + ogg_packet* packet = NextOggPacket(aState); 1.160 + // DecodeHeader is responsible for releasing packet. 1.161 + if (!packet || !aState->DecodeHeader(packet)) { 1.162 + aState->Deactivate(); 1.163 + return false; 1.164 + } 1.165 + } 1.166 + return aState->Init(); 1.167 +} 1.168 + 1.169 +void OggReader::BuildSerialList(nsTArray<uint32_t>& aTracks) 1.170 +{ 1.171 + if (HasVideo()) { 1.172 + aTracks.AppendElement(mTheoraState->mSerial); 1.173 + } 1.174 + if (HasAudio()) { 1.175 + if (mVorbisState) { 1.176 + aTracks.AppendElement(mVorbisState->mSerial); 1.177 +#ifdef MOZ_OPUS 1.178 + } else if (mOpusState) { 1.179 + aTracks.AppendElement(mOpusState->mSerial); 1.180 +#endif /* MOZ_OPUS */ 1.181 + } 1.182 + } 1.183 +} 1.184 + 1.185 +nsresult OggReader::ReadMetadata(MediaInfo* aInfo, 1.186 + MetadataTags** aTags) 1.187 +{ 1.188 + NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); 1.189 + 1.190 + // We read packets until all bitstreams have read all their header packets. 1.191 + // We record the offset of the first non-header page so that we know 1.192 + // what page to seek to when seeking to the media start. 1.193 + 1.194 + NS_ASSERTION(aTags, "Called with null MetadataTags**."); 1.195 + *aTags = nullptr; 1.196 + 1.197 + ogg_page page; 1.198 + nsAutoTArray<OggCodecState*,4> bitstreams; 1.199 + bool readAllBOS = false; 1.200 + while (!readAllBOS) { 1.201 + if (!ReadOggPage(&page)) { 1.202 + // Some kind of error... 1.203 + break; 1.204 + } 1.205 + 1.206 + int serial = ogg_page_serialno(&page); 1.207 + OggCodecState* codecState = 0; 1.208 + 1.209 + if (!ogg_page_bos(&page)) { 1.210 + // We've encountered a non Beginning Of Stream page. No more BOS pages 1.211 + // can follow in this Ogg segment, so there will be no other bitstreams 1.212 + // in the Ogg (unless it's invalid). 1.213 + readAllBOS = true; 1.214 + } else if (!mCodecStore.Contains(serial)) { 1.215 + // We've not encountered a stream with this serial number before. Create 1.216 + // an OggCodecState to demux it, and map that to the OggCodecState 1.217 + // in mCodecStates. 1.218 + codecState = OggCodecState::Create(&page); 1.219 + mCodecStore.Add(serial, codecState); 1.220 + bitstreams.AppendElement(codecState); 1.221 + if (codecState && 1.222 + codecState->GetType() == OggCodecState::TYPE_VORBIS && 1.223 + !mVorbisState) 1.224 + { 1.225 + // First Vorbis bitstream, we'll play this one. Subsequent Vorbis 1.226 + // bitstreams will be ignored. 1.227 + mVorbisState = static_cast<VorbisState*>(codecState); 1.228 + } 1.229 + if (codecState && 1.230 + codecState->GetType() == OggCodecState::TYPE_THEORA && 1.231 + !mTheoraState) 1.232 + { 1.233 + // First Theora bitstream, we'll play this one. Subsequent Theora 1.234 + // bitstreams will be ignored. 1.235 + mTheoraState = static_cast<TheoraState*>(codecState); 1.236 + } 1.237 +#ifdef MOZ_OPUS 1.238 + if (codecState && 1.239 + codecState->GetType() == OggCodecState::TYPE_OPUS && 1.240 + !mOpusState) 1.241 + { 1.242 + if (mOpusEnabled) { 1.243 + mOpusState = static_cast<OpusState*>(codecState); 1.244 + } else { 1.245 + NS_WARNING("Opus decoding disabled." 1.246 + " See media.opus.enabled in about:config"); 1.247 + } 1.248 + } 1.249 +#endif /* MOZ_OPUS */ 1.250 + if (codecState && 1.251 + codecState->GetType() == OggCodecState::TYPE_SKELETON && 1.252 + !mSkeletonState) 1.253 + { 1.254 + mSkeletonState = static_cast<SkeletonState*>(codecState); 1.255 + } 1.256 + } 1.257 + 1.258 + codecState = mCodecStore.Get(serial); 1.259 + NS_ENSURE_TRUE(codecState != nullptr, NS_ERROR_FAILURE); 1.260 + 1.261 + if (NS_FAILED(codecState->PageIn(&page))) { 1.262 + return NS_ERROR_FAILURE; 1.263 + } 1.264 + } 1.265 + 1.266 + // We've read all BOS pages, so we know the streams contained in the media. 1.267 + // Now process all available header packets in the active Theora, Vorbis and 1.268 + // Skeleton streams. 1.269 + 1.270 + // Deactivate any non-primary bitstreams. 1.271 + for (uint32_t i = 0; i < bitstreams.Length(); i++) { 1.272 + OggCodecState* s = bitstreams[i]; 1.273 + if (s != mVorbisState && 1.274 +#ifdef MOZ_OPUS 1.275 + s != mOpusState && 1.276 +#endif /* MOZ_OPUS */ 1.277 + s != mTheoraState && s != mSkeletonState) { 1.278 + s->Deactivate(); 1.279 + } 1.280 + } 1.281 + 1.282 + if (mTheoraState && ReadHeaders(mTheoraState)) { 1.283 + nsIntRect picture = nsIntRect(mTheoraState->mInfo.pic_x, 1.284 + mTheoraState->mInfo.pic_y, 1.285 + mTheoraState->mInfo.pic_width, 1.286 + mTheoraState->mInfo.pic_height); 1.287 + 1.288 + nsIntSize displaySize = nsIntSize(mTheoraState->mInfo.pic_width, 1.289 + mTheoraState->mInfo.pic_height); 1.290 + 1.291 + // Apply the aspect ratio to produce the intrinsic display size we report 1.292 + // to the element. 1.293 + ScaleDisplayByAspectRatio(displaySize, mTheoraState->mPixelAspectRatio); 1.294 + 1.295 + nsIntSize frameSize(mTheoraState->mInfo.frame_width, 1.296 + mTheoraState->mInfo.frame_height); 1.297 + if (IsValidVideoRegion(frameSize, picture, displaySize)) { 1.298 + // Video track's frame sizes will not overflow. Activate the video track. 1.299 + mInfo.mVideo.mHasVideo = true; 1.300 + mInfo.mVideo.mDisplay = displaySize; 1.301 + mPicture = picture; 1.302 + 1.303 + VideoFrameContainer* container = mDecoder->GetVideoFrameContainer(); 1.304 + if (container) { 1.305 + container->SetCurrentFrame(gfxIntSize(displaySize.width, displaySize.height), 1.306 + nullptr, 1.307 + TimeStamp::Now()); 1.308 + } 1.309 + 1.310 + // Copy Theora info data for time computations on other threads. 1.311 + memcpy(&mTheoraInfo, &mTheoraState->mInfo, sizeof(mTheoraInfo)); 1.312 + mTheoraSerial = mTheoraState->mSerial; 1.313 + } 1.314 + } 1.315 + 1.316 + if (mVorbisState && ReadHeaders(mVorbisState)) { 1.317 + mInfo.mAudio.mHasAudio = true; 1.318 + mInfo.mAudio.mRate = mVorbisState->mInfo.rate; 1.319 + mInfo.mAudio.mChannels = mVorbisState->mInfo.channels; 1.320 + // Copy Vorbis info data for time computations on other threads. 1.321 + memcpy(&mVorbisInfo, &mVorbisState->mInfo, sizeof(mVorbisInfo)); 1.322 + mVorbisInfo.codec_setup = nullptr; 1.323 + mVorbisSerial = mVorbisState->mSerial; 1.324 + *aTags = mVorbisState->GetTags(); 1.325 + } else { 1.326 + memset(&mVorbisInfo, 0, sizeof(mVorbisInfo)); 1.327 + } 1.328 +#ifdef MOZ_OPUS 1.329 + if (mOpusState && ReadHeaders(mOpusState)) { 1.330 + mInfo.mAudio.mHasAudio = true; 1.331 + mInfo.mAudio.mRate = mOpusState->mRate; 1.332 + mInfo.mAudio.mChannels = mOpusState->mChannels; 1.333 + mOpusSerial = mOpusState->mSerial; 1.334 + mOpusPreSkip = mOpusState->mPreSkip; 1.335 + 1.336 + *aTags = mOpusState->GetTags(); 1.337 + } 1.338 +#endif 1.339 + if (mSkeletonState) { 1.340 + if (!HasAudio() && !HasVideo()) { 1.341 + // We have a skeleton track, but no audio or video, may as well disable 1.342 + // the skeleton, we can't do anything useful with this media. 1.343 + mSkeletonState->Deactivate(); 1.344 + } else if (ReadHeaders(mSkeletonState) && mSkeletonState->HasIndex()) { 1.345 + // Extract the duration info out of the index, so we don't need to seek to 1.346 + // the end of resource to get it. 1.347 + nsAutoTArray<uint32_t, 2> tracks; 1.348 + BuildSerialList(tracks); 1.349 + int64_t duration = 0; 1.350 + if (NS_SUCCEEDED(mSkeletonState->GetDuration(tracks, duration))) { 1.351 + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); 1.352 + mDecoder->SetMediaDuration(duration); 1.353 + LOG(PR_LOG_DEBUG, ("Got duration from Skeleton index %lld", duration)); 1.354 + } 1.355 + } 1.356 + } 1.357 + 1.358 + if (HasAudio() || HasVideo()) { 1.359 + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); 1.360 + 1.361 + MediaResource* resource = mDecoder->GetResource(); 1.362 + if (mDecoder->GetMediaDuration() == -1 && 1.363 + !mDecoder->IsShutdown() && 1.364 + resource->GetLength() >= 0 && 1.365 + mDecoder->IsMediaSeekable()) 1.366 + { 1.367 + // We didn't get a duration from the index or a Content-Duration header. 1.368 + // Seek to the end of file to find the end time. 1.369 + mDecoder->GetResource()->StartSeekingForMetadata(); 1.370 + int64_t length = resource->GetLength(); 1.371 + 1.372 + NS_ASSERTION(length > 0, "Must have a content length to get end time"); 1.373 + 1.374 + int64_t endTime = 0; 1.375 + { 1.376 + ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor()); 1.377 + endTime = RangeEndTime(length); 1.378 + } 1.379 + if (endTime != -1) { 1.380 + mDecoder->SetMediaEndTime(endTime); 1.381 + LOG(PR_LOG_DEBUG, ("Got Ogg duration from seeking to end %lld", endTime)); 1.382 + } 1.383 + mDecoder->GetResource()->EndSeekingForMetadata(); 1.384 + } else if (mDecoder->GetMediaDuration() == -1) { 1.385 + // We don't have a duration, and we don't know enough about the resource 1.386 + // to try a seek. Abort trying to get a duration. This happens for example 1.387 + // when the server says it accepts range requests, but does not give us a 1.388 + // Content-Length. 1.389 + mDecoder->SetTransportSeekable(false); 1.390 + } 1.391 + } else { 1.392 + return NS_ERROR_FAILURE; 1.393 + } 1.394 + *aInfo = mInfo; 1.395 + 1.396 + return NS_OK; 1.397 +} 1.398 + 1.399 +nsresult OggReader::DecodeVorbis(ogg_packet* aPacket) { 1.400 + NS_ASSERTION(aPacket->granulepos != -1, "Must know vorbis granulepos!"); 1.401 + 1.402 + if (vorbis_synthesis(&mVorbisState->mBlock, aPacket) != 0) { 1.403 + return NS_ERROR_FAILURE; 1.404 + } 1.405 + if (vorbis_synthesis_blockin(&mVorbisState->mDsp, 1.406 + &mVorbisState->mBlock) != 0) 1.407 + { 1.408 + return NS_ERROR_FAILURE; 1.409 + } 1.410 + 1.411 + VorbisPCMValue** pcm = 0; 1.412 + int32_t frames = 0; 1.413 + uint32_t channels = mVorbisState->mInfo.channels; 1.414 + ogg_int64_t endFrame = aPacket->granulepos; 1.415 + while ((frames = vorbis_synthesis_pcmout(&mVorbisState->mDsp, &pcm)) > 0) { 1.416 + mVorbisState->ValidateVorbisPacketSamples(aPacket, frames); 1.417 + nsAutoArrayPtr<AudioDataValue> buffer(new AudioDataValue[frames * channels]); 1.418 + for (uint32_t j = 0; j < channels; ++j) { 1.419 + VorbisPCMValue* channel = pcm[j]; 1.420 + for (uint32_t i = 0; i < uint32_t(frames); ++i) { 1.421 + buffer[i*channels + j] = MOZ_CONVERT_VORBIS_SAMPLE(channel[i]); 1.422 + } 1.423 + } 1.424 + 1.425 + // No channel mapping for more than 8 channels. 1.426 + if (channels > 8) { 1.427 + return NS_ERROR_FAILURE; 1.428 + } 1.429 + 1.430 + int64_t duration = mVorbisState->Time((int64_t)frames); 1.431 + int64_t startTime = mVorbisState->Time(endFrame - frames); 1.432 + mAudioQueue.Push(new AudioData(mDecoder->GetResource()->Tell(), 1.433 + startTime, 1.434 + duration, 1.435 + frames, 1.436 + buffer.forget(), 1.437 + channels)); 1.438 + 1.439 + mDecodedAudioFrames += frames; 1.440 + 1.441 + endFrame -= frames; 1.442 + if (vorbis_synthesis_read(&mVorbisState->mDsp, frames) != 0) { 1.443 + return NS_ERROR_FAILURE; 1.444 + } 1.445 + } 1.446 + return NS_OK; 1.447 +} 1.448 +#ifdef MOZ_OPUS 1.449 +nsresult OggReader::DecodeOpus(ogg_packet* aPacket) { 1.450 + NS_ASSERTION(aPacket->granulepos != -1, "Must know opus granulepos!"); 1.451 + 1.452 + // Maximum value is 63*2880, so there's no chance of overflow. 1.453 + int32_t frames_number = opus_packet_get_nb_frames(aPacket->packet, 1.454 + aPacket->bytes); 1.455 + if (frames_number <= 0) 1.456 + return NS_ERROR_FAILURE; // Invalid packet header. 1.457 + int32_t samples = opus_packet_get_samples_per_frame(aPacket->packet, 1.458 + (opus_int32) mOpusState->mRate); 1.459 + int32_t frames = frames_number*samples; 1.460 + 1.461 + // A valid Opus packet must be between 2.5 and 120 ms long. 1.462 + if (frames < 120 || frames > 5760) 1.463 + return NS_ERROR_FAILURE; 1.464 + uint32_t channels = mOpusState->mChannels; 1.465 + nsAutoArrayPtr<AudioDataValue> buffer(new AudioDataValue[frames * channels]); 1.466 + 1.467 + // Decode to the appropriate sample type. 1.468 +#ifdef MOZ_SAMPLE_TYPE_FLOAT32 1.469 + int ret = opus_multistream_decode_float(mOpusState->mDecoder, 1.470 + aPacket->packet, aPacket->bytes, 1.471 + buffer, frames, false); 1.472 +#else 1.473 + int ret = opus_multistream_decode(mOpusState->mDecoder, 1.474 + aPacket->packet, aPacket->bytes, 1.475 + buffer, frames, false); 1.476 +#endif 1.477 + if (ret < 0) 1.478 + return NS_ERROR_FAILURE; 1.479 + NS_ASSERTION(ret == frames, "Opus decoded too few audio samples"); 1.480 + 1.481 + int64_t endFrame = aPacket->granulepos; 1.482 + int64_t startFrame; 1.483 + // If this is the last packet, perform end trimming. 1.484 + if (aPacket->e_o_s && mOpusState->mPrevPacketGranulepos != -1) { 1.485 + startFrame = mOpusState->mPrevPacketGranulepos; 1.486 + frames = static_cast<int32_t>(std::max(static_cast<int64_t>(0), 1.487 + std::min(endFrame - startFrame, 1.488 + static_cast<int64_t>(frames)))); 1.489 + } else { 1.490 + startFrame = endFrame - frames; 1.491 + } 1.492 + 1.493 + // Trim the initial frames while the decoder is settling. 1.494 + if (mOpusState->mSkip > 0) { 1.495 + int32_t skipFrames = std::min(mOpusState->mSkip, frames); 1.496 + if (skipFrames == frames) { 1.497 + // discard the whole packet 1.498 + mOpusState->mSkip -= frames; 1.499 + LOG(PR_LOG_DEBUG, ("Opus decoder skipping %d frames" 1.500 + " (whole packet)", frames)); 1.501 + return NS_OK; 1.502 + } 1.503 + int32_t keepFrames = frames - skipFrames; 1.504 + int samples = keepFrames * channels; 1.505 + nsAutoArrayPtr<AudioDataValue> trimBuffer(new AudioDataValue[samples]); 1.506 + for (int i = 0; i < samples; i++) 1.507 + trimBuffer[i] = buffer[skipFrames*channels + i]; 1.508 + 1.509 + startFrame = endFrame - keepFrames; 1.510 + frames = keepFrames; 1.511 + buffer = trimBuffer; 1.512 + 1.513 + mOpusState->mSkip -= skipFrames; 1.514 + LOG(PR_LOG_DEBUG, ("Opus decoder skipping %d frames", skipFrames)); 1.515 + } 1.516 + // Save this packet's granule position in case we need to perform end 1.517 + // trimming on the next packet. 1.518 + mOpusState->mPrevPacketGranulepos = endFrame; 1.519 + 1.520 + // Apply the header gain if one was specified. 1.521 +#ifdef MOZ_SAMPLE_TYPE_FLOAT32 1.522 + if (mOpusState->mGain != 1.0f) { 1.523 + float gain = mOpusState->mGain; 1.524 + int samples = frames * channels; 1.525 + for (int i = 0; i < samples; i++) { 1.526 + buffer[i] *= gain; 1.527 + } 1.528 + } 1.529 +#else 1.530 + if (mOpusState->mGain_Q16 != 65536) { 1.531 + int64_t gain_Q16 = mOpusState->mGain_Q16; 1.532 + int samples = frames * channels; 1.533 + for (int i = 0; i < samples; i++) { 1.534 + int32_t val = static_cast<int32_t>((gain_Q16*buffer[i] + 32768)>>16); 1.535 + buffer[i] = static_cast<AudioDataValue>(MOZ_CLIP_TO_15(val)); 1.536 + } 1.537 + } 1.538 +#endif 1.539 + 1.540 + // No channel mapping for more than 8 channels. 1.541 + if (channels > 8) { 1.542 + return NS_ERROR_FAILURE; 1.543 + } 1.544 + 1.545 + LOG(PR_LOG_DEBUG, ("Opus decoder pushing %d frames", frames)); 1.546 + int64_t startTime = mOpusState->Time(startFrame); 1.547 + int64_t endTime = mOpusState->Time(endFrame); 1.548 + mAudioQueue.Push(new AudioData(mDecoder->GetResource()->Tell(), 1.549 + startTime, 1.550 + endTime - startTime, 1.551 + frames, 1.552 + buffer.forget(), 1.553 + channels)); 1.554 + 1.555 + mDecodedAudioFrames += frames; 1.556 + 1.557 + return NS_OK; 1.558 +} 1.559 +#endif /* MOZ_OPUS */ 1.560 + 1.561 +bool OggReader::DecodeAudioData() 1.562 +{ 1.563 + NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); 1.564 + DebugOnly<bool> haveCodecState = mVorbisState != nullptr 1.565 +#ifdef MOZ_OPUS 1.566 + || mOpusState != nullptr 1.567 +#endif /* MOZ_OPUS */ 1.568 + ; 1.569 + NS_ASSERTION(haveCodecState, "Need audio codec state to decode audio"); 1.570 + 1.571 + // Read the next data packet. Skip any non-data packets we encounter. 1.572 + ogg_packet* packet = 0; 1.573 + OggCodecState* codecState; 1.574 + if (mVorbisState) 1.575 + codecState = static_cast<OggCodecState*>(mVorbisState); 1.576 +#ifdef MOZ_OPUS 1.577 + else 1.578 + codecState = static_cast<OggCodecState*>(mOpusState); 1.579 +#endif /* MOZ_OPUS */ 1.580 + do { 1.581 + if (packet) { 1.582 + OggCodecState::ReleasePacket(packet); 1.583 + } 1.584 + packet = NextOggPacket(codecState); 1.585 + } while (packet && codecState->IsHeader(packet)); 1.586 + 1.587 + if (!packet) { 1.588 + return false; 1.589 + } 1.590 + 1.591 + NS_ASSERTION(packet && packet->granulepos != -1, 1.592 + "Must have packet with known granulepos"); 1.593 + nsAutoRef<ogg_packet> autoRelease(packet); 1.594 + if (mVorbisState) { 1.595 + DecodeVorbis(packet); 1.596 +#ifdef MOZ_OPUS 1.597 + } else if (mOpusState) { 1.598 + DecodeOpus(packet); 1.599 +#endif 1.600 + } 1.601 + 1.602 + if ((packet->e_o_s) && (!ReadOggChain())) { 1.603 + // We've encountered an end of bitstream packet, or we've hit the end of 1.604 + // file while trying to decode, so inform the audio queue that there'll 1.605 + // be no more samples. 1.606 + return false; 1.607 + } 1.608 + 1.609 + return true; 1.610 +} 1.611 + 1.612 +void OggReader::SetChained(bool aIsChained) { 1.613 + { 1.614 + ReentrantMonitorAutoEnter mon(mMonitor); 1.615 + mIsChained = aIsChained; 1.616 + } 1.617 + { 1.618 + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); 1.619 + mDecoder->SetMediaSeekable(false); 1.620 + } 1.621 +} 1.622 + 1.623 +bool OggReader::ReadOggChain() 1.624 +{ 1.625 + bool chained = false; 1.626 +#ifdef MOZ_OPUS 1.627 + OpusState* newOpusState = nullptr; 1.628 +#endif /* MOZ_OPUS */ 1.629 + VorbisState* newVorbisState = nullptr; 1.630 + int channels = 0; 1.631 + long rate = 0; 1.632 + MetadataTags* tags = nullptr; 1.633 + 1.634 + if (HasVideo() || HasSkeleton() || !HasAudio()) { 1.635 + return false; 1.636 + } 1.637 + 1.638 + ogg_page page; 1.639 + if (!ReadOggPage(&page) || !ogg_page_bos(&page)) { 1.640 + return false; 1.641 + } 1.642 + 1.643 + int serial = ogg_page_serialno(&page); 1.644 + if (mCodecStore.Contains(serial)) { 1.645 + return false; 1.646 + } 1.647 + 1.648 + nsAutoPtr<OggCodecState> codecState; 1.649 + codecState = OggCodecState::Create(&page); 1.650 + if (!codecState) { 1.651 + return false; 1.652 + } 1.653 + 1.654 + if (mVorbisState && (codecState->GetType() == OggCodecState::TYPE_VORBIS)) { 1.655 + newVorbisState = static_cast<VorbisState*>(codecState.get()); 1.656 + } 1.657 +#ifdef MOZ_OPUS 1.658 + else if (mOpusState && (codecState->GetType() == OggCodecState::TYPE_OPUS)) { 1.659 + newOpusState = static_cast<OpusState*>(codecState.get()); 1.660 + } 1.661 +#endif 1.662 + else { 1.663 + return false; 1.664 + } 1.665 + OggCodecState* state; 1.666 + 1.667 + mCodecStore.Add(serial, codecState.forget()); 1.668 + state = mCodecStore.Get(serial); 1.669 + 1.670 + NS_ENSURE_TRUE(state != nullptr, false); 1.671 + 1.672 + if (NS_FAILED(state->PageIn(&page))) { 1.673 + return false; 1.674 + } 1.675 + 1.676 + if ((newVorbisState && ReadHeaders(newVorbisState)) && 1.677 + (mVorbisState->mInfo.rate == newVorbisState->mInfo.rate) && 1.678 + (mVorbisState->mInfo.channels == newVorbisState->mInfo.channels)) { 1.679 + mVorbisState->Reset(); 1.680 + mVorbisState = newVorbisState; 1.681 + mVorbisSerial = mVorbisState->mSerial; 1.682 + LOG(PR_LOG_DEBUG, ("New vorbis ogg link, serial=%d\n", mVorbisSerial)); 1.683 + chained = true; 1.684 + rate = mVorbisState->mInfo.rate; 1.685 + channels = mVorbisState->mInfo.channels; 1.686 + tags = mVorbisState->GetTags(); 1.687 + } 1.688 + 1.689 +#ifdef MOZ_OPUS 1.690 + if ((newOpusState && ReadHeaders(newOpusState)) && 1.691 + (mOpusState->mRate == newOpusState->mRate) && 1.692 + (mOpusState->mChannels == newOpusState->mChannels)) { 1.693 + mOpusState->Reset(); 1.694 + mOpusState = newOpusState; 1.695 + mOpusSerial = mOpusState->mSerial; 1.696 + chained = true; 1.697 + rate = mOpusState->mRate; 1.698 + channels = mOpusState->mChannels; 1.699 + tags = mOpusState->GetTags(); 1.700 + } 1.701 +#endif 1.702 + 1.703 + if (chained) { 1.704 + SetChained(true); 1.705 + { 1.706 + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); 1.707 + mDecoder->QueueMetadata((mDecodedAudioFrames * USECS_PER_S) / rate, 1.708 + channels, 1.709 + rate, 1.710 + HasAudio(), 1.711 + HasVideo(), 1.712 + tags); 1.713 + } 1.714 + return true; 1.715 + } 1.716 + 1.717 + return false; 1.718 +} 1.719 + 1.720 +nsresult OggReader::DecodeTheora(ogg_packet* aPacket, int64_t aTimeThreshold) 1.721 +{ 1.722 + NS_ASSERTION(aPacket->granulepos >= TheoraVersion(&mTheoraState->mInfo,3,2,1), 1.723 + "Packets must have valid granulepos and packetno"); 1.724 + 1.725 + int ret = th_decode_packetin(mTheoraState->mCtx, aPacket, 0); 1.726 + if (ret != 0 && ret != TH_DUPFRAME) { 1.727 + return NS_ERROR_FAILURE; 1.728 + } 1.729 + int64_t time = mTheoraState->StartTime(aPacket->granulepos); 1.730 + 1.731 + // Don't use the frame if it's outside the bounds of the presentation 1.732 + // start time in the skeleton track. Note we still must submit the frame 1.733 + // to the decoder (via th_decode_packetin), as the frames which are 1.734 + // presentable may depend on this frame's data. 1.735 + if (mSkeletonState && !mSkeletonState->IsPresentable(time)) { 1.736 + return NS_OK; 1.737 + } 1.738 + 1.739 + int64_t endTime = mTheoraState->Time(aPacket->granulepos); 1.740 + if (endTime < aTimeThreshold) { 1.741 + // The end time of this frame is already before the current playback 1.742 + // position. It will never be displayed, don't bother enqueing it. 1.743 + return NS_OK; 1.744 + } 1.745 + 1.746 + if (ret == TH_DUPFRAME) { 1.747 + VideoData* v = VideoData::CreateDuplicate(mDecoder->GetResource()->Tell(), 1.748 + time, 1.749 + endTime - time, 1.750 + aPacket->granulepos); 1.751 + mVideoQueue.Push(v); 1.752 + } else if (ret == 0) { 1.753 + th_ycbcr_buffer buffer; 1.754 + ret = th_decode_ycbcr_out(mTheoraState->mCtx, buffer); 1.755 + NS_ASSERTION(ret == 0, "th_decode_ycbcr_out failed"); 1.756 + bool isKeyframe = th_packet_iskeyframe(aPacket) == 1; 1.757 + VideoData::YCbCrBuffer b; 1.758 + for (uint32_t i=0; i < 3; ++i) { 1.759 + b.mPlanes[i].mData = buffer[i].data; 1.760 + b.mPlanes[i].mHeight = buffer[i].height; 1.761 + b.mPlanes[i].mWidth = buffer[i].width; 1.762 + b.mPlanes[i].mStride = buffer[i].stride; 1.763 + b.mPlanes[i].mOffset = b.mPlanes[i].mSkip = 0; 1.764 + } 1.765 + 1.766 + VideoData *v = VideoData::Create(mInfo.mVideo, 1.767 + mDecoder->GetImageContainer(), 1.768 + mDecoder->GetResource()->Tell(), 1.769 + time, 1.770 + endTime - time, 1.771 + b, 1.772 + isKeyframe, 1.773 + aPacket->granulepos, 1.774 + ToIntRect(mPicture)); 1.775 + if (!v) { 1.776 + // There may be other reasons for this error, but for 1.777 + // simplicity just assume the worst case: out of memory. 1.778 + NS_WARNING("Failed to allocate memory for video frame"); 1.779 + return NS_ERROR_OUT_OF_MEMORY; 1.780 + } 1.781 + mVideoQueue.Push(v); 1.782 + } 1.783 + return NS_OK; 1.784 +} 1.785 + 1.786 +bool OggReader::DecodeVideoFrame(bool &aKeyframeSkip, 1.787 + int64_t aTimeThreshold) 1.788 +{ 1.789 + NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); 1.790 + 1.791 + // Record number of frames decoded and parsed. Automatically update the 1.792 + // stats counters using the AutoNotifyDecoded stack-based class. 1.793 + uint32_t parsed = 0, decoded = 0; 1.794 + AbstractMediaDecoder::AutoNotifyDecoded autoNotify(mDecoder, parsed, decoded); 1.795 + 1.796 + // Read the next data packet. Skip any non-data packets we encounter. 1.797 + ogg_packet* packet = 0; 1.798 + do { 1.799 + if (packet) { 1.800 + OggCodecState::ReleasePacket(packet); 1.801 + } 1.802 + packet = NextOggPacket(mTheoraState); 1.803 + } while (packet && mTheoraState->IsHeader(packet)); 1.804 + if (!packet) { 1.805 + return false; 1.806 + } 1.807 + nsAutoRef<ogg_packet> autoRelease(packet); 1.808 + 1.809 + parsed++; 1.810 + NS_ASSERTION(packet && packet->granulepos != -1, 1.811 + "Must know first packet's granulepos"); 1.812 + bool eos = packet->e_o_s; 1.813 + int64_t frameEndTime = mTheoraState->Time(packet->granulepos); 1.814 + if (!aKeyframeSkip || 1.815 + (th_packet_iskeyframe(packet) && frameEndTime >= aTimeThreshold)) 1.816 + { 1.817 + aKeyframeSkip = false; 1.818 + nsresult res = DecodeTheora(packet, aTimeThreshold); 1.819 + decoded++; 1.820 + if (NS_FAILED(res)) { 1.821 + return false; 1.822 + } 1.823 + } 1.824 + 1.825 + if (eos) { 1.826 + // We've encountered an end of bitstream packet. Inform the queue that 1.827 + // there will be no more frames. 1.828 + return false; 1.829 + } 1.830 + 1.831 + return true; 1.832 +} 1.833 + 1.834 +bool OggReader::ReadOggPage(ogg_page* aPage) 1.835 +{ 1.836 + NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); 1.837 + 1.838 + int ret = 0; 1.839 + while((ret = ogg_sync_pageseek(&mOggState, aPage)) <= 0) { 1.840 + if (ret < 0) { 1.841 + // Lost page sync, have to skip up to next page. 1.842 + continue; 1.843 + } 1.844 + // Returns a buffer that can be written too 1.845 + // with the given size. This buffer is stored 1.846 + // in the ogg synchronisation structure. 1.847 + char* buffer = ogg_sync_buffer(&mOggState, 4096); 1.848 + NS_ASSERTION(buffer, "ogg_sync_buffer failed"); 1.849 + 1.850 + // Read from the resource into the buffer 1.851 + uint32_t bytesRead = 0; 1.852 + 1.853 + nsresult rv = mDecoder->GetResource()->Read(buffer, 4096, &bytesRead); 1.854 + if (NS_FAILED(rv) || (bytesRead == 0 && ret == 0)) { 1.855 + // End of file. 1.856 + return false; 1.857 + } 1.858 + 1.859 + // Update the synchronisation layer with the number 1.860 + // of bytes written to the buffer 1.861 + ret = ogg_sync_wrote(&mOggState, bytesRead); 1.862 + NS_ENSURE_TRUE(ret == 0, false); 1.863 + } 1.864 + 1.865 + return true; 1.866 +} 1.867 + 1.868 +ogg_packet* OggReader::NextOggPacket(OggCodecState* aCodecState) 1.869 +{ 1.870 + NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); 1.871 + 1.872 + if (!aCodecState || !aCodecState->mActive) { 1.873 + return nullptr; 1.874 + } 1.875 + 1.876 + ogg_packet* packet; 1.877 + while ((packet = aCodecState->PacketOut()) == nullptr) { 1.878 + // The codec state does not have any buffered pages, so try to read another 1.879 + // page from the channel. 1.880 + ogg_page page; 1.881 + if (!ReadOggPage(&page)) { 1.882 + return nullptr; 1.883 + } 1.884 + 1.885 + uint32_t serial = ogg_page_serialno(&page); 1.886 + OggCodecState* codecState = nullptr; 1.887 + codecState = mCodecStore.Get(serial); 1.888 + if (codecState && NS_FAILED(codecState->PageIn(&page))) { 1.889 + return nullptr; 1.890 + } 1.891 + } 1.892 + 1.893 + return packet; 1.894 +} 1.895 + 1.896 +// Returns an ogg page's checksum. 1.897 +static ogg_uint32_t 1.898 +GetChecksum(ogg_page* page) 1.899 +{ 1.900 + if (page == 0 || page->header == 0 || page->header_len < 25) { 1.901 + return 0; 1.902 + } 1.903 + const unsigned char* p = page->header + 22; 1.904 + uint32_t c = p[0] + 1.905 + (p[1] << 8) + 1.906 + (p[2] << 16) + 1.907 + (p[3] << 24); 1.908 + return c; 1.909 +} 1.910 + 1.911 +int64_t OggReader::RangeStartTime(int64_t aOffset) 1.912 +{ 1.913 + NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); 1.914 + MediaResource* resource = mDecoder->GetResource(); 1.915 + NS_ENSURE_TRUE(resource != nullptr, 0); 1.916 + nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET, aOffset); 1.917 + NS_ENSURE_SUCCESS(res, 0); 1.918 + int64_t startTime = 0; 1.919 + MediaDecoderReader::FindStartTime(startTime); 1.920 + return startTime; 1.921 +} 1.922 + 1.923 +struct nsAutoOggSyncState { 1.924 + nsAutoOggSyncState() { 1.925 + ogg_sync_init(&mState); 1.926 + } 1.927 + ~nsAutoOggSyncState() { 1.928 + ogg_sync_clear(&mState); 1.929 + } 1.930 + ogg_sync_state mState; 1.931 +}; 1.932 + 1.933 +int64_t OggReader::RangeEndTime(int64_t aEndOffset) 1.934 +{ 1.935 + NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(), 1.936 + "Should be on state machine or decode thread."); 1.937 + 1.938 + MediaResource* resource = mDecoder->GetResource(); 1.939 + NS_ENSURE_TRUE(resource != nullptr, -1); 1.940 + int64_t position = resource->Tell(); 1.941 + int64_t endTime = RangeEndTime(0, aEndOffset, false); 1.942 + nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET, position); 1.943 + NS_ENSURE_SUCCESS(res, -1); 1.944 + return endTime; 1.945 +} 1.946 + 1.947 +int64_t OggReader::RangeEndTime(int64_t aStartOffset, 1.948 + int64_t aEndOffset, 1.949 + bool aCachedDataOnly) 1.950 +{ 1.951 + MediaResource* resource = mDecoder->GetResource(); 1.952 + nsAutoOggSyncState sync; 1.953 + 1.954 + // We need to find the last page which ends before aEndOffset that 1.955 + // has a granulepos that we can convert to a timestamp. We do this by 1.956 + // backing off from aEndOffset until we encounter a page on which we can 1.957 + // interpret the granulepos. If while backing off we encounter a page which 1.958 + // we've previously encountered before, we'll either backoff again if we 1.959 + // haven't found an end time yet, or return the last end time found. 1.960 + const int step = 5000; 1.961 + const int maxOggPageSize = 65306; 1.962 + int64_t readStartOffset = aEndOffset; 1.963 + int64_t readLimitOffset = aEndOffset; 1.964 + int64_t readHead = aEndOffset; 1.965 + int64_t endTime = -1; 1.966 + uint32_t checksumAfterSeek = 0; 1.967 + uint32_t prevChecksumAfterSeek = 0; 1.968 + bool mustBackOff = false; 1.969 + while (true) { 1.970 + ogg_page page; 1.971 + int ret = ogg_sync_pageseek(&sync.mState, &page); 1.972 + if (ret == 0) { 1.973 + // We need more data if we've not encountered a page we've seen before, 1.974 + // or we've read to the end of file. 1.975 + if (mustBackOff || readHead == aEndOffset || readHead == aStartOffset) { 1.976 + if (endTime != -1 || readStartOffset == 0) { 1.977 + // We have encountered a page before, or we're at the end of file. 1.978 + break; 1.979 + } 1.980 + mustBackOff = false; 1.981 + prevChecksumAfterSeek = checksumAfterSeek; 1.982 + checksumAfterSeek = 0; 1.983 + ogg_sync_reset(&sync.mState); 1.984 + readStartOffset = std::max(static_cast<int64_t>(0), readStartOffset - step); 1.985 + // There's no point reading more than the maximum size of 1.986 + // an Ogg page into data we've previously scanned. Any data 1.987 + // between readLimitOffset and aEndOffset must be garbage 1.988 + // and we can ignore it thereafter. 1.989 + readLimitOffset = std::min(readLimitOffset, 1.990 + readStartOffset + maxOggPageSize); 1.991 + readHead = std::max(aStartOffset, readStartOffset); 1.992 + } 1.993 + 1.994 + int64_t limit = std::min(static_cast<int64_t>(UINT32_MAX), 1.995 + aEndOffset - readHead); 1.996 + limit = std::max(static_cast<int64_t>(0), limit); 1.997 + limit = std::min(limit, static_cast<int64_t>(step)); 1.998 + uint32_t bytesToRead = static_cast<uint32_t>(limit); 1.999 + uint32_t bytesRead = 0; 1.1000 + char* buffer = ogg_sync_buffer(&sync.mState, bytesToRead); 1.1001 + NS_ASSERTION(buffer, "Must have buffer"); 1.1002 + nsresult res; 1.1003 + if (aCachedDataOnly) { 1.1004 + res = resource->ReadFromCache(buffer, readHead, bytesToRead); 1.1005 + NS_ENSURE_SUCCESS(res, -1); 1.1006 + bytesRead = bytesToRead; 1.1007 + } else { 1.1008 + NS_ASSERTION(readHead < aEndOffset, 1.1009 + "resource pos must be before range end"); 1.1010 + res = resource->Seek(nsISeekableStream::NS_SEEK_SET, readHead); 1.1011 + NS_ENSURE_SUCCESS(res, -1); 1.1012 + res = resource->Read(buffer, bytesToRead, &bytesRead); 1.1013 + NS_ENSURE_SUCCESS(res, -1); 1.1014 + } 1.1015 + readHead += bytesRead; 1.1016 + if (readHead > readLimitOffset) { 1.1017 + mustBackOff = true; 1.1018 + } 1.1019 + 1.1020 + // Update the synchronisation layer with the number 1.1021 + // of bytes written to the buffer 1.1022 + ret = ogg_sync_wrote(&sync.mState, bytesRead); 1.1023 + if (ret != 0) { 1.1024 + endTime = -1; 1.1025 + break; 1.1026 + } 1.1027 + 1.1028 + continue; 1.1029 + } 1.1030 + 1.1031 + if (ret < 0 || ogg_page_granulepos(&page) < 0) { 1.1032 + continue; 1.1033 + } 1.1034 + 1.1035 + uint32_t checksum = GetChecksum(&page); 1.1036 + if (checksumAfterSeek == 0) { 1.1037 + // This is the first page we've decoded after a backoff/seek. Remember 1.1038 + // the page checksum. If we backoff further and encounter this page 1.1039 + // again, we'll know that we won't find a page with an end time after 1.1040 + // this one, so we'll know to back off again. 1.1041 + checksumAfterSeek = checksum; 1.1042 + } 1.1043 + if (checksum == prevChecksumAfterSeek) { 1.1044 + // This page has the same checksum as the first page we encountered 1.1045 + // after the last backoff/seek. Since we've already scanned after this 1.1046 + // page and failed to find an end time, we may as well backoff again and 1.1047 + // try to find an end time from an earlier page. 1.1048 + mustBackOff = true; 1.1049 + continue; 1.1050 + } 1.1051 + 1.1052 + int64_t granulepos = ogg_page_granulepos(&page); 1.1053 + int serial = ogg_page_serialno(&page); 1.1054 + 1.1055 + OggCodecState* codecState = nullptr; 1.1056 + codecState = mCodecStore.Get(serial); 1.1057 + 1.1058 + if (!codecState) { 1.1059 + // This page is from a bitstream which we haven't encountered yet. 1.1060 + // It's probably from a new "link" in a "chained" ogg. Don't 1.1061 + // bother even trying to find a duration... 1.1062 + SetChained(true); 1.1063 + endTime = -1; 1.1064 + break; 1.1065 + } 1.1066 + 1.1067 + int64_t t = codecState->Time(granulepos); 1.1068 + if (t != -1) { 1.1069 + endTime = t; 1.1070 + } 1.1071 + } 1.1072 + 1.1073 + return endTime; 1.1074 +} 1.1075 + 1.1076 +nsresult OggReader::GetSeekRanges(nsTArray<SeekRange>& aRanges) 1.1077 +{ 1.1078 + NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); 1.1079 + nsTArray<MediaByteRange> cached; 1.1080 + nsresult res = mDecoder->GetResource()->GetCachedRanges(cached); 1.1081 + NS_ENSURE_SUCCESS(res, res); 1.1082 + 1.1083 + for (uint32_t index = 0; index < cached.Length(); index++) { 1.1084 + MediaByteRange& range = cached[index]; 1.1085 + int64_t startTime = -1; 1.1086 + int64_t endTime = -1; 1.1087 + if (NS_FAILED(ResetDecode())) { 1.1088 + return NS_ERROR_FAILURE; 1.1089 + } 1.1090 + int64_t startOffset = range.mStart; 1.1091 + int64_t endOffset = range.mEnd; 1.1092 + startTime = RangeStartTime(startOffset); 1.1093 + if (startTime != -1 && 1.1094 + ((endTime = RangeEndTime(endOffset)) != -1)) 1.1095 + { 1.1096 + NS_WARN_IF_FALSE(startTime < endTime, 1.1097 + "Start time must be before end time"); 1.1098 + aRanges.AppendElement(SeekRange(startOffset, 1.1099 + endOffset, 1.1100 + startTime, 1.1101 + endTime)); 1.1102 + } 1.1103 + } 1.1104 + if (NS_FAILED(ResetDecode())) { 1.1105 + return NS_ERROR_FAILURE; 1.1106 + } 1.1107 + return NS_OK; 1.1108 +} 1.1109 + 1.1110 +OggReader::SeekRange 1.1111 +OggReader::SelectSeekRange(const nsTArray<SeekRange>& ranges, 1.1112 + int64_t aTarget, 1.1113 + int64_t aStartTime, 1.1114 + int64_t aEndTime, 1.1115 + bool aExact) 1.1116 +{ 1.1117 + NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); 1.1118 + int64_t so = 0; 1.1119 + int64_t eo = mDecoder->GetResource()->GetLength(); 1.1120 + int64_t st = aStartTime; 1.1121 + int64_t et = aEndTime; 1.1122 + for (uint32_t i = 0; i < ranges.Length(); i++) { 1.1123 + const SeekRange &r = ranges[i]; 1.1124 + if (r.mTimeStart < aTarget) { 1.1125 + so = r.mOffsetStart; 1.1126 + st = r.mTimeStart; 1.1127 + } 1.1128 + if (r.mTimeEnd >= aTarget && r.mTimeEnd < et) { 1.1129 + eo = r.mOffsetEnd; 1.1130 + et = r.mTimeEnd; 1.1131 + } 1.1132 + 1.1133 + if (r.mTimeStart < aTarget && aTarget <= r.mTimeEnd) { 1.1134 + // Target lies exactly in this range. 1.1135 + return ranges[i]; 1.1136 + } 1.1137 + } 1.1138 + if (aExact || eo == -1) { 1.1139 + return SeekRange(); 1.1140 + } 1.1141 + return SeekRange(so, eo, st, et); 1.1142 +} 1.1143 + 1.1144 +OggReader::IndexedSeekResult OggReader::RollbackIndexedSeek(int64_t aOffset) 1.1145 +{ 1.1146 + mSkeletonState->Deactivate(); 1.1147 + MediaResource* resource = mDecoder->GetResource(); 1.1148 + NS_ENSURE_TRUE(resource != nullptr, SEEK_FATAL_ERROR); 1.1149 + nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET, aOffset); 1.1150 + NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR); 1.1151 + return SEEK_INDEX_FAIL; 1.1152 +} 1.1153 + 1.1154 +OggReader::IndexedSeekResult OggReader::SeekToKeyframeUsingIndex(int64_t aTarget) 1.1155 +{ 1.1156 + MediaResource* resource = mDecoder->GetResource(); 1.1157 + NS_ENSURE_TRUE(resource != nullptr, SEEK_FATAL_ERROR); 1.1158 + if (!HasSkeleton() || !mSkeletonState->HasIndex()) { 1.1159 + return SEEK_INDEX_FAIL; 1.1160 + } 1.1161 + // We have an index from the Skeleton track, try to use it to seek. 1.1162 + nsAutoTArray<uint32_t, 2> tracks; 1.1163 + BuildSerialList(tracks); 1.1164 + SkeletonState::nsSeekTarget keyframe; 1.1165 + if (NS_FAILED(mSkeletonState->IndexedSeekTarget(aTarget, 1.1166 + tracks, 1.1167 + keyframe))) 1.1168 + { 1.1169 + // Could not locate a keypoint for the target in the index. 1.1170 + return SEEK_INDEX_FAIL; 1.1171 + } 1.1172 + 1.1173 + // Remember original resource read cursor position so we can rollback on failure. 1.1174 + int64_t tell = resource->Tell(); 1.1175 + 1.1176 + // Seek to the keypoint returned by the index. 1.1177 + if (keyframe.mKeyPoint.mOffset > resource->GetLength() || 1.1178 + keyframe.mKeyPoint.mOffset < 0) 1.1179 + { 1.1180 + // Index must be invalid. 1.1181 + return RollbackIndexedSeek(tell); 1.1182 + } 1.1183 + LOG(PR_LOG_DEBUG, ("Seeking using index to keyframe at offset %lld\n", 1.1184 + keyframe.mKeyPoint.mOffset)); 1.1185 + nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET, 1.1186 + keyframe.mKeyPoint.mOffset); 1.1187 + NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR); 1.1188 + 1.1189 + // We've moved the read set, so reset decode. 1.1190 + res = ResetDecode(); 1.1191 + NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR); 1.1192 + 1.1193 + // Check that the page the index thinks is exactly here is actually exactly 1.1194 + // here. If not, the index is invalid. 1.1195 + ogg_page page; 1.1196 + int skippedBytes = 0; 1.1197 + PageSyncResult syncres = PageSync(resource, 1.1198 + &mOggState, 1.1199 + false, 1.1200 + keyframe.mKeyPoint.mOffset, 1.1201 + resource->GetLength(), 1.1202 + &page, 1.1203 + skippedBytes); 1.1204 + NS_ENSURE_TRUE(syncres != PAGE_SYNC_ERROR, SEEK_FATAL_ERROR); 1.1205 + if (syncres != PAGE_SYNC_OK || skippedBytes != 0) { 1.1206 + LOG(PR_LOG_DEBUG, ("Indexed-seek failure: Ogg Skeleton Index is invalid " 1.1207 + "or sync error after seek")); 1.1208 + return RollbackIndexedSeek(tell); 1.1209 + } 1.1210 + uint32_t serial = ogg_page_serialno(&page); 1.1211 + if (serial != keyframe.mSerial) { 1.1212 + // Serialno of page at offset isn't what the index told us to expect. 1.1213 + // Assume the index is invalid. 1.1214 + return RollbackIndexedSeek(tell); 1.1215 + } 1.1216 + OggCodecState* codecState = mCodecStore.Get(serial); 1.1217 + if (codecState && 1.1218 + codecState->mActive && 1.1219 + ogg_stream_pagein(&codecState->mState, &page) != 0) 1.1220 + { 1.1221 + // Couldn't insert page into the ogg resource, or somehow the resource 1.1222 + // is no longer active. 1.1223 + return RollbackIndexedSeek(tell); 1.1224 + } 1.1225 + return SEEK_OK; 1.1226 +} 1.1227 + 1.1228 +nsresult OggReader::SeekInBufferedRange(int64_t aTarget, 1.1229 + int64_t aAdjustedTarget, 1.1230 + int64_t aStartTime, 1.1231 + int64_t aEndTime, 1.1232 + const nsTArray<SeekRange>& aRanges, 1.1233 + const SeekRange& aRange) 1.1234 +{ 1.1235 + LOG(PR_LOG_DEBUG, ("%p Seeking in buffered data to %lld using bisection search", mDecoder, aTarget)); 1.1236 + nsresult res = NS_OK; 1.1237 + if (HasVideo() || aAdjustedTarget >= aTarget) { 1.1238 + // We know the exact byte range in which the target must lie. It must 1.1239 + // be buffered in the media cache. Seek there. 1.1240 + nsresult res = SeekBisection(aTarget, aRange, 0); 1.1241 + if (NS_FAILED(res) || !HasVideo()) { 1.1242 + return res; 1.1243 + } 1.1244 + 1.1245 + // We have an active Theora bitstream. Decode the next Theora frame, and 1.1246 + // extract its keyframe's time. 1.1247 + bool eof; 1.1248 + do { 1.1249 + bool skip = false; 1.1250 + eof = !DecodeVideoFrame(skip, 0); 1.1251 + { 1.1252 + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); 1.1253 + if (mDecoder->IsShutdown()) { 1.1254 + return NS_ERROR_FAILURE; 1.1255 + } 1.1256 + } 1.1257 + } while (!eof && 1.1258 + mVideoQueue.GetSize() == 0); 1.1259 + 1.1260 + VideoData* video = mVideoQueue.PeekFront(); 1.1261 + if (video && !video->mKeyframe) { 1.1262 + // First decoded frame isn't a keyframe, seek back to previous keyframe, 1.1263 + // otherwise we'll get visual artifacts. 1.1264 + NS_ASSERTION(video->mTimecode != -1, "Must have a granulepos"); 1.1265 + int shift = mTheoraState->mInfo.keyframe_granule_shift; 1.1266 + int64_t keyframeGranulepos = (video->mTimecode >> shift) << shift; 1.1267 + int64_t keyframeTime = mTheoraState->StartTime(keyframeGranulepos); 1.1268 + SEEK_LOG(PR_LOG_DEBUG, ("Keyframe for %lld is at %lld, seeking back to it", 1.1269 + video->mTime, keyframeTime)); 1.1270 + aAdjustedTarget = std::min(aAdjustedTarget, keyframeTime); 1.1271 + } 1.1272 + } 1.1273 + if (aAdjustedTarget < aTarget) { 1.1274 + SeekRange k = SelectSeekRange(aRanges, 1.1275 + aAdjustedTarget, 1.1276 + aStartTime, 1.1277 + aEndTime, 1.1278 + false); 1.1279 + res = SeekBisection(aAdjustedTarget, k, SEEK_FUZZ_USECS); 1.1280 + } 1.1281 + return res; 1.1282 +} 1.1283 + 1.1284 +nsresult OggReader::SeekInUnbuffered(int64_t aTarget, 1.1285 + int64_t aStartTime, 1.1286 + int64_t aEndTime, 1.1287 + const nsTArray<SeekRange>& aRanges) 1.1288 +{ 1.1289 + LOG(PR_LOG_DEBUG, ("%p Seeking in unbuffered data to %lld using bisection search", mDecoder, aTarget)); 1.1290 + 1.1291 + // If we've got an active Theora bitstream, determine the maximum possible 1.1292 + // time in usecs which a keyframe could be before a given interframe. We 1.1293 + // subtract this from our seek target, seek to the new target, and then 1.1294 + // will decode forward to the original seek target. We should encounter a 1.1295 + // keyframe in that interval. This prevents us from needing to run two 1.1296 + // bisections; one for the seek target frame, and another to find its 1.1297 + // keyframe. It's usually faster to just download this extra data, rather 1.1298 + // tham perform two bisections to find the seek target's keyframe. We 1.1299 + // don't do this offsetting when seeking in a buffered range, 1.1300 + // as the extra decoding causes a noticeable speed hit when all the data 1.1301 + // is buffered (compared to just doing a bisection to exactly find the 1.1302 + // keyframe). 1.1303 + int64_t keyframeOffsetMs = 0; 1.1304 + if (HasVideo() && mTheoraState) { 1.1305 + keyframeOffsetMs = mTheoraState->MaxKeyframeOffset(); 1.1306 + } 1.1307 +#ifdef MOZ_OPUS 1.1308 + // Add in the Opus pre-roll if necessary, as well. 1.1309 + if (HasAudio() && mOpusState) { 1.1310 + keyframeOffsetMs = std::max(keyframeOffsetMs, SEEK_OPUS_PREROLL); 1.1311 + } 1.1312 +#endif /* MOZ_OPUS */ 1.1313 + int64_t seekTarget = std::max(aStartTime, aTarget - keyframeOffsetMs); 1.1314 + // Minimize the bisection search space using the known timestamps from the 1.1315 + // buffered ranges. 1.1316 + SeekRange k = SelectSeekRange(aRanges, seekTarget, aStartTime, aEndTime, false); 1.1317 + return SeekBisection(seekTarget, k, SEEK_FUZZ_USECS); 1.1318 +} 1.1319 + 1.1320 +nsresult OggReader::Seek(int64_t aTarget, 1.1321 + int64_t aStartTime, 1.1322 + int64_t aEndTime, 1.1323 + int64_t aCurrentTime) 1.1324 +{ 1.1325 + NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); 1.1326 + if (mIsChained) 1.1327 + return NS_ERROR_FAILURE; 1.1328 + LOG(PR_LOG_DEBUG, ("%p About to seek to %lld", mDecoder, aTarget)); 1.1329 + nsresult res; 1.1330 + MediaResource* resource = mDecoder->GetResource(); 1.1331 + NS_ENSURE_TRUE(resource != nullptr, NS_ERROR_FAILURE); 1.1332 + int64_t adjustedTarget = aTarget; 1.1333 +#ifdef MOZ_OPUS 1.1334 + if (HasAudio() && mOpusState){ 1.1335 + adjustedTarget = std::max(aStartTime, aTarget - SEEK_OPUS_PREROLL); 1.1336 + } 1.1337 +#endif /* MOZ_OPUS */ 1.1338 + 1.1339 + if (adjustedTarget == aStartTime) { 1.1340 + // We've seeked to the media start. Just seek to the offset of the first 1.1341 + // content page. 1.1342 + res = resource->Seek(nsISeekableStream::NS_SEEK_SET, 0); 1.1343 + NS_ENSURE_SUCCESS(res,res); 1.1344 + 1.1345 + res = ResetDecode(true); 1.1346 + NS_ENSURE_SUCCESS(res,res); 1.1347 + 1.1348 + NS_ASSERTION(aStartTime != -1, "mStartTime should be known"); 1.1349 + { 1.1350 + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); 1.1351 + mDecoder->UpdatePlaybackPosition(aStartTime); 1.1352 + } 1.1353 + } else { 1.1354 + // TODO: This may seek back unnecessarily far in the video, but we don't 1.1355 + // have a way of asking Skeleton to seek to a different target for each 1.1356 + // stream yet. Using adjustedTarget here is at least correct, if slow. 1.1357 + IndexedSeekResult sres = SeekToKeyframeUsingIndex(adjustedTarget); 1.1358 + NS_ENSURE_TRUE(sres != SEEK_FATAL_ERROR, NS_ERROR_FAILURE); 1.1359 + if (sres == SEEK_INDEX_FAIL) { 1.1360 + // No index or other non-fatal index-related failure. Try to seek 1.1361 + // using a bisection search. Determine the already downloaded data 1.1362 + // in the media cache, so we can try to seek in the cached data first. 1.1363 + nsAutoTArray<SeekRange, 16> ranges; 1.1364 + res = GetSeekRanges(ranges); 1.1365 + NS_ENSURE_SUCCESS(res,res); 1.1366 + 1.1367 + // Figure out if the seek target lies in a buffered range. 1.1368 + SeekRange r = SelectSeekRange(ranges, aTarget, aStartTime, aEndTime, true); 1.1369 + 1.1370 + if (!r.IsNull()) { 1.1371 + // We know the buffered range in which the seek target lies, do a 1.1372 + // bisection search in that buffered range. 1.1373 + res = SeekInBufferedRange(aTarget, adjustedTarget, aStartTime, aEndTime, ranges, r); 1.1374 + NS_ENSURE_SUCCESS(res,res); 1.1375 + } else { 1.1376 + // The target doesn't lie in a buffered range. Perform a bisection 1.1377 + // search over the whole media, using the known buffered ranges to 1.1378 + // reduce the search space. 1.1379 + res = SeekInUnbuffered(aTarget, aStartTime, aEndTime, ranges); 1.1380 + NS_ENSURE_SUCCESS(res,res); 1.1381 + } 1.1382 + } 1.1383 + } 1.1384 + 1.1385 + if (HasVideo()) { 1.1386 + // Decode forwards until we find the next keyframe. This is required, 1.1387 + // as although the seek should finish on a page containing a keyframe, 1.1388 + // there may be non-keyframes in the page before the keyframe. 1.1389 + // When doing fastSeek we display the first frame after the seek, so 1.1390 + // we need to advance the decode to the keyframe otherwise we'll get 1.1391 + // visual artifacts in the first frame output after the seek. 1.1392 + bool skip = true; 1.1393 + while (DecodeVideoFrame(skip, 0) && skip) { 1.1394 + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); 1.1395 + if (mDecoder->IsShutdown()) { 1.1396 + return NS_ERROR_FAILURE; 1.1397 + } 1.1398 + } 1.1399 + 1.1400 +#ifdef DEBUG 1.1401 + const VideoData* v = mVideoQueue.PeekFront(); 1.1402 + if (!v || !v->mKeyframe) { 1.1403 + NS_WARNING("Ogg seek didn't end up before a key frame!"); 1.1404 + } 1.1405 +#endif 1.1406 + } 1.1407 + return NS_OK; 1.1408 +} 1.1409 + 1.1410 +// Reads a page from the media resource. 1.1411 +static PageSyncResult 1.1412 +PageSync(MediaResource* aResource, 1.1413 + ogg_sync_state* aState, 1.1414 + bool aCachedDataOnly, 1.1415 + int64_t aOffset, 1.1416 + int64_t aEndOffset, 1.1417 + ogg_page* aPage, 1.1418 + int& aSkippedBytes) 1.1419 +{ 1.1420 + aSkippedBytes = 0; 1.1421 + // Sync to the next page. 1.1422 + int ret = 0; 1.1423 + uint32_t bytesRead = 0; 1.1424 + int64_t readHead = aOffset; 1.1425 + while (ret <= 0) { 1.1426 + ret = ogg_sync_pageseek(aState, aPage); 1.1427 + if (ret == 0) { 1.1428 + char* buffer = ogg_sync_buffer(aState, PAGE_STEP); 1.1429 + NS_ASSERTION(buffer, "Must have a buffer"); 1.1430 + 1.1431 + // Read from the file into the buffer 1.1432 + int64_t bytesToRead = std::min(static_cast<int64_t>(PAGE_STEP), 1.1433 + aEndOffset - readHead); 1.1434 + NS_ASSERTION(bytesToRead <= UINT32_MAX, "bytesToRead range check"); 1.1435 + if (bytesToRead <= 0) { 1.1436 + return PAGE_SYNC_END_OF_RANGE; 1.1437 + } 1.1438 + nsresult rv = NS_OK; 1.1439 + if (aCachedDataOnly) { 1.1440 + rv = aResource->ReadFromCache(buffer, readHead, 1.1441 + static_cast<uint32_t>(bytesToRead)); 1.1442 + NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR); 1.1443 + bytesRead = static_cast<uint32_t>(bytesToRead); 1.1444 + } else { 1.1445 + rv = aResource->Seek(nsISeekableStream::NS_SEEK_SET, readHead); 1.1446 + NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR); 1.1447 + rv = aResource->Read(buffer, 1.1448 + static_cast<uint32_t>(bytesToRead), 1.1449 + &bytesRead); 1.1450 + NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR); 1.1451 + } 1.1452 + if (bytesRead == 0 && NS_SUCCEEDED(rv)) { 1.1453 + // End of file. 1.1454 + return PAGE_SYNC_END_OF_RANGE; 1.1455 + } 1.1456 + readHead += bytesRead; 1.1457 + 1.1458 + // Update the synchronisation layer with the number 1.1459 + // of bytes written to the buffer 1.1460 + ret = ogg_sync_wrote(aState, bytesRead); 1.1461 + NS_ENSURE_TRUE(ret == 0, PAGE_SYNC_ERROR); 1.1462 + continue; 1.1463 + } 1.1464 + 1.1465 + if (ret < 0) { 1.1466 + NS_ASSERTION(aSkippedBytes >= 0, "Offset >= 0"); 1.1467 + aSkippedBytes += -ret; 1.1468 + NS_ASSERTION(aSkippedBytes >= 0, "Offset >= 0"); 1.1469 + continue; 1.1470 + } 1.1471 + } 1.1472 + 1.1473 + return PAGE_SYNC_OK; 1.1474 +} 1.1475 + 1.1476 +nsresult OggReader::SeekBisection(int64_t aTarget, 1.1477 + const SeekRange& aRange, 1.1478 + uint32_t aFuzz) 1.1479 +{ 1.1480 + NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); 1.1481 + nsresult res; 1.1482 + MediaResource* resource = mDecoder->GetResource(); 1.1483 + 1.1484 + if (aTarget == aRange.mTimeStart) { 1.1485 + if (NS_FAILED(ResetDecode())) { 1.1486 + return NS_ERROR_FAILURE; 1.1487 + } 1.1488 + res = resource->Seek(nsISeekableStream::NS_SEEK_SET, 0); 1.1489 + NS_ENSURE_SUCCESS(res,res); 1.1490 + return NS_OK; 1.1491 + } 1.1492 + 1.1493 + // Bisection search, find start offset of last page with end time less than 1.1494 + // the seek target. 1.1495 + ogg_int64_t startOffset = aRange.mOffsetStart; 1.1496 + ogg_int64_t startTime = aRange.mTimeStart; 1.1497 + ogg_int64_t startLength = 0; // Length of the page at startOffset. 1.1498 + ogg_int64_t endOffset = aRange.mOffsetEnd; 1.1499 + ogg_int64_t endTime = aRange.mTimeEnd; 1.1500 + 1.1501 + ogg_int64_t seekTarget = aTarget; 1.1502 + int64_t seekLowerBound = std::max(static_cast<int64_t>(0), aTarget - aFuzz); 1.1503 + int hops = 0; 1.1504 + DebugOnly<ogg_int64_t> previousGuess = -1; 1.1505 + int backsteps = 0; 1.1506 + const int maxBackStep = 10; 1.1507 + NS_ASSERTION(static_cast<uint64_t>(PAGE_STEP) * pow(2.0, maxBackStep) < INT32_MAX, 1.1508 + "Backstep calculation must not overflow"); 1.1509 + 1.1510 + // Seek via bisection search. Loop until we find the offset where the page 1.1511 + // before the offset is before the seek target, and the page after the offset 1.1512 + // is after the seek target. 1.1513 + while (true) { 1.1514 + ogg_int64_t duration = 0; 1.1515 + double target = 0; 1.1516 + ogg_int64_t interval = 0; 1.1517 + ogg_int64_t guess = 0; 1.1518 + ogg_page page; 1.1519 + int skippedBytes = 0; 1.1520 + ogg_int64_t pageOffset = 0; 1.1521 + ogg_int64_t pageLength = 0; 1.1522 + ogg_int64_t granuleTime = -1; 1.1523 + bool mustBackoff = false; 1.1524 + 1.1525 + // Guess where we should bisect to, based on the bit rate and the time 1.1526 + // remaining in the interval. Loop until we can determine the time at 1.1527 + // the guess offset. 1.1528 + while (true) { 1.1529 + 1.1530 + // Discard any previously buffered packets/pages. 1.1531 + if (NS_FAILED(ResetDecode())) { 1.1532 + return NS_ERROR_FAILURE; 1.1533 + } 1.1534 + 1.1535 + interval = endOffset - startOffset - startLength; 1.1536 + if (interval == 0) { 1.1537 + // Our interval is empty, we've found the optimal seek point, as the 1.1538 + // page at the start offset is before the seek target, and the page 1.1539 + // at the end offset is after the seek target. 1.1540 + SEEK_LOG(PR_LOG_DEBUG, ("Interval narrowed, terminating bisection.")); 1.1541 + break; 1.1542 + } 1.1543 + 1.1544 + // Guess bisection point. 1.1545 + duration = endTime - startTime; 1.1546 + target = (double)(seekTarget - startTime) / (double)duration; 1.1547 + guess = startOffset + startLength + 1.1548 + static_cast<ogg_int64_t>((double)interval * target); 1.1549 + guess = std::min(guess, endOffset - PAGE_STEP); 1.1550 + if (mustBackoff) { 1.1551 + // We previously failed to determine the time at the guess offset, 1.1552 + // probably because we ran out of data to decode. This usually happens 1.1553 + // when we guess very close to the end offset. So reduce the guess 1.1554 + // offset using an exponential backoff until we determine the time. 1.1555 + SEEK_LOG(PR_LOG_DEBUG, ("Backing off %d bytes, backsteps=%d", 1.1556 + static_cast<int32_t>(PAGE_STEP * pow(2.0, backsteps)), backsteps)); 1.1557 + guess -= PAGE_STEP * static_cast<ogg_int64_t>(pow(2.0, backsteps)); 1.1558 + 1.1559 + if (guess <= startOffset) { 1.1560 + // We've tried to backoff to before the start offset of our seek 1.1561 + // range. This means we couldn't find a seek termination position 1.1562 + // near the end of the seek range, so just set the seek termination 1.1563 + // condition, and break out of the bisection loop. We'll begin 1.1564 + // decoding from the start of the seek range. 1.1565 + interval = 0; 1.1566 + break; 1.1567 + } 1.1568 + 1.1569 + backsteps = std::min(backsteps + 1, maxBackStep); 1.1570 + // We reset mustBackoff. If we still need to backoff further, it will 1.1571 + // be set to true again. 1.1572 + mustBackoff = false; 1.1573 + } else { 1.1574 + backsteps = 0; 1.1575 + } 1.1576 + guess = std::max(guess, startOffset + startLength); 1.1577 + 1.1578 + SEEK_LOG(PR_LOG_DEBUG, ("Seek loop start[o=%lld..%lld t=%lld] " 1.1579 + "end[o=%lld t=%lld] " 1.1580 + "interval=%lld target=%lf guess=%lld", 1.1581 + startOffset, (startOffset+startLength), startTime, 1.1582 + endOffset, endTime, interval, target, guess)); 1.1583 + 1.1584 + NS_ASSERTION(guess >= startOffset + startLength, "Guess must be after range start"); 1.1585 + NS_ASSERTION(guess < endOffset, "Guess must be before range end"); 1.1586 + NS_ASSERTION(guess != previousGuess, "Guess should be different to previous"); 1.1587 + previousGuess = guess; 1.1588 + 1.1589 + hops++; 1.1590 + 1.1591 + // Locate the next page after our seek guess, and then figure out the 1.1592 + // granule time of the audio and video bitstreams there. We can then 1.1593 + // make a bisection decision based on our location in the media. 1.1594 + PageSyncResult res = PageSync(resource, 1.1595 + &mOggState, 1.1596 + false, 1.1597 + guess, 1.1598 + endOffset, 1.1599 + &page, 1.1600 + skippedBytes); 1.1601 + NS_ENSURE_TRUE(res != PAGE_SYNC_ERROR, NS_ERROR_FAILURE); 1.1602 + 1.1603 + if (res == PAGE_SYNC_END_OF_RANGE) { 1.1604 + // Our guess was too close to the end, we've ended up reading the end 1.1605 + // page. Backoff exponentially from the end point, in case the last 1.1606 + // page/frame/sample is huge. 1.1607 + mustBackoff = true; 1.1608 + SEEK_LOG(PR_LOG_DEBUG, ("Hit the end of range, backing off")); 1.1609 + continue; 1.1610 + } 1.1611 + 1.1612 + // We've located a page of length |ret| at |guess + skippedBytes|. 1.1613 + // Remember where the page is located. 1.1614 + pageOffset = guess + skippedBytes; 1.1615 + pageLength = page.header_len + page.body_len; 1.1616 + 1.1617 + // Read pages until we can determine the granule time of the audio and 1.1618 + // video bitstream. 1.1619 + ogg_int64_t audioTime = -1; 1.1620 + ogg_int64_t videoTime = -1; 1.1621 + do { 1.1622 + // Add the page to its codec state, determine its granule time. 1.1623 + uint32_t serial = ogg_page_serialno(&page); 1.1624 + OggCodecState* codecState = mCodecStore.Get(serial); 1.1625 + if (codecState && codecState->mActive) { 1.1626 + int ret = ogg_stream_pagein(&codecState->mState, &page); 1.1627 + NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE); 1.1628 + } 1.1629 + 1.1630 + ogg_int64_t granulepos = ogg_page_granulepos(&page); 1.1631 + 1.1632 + if (HasAudio() && granulepos > 0 && audioTime == -1) { 1.1633 + if (mVorbisState && serial == mVorbisState->mSerial) { 1.1634 + audioTime = mVorbisState->Time(granulepos); 1.1635 +#ifdef MOZ_OPUS 1.1636 + } else if (mOpusState && serial == mOpusState->mSerial) { 1.1637 + audioTime = mOpusState->Time(granulepos); 1.1638 +#endif 1.1639 + } 1.1640 + } 1.1641 + 1.1642 + if (HasVideo() && 1.1643 + granulepos > 0 && 1.1644 + serial == mTheoraState->mSerial && 1.1645 + videoTime == -1) { 1.1646 + videoTime = mTheoraState->Time(granulepos); 1.1647 + } 1.1648 + 1.1649 + if (pageOffset + pageLength >= endOffset) { 1.1650 + // Hit end of readable data. 1.1651 + break; 1.1652 + } 1.1653 + 1.1654 + if (!ReadOggPage(&page)) { 1.1655 + break; 1.1656 + } 1.1657 + 1.1658 + } while ((HasAudio() && audioTime == -1) || 1.1659 + (HasVideo() && videoTime == -1)); 1.1660 + 1.1661 + 1.1662 + if ((HasAudio() && audioTime == -1) || 1.1663 + (HasVideo() && videoTime == -1)) 1.1664 + { 1.1665 + // We don't have timestamps for all active tracks... 1.1666 + if (pageOffset == startOffset + startLength && 1.1667 + pageOffset + pageLength >= endOffset) { 1.1668 + // We read the entire interval without finding timestamps for all 1.1669 + // active tracks. We know the interval start offset is before the seek 1.1670 + // target, and the interval end is after the seek target, and we can't 1.1671 + // terminate inside the interval, so we terminate the seek at the 1.1672 + // start of the interval. 1.1673 + interval = 0; 1.1674 + break; 1.1675 + } 1.1676 + 1.1677 + // We should backoff; cause the guess to back off from the end, so 1.1678 + // that we've got more room to capture. 1.1679 + mustBackoff = true; 1.1680 + continue; 1.1681 + } 1.1682 + 1.1683 + // We've found appropriate time stamps here. Proceed to bisect 1.1684 + // the search space. 1.1685 + granuleTime = std::max(audioTime, videoTime); 1.1686 + NS_ASSERTION(granuleTime > 0, "Must get a granuletime"); 1.1687 + break; 1.1688 + } // End of "until we determine time at guess offset" loop. 1.1689 + 1.1690 + if (interval == 0) { 1.1691 + // Seek termination condition; we've found the page boundary of the 1.1692 + // last page before the target, and the first page after the target. 1.1693 + SEEK_LOG(PR_LOG_DEBUG, ("Terminating seek at offset=%lld", startOffset)); 1.1694 + NS_ASSERTION(startTime < aTarget, "Start time must always be less than target"); 1.1695 + res = resource->Seek(nsISeekableStream::NS_SEEK_SET, startOffset); 1.1696 + NS_ENSURE_SUCCESS(res,res); 1.1697 + if (NS_FAILED(ResetDecode())) { 1.1698 + return NS_ERROR_FAILURE; 1.1699 + } 1.1700 + break; 1.1701 + } 1.1702 + 1.1703 + SEEK_LOG(PR_LOG_DEBUG, ("Time at offset %lld is %lld", guess, granuleTime)); 1.1704 + if (granuleTime < seekTarget && granuleTime > seekLowerBound) { 1.1705 + // We're within the fuzzy region in which we want to terminate the search. 1.1706 + res = resource->Seek(nsISeekableStream::NS_SEEK_SET, pageOffset); 1.1707 + NS_ENSURE_SUCCESS(res,res); 1.1708 + if (NS_FAILED(ResetDecode())) { 1.1709 + return NS_ERROR_FAILURE; 1.1710 + } 1.1711 + SEEK_LOG(PR_LOG_DEBUG, ("Terminating seek at offset=%lld", pageOffset)); 1.1712 + break; 1.1713 + } 1.1714 + 1.1715 + if (granuleTime >= seekTarget) { 1.1716 + // We've landed after the seek target. 1.1717 + NS_ASSERTION(pageOffset < endOffset, "offset_end must decrease"); 1.1718 + endOffset = pageOffset; 1.1719 + endTime = granuleTime; 1.1720 + } else if (granuleTime < seekTarget) { 1.1721 + // Landed before seek target. 1.1722 + NS_ASSERTION(pageOffset >= startOffset + startLength, 1.1723 + "Bisection point should be at or after end of first page in interval"); 1.1724 + startOffset = pageOffset; 1.1725 + startLength = pageLength; 1.1726 + startTime = granuleTime; 1.1727 + } 1.1728 + NS_ASSERTION(startTime < seekTarget, "Must be before seek target"); 1.1729 + NS_ASSERTION(endTime >= seekTarget, "End must be after seek target"); 1.1730 + } 1.1731 + 1.1732 + SEEK_LOG(PR_LOG_DEBUG, ("Seek complete in %d bisections.", hops)); 1.1733 + 1.1734 + return NS_OK; 1.1735 +} 1.1736 + 1.1737 +nsresult OggReader::GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime) 1.1738 +{ 1.1739 + { 1.1740 + mozilla::ReentrantMonitorAutoEnter mon(mMonitor); 1.1741 + if (mIsChained) 1.1742 + return NS_ERROR_FAILURE; 1.1743 + } 1.1744 +#ifdef OGG_ESTIMATE_BUFFERED 1.1745 + return MediaDecoderReader::GetBuffered(aBuffered, aStartTime); 1.1746 +#else 1.1747 + // HasAudio and HasVideo are not used here as they take a lock and cause 1.1748 + // a deadlock. Accessing mInfo doesn't require a lock - it doesn't change 1.1749 + // after metadata is read. 1.1750 + if (!mInfo.HasValidMedia()) { 1.1751 + // No need to search through the file if there are no audio or video tracks 1.1752 + return NS_OK; 1.1753 + } 1.1754 + 1.1755 + MediaResource* resource = mDecoder->GetResource(); 1.1756 + nsTArray<MediaByteRange> ranges; 1.1757 + nsresult res = resource->GetCachedRanges(ranges); 1.1758 + NS_ENSURE_SUCCESS(res, res); 1.1759 + 1.1760 + // Traverse across the buffered byte ranges, determining the time ranges 1.1761 + // they contain. MediaResource::GetNextCachedData(offset) returns -1 when 1.1762 + // offset is after the end of the media resource, or there's no more cached 1.1763 + // data after the offset. This loop will run until we've checked every 1.1764 + // buffered range in the media, in increasing order of offset. 1.1765 + nsAutoOggSyncState sync; 1.1766 + for (uint32_t index = 0; index < ranges.Length(); index++) { 1.1767 + // Ensure the offsets are after the header pages. 1.1768 + int64_t startOffset = ranges[index].mStart; 1.1769 + int64_t endOffset = ranges[index].mEnd; 1.1770 + 1.1771 + // Because the granulepos time is actually the end time of the page, 1.1772 + // we special-case (startOffset == 0) so that the first 1.1773 + // buffered range always appears to be buffered from the media start 1.1774 + // time, rather than from the end-time of the first page. 1.1775 + int64_t startTime = (startOffset == 0) ? aStartTime : -1; 1.1776 + 1.1777 + // Find the start time of the range. Read pages until we find one with a 1.1778 + // granulepos which we can convert into a timestamp to use as the time of 1.1779 + // the start of the buffered range. 1.1780 + ogg_sync_reset(&sync.mState); 1.1781 + while (startTime == -1) { 1.1782 + ogg_page page; 1.1783 + int32_t discard; 1.1784 + PageSyncResult res = PageSync(resource, 1.1785 + &sync.mState, 1.1786 + true, 1.1787 + startOffset, 1.1788 + endOffset, 1.1789 + &page, 1.1790 + discard); 1.1791 + if (res == PAGE_SYNC_ERROR) { 1.1792 + return NS_ERROR_FAILURE; 1.1793 + } else if (res == PAGE_SYNC_END_OF_RANGE) { 1.1794 + // Hit the end of range without reading a page, give up trying to 1.1795 + // find a start time for this buffered range, skip onto the next one. 1.1796 + break; 1.1797 + } 1.1798 + 1.1799 + int64_t granulepos = ogg_page_granulepos(&page); 1.1800 + if (granulepos == -1) { 1.1801 + // Page doesn't have an end time, advance to the next page 1.1802 + // until we find one. 1.1803 + startOffset += page.header_len + page.body_len; 1.1804 + continue; 1.1805 + } 1.1806 + 1.1807 + uint32_t serial = ogg_page_serialno(&page); 1.1808 + if (mVorbisState && serial == mVorbisSerial) { 1.1809 + startTime = VorbisState::Time(&mVorbisInfo, granulepos); 1.1810 + NS_ASSERTION(startTime > 0, "Must have positive start time"); 1.1811 + } 1.1812 +#ifdef MOZ_OPUS 1.1813 + else if (mOpusState && serial == mOpusSerial) { 1.1814 + startTime = OpusState::Time(mOpusPreSkip, granulepos); 1.1815 + NS_ASSERTION(startTime > 0, "Must have positive start time"); 1.1816 + } 1.1817 +#endif /* MOZ_OPUS */ 1.1818 + else if (mTheoraState && serial == mTheoraSerial) { 1.1819 + startTime = TheoraState::Time(&mTheoraInfo, granulepos); 1.1820 + NS_ASSERTION(startTime > 0, "Must have positive start time"); 1.1821 + } 1.1822 + else if (mCodecStore.Contains(serial)) { 1.1823 + // Stream is not the theora or vorbis stream we're playing, 1.1824 + // but is one that we have header data for. 1.1825 + startOffset += page.header_len + page.body_len; 1.1826 + continue; 1.1827 + } 1.1828 + else { 1.1829 + // Page is for a stream we don't know about (possibly a chained 1.1830 + // ogg), return OK to abort the finding any further ranges. This 1.1831 + // prevents us searching through the rest of the media when we 1.1832 + // may not be able to extract timestamps from it. 1.1833 + SetChained(true); 1.1834 + return NS_OK; 1.1835 + } 1.1836 + } 1.1837 + 1.1838 + if (startTime != -1) { 1.1839 + // We were able to find a start time for that range, see if we can 1.1840 + // find an end time. 1.1841 + int64_t endTime = RangeEndTime(startOffset, endOffset, true); 1.1842 + if (endTime != -1) { 1.1843 + aBuffered->Add((startTime - aStartTime) / static_cast<double>(USECS_PER_S), 1.1844 + (endTime - aStartTime) / static_cast<double>(USECS_PER_S)); 1.1845 + } 1.1846 + } 1.1847 + } 1.1848 + 1.1849 + return NS_OK; 1.1850 +#endif 1.1851 +} 1.1852 + 1.1853 +OggCodecStore::OggCodecStore() 1.1854 +: mMonitor("CodecStore") 1.1855 +{ 1.1856 +} 1.1857 + 1.1858 +void OggCodecStore::Add(uint32_t serial, OggCodecState* codecState) 1.1859 +{ 1.1860 + MonitorAutoLock mon(mMonitor); 1.1861 + mCodecStates.Put(serial, codecState); 1.1862 +} 1.1863 + 1.1864 +bool OggCodecStore::Contains(uint32_t serial) 1.1865 +{ 1.1866 + MonitorAutoLock mon(mMonitor); 1.1867 + return mCodecStates.Get(serial, nullptr); 1.1868 +} 1.1869 + 1.1870 +OggCodecState* OggCodecStore::Get(uint32_t serial) 1.1871 +{ 1.1872 + MonitorAutoLock mon(mMonitor); 1.1873 + return mCodecStates.Get(serial); 1.1874 +} 1.1875 + 1.1876 +} // namespace mozilla 1.1877 +