content/media/ogg/OggReader.cpp

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

mercurial