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 : }
|