Coverage Report

Created: 2018-09-25 14:53

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