content/media/ogg/OggReader.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial