LCOV - code coverage report
Current view: directory - content/media/ogg - nsOggReader.cpp (source / functions) Found Hit Coverage
Test: app.info Lines: 701 0 0.0 %
Date: 2012-06-02 Functions: 32 0 0.0 %

       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                 : /* ***** BEGIN LICENSE BLOCK *****
       4                 :  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
       5                 :  *
       6                 :  * The contents of this file are subject to the Mozilla Public License Version
       7                 :  * 1.1 (the "License"); you may not use this file except in compliance with
       8                 :  * the License. You may obtain a copy of the License at
       9                 :  * http://www.mozilla.org/MPL/
      10                 :  *
      11                 :  * Software distributed under the License is distributed on an "AS IS" basis,
      12                 :  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
      13                 :  * for the specific language governing rights and limitations under the
      14                 :  * License.
      15                 :  *
      16                 :  * The Original Code is Mozilla code.
      17                 :  *
      18                 :  * The Initial Developer of the Original Code is the Mozilla Corporation.
      19                 :  * Portions created by the Initial Developer are Copyright (C) 2007
      20                 :  * the Initial Developer. All Rights Reserved.
      21                 :  *
      22                 :  * Contributor(s):
      23                 :  *  Chris Double <chris.double@double.co.nz>
      24                 :  *  Chris Pearce <chris@pearce.org.nz>
      25                 :  *
      26                 :  * Alternatively, the contents of this file may be used under the terms of
      27                 :  * either the GNU General Public License Version 2 or later (the "GPL"), or
      28                 :  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
      29                 :  * in which case the provisions of the GPL or the LGPL are applicable instead
      30                 :  * of those above. If you wish to allow use of your version of this file only
      31                 :  * under the terms of either the GPL or the LGPL, and not to allow others to
      32                 :  * use your version of this file under the terms of the MPL, indicate your
      33                 :  * decision by deleting the provisions above and replace them with the notice
      34                 :  * and other provisions required by the GPL or the LGPL. If you do not delete
      35                 :  * the provisions above, a recipient may use your version of this file under
      36                 :  * the terms of any one of the MPL, the GPL or the LGPL.
      37                 :  *
      38                 :  * ***** END LICENSE BLOCK ***** */
      39                 : #include "nsError.h"
      40                 : #include "nsBuiltinDecoderStateMachine.h"
      41                 : #include "nsBuiltinDecoder.h"
      42                 : #include "nsOggReader.h"
      43                 : #include "VideoUtils.h"
      44                 : #include "theora/theoradec.h"
      45                 : #include "nsTimeRanges.h"
      46                 : #include "mozilla/TimeStamp.h"
      47                 : 
      48                 : using namespace mozilla;
      49                 : 
      50                 : // Un-comment to enable logging of seek bisections.
      51                 : //#define SEEK_LOGGING
      52                 : 
      53                 : #ifdef PR_LOGGING
      54                 : extern PRLogModuleInfo* gBuiltinDecoderLog;
      55                 : #define LOG(type, msg) PR_LOG(gBuiltinDecoderLog, type, msg)
      56                 : #ifdef SEEK_LOGGING
      57                 : #define SEEK_LOG(type, msg) PR_LOG(gBuiltinDecoderLog, type, msg)
      58                 : #else
      59                 : #define SEEK_LOG(type, msg)
      60                 : #endif
      61                 : #else
      62                 : #define LOG(type, msg)
      63                 : #define SEEK_LOG(type, msg)
      64                 : #endif
      65                 : 
      66                 : // The number of microseconds of "fuzz" we use in a bisection search over
      67                 : // HTTP. When we're seeking with fuzz, we'll stop the search if a bisection
      68                 : // lands between the seek target and SEEK_FUZZ_USECS microseconds before the
      69                 : // seek target.  This is becaue it's usually quicker to just keep downloading
      70                 : // from an exisiting connection than to do another bisection inside that
      71                 : // small range, which would open a new HTTP connetion.
      72                 : static const PRUint32 SEEK_FUZZ_USECS = 500000;
      73                 : 
      74                 : enum PageSyncResult {
      75                 :   PAGE_SYNC_ERROR = 1,
      76                 :   PAGE_SYNC_END_OF_RANGE= 2,
      77                 :   PAGE_SYNC_OK = 3
      78                 : };
      79                 : 
      80                 : // Reads a page from the media resource.
      81                 : static PageSyncResult
      82                 : PageSync(MediaResource* aResource,
      83                 :          ogg_sync_state* aState,
      84                 :          bool aCachedDataOnly,
      85                 :          PRInt64 aOffset,
      86                 :          PRInt64 aEndOffset,
      87                 :          ogg_page* aPage,
      88                 :          int& aSkippedBytes);
      89                 : 
      90                 : // Chunk size to read when reading Ogg files. Average Ogg page length
      91                 : // is about 4300 bytes, so we read the file in chunks larger than that.
      92                 : static const int PAGE_STEP = 8192;
      93                 : 
      94                 : class nsAutoReleasePacket {
      95                 : public:
      96               0 :   nsAutoReleasePacket(ogg_packet* aPacket) : mPacket(aPacket) { }
      97               0 :   ~nsAutoReleasePacket() {
      98               0 :     nsOggCodecState::ReleasePacket(mPacket);
      99               0 :   }
     100                 : private:
     101                 :   ogg_packet* mPacket;
     102                 : };
     103                 : 
     104               0 : nsOggReader::nsOggReader(nsBuiltinDecoder* aDecoder)
     105                 :   : nsBuiltinDecoderReader(aDecoder),
     106                 :     mTheoraState(nsnull),
     107                 :     mVorbisState(nsnull),
     108                 :     mSkeletonState(nsnull),
     109                 :     mVorbisSerial(0),
     110                 :     mTheoraSerial(0),
     111               0 :     mPageOffset(0)
     112                 : {
     113               0 :   MOZ_COUNT_CTOR(nsOggReader);
     114               0 :   memset(&mTheoraInfo, 0, sizeof(mTheoraInfo));
     115               0 : }
     116                 : 
     117               0 : nsOggReader::~nsOggReader()
     118                 : {
     119               0 :   ogg_sync_clear(&mOggState);
     120               0 :   MOZ_COUNT_DTOR(nsOggReader);
     121               0 : }
     122                 : 
     123               0 : nsresult nsOggReader::Init(nsBuiltinDecoderReader* aCloneDonor) {
     124               0 :   bool init = mCodecStates.Init();
     125               0 :   NS_ASSERTION(init, "Failed to initialize mCodecStates");
     126               0 :   if (!init) {
     127               0 :     return NS_ERROR_FAILURE;
     128                 :   }
     129               0 :   int ret = ogg_sync_init(&mOggState);
     130               0 :   NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE);
     131               0 :   return NS_OK;
     132                 : }
     133                 : 
     134               0 : nsresult nsOggReader::ResetDecode()
     135                 : {
     136               0 :   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
     137               0 :   nsresult res = NS_OK;
     138                 : 
     139               0 :   if (NS_FAILED(nsBuiltinDecoderReader::ResetDecode())) {
     140               0 :     res = NS_ERROR_FAILURE;
     141                 :   }
     142                 : 
     143                 :   // Discard any previously buffered packets/pages.
     144               0 :   ogg_sync_reset(&mOggState);
     145               0 :   if (mVorbisState && NS_FAILED(mVorbisState->Reset())) {
     146               0 :     res = NS_ERROR_FAILURE;
     147                 :   }
     148               0 :   if (mTheoraState && NS_FAILED(mTheoraState->Reset())) {
     149               0 :     res = NS_ERROR_FAILURE;
     150                 :   }
     151                 : 
     152               0 :   return res;
     153                 : }
     154                 : 
     155               0 : bool nsOggReader::ReadHeaders(nsOggCodecState* aState)
     156                 : {
     157               0 :   while (!aState->DoneReadingHeaders()) {
     158               0 :     ogg_packet* packet = NextOggPacket(aState);
     159               0 :     nsAutoReleasePacket autoRelease(packet);
     160               0 :     if (!packet || !aState->IsHeader(packet)) {
     161               0 :       aState->Deactivate();
     162                 :     } else {
     163               0 :       aState->DecodeHeader(packet);
     164                 :     }
     165                 :   }
     166               0 :   return aState->Init();
     167                 : }
     168                 : 
     169               0 : nsresult nsOggReader::ReadMetadata(nsVideoInfo* aInfo)
     170                 : {
     171               0 :   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
     172                 : 
     173                 :   // We read packets until all bitstreams have read all their header packets.
     174                 :   // We record the offset of the first non-header page so that we know
     175                 :   // what page to seek to when seeking to the media start.
     176                 : 
     177                 :   ogg_page page;
     178               0 :   nsAutoTArray<nsOggCodecState*,4> bitstreams;
     179               0 :   bool readAllBOS = false;
     180               0 :   while (!readAllBOS) {
     181               0 :     PRInt64 pageOffset = ReadOggPage(&page);
     182               0 :     if (pageOffset == -1) {
     183                 :       // Some kind of error...
     184               0 :       break;
     185                 :     }
     186                 : 
     187               0 :     int serial = ogg_page_serialno(&page);
     188               0 :     nsOggCodecState* codecState = 0;
     189                 : 
     190               0 :     if (!ogg_page_bos(&page)) {
     191                 :       // We've encountered a non Beginning Of Stream page. No more BOS pages
     192                 :       // can follow in this Ogg segment, so there will be no other bitstreams
     193                 :       // in the Ogg (unless it's invalid).
     194               0 :       readAllBOS = true;
     195               0 :     } else if (!mCodecStates.Get(serial, nsnull)) {
     196                 :       // We've not encountered a stream with this serial number before. Create
     197                 :       // an nsOggCodecState to demux it, and map that to the nsOggCodecState
     198                 :       // in mCodecStates.
     199               0 :       codecState = nsOggCodecState::Create(&page);
     200               0 :       DebugOnly<bool> r = mCodecStates.Put(serial, codecState);
     201               0 :       NS_ASSERTION(r, "Failed to insert into mCodecStates");
     202               0 :       bitstreams.AppendElement(codecState);
     203               0 :       mKnownStreams.AppendElement(serial);
     204               0 :       if (codecState &&
     205               0 :           codecState->GetType() == nsOggCodecState::TYPE_VORBIS &&
     206               0 :           !mVorbisState)
     207                 :       {
     208                 :         // First Vorbis bitstream, we'll play this one. Subsequent Vorbis
     209                 :         // bitstreams will be ignored.
     210               0 :         mVorbisState = static_cast<nsVorbisState*>(codecState);
     211                 :       }
     212               0 :       if (codecState &&
     213               0 :           codecState->GetType() == nsOggCodecState::TYPE_THEORA &&
     214               0 :           !mTheoraState)
     215                 :       {
     216                 :         // First Theora bitstream, we'll play this one. Subsequent Theora
     217                 :         // bitstreams will be ignored.
     218               0 :         mTheoraState = static_cast<nsTheoraState*>(codecState);
     219                 :       }
     220               0 :       if (codecState &&
     221               0 :           codecState->GetType() == nsOggCodecState::TYPE_SKELETON &&
     222               0 :           !mSkeletonState)
     223                 :       {
     224               0 :         mSkeletonState = static_cast<nsSkeletonState*>(codecState);
     225                 :       }
     226                 :     }
     227                 : 
     228               0 :     mCodecStates.Get(serial, &codecState);
     229               0 :     NS_ENSURE_TRUE(codecState, NS_ERROR_FAILURE);
     230                 : 
     231               0 :     if (NS_FAILED(codecState->PageIn(&page))) {
     232               0 :       return NS_ERROR_FAILURE;
     233                 :     }
     234                 :   }
     235                 : 
     236                 :   // We've read all BOS pages, so we know the streams contained in the media.
     237                 :   // Now process all available header packets in the active Theora, Vorbis and
     238                 :   // Skeleton streams.
     239                 : 
     240                 :   // Deactivate any non-primary bitstreams.
     241               0 :   for (PRUint32 i = 0; i < bitstreams.Length(); i++) {
     242               0 :     nsOggCodecState* s = bitstreams[i];
     243               0 :     if (s != mVorbisState && s != mTheoraState && s != mSkeletonState) {
     244               0 :       s->Deactivate();
     245                 :     }
     246                 :   }
     247                 : 
     248               0 :   if (mTheoraState && ReadHeaders(mTheoraState)) {
     249                 :     nsIntRect picture = nsIntRect(mTheoraState->mInfo.pic_x,
     250                 :                                   mTheoraState->mInfo.pic_y,
     251                 :                                   mTheoraState->mInfo.pic_width,
     252               0 :                                   mTheoraState->mInfo.pic_height);
     253                 : 
     254                 :     nsIntSize displaySize = nsIntSize(mTheoraState->mInfo.pic_width,
     255               0 :                                       mTheoraState->mInfo.pic_height);
     256                 : 
     257                 :     // Apply the aspect ratio to produce the intrinsic display size we report
     258                 :     // to the element.
     259               0 :     ScaleDisplayByAspectRatio(displaySize, mTheoraState->mPixelAspectRatio);
     260                 : 
     261                 :     nsIntSize frameSize(mTheoraState->mInfo.frame_width,
     262               0 :                         mTheoraState->mInfo.frame_height);
     263               0 :     if (nsVideoInfo::ValidateVideoRegion(frameSize, picture, displaySize)) {
     264                 :       // Video track's frame sizes will not overflow. Activate the video track.
     265               0 :       mInfo.mHasVideo = true;
     266               0 :       mInfo.mDisplay = displaySize;
     267               0 :       mPicture = picture;
     268                 : 
     269               0 :       VideoFrameContainer* container = mDecoder->GetVideoFrameContainer();
     270               0 :       if (container) {
     271                 :         container->SetCurrentFrame(gfxIntSize(displaySize.width, displaySize.height),
     272                 :                                    nsnull,
     273               0 :                                    TimeStamp::Now());
     274                 :       }
     275                 : 
     276                 :       // Copy Theora info data for time computations on other threads.
     277               0 :       memcpy(&mTheoraInfo, &mTheoraState->mInfo, sizeof(mTheoraInfo));
     278               0 :       mTheoraSerial = mTheoraState->mSerial;
     279                 :     }
     280                 :   }
     281                 : 
     282               0 :   if (mVorbisState && ReadHeaders(mVorbisState)) {
     283               0 :     mInfo.mHasAudio = true;
     284               0 :     mInfo.mAudioRate = mVorbisState->mInfo.rate;
     285               0 :     mInfo.mAudioChannels = mVorbisState->mInfo.channels;
     286                 :     // Copy Vorbis info data for time computations on other threads.
     287               0 :     memcpy(&mVorbisInfo, &mVorbisState->mInfo, sizeof(mVorbisInfo));
     288               0 :     mVorbisInfo.codec_setup = NULL;
     289               0 :     mVorbisSerial = mVorbisState->mSerial;
     290                 :   } else {
     291               0 :     memset(&mVorbisInfo, 0, sizeof(mVorbisInfo));
     292                 :   }
     293                 : 
     294               0 :   if (mSkeletonState) {
     295               0 :     if (!HasAudio() && !HasVideo()) {
     296                 :       // We have a skeleton track, but no audio or video, may as well disable
     297                 :       // the skeleton, we can't do anything useful with this media.
     298               0 :       mSkeletonState->Deactivate();
     299               0 :     } else if (ReadHeaders(mSkeletonState) && mSkeletonState->HasIndex()) {
     300                 :       // Extract the duration info out of the index, so we don't need to seek to
     301                 :       // the end of resource to get it.
     302               0 :       nsAutoTArray<PRUint32, 2> tracks;
     303               0 :       if (HasVideo()) {
     304               0 :         tracks.AppendElement(mTheoraState->mSerial);
     305                 :       }
     306               0 :       if (HasAudio()) {
     307               0 :         tracks.AppendElement(mVorbisState->mSerial);
     308                 :       }
     309               0 :       PRInt64 duration = 0;
     310               0 :       if (NS_SUCCEEDED(mSkeletonState->GetDuration(tracks, duration))) {
     311               0 :         ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     312               0 :         mDecoder->GetStateMachine()->SetDuration(duration);
     313               0 :         LOG(PR_LOG_DEBUG, ("Got duration from Skeleton index %lld", duration));
     314                 :       }
     315                 :     }
     316                 :   }
     317                 : 
     318                 :   {
     319               0 :     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     320                 : 
     321               0 :     MediaResource* resource = mDecoder->GetResource();
     322               0 :     if (mDecoder->GetStateMachine()->GetDuration() == -1 &&
     323               0 :         mDecoder->GetStateMachine()->GetState() != nsDecoderStateMachine::DECODER_STATE_SHUTDOWN &&
     324               0 :         resource->GetLength() >= 0 &&
     325               0 :         mDecoder->GetStateMachine()->IsSeekable())
     326                 :     {
     327                 :       // We didn't get a duration from the index or a Content-Duration header.
     328                 :       // Seek to the end of file to find the end time.
     329               0 :       PRInt64 length = resource->GetLength();
     330               0 :       NS_ASSERTION(length > 0, "Must have a content length to get end time");
     331                 : 
     332               0 :       PRInt64 endTime = 0;
     333                 :       {
     334               0 :         ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
     335               0 :         endTime = RangeEndTime(length);
     336                 :       }
     337               0 :       if (endTime != -1) {
     338               0 :         mDecoder->GetStateMachine()->SetEndTime(endTime);
     339               0 :         LOG(PR_LOG_DEBUG, ("Got Ogg duration from seeking to end %lld", endTime));
     340                 :       }
     341                 :     }
     342                 :   }
     343               0 :   *aInfo = mInfo;
     344                 : 
     345               0 :   return NS_OK;
     346                 : }
     347                 : 
     348               0 : nsresult nsOggReader::DecodeVorbis(ogg_packet* aPacket) {
     349               0 :   NS_ASSERTION(aPacket->granulepos != -1, "Must know vorbis granulepos!");
     350                 : 
     351               0 :   if (vorbis_synthesis(&mVorbisState->mBlock, aPacket) != 0) {
     352               0 :     return NS_ERROR_FAILURE;
     353                 :   }
     354               0 :   if (vorbis_synthesis_blockin(&mVorbisState->mDsp,
     355               0 :                                &mVorbisState->mBlock) != 0)
     356                 :   {
     357               0 :     return NS_ERROR_FAILURE;
     358                 :   }
     359                 : 
     360               0 :   VorbisPCMValue** pcm = 0;
     361               0 :   PRInt32 frames = 0;
     362               0 :   PRUint32 channels = mVorbisState->mInfo.channels;
     363               0 :   ogg_int64_t endFrame = aPacket->granulepos;
     364               0 :   while ((frames = vorbis_synthesis_pcmout(&mVorbisState->mDsp, &pcm)) > 0) {
     365               0 :     mVorbisState->ValidateVorbisPacketSamples(aPacket, frames);
     366               0 :     nsAutoArrayPtr<AudioDataValue> buffer(new AudioDataValue[frames * channels]);
     367               0 :     for (PRUint32 j = 0; j < channels; ++j) {
     368               0 :       VorbisPCMValue* channel = pcm[j];
     369               0 :       for (PRUint32 i = 0; i < PRUint32(frames); ++i) {
     370               0 :         buffer[i*channels + j] = MOZ_CONVERT_VORBIS_SAMPLE(channel[i]);
     371                 :       }
     372                 :     }
     373                 : 
     374               0 :     PRInt64 duration = mVorbisState->Time((PRInt64)frames);
     375               0 :     PRInt64 startTime = mVorbisState->Time(endFrame - frames);
     376                 :     mAudioQueue.Push(new AudioData(mPageOffset,
     377                 :                                    startTime,
     378                 :                                    duration,
     379                 :                                    frames,
     380                 :                                    buffer.forget(),
     381               0 :                                    channels));
     382               0 :     endFrame -= frames;
     383               0 :     if (vorbis_synthesis_read(&mVorbisState->mDsp, frames) != 0) {
     384               0 :       return NS_ERROR_FAILURE;
     385                 :     }
     386                 :   }
     387               0 :   return NS_OK;
     388                 : }
     389                 : 
     390               0 : bool nsOggReader::DecodeAudioData()
     391                 : {
     392               0 :   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
     393               0 :   NS_ASSERTION(mVorbisState!=0, "Need Vorbis state to decode audio");
     394                 : 
     395                 :   // Read the next data packet. Skip any non-data packets we encounter.
     396               0 :   ogg_packet* packet = 0;
     397               0 :   do {
     398               0 :     if (packet) {
     399               0 :       nsOggCodecState::ReleasePacket(packet);
     400                 :     }
     401               0 :     packet = NextOggPacket(mVorbisState);
     402               0 :   } while (packet && mVorbisState->IsHeader(packet));
     403               0 :   if (!packet) {
     404               0 :     mAudioQueue.Finish();
     405               0 :     return false;
     406                 :   }
     407                 : 
     408               0 :   NS_ASSERTION(packet && packet->granulepos != -1,
     409                 :     "Must have packet with known granulepos");
     410               0 :   nsAutoReleasePacket autoRelease(packet);
     411               0 :   DecodeVorbis(packet);
     412               0 :   if (packet->e_o_s) {
     413                 :     // We've encountered an end of bitstream packet, or we've hit the end of
     414                 :     // file while trying to decode, so inform the audio queue that there'll
     415                 :     // be no more samples.
     416               0 :     mAudioQueue.Finish();
     417               0 :     return false;
     418                 :   }
     419                 : 
     420               0 :   return true;
     421                 : }
     422                 : 
     423               0 : nsresult nsOggReader::DecodeTheora(ogg_packet* aPacket, PRInt64 aTimeThreshold)
     424                 : {
     425               0 :   NS_ASSERTION(aPacket->granulepos >= TheoraVersion(&mTheoraState->mInfo,3,2,1),
     426                 :     "Packets must have valid granulepos and packetno");
     427                 : 
     428               0 :   int ret = th_decode_packetin(mTheoraState->mCtx, aPacket, 0);
     429               0 :   if (ret != 0 && ret != TH_DUPFRAME) {
     430               0 :     return NS_ERROR_FAILURE;
     431                 :   }
     432               0 :   PRInt64 time = mTheoraState->StartTime(aPacket->granulepos);
     433                 : 
     434                 :   // Don't use the frame if it's outside the bounds of the presentation
     435                 :   // start time in the skeleton track. Note we still must submit the frame
     436                 :   // to the decoder (via th_decode_packetin), as the frames which are
     437                 :   // presentable may depend on this frame's data.
     438               0 :   if (mSkeletonState && !mSkeletonState->IsPresentable(time)) {
     439               0 :     return NS_OK;
     440                 :   }
     441                 : 
     442               0 :   PRInt64 endTime = mTheoraState->Time(aPacket->granulepos);
     443               0 :   if (endTime < aTimeThreshold) {
     444                 :     // The end time of this frame is already before the current playback
     445                 :     // position. It will never be displayed, don't bother enqueing it.
     446               0 :     return NS_OK;
     447                 :   }
     448                 : 
     449               0 :   if (ret == TH_DUPFRAME) {
     450                 :     VideoData* v = VideoData::CreateDuplicate(mPageOffset,
     451                 :                                               time,
     452                 :                                               endTime,
     453               0 :                                               aPacket->granulepos);
     454               0 :     mVideoQueue.Push(v);
     455               0 :   } else if (ret == 0) {
     456                 :     th_ycbcr_buffer buffer;
     457               0 :     ret = th_decode_ycbcr_out(mTheoraState->mCtx, buffer);
     458               0 :     NS_ASSERTION(ret == 0, "th_decode_ycbcr_out failed");
     459               0 :     bool isKeyframe = th_packet_iskeyframe(aPacket) == 1;
     460                 :     VideoData::YCbCrBuffer b;
     461               0 :     for (PRUint32 i=0; i < 3; ++i) {
     462               0 :       b.mPlanes[i].mData = buffer[i].data;
     463               0 :       b.mPlanes[i].mHeight = buffer[i].height;
     464               0 :       b.mPlanes[i].mWidth = buffer[i].width;
     465               0 :       b.mPlanes[i].mStride = buffer[i].stride;
     466                 :     }
     467                 : 
     468                 :     VideoData *v = VideoData::Create(mInfo,
     469                 :                                      mDecoder->GetImageContainer(),
     470                 :                                      mPageOffset,
     471                 :                                      time,
     472                 :                                      endTime,
     473                 :                                      b,
     474                 :                                      isKeyframe,
     475                 :                                      aPacket->granulepos,
     476               0 :                                      mPicture);
     477               0 :     if (!v) {
     478                 :       // There may be other reasons for this error, but for
     479                 :       // simplicity just assume the worst case: out of memory.
     480               0 :       NS_WARNING("Failed to allocate memory for video frame");
     481               0 :       return NS_ERROR_OUT_OF_MEMORY;
     482                 :     }
     483               0 :     mVideoQueue.Push(v);
     484                 :   }
     485               0 :   return NS_OK;
     486                 : }
     487                 : 
     488               0 : bool nsOggReader::DecodeVideoFrame(bool &aKeyframeSkip,
     489                 :                                      PRInt64 aTimeThreshold)
     490                 : {
     491               0 :   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
     492                 : 
     493                 :   // Record number of frames decoded and parsed. Automatically update the
     494                 :   // stats counters using the AutoNotifyDecoded stack-based class.
     495               0 :   PRUint32 parsed = 0, decoded = 0;
     496               0 :   nsMediaDecoder::AutoNotifyDecoded autoNotify(mDecoder, parsed, decoded);
     497                 : 
     498                 :   // Read the next data packet. Skip any non-data packets we encounter.
     499               0 :   ogg_packet* packet = 0;
     500               0 :   do {
     501               0 :     if (packet) {
     502               0 :       nsOggCodecState::ReleasePacket(packet);
     503                 :     }
     504               0 :     packet = NextOggPacket(mTheoraState);
     505               0 :   } while (packet && mTheoraState->IsHeader(packet));
     506               0 :   if (!packet) {
     507               0 :     mVideoQueue.Finish();
     508               0 :     return false;
     509                 :   }
     510               0 :   nsAutoReleasePacket autoRelease(packet);
     511                 : 
     512               0 :   parsed++;
     513               0 :   NS_ASSERTION(packet && packet->granulepos != -1,
     514                 :                 "Must know first packet's granulepos");
     515               0 :   bool eos = packet->e_o_s;
     516               0 :   PRInt64 frameEndTime = mTheoraState->Time(packet->granulepos);
     517               0 :   if (!aKeyframeSkip ||
     518               0 :      (th_packet_iskeyframe(packet) && frameEndTime >= aTimeThreshold))
     519                 :   {
     520               0 :     aKeyframeSkip = false;
     521               0 :     nsresult res = DecodeTheora(packet, aTimeThreshold);
     522               0 :     decoded++;
     523               0 :     if (NS_FAILED(res)) {
     524               0 :       return false;
     525                 :     }
     526                 :   }
     527                 : 
     528               0 :   if (eos) {
     529                 :     // We've encountered an end of bitstream packet. Inform the queue that
     530                 :     // there will be no more frames.
     531               0 :     mVideoQueue.Finish();
     532               0 :     return false;
     533                 :   }
     534                 : 
     535               0 :   return true;
     536                 : }
     537                 : 
     538               0 : PRInt64 nsOggReader::ReadOggPage(ogg_page* aPage)
     539                 : {
     540               0 :   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
     541                 : 
     542               0 :   int ret = 0;
     543               0 :   while((ret = ogg_sync_pageseek(&mOggState, aPage)) <= 0) {
     544               0 :     if (ret < 0) {
     545                 :       // Lost page sync, have to skip up to next page.
     546               0 :       mPageOffset += -ret;
     547               0 :       continue;
     548                 :     }
     549                 :     // Returns a buffer that can be written too
     550                 :     // with the given size. This buffer is stored
     551                 :     // in the ogg synchronisation structure.
     552               0 :     char* buffer = ogg_sync_buffer(&mOggState, 4096);
     553               0 :     NS_ASSERTION(buffer, "ogg_sync_buffer failed");
     554                 : 
     555                 :     // Read from the resource into the buffer
     556               0 :     PRUint32 bytesRead = 0;
     557                 : 
     558               0 :     nsresult rv = mDecoder->GetResource()->Read(buffer, 4096, &bytesRead);
     559               0 :     if (NS_FAILED(rv) || (bytesRead == 0 && ret == 0)) {
     560                 :       // End of file.
     561               0 :       return -1;
     562                 :     }
     563                 : 
     564               0 :     mDecoder->NotifyBytesConsumed(bytesRead);
     565                 :     // Update the synchronisation layer with the number
     566                 :     // of bytes written to the buffer
     567               0 :     ret = ogg_sync_wrote(&mOggState, bytesRead);
     568               0 :     NS_ENSURE_TRUE(ret == 0, -1);    
     569                 :   }
     570               0 :   PRInt64 offset = mPageOffset;
     571               0 :   mPageOffset += aPage->header_len + aPage->body_len;
     572                 :   
     573               0 :   return offset;
     574                 : }
     575                 : 
     576               0 : ogg_packet* nsOggReader::NextOggPacket(nsOggCodecState* aCodecState)
     577                 : {
     578               0 :   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
     579                 : 
     580               0 :   if (!aCodecState || !aCodecState->mActive) {
     581               0 :     return nsnull;
     582                 :   }
     583                 : 
     584                 :   ogg_packet* packet;
     585               0 :   while ((packet = aCodecState->PacketOut()) == nsnull) {
     586                 :     // The codec state does not have any buffered pages, so try to read another
     587                 :     // page from the channel.
     588                 :     ogg_page page;
     589               0 :     if (ReadOggPage(&page) == -1) {
     590               0 :       return nsnull;
     591                 :     }
     592                 : 
     593               0 :     PRUint32 serial = ogg_page_serialno(&page);
     594               0 :     nsOggCodecState* codecState = nsnull;
     595               0 :     mCodecStates.Get(serial, &codecState);
     596               0 :     if (codecState && NS_FAILED(codecState->PageIn(&page))) {
     597               0 :       return nsnull;
     598                 :     }
     599                 :   }
     600                 : 
     601               0 :   return packet;
     602                 : }
     603                 : 
     604                 : // Returns an ogg page's checksum.
     605                 : static ogg_uint32_t
     606               0 : GetChecksum(ogg_page* page)
     607                 : {
     608               0 :   if (page == 0 || page->header == 0 || page->header_len < 25) {
     609               0 :     return 0;
     610                 :   }
     611               0 :   const unsigned char* p = page->header + 22;
     612               0 :   PRUint32 c =  p[0] +
     613               0 :                (p[1] << 8) + 
     614               0 :                (p[2] << 16) +
     615               0 :                (p[3] << 24);
     616               0 :   return c;
     617                 : }
     618                 : 
     619               0 : PRInt64 nsOggReader::RangeStartTime(PRInt64 aOffset)
     620                 : {
     621               0 :   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
     622               0 :   MediaResource* resource = mDecoder->GetResource();
     623               0 :   NS_ENSURE_TRUE(resource != nsnull, nsnull);
     624               0 :   nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
     625               0 :   NS_ENSURE_SUCCESS(res, nsnull);
     626               0 :   PRInt64 startTime = 0;
     627               0 :   nsBuiltinDecoderReader::FindStartTime(startTime);
     628               0 :   return startTime;
     629                 : }
     630                 : 
     631                 : struct nsAutoOggSyncState {
     632               0 :   nsAutoOggSyncState() {
     633               0 :     ogg_sync_init(&mState);
     634               0 :   }
     635               0 :   ~nsAutoOggSyncState() {
     636               0 :     ogg_sync_clear(&mState);
     637               0 :   }
     638                 :   ogg_sync_state mState;
     639                 : };
     640                 : 
     641               0 : PRInt64 nsOggReader::RangeEndTime(PRInt64 aEndOffset)
     642                 : {
     643               0 :   NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
     644                 :                "Should be on state machine or decode thread.");
     645                 : 
     646               0 :   MediaResource* resource = mDecoder->GetResource();
     647               0 :   NS_ENSURE_TRUE(resource != nsnull, -1);
     648               0 :   PRInt64 position = resource->Tell();
     649               0 :   PRInt64 endTime = RangeEndTime(0, aEndOffset, false);
     650               0 :   nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET, position);
     651               0 :   NS_ENSURE_SUCCESS(res, -1);
     652               0 :   return endTime;
     653                 : }
     654                 : 
     655               0 : PRInt64 nsOggReader::RangeEndTime(PRInt64 aStartOffset,
     656                 :                                   PRInt64 aEndOffset,
     657                 :                                   bool aCachedDataOnly)
     658                 : {
     659               0 :   MediaResource* resource = mDecoder->GetResource();
     660               0 :   nsAutoOggSyncState sync;
     661                 : 
     662                 :   // We need to find the last page which ends before aEndOffset that
     663                 :   // has a granulepos that we can convert to a timestamp. We do this by
     664                 :   // backing off from aEndOffset until we encounter a page on which we can
     665                 :   // interpret the granulepos. If while backing off we encounter a page which
     666                 :   // we've previously encountered before, we'll either backoff again if we
     667                 :   // haven't found an end time yet, or return the last end time found.
     668               0 :   const int step = 5000;
     669               0 :   PRInt64 readStartOffset = aEndOffset;
     670               0 :   PRInt64 readHead = aEndOffset;
     671               0 :   PRInt64 endTime = -1;
     672               0 :   PRUint32 checksumAfterSeek = 0;
     673               0 :   PRUint32 prevChecksumAfterSeek = 0;
     674               0 :   bool mustBackOff = false;
     675               0 :   while (true) {
     676                 :     ogg_page page;    
     677               0 :     int ret = ogg_sync_pageseek(&sync.mState, &page);
     678               0 :     if (ret == 0) {
     679                 :       // We need more data if we've not encountered a page we've seen before,
     680                 :       // or we've read to the end of file.
     681               0 :       if (mustBackOff || readHead == aEndOffset || readHead == aStartOffset) {
     682               0 :         if (endTime != -1 || readStartOffset == 0) {
     683                 :           // We have encountered a page before, or we're at the end of file.
     684               0 :           break;
     685                 :         }
     686               0 :         mustBackOff = false;
     687               0 :         prevChecksumAfterSeek = checksumAfterSeek;
     688               0 :         checksumAfterSeek = 0;
     689               0 :         ogg_sync_reset(&sync.mState);
     690               0 :         readStartOffset = NS_MAX(static_cast<PRInt64>(0), readStartOffset - step);
     691               0 :         readHead = NS_MAX(aStartOffset, readStartOffset);
     692                 :       }
     693                 : 
     694                 :       PRInt64 limit = NS_MIN(static_cast<PRInt64>(PR_UINT32_MAX),
     695               0 :                              aEndOffset - readHead);
     696               0 :       limit = NS_MAX(static_cast<PRInt64>(0), limit);
     697               0 :       limit = NS_MIN(limit, static_cast<PRInt64>(step));
     698               0 :       PRUint32 bytesToRead = static_cast<PRUint32>(limit);
     699               0 :       PRUint32 bytesRead = 0;
     700               0 :       char* buffer = ogg_sync_buffer(&sync.mState, bytesToRead);
     701               0 :       NS_ASSERTION(buffer, "Must have buffer");
     702                 :       nsresult res;
     703               0 :       if (aCachedDataOnly) {
     704               0 :         res = resource->ReadFromCache(buffer, readHead, bytesToRead);
     705               0 :         NS_ENSURE_SUCCESS(res, -1);
     706               0 :         bytesRead = bytesToRead;
     707                 :       } else {
     708               0 :         NS_ASSERTION(readHead < aEndOffset,
     709                 :                      "resource pos must be before range end");
     710               0 :         res = resource->Seek(nsISeekableStream::NS_SEEK_SET, readHead);
     711               0 :         NS_ENSURE_SUCCESS(res, -1);
     712               0 :         res = resource->Read(buffer, bytesToRead, &bytesRead);
     713               0 :         NS_ENSURE_SUCCESS(res, -1);
     714                 :       }
     715               0 :       readHead += bytesRead;
     716                 : 
     717                 :       // Update the synchronisation layer with the number
     718                 :       // of bytes written to the buffer
     719               0 :       ret = ogg_sync_wrote(&sync.mState, bytesRead);
     720               0 :       if (ret != 0) {
     721               0 :         endTime = -1;
     722               0 :         break;
     723                 :       }
     724                 : 
     725               0 :       continue;
     726                 :     }
     727                 : 
     728               0 :     if (ret < 0 || ogg_page_granulepos(&page) < 0) {
     729               0 :       continue;
     730                 :     }
     731                 : 
     732               0 :     PRUint32 checksum = GetChecksum(&page);
     733               0 :     if (checksumAfterSeek == 0) {
     734                 :       // This is the first page we've decoded after a backoff/seek. Remember
     735                 :       // the page checksum. If we backoff further and encounter this page
     736                 :       // again, we'll know that we won't find a page with an end time after
     737                 :       // this one, so we'll know to back off again.
     738               0 :       checksumAfterSeek = checksum;
     739                 :     }
     740               0 :     if (checksum == prevChecksumAfterSeek) {
     741                 :       // This page has the same checksum as the first page we encountered
     742                 :       // after the last backoff/seek. Since we've already scanned after this
     743                 :       // page and failed to find an end time, we may as well backoff again and
     744                 :       // try to find an end time from an earlier page.
     745               0 :       mustBackOff = true;
     746               0 :       continue;
     747                 :     }
     748                 : 
     749               0 :     PRInt64 granulepos = ogg_page_granulepos(&page);
     750               0 :     int serial = ogg_page_serialno(&page);
     751                 : 
     752               0 :     nsOggCodecState* codecState = nsnull;
     753               0 :     mCodecStates.Get(serial, &codecState);
     754                 : 
     755               0 :     if (!codecState) {
     756                 :       // This page is from a bitstream which we haven't encountered yet.
     757                 :       // It's probably from a new "link" in a "chained" ogg. Don't
     758                 :       // bother even trying to find a duration...
     759               0 :       endTime = -1;
     760               0 :       break;
     761                 :     }
     762                 : 
     763               0 :     PRInt64 t = codecState->Time(granulepos);
     764               0 :     if (t != -1) {
     765               0 :       endTime = t;
     766                 :     }
     767                 :   }
     768                 : 
     769               0 :   return endTime;
     770                 : }
     771                 : 
     772               0 : nsresult nsOggReader::GetSeekRanges(nsTArray<SeekRange>& aRanges)
     773                 : {
     774               0 :   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
     775               0 :   nsTArray<MediaByteRange> cached;
     776               0 :   nsresult res = mDecoder->GetResource()->GetCachedRanges(cached);
     777               0 :   NS_ENSURE_SUCCESS(res, res);
     778                 : 
     779               0 :   for (PRUint32 index = 0; index < cached.Length(); index++) {
     780               0 :     MediaByteRange& range = cached[index];
     781               0 :     PRInt64 startTime = -1;
     782               0 :     PRInt64 endTime = -1;
     783               0 :     if (NS_FAILED(ResetDecode())) {
     784               0 :       return NS_ERROR_FAILURE;
     785                 :     }
     786               0 :     PRInt64 startOffset = range.mStart;
     787               0 :     PRInt64 endOffset = range.mEnd;
     788               0 :     startTime = RangeStartTime(startOffset);
     789               0 :     if (startTime != -1 &&
     790                 :         ((endTime = RangeEndTime(endOffset)) != -1))
     791                 :     {
     792               0 :       NS_ASSERTION(startTime < endTime,
     793                 :                    "Start time must be before end time");
     794                 :       aRanges.AppendElement(SeekRange(startOffset,
     795                 :                                       endOffset,
     796                 :                                       startTime,
     797               0 :                                       endTime));
     798                 :      }
     799                 :   }
     800               0 :   if (NS_FAILED(ResetDecode())) {
     801               0 :     return NS_ERROR_FAILURE;
     802                 :   }
     803               0 :   return NS_OK;
     804                 : }
     805                 : 
     806                 : nsOggReader::SeekRange
     807               0 : nsOggReader::SelectSeekRange(const nsTArray<SeekRange>& ranges,
     808                 :                              PRInt64 aTarget,
     809                 :                              PRInt64 aStartTime,
     810                 :                              PRInt64 aEndTime,
     811                 :                              bool aExact)
     812                 : {
     813               0 :   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
     814               0 :   PRInt64 so = 0;
     815               0 :   PRInt64 eo = mDecoder->GetResource()->GetLength();
     816               0 :   PRInt64 st = aStartTime;
     817               0 :   PRInt64 et = aEndTime;
     818               0 :   for (PRUint32 i = 0; i < ranges.Length(); i++) {
     819               0 :     const SeekRange &r = ranges[i];
     820               0 :     if (r.mTimeStart < aTarget) {
     821               0 :       so = r.mOffsetStart;
     822               0 :       st = r.mTimeStart;
     823                 :     }
     824               0 :     if (r.mTimeEnd >= aTarget && r.mTimeEnd < et) {
     825               0 :       eo = r.mOffsetEnd;
     826               0 :       et = r.mTimeEnd;
     827                 :     }
     828                 : 
     829               0 :     if (r.mTimeStart < aTarget && aTarget <= r.mTimeEnd) {
     830                 :       // Target lies exactly in this range.
     831               0 :       return ranges[i];
     832                 :     }
     833                 :   }
     834               0 :   if (aExact || eo == -1) {
     835               0 :     return SeekRange();
     836                 :   }
     837               0 :   return SeekRange(so, eo, st, et);
     838                 : }
     839                 : 
     840               0 : nsOggReader::IndexedSeekResult nsOggReader::RollbackIndexedSeek(PRInt64 aOffset)
     841                 : {
     842               0 :   mSkeletonState->Deactivate();
     843               0 :   MediaResource* resource = mDecoder->GetResource();
     844               0 :   NS_ENSURE_TRUE(resource != nsnull, SEEK_FATAL_ERROR);
     845               0 :   nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
     846               0 :   NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR);
     847               0 :   return SEEK_INDEX_FAIL;
     848                 : }
     849                 :  
     850               0 : nsOggReader::IndexedSeekResult nsOggReader::SeekToKeyframeUsingIndex(PRInt64 aTarget)
     851                 : {
     852               0 :   MediaResource* resource = mDecoder->GetResource();
     853               0 :   NS_ENSURE_TRUE(resource != nsnull, SEEK_FATAL_ERROR);
     854               0 :   if (!HasSkeleton() || !mSkeletonState->HasIndex()) {
     855               0 :     return SEEK_INDEX_FAIL;
     856                 :   }
     857                 :   // We have an index from the Skeleton track, try to use it to seek.
     858               0 :   nsAutoTArray<PRUint32, 2> tracks;
     859               0 :   if (HasVideo()) {
     860               0 :     tracks.AppendElement(mTheoraState->mSerial);
     861                 :   }
     862               0 :   if (HasAudio()) {
     863               0 :     tracks.AppendElement(mVorbisState->mSerial);
     864                 :   }
     865               0 :   nsSkeletonState::nsSeekTarget keyframe;
     866               0 :   if (NS_FAILED(mSkeletonState->IndexedSeekTarget(aTarget,
     867                 :                                                   tracks,
     868                 :                                                   keyframe)))
     869                 :   {
     870                 :     // Could not locate a keypoint for the target in the index.
     871               0 :     return SEEK_INDEX_FAIL;
     872                 :   }
     873                 : 
     874                 :   // Remember original resource read cursor position so we can rollback on failure.
     875               0 :   PRInt64 tell = resource->Tell();
     876                 : 
     877                 :   // Seek to the keypoint returned by the index.
     878               0 :   if (keyframe.mKeyPoint.mOffset > resource->GetLength() ||
     879                 :       keyframe.mKeyPoint.mOffset < 0)
     880                 :   {
     881                 :     // Index must be invalid.
     882               0 :     return RollbackIndexedSeek(tell);
     883                 :   }
     884               0 :   LOG(PR_LOG_DEBUG, ("Seeking using index to keyframe at offset %lld\n",
     885                 :                      keyframe.mKeyPoint.mOffset));
     886                 :   nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET,
     887               0 :                               keyframe.mKeyPoint.mOffset);
     888               0 :   NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR);
     889               0 :   mPageOffset = keyframe.mKeyPoint.mOffset;
     890                 : 
     891                 :   // We've moved the read set, so reset decode.
     892               0 :   res = ResetDecode();
     893               0 :   NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR);
     894                 : 
     895                 :   // Check that the page the index thinks is exactly here is actually exactly
     896                 :   // here. If not, the index is invalid.
     897                 :   ogg_page page;
     898               0 :   int skippedBytes = 0;
     899                 :   PageSyncResult syncres = PageSync(resource,
     900                 :                                     &mOggState,
     901                 :                                     false,
     902                 :                                     mPageOffset,
     903               0 :                                     resource->GetLength(),
     904                 :                                     &page,
     905               0 :                                     skippedBytes);
     906               0 :   NS_ENSURE_TRUE(syncres != PAGE_SYNC_ERROR, SEEK_FATAL_ERROR);
     907               0 :   if (syncres != PAGE_SYNC_OK || skippedBytes != 0) {
     908               0 :     LOG(PR_LOG_DEBUG, ("Indexed-seek failure: Ogg Skeleton Index is invalid "
     909                 :                        "or sync error after seek"));
     910               0 :     return RollbackIndexedSeek(tell);
     911                 :   }
     912               0 :   PRUint32 serial = ogg_page_serialno(&page);
     913               0 :   if (serial != keyframe.mSerial) {
     914                 :     // Serialno of page at offset isn't what the index told us to expect.
     915                 :     // Assume the index is invalid.
     916               0 :     return RollbackIndexedSeek(tell);
     917                 :   }
     918               0 :   nsOggCodecState* codecState = nsnull;
     919               0 :   mCodecStates.Get(serial, &codecState);
     920               0 :   if (codecState &&
     921                 :       codecState->mActive &&
     922               0 :       ogg_stream_pagein(&codecState->mState, &page) != 0)
     923                 :   {
     924                 :     // Couldn't insert page into the ogg resource, or somehow the resource
     925                 :     // is no longer active.
     926               0 :     return RollbackIndexedSeek(tell);
     927                 :   }      
     928               0 :   mPageOffset = keyframe.mKeyPoint.mOffset + page.header_len + page.body_len;
     929               0 :   return SEEK_OK;
     930                 : }
     931                 : 
     932               0 : nsresult nsOggReader::SeekInBufferedRange(PRInt64 aTarget,
     933                 :                                           PRInt64 aStartTime,
     934                 :                                           PRInt64 aEndTime,
     935                 :                                           const nsTArray<SeekRange>& aRanges,
     936                 :                                           const SeekRange& aRange)
     937                 : {
     938               0 :   LOG(PR_LOG_DEBUG, ("%p Seeking in buffered data to %lld using bisection search", mDecoder, aTarget));
     939                 : 
     940                 :   // We know the exact byte range in which the target must lie. It must
     941                 :   // be buffered in the media cache. Seek there.
     942               0 :   nsresult res = SeekBisection(aTarget, aRange, 0);
     943               0 :   if (NS_FAILED(res) || !HasVideo()) {
     944               0 :     return res;
     945                 :   }
     946                 : 
     947                 :   // We have an active Theora bitstream. Decode the next Theora frame, and
     948                 :   // extract its keyframe's time.
     949                 :   bool eof;
     950               0 :   do {
     951               0 :     bool skip = false;
     952               0 :     eof = !DecodeVideoFrame(skip, 0);
     953                 :     {
     954               0 :       ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     955               0 :       if (mDecoder->GetDecodeState() == nsBuiltinDecoderStateMachine::DECODER_STATE_SHUTDOWN) {
     956               0 :         return NS_ERROR_FAILURE;
     957                 :       }
     958                 :     }
     959               0 :   } while (!eof &&
     960               0 :            mVideoQueue.GetSize() == 0);
     961                 : 
     962               0 :   VideoData* video = mVideoQueue.PeekFront();
     963               0 :   if (video && !video->mKeyframe) {
     964                 :     // First decoded frame isn't a keyframe, seek back to previous keyframe,
     965                 :     // otherwise we'll get visual artifacts.
     966               0 :     NS_ASSERTION(video->mTimecode != -1, "Must have a granulepos");
     967               0 :     int shift = mTheoraState->mInfo.keyframe_granule_shift;
     968               0 :     PRInt64 keyframeGranulepos = (video->mTimecode >> shift) << shift;
     969               0 :     PRInt64 keyframeTime = mTheoraState->StartTime(keyframeGranulepos);
     970                 :     SEEK_LOG(PR_LOG_DEBUG, ("Keyframe for %lld is at %lld, seeking back to it",
     971                 :                             video->mTime, keyframeTime));
     972                 :     SeekRange k = SelectSeekRange(aRanges,
     973                 :                                   keyframeTime,
     974                 :                                   aStartTime,
     975                 :                                   aEndTime,
     976               0 :                                   false);
     977               0 :     res = SeekBisection(keyframeTime, k, SEEK_FUZZ_USECS);
     978                 :   }
     979               0 :   return res;
     980                 : }
     981                 : 
     982               0 : nsresult nsOggReader::SeekInUnbuffered(PRInt64 aTarget,
     983                 :                                        PRInt64 aStartTime,
     984                 :                                        PRInt64 aEndTime,
     985                 :                                        const nsTArray<SeekRange>& aRanges)
     986                 : {
     987               0 :   LOG(PR_LOG_DEBUG, ("%p Seeking in unbuffered data to %lld using bisection search", mDecoder, aTarget));
     988                 :   
     989                 :   // If we've got an active Theora bitstream, determine the maximum possible
     990                 :   // time in usecs which a keyframe could be before a given interframe. We
     991                 :   // subtract this from our seek target, seek to the new target, and then
     992                 :   // will decode forward to the original seek target. We should encounter a
     993                 :   // keyframe in that interval. This prevents us from needing to run two
     994                 :   // bisections; one for the seek target frame, and another to find its
     995                 :   // keyframe. It's usually faster to just download this extra data, rather
     996                 :   // tham perform two bisections to find the seek target's keyframe. We
     997                 :   // don't do this offsetting when seeking in a buffered range,
     998                 :   // as the extra decoding causes a noticeable speed hit when all the data
     999                 :   // is buffered (compared to just doing a bisection to exactly find the
    1000                 :   // keyframe).
    1001               0 :   PRInt64 keyframeOffsetMs = 0;
    1002               0 :   if (HasVideo() && mTheoraState) {
    1003               0 :     keyframeOffsetMs = mTheoraState->MaxKeyframeOffset();
    1004                 :   }
    1005               0 :   PRInt64 seekTarget = NS_MAX(aStartTime, aTarget - keyframeOffsetMs);
    1006                 :   // Minimize the bisection search space using the known timestamps from the
    1007                 :   // buffered ranges.
    1008               0 :   SeekRange k = SelectSeekRange(aRanges, seekTarget, aStartTime, aEndTime, false);
    1009               0 :   return SeekBisection(seekTarget, k, SEEK_FUZZ_USECS);
    1010                 : }
    1011                 : 
    1012               0 : nsresult nsOggReader::Seek(PRInt64 aTarget,
    1013                 :                            PRInt64 aStartTime,
    1014                 :                            PRInt64 aEndTime,
    1015                 :                            PRInt64 aCurrentTime)
    1016                 : {
    1017               0 :   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
    1018               0 :   LOG(PR_LOG_DEBUG, ("%p About to seek to %lld", mDecoder, aTarget));
    1019                 :   nsresult res;
    1020               0 :   MediaResource* resource = mDecoder->GetResource();
    1021               0 :   NS_ENSURE_TRUE(resource != nsnull, NS_ERROR_FAILURE);
    1022                 : 
    1023               0 :   if (aTarget == aStartTime) {
    1024                 :     // We've seeked to the media start. Just seek to the offset of the first
    1025                 :     // content page.
    1026               0 :     res = resource->Seek(nsISeekableStream::NS_SEEK_SET, 0);
    1027               0 :     NS_ENSURE_SUCCESS(res,res);
    1028                 : 
    1029               0 :     mPageOffset = 0;
    1030               0 :     res = ResetDecode();
    1031               0 :     NS_ENSURE_SUCCESS(res,res);
    1032                 : 
    1033               0 :     NS_ASSERTION(aStartTime != -1, "mStartTime should be known");
    1034                 :     {
    1035               0 :       ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
    1036               0 :       mDecoder->UpdatePlaybackPosition(aStartTime);
    1037                 :     }
    1038                 :   } else {
    1039               0 :     IndexedSeekResult sres = SeekToKeyframeUsingIndex(aTarget);
    1040               0 :     NS_ENSURE_TRUE(sres != SEEK_FATAL_ERROR, NS_ERROR_FAILURE);
    1041               0 :     if (sres == SEEK_INDEX_FAIL) {
    1042                 :       // No index or other non-fatal index-related failure. Try to seek
    1043                 :       // using a bisection search. Determine the already downloaded data
    1044                 :       // in the media cache, so we can try to seek in the cached data first.
    1045               0 :       nsAutoTArray<SeekRange, 16> ranges;
    1046               0 :       res = GetSeekRanges(ranges);
    1047               0 :       NS_ENSURE_SUCCESS(res,res);
    1048                 : 
    1049                 :       // Figure out if the seek target lies in a buffered range.
    1050               0 :       SeekRange r = SelectSeekRange(ranges, aTarget, aStartTime, aEndTime, true);
    1051                 : 
    1052               0 :       if (!r.IsNull()) {
    1053                 :         // We know the buffered range in which the seek target lies, do a
    1054                 :         // bisection search in that buffered range.
    1055               0 :         res = SeekInBufferedRange(aTarget, aStartTime, aEndTime, ranges, r);
    1056               0 :         NS_ENSURE_SUCCESS(res,res);
    1057                 :       } else {
    1058                 :         // The target doesn't lie in a buffered range. Perform a bisection
    1059                 :         // search over the whole media, using the known buffered ranges to
    1060                 :         // reduce the search space.
    1061               0 :         res = SeekInUnbuffered(aTarget, aStartTime, aEndTime, ranges);
    1062               0 :         NS_ENSURE_SUCCESS(res,res);
    1063                 :       }
    1064                 :     }
    1065                 :   }
    1066                 : 
    1067                 :   // The decode position must now be either close to the seek target, or
    1068                 :   // we've seeked to before the keyframe before the seek target. Decode
    1069                 :   // forward to the seek target frame.
    1070               0 :   return DecodeToTarget(aTarget);
    1071                 : }
    1072                 : 
    1073                 : // Reads a page from the media resource.
    1074                 : static PageSyncResult
    1075               0 : PageSync(MediaResource* aResource,
    1076                 :          ogg_sync_state* aState,
    1077                 :          bool aCachedDataOnly,
    1078                 :          PRInt64 aOffset,
    1079                 :          PRInt64 aEndOffset,
    1080                 :          ogg_page* aPage,
    1081                 :          int& aSkippedBytes)
    1082                 : {
    1083               0 :   aSkippedBytes = 0;
    1084                 :   // Sync to the next page.
    1085               0 :   int ret = 0;
    1086               0 :   PRUint32 bytesRead = 0;
    1087               0 :   PRInt64 readHead = aOffset;
    1088               0 :   while (ret <= 0) {
    1089               0 :     ret = ogg_sync_pageseek(aState, aPage);
    1090               0 :     if (ret == 0) {
    1091               0 :       char* buffer = ogg_sync_buffer(aState, PAGE_STEP);
    1092               0 :       NS_ASSERTION(buffer, "Must have a buffer");
    1093                 : 
    1094                 :       // Read from the file into the buffer
    1095                 :       PRInt64 bytesToRead = NS_MIN(static_cast<PRInt64>(PAGE_STEP),
    1096               0 :                                    aEndOffset - readHead);
    1097               0 :       NS_ASSERTION(bytesToRead <= PR_UINT32_MAX, "bytesToRead range check");
    1098               0 :       if (bytesToRead <= 0) {
    1099               0 :         return PAGE_SYNC_END_OF_RANGE;
    1100                 :       }
    1101               0 :       nsresult rv = NS_OK;
    1102               0 :       if (aCachedDataOnly) {
    1103                 :         rv = aResource->ReadFromCache(buffer, readHead,
    1104               0 :                                       static_cast<PRUint32>(bytesToRead));
    1105               0 :         NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR);
    1106               0 :         bytesRead = static_cast<PRUint32>(bytesToRead);
    1107                 :       } else {
    1108               0 :         rv = aResource->Seek(nsISeekableStream::NS_SEEK_SET, readHead);
    1109               0 :         NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR);
    1110                 :         rv = aResource->Read(buffer,
    1111                 :                              static_cast<PRUint32>(bytesToRead),
    1112               0 :                              &bytesRead);
    1113               0 :         NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR);
    1114                 :       }
    1115               0 :       if (bytesRead == 0 && NS_SUCCEEDED(rv)) {
    1116                 :         // End of file.
    1117               0 :         return PAGE_SYNC_END_OF_RANGE;
    1118                 :       }
    1119               0 :       readHead += bytesRead;
    1120                 : 
    1121                 :       // Update the synchronisation layer with the number
    1122                 :       // of bytes written to the buffer
    1123               0 :       ret = ogg_sync_wrote(aState, bytesRead);
    1124               0 :       NS_ENSURE_TRUE(ret == 0, PAGE_SYNC_ERROR);    
    1125               0 :       continue;
    1126                 :     }
    1127                 : 
    1128               0 :     if (ret < 0) {
    1129               0 :       NS_ASSERTION(aSkippedBytes >= 0, "Offset >= 0");
    1130               0 :       aSkippedBytes += -ret;
    1131               0 :       NS_ASSERTION(aSkippedBytes >= 0, "Offset >= 0");
    1132               0 :       continue;
    1133                 :     }
    1134                 :   }
    1135                 :   
    1136               0 :   return PAGE_SYNC_OK;
    1137                 : }
    1138                 : 
    1139               0 : nsresult nsOggReader::SeekBisection(PRInt64 aTarget,
    1140                 :                                     const SeekRange& aRange,
    1141                 :                                     PRUint32 aFuzz)
    1142                 : {
    1143               0 :   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
    1144                 :   nsresult res;
    1145               0 :   MediaResource* resource = mDecoder->GetResource();
    1146                 : 
    1147               0 :   if (aTarget == aRange.mTimeStart) {
    1148               0 :     if (NS_FAILED(ResetDecode())) {
    1149               0 :       return NS_ERROR_FAILURE;
    1150                 :     }
    1151               0 :     res = resource->Seek(nsISeekableStream::NS_SEEK_SET, 0);
    1152               0 :     NS_ENSURE_SUCCESS(res,res);
    1153               0 :     mPageOffset = 0;
    1154               0 :     return NS_OK;
    1155                 :   }
    1156                 : 
    1157                 :   // Bisection search, find start offset of last page with end time less than
    1158                 :   // the seek target.
    1159               0 :   ogg_int64_t startOffset = aRange.mOffsetStart;
    1160               0 :   ogg_int64_t startTime = aRange.mTimeStart;
    1161               0 :   ogg_int64_t startLength = 0; // Length of the page at startOffset.
    1162               0 :   ogg_int64_t endOffset = aRange.mOffsetEnd;
    1163               0 :   ogg_int64_t endTime = aRange.mTimeEnd;
    1164                 : 
    1165               0 :   ogg_int64_t seekTarget = aTarget;
    1166               0 :   PRInt64 seekLowerBound = NS_MAX(static_cast<PRInt64>(0), aTarget - aFuzz);
    1167               0 :   int hops = 0;
    1168               0 :   ogg_int64_t previousGuess = -1;
    1169               0 :   int backsteps = 0;
    1170               0 :   const int maxBackStep = 10;
    1171                 :   NS_ASSERTION(static_cast<PRUint64>(PAGE_STEP) * pow(2.0, maxBackStep) < PR_INT32_MAX,
    1172                 :                "Backstep calculation must not overflow");
    1173                 : 
    1174                 :   // Seek via bisection search. Loop until we find the offset where the page
    1175                 :   // before the offset is before the seek target, and the page after the offset
    1176                 :   // is after the seek target.
    1177               0 :   while (true) {
    1178               0 :     ogg_int64_t duration = 0;
    1179               0 :     double target = 0;
    1180               0 :     ogg_int64_t interval = 0;
    1181               0 :     ogg_int64_t guess = 0;
    1182                 :     ogg_page page;
    1183               0 :     int skippedBytes = 0;
    1184               0 :     ogg_int64_t pageOffset = 0;
    1185               0 :     ogg_int64_t pageLength = 0;
    1186               0 :     ogg_int64_t granuleTime = -1;
    1187               0 :     bool mustBackoff = false;
    1188                 : 
    1189                 :     // Guess where we should bisect to, based on the bit rate and the time
    1190                 :     // remaining in the interval. Loop until we can determine the time at
    1191                 :     // the guess offset.
    1192               0 :     while (true) {
    1193                 :   
    1194                 :       // Discard any previously buffered packets/pages.
    1195               0 :       if (NS_FAILED(ResetDecode())) {
    1196               0 :         return NS_ERROR_FAILURE;
    1197                 :       }
    1198                 : 
    1199               0 :       interval = endOffset - startOffset - startLength;
    1200               0 :       if (interval == 0) {
    1201                 :         // Our interval is empty, we've found the optimal seek point, as the
    1202                 :         // page at the start offset is before the seek target, and the page
    1203                 :         // at the end offset is after the seek target.
    1204                 :         SEEK_LOG(PR_LOG_DEBUG, ("Interval narrowed, terminating bisection."));
    1205               0 :         break;
    1206                 :       }
    1207                 : 
    1208                 :       // Guess bisection point.
    1209               0 :       duration = endTime - startTime;
    1210               0 :       target = (double)(seekTarget - startTime) / (double)duration;
    1211                 :       guess = startOffset + startLength +
    1212               0 :               static_cast<ogg_int64_t>((double)interval * target);
    1213               0 :       guess = NS_MIN(guess, endOffset - PAGE_STEP);
    1214               0 :       if (mustBackoff) {
    1215                 :         // We previously failed to determine the time at the guess offset,
    1216                 :         // probably because we ran out of data to decode. This usually happens
    1217                 :         // when we guess very close to the end offset. So reduce the guess
    1218                 :         // offset using an exponential backoff until we determine the time.
    1219                 :         SEEK_LOG(PR_LOG_DEBUG, ("Backing off %d bytes, backsteps=%d",
    1220                 :           static_cast<PRInt32>(PAGE_STEP * pow(2.0, backsteps)), backsteps));
    1221               0 :         guess -= PAGE_STEP * static_cast<ogg_int64_t>(pow(2.0, backsteps));
    1222                 : 
    1223               0 :         if (guess <= startOffset) {
    1224                 :           // We've tried to backoff to before the start offset of our seek
    1225                 :           // range. This means we couldn't find a seek termination position
    1226                 :           // near the end of the seek range, so just set the seek termination
    1227                 :           // condition, and break out of the bisection loop. We'll begin
    1228                 :           // decoding from the start of the seek range.
    1229               0 :           interval = 0;
    1230               0 :           break;
    1231                 :         }
    1232                 : 
    1233               0 :         backsteps = NS_MIN(backsteps + 1, maxBackStep);
    1234                 :         // We reset mustBackoff. If we still need to backoff further, it will
    1235                 :         // be set to true again.
    1236               0 :         mustBackoff = false;
    1237                 :       } else {
    1238               0 :         backsteps = 0;
    1239                 :       }
    1240               0 :       guess = NS_MAX(guess, startOffset + startLength);
    1241                 : 
    1242                 :       SEEK_LOG(PR_LOG_DEBUG, ("Seek loop start[o=%lld..%lld t=%lld] "
    1243                 :                               "end[o=%lld t=%lld] "
    1244                 :                               "interval=%lld target=%lf guess=%lld",
    1245                 :                               startOffset, (startOffset+startLength), startTime,
    1246                 :                               endOffset, endTime, interval, target, guess));
    1247                 : 
    1248               0 :       NS_ASSERTION(guess >= startOffset + startLength, "Guess must be after range start");
    1249               0 :       NS_ASSERTION(guess < endOffset, "Guess must be before range end");
    1250               0 :       NS_ASSERTION(guess != previousGuess, "Guess should be different to previous");
    1251               0 :       previousGuess = guess;
    1252                 : 
    1253               0 :       hops++;
    1254                 :     
    1255                 :       // Locate the next page after our seek guess, and then figure out the
    1256                 :       // granule time of the audio and video bitstreams there. We can then
    1257                 :       // make a bisection decision based on our location in the media.
    1258                 :       PageSyncResult res = PageSync(resource,
    1259                 :                                     &mOggState,
    1260                 :                                     false,
    1261                 :                                     guess,
    1262                 :                                     endOffset,
    1263                 :                                     &page,
    1264               0 :                                     skippedBytes);
    1265               0 :       NS_ENSURE_TRUE(res != PAGE_SYNC_ERROR, NS_ERROR_FAILURE);
    1266                 : 
    1267                 :       // We've located a page of length |ret| at |guess + skippedBytes|.
    1268                 :       // Remember where the page is located.
    1269               0 :       pageOffset = guess + skippedBytes;
    1270               0 :       pageLength = page.header_len + page.body_len;
    1271               0 :       mPageOffset = pageOffset + pageLength;
    1272                 : 
    1273               0 :       if (res == PAGE_SYNC_END_OF_RANGE) {
    1274                 :         // Our guess was too close to the end, we've ended up reading the end
    1275                 :         // page. Backoff exponentially from the end point, in case the last
    1276                 :         // page/frame/sample is huge.
    1277               0 :         mustBackoff = true;
    1278                 :         SEEK_LOG(PR_LOG_DEBUG, ("Hit the end of range, backing off"));
    1279               0 :         continue;
    1280                 :       }
    1281                 : 
    1282                 :       // Read pages until we can determine the granule time of the audio and 
    1283                 :       // video bitstream.
    1284               0 :       ogg_int64_t audioTime = -1;
    1285               0 :       ogg_int64_t videoTime = -1;
    1286               0 :       do {
    1287                 :         // Add the page to its codec state, determine its granule time.
    1288               0 :         PRUint32 serial = ogg_page_serialno(&page);
    1289               0 :         nsOggCodecState* codecState = nsnull;
    1290               0 :         mCodecStates.Get(serial, &codecState);
    1291               0 :         if (codecState && codecState->mActive) {
    1292               0 :           int ret = ogg_stream_pagein(&codecState->mState, &page);
    1293               0 :           NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE);
    1294                 :         }
    1295                 : 
    1296               0 :         ogg_int64_t granulepos = ogg_page_granulepos(&page);
    1297                 : 
    1298               0 :         if (HasAudio() &&
    1299                 :             granulepos > 0 &&
    1300                 :             serial == mVorbisState->mSerial &&
    1301                 :             audioTime == -1) {
    1302               0 :           audioTime = mVorbisState->Time(granulepos);
    1303                 :         }
    1304                 :         
    1305               0 :         if (HasVideo() &&
    1306                 :             granulepos > 0 &&
    1307                 :             serial == mTheoraState->mSerial &&
    1308                 :             videoTime == -1) {
    1309               0 :           videoTime = mTheoraState->StartTime(granulepos);
    1310                 :         }
    1311                 : 
    1312               0 :         if (mPageOffset == endOffset) {
    1313                 :           // Hit end of readable data.
    1314               0 :           break;
    1315                 :         }
    1316                 : 
    1317               0 :         if (ReadOggPage(&page) == -1) {
    1318               0 :           break;
    1319                 :         }
    1320                 :         
    1321                 :       } while ((mVorbisState && audioTime == -1) ||
    1322                 :                (mTheoraState && videoTime == -1));
    1323                 : 
    1324               0 :       NS_ASSERTION(mPageOffset <= endOffset, "Page read cursor should be inside range");
    1325                 : 
    1326               0 :       if ((HasAudio() && audioTime == -1) ||
    1327               0 :           (HasVideo() && videoTime == -1)) 
    1328                 :       {
    1329                 :         // We don't have timestamps for all active tracks...
    1330               0 :         if (pageOffset == startOffset + startLength && mPageOffset == endOffset) {
    1331                 :           // We read the entire interval without finding timestamps for all
    1332                 :           // active tracks. We know the interval start offset is before the seek
    1333                 :           // target, and the interval end is after the seek target, and we can't
    1334                 :           // terminate inside the interval, so we terminate the seek at the
    1335                 :           // start of the interval.
    1336               0 :           interval = 0;
    1337               0 :           break;
    1338                 :         }
    1339                 : 
    1340                 :         // We should backoff; cause the guess to back off from the end, so
    1341                 :         // that we've got more room to capture.
    1342               0 :         mustBackoff = true;
    1343               0 :         continue;
    1344                 :       }
    1345                 : 
    1346                 :       // We've found appropriate time stamps here. Proceed to bisect
    1347                 :       // the search space.
    1348               0 :       granuleTime = NS_MAX(audioTime, videoTime);
    1349               0 :       NS_ASSERTION(granuleTime > 0, "Must get a granuletime");
    1350               0 :       break;
    1351                 :     } // End of "until we determine time at guess offset" loop.
    1352                 : 
    1353               0 :     if (interval == 0) {
    1354                 :       // Seek termination condition; we've found the page boundary of the
    1355                 :       // last page before the target, and the first page after the target.
    1356                 :       SEEK_LOG(PR_LOG_DEBUG, ("Terminating seek at offset=%lld", startOffset));
    1357               0 :       NS_ASSERTION(startTime < aTarget, "Start time must always be less than target");
    1358               0 :       res = resource->Seek(nsISeekableStream::NS_SEEK_SET, startOffset);
    1359               0 :       NS_ENSURE_SUCCESS(res,res);
    1360               0 :       mPageOffset = startOffset;
    1361               0 :       if (NS_FAILED(ResetDecode())) {
    1362               0 :         return NS_ERROR_FAILURE;
    1363                 :       }
    1364               0 :       break;
    1365                 :     }
    1366                 : 
    1367                 :     SEEK_LOG(PR_LOG_DEBUG, ("Time at offset %lld is %lld", guess, granuleTime));
    1368               0 :     if (granuleTime < seekTarget && granuleTime > seekLowerBound) {
    1369                 :       // We're within the fuzzy region in which we want to terminate the search.
    1370               0 :       res = resource->Seek(nsISeekableStream::NS_SEEK_SET, pageOffset);
    1371               0 :       NS_ENSURE_SUCCESS(res,res);
    1372               0 :       mPageOffset = pageOffset;
    1373               0 :       if (NS_FAILED(ResetDecode())) {
    1374               0 :         return NS_ERROR_FAILURE;
    1375                 :       }
    1376                 :       SEEK_LOG(PR_LOG_DEBUG, ("Terminating seek at offset=%lld", mPageOffset));
    1377               0 :       break;
    1378                 :     }
    1379                 : 
    1380               0 :     if (granuleTime >= seekTarget) {
    1381                 :       // We've landed after the seek target.
    1382               0 :       NS_ASSERTION(pageOffset < endOffset, "offset_end must decrease");
    1383               0 :       endOffset = pageOffset;
    1384               0 :       endTime = granuleTime;
    1385               0 :     } else if (granuleTime < seekTarget) {
    1386                 :       // Landed before seek target.
    1387               0 :       NS_ASSERTION(pageOffset >= startOffset + startLength,
    1388                 :         "Bisection point should be at or after end of first page in interval");
    1389               0 :       startOffset = pageOffset;
    1390               0 :       startLength = pageLength;
    1391               0 :       startTime = granuleTime;
    1392                 :     }
    1393               0 :     NS_ASSERTION(startTime < seekTarget, "Must be before seek target");
    1394               0 :     NS_ASSERTION(endTime >= seekTarget, "End must be after seek target");
    1395                 :   }
    1396                 : 
    1397                 :   SEEK_LOG(PR_LOG_DEBUG, ("Seek complete in %d bisections.", hops));
    1398                 : 
    1399               0 :   return NS_OK;
    1400                 : }
    1401                 : 
    1402               0 : nsresult nsOggReader::GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime)
    1403                 : {
    1404                 :   // HasAudio and HasVideo are not used here as they take a lock and cause
    1405                 :   // a deadlock. Accessing mInfo doesn't require a lock - it doesn't change
    1406                 :   // after metadata is read and GetBuffered isn't called before metadata is
    1407                 :   // read.
    1408               0 :   if (!mInfo.mHasVideo && !mInfo.mHasAudio) {
    1409                 :     // No need to search through the file if there are no audio or video tracks
    1410               0 :     return NS_OK;
    1411                 :   }
    1412                 : 
    1413               0 :   MediaResource* resource = mDecoder->GetResource();
    1414               0 :   nsTArray<MediaByteRange> ranges;
    1415               0 :   nsresult res = resource->GetCachedRanges(ranges);
    1416               0 :   NS_ENSURE_SUCCESS(res, res);
    1417                 : 
    1418                 :   // Traverse across the buffered byte ranges, determining the time ranges
    1419                 :   // they contain. MediaResource::GetNextCachedData(offset) returns -1 when
    1420                 :   // offset is after the end of the media resource, or there's no more cached
    1421                 :   // data after the offset. This loop will run until we've checked every
    1422                 :   // buffered range in the media, in increasing order of offset.
    1423               0 :   nsAutoOggSyncState sync;
    1424               0 :   for (PRUint32 index = 0; index < ranges.Length(); index++) {
    1425                 :     // Ensure the offsets are after the header pages.
    1426               0 :     PRInt64 startOffset = ranges[index].mStart;
    1427               0 :     PRInt64 endOffset = ranges[index].mEnd;
    1428                 : 
    1429                 :     // Because the granulepos time is actually the end time of the page,
    1430                 :     // we special-case (startOffset == 0) so that the first
    1431                 :     // buffered range always appears to be buffered from the media start
    1432                 :     // time, rather than from the end-time of the first page.
    1433               0 :     PRInt64 startTime = (startOffset == 0) ? aStartTime : -1;
    1434                 : 
    1435                 :     // Find the start time of the range. Read pages until we find one with a
    1436                 :     // granulepos which we can convert into a timestamp to use as the time of
    1437                 :     // the start of the buffered range.
    1438               0 :     ogg_sync_reset(&sync.mState);
    1439               0 :     while (startTime == -1) {
    1440                 :       ogg_page page;
    1441                 :       PRInt32 discard;
    1442                 :       PageSyncResult res = PageSync(resource,
    1443                 :                                     &sync.mState,
    1444                 :                                     true,
    1445                 :                                     startOffset,
    1446                 :                                     endOffset,
    1447                 :                                     &page,
    1448               0 :                                     discard);
    1449               0 :       if (res == PAGE_SYNC_ERROR) {
    1450               0 :         return NS_ERROR_FAILURE;
    1451               0 :       } else if (res == PAGE_SYNC_END_OF_RANGE) {
    1452                 :         // Hit the end of range without reading a page, give up trying to
    1453                 :         // find a start time for this buffered range, skip onto the next one.
    1454               0 :         break;
    1455                 :       }
    1456                 : 
    1457               0 :       PRInt64 granulepos = ogg_page_granulepos(&page);
    1458               0 :       if (granulepos == -1) {
    1459                 :         // Page doesn't have an end time, advance to the next page
    1460                 :         // until we find one.
    1461               0 :         startOffset += page.header_len + page.body_len;
    1462               0 :         continue;
    1463                 :       }
    1464                 : 
    1465               0 :       PRUint32 serial = ogg_page_serialno(&page);
    1466               0 :       if (mVorbisState && serial == mVorbisSerial) {
    1467               0 :         startTime = nsVorbisState::Time(&mVorbisInfo, granulepos);
    1468               0 :         NS_ASSERTION(startTime > 0, "Must have positive start time");
    1469                 :       }
    1470               0 :       else if (mTheoraState && serial == mTheoraSerial) {
    1471               0 :         startTime = nsTheoraState::Time(&mTheoraInfo, granulepos);
    1472               0 :         NS_ASSERTION(startTime > 0, "Must have positive start time");
    1473                 :       }
    1474               0 :       else if (IsKnownStream(serial)) {
    1475                 :         // Stream is not the theora or vorbis stream we're playing,
    1476                 :         // but is one that we have header data for.
    1477               0 :         startOffset += page.header_len + page.body_len;
    1478               0 :         continue;
    1479                 :       }
    1480                 :       else {
    1481                 :         // Page is for a stream we don't know about (possibly a chained
    1482                 :         // ogg), return an error.
    1483               0 :         return PAGE_SYNC_ERROR;
    1484                 :       }
    1485                 :     }
    1486                 : 
    1487               0 :     if (startTime != -1) {
    1488                 :       // We were able to find a start time for that range, see if we can
    1489                 :       // find an end time.
    1490               0 :       PRInt64 endTime = RangeEndTime(startOffset, endOffset, true);
    1491               0 :       if (endTime != -1) {
    1492                 :         aBuffered->Add((startTime - aStartTime) / static_cast<double>(USECS_PER_S),
    1493               0 :                        (endTime - aStartTime) / static_cast<double>(USECS_PER_S));
    1494                 :       }
    1495                 :     }
    1496                 :   }
    1497                 : 
    1498               0 :   return NS_OK;
    1499                 : }
    1500                 : 
    1501               0 : bool nsOggReader::IsKnownStream(PRUint32 aSerial)
    1502                 : {
    1503               0 :   for (PRUint32 i = 0; i < mKnownStreams.Length(); i++) {
    1504               0 :     PRUint32 serial = mKnownStreams[i];
    1505               0 :     if (serial == aSerial) {
    1506               0 :       return true;
    1507                 :     }
    1508                 :   }
    1509                 : 
    1510               0 :   return false;
    1511                 : }

Generated by: LCOV version 1.7