Line data Source code
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/. */
6 :
7 : #include "nsError.h"
8 : #include "MediaDecoderStateMachine.h"
9 : #include "AbstractMediaDecoder.h"
10 : #include "OggDemuxer.h"
11 : #include "OggCodecState.h"
12 : #include "mozilla/AbstractThread.h"
13 : #include "mozilla/Atomics.h"
14 : #include "mozilla/PodOperations.h"
15 : #include "mozilla/SharedThreadPool.h"
16 : #include "mozilla/Telemetry.h"
17 : #include "mozilla/TimeStamp.h"
18 : #include "MediaDataDemuxer.h"
19 : #include "nsAutoRef.h"
20 : #include "XiphExtradata.h"
21 : #include "MediaPrefs.h"
22 :
23 : #include <algorithm>
24 :
25 : extern mozilla::LazyLogModule gMediaDemuxerLog;
26 : #define OGG_DEBUG(arg, ...) MOZ_LOG(gMediaDemuxerLog, mozilla::LogLevel::Debug, ("OggDemuxer(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
27 :
28 : // Un-comment to enable logging of seek bisections.
29 : //#define SEEK_LOGGING
30 : #ifdef SEEK_LOGGING
31 : #define SEEK_LOG(type, msg) MOZ_LOG(gMediaDemuxerLog, type, msg)
32 : #else
33 : #define SEEK_LOG(type, msg)
34 : #endif
35 :
36 : namespace mozilla
37 : {
38 :
39 : using media::TimeUnit;
40 : using media::TimeInterval;
41 : using media::TimeIntervals;
42 :
43 : // The number of microseconds of "fuzz" we use in a bisection search over
44 : // HTTP. When we're seeking with fuzz, we'll stop the search if a bisection
45 : // lands between the seek target and OGG_SEEK_FUZZ_USECS microseconds before the
46 : // seek target. This is becaue it's usually quicker to just keep downloading
47 : // from an exisiting connection than to do another bisection inside that
48 : // small range, which would open a new HTTP connetion.
49 : static const uint32_t OGG_SEEK_FUZZ_USECS = 500000;
50 :
51 : // The number of microseconds of "pre-roll" we use for Opus streams.
52 : // The specification recommends 80 ms.
53 : static const int64_t OGG_SEEK_OPUS_PREROLL = 80 * USECS_PER_MS;
54 :
55 : static Atomic<uint32_t> sStreamSourceID(0u);
56 :
57 : // Return the corresponding category in aKind based on the following specs.
58 : // (https://www.whatwg.org/specs/web-apps/current-
59 : // work/multipage/embedded-content.html#dom-audiotrack-kind) &
60 : // (http://wiki.xiph.org/SkeletonHeaders)
61 : const nsString
62 0 : OggDemuxer::GetKind(const nsCString& aRole)
63 : {
64 0 : if (aRole.Find("audio/main") != -1 || aRole.Find("video/main") != -1) {
65 0 : return NS_LITERAL_STRING("main");
66 0 : } else if (aRole.Find("audio/alternate") != -1 ||
67 0 : aRole.Find("video/alternate") != -1) {
68 0 : return NS_LITERAL_STRING("alternative");
69 0 : } else if (aRole.Find("audio/audiodesc") != -1) {
70 0 : return NS_LITERAL_STRING("descriptions");
71 0 : } else if (aRole.Find("audio/described") != -1) {
72 0 : return NS_LITERAL_STRING("main-desc");
73 0 : } else if (aRole.Find("audio/dub") != -1) {
74 0 : return NS_LITERAL_STRING("translation");
75 0 : } else if (aRole.Find("audio/commentary") != -1) {
76 0 : return NS_LITERAL_STRING("commentary");
77 0 : } else if (aRole.Find("video/sign") != -1) {
78 0 : return NS_LITERAL_STRING("sign");
79 0 : } else if (aRole.Find("video/captioned") != -1) {
80 0 : return NS_LITERAL_STRING("captions");
81 0 : } else if (aRole.Find("video/subtitled") != -1) {
82 0 : return NS_LITERAL_STRING("subtitles");
83 : }
84 0 : return EmptyString();
85 : }
86 :
87 : void
88 0 : OggDemuxer::InitTrack(MessageField* aMsgInfo,
89 : TrackInfo* aInfo,
90 : bool aEnable)
91 : {
92 0 : MOZ_ASSERT(aMsgInfo);
93 0 : MOZ_ASSERT(aInfo);
94 :
95 0 : nsCString* sName = aMsgInfo->mValuesStore.Get(eName);
96 0 : nsCString* sRole = aMsgInfo->mValuesStore.Get(eRole);
97 0 : nsCString* sTitle = aMsgInfo->mValuesStore.Get(eTitle);
98 0 : nsCString* sLanguage = aMsgInfo->mValuesStore.Get(eLanguage);
99 0 : aInfo->Init(sName? NS_ConvertUTF8toUTF16(*sName):EmptyString(),
100 0 : sRole? GetKind(*sRole):EmptyString(),
101 0 : sTitle? NS_ConvertUTF8toUTF16(*sTitle):EmptyString(),
102 0 : sLanguage? NS_ConvertUTF8toUTF16(*sLanguage):EmptyString(),
103 0 : aEnable);
104 0 : }
105 :
106 0 : OggDemuxer::OggDemuxer(MediaResource* aResource)
107 : : mTheoraState(nullptr)
108 : , mVorbisState(nullptr)
109 : , mOpusState(nullptr)
110 : , mFlacState(nullptr)
111 0 : , mOpusEnabled(MediaDecoder::IsOpusEnabled())
112 : , mSkeletonState(nullptr)
113 : , mAudioOggState(aResource)
114 : , mVideoOggState(aResource)
115 : , mIsChained(false)
116 : , mTimedMetadataEvent(nullptr)
117 0 : , mOnSeekableEvent(nullptr)
118 : {
119 0 : MOZ_COUNT_CTOR(OggDemuxer);
120 0 : }
121 :
122 0 : OggDemuxer::~OggDemuxer()
123 : {
124 0 : MOZ_COUNT_DTOR(OggDemuxer);
125 0 : Reset(TrackInfo::kAudioTrack);
126 0 : Reset(TrackInfo::kVideoTrack);
127 0 : if (HasAudio() || HasVideo()) {
128 : // If we were able to initialize our decoders, report whether we encountered
129 : // a chained stream or not.
130 0 : bool isChained = mIsChained;
131 0 : void* ptr = this;
132 0 : nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
133 0 : "OggDemuxer::~OggDemuxer", [ptr, isChained]() -> void {
134 : // We can't use OGG_DEBUG here because it implicitly refers to `this`,
135 : // which we can't capture in this runnable.
136 0 : MOZ_LOG(gMediaDemuxerLog,
137 : mozilla::LogLevel::Debug,
138 : ("OggDemuxer(%p)::%s: Reporting telemetry "
139 : "MEDIA_OGG_LOADED_IS_CHAINED=%d",
140 : ptr,
141 : __func__,
142 : isChained));
143 0 : Telemetry::Accumulate(
144 0 : Telemetry::HistogramID::MEDIA_OGG_LOADED_IS_CHAINED, isChained);
145 0 : });
146 : SystemGroup::Dispatch("~OggDemuxer::report_telemetry",
147 : TaskCategory::Other,
148 0 : task.forget());
149 : }
150 0 : }
151 :
152 : void
153 0 : OggDemuxer::SetChainingEvents(TimedMetadataEventProducer* aMetadataEvent,
154 : MediaEventProducer<void>* aOnSeekableEvent)
155 : {
156 0 : mTimedMetadataEvent = aMetadataEvent;
157 0 : mOnSeekableEvent = aOnSeekableEvent;
158 0 : }
159 :
160 :
161 : bool
162 0 : OggDemuxer::HasAudio()
163 : const
164 : {
165 0 : return mVorbisState || mOpusState || mFlacState;
166 : }
167 :
168 : bool
169 0 : OggDemuxer::HasVideo()
170 : const
171 : {
172 0 : return mTheoraState;
173 : }
174 :
175 : bool
176 0 : OggDemuxer::HaveStartTime()
177 : const
178 : {
179 0 : return mStartTime.isSome();
180 : }
181 :
182 : int64_t
183 0 : OggDemuxer::StartTime() const
184 : {
185 0 : return mStartTime.refOr(0);
186 : }
187 :
188 : bool
189 0 : OggDemuxer::HaveStartTime(TrackInfo::TrackType aType)
190 : {
191 0 : return OggState(aType).mStartTime.isSome();
192 : }
193 :
194 : int64_t
195 0 : OggDemuxer::StartTime(TrackInfo::TrackType aType)
196 : {
197 0 : return OggState(aType).mStartTime.refOr(TimeUnit::Zero()).ToMicroseconds();
198 : }
199 :
200 : RefPtr<OggDemuxer::InitPromise>
201 0 : OggDemuxer::Init()
202 : {
203 0 : int ret = ogg_sync_init(OggSyncState(TrackInfo::kAudioTrack));
204 0 : if (ret != 0) {
205 0 : return InitPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
206 : }
207 0 : ret = ogg_sync_init(OggSyncState(TrackInfo::kVideoTrack));
208 0 : if (ret != 0) {
209 0 : return InitPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__);
210 : }
211 0 : if (ReadMetadata() != NS_OK) {
212 0 : return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__);
213 : }
214 :
215 0 : if (!GetNumberTracks(TrackInfo::kAudioTrack) &&
216 0 : !GetNumberTracks(TrackInfo::kVideoTrack)) {
217 0 : return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__);
218 : }
219 :
220 0 : return InitPromise::CreateAndResolve(NS_OK, __func__);
221 : }
222 :
223 : bool
224 0 : OggDemuxer::HasTrackType(TrackInfo::TrackType aType) const
225 : {
226 0 : return !!GetNumberTracks(aType);
227 : }
228 :
229 : OggCodecState*
230 0 : OggDemuxer::GetTrackCodecState(TrackInfo::TrackType aType) const
231 : {
232 0 : switch(aType) {
233 : case TrackInfo::kAudioTrack:
234 0 : if (mVorbisState) {
235 0 : return mVorbisState;
236 0 : } else if (mOpusState) {
237 0 : return mOpusState;
238 : } else {
239 0 : return mFlacState;
240 : }
241 : case TrackInfo::kVideoTrack:
242 0 : return mTheoraState;
243 : default:
244 0 : return 0;
245 : }
246 : }
247 :
248 : TrackInfo::TrackType
249 0 : OggDemuxer::GetCodecStateType(OggCodecState* aState) const
250 : {
251 0 : switch (aState->GetType()) {
252 : case OggCodecState::TYPE_THEORA:
253 0 : return TrackInfo::kVideoTrack;
254 : case OggCodecState::TYPE_OPUS:
255 : case OggCodecState::TYPE_VORBIS:
256 : case OggCodecState::TYPE_FLAC:
257 0 : return TrackInfo::kAudioTrack;
258 : default:
259 0 : return TrackInfo::kUndefinedTrack;
260 : }
261 : }
262 :
263 : uint32_t
264 0 : OggDemuxer::GetNumberTracks(TrackInfo::TrackType aType) const
265 : {
266 0 : switch(aType) {
267 : case TrackInfo::kAudioTrack:
268 0 : return HasAudio() ? 1 : 0;
269 : case TrackInfo::kVideoTrack:
270 0 : return HasVideo() ? 1 : 0;
271 : default:
272 0 : return 0;
273 : }
274 : }
275 :
276 : UniquePtr<TrackInfo>
277 0 : OggDemuxer::GetTrackInfo(TrackInfo::TrackType aType, size_t aTrackNumber) const
278 : {
279 0 : switch(aType) {
280 : case TrackInfo::kAudioTrack:
281 0 : return mInfo.mAudio.Clone();
282 : case TrackInfo::kVideoTrack:
283 0 : return mInfo.mVideo.Clone();
284 : default:
285 0 : return nullptr;
286 : }
287 : }
288 :
289 : already_AddRefed<MediaTrackDemuxer>
290 0 : OggDemuxer::GetTrackDemuxer(TrackInfo::TrackType aType, uint32_t aTrackNumber)
291 : {
292 0 : if (GetNumberTracks(aType) <= aTrackNumber) {
293 0 : return nullptr;
294 : }
295 0 : RefPtr<OggTrackDemuxer> e = new OggTrackDemuxer(this, aType, aTrackNumber);
296 0 : mDemuxers.AppendElement(e);
297 :
298 0 : return e.forget();
299 : }
300 :
301 : nsresult
302 0 : OggDemuxer::Reset(TrackInfo::TrackType aType)
303 : {
304 : // Discard any previously buffered packets/pages.
305 0 : ogg_sync_reset(OggSyncState(aType));
306 0 : OggCodecState* trackState = GetTrackCodecState(aType);
307 0 : if (trackState) {
308 0 : return trackState->Reset();
309 : }
310 0 : OggState(aType).mNeedKeyframe = true;
311 0 : return NS_OK;
312 : }
313 :
314 : bool
315 0 : OggDemuxer::ReadHeaders(TrackInfo::TrackType aType,
316 : OggCodecState* aState)
317 : {
318 0 : while (!aState->DoneReadingHeaders()) {
319 0 : DemuxUntilPacketAvailable(aType, aState);
320 0 : OggPacketPtr packet = aState->PacketOut();
321 0 : if (!packet) {
322 0 : OGG_DEBUG("Ran out of header packets early; deactivating stream %" PRIu32, aState->mSerial);
323 0 : aState->Deactivate();
324 0 : return false;
325 : }
326 :
327 : // Local OggCodecState needs to decode headers in order to process
328 : // packet granulepos -> time mappings, etc.
329 0 : if (!aState->DecodeHeader(Move(packet))) {
330 0 : OGG_DEBUG("Failed to decode ogg header packet; deactivating stream %" PRIu32, aState->mSerial);
331 0 : aState->Deactivate();
332 0 : return false;
333 : }
334 : }
335 :
336 0 : return aState->Init();
337 : }
338 :
339 : void
340 0 : OggDemuxer::BuildSerialList(nsTArray<uint32_t>& aTracks)
341 : {
342 : // Obtaining seek index information for currently active bitstreams.
343 0 : if (HasVideo()) {
344 0 : aTracks.AppendElement(mTheoraState->mSerial);
345 : }
346 0 : if (HasAudio()) {
347 0 : if (mVorbisState) {
348 0 : aTracks.AppendElement(mVorbisState->mSerial);
349 0 : } else if (mOpusState) {
350 0 : aTracks.AppendElement(mOpusState->mSerial);
351 : }
352 : }
353 0 : }
354 :
355 : void
356 0 : OggDemuxer::SetupTarget(OggCodecState** aSavedState, OggCodecState* aNewState)
357 : {
358 0 : if (*aSavedState) {
359 0 : (*aSavedState)->Reset();
360 : }
361 :
362 0 : if (aNewState->GetInfo()->GetAsAudioInfo()) {
363 0 : mInfo.mAudio = *aNewState->GetInfo()->GetAsAudioInfo();
364 : } else {
365 0 : mInfo.mVideo = *aNewState->GetInfo()->GetAsVideoInfo();
366 : }
367 0 : *aSavedState = aNewState;
368 0 : }
369 :
370 : void
371 0 : OggDemuxer::SetupTargetSkeleton()
372 : {
373 : // Setup skeleton related information after mVorbisState & mTheroState
374 : // being set (if they exist).
375 0 : if (mSkeletonState) {
376 0 : if (!HasAudio() && !HasVideo()) {
377 : // We have a skeleton track, but no audio or video, may as well disable
378 : // the skeleton, we can't do anything useful with this media.
379 0 : OGG_DEBUG("Deactivating skeleton stream %" PRIu32, mSkeletonState->mSerial);
380 0 : mSkeletonState->Deactivate();
381 0 : } else if (ReadHeaders(TrackInfo::kAudioTrack, mSkeletonState) &&
382 0 : mSkeletonState->HasIndex()) {
383 : // We don't particularly care about which track we are currently using
384 : // as both MediaResource points to the same content.
385 : // Extract the duration info out of the index, so we don't need to seek to
386 : // the end of resource to get it.
387 0 : nsTArray<uint32_t> tracks;
388 0 : BuildSerialList(tracks);
389 0 : int64_t duration = 0;
390 0 : if (NS_SUCCEEDED(mSkeletonState->GetDuration(tracks, duration))) {
391 0 : OGG_DEBUG("Got duration from Skeleton index %" PRId64, duration);
392 0 : mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(duration));
393 : }
394 : }
395 : }
396 0 : }
397 :
398 : void
399 0 : OggDemuxer::SetupMediaTracksInfo(const nsTArray<uint32_t>& aSerials)
400 : {
401 : // For each serial number
402 : // 1. Retrieve a codecState from mCodecStore by this serial number.
403 : // 2. Retrieve a message field from mMsgFieldStore by this serial number.
404 : // 3. For now, skip if the serial number refers to a non-primary bitstream.
405 : // 4. Setup track and other audio/video related information per different types.
406 0 : for (size_t i = 0; i < aSerials.Length(); i++) {
407 0 : uint32_t serial = aSerials[i];
408 0 : OggCodecState* codecState = mCodecStore.Get(serial);
409 :
410 0 : MessageField* msgInfo = nullptr;
411 0 : if (mSkeletonState) {
412 0 : mSkeletonState->mMsgFieldStore.Get(serial, &msgInfo);
413 : }
414 :
415 0 : OggCodecState* primeState = nullptr;
416 0 : switch (codecState->GetType()) {
417 : case OggCodecState::TYPE_THEORA:
418 0 : primeState = mTheoraState;
419 0 : break;
420 : case OggCodecState::TYPE_VORBIS:
421 0 : primeState = mVorbisState;
422 0 : break;
423 : case OggCodecState::TYPE_OPUS:
424 0 : primeState = mOpusState;
425 0 : break;
426 : case OggCodecState::TYPE_FLAC:
427 0 : primeState = mFlacState;
428 0 : break;
429 : default:
430 0 : break;
431 : }
432 0 : if (primeState && primeState == codecState) {
433 0 : bool isAudio = primeState->GetInfo()->GetAsAudioInfo();
434 0 : if (msgInfo) {
435 0 : InitTrack(msgInfo, isAudio ? static_cast<TrackInfo*>(&mInfo.mAudio)
436 : : &mInfo.mVideo,
437 0 : true);
438 : }
439 0 : FillTags(isAudio ? static_cast<TrackInfo*>(&mInfo.mAudio) : &mInfo.mVideo,
440 0 : primeState->GetTags());
441 : }
442 : }
443 0 : }
444 :
445 : void
446 0 : OggDemuxer::FillTags(TrackInfo* aInfo, MetadataTags* aTags)
447 : {
448 0 : if (!aTags) {
449 0 : return;
450 : }
451 0 : nsAutoPtr<MetadataTags> tags(aTags);
452 0 : for (auto iter = aTags->Iter(); !iter.Done(); iter.Next()) {
453 0 : aInfo->mTags.AppendElement(MetadataTag(iter.Key(), iter.Data()));
454 : }
455 : }
456 :
457 : nsresult
458 0 : OggDemuxer::ReadMetadata()
459 : {
460 0 : OGG_DEBUG("OggDemuxer::ReadMetadata called!");
461 :
462 : // We read packets until all bitstreams have read all their header packets.
463 : // We record the offset of the first non-header page so that we know
464 : // what page to seek to when seeking to the media start.
465 :
466 : // @FIXME we have to read all the header packets on all the streams
467 : // and THEN we can run SetupTarget*
468 : // @fixme fixme
469 :
470 : TrackInfo::TrackType tracks[2] =
471 0 : { TrackInfo::kAudioTrack, TrackInfo::kVideoTrack };
472 :
473 0 : nsTArray<OggCodecState*> bitstreams;
474 0 : nsTArray<uint32_t> serials;
475 :
476 0 : for (uint32_t i = 0; i < ArrayLength(tracks); i++) {
477 : ogg_page page;
478 0 : bool readAllBOS = false;
479 0 : while (!readAllBOS) {
480 0 : if (!ReadOggPage(tracks[i], &page)) {
481 : // Some kind of error...
482 0 : OGG_DEBUG("OggDemuxer::ReadOggPage failed? leaving ReadMetadata...");
483 0 : return NS_ERROR_FAILURE;
484 : }
485 :
486 0 : int serial = ogg_page_serialno(&page);
487 :
488 0 : if (!ogg_page_bos(&page)) {
489 : // We've encountered a non Beginning Of Stream page. No more BOS pages
490 : // can follow in this Ogg segment, so there will be no other bitstreams
491 : // in the Ogg (unless it's invalid).
492 0 : readAllBOS = true;
493 0 : } else if (!mCodecStore.Contains(serial)) {
494 : // We've not encountered a stream with this serial number before. Create
495 : // an OggCodecState to demux it, and map that to the OggCodecState
496 : // in mCodecStates.
497 0 : OggCodecState* codecState = OggCodecState::Create(&page);
498 0 : mCodecStore.Add(serial, codecState);
499 0 : bitstreams.AppendElement(codecState);
500 0 : serials.AppendElement(serial);
501 : }
502 0 : if (NS_FAILED(DemuxOggPage(tracks[i], &page))) {
503 0 : return NS_ERROR_FAILURE;
504 : }
505 : }
506 : }
507 :
508 : // We've read all BOS pages, so we know the streams contained in the media.
509 : // 1. Find the first encountered Theora/Vorbis/Opus bitstream, and configure
510 : // it as the target A/V bitstream.
511 : // 2. Deactivate the rest of bitstreams for now, until we have MediaInfo
512 : // support multiple track infos.
513 0 : for (uint32_t i = 0; i < bitstreams.Length(); ++i) {
514 0 : OggCodecState* s = bitstreams[i];
515 0 : if (s) {
516 0 : if (s->GetType() == OggCodecState::TYPE_THEORA &&
517 0 : ReadHeaders(TrackInfo::kVideoTrack, s)) {
518 0 : if (!mTheoraState) {
519 0 : SetupTarget(&mTheoraState, s);
520 : } else {
521 0 : s->Deactivate();
522 : }
523 0 : } else if (s->GetType() == OggCodecState::TYPE_VORBIS &&
524 0 : ReadHeaders(TrackInfo::kAudioTrack, s)) {
525 0 : if (!mVorbisState) {
526 0 : SetupTarget(&mVorbisState, s);
527 : } else {
528 0 : s->Deactivate();
529 : }
530 0 : } else if (s->GetType() == OggCodecState::TYPE_OPUS &&
531 0 : ReadHeaders(TrackInfo::kAudioTrack, s)) {
532 0 : if (mOpusEnabled) {
533 0 : if (!mOpusState) {
534 0 : SetupTarget(&mOpusState, s);
535 : } else {
536 0 : s->Deactivate();
537 : }
538 : } else {
539 : NS_WARNING("Opus decoding disabled."
540 0 : " See media.opus.enabled in about:config");
541 : }
542 0 : } else if (MediaPrefs::FlacInOgg() &&
543 0 : s->GetType() == OggCodecState::TYPE_FLAC &&
544 0 : ReadHeaders(TrackInfo::kAudioTrack, s)) {
545 0 : if (!mFlacState) {
546 0 : SetupTarget(&mFlacState, s);
547 : } else {
548 0 : s->Deactivate();
549 : }
550 0 : } else if (s->GetType() == OggCodecState::TYPE_SKELETON && !mSkeletonState) {
551 0 : mSkeletonState = static_cast<SkeletonState*>(s);
552 : } else {
553 : // Deactivate any non-primary bitstreams.
554 0 : s->Deactivate();
555 : }
556 : }
557 : }
558 :
559 0 : SetupTargetSkeleton();
560 0 : SetupMediaTracksInfo(serials);
561 :
562 0 : if (HasAudio() || HasVideo()) {
563 0 : int64_t startTime = -1;
564 0 : FindStartTime(startTime);
565 0 : if (startTime >= 0) {
566 0 : OGG_DEBUG("Detected stream start time %" PRId64, startTime);
567 0 : mStartTime.emplace(startTime);
568 : }
569 :
570 0 : if (mInfo.mMetadataDuration.isNothing() &&
571 0 : Resource(TrackInfo::kAudioTrack)->GetLength() >= 0) {
572 : // We didn't get a duration from the index or a Content-Duration header.
573 : // Seek to the end of file to find the end time.
574 0 : int64_t length = Resource(TrackInfo::kAudioTrack)->GetLength();
575 :
576 0 : NS_ASSERTION(length > 0, "Must have a content length to get end time");
577 :
578 0 : int64_t endTime = RangeEndTime(TrackInfo::kAudioTrack, length);
579 :
580 0 : if (endTime != -1) {
581 0 : mInfo.mUnadjustedMetadataEndTime.emplace(TimeUnit::FromMicroseconds(endTime));
582 0 : mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(endTime - mStartTime.refOr(0)));
583 0 : OGG_DEBUG("Got Ogg duration from seeking to end %" PRId64, endTime);
584 : }
585 : }
586 0 : if (mInfo.mMetadataDuration.isNothing()) {
587 0 : mInfo.mMetadataDuration.emplace(TimeUnit::FromInfinity());
588 : }
589 0 : if (HasAudio()) {
590 0 : mInfo.mAudio.mDuration = mInfo.mMetadataDuration.ref();
591 : }
592 0 : if (HasVideo()) {
593 0 : mInfo.mVideo.mDuration = mInfo.mMetadataDuration.ref();
594 : }
595 : } else {
596 0 : OGG_DEBUG("no audio or video tracks");
597 0 : return NS_ERROR_FAILURE;
598 : }
599 :
600 0 : OGG_DEBUG("success?!");
601 0 : return NS_OK;
602 : }
603 :
604 : void
605 0 : OggDemuxer::SetChained() {
606 : {
607 0 : if (mIsChained) {
608 0 : return;
609 : }
610 0 : mIsChained = true;
611 : }
612 0 : if (mOnSeekableEvent) {
613 0 : mOnSeekableEvent->Notify();
614 : }
615 : }
616 :
617 : bool
618 0 : OggDemuxer::ReadOggChain(const media::TimeUnit& aLastEndTime)
619 : {
620 0 : bool chained = false;
621 0 : OpusState* newOpusState = nullptr;
622 0 : VorbisState* newVorbisState = nullptr;
623 0 : FlacState* newFlacState = nullptr;
624 0 : nsAutoPtr<MetadataTags> tags;
625 :
626 0 : if (HasVideo() || HasSkeleton() || !HasAudio()) {
627 0 : return false;
628 : }
629 :
630 : ogg_page page;
631 0 : if (!ReadOggPage(TrackInfo::kAudioTrack, &page) || !ogg_page_bos(&page)) {
632 : // Chaining is only supported for audio only ogg files.
633 0 : return false;
634 : }
635 :
636 0 : int serial = ogg_page_serialno(&page);
637 0 : if (mCodecStore.Contains(serial)) {
638 0 : return false;
639 : }
640 :
641 0 : nsAutoPtr<OggCodecState> codecState;
642 0 : codecState = OggCodecState::Create(&page);
643 0 : if (!codecState) {
644 0 : return false;
645 : }
646 :
647 0 : if (mVorbisState && (codecState->GetType() == OggCodecState::TYPE_VORBIS)) {
648 0 : newVorbisState = static_cast<VorbisState*>(codecState.get());
649 0 : } else if (mOpusState && (codecState->GetType() == OggCodecState::TYPE_OPUS)) {
650 0 : newOpusState = static_cast<OpusState*>(codecState.get());
651 0 : } else if (mFlacState && (codecState->GetType() == OggCodecState::TYPE_FLAC)) {
652 0 : newFlacState = static_cast<FlacState*>(codecState.get());
653 : } else {
654 0 : return false;
655 : }
656 :
657 : OggCodecState* state;
658 :
659 0 : mCodecStore.Add(serial, codecState.forget());
660 0 : state = mCodecStore.Get(serial);
661 :
662 0 : NS_ENSURE_TRUE(state != nullptr, false);
663 :
664 0 : if (NS_FAILED(state->PageIn(&page))) {
665 0 : return false;
666 : }
667 :
668 0 : MessageField* msgInfo = nullptr;
669 0 : if (mSkeletonState) {
670 0 : mSkeletonState->mMsgFieldStore.Get(serial, &msgInfo);
671 : }
672 :
673 0 : if ((newVorbisState &&
674 0 : ReadHeaders(TrackInfo::kAudioTrack, newVorbisState)) &&
675 0 : (mVorbisState->GetInfo()->GetAsAudioInfo()->mRate ==
676 0 : newVorbisState->GetInfo()->GetAsAudioInfo()->mRate) &&
677 0 : (mVorbisState->GetInfo()->GetAsAudioInfo()->mChannels ==
678 0 : newVorbisState->GetInfo()->GetAsAudioInfo()->mChannels)) {
679 :
680 0 : SetupTarget(&mVorbisState, newVorbisState);
681 0 : LOG(LogLevel::Debug,
682 : ("New vorbis ogg link, serial=%d\n", mVorbisState->mSerial));
683 :
684 0 : if (msgInfo) {
685 0 : InitTrack(msgInfo, &mInfo.mAudio, true);
686 : }
687 :
688 0 : chained = true;
689 0 : tags = newVorbisState->GetTags();
690 : }
691 :
692 0 : if ((newOpusState &&
693 0 : ReadHeaders(TrackInfo::kAudioTrack, newOpusState)) &&
694 0 : (mOpusState->GetInfo()->GetAsAudioInfo()->mRate ==
695 0 : newOpusState->GetInfo()->GetAsAudioInfo()->mRate) &&
696 0 : (mOpusState->GetInfo()->GetAsAudioInfo()->mChannels ==
697 0 : newOpusState->GetInfo()->GetAsAudioInfo()->mChannels)) {
698 :
699 0 : SetupTarget(&mOpusState, newOpusState);
700 :
701 0 : if (msgInfo) {
702 0 : InitTrack(msgInfo, &mInfo.mAudio, true);
703 : }
704 :
705 0 : chained = true;
706 0 : tags = newOpusState->GetTags();
707 : }
708 :
709 0 : if ((newFlacState &&
710 0 : ReadHeaders(TrackInfo::kAudioTrack, newFlacState)) &&
711 0 : (mFlacState->GetInfo()->GetAsAudioInfo()->mRate ==
712 0 : newFlacState->GetInfo()->GetAsAudioInfo()->mRate) &&
713 0 : (mFlacState->GetInfo()->GetAsAudioInfo()->mChannels ==
714 0 : newFlacState->GetInfo()->GetAsAudioInfo()->mChannels)) {
715 :
716 0 : SetupTarget(&mFlacState, newFlacState);
717 0 : LOG(LogLevel::Debug,
718 : ("New flac ogg link, serial=%d\n", mFlacState->mSerial));
719 :
720 0 : if (msgInfo) {
721 0 : InitTrack(msgInfo, &mInfo.mAudio, true);
722 : }
723 :
724 0 : chained = true;
725 0 : tags = newFlacState->GetTags();
726 : }
727 :
728 0 : if (chained) {
729 0 : SetChained();
730 0 : mInfo.mMediaSeekable = false;
731 0 : mDecodedAudioDuration += aLastEndTime;
732 0 : if (mTimedMetadataEvent) {
733 0 : mTimedMetadataEvent->Notify(
734 0 : TimedMetadata(mDecodedAudioDuration,
735 0 : Move(tags),
736 0 : nsAutoPtr<MediaInfo>(new MediaInfo(mInfo))));
737 : }
738 : // Setup a new TrackInfo so that the MediaFormatReader will flush the
739 : // current decoder.
740 0 : mSharedAudioTrackInfo = new TrackInfoSharedPtr(mInfo.mAudio, ++sStreamSourceID);
741 0 : return true;
742 : }
743 :
744 0 : return false;
745 : }
746 :
747 : OggDemuxer::OggStateContext&
748 0 : OggDemuxer::OggState(TrackInfo::TrackType aType)
749 : {
750 0 : if (aType == TrackInfo::kVideoTrack) {
751 0 : return mVideoOggState;
752 : }
753 0 : return mAudioOggState;
754 : }
755 :
756 : ogg_sync_state*
757 0 : OggDemuxer::OggSyncState(TrackInfo::TrackType aType)
758 : {
759 0 : return &OggState(aType).mOggState.mState;
760 : }
761 :
762 : MediaResourceIndex*
763 0 : OggDemuxer::Resource(TrackInfo::TrackType aType)
764 : {
765 0 : return &OggState(aType).mResource;
766 : }
767 :
768 : MediaResourceIndex*
769 0 : OggDemuxer::CommonResource()
770 : {
771 0 : return &mAudioOggState.mResource;
772 : }
773 :
774 : bool
775 0 : OggDemuxer::ReadOggPage(TrackInfo::TrackType aType, ogg_page* aPage)
776 : {
777 0 : int ret = 0;
778 0 : while((ret = ogg_sync_pageseek(OggSyncState(aType), aPage)) <= 0) {
779 0 : if (ret < 0) {
780 : // Lost page sync, have to skip up to next page.
781 0 : continue;
782 : }
783 : // Returns a buffer that can be written too
784 : // with the given size. This buffer is stored
785 : // in the ogg synchronisation structure.
786 0 : char* buffer = ogg_sync_buffer(OggSyncState(aType), 4096);
787 0 : NS_ASSERTION(buffer, "ogg_sync_buffer failed");
788 :
789 : // Read from the resource into the buffer
790 0 : uint32_t bytesRead = 0;
791 :
792 0 : nsresult rv = Resource(aType)->Read(buffer, 4096, &bytesRead);
793 0 : if (NS_FAILED(rv) || !bytesRead) {
794 : // End of file or error.
795 0 : return false;
796 : }
797 :
798 : // Update the synchronisation layer with the number
799 : // of bytes written to the buffer
800 0 : ret = ogg_sync_wrote(OggSyncState(aType), bytesRead);
801 0 : NS_ENSURE_TRUE(ret == 0, false);
802 : }
803 :
804 0 : return true;
805 : }
806 :
807 : nsresult
808 0 : OggDemuxer::DemuxOggPage(TrackInfo::TrackType aType, ogg_page* aPage)
809 : {
810 0 : int serial = ogg_page_serialno(aPage);
811 0 : OggCodecState* codecState = mCodecStore.Get(serial);
812 0 : if (codecState == nullptr) {
813 0 : OGG_DEBUG("encountered packet for unrecognized codecState");
814 0 : return NS_ERROR_FAILURE;
815 : }
816 0 : if (GetCodecStateType(codecState) != aType &&
817 0 : codecState->GetType() != OggCodecState::TYPE_SKELETON) {
818 : // Not a page we're interested in.
819 0 : return NS_OK;
820 : }
821 0 : if (NS_FAILED(codecState->PageIn(aPage))) {
822 0 : OGG_DEBUG("codecState->PageIn failed");
823 0 : return NS_ERROR_FAILURE;
824 : }
825 0 : return NS_OK;
826 : }
827 :
828 : bool
829 0 : OggDemuxer::IsSeekable() const
830 : {
831 0 : if (mIsChained) {
832 0 : return false;
833 : }
834 0 : return true;
835 : }
836 :
837 : UniquePtr<EncryptionInfo>
838 0 : OggDemuxer::GetCrypto()
839 : {
840 0 : return nullptr;
841 : }
842 :
843 : ogg_packet*
844 0 : OggDemuxer::GetNextPacket(TrackInfo::TrackType aType)
845 : {
846 0 : OggCodecState* state = GetTrackCodecState(aType);
847 0 : ogg_packet* packet = nullptr;
848 0 : OggStateContext& context = OggState(aType);
849 :
850 : while (true) {
851 0 : if (packet) {
852 0 : Unused << state->PacketOut();
853 : }
854 0 : DemuxUntilPacketAvailable(aType, state);
855 :
856 0 : packet = state->PacketPeek();
857 0 : if (!packet) {
858 0 : break;
859 : }
860 0 : if (state->IsHeader(packet)) {
861 0 : continue;
862 : }
863 0 : if (context.mNeedKeyframe && !state->IsKeyframe(packet)) {
864 0 : continue;
865 : }
866 0 : context.mNeedKeyframe = false;
867 0 : break;
868 : }
869 :
870 0 : return packet;
871 : }
872 :
873 : void
874 0 : OggDemuxer::DemuxUntilPacketAvailable(TrackInfo::TrackType aType,
875 : OggCodecState* aState)
876 : {
877 0 : while (!aState->IsPacketReady()) {
878 0 : OGG_DEBUG("no packet yet, reading some more");
879 : ogg_page page;
880 0 : if (!ReadOggPage(aType, &page)) {
881 0 : OGG_DEBUG("no more pages to read in resource?");
882 0 : return;
883 : }
884 0 : DemuxOggPage(aType, &page);
885 : }
886 : }
887 :
888 : TimeIntervals
889 0 : OggDemuxer::GetBuffered(TrackInfo::TrackType aType)
890 : {
891 0 : if (!HaveStartTime(aType)) {
892 0 : return TimeIntervals();
893 : }
894 0 : if (mIsChained) {
895 0 : return TimeIntervals::Invalid();
896 : }
897 0 : TimeIntervals buffered;
898 : // HasAudio and HasVideo are not used here as they take a lock and cause
899 : // a deadlock. Accessing mInfo doesn't require a lock - it doesn't change
900 : // after metadata is read.
901 0 : if (!mInfo.HasValidMedia()) {
902 : // No need to search through the file if there are no audio or video tracks
903 0 : return buffered;
904 : }
905 :
906 0 : AutoPinned<MediaResource> resource(Resource(aType)->GetResource());
907 0 : MediaByteRangeSet ranges;
908 0 : nsresult res = resource->GetCachedRanges(ranges);
909 0 : NS_ENSURE_SUCCESS(res, TimeIntervals::Invalid());
910 :
911 : // Traverse across the buffered byte ranges, determining the time ranges
912 : // they contain. MediaResource::GetNextCachedData(offset) returns -1 when
913 : // offset is after the end of the media resource, or there's no more cached
914 : // data after the offset. This loop will run until we've checked every
915 : // buffered range in the media, in increasing order of offset.
916 0 : nsAutoOggSyncState sync;
917 0 : for (uint32_t index = 0; index < ranges.Length(); index++) {
918 : // Ensure the offsets are after the header pages.
919 0 : int64_t startOffset = ranges[index].mStart;
920 0 : int64_t endOffset = ranges[index].mEnd;
921 :
922 : // Because the granulepos time is actually the end time of the page,
923 : // we special-case (startOffset == 0) so that the first
924 : // buffered range always appears to be buffered from the media start
925 : // time, rather than from the end-time of the first page.
926 0 : int64_t startTime = (startOffset == 0) ? StartTime() : -1;
927 :
928 : // Find the start time of the range. Read pages until we find one with a
929 : // granulepos which we can convert into a timestamp to use as the time of
930 : // the start of the buffered range.
931 0 : ogg_sync_reset(&sync.mState);
932 0 : while (startTime == -1) {
933 : ogg_page page;
934 : int32_t discard;
935 0 : PageSyncResult pageSyncResult = PageSync(Resource(aType),
936 : &sync.mState,
937 : true,
938 : startOffset,
939 : endOffset,
940 : &page,
941 0 : discard);
942 0 : if (pageSyncResult == PAGE_SYNC_ERROR) {
943 0 : return TimeIntervals::Invalid();
944 0 : } else if (pageSyncResult == PAGE_SYNC_END_OF_RANGE) {
945 : // Hit the end of range without reading a page, give up trying to
946 : // find a start time for this buffered range, skip onto the next one.
947 0 : break;
948 : }
949 :
950 0 : int64_t granulepos = ogg_page_granulepos(&page);
951 0 : if (granulepos == -1) {
952 : // Page doesn't have an end time, advance to the next page
953 : // until we find one.
954 0 : startOffset += page.header_len + page.body_len;
955 0 : continue;
956 : }
957 :
958 0 : uint32_t serial = ogg_page_serialno(&page);
959 0 : if (aType == TrackInfo::kAudioTrack && mVorbisState &&
960 0 : serial == mVorbisState->mSerial) {
961 0 : startTime = mVorbisState->Time(granulepos);
962 0 : NS_ASSERTION(startTime > 0, "Must have positive start time");
963 0 : } else if (aType == TrackInfo::kAudioTrack && mOpusState &&
964 0 : serial == mOpusState->mSerial) {
965 0 : startTime = mOpusState->Time(granulepos);
966 0 : NS_ASSERTION(startTime > 0, "Must have positive start time");
967 0 : } else if (aType == TrackInfo::kAudioTrack && mFlacState &&
968 0 : serial == mFlacState->mSerial) {
969 0 : startTime = mFlacState->Time(granulepos);
970 0 : NS_ASSERTION(startTime > 0, "Must have positive start time");
971 0 : } else if (aType == TrackInfo::kVideoTrack && mTheoraState &&
972 0 : serial == mTheoraState->mSerial) {
973 0 : startTime = mTheoraState->Time(granulepos);
974 0 : NS_ASSERTION(startTime > 0, "Must have positive start time");
975 0 : } else if (mCodecStore.Contains(serial)) {
976 : // Stream is not the theora or vorbis stream we're playing,
977 : // but is one that we have header data for.
978 0 : startOffset += page.header_len + page.body_len;
979 0 : continue;
980 : } else {
981 : // Page is for a stream we don't know about (possibly a chained
982 : // ogg), return OK to abort the finding any further ranges. This
983 : // prevents us searching through the rest of the media when we
984 : // may not be able to extract timestamps from it.
985 0 : SetChained();
986 0 : return buffered;
987 : }
988 : }
989 :
990 0 : if (startTime != -1) {
991 : // We were able to find a start time for that range, see if we can
992 : // find an end time.
993 0 : int64_t endTime = RangeEndTime(aType, startOffset, endOffset, true);
994 0 : if (endTime > startTime) {
995 0 : buffered += TimeInterval(
996 0 : TimeUnit::FromMicroseconds(startTime - StartTime()),
997 0 : TimeUnit::FromMicroseconds(endTime - StartTime()));
998 : }
999 : }
1000 : }
1001 :
1002 0 : return buffered;
1003 : }
1004 :
1005 : void
1006 0 : OggDemuxer::FindStartTime(int64_t& aOutStartTime)
1007 : {
1008 : // Extract the start times of the bitstreams in order to calculate
1009 : // the duration.
1010 0 : int64_t videoStartTime = INT64_MAX;
1011 0 : int64_t audioStartTime = INT64_MAX;
1012 :
1013 0 : if (HasVideo()) {
1014 0 : FindStartTime(TrackInfo::kVideoTrack, videoStartTime);
1015 0 : if (videoStartTime != INT64_MAX) {
1016 0 : OGG_DEBUG("OggDemuxer::FindStartTime() video=%" PRId64, videoStartTime);
1017 : mVideoOggState.mStartTime =
1018 0 : Some(TimeUnit::FromMicroseconds(videoStartTime));
1019 : }
1020 : }
1021 0 : if (HasAudio()) {
1022 0 : FindStartTime(TrackInfo::kAudioTrack, audioStartTime);
1023 0 : if (audioStartTime != INT64_MAX) {
1024 0 : OGG_DEBUG("OggDemuxer::FindStartTime() audio=%" PRId64, audioStartTime);
1025 : mAudioOggState.mStartTime =
1026 0 : Some(TimeUnit::FromMicroseconds(audioStartTime));
1027 : }
1028 : }
1029 :
1030 0 : int64_t startTime = std::min(videoStartTime, audioStartTime);
1031 0 : if (startTime != INT64_MAX) {
1032 0 : aOutStartTime = startTime;
1033 : }
1034 0 : }
1035 :
1036 : void
1037 0 : OggDemuxer::FindStartTime(TrackInfo::TrackType aType, int64_t& aOutStartTime)
1038 : {
1039 0 : int64_t startTime = INT64_MAX;
1040 :
1041 0 : OggCodecState* state = GetTrackCodecState(aType);
1042 0 : ogg_packet* pkt = GetNextPacket(aType);
1043 0 : if (pkt) {
1044 0 : startTime = state->PacketStartTime(pkt);
1045 : }
1046 :
1047 0 : if (startTime != INT64_MAX) {
1048 0 : aOutStartTime = startTime;
1049 : }
1050 0 : }
1051 :
1052 : nsresult
1053 0 : OggDemuxer::SeekInternal(TrackInfo::TrackType aType, const TimeUnit& aTarget)
1054 : {
1055 0 : int64_t target = aTarget.ToMicroseconds();
1056 0 : OGG_DEBUG("About to seek to %" PRId64, target);
1057 : nsresult res;
1058 0 : int64_t adjustedTarget = target;
1059 0 : int64_t startTime = StartTime(aType);
1060 0 : int64_t endTime = mInfo.mMetadataDuration->ToMicroseconds();
1061 0 : if (aType == TrackInfo::kAudioTrack && mOpusState){
1062 0 : adjustedTarget = std::max(startTime, target - OGG_SEEK_OPUS_PREROLL);
1063 : }
1064 :
1065 0 : if (!HaveStartTime(aType) || adjustedTarget == startTime) {
1066 : // We've seeked to the media start or we can't seek.
1067 : // Just seek to the offset of the first content page.
1068 0 : res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, 0);
1069 0 : NS_ENSURE_SUCCESS(res,res);
1070 :
1071 0 : res = Reset(aType);
1072 0 : NS_ENSURE_SUCCESS(res,res);
1073 : } else {
1074 : // TODO: This may seek back unnecessarily far in the video, but we don't
1075 : // have a way of asking Skeleton to seek to a different target for each
1076 : // stream yet. Using adjustedTarget here is at least correct, if slow.
1077 0 : IndexedSeekResult sres = SeekToKeyframeUsingIndex(aType, adjustedTarget);
1078 0 : NS_ENSURE_TRUE(sres != SEEK_FATAL_ERROR, NS_ERROR_FAILURE);
1079 0 : if (sres == SEEK_INDEX_FAIL) {
1080 : // No index or other non-fatal index-related failure. Try to seek
1081 : // using a bisection search. Determine the already downloaded data
1082 : // in the media cache, so we can try to seek in the cached data first.
1083 0 : AutoTArray<SeekRange, 16> ranges;
1084 0 : res = GetSeekRanges(aType, ranges);
1085 0 : NS_ENSURE_SUCCESS(res,res);
1086 :
1087 : // Figure out if the seek target lies in a buffered range.
1088 0 : SeekRange r = SelectSeekRange(aType, ranges, target, startTime, endTime, true);
1089 :
1090 0 : if (!r.IsNull()) {
1091 : // We know the buffered range in which the seek target lies, do a
1092 : // bisection search in that buffered range.
1093 0 : res = SeekInBufferedRange(aType, target, adjustedTarget, startTime, endTime, ranges, r);
1094 0 : NS_ENSURE_SUCCESS(res,res);
1095 : } else {
1096 : // The target doesn't lie in a buffered range. Perform a bisection
1097 : // search over the whole media, using the known buffered ranges to
1098 : // reduce the search space.
1099 0 : res = SeekInUnbuffered(aType, target, startTime, endTime, ranges);
1100 0 : NS_ENSURE_SUCCESS(res,res);
1101 : }
1102 : }
1103 : }
1104 :
1105 : // Demux forwards until we find the first keyframe prior the target.
1106 : // there may be non-keyframes in the page before the keyframe.
1107 : // Additionally, we may have seeked to the first page referenced by the
1108 : // page index which may be quite far off the target.
1109 : // When doing fastSeek we display the first frame after the seek, so
1110 : // we need to advance the decode to the keyframe otherwise we'll get
1111 : // visual artifacts in the first frame output after the seek.
1112 0 : OggCodecState* state = GetTrackCodecState(aType);
1113 0 : OggPacketQueue tempPackets;
1114 0 : bool foundKeyframe = false;
1115 : while (true) {
1116 0 : DemuxUntilPacketAvailable(aType, state);
1117 0 : ogg_packet* packet = state->PacketPeek();
1118 0 : if (packet == nullptr) {
1119 0 : OGG_DEBUG("End of stream reached before keyframe found in indexed seek");
1120 0 : break;
1121 : }
1122 0 : int64_t startTstamp = state->PacketStartTime(packet);
1123 0 : if (foundKeyframe && startTstamp > adjustedTarget) {
1124 0 : break;
1125 : }
1126 0 : if (state->IsKeyframe(packet)) {
1127 0 : OGG_DEBUG("keyframe found after seeking at %" PRId64, startTstamp);
1128 0 : tempPackets.Erase();
1129 0 : foundKeyframe = true;
1130 : }
1131 0 : if (foundKeyframe && startTstamp == adjustedTarget) {
1132 0 : break;
1133 : }
1134 0 : if (foundKeyframe) {
1135 0 : tempPackets.Append(state->PacketOut());
1136 : } else {
1137 : // Discard video packets before the first keyframe.
1138 0 : Unused << state->PacketOut();
1139 : }
1140 0 : }
1141 : // Re-add all packet into the codec state in order.
1142 0 : state->PushFront(Move(tempPackets));
1143 :
1144 0 : return NS_OK;
1145 : }
1146 :
1147 : OggDemuxer::IndexedSeekResult
1148 0 : OggDemuxer::RollbackIndexedSeek(TrackInfo::TrackType aType, int64_t aOffset)
1149 : {
1150 0 : if (mSkeletonState) {
1151 0 : mSkeletonState->Deactivate();
1152 : }
1153 0 : nsresult res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
1154 0 : NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR);
1155 0 : return SEEK_INDEX_FAIL;
1156 : }
1157 :
1158 : OggDemuxer::IndexedSeekResult
1159 0 : OggDemuxer::SeekToKeyframeUsingIndex(TrackInfo::TrackType aType, int64_t aTarget)
1160 : {
1161 0 : if (!HasSkeleton() || !mSkeletonState->HasIndex()) {
1162 0 : return SEEK_INDEX_FAIL;
1163 : }
1164 : // We have an index from the Skeleton track, try to use it to seek.
1165 0 : AutoTArray<uint32_t, 2> tracks;
1166 0 : BuildSerialList(tracks);
1167 0 : SkeletonState::nsSeekTarget keyframe;
1168 0 : if (NS_FAILED(mSkeletonState->IndexedSeekTarget(aTarget,
1169 : tracks,
1170 : keyframe))) {
1171 : // Could not locate a keypoint for the target in the index.
1172 0 : return SEEK_INDEX_FAIL;
1173 : }
1174 :
1175 : // Remember original resource read cursor position so we can rollback on failure.
1176 0 : int64_t tell = Resource(aType)->Tell();
1177 :
1178 : // Seek to the keypoint returned by the index.
1179 0 : if (keyframe.mKeyPoint.mOffset > Resource(aType)->GetLength() ||
1180 0 : keyframe.mKeyPoint.mOffset < 0) {
1181 : // Index must be invalid.
1182 0 : return RollbackIndexedSeek(aType, tell);
1183 : }
1184 0 : LOG(LogLevel::Debug, ("Seeking using index to keyframe at offset %" PRId64 "\n",
1185 : keyframe.mKeyPoint.mOffset));
1186 0 : nsresult res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET,
1187 0 : keyframe.mKeyPoint.mOffset);
1188 0 : NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR);
1189 :
1190 : // We've moved the read set, so reset decode.
1191 0 : res = Reset(aType);
1192 0 : NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR);
1193 :
1194 : // Check that the page the index thinks is exactly here is actually exactly
1195 : // here. If not, the index is invalid.
1196 : ogg_page page;
1197 0 : int skippedBytes = 0;
1198 0 : PageSyncResult syncres = PageSync(Resource(aType),
1199 : OggSyncState(aType),
1200 : false,
1201 : keyframe.mKeyPoint.mOffset,
1202 : Resource(aType)->GetLength(),
1203 : &page,
1204 0 : skippedBytes);
1205 0 : NS_ENSURE_TRUE(syncres != PAGE_SYNC_ERROR, SEEK_FATAL_ERROR);
1206 0 : if (syncres != PAGE_SYNC_OK || skippedBytes != 0) {
1207 0 : LOG(LogLevel::Debug, ("Indexed-seek failure: Ogg Skeleton Index is invalid "
1208 : "or sync error after seek"));
1209 0 : return RollbackIndexedSeek(aType, tell);
1210 : }
1211 0 : uint32_t serial = ogg_page_serialno(&page);
1212 0 : if (serial != keyframe.mSerial) {
1213 : // Serialno of page at offset isn't what the index told us to expect.
1214 : // Assume the index is invalid.
1215 0 : return RollbackIndexedSeek(aType, tell);
1216 : }
1217 0 : OggCodecState* codecState = mCodecStore.Get(serial);
1218 0 : if (codecState && codecState->mActive &&
1219 0 : ogg_stream_pagein(&codecState->mState, &page) != 0) {
1220 : // Couldn't insert page into the ogg resource, or somehow the resource
1221 : // is no longer active.
1222 0 : return RollbackIndexedSeek(aType, tell);
1223 : }
1224 0 : return SEEK_OK;
1225 : }
1226 :
1227 : // Reads a page from the media resource.
1228 : OggDemuxer::PageSyncResult
1229 0 : OggDemuxer::PageSync(MediaResourceIndex* aResource,
1230 : ogg_sync_state* aState,
1231 : bool aCachedDataOnly,
1232 : int64_t aOffset,
1233 : int64_t aEndOffset,
1234 : ogg_page* aPage,
1235 : int& aSkippedBytes)
1236 : {
1237 0 : aSkippedBytes = 0;
1238 : // Sync to the next page.
1239 0 : int ret = 0;
1240 0 : uint32_t bytesRead = 0;
1241 0 : int64_t readHead = aOffset;
1242 0 : while (ret <= 0) {
1243 0 : ret = ogg_sync_pageseek(aState, aPage);
1244 0 : if (ret == 0) {
1245 0 : char* buffer = ogg_sync_buffer(aState, PAGE_STEP);
1246 0 : NS_ASSERTION(buffer, "Must have a buffer");
1247 :
1248 : // Read from the file into the buffer
1249 : int64_t bytesToRead = std::min(static_cast<int64_t>(PAGE_STEP),
1250 0 : aEndOffset - readHead);
1251 0 : NS_ASSERTION(bytesToRead <= UINT32_MAX, "bytesToRead range check");
1252 0 : if (bytesToRead <= 0) {
1253 0 : return PAGE_SYNC_END_OF_RANGE;
1254 : }
1255 0 : nsresult rv = NS_OK;
1256 0 : if (aCachedDataOnly) {
1257 0 : rv = aResource->GetResource()->ReadFromCache(buffer, readHead,
1258 0 : static_cast<uint32_t>(bytesToRead));
1259 0 : NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR);
1260 0 : bytesRead = static_cast<uint32_t>(bytesToRead);
1261 : } else {
1262 0 : rv = aResource->Seek(nsISeekableStream::NS_SEEK_SET, readHead);
1263 0 : NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR);
1264 0 : rv = aResource->Read(buffer,
1265 : static_cast<uint32_t>(bytesToRead),
1266 0 : &bytesRead);
1267 0 : NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR);
1268 : }
1269 0 : if (bytesRead == 0 && NS_SUCCEEDED(rv)) {
1270 : // End of file.
1271 0 : return PAGE_SYNC_END_OF_RANGE;
1272 : }
1273 0 : readHead += bytesRead;
1274 :
1275 : // Update the synchronisation layer with the number
1276 : // of bytes written to the buffer
1277 0 : ret = ogg_sync_wrote(aState, bytesRead);
1278 0 : NS_ENSURE_TRUE(ret == 0, PAGE_SYNC_ERROR);
1279 0 : continue;
1280 : }
1281 :
1282 0 : if (ret < 0) {
1283 0 : NS_ASSERTION(aSkippedBytes >= 0, "Offset >= 0");
1284 0 : aSkippedBytes += -ret;
1285 0 : NS_ASSERTION(aSkippedBytes >= 0, "Offset >= 0");
1286 0 : continue;
1287 : }
1288 : }
1289 :
1290 0 : return PAGE_SYNC_OK;
1291 : }
1292 :
1293 : //OggTrackDemuxer
1294 0 : OggTrackDemuxer::OggTrackDemuxer(OggDemuxer* aParent,
1295 : TrackInfo::TrackType aType,
1296 0 : uint32_t aTrackNumber)
1297 : : mParent(aParent)
1298 0 : , mType(aType)
1299 : {
1300 0 : mInfo = mParent->GetTrackInfo(aType, aTrackNumber);
1301 0 : MOZ_ASSERT(mInfo);
1302 0 : }
1303 :
1304 0 : OggTrackDemuxer::~OggTrackDemuxer()
1305 : {
1306 0 : }
1307 :
1308 : UniquePtr<TrackInfo>
1309 0 : OggTrackDemuxer::GetInfo() const
1310 : {
1311 0 : return mInfo->Clone();
1312 : }
1313 :
1314 : RefPtr<OggTrackDemuxer::SeekPromise>
1315 0 : OggTrackDemuxer::Seek(const TimeUnit& aTime)
1316 : {
1317 : // Seeks to aTime. Upon success, SeekPromise will be resolved with the
1318 : // actual time seeked to. Typically the random access point time
1319 0 : mQueuedSample = nullptr;
1320 0 : TimeUnit seekTime = aTime;
1321 0 : if (mParent->SeekInternal(mType, aTime) == NS_OK) {
1322 0 : RefPtr<MediaRawData> sample(NextSample());
1323 :
1324 : // Check what time we actually seeked to.
1325 0 : if (sample != nullptr) {
1326 0 : seekTime = sample->mTime;
1327 0 : OGG_DEBUG("%p seeked to time %" PRId64, this, seekTime.ToMicroseconds());
1328 : }
1329 0 : mQueuedSample = sample;
1330 :
1331 0 : return SeekPromise::CreateAndResolve(seekTime, __func__);
1332 : } else {
1333 0 : return SeekPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, __func__);
1334 : }
1335 : }
1336 :
1337 : RefPtr<MediaRawData>
1338 0 : OggTrackDemuxer::NextSample()
1339 : {
1340 0 : if (mQueuedSample) {
1341 0 : RefPtr<MediaRawData> nextSample = mQueuedSample;
1342 0 : mQueuedSample = nullptr;
1343 0 : if (mType == TrackInfo::kAudioTrack) {
1344 0 : nextSample->mTrackInfo = mParent->mSharedAudioTrackInfo;
1345 : }
1346 0 : return nextSample;
1347 : }
1348 0 : ogg_packet* packet = mParent->GetNextPacket(mType);
1349 0 : if (!packet) {
1350 0 : return nullptr;
1351 : }
1352 : // Check the eos state in case we need to look for chained streams.
1353 0 : bool eos = packet->e_o_s;
1354 0 : OggCodecState* state = mParent->GetTrackCodecState(mType);
1355 0 : RefPtr<MediaRawData> data = state->PacketOutAsMediaRawData();
1356 0 : if (!data) {
1357 0 : return nullptr;
1358 : }
1359 0 : if (mType == TrackInfo::kAudioTrack) {
1360 0 : data->mTrackInfo = mParent->mSharedAudioTrackInfo;
1361 : }
1362 0 : if (eos) {
1363 : // We've encountered an end of bitstream packet; check for a chained
1364 : // bitstream following this one.
1365 : // This will also update mSharedAudioTrackInfo.
1366 0 : mParent->ReadOggChain(data->GetEndTime());
1367 : }
1368 0 : return data;
1369 : }
1370 :
1371 : RefPtr<OggTrackDemuxer::SamplesPromise>
1372 0 : OggTrackDemuxer::GetSamples(int32_t aNumSamples)
1373 : {
1374 0 : RefPtr<SamplesHolder> samples = new SamplesHolder;
1375 0 : if (!aNumSamples) {
1376 0 : return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, __func__);
1377 : }
1378 :
1379 0 : while (aNumSamples) {
1380 0 : RefPtr<MediaRawData> sample(NextSample());
1381 0 : if (!sample) {
1382 0 : break;
1383 : }
1384 0 : samples->mSamples.AppendElement(sample);
1385 0 : aNumSamples--;
1386 : }
1387 :
1388 0 : if (samples->mSamples.IsEmpty()) {
1389 0 : return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__);
1390 : } else {
1391 0 : return SamplesPromise::CreateAndResolve(samples, __func__);
1392 : }
1393 : }
1394 :
1395 : void
1396 0 : OggTrackDemuxer::Reset()
1397 : {
1398 0 : mParent->Reset(mType);
1399 0 : mQueuedSample = nullptr;
1400 0 : }
1401 :
1402 : RefPtr<OggTrackDemuxer::SkipAccessPointPromise>
1403 0 : OggTrackDemuxer::SkipToNextRandomAccessPoint(const TimeUnit& aTimeThreshold)
1404 : {
1405 0 : uint32_t parsed = 0;
1406 0 : bool found = false;
1407 0 : RefPtr<MediaRawData> sample;
1408 :
1409 0 : OGG_DEBUG("TimeThreshold: %f", aTimeThreshold.ToSeconds());
1410 0 : while (!found && (sample = NextSample())) {
1411 0 : parsed++;
1412 0 : if (sample->mKeyframe && sample->mTime >= aTimeThreshold) {
1413 0 : found = true;
1414 0 : mQueuedSample = sample;
1415 : }
1416 : }
1417 0 : if (found) {
1418 0 : OGG_DEBUG("next sample: %f (parsed: %d)",
1419 : sample->mTime.ToSeconds(), parsed);
1420 0 : return SkipAccessPointPromise::CreateAndResolve(parsed, __func__);
1421 : } else {
1422 0 : SkipFailureHolder failure(NS_ERROR_DOM_MEDIA_END_OF_STREAM, parsed);
1423 0 : return SkipAccessPointPromise::CreateAndReject(Move(failure), __func__);
1424 : }
1425 : }
1426 :
1427 : TimeIntervals
1428 0 : OggTrackDemuxer::GetBuffered()
1429 : {
1430 0 : return mParent->GetBuffered(mType);
1431 : }
1432 :
1433 : void
1434 0 : OggTrackDemuxer::BreakCycles()
1435 : {
1436 0 : mParent = nullptr;
1437 0 : }
1438 :
1439 :
1440 : // Returns an ogg page's checksum.
1441 : ogg_uint32_t
1442 0 : OggDemuxer::GetPageChecksum(ogg_page* page)
1443 : {
1444 0 : if (page == 0 || page->header == 0 || page->header_len < 25) {
1445 0 : return 0;
1446 : }
1447 0 : const unsigned char* p = page->header + 22;
1448 0 : uint32_t c = p[0] + (p[1] << 8) + (p[2] << 16) + (p[3] << 24);
1449 0 : return c;
1450 : }
1451 :
1452 : int64_t
1453 0 : OggDemuxer::RangeStartTime(TrackInfo::TrackType aType, int64_t aOffset)
1454 : {
1455 0 : int64_t position = Resource(aType)->Tell();
1456 0 : nsresult res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
1457 0 : NS_ENSURE_SUCCESS(res, 0);
1458 0 : int64_t startTime = 0;
1459 0 : FindStartTime(aType, startTime);
1460 0 : res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, position);
1461 0 : NS_ENSURE_SUCCESS(res, -1);
1462 0 : return startTime;
1463 : }
1464 :
1465 : struct nsDemuxerAutoOggSyncState
1466 : {
1467 0 : nsDemuxerAutoOggSyncState()
1468 0 : {
1469 0 : ogg_sync_init(&mState);
1470 0 : }
1471 0 : ~nsDemuxerAutoOggSyncState()
1472 0 : {
1473 0 : ogg_sync_clear(&mState);
1474 0 : }
1475 : ogg_sync_state mState;
1476 : };
1477 :
1478 : int64_t
1479 0 : OggDemuxer::RangeEndTime(TrackInfo::TrackType aType, int64_t aEndOffset)
1480 : {
1481 0 : int64_t position = Resource(aType)->Tell();
1482 0 : int64_t endTime = RangeEndTime(aType, 0, aEndOffset, false);
1483 0 : nsresult res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, position);
1484 0 : NS_ENSURE_SUCCESS(res, -1);
1485 0 : return endTime;
1486 : }
1487 :
1488 : int64_t
1489 0 : OggDemuxer::RangeEndTime(TrackInfo::TrackType aType,
1490 : int64_t aStartOffset,
1491 : int64_t aEndOffset,
1492 : bool aCachedDataOnly)
1493 : {
1494 0 : nsDemuxerAutoOggSyncState sync;
1495 :
1496 : // We need to find the last page which ends before aEndOffset that
1497 : // has a granulepos that we can convert to a timestamp. We do this by
1498 : // backing off from aEndOffset until we encounter a page on which we can
1499 : // interpret the granulepos. If while backing off we encounter a page which
1500 : // we've previously encountered before, we'll either backoff again if we
1501 : // haven't found an end time yet, or return the last end time found.
1502 0 : const int step = 5000;
1503 0 : const int maxOggPageSize = 65306;
1504 0 : int64_t readStartOffset = aEndOffset;
1505 0 : int64_t readLimitOffset = aEndOffset;
1506 0 : int64_t readHead = aEndOffset;
1507 0 : int64_t endTime = -1;
1508 0 : uint32_t checksumAfterSeek = 0;
1509 0 : uint32_t prevChecksumAfterSeek = 0;
1510 0 : bool mustBackOff = false;
1511 : while (true) {
1512 : ogg_page page;
1513 0 : int ret = ogg_sync_pageseek(&sync.mState, &page);
1514 0 : if (ret == 0) {
1515 : // We need more data if we've not encountered a page we've seen before,
1516 : // or we've read to the end of file.
1517 0 : if (mustBackOff || readHead == aEndOffset || readHead == aStartOffset) {
1518 0 : if (endTime != -1 || readStartOffset == 0) {
1519 : // We have encountered a page before, or we're at the end of file.
1520 : break;
1521 : }
1522 0 : mustBackOff = false;
1523 0 : prevChecksumAfterSeek = checksumAfterSeek;
1524 0 : checksumAfterSeek = 0;
1525 0 : ogg_sync_reset(&sync.mState);
1526 0 : readStartOffset = std::max(static_cast<int64_t>(0), readStartOffset - step);
1527 : // There's no point reading more than the maximum size of
1528 : // an Ogg page into data we've previously scanned. Any data
1529 : // between readLimitOffset and aEndOffset must be garbage
1530 : // and we can ignore it thereafter.
1531 0 : readLimitOffset = std::min(readLimitOffset,
1532 0 : readStartOffset + maxOggPageSize);
1533 0 : readHead = std::max(aStartOffset, readStartOffset);
1534 : }
1535 :
1536 0 : int64_t limit = std::min(static_cast<int64_t>(UINT32_MAX),
1537 0 : aEndOffset - readHead);
1538 0 : limit = std::max(static_cast<int64_t>(0), limit);
1539 0 : limit = std::min(limit, static_cast<int64_t>(step));
1540 0 : uint32_t bytesToRead = static_cast<uint32_t>(limit);
1541 0 : uint32_t bytesRead = 0;
1542 0 : char* buffer = ogg_sync_buffer(&sync.mState, bytesToRead);
1543 0 : NS_ASSERTION(buffer, "Must have buffer");
1544 : nsresult res;
1545 0 : if (aCachedDataOnly) {
1546 0 : res = Resource(aType)->GetResource()->ReadFromCache(buffer, readHead, bytesToRead);
1547 0 : NS_ENSURE_SUCCESS(res, -1);
1548 0 : bytesRead = bytesToRead;
1549 : } else {
1550 0 : NS_ASSERTION(readHead < aEndOffset,
1551 : "resource pos must be before range end");
1552 0 : res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, readHead);
1553 0 : NS_ENSURE_SUCCESS(res, -1);
1554 0 : res = Resource(aType)->Read(buffer, bytesToRead, &bytesRead);
1555 0 : NS_ENSURE_SUCCESS(res, -1);
1556 : }
1557 0 : readHead += bytesRead;
1558 0 : if (readHead > readLimitOffset) {
1559 0 : mustBackOff = true;
1560 : }
1561 :
1562 : // Update the synchronisation layer with the number
1563 : // of bytes written to the buffer
1564 0 : ret = ogg_sync_wrote(&sync.mState, bytesRead);
1565 0 : if (ret != 0) {
1566 0 : endTime = -1;
1567 0 : break;
1568 : }
1569 0 : continue;
1570 : }
1571 :
1572 0 : if (ret < 0 || ogg_page_granulepos(&page) < 0) {
1573 0 : continue;
1574 : }
1575 :
1576 0 : uint32_t checksum = GetPageChecksum(&page);
1577 0 : if (checksumAfterSeek == 0) {
1578 : // This is the first page we've decoded after a backoff/seek. Remember
1579 : // the page checksum. If we backoff further and encounter this page
1580 : // again, we'll know that we won't find a page with an end time after
1581 : // this one, so we'll know to back off again.
1582 0 : checksumAfterSeek = checksum;
1583 : }
1584 0 : if (checksum == prevChecksumAfterSeek) {
1585 : // This page has the same checksum as the first page we encountered
1586 : // after the last backoff/seek. Since we've already scanned after this
1587 : // page and failed to find an end time, we may as well backoff again and
1588 : // try to find an end time from an earlier page.
1589 0 : mustBackOff = true;
1590 0 : continue;
1591 : }
1592 :
1593 0 : int64_t granulepos = ogg_page_granulepos(&page);
1594 0 : int serial = ogg_page_serialno(&page);
1595 :
1596 0 : OggCodecState* codecState = nullptr;
1597 0 : codecState = mCodecStore.Get(serial);
1598 0 : if (!codecState) {
1599 : // This page is from a bitstream which we haven't encountered yet.
1600 : // It's probably from a new "link" in a "chained" ogg. Don't
1601 : // bother even trying to find a duration...
1602 0 : SetChained();
1603 0 : endTime = -1;
1604 0 : break;
1605 : }
1606 :
1607 0 : int64_t t = codecState->Time(granulepos);
1608 0 : if (t != -1) {
1609 0 : endTime = t;
1610 : }
1611 0 : }
1612 :
1613 0 : return endTime;
1614 : }
1615 :
1616 : nsresult
1617 0 : OggDemuxer::GetSeekRanges(TrackInfo::TrackType aType,
1618 : nsTArray<SeekRange>& aRanges)
1619 : {
1620 0 : AutoPinned<MediaResource> resource(Resource(aType)->GetResource());
1621 0 : MediaByteRangeSet cached;
1622 0 : nsresult res = resource->GetCachedRanges(cached);
1623 0 : NS_ENSURE_SUCCESS(res, res);
1624 :
1625 0 : for (uint32_t index = 0; index < cached.Length(); index++) {
1626 0 : auto& range = cached[index];
1627 0 : int64_t startTime = -1;
1628 0 : int64_t endTime = -1;
1629 0 : if (NS_FAILED(Reset(aType))) {
1630 0 : return NS_ERROR_FAILURE;
1631 : }
1632 0 : int64_t startOffset = range.mStart;
1633 0 : int64_t endOffset = range.mEnd;
1634 0 : startTime = RangeStartTime(aType, startOffset);
1635 0 : if (startTime != -1 &&
1636 : ((endTime = RangeEndTime(aType, endOffset)) != -1)) {
1637 0 : NS_WARNING_ASSERTION(startTime < endTime,
1638 : "Start time must be before end time");
1639 0 : aRanges.AppendElement(SeekRange(startOffset,
1640 : endOffset,
1641 : startTime,
1642 0 : endTime));
1643 : }
1644 : }
1645 0 : if (NS_FAILED(Reset(aType))) {
1646 0 : return NS_ERROR_FAILURE;
1647 : }
1648 0 : return NS_OK;
1649 : }
1650 :
1651 : OggDemuxer::SeekRange
1652 0 : OggDemuxer::SelectSeekRange(TrackInfo::TrackType aType,
1653 : const nsTArray<SeekRange>& ranges,
1654 : int64_t aTarget,
1655 : int64_t aStartTime,
1656 : int64_t aEndTime,
1657 : bool aExact)
1658 : {
1659 0 : int64_t so = 0;
1660 0 : int64_t eo = Resource(aType)->GetLength();
1661 0 : int64_t st = aStartTime;
1662 0 : int64_t et = aEndTime;
1663 0 : for (uint32_t i = 0; i < ranges.Length(); i++) {
1664 0 : const SeekRange& r = ranges[i];
1665 0 : if (r.mTimeStart < aTarget) {
1666 0 : so = r.mOffsetStart;
1667 0 : st = r.mTimeStart;
1668 : }
1669 0 : if (r.mTimeEnd >= aTarget && r.mTimeEnd < et) {
1670 0 : eo = r.mOffsetEnd;
1671 0 : et = r.mTimeEnd;
1672 : }
1673 :
1674 0 : if (r.mTimeStart < aTarget && aTarget <= r.mTimeEnd) {
1675 : // Target lies exactly in this range.
1676 0 : return ranges[i];
1677 : }
1678 : }
1679 0 : if (aExact || eo == -1) {
1680 0 : return SeekRange();
1681 : }
1682 0 : return SeekRange(so, eo, st, et);
1683 : }
1684 :
1685 :
1686 : nsresult
1687 0 : OggDemuxer::SeekInBufferedRange(TrackInfo::TrackType aType,
1688 : int64_t aTarget,
1689 : int64_t aAdjustedTarget,
1690 : int64_t aStartTime,
1691 : int64_t aEndTime,
1692 : const nsTArray<SeekRange>& aRanges,
1693 : const SeekRange& aRange)
1694 : {
1695 0 : OGG_DEBUG("Seeking in buffered data to %" PRId64 " using bisection search", aTarget);
1696 0 : if (aType == TrackInfo::kVideoTrack || aAdjustedTarget >= aTarget) {
1697 : // We know the exact byte range in which the target must lie. It must
1698 : // be buffered in the media cache. Seek there.
1699 0 : nsresult res = SeekBisection(aType, aTarget, aRange, 0);
1700 0 : if (NS_FAILED(res) || aType != TrackInfo::kVideoTrack) {
1701 0 : return res;
1702 : }
1703 :
1704 : // We have an active Theora bitstream. Peek the next Theora frame, and
1705 : // extract its keyframe's time.
1706 0 : DemuxUntilPacketAvailable(aType, mTheoraState);
1707 0 : ogg_packet* packet = mTheoraState->PacketPeek();
1708 0 : if (packet && !mTheoraState->IsKeyframe(packet)) {
1709 : // First post-seek frame isn't a keyframe, seek back to previous keyframe,
1710 : // otherwise we'll get visual artifacts.
1711 0 : NS_ASSERTION(packet->granulepos != -1, "Must have a granulepos");
1712 0 : int shift = mTheoraState->KeyFrameGranuleJobs();
1713 0 : int64_t keyframeGranulepos = (packet->granulepos >> shift) << shift;
1714 0 : int64_t keyframeTime = mTheoraState->StartTime(keyframeGranulepos);
1715 : SEEK_LOG(LogLevel::Debug,
1716 : ("Keyframe for %lld is at %lld, seeking back to it", frameTime,
1717 : keyframeTime));
1718 0 : aAdjustedTarget = std::min(aAdjustedTarget, keyframeTime);
1719 : }
1720 : }
1721 :
1722 0 : nsresult res = NS_OK;
1723 0 : if (aAdjustedTarget < aTarget) {
1724 : SeekRange k = SelectSeekRange(aType,
1725 : aRanges,
1726 : aAdjustedTarget,
1727 : aStartTime,
1728 : aEndTime,
1729 0 : false);
1730 0 : res = SeekBisection(aType, aAdjustedTarget, k, OGG_SEEK_FUZZ_USECS);
1731 : }
1732 0 : return res;
1733 : }
1734 :
1735 : nsresult
1736 0 : OggDemuxer::SeekInUnbuffered(TrackInfo::TrackType aType,
1737 : int64_t aTarget,
1738 : int64_t aStartTime,
1739 : int64_t aEndTime,
1740 : const nsTArray<SeekRange>& aRanges)
1741 : {
1742 0 : OGG_DEBUG("Seeking in unbuffered data to %" PRId64 " using bisection search", aTarget);
1743 :
1744 : // If we've got an active Theora bitstream, determine the maximum possible
1745 : // time in usecs which a keyframe could be before a given interframe. We
1746 : // subtract this from our seek target, seek to the new target, and then
1747 : // will decode forward to the original seek target. We should encounter a
1748 : // keyframe in that interval. This prevents us from needing to run two
1749 : // bisections; one for the seek target frame, and another to find its
1750 : // keyframe. It's usually faster to just download this extra data, rather
1751 : // tham perform two bisections to find the seek target's keyframe. We
1752 : // don't do this offsetting when seeking in a buffered range,
1753 : // as the extra decoding causes a noticeable speed hit when all the data
1754 : // is buffered (compared to just doing a bisection to exactly find the
1755 : // keyframe).
1756 0 : int64_t keyframeOffsetMs = 0;
1757 0 : if (aType == TrackInfo::kVideoTrack && mTheoraState) {
1758 0 : keyframeOffsetMs = mTheoraState->MaxKeyframeOffset();
1759 : }
1760 : // Add in the Opus pre-roll if necessary, as well.
1761 0 : if (aType == TrackInfo::kAudioTrack && mOpusState) {
1762 0 : keyframeOffsetMs = std::max(keyframeOffsetMs, OGG_SEEK_OPUS_PREROLL);
1763 : }
1764 0 : int64_t seekTarget = std::max(aStartTime, aTarget - keyframeOffsetMs);
1765 : // Minimize the bisection search space using the known timestamps from the
1766 : // buffered ranges.
1767 : SeekRange k =
1768 0 : SelectSeekRange(aType, aRanges, seekTarget, aStartTime, aEndTime, false);
1769 0 : return SeekBisection(aType, seekTarget, k, OGG_SEEK_FUZZ_USECS);
1770 : }
1771 :
1772 : nsresult
1773 0 : OggDemuxer::SeekBisection(TrackInfo::TrackType aType,
1774 : int64_t aTarget,
1775 : const SeekRange& aRange,
1776 : uint32_t aFuzz)
1777 : {
1778 : nsresult res;
1779 :
1780 0 : if (aTarget <= aRange.mTimeStart) {
1781 0 : if (NS_FAILED(Reset(aType))) {
1782 0 : return NS_ERROR_FAILURE;
1783 : }
1784 0 : res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, 0);
1785 0 : NS_ENSURE_SUCCESS(res,res);
1786 0 : return NS_OK;
1787 : }
1788 :
1789 : // Bisection search, find start offset of last page with end time less than
1790 : // the seek target.
1791 0 : ogg_int64_t startOffset = aRange.mOffsetStart;
1792 0 : ogg_int64_t startTime = aRange.mTimeStart;
1793 0 : ogg_int64_t startLength = 0; // Length of the page at startOffset.
1794 0 : ogg_int64_t endOffset = aRange.mOffsetEnd;
1795 0 : ogg_int64_t endTime = aRange.mTimeEnd;
1796 :
1797 0 : ogg_int64_t seekTarget = aTarget;
1798 0 : int64_t seekLowerBound = std::max(static_cast<int64_t>(0), aTarget - aFuzz);
1799 0 : int hops = 0;
1800 0 : DebugOnly<ogg_int64_t> previousGuess = -1;
1801 0 : int backsteps = 0;
1802 0 : const int maxBackStep = 10;
1803 0 : NS_ASSERTION(static_cast<uint64_t>(PAGE_STEP) * pow(2.0, maxBackStep) < INT32_MAX,
1804 : "Backstep calculation must not overflow");
1805 :
1806 : // Seek via bisection search. Loop until we find the offset where the page
1807 : // before the offset is before the seek target, and the page after the offset
1808 : // is after the seek target.
1809 : while (true) {
1810 0 : ogg_int64_t duration = 0;
1811 0 : double target = 0;
1812 0 : ogg_int64_t interval = 0;
1813 0 : ogg_int64_t guess = 0;
1814 : ogg_page page;
1815 0 : int skippedBytes = 0;
1816 0 : ogg_int64_t pageOffset = 0;
1817 0 : ogg_int64_t pageLength = 0;
1818 0 : ogg_int64_t granuleTime = -1;
1819 0 : bool mustBackoff = false;
1820 :
1821 : // Guess where we should bisect to, based on the bit rate and the time
1822 : // remaining in the interval. Loop until we can determine the time at
1823 : // the guess offset.
1824 : while (true) {
1825 :
1826 : // Discard any previously buffered packets/pages.
1827 0 : if (NS_FAILED(Reset(aType))) {
1828 0 : return NS_ERROR_FAILURE;
1829 : }
1830 :
1831 0 : interval = endOffset - startOffset - startLength;
1832 0 : if (interval == 0) {
1833 : // Our interval is empty, we've found the optimal seek point, as the
1834 : // page at the start offset is before the seek target, and the page
1835 : // at the end offset is after the seek target.
1836 : SEEK_LOG(LogLevel::Debug, ("Interval narrowed, terminating bisection."));
1837 0 : break;
1838 : }
1839 :
1840 : // Guess bisection point.
1841 0 : duration = endTime - startTime;
1842 0 : target = (double)(seekTarget - startTime) / (double)duration;
1843 0 : guess = startOffset + startLength +
1844 0 : static_cast<ogg_int64_t>((double)interval * target);
1845 0 : guess = std::min(guess, endOffset - PAGE_STEP);
1846 0 : if (mustBackoff) {
1847 : // We previously failed to determine the time at the guess offset,
1848 : // probably because we ran out of data to decode. This usually happens
1849 : // when we guess very close to the end offset. So reduce the guess
1850 : // offset using an exponential backoff until we determine the time.
1851 : SEEK_LOG(LogLevel::Debug, ("Backing off %d bytes, backsteps=%d",
1852 : static_cast<int32_t>(PAGE_STEP * pow(2.0, backsteps)), backsteps));
1853 0 : guess -= PAGE_STEP * static_cast<ogg_int64_t>(pow(2.0, backsteps));
1854 :
1855 0 : if (guess <= startOffset) {
1856 : // We've tried to backoff to before the start offset of our seek
1857 : // range. This means we couldn't find a seek termination position
1858 : // near the end of the seek range, so just set the seek termination
1859 : // condition, and break out of the bisection loop. We'll begin
1860 : // decoding from the start of the seek range.
1861 0 : interval = 0;
1862 0 : break;
1863 : }
1864 :
1865 0 : backsteps = std::min(backsteps + 1, maxBackStep);
1866 : // We reset mustBackoff. If we still need to backoff further, it will
1867 : // be set to true again.
1868 0 : mustBackoff = false;
1869 : } else {
1870 0 : backsteps = 0;
1871 : }
1872 0 : guess = std::max(guess, startOffset + startLength);
1873 :
1874 : SEEK_LOG(LogLevel::Debug, ("Seek loop start[o=%lld..%lld t=%lld] "
1875 : "end[o=%lld t=%lld] "
1876 : "interval=%lld target=%lf guess=%lld",
1877 : startOffset, (startOffset+startLength), startTime,
1878 : endOffset, endTime, interval, target, guess));
1879 :
1880 0 : NS_ASSERTION(guess >= startOffset + startLength, "Guess must be after range start");
1881 0 : NS_ASSERTION(guess < endOffset, "Guess must be before range end");
1882 0 : NS_ASSERTION(guess != previousGuess, "Guess should be different to previous");
1883 0 : previousGuess = guess;
1884 :
1885 0 : hops++;
1886 :
1887 : // Locate the next page after our seek guess, and then figure out the
1888 : // granule time of the audio and video bitstreams there. We can then
1889 : // make a bisection decision based on our location in the media.
1890 0 : PageSyncResult pageSyncResult = PageSync(Resource(aType),
1891 : OggSyncState(aType),
1892 : false,
1893 : guess,
1894 : endOffset,
1895 : &page,
1896 0 : skippedBytes);
1897 0 : NS_ENSURE_TRUE(pageSyncResult != PAGE_SYNC_ERROR, NS_ERROR_FAILURE);
1898 :
1899 0 : if (pageSyncResult == PAGE_SYNC_END_OF_RANGE) {
1900 : // Our guess was too close to the end, we've ended up reading the end
1901 : // page. Backoff exponentially from the end point, in case the last
1902 : // page/frame/sample is huge.
1903 0 : mustBackoff = true;
1904 : SEEK_LOG(LogLevel::Debug, ("Hit the end of range, backing off"));
1905 0 : continue;
1906 : }
1907 :
1908 : // We've located a page of length |ret| at |guess + skippedBytes|.
1909 : // Remember where the page is located.
1910 0 : pageOffset = guess + skippedBytes;
1911 0 : pageLength = page.header_len + page.body_len;
1912 :
1913 : // Read pages until we can determine the granule time of the audio and
1914 : // video bitstream.
1915 0 : ogg_int64_t audioTime = -1;
1916 0 : ogg_int64_t videoTime = -1;
1917 0 : do {
1918 : // Add the page to its codec state, determine its granule time.
1919 0 : uint32_t serial = ogg_page_serialno(&page);
1920 0 : OggCodecState* codecState = mCodecStore.Get(serial);
1921 0 : if (codecState && GetCodecStateType(codecState) == aType) {
1922 0 : if (codecState->mActive) {
1923 0 : int ret = ogg_stream_pagein(&codecState->mState, &page);
1924 0 : NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE);
1925 : }
1926 :
1927 0 : ogg_int64_t granulepos = ogg_page_granulepos(&page);
1928 :
1929 0 : if (aType == TrackInfo::kAudioTrack &&
1930 0 : granulepos > 0 && audioTime == -1) {
1931 0 : if (mVorbisState && serial == mVorbisState->mSerial) {
1932 0 : audioTime = mVorbisState->Time(granulepos);
1933 0 : } else if (mOpusState && serial == mOpusState->mSerial) {
1934 0 : audioTime = mOpusState->Time(granulepos);
1935 0 : } else if (mFlacState && serial == mFlacState->mSerial) {
1936 0 : audioTime = mFlacState->Time(granulepos);
1937 : }
1938 : }
1939 :
1940 0 : if (aType == TrackInfo::kVideoTrack &&
1941 0 : granulepos > 0 && serial == mTheoraState->mSerial &&
1942 : videoTime == -1) {
1943 0 : videoTime = mTheoraState->Time(granulepos);
1944 : }
1945 :
1946 0 : if (pageOffset + pageLength >= endOffset) {
1947 : // Hit end of readable data.
1948 0 : break;
1949 : }
1950 : }
1951 0 : if (!ReadOggPage(aType, &page)) {
1952 0 : break;
1953 : }
1954 :
1955 0 : } while ((aType == TrackInfo::kAudioTrack && audioTime == -1) ||
1956 0 : (aType == TrackInfo::kVideoTrack && videoTime == -1));
1957 :
1958 :
1959 0 : if ((aType == TrackInfo::kAudioTrack && audioTime == -1) ||
1960 0 : (aType == TrackInfo::kVideoTrack && videoTime == -1)) {
1961 : // We don't have timestamps for all active tracks...
1962 0 : if (pageOffset == startOffset + startLength &&
1963 0 : pageOffset + pageLength >= endOffset) {
1964 : // We read the entire interval without finding timestamps for all
1965 : // active tracks. We know the interval start offset is before the seek
1966 : // target, and the interval end is after the seek target, and we can't
1967 : // terminate inside the interval, so we terminate the seek at the
1968 : // start of the interval.
1969 0 : interval = 0;
1970 0 : break;
1971 : }
1972 :
1973 : // We should backoff; cause the guess to back off from the end, so
1974 : // that we've got more room to capture.
1975 0 : mustBackoff = true;
1976 0 : continue;
1977 : }
1978 :
1979 : // We've found appropriate time stamps here. Proceed to bisect
1980 : // the search space.
1981 0 : granuleTime = aType == TrackInfo::kAudioTrack ? audioTime : videoTime;
1982 0 : NS_ASSERTION(granuleTime > 0, "Must get a granuletime");
1983 0 : break;
1984 0 : } // End of "until we determine time at guess offset" loop.
1985 :
1986 0 : if (interval == 0) {
1987 : // Seek termination condition; we've found the page boundary of the
1988 : // last page before the target, and the first page after the target.
1989 : SEEK_LOG(LogLevel::Debug, ("Terminating seek at offset=%lld", startOffset));
1990 0 : NS_ASSERTION(startTime < aTarget, "Start time must always be less than target");
1991 0 : res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, startOffset);
1992 0 : NS_ENSURE_SUCCESS(res,res);
1993 0 : if (NS_FAILED(Reset(aType))) {
1994 0 : return NS_ERROR_FAILURE;
1995 : }
1996 0 : break;
1997 : }
1998 :
1999 : SEEK_LOG(LogLevel::Debug, ("Time at offset %lld is %lld", guess, granuleTime));
2000 0 : if (granuleTime < seekTarget && granuleTime > seekLowerBound) {
2001 : // We're within the fuzzy region in which we want to terminate the search.
2002 0 : res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, pageOffset);
2003 0 : NS_ENSURE_SUCCESS(res,res);
2004 0 : if (NS_FAILED(Reset(aType))) {
2005 0 : return NS_ERROR_FAILURE;
2006 : }
2007 : SEEK_LOG(LogLevel::Debug, ("Terminating seek at offset=%lld", pageOffset));
2008 0 : break;
2009 : }
2010 :
2011 0 : if (granuleTime >= seekTarget) {
2012 : // We've landed after the seek target.
2013 0 : NS_ASSERTION(pageOffset < endOffset, "offset_end must decrease");
2014 0 : endOffset = pageOffset;
2015 0 : endTime = granuleTime;
2016 0 : } else if (granuleTime < seekTarget) {
2017 : // Landed before seek target.
2018 0 : NS_ASSERTION(pageOffset >= startOffset + startLength,
2019 : "Bisection point should be at or after end of first page in interval");
2020 0 : startOffset = pageOffset;
2021 0 : startLength = pageLength;
2022 0 : startTime = granuleTime;
2023 : }
2024 0 : NS_ASSERTION(startTime <= seekTarget, "Must be before seek target");
2025 0 : NS_ASSERTION(endTime >= seekTarget, "End must be after seek target");
2026 0 : }
2027 :
2028 : SEEK_LOG(LogLevel::Debug, ("Seek complete in %d bisections.", hops));
2029 :
2030 0 : return NS_OK;
2031 : }
2032 :
2033 : #undef OGG_DEBUG
2034 : #undef SEEK_DEBUG
2035 : } // namespace mozilla
|