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