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