/src/mozilla-central/dom/media/ogg/OggDemuxer.cpp
Line | Count | Source (jump to first uncovered line) |
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 "nsError.h" |
8 | | #include "MediaDecoderStateMachine.h" |
9 | | #include "OggDemuxer.h" |
10 | | #include "OggCodecState.h" |
11 | | #include "mozilla/AbstractThread.h" |
12 | | #include "mozilla/Atomics.h" |
13 | | #include "mozilla/PodOperations.h" |
14 | | #include "mozilla/SharedThreadPool.h" |
15 | | #include "mozilla/StaticPrefs.h" |
16 | | #include "mozilla/Telemetry.h" |
17 | | #include "mozilla/TimeStamp.h" |
18 | | #include "MediaDataDemuxer.h" |
19 | | #include "nsAutoRef.h" |
20 | | #include "XiphExtradata.h" |
21 | | |
22 | | #include <algorithm> |
23 | | |
24 | | extern mozilla::LazyLogModule gMediaDemuxerLog; |
25 | | #define OGG_DEBUG(arg, ...) \ |
26 | 0 | DDMOZ_LOG(gMediaDemuxerLog, \ |
27 | 0 | mozilla::LogLevel::Debug, \ |
28 | 0 | "::%s: " arg, \ |
29 | 0 | __func__, \ |
30 | 0 | ##__VA_ARGS__) |
31 | | |
32 | | // Un-comment to enable logging of seek bisections. |
33 | | //#define SEEK_LOGGING |
34 | | #ifdef SEEK_LOGGING |
35 | | #define SEEK_LOG(type, msg) MOZ_LOG(gMediaDemuxerLog, type, msg) |
36 | | #else |
37 | | #define SEEK_LOG(type, msg) |
38 | | #endif |
39 | | |
40 | | namespace mozilla |
41 | | { |
42 | | |
43 | | using media::TimeUnit; |
44 | | using media::TimeInterval; |
45 | | using media::TimeIntervals; |
46 | | |
47 | | // The number of microseconds of "fuzz" we use in a bisection search over |
48 | | // HTTP. When we're seeking with fuzz, we'll stop the search if a bisection |
49 | | // lands between the seek target and OGG_SEEK_FUZZ_USECS microseconds before the |
50 | | // seek target. This is becaue it's usually quicker to just keep downloading |
51 | | // from an exisiting connection than to do another bisection inside that |
52 | | // small range, which would open a new HTTP connetion. |
53 | | static const uint32_t OGG_SEEK_FUZZ_USECS = 500000; |
54 | | |
55 | | // The number of microseconds of "pre-roll" we use for Opus streams. |
56 | | // The specification recommends 80 ms. |
57 | | static const int64_t OGG_SEEK_OPUS_PREROLL = 80 * USECS_PER_MS; |
58 | | |
59 | | static Atomic<uint32_t> sStreamSourceID(0u); |
60 | | |
61 | | // Return the corresponding category in aKind based on the following specs. |
62 | | // (https://www.whatwg.org/specs/web-apps/current- |
63 | | // work/multipage/embedded-content.html#dom-audiotrack-kind) & |
64 | | // (http://wiki.xiph.org/SkeletonHeaders) |
65 | | const nsString |
66 | | OggDemuxer::GetKind(const nsCString& aRole) |
67 | 0 | { |
68 | 0 | if (aRole.Find("audio/main") != -1 || aRole.Find("video/main") != -1) { |
69 | 0 | return NS_LITERAL_STRING("main"); |
70 | 0 | } else if (aRole.Find("audio/alternate") != -1 || |
71 | 0 | aRole.Find("video/alternate") != -1) { |
72 | 0 | return NS_LITERAL_STRING("alternative"); |
73 | 0 | } else if (aRole.Find("audio/audiodesc") != -1) { |
74 | 0 | return NS_LITERAL_STRING("descriptions"); |
75 | 0 | } else if (aRole.Find("audio/described") != -1) { |
76 | 0 | return NS_LITERAL_STRING("main-desc"); |
77 | 0 | } else if (aRole.Find("audio/dub") != -1) { |
78 | 0 | return NS_LITERAL_STRING("translation"); |
79 | 0 | } else if (aRole.Find("audio/commentary") != -1) { |
80 | 0 | return NS_LITERAL_STRING("commentary"); |
81 | 0 | } else if (aRole.Find("video/sign") != -1) { |
82 | 0 | return NS_LITERAL_STRING("sign"); |
83 | 0 | } else if (aRole.Find("video/captioned") != -1) { |
84 | 0 | return NS_LITERAL_STRING("captions"); |
85 | 0 | } else if (aRole.Find("video/subtitled") != -1) { |
86 | 0 | return NS_LITERAL_STRING("subtitles"); |
87 | 0 | } |
88 | 0 | return EmptyString(); |
89 | 0 | } |
90 | | |
91 | | void |
92 | | OggDemuxer::InitTrack(MessageField* aMsgInfo, |
93 | | TrackInfo* aInfo, |
94 | | bool aEnable) |
95 | 0 | { |
96 | 0 | MOZ_ASSERT(aMsgInfo); |
97 | 0 | MOZ_ASSERT(aInfo); |
98 | 0 |
|
99 | 0 | nsCString* sName = aMsgInfo->mValuesStore.Get(eName); |
100 | 0 | nsCString* sRole = aMsgInfo->mValuesStore.Get(eRole); |
101 | 0 | nsCString* sTitle = aMsgInfo->mValuesStore.Get(eTitle); |
102 | 0 | nsCString* sLanguage = aMsgInfo->mValuesStore.Get(eLanguage); |
103 | 0 | aInfo->Init(sName? NS_ConvertUTF8toUTF16(*sName):EmptyString(), |
104 | 0 | sRole? GetKind(*sRole):EmptyString(), |
105 | 0 | sTitle? NS_ConvertUTF8toUTF16(*sTitle):EmptyString(), |
106 | 0 | sLanguage? NS_ConvertUTF8toUTF16(*sLanguage):EmptyString(), |
107 | 0 | aEnable); |
108 | 0 | } |
109 | | |
110 | | OggDemuxer::OggDemuxer(MediaResource* aResource) |
111 | | : mTheoraState(nullptr) |
112 | | , mVorbisState(nullptr) |
113 | | , mOpusState(nullptr) |
114 | | , mFlacState(nullptr) |
115 | | , mOpusEnabled(MediaDecoder::IsOpusEnabled()) |
116 | | , mSkeletonState(nullptr) |
117 | | , mAudioOggState(aResource) |
118 | | , mVideoOggState(aResource) |
119 | | , mIsChained(false) |
120 | | , mTimedMetadataEvent(nullptr) |
121 | | , mOnSeekableEvent(nullptr) |
122 | 0 | { |
123 | 0 | MOZ_COUNT_CTOR(OggDemuxer); |
124 | 0 | // aResource is referenced through inner m{Audio,Video}OffState members. |
125 | 0 | DDLINKCHILD("resource", aResource); |
126 | 0 | } |
127 | | |
128 | | OggDemuxer::~OggDemuxer() |
129 | 0 | { |
130 | 0 | MOZ_COUNT_DTOR(OggDemuxer); |
131 | 0 | Reset(TrackInfo::kAudioTrack); |
132 | 0 | Reset(TrackInfo::kVideoTrack); |
133 | 0 | if (HasAudio() || HasVideo()) { |
134 | 0 | // If we were able to initialize our decoders, report whether we encountered |
135 | 0 | // a chained stream or not. |
136 | 0 | bool isChained = mIsChained; |
137 | 0 | void* ptr = this; |
138 | 0 | nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction( |
139 | 0 | "OggDemuxer::~OggDemuxer", [ptr, isChained]() -> void { |
140 | 0 | // We can't use OGG_DEBUG here because it implicitly refers to `this`, |
141 | 0 | // which we can't capture in this runnable. |
142 | 0 | MOZ_LOG(gMediaDemuxerLog, |
143 | 0 | mozilla::LogLevel::Debug, |
144 | 0 | ("OggDemuxer(%p)::%s: Reporting telemetry " |
145 | 0 | "MEDIA_OGG_LOADED_IS_CHAINED=%d", |
146 | 0 | ptr, |
147 | 0 | __func__, |
148 | 0 | isChained)); |
149 | 0 | Telemetry::Accumulate( |
150 | 0 | Telemetry::HistogramID::MEDIA_OGG_LOADED_IS_CHAINED, isChained); |
151 | 0 | }); |
152 | 0 | SystemGroup::Dispatch(TaskCategory::Other, task.forget()); |
153 | 0 | } |
154 | 0 | } |
155 | | |
156 | | void |
157 | | OggDemuxer::SetChainingEvents(TimedMetadataEventProducer* aMetadataEvent, |
158 | | MediaEventProducer<void>* aOnSeekableEvent) |
159 | 0 | { |
160 | 0 | mTimedMetadataEvent = aMetadataEvent; |
161 | 0 | mOnSeekableEvent = aOnSeekableEvent; |
162 | 0 | } |
163 | | |
164 | | |
165 | | bool |
166 | | OggDemuxer::HasAudio() |
167 | | const |
168 | 0 | { |
169 | 0 | return mVorbisState || mOpusState || mFlacState; |
170 | 0 | } |
171 | | |
172 | | bool |
173 | | OggDemuxer::HasVideo() |
174 | | const |
175 | 0 | { |
176 | 0 | return mTheoraState; |
177 | 0 | } |
178 | | |
179 | | bool |
180 | | OggDemuxer::HaveStartTime() |
181 | | const |
182 | 0 | { |
183 | 0 | return mStartTime.isSome(); |
184 | 0 | } |
185 | | |
186 | | int64_t |
187 | | OggDemuxer::StartTime() const |
188 | 0 | { |
189 | 0 | return mStartTime.refOr(0); |
190 | 0 | } |
191 | | |
192 | | bool |
193 | | OggDemuxer::HaveStartTime(TrackInfo::TrackType aType) |
194 | 0 | { |
195 | 0 | return OggState(aType).mStartTime.isSome(); |
196 | 0 | } |
197 | | |
198 | | int64_t |
199 | | OggDemuxer::StartTime(TrackInfo::TrackType aType) |
200 | 0 | { |
201 | 0 | return OggState(aType).mStartTime.refOr(TimeUnit::Zero()).ToMicroseconds(); |
202 | 0 | } |
203 | | |
204 | | RefPtr<OggDemuxer::InitPromise> |
205 | | OggDemuxer::Init() |
206 | 0 | { |
207 | 0 | int ret = ogg_sync_init(OggSyncState(TrackInfo::kAudioTrack)); |
208 | 0 | if (ret != 0) { |
209 | 0 | return InitPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__); |
210 | 0 | } |
211 | 0 | ret = ogg_sync_init(OggSyncState(TrackInfo::kVideoTrack)); |
212 | 0 | if (ret != 0) { |
213 | 0 | return InitPromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__); |
214 | 0 | } |
215 | 0 | if (ReadMetadata() != NS_OK) { |
216 | 0 | return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__); |
217 | 0 | } |
218 | 0 | |
219 | 0 | if (!GetNumberTracks(TrackInfo::kAudioTrack) && |
220 | 0 | !GetNumberTracks(TrackInfo::kVideoTrack)) { |
221 | 0 | return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__); |
222 | 0 | } |
223 | 0 | |
224 | 0 | return InitPromise::CreateAndResolve(NS_OK, __func__); |
225 | 0 | } |
226 | | |
227 | | OggCodecState* |
228 | | OggDemuxer::GetTrackCodecState(TrackInfo::TrackType aType) const |
229 | 0 | { |
230 | 0 | switch(aType) { |
231 | 0 | case TrackInfo::kAudioTrack: |
232 | 0 | if (mVorbisState) { |
233 | 0 | return mVorbisState; |
234 | 0 | } else if (mOpusState) { |
235 | 0 | return mOpusState; |
236 | 0 | } else { |
237 | 0 | return mFlacState; |
238 | 0 | } |
239 | 0 | case TrackInfo::kVideoTrack: |
240 | 0 | return mTheoraState; |
241 | 0 | default: |
242 | 0 | return 0; |
243 | 0 | } |
244 | 0 | } |
245 | | |
246 | | TrackInfo::TrackType |
247 | | OggDemuxer::GetCodecStateType(OggCodecState* aState) const |
248 | | { |
249 | | switch (aState->GetType()) { |
250 | | case OggCodecState::TYPE_THEORA: |
251 | | return TrackInfo::kVideoTrack; |
252 | | case OggCodecState::TYPE_OPUS: |
253 | | case OggCodecState::TYPE_VORBIS: |
254 | | case OggCodecState::TYPE_FLAC: |
255 | | return TrackInfo::kAudioTrack; |
256 | | default: |
257 | | return TrackInfo::kUndefinedTrack; |
258 | | } |
259 | | } |
260 | | |
261 | | uint32_t |
262 | | OggDemuxer::GetNumberTracks(TrackInfo::TrackType aType) const |
263 | 0 | { |
264 | 0 | switch(aType) { |
265 | 0 | case TrackInfo::kAudioTrack: |
266 | 0 | return HasAudio() ? 1 : 0; |
267 | 0 | case TrackInfo::kVideoTrack: |
268 | 0 | return HasVideo() ? 1 : 0; |
269 | 0 | default: |
270 | 0 | return 0; |
271 | 0 | } |
272 | 0 | } |
273 | | |
274 | | UniquePtr<TrackInfo> |
275 | | OggDemuxer::GetTrackInfo(TrackInfo::TrackType aType, size_t aTrackNumber) const |
276 | | { |
277 | | switch(aType) { |
278 | | case TrackInfo::kAudioTrack: |
279 | | return mInfo.mAudio.Clone(); |
280 | | case TrackInfo::kVideoTrack: |
281 | | return mInfo.mVideo.Clone(); |
282 | | default: |
283 | | return nullptr; |
284 | | } |
285 | | } |
286 | | |
287 | | already_AddRefed<MediaTrackDemuxer> |
288 | | OggDemuxer::GetTrackDemuxer(TrackInfo::TrackType aType, uint32_t aTrackNumber) |
289 | 0 | { |
290 | 0 | if (GetNumberTracks(aType) <= aTrackNumber) { |
291 | 0 | return nullptr; |
292 | 0 | } |
293 | 0 | RefPtr<OggTrackDemuxer> e = new OggTrackDemuxer(this, aType, aTrackNumber); |
294 | 0 | DDLINKCHILD("track demuxer", e.get()); |
295 | 0 | mDemuxers.AppendElement(e); |
296 | 0 |
|
297 | 0 | return e.forget(); |
298 | 0 | } |
299 | | |
300 | | nsresult |
301 | | OggDemuxer::Reset(TrackInfo::TrackType aType) |
302 | 0 | { |
303 | 0 | // Discard any previously buffered packets/pages. |
304 | 0 | ogg_sync_reset(OggSyncState(aType)); |
305 | 0 | OggCodecState* trackState = GetTrackCodecState(aType); |
306 | 0 | if (trackState) { |
307 | 0 | return trackState->Reset(); |
308 | 0 | } |
309 | 0 | OggState(aType).mNeedKeyframe = true; |
310 | 0 | return NS_OK; |
311 | 0 | } |
312 | | |
313 | | bool |
314 | | OggDemuxer::ReadHeaders(TrackInfo::TrackType aType, |
315 | | OggCodecState* aState) |
316 | 0 | { |
317 | 0 | while (!aState->DoneReadingHeaders()) { |
318 | 0 | DemuxUntilPacketAvailable(aType, aState); |
319 | 0 | OggPacketPtr packet = aState->PacketOut(); |
320 | 0 | if (!packet) { |
321 | 0 | OGG_DEBUG("Ran out of header packets early; deactivating stream %" PRIu32, aState->mSerial); |
322 | 0 | aState->Deactivate(); |
323 | 0 | return false; |
324 | 0 | } |
325 | 0 |
|
326 | 0 | // Local OggCodecState needs to decode headers in order to process |
327 | 0 | // packet granulepos -> time mappings, etc. |
328 | 0 | if (!aState->DecodeHeader(std::move(packet))) { |
329 | 0 | OGG_DEBUG("Failed to decode ogg header packet; deactivating stream %" PRIu32, aState->mSerial); |
330 | 0 | aState->Deactivate(); |
331 | 0 | return false; |
332 | 0 | } |
333 | 0 | } |
334 | 0 |
|
335 | 0 | return aState->Init(); |
336 | 0 | } |
337 | | |
338 | | void |
339 | | OggDemuxer::BuildSerialList(nsTArray<uint32_t>& aTracks) |
340 | 0 | { |
341 | 0 | // Obtaining seek index information for currently active bitstreams. |
342 | 0 | if (HasVideo()) { |
343 | 0 | aTracks.AppendElement(mTheoraState->mSerial); |
344 | 0 | } |
345 | 0 | if (HasAudio()) { |
346 | 0 | if (mVorbisState) { |
347 | 0 | aTracks.AppendElement(mVorbisState->mSerial); |
348 | 0 | } else if (mOpusState) { |
349 | 0 | aTracks.AppendElement(mOpusState->mSerial); |
350 | 0 | } |
351 | 0 | } |
352 | 0 | } |
353 | | |
354 | | void |
355 | | OggDemuxer::SetupTarget(OggCodecState** aSavedState, OggCodecState* aNewState) |
356 | 0 | { |
357 | 0 | if (*aSavedState) { |
358 | 0 | (*aSavedState)->Reset(); |
359 | 0 | } |
360 | 0 |
|
361 | 0 | if (aNewState->GetInfo()->GetAsAudioInfo()) { |
362 | 0 | mInfo.mAudio = *aNewState->GetInfo()->GetAsAudioInfo(); |
363 | 0 | } else { |
364 | 0 | mInfo.mVideo = *aNewState->GetInfo()->GetAsVideoInfo(); |
365 | 0 | } |
366 | 0 | *aSavedState = aNewState; |
367 | 0 | } |
368 | | |
369 | | void |
370 | | OggDemuxer::SetupTargetSkeleton() |
371 | 0 | { |
372 | 0 | // Setup skeleton related information after mVorbisState & mTheroState |
373 | 0 | // being set (if they exist). |
374 | 0 | if (mSkeletonState) { |
375 | 0 | if (!HasAudio() && !HasVideo()) { |
376 | 0 | // We have a skeleton track, but no audio or video, may as well disable |
377 | 0 | // the skeleton, we can't do anything useful with this media. |
378 | 0 | OGG_DEBUG("Deactivating skeleton stream %" PRIu32, mSkeletonState->mSerial); |
379 | 0 | mSkeletonState->Deactivate(); |
380 | 0 | } else if (ReadHeaders(TrackInfo::kAudioTrack, mSkeletonState) && |
381 | 0 | mSkeletonState->HasIndex()) { |
382 | 0 | // We don't particularly care about which track we are currently using |
383 | 0 | // as both MediaResource points to the same content. |
384 | 0 | // Extract the duration info out of the index, so we don't need to seek to |
385 | 0 | // the end of resource to get it. |
386 | 0 | nsTArray<uint32_t> tracks; |
387 | 0 | BuildSerialList(tracks); |
388 | 0 | int64_t duration = 0; |
389 | 0 | if (NS_SUCCEEDED(mSkeletonState->GetDuration(tracks, duration))) { |
390 | 0 | OGG_DEBUG("Got duration from Skeleton index %" PRId64, duration); |
391 | 0 | mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(duration)); |
392 | 0 | } |
393 | 0 | } |
394 | 0 | } |
395 | 0 | } |
396 | | |
397 | | void |
398 | | OggDemuxer::SetupMediaTracksInfo(const nsTArray<uint32_t>& aSerials) |
399 | 0 | { |
400 | 0 | // For each serial number |
401 | 0 | // 1. Retrieve a codecState from mCodecStore by this serial number. |
402 | 0 | // 2. Retrieve a message field from mMsgFieldStore by this serial number. |
403 | 0 | // 3. For now, skip if the serial number refers to a non-primary bitstream. |
404 | 0 | // 4. Setup track and other audio/video related information per different types. |
405 | 0 | for (size_t i = 0; i < aSerials.Length(); i++) { |
406 | 0 | uint32_t serial = aSerials[i]; |
407 | 0 | OggCodecState* codecState = mCodecStore.Get(serial); |
408 | 0 |
|
409 | 0 | MessageField* msgInfo = nullptr; |
410 | 0 | if (mSkeletonState) { |
411 | 0 | mSkeletonState->mMsgFieldStore.Get(serial, &msgInfo); |
412 | 0 | } |
413 | 0 |
|
414 | 0 | OggCodecState* primeState = nullptr; |
415 | 0 | switch (codecState->GetType()) { |
416 | 0 | case OggCodecState::TYPE_THEORA: |
417 | 0 | primeState = mTheoraState; |
418 | 0 | break; |
419 | 0 | case OggCodecState::TYPE_VORBIS: |
420 | 0 | primeState = mVorbisState; |
421 | 0 | break; |
422 | 0 | case OggCodecState::TYPE_OPUS: |
423 | 0 | primeState = mOpusState; |
424 | 0 | break; |
425 | 0 | case OggCodecState::TYPE_FLAC: |
426 | 0 | primeState = mFlacState; |
427 | 0 | break; |
428 | 0 | default: |
429 | 0 | break; |
430 | 0 | } |
431 | 0 | if (primeState && primeState == codecState) { |
432 | 0 | bool isAudio = primeState->GetInfo()->GetAsAudioInfo(); |
433 | 0 | if (msgInfo) { |
434 | 0 | InitTrack(msgInfo, isAudio ? static_cast<TrackInfo*>(&mInfo.mAudio) |
435 | 0 | : &mInfo.mVideo, |
436 | 0 | true); |
437 | 0 | } |
438 | 0 | FillTags(isAudio ? static_cast<TrackInfo*>(&mInfo.mAudio) : &mInfo.mVideo, |
439 | 0 | primeState->GetTags()); |
440 | 0 | } |
441 | 0 | } |
442 | 0 | } |
443 | | |
444 | | void |
445 | | OggDemuxer::FillTags(TrackInfo* aInfo, MetadataTags* aTags) |
446 | 0 | { |
447 | 0 | if (!aTags) { |
448 | 0 | return; |
449 | 0 | } |
450 | 0 | nsAutoPtr<MetadataTags> tags(aTags); |
451 | 0 | for (auto iter = aTags->Iter(); !iter.Done(); iter.Next()) { |
452 | 0 | aInfo->mTags.AppendElement(MetadataTag(iter.Key(), iter.Data())); |
453 | 0 | } |
454 | 0 | } |
455 | | |
456 | | nsresult |
457 | | OggDemuxer::ReadMetadata() |
458 | 0 | { |
459 | 0 | OGG_DEBUG("OggDemuxer::ReadMetadata called!"); |
460 | 0 |
|
461 | 0 | // We read packets until all bitstreams have read all their header packets. |
462 | 0 | // We record the offset of the first non-header page so that we know |
463 | 0 | // what page to seek to when seeking to the media start. |
464 | 0 |
|
465 | 0 | // @FIXME we have to read all the header packets on all the streams |
466 | 0 | // and THEN we can run SetupTarget* |
467 | 0 | // @fixme fixme |
468 | 0 |
|
469 | 0 | TrackInfo::TrackType tracks[2] = |
470 | 0 | { TrackInfo::kAudioTrack, TrackInfo::kVideoTrack }; |
471 | 0 |
|
472 | 0 | nsTArray<OggCodecState*> bitstreams; |
473 | 0 | nsTArray<uint32_t> serials; |
474 | 0 |
|
475 | 0 | for (uint32_t i = 0; i < ArrayLength(tracks); i++) { |
476 | 0 | ogg_page page; |
477 | 0 | bool readAllBOS = false; |
478 | 0 | while (!readAllBOS) { |
479 | 0 | if (!ReadOggPage(tracks[i], &page)) { |
480 | 0 | // Some kind of error... |
481 | 0 | OGG_DEBUG("OggDemuxer::ReadOggPage failed? leaving ReadMetadata..."); |
482 | 0 | return NS_ERROR_FAILURE; |
483 | 0 | } |
484 | 0 |
|
485 | 0 | int serial = ogg_page_serialno(&page); |
486 | 0 |
|
487 | 0 | if (!ogg_page_bos(&page)) { |
488 | 0 | // We've encountered a non Beginning Of Stream page. No more BOS pages |
489 | 0 | // can follow in this Ogg segment, so there will be no other bitstreams |
490 | 0 | // in the Ogg (unless it's invalid). |
491 | 0 | readAllBOS = true; |
492 | 0 | } else if (!mCodecStore.Contains(serial)) { |
493 | 0 | // We've not encountered a stream with this serial number before. Create |
494 | 0 | // an OggCodecState to demux it, and map that to the OggCodecState |
495 | 0 | // in mCodecStates. |
496 | 0 | OggCodecState* codecState = OggCodecState::Create(&page); |
497 | 0 | mCodecStore.Add(serial, codecState); |
498 | 0 | bitstreams.AppendElement(codecState); |
499 | 0 | serials.AppendElement(serial); |
500 | 0 | } |
501 | 0 | if (NS_FAILED(DemuxOggPage(tracks[i], &page))) { |
502 | 0 | return NS_ERROR_FAILURE; |
503 | 0 | } |
504 | 0 | } |
505 | 0 | } |
506 | 0 |
|
507 | 0 | // We've read all BOS pages, so we know the streams contained in the media. |
508 | 0 | // 1. Find the first encountered Theora/Vorbis/Opus bitstream, and configure |
509 | 0 | // it as the target A/V bitstream. |
510 | 0 | // 2. Deactivate the rest of bitstreams for now, until we have MediaInfo |
511 | 0 | // support multiple track infos. |
512 | 0 | for (uint32_t i = 0; i < bitstreams.Length(); ++i) { |
513 | 0 | OggCodecState* s = bitstreams[i]; |
514 | 0 | if (s) { |
515 | 0 | if (s->GetType() == OggCodecState::TYPE_THEORA && |
516 | 0 | ReadHeaders(TrackInfo::kVideoTrack, s)) { |
517 | 0 | if (!mTheoraState) { |
518 | 0 | SetupTarget(&mTheoraState, s); |
519 | 0 | } else { |
520 | 0 | s->Deactivate(); |
521 | 0 | } |
522 | 0 | } else if (s->GetType() == OggCodecState::TYPE_VORBIS && |
523 | 0 | ReadHeaders(TrackInfo::kAudioTrack, s)) { |
524 | 0 | if (!mVorbisState) { |
525 | 0 | SetupTarget(&mVorbisState, s); |
526 | 0 | } else { |
527 | 0 | s->Deactivate(); |
528 | 0 | } |
529 | 0 | } else if (s->GetType() == OggCodecState::TYPE_OPUS && |
530 | 0 | ReadHeaders(TrackInfo::kAudioTrack, s)) { |
531 | 0 | if (mOpusEnabled) { |
532 | 0 | if (!mOpusState) { |
533 | 0 | SetupTarget(&mOpusState, s); |
534 | 0 | } else { |
535 | 0 | s->Deactivate(); |
536 | 0 | } |
537 | 0 | } else { |
538 | 0 | NS_WARNING("Opus decoding disabled." |
539 | 0 | " See media.opus.enabled in about:config"); |
540 | 0 | } |
541 | 0 | } else if (s->GetType() == OggCodecState::TYPE_FLAC && |
542 | 0 | ReadHeaders(TrackInfo::kAudioTrack, s)) { |
543 | 0 | if (!mFlacState) { |
544 | 0 | SetupTarget(&mFlacState, s); |
545 | 0 | } else { |
546 | 0 | s->Deactivate(); |
547 | 0 | } |
548 | 0 | } else if (s->GetType() == OggCodecState::TYPE_SKELETON && !mSkeletonState) { |
549 | 0 | mSkeletonState = static_cast<SkeletonState*>(s); |
550 | 0 | } else { |
551 | 0 | // Deactivate any non-primary bitstreams. |
552 | 0 | s->Deactivate(); |
553 | 0 | } |
554 | 0 | } |
555 | 0 | } |
556 | 0 |
|
557 | 0 | SetupTargetSkeleton(); |
558 | 0 | SetupMediaTracksInfo(serials); |
559 | 0 |
|
560 | 0 | if (HasAudio() || HasVideo()) { |
561 | 0 | int64_t startTime = -1; |
562 | 0 | FindStartTime(startTime); |
563 | 0 | if (startTime >= 0) { |
564 | 0 | OGG_DEBUG("Detected stream start time %" PRId64, startTime); |
565 | 0 | mStartTime.emplace(startTime); |
566 | 0 | } |
567 | 0 |
|
568 | 0 | if (mInfo.mMetadataDuration.isNothing() && |
569 | 0 | Resource(TrackInfo::kAudioTrack)->GetLength() >= 0) { |
570 | 0 | // We didn't get a duration from the index or a Content-Duration header. |
571 | 0 | // Seek to the end of file to find the end time. |
572 | 0 | int64_t length = Resource(TrackInfo::kAudioTrack)->GetLength(); |
573 | 0 |
|
574 | 0 | MOZ_ASSERT(length > 0, "Must have a content length to get end time"); |
575 | 0 |
|
576 | 0 | int64_t endTime = RangeEndTime(TrackInfo::kAudioTrack, length); |
577 | 0 |
|
578 | 0 | if (endTime != -1) { |
579 | 0 | mInfo.mUnadjustedMetadataEndTime.emplace(TimeUnit::FromMicroseconds(endTime)); |
580 | 0 | mInfo.mMetadataDuration.emplace(TimeUnit::FromMicroseconds(endTime - mStartTime.refOr(0))); |
581 | 0 | OGG_DEBUG("Got Ogg duration from seeking to end %" PRId64, endTime); |
582 | 0 | } |
583 | 0 | } |
584 | 0 | if (mInfo.mMetadataDuration.isNothing()) { |
585 | 0 | mInfo.mMetadataDuration.emplace(TimeUnit::FromInfinity()); |
586 | 0 | } |
587 | 0 | if (HasAudio()) { |
588 | 0 | mInfo.mAudio.mDuration = mInfo.mMetadataDuration.ref(); |
589 | 0 | } |
590 | 0 | if (HasVideo()) { |
591 | 0 | mInfo.mVideo.mDuration = mInfo.mMetadataDuration.ref(); |
592 | 0 | } |
593 | 0 | } else { |
594 | 0 | OGG_DEBUG("no audio or video tracks"); |
595 | 0 | return NS_ERROR_FAILURE; |
596 | 0 | } |
597 | 0 |
|
598 | 0 | OGG_DEBUG("success?!"); |
599 | 0 | return NS_OK; |
600 | 0 | } |
601 | | |
602 | | void |
603 | 0 | OggDemuxer::SetChained() { |
604 | 0 | { |
605 | 0 | if (mIsChained) { |
606 | 0 | return; |
607 | 0 | } |
608 | 0 | mIsChained = true; |
609 | 0 | } |
610 | 0 | if (mOnSeekableEvent) { |
611 | 0 | mOnSeekableEvent->Notify(); |
612 | 0 | } |
613 | 0 | } |
614 | | |
615 | | bool |
616 | | OggDemuxer::ReadOggChain(const media::TimeUnit& aLastEndTime) |
617 | 0 | { |
618 | 0 | bool chained = false; |
619 | 0 | OpusState* newOpusState = nullptr; |
620 | 0 | VorbisState* newVorbisState = nullptr; |
621 | 0 | FlacState* newFlacState = nullptr; |
622 | 0 | nsAutoPtr<MetadataTags> tags; |
623 | 0 |
|
624 | 0 | if (HasVideo() || HasSkeleton() || !HasAudio()) { |
625 | 0 | return false; |
626 | 0 | } |
627 | 0 | |
628 | 0 | ogg_page page; |
629 | 0 | if (!ReadOggPage(TrackInfo::kAudioTrack, &page) || !ogg_page_bos(&page)) { |
630 | 0 | // Chaining is only supported for audio only ogg files. |
631 | 0 | return false; |
632 | 0 | } |
633 | 0 | |
634 | 0 | int serial = ogg_page_serialno(&page); |
635 | 0 | if (mCodecStore.Contains(serial)) { |
636 | 0 | return false; |
637 | 0 | } |
638 | 0 | |
639 | 0 | nsAutoPtr<OggCodecState> codecState; |
640 | 0 | codecState = OggCodecState::Create(&page); |
641 | 0 | if (!codecState) { |
642 | 0 | return false; |
643 | 0 | } |
644 | 0 | |
645 | 0 | if (mVorbisState && (codecState->GetType() == OggCodecState::TYPE_VORBIS)) { |
646 | 0 | newVorbisState = static_cast<VorbisState*>(codecState.get()); |
647 | 0 | } else if (mOpusState && (codecState->GetType() == OggCodecState::TYPE_OPUS)) { |
648 | 0 | newOpusState = static_cast<OpusState*>(codecState.get()); |
649 | 0 | } else if (mFlacState && (codecState->GetType() == OggCodecState::TYPE_FLAC)) { |
650 | 0 | newFlacState = static_cast<FlacState*>(codecState.get()); |
651 | 0 | } else { |
652 | 0 | return false; |
653 | 0 | } |
654 | 0 | |
655 | 0 | OggCodecState* state; |
656 | 0 |
|
657 | 0 | mCodecStore.Add(serial, codecState.forget()); |
658 | 0 | state = mCodecStore.Get(serial); |
659 | 0 |
|
660 | 0 | NS_ENSURE_TRUE(state != nullptr, false); |
661 | 0 |
|
662 | 0 | if (NS_FAILED(state->PageIn(&page))) { |
663 | 0 | return false; |
664 | 0 | } |
665 | 0 | |
666 | 0 | MessageField* msgInfo = nullptr; |
667 | 0 | if (mSkeletonState) { |
668 | 0 | mSkeletonState->mMsgFieldStore.Get(serial, &msgInfo); |
669 | 0 | } |
670 | 0 |
|
671 | 0 | if ((newVorbisState && |
672 | 0 | ReadHeaders(TrackInfo::kAudioTrack, newVorbisState)) && |
673 | 0 | (mVorbisState->GetInfo()->GetAsAudioInfo()->mRate == |
674 | 0 | newVorbisState->GetInfo()->GetAsAudioInfo()->mRate) && |
675 | 0 | (mVorbisState->GetInfo()->GetAsAudioInfo()->mChannels == |
676 | 0 | newVorbisState->GetInfo()->GetAsAudioInfo()->mChannels)) { |
677 | 0 |
|
678 | 0 | SetupTarget(&mVorbisState, newVorbisState); |
679 | 0 | OGG_DEBUG("New vorbis ogg link, serial=%d\n", mVorbisState->mSerial); |
680 | 0 |
|
681 | 0 | if (msgInfo) { |
682 | 0 | InitTrack(msgInfo, &mInfo.mAudio, true); |
683 | 0 | } |
684 | 0 |
|
685 | 0 | chained = true; |
686 | 0 | tags = newVorbisState->GetTags(); |
687 | 0 | } |
688 | 0 |
|
689 | 0 | if ((newOpusState && |
690 | 0 | ReadHeaders(TrackInfo::kAudioTrack, newOpusState)) && |
691 | 0 | (mOpusState->GetInfo()->GetAsAudioInfo()->mRate == |
692 | 0 | newOpusState->GetInfo()->GetAsAudioInfo()->mRate) && |
693 | 0 | (mOpusState->GetInfo()->GetAsAudioInfo()->mChannels == |
694 | 0 | newOpusState->GetInfo()->GetAsAudioInfo()->mChannels)) { |
695 | 0 |
|
696 | 0 | SetupTarget(&mOpusState, newOpusState); |
697 | 0 |
|
698 | 0 | if (msgInfo) { |
699 | 0 | InitTrack(msgInfo, &mInfo.mAudio, true); |
700 | 0 | } |
701 | 0 |
|
702 | 0 | chained = true; |
703 | 0 | tags = newOpusState->GetTags(); |
704 | 0 | } |
705 | 0 |
|
706 | 0 | if ((newFlacState && |
707 | 0 | ReadHeaders(TrackInfo::kAudioTrack, newFlacState)) && |
708 | 0 | (mFlacState->GetInfo()->GetAsAudioInfo()->mRate == |
709 | 0 | newFlacState->GetInfo()->GetAsAudioInfo()->mRate) && |
710 | 0 | (mFlacState->GetInfo()->GetAsAudioInfo()->mChannels == |
711 | 0 | newFlacState->GetInfo()->GetAsAudioInfo()->mChannels)) { |
712 | 0 |
|
713 | 0 | SetupTarget(&mFlacState, newFlacState); |
714 | 0 | OGG_DEBUG("New flac ogg link, serial=%d\n", mFlacState->mSerial); |
715 | 0 |
|
716 | 0 | if (msgInfo) { |
717 | 0 | InitTrack(msgInfo, &mInfo.mAudio, true); |
718 | 0 | } |
719 | 0 |
|
720 | 0 | chained = true; |
721 | 0 | tags = newFlacState->GetTags(); |
722 | 0 | } |
723 | 0 |
|
724 | 0 | if (chained) { |
725 | 0 | SetChained(); |
726 | 0 | mInfo.mMediaSeekable = false; |
727 | 0 | mDecodedAudioDuration += aLastEndTime; |
728 | 0 | if (mTimedMetadataEvent) { |
729 | 0 | mTimedMetadataEvent->Notify( |
730 | 0 | TimedMetadata(mDecodedAudioDuration, |
731 | 0 | std::move(tags), |
732 | 0 | nsAutoPtr<MediaInfo>(new MediaInfo(mInfo)))); |
733 | 0 | } |
734 | 0 | // Setup a new TrackInfo so that the MediaFormatReader will flush the |
735 | 0 | // current decoder. |
736 | 0 | mSharedAudioTrackInfo = new TrackInfoSharedPtr(mInfo.mAudio, ++sStreamSourceID); |
737 | 0 | return true; |
738 | 0 | } |
739 | 0 |
|
740 | 0 | return false; |
741 | 0 | } |
742 | | |
743 | | OggDemuxer::OggStateContext& |
744 | | OggDemuxer::OggState(TrackInfo::TrackType aType) |
745 | 0 | { |
746 | 0 | if (aType == TrackInfo::kVideoTrack) { |
747 | 0 | return mVideoOggState; |
748 | 0 | } |
749 | 0 | return mAudioOggState; |
750 | 0 | } |
751 | | |
752 | | ogg_sync_state* |
753 | | OggDemuxer::OggSyncState(TrackInfo::TrackType aType) |
754 | 0 | { |
755 | 0 | return &OggState(aType).mOggState.mState; |
756 | 0 | } |
757 | | |
758 | | MediaResourceIndex* |
759 | | OggDemuxer::Resource(TrackInfo::TrackType aType) |
760 | 0 | { |
761 | 0 | return &OggState(aType).mResource; |
762 | 0 | } |
763 | | |
764 | | MediaResourceIndex* |
765 | | OggDemuxer::CommonResource() |
766 | 0 | { |
767 | 0 | return &mAudioOggState.mResource; |
768 | 0 | } |
769 | | |
770 | | bool |
771 | | OggDemuxer::ReadOggPage(TrackInfo::TrackType aType, ogg_page* aPage) |
772 | 0 | { |
773 | 0 | int ret = 0; |
774 | 0 | while((ret = ogg_sync_pageseek(OggSyncState(aType), aPage)) <= 0) { |
775 | 0 | if (ret < 0) { |
776 | 0 | // Lost page sync, have to skip up to next page. |
777 | 0 | continue; |
778 | 0 | } |
779 | 0 | // Returns a buffer that can be written too |
780 | 0 | // with the given size. This buffer is stored |
781 | 0 | // in the ogg synchronisation structure. |
782 | 0 | char* buffer = ogg_sync_buffer(OggSyncState(aType), 4096); |
783 | 0 | MOZ_ASSERT(buffer, "ogg_sync_buffer failed"); |
784 | 0 |
|
785 | 0 | // Read from the resource into the buffer |
786 | 0 | uint32_t bytesRead = 0; |
787 | 0 |
|
788 | 0 | nsresult rv = Resource(aType)->Read(buffer, 4096, &bytesRead); |
789 | 0 | if (NS_FAILED(rv) || !bytesRead) { |
790 | 0 | // End of file or error. |
791 | 0 | return false; |
792 | 0 | } |
793 | 0 | |
794 | 0 | // Update the synchronisation layer with the number |
795 | 0 | // of bytes written to the buffer |
796 | 0 | ret = ogg_sync_wrote(OggSyncState(aType), bytesRead); |
797 | 0 | NS_ENSURE_TRUE(ret == 0, false); |
798 | 0 | } |
799 | 0 |
|
800 | 0 | return true; |
801 | 0 | } |
802 | | |
803 | | nsresult |
804 | | OggDemuxer::DemuxOggPage(TrackInfo::TrackType aType, ogg_page* aPage) |
805 | 0 | { |
806 | 0 | int serial = ogg_page_serialno(aPage); |
807 | 0 | OggCodecState* codecState = mCodecStore.Get(serial); |
808 | 0 | if (codecState == nullptr) { |
809 | 0 | OGG_DEBUG("encountered packet for unrecognized codecState"); |
810 | 0 | return NS_ERROR_FAILURE; |
811 | 0 | } |
812 | 0 | if (GetCodecStateType(codecState) != aType && |
813 | 0 | codecState->GetType() != OggCodecState::TYPE_SKELETON) { |
814 | 0 | // Not a page we're interested in. |
815 | 0 | return NS_OK; |
816 | 0 | } |
817 | 0 | if (NS_FAILED(codecState->PageIn(aPage))) { |
818 | 0 | OGG_DEBUG("codecState->PageIn failed"); |
819 | 0 | return NS_ERROR_FAILURE; |
820 | 0 | } |
821 | 0 | return NS_OK; |
822 | 0 | } |
823 | | |
824 | | bool |
825 | | OggDemuxer::IsSeekable() const |
826 | 0 | { |
827 | 0 | if (mIsChained) { |
828 | 0 | return false; |
829 | 0 | } |
830 | 0 | return true; |
831 | 0 | } |
832 | | |
833 | | UniquePtr<EncryptionInfo> |
834 | | OggDemuxer::GetCrypto() |
835 | 0 | { |
836 | 0 | return nullptr; |
837 | 0 | } |
838 | | |
839 | | ogg_packet* |
840 | | OggDemuxer::GetNextPacket(TrackInfo::TrackType aType) |
841 | 0 | { |
842 | 0 | OggCodecState* state = GetTrackCodecState(aType); |
843 | 0 | ogg_packet* packet = nullptr; |
844 | 0 | OggStateContext& context = OggState(aType); |
845 | 0 |
|
846 | 0 | while (true) { |
847 | 0 | if (packet) { |
848 | 0 | Unused << state->PacketOut(); |
849 | 0 | } |
850 | 0 | DemuxUntilPacketAvailable(aType, state); |
851 | 0 |
|
852 | 0 | packet = state->PacketPeek(); |
853 | 0 | if (!packet) { |
854 | 0 | break; |
855 | 0 | } |
856 | 0 | if (state->IsHeader(packet)) { |
857 | 0 | continue; |
858 | 0 | } |
859 | 0 | if (context.mNeedKeyframe && !state->IsKeyframe(packet)) { |
860 | 0 | continue; |
861 | 0 | } |
862 | 0 | context.mNeedKeyframe = false; |
863 | 0 | break; |
864 | 0 | } |
865 | 0 |
|
866 | 0 | return packet; |
867 | 0 | } |
868 | | |
869 | | void |
870 | | OggDemuxer::DemuxUntilPacketAvailable(TrackInfo::TrackType aType, |
871 | | OggCodecState* aState) |
872 | 0 | { |
873 | 0 | while (!aState->IsPacketReady()) { |
874 | 0 | OGG_DEBUG("no packet yet, reading some more"); |
875 | 0 | ogg_page page; |
876 | 0 | if (!ReadOggPage(aType, &page)) { |
877 | 0 | OGG_DEBUG("no more pages to read in resource?"); |
878 | 0 | return; |
879 | 0 | } |
880 | 0 | DemuxOggPage(aType, &page); |
881 | 0 | } |
882 | 0 | } |
883 | | |
884 | | TimeIntervals |
885 | | OggDemuxer::GetBuffered(TrackInfo::TrackType aType) |
886 | 0 | { |
887 | 0 | if (!HaveStartTime(aType)) { |
888 | 0 | return TimeIntervals(); |
889 | 0 | } |
890 | 0 | if (mIsChained) { |
891 | 0 | return TimeIntervals::Invalid(); |
892 | 0 | } |
893 | 0 | TimeIntervals buffered; |
894 | 0 | // HasAudio and HasVideo are not used here as they take a lock and cause |
895 | 0 | // a deadlock. Accessing mInfo doesn't require a lock - it doesn't change |
896 | 0 | // after metadata is read. |
897 | 0 | if (!mInfo.HasValidMedia()) { |
898 | 0 | // No need to search through the file if there are no audio or video tracks |
899 | 0 | return buffered; |
900 | 0 | } |
901 | 0 | |
902 | 0 | AutoPinned<MediaResource> resource(Resource(aType)->GetResource()); |
903 | 0 | MediaByteRangeSet ranges; |
904 | 0 | nsresult res = resource->GetCachedRanges(ranges); |
905 | 0 | NS_ENSURE_SUCCESS(res, TimeIntervals::Invalid()); |
906 | 0 |
|
907 | 0 | // Traverse across the buffered byte ranges, determining the time ranges |
908 | 0 | // they contain. MediaResource::GetNextCachedData(offset) returns -1 when |
909 | 0 | // offset is after the end of the media resource, or there's no more cached |
910 | 0 | // data after the offset. This loop will run until we've checked every |
911 | 0 | // buffered range in the media, in increasing order of offset. |
912 | 0 | nsAutoOggSyncState sync; |
913 | 0 | for (uint32_t index = 0; index < ranges.Length(); index++) { |
914 | 0 | // Ensure the offsets are after the header pages. |
915 | 0 | int64_t startOffset = ranges[index].mStart; |
916 | 0 | int64_t endOffset = ranges[index].mEnd; |
917 | 0 |
|
918 | 0 | // Because the granulepos time is actually the end time of the page, |
919 | 0 | // we special-case (startOffset == 0) so that the first |
920 | 0 | // buffered range always appears to be buffered from the media start |
921 | 0 | // time, rather than from the end-time of the first page. |
922 | 0 | int64_t startTime = (startOffset == 0) ? StartTime() : -1; |
923 | 0 |
|
924 | 0 | // Find the start time of the range. Read pages until we find one with a |
925 | 0 | // granulepos which we can convert into a timestamp to use as the time of |
926 | 0 | // the start of the buffered range. |
927 | 0 | ogg_sync_reset(&sync.mState); |
928 | 0 | while (startTime == -1) { |
929 | 0 | ogg_page page; |
930 | 0 | int32_t discard; |
931 | 0 | PageSyncResult pageSyncResult = PageSync(Resource(aType), |
932 | 0 | &sync.mState, |
933 | 0 | true, |
934 | 0 | startOffset, |
935 | 0 | endOffset, |
936 | 0 | &page, |
937 | 0 | discard); |
938 | 0 | if (pageSyncResult == PAGE_SYNC_ERROR) { |
939 | 0 | return TimeIntervals::Invalid(); |
940 | 0 | } else if (pageSyncResult == PAGE_SYNC_END_OF_RANGE) { |
941 | 0 | // Hit the end of range without reading a page, give up trying to |
942 | 0 | // find a start time for this buffered range, skip onto the next one. |
943 | 0 | break; |
944 | 0 | } |
945 | 0 | |
946 | 0 | int64_t granulepos = ogg_page_granulepos(&page); |
947 | 0 | if (granulepos == -1) { |
948 | 0 | // Page doesn't have an end time, advance to the next page |
949 | 0 | // until we find one. |
950 | 0 | startOffset += page.header_len + page.body_len; |
951 | 0 | continue; |
952 | 0 | } |
953 | 0 | |
954 | 0 | uint32_t serial = ogg_page_serialno(&page); |
955 | 0 | if (aType == TrackInfo::kAudioTrack && mVorbisState && |
956 | 0 | serial == mVorbisState->mSerial) { |
957 | 0 | startTime = mVorbisState->Time(granulepos); |
958 | 0 | MOZ_ASSERT(startTime > 0, "Must have positive start time"); |
959 | 0 | } else if (aType == TrackInfo::kAudioTrack && mOpusState && |
960 | 0 | serial == mOpusState->mSerial) { |
961 | 0 | startTime = mOpusState->Time(granulepos); |
962 | 0 | MOZ_ASSERT(startTime > 0, "Must have positive start time"); |
963 | 0 | } else if (aType == TrackInfo::kAudioTrack && mFlacState && |
964 | 0 | serial == mFlacState->mSerial) { |
965 | 0 | startTime = mFlacState->Time(granulepos); |
966 | 0 | MOZ_ASSERT(startTime > 0, "Must have positive start time"); |
967 | 0 | } else if (aType == TrackInfo::kVideoTrack && mTheoraState && |
968 | 0 | serial == mTheoraState->mSerial) { |
969 | 0 | startTime = mTheoraState->Time(granulepos); |
970 | 0 | MOZ_ASSERT(startTime > 0, "Must have positive start time"); |
971 | 0 | } else if (mCodecStore.Contains(serial)) { |
972 | 0 | // Stream is not the theora or vorbis stream we're playing, |
973 | 0 | // but is one that we have header data for. |
974 | 0 | startOffset += page.header_len + page.body_len; |
975 | 0 | continue; |
976 | 0 | } else { |
977 | 0 | // Page is for a stream we don't know about (possibly a chained |
978 | 0 | // ogg), return OK to abort the finding any further ranges. This |
979 | 0 | // prevents us searching through the rest of the media when we |
980 | 0 | // may not be able to extract timestamps from it. |
981 | 0 | SetChained(); |
982 | 0 | return buffered; |
983 | 0 | } |
984 | 0 | } |
985 | 0 |
|
986 | 0 | if (startTime != -1) { |
987 | 0 | // We were able to find a start time for that range, see if we can |
988 | 0 | // find an end time. |
989 | 0 | int64_t endTime = RangeEndTime(aType, startOffset, endOffset, true); |
990 | 0 | if (endTime > startTime) { |
991 | 0 | buffered += TimeInterval( |
992 | 0 | TimeUnit::FromMicroseconds(startTime - StartTime()), |
993 | 0 | TimeUnit::FromMicroseconds(endTime - StartTime())); |
994 | 0 | } |
995 | 0 | } |
996 | 0 | } |
997 | 0 |
|
998 | 0 | return buffered; |
999 | 0 | } |
1000 | | |
1001 | | void |
1002 | | OggDemuxer::FindStartTime(int64_t& aOutStartTime) |
1003 | 0 | { |
1004 | 0 | // Extract the start times of the bitstreams in order to calculate |
1005 | 0 | // the duration. |
1006 | 0 | int64_t videoStartTime = INT64_MAX; |
1007 | 0 | int64_t audioStartTime = INT64_MAX; |
1008 | 0 |
|
1009 | 0 | if (HasVideo()) { |
1010 | 0 | FindStartTime(TrackInfo::kVideoTrack, videoStartTime); |
1011 | 0 | if (videoStartTime != INT64_MAX) { |
1012 | 0 | OGG_DEBUG("OggDemuxer::FindStartTime() video=%" PRId64, videoStartTime); |
1013 | 0 | mVideoOggState.mStartTime = |
1014 | 0 | Some(TimeUnit::FromMicroseconds(videoStartTime)); |
1015 | 0 | } |
1016 | 0 | } |
1017 | 0 | if (HasAudio()) { |
1018 | 0 | FindStartTime(TrackInfo::kAudioTrack, audioStartTime); |
1019 | 0 | if (audioStartTime != INT64_MAX) { |
1020 | 0 | OGG_DEBUG("OggDemuxer::FindStartTime() audio=%" PRId64, audioStartTime); |
1021 | 0 | mAudioOggState.mStartTime = |
1022 | 0 | Some(TimeUnit::FromMicroseconds(audioStartTime)); |
1023 | 0 | } |
1024 | 0 | } |
1025 | 0 |
|
1026 | 0 | int64_t startTime = std::min(videoStartTime, audioStartTime); |
1027 | 0 | if (startTime != INT64_MAX) { |
1028 | 0 | aOutStartTime = startTime; |
1029 | 0 | } |
1030 | 0 | } |
1031 | | |
1032 | | void |
1033 | | OggDemuxer::FindStartTime(TrackInfo::TrackType aType, int64_t& aOutStartTime) |
1034 | 0 | { |
1035 | 0 | int64_t startTime = INT64_MAX; |
1036 | 0 |
|
1037 | 0 | OggCodecState* state = GetTrackCodecState(aType); |
1038 | 0 | ogg_packet* pkt = GetNextPacket(aType); |
1039 | 0 | if (pkt) { |
1040 | 0 | startTime = state->PacketStartTime(pkt); |
1041 | 0 | } |
1042 | 0 |
|
1043 | 0 | if (startTime != INT64_MAX) { |
1044 | 0 | aOutStartTime = startTime; |
1045 | 0 | } |
1046 | 0 | } |
1047 | | |
1048 | | nsresult |
1049 | | OggDemuxer::SeekInternal(TrackInfo::TrackType aType, const TimeUnit& aTarget) |
1050 | 0 | { |
1051 | 0 | int64_t target = aTarget.ToMicroseconds(); |
1052 | 0 | OGG_DEBUG("About to seek to %" PRId64, target); |
1053 | 0 | nsresult res; |
1054 | 0 | int64_t adjustedTarget = target; |
1055 | 0 | int64_t startTime = StartTime(aType); |
1056 | 0 | int64_t endTime = mInfo.mMetadataDuration->ToMicroseconds() + startTime; |
1057 | 0 | if (aType == TrackInfo::kAudioTrack && mOpusState){ |
1058 | 0 | adjustedTarget = std::max(startTime, target - OGG_SEEK_OPUS_PREROLL); |
1059 | 0 | } |
1060 | 0 |
|
1061 | 0 | if (!HaveStartTime(aType) || adjustedTarget == startTime) { |
1062 | 0 | // We've seeked to the media start or we can't seek. |
1063 | 0 | // Just seek to the offset of the first content page. |
1064 | 0 | res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, 0); |
1065 | 0 | NS_ENSURE_SUCCESS(res,res); |
1066 | 0 |
|
1067 | 0 | res = Reset(aType); |
1068 | 0 | NS_ENSURE_SUCCESS(res,res); |
1069 | 0 | } else { |
1070 | 0 | // TODO: This may seek back unnecessarily far in the video, but we don't |
1071 | 0 | // have a way of asking Skeleton to seek to a different target for each |
1072 | 0 | // stream yet. Using adjustedTarget here is at least correct, if slow. |
1073 | 0 | IndexedSeekResult sres = SeekToKeyframeUsingIndex(aType, adjustedTarget); |
1074 | 0 | NS_ENSURE_TRUE(sres != SEEK_FATAL_ERROR, NS_ERROR_FAILURE); |
1075 | 0 | if (sres == SEEK_INDEX_FAIL) { |
1076 | 0 | // No index or other non-fatal index-related failure. Try to seek |
1077 | 0 | // using a bisection search. Determine the already downloaded data |
1078 | 0 | // in the media cache, so we can try to seek in the cached data first. |
1079 | 0 | AutoTArray<SeekRange, 16> ranges; |
1080 | 0 | res = GetSeekRanges(aType, ranges); |
1081 | 0 | NS_ENSURE_SUCCESS(res,res); |
1082 | 0 |
|
1083 | 0 | // Figure out if the seek target lies in a buffered range. |
1084 | 0 | SeekRange r = SelectSeekRange(aType, ranges, target, startTime, endTime, true); |
1085 | 0 |
|
1086 | 0 | if (!r.IsNull()) { |
1087 | 0 | // We know the buffered range in which the seek target lies, do a |
1088 | 0 | // bisection search in that buffered range. |
1089 | 0 | res = SeekInBufferedRange(aType, target, adjustedTarget, startTime, endTime, ranges, r); |
1090 | 0 | NS_ENSURE_SUCCESS(res,res); |
1091 | 0 | } else { |
1092 | 0 | // The target doesn't lie in a buffered range. Perform a bisection |
1093 | 0 | // search over the whole media, using the known buffered ranges to |
1094 | 0 | // reduce the search space. |
1095 | 0 | res = SeekInUnbuffered(aType, target, startTime, endTime, ranges); |
1096 | 0 | NS_ENSURE_SUCCESS(res,res); |
1097 | 0 | } |
1098 | 0 | } |
1099 | 0 | } |
1100 | 0 |
|
1101 | 0 | // Demux forwards until we find the first keyframe prior the target. |
1102 | 0 | // there may be non-keyframes in the page before the keyframe. |
1103 | 0 | // Additionally, we may have seeked to the first page referenced by the |
1104 | 0 | // page index which may be quite far off the target. |
1105 | 0 | // When doing fastSeek we display the first frame after the seek, so |
1106 | 0 | // we need to advance the decode to the keyframe otherwise we'll get |
1107 | 0 | // visual artifacts in the first frame output after the seek. |
1108 | 0 | OggCodecState* state = GetTrackCodecState(aType); |
1109 | 0 | OggPacketQueue tempPackets; |
1110 | 0 | bool foundKeyframe = false; |
1111 | 0 | while (true) { |
1112 | 0 | DemuxUntilPacketAvailable(aType, state); |
1113 | 0 | ogg_packet* packet = state->PacketPeek(); |
1114 | 0 | if (packet == nullptr) { |
1115 | 0 | OGG_DEBUG("End of stream reached before keyframe found in indexed seek"); |
1116 | 0 | break; |
1117 | 0 | } |
1118 | 0 | int64_t startTstamp = state->PacketStartTime(packet); |
1119 | 0 | if (foundKeyframe && startTstamp > adjustedTarget) { |
1120 | 0 | break; |
1121 | 0 | } |
1122 | 0 | if (state->IsKeyframe(packet)) { |
1123 | 0 | OGG_DEBUG("keyframe found after seeking at %" PRId64, startTstamp); |
1124 | 0 | tempPackets.Erase(); |
1125 | 0 | foundKeyframe = true; |
1126 | 0 | } |
1127 | 0 | if (foundKeyframe && startTstamp == adjustedTarget) { |
1128 | 0 | break; |
1129 | 0 | } |
1130 | 0 | if (foundKeyframe) { |
1131 | 0 | tempPackets.Append(state->PacketOut()); |
1132 | 0 | } else { |
1133 | 0 | // Discard video packets before the first keyframe. |
1134 | 0 | Unused << state->PacketOut(); |
1135 | 0 | } |
1136 | 0 | } |
1137 | 0 | // Re-add all packet into the codec state in order. |
1138 | 0 | state->PushFront(std::move(tempPackets)); |
1139 | 0 |
|
1140 | 0 | return NS_OK; |
1141 | 0 | } |
1142 | | |
1143 | | OggDemuxer::IndexedSeekResult |
1144 | | OggDemuxer::RollbackIndexedSeek(TrackInfo::TrackType aType, int64_t aOffset) |
1145 | 0 | { |
1146 | 0 | if (mSkeletonState) { |
1147 | 0 | mSkeletonState->Deactivate(); |
1148 | 0 | } |
1149 | 0 | nsresult res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, aOffset); |
1150 | 0 | NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR); |
1151 | 0 | return SEEK_INDEX_FAIL; |
1152 | 0 | } |
1153 | | |
1154 | | OggDemuxer::IndexedSeekResult |
1155 | | OggDemuxer::SeekToKeyframeUsingIndex(TrackInfo::TrackType aType, int64_t aTarget) |
1156 | 0 | { |
1157 | 0 | if (!HasSkeleton() || !mSkeletonState->HasIndex()) { |
1158 | 0 | return SEEK_INDEX_FAIL; |
1159 | 0 | } |
1160 | 0 | // We have an index from the Skeleton track, try to use it to seek. |
1161 | 0 | AutoTArray<uint32_t, 2> tracks; |
1162 | 0 | BuildSerialList(tracks); |
1163 | 0 | SkeletonState::nsSeekTarget keyframe; |
1164 | 0 | if (NS_FAILED(mSkeletonState->IndexedSeekTarget(aTarget, |
1165 | 0 | tracks, |
1166 | 0 | keyframe))) { |
1167 | 0 | // Could not locate a keypoint for the target in the index. |
1168 | 0 | return SEEK_INDEX_FAIL; |
1169 | 0 | } |
1170 | 0 | |
1171 | 0 | // Remember original resource read cursor position so we can rollback on failure. |
1172 | 0 | int64_t tell = Resource(aType)->Tell(); |
1173 | 0 |
|
1174 | 0 | // Seek to the keypoint returned by the index. |
1175 | 0 | if (keyframe.mKeyPoint.mOffset > Resource(aType)->GetLength() || |
1176 | 0 | keyframe.mKeyPoint.mOffset < 0) { |
1177 | 0 | // Index must be invalid. |
1178 | 0 | return RollbackIndexedSeek(aType, tell); |
1179 | 0 | } |
1180 | 0 | OGG_DEBUG("Seeking using index to keyframe at offset %" PRId64 "\n", |
1181 | 0 | keyframe.mKeyPoint.mOffset); |
1182 | 0 | nsresult res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, |
1183 | 0 | keyframe.mKeyPoint.mOffset); |
1184 | 0 | NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR); |
1185 | 0 |
|
1186 | 0 | // We've moved the read set, so reset decode. |
1187 | 0 | res = Reset(aType); |
1188 | 0 | NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR); |
1189 | 0 |
|
1190 | 0 | // Check that the page the index thinks is exactly here is actually exactly |
1191 | 0 | // here. If not, the index is invalid. |
1192 | 0 | ogg_page page; |
1193 | 0 | int skippedBytes = 0; |
1194 | 0 | PageSyncResult syncres = PageSync(Resource(aType), |
1195 | 0 | OggSyncState(aType), |
1196 | 0 | false, |
1197 | 0 | keyframe.mKeyPoint.mOffset, |
1198 | 0 | Resource(aType)->GetLength(), |
1199 | 0 | &page, |
1200 | 0 | skippedBytes); |
1201 | 0 | NS_ENSURE_TRUE(syncres != PAGE_SYNC_ERROR, SEEK_FATAL_ERROR); |
1202 | 0 | if (syncres != PAGE_SYNC_OK || skippedBytes != 0) { |
1203 | 0 | OGG_DEBUG("Indexed-seek failure: Ogg Skeleton Index is invalid " |
1204 | 0 | "or sync error after seek"); |
1205 | 0 | return RollbackIndexedSeek(aType, tell); |
1206 | 0 | } |
1207 | 0 | uint32_t serial = ogg_page_serialno(&page); |
1208 | 0 | if (serial != keyframe.mSerial) { |
1209 | 0 | // Serialno of page at offset isn't what the index told us to expect. |
1210 | 0 | // Assume the index is invalid. |
1211 | 0 | return RollbackIndexedSeek(aType, tell); |
1212 | 0 | } |
1213 | 0 | OggCodecState* codecState = mCodecStore.Get(serial); |
1214 | 0 | if (codecState && codecState->mActive && |
1215 | 0 | ogg_stream_pagein(&codecState->mState, &page) != 0) { |
1216 | 0 | // Couldn't insert page into the ogg resource, or somehow the resource |
1217 | 0 | // is no longer active. |
1218 | 0 | return RollbackIndexedSeek(aType, tell); |
1219 | 0 | } |
1220 | 0 | return SEEK_OK; |
1221 | 0 | } |
1222 | | |
1223 | | // Reads a page from the media resource. |
1224 | | OggDemuxer::PageSyncResult |
1225 | | OggDemuxer::PageSync(MediaResourceIndex* aResource, |
1226 | | ogg_sync_state* aState, |
1227 | | bool aCachedDataOnly, |
1228 | | int64_t aOffset, |
1229 | | int64_t aEndOffset, |
1230 | | ogg_page* aPage, |
1231 | | int& aSkippedBytes) |
1232 | 0 | { |
1233 | 0 | aSkippedBytes = 0; |
1234 | 0 | // Sync to the next page. |
1235 | 0 | int ret = 0; |
1236 | 0 | uint32_t bytesRead = 0; |
1237 | 0 | int64_t readHead = aOffset; |
1238 | 0 | while (ret <= 0) { |
1239 | 0 | ret = ogg_sync_pageseek(aState, aPage); |
1240 | 0 | if (ret == 0) { |
1241 | 0 | char* buffer = ogg_sync_buffer(aState, PAGE_STEP); |
1242 | 0 | MOZ_ASSERT(buffer, "Must have a buffer"); |
1243 | 0 |
|
1244 | 0 | // Read from the file into the buffer |
1245 | 0 | int64_t bytesToRead = std::min(static_cast<int64_t>(PAGE_STEP), |
1246 | 0 | aEndOffset - readHead); |
1247 | 0 | MOZ_ASSERT(bytesToRead <= UINT32_MAX, "bytesToRead range check"); |
1248 | 0 | if (bytesToRead <= 0) { |
1249 | 0 | return PAGE_SYNC_END_OF_RANGE; |
1250 | 0 | } |
1251 | 0 | nsresult rv = NS_OK; |
1252 | 0 | if (aCachedDataOnly) { |
1253 | 0 | rv = aResource->GetResource()->ReadFromCache(buffer, readHead, |
1254 | 0 | static_cast<uint32_t>(bytesToRead)); |
1255 | 0 | NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR); |
1256 | 0 | bytesRead = static_cast<uint32_t>(bytesToRead); |
1257 | 0 | } else { |
1258 | 0 | rv = aResource->Seek(nsISeekableStream::NS_SEEK_SET, readHead); |
1259 | 0 | NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR); |
1260 | 0 | rv = aResource->Read(buffer, |
1261 | 0 | static_cast<uint32_t>(bytesToRead), |
1262 | 0 | &bytesRead); |
1263 | 0 | NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR); |
1264 | 0 | } |
1265 | 0 | if (bytesRead == 0 && NS_SUCCEEDED(rv)) { |
1266 | 0 | // End of file. |
1267 | 0 | return PAGE_SYNC_END_OF_RANGE; |
1268 | 0 | } |
1269 | 0 | readHead += bytesRead; |
1270 | 0 |
|
1271 | 0 | // Update the synchronisation layer with the number |
1272 | 0 | // of bytes written to the buffer |
1273 | 0 | ret = ogg_sync_wrote(aState, bytesRead); |
1274 | 0 | NS_ENSURE_TRUE(ret == 0, PAGE_SYNC_ERROR); |
1275 | 0 | continue; |
1276 | 0 | } |
1277 | 0 | |
1278 | 0 | if (ret < 0) { |
1279 | 0 | MOZ_ASSERT(aSkippedBytes >= 0, "Offset >= 0"); |
1280 | 0 | aSkippedBytes += -ret; |
1281 | 0 | MOZ_ASSERT(aSkippedBytes >= 0, "Offset >= 0"); |
1282 | 0 | continue; |
1283 | 0 | } |
1284 | 0 | } |
1285 | 0 |
|
1286 | 0 | return PAGE_SYNC_OK; |
1287 | 0 | } |
1288 | | |
1289 | | //OggTrackDemuxer |
1290 | | OggTrackDemuxer::OggTrackDemuxer(OggDemuxer* aParent, |
1291 | | TrackInfo::TrackType aType, |
1292 | | uint32_t aTrackNumber) |
1293 | | : mParent(aParent) |
1294 | | , mType(aType) |
1295 | 0 | { |
1296 | 0 | mInfo = mParent->GetTrackInfo(aType, aTrackNumber); |
1297 | 0 | MOZ_ASSERT(mInfo); |
1298 | 0 | } |
1299 | | |
1300 | | OggTrackDemuxer::~OggTrackDemuxer() |
1301 | 0 | { |
1302 | 0 | } |
1303 | | |
1304 | | UniquePtr<TrackInfo> |
1305 | | OggTrackDemuxer::GetInfo() const |
1306 | 0 | { |
1307 | 0 | return mInfo->Clone(); |
1308 | 0 | } |
1309 | | |
1310 | | RefPtr<OggTrackDemuxer::SeekPromise> |
1311 | | OggTrackDemuxer::Seek(const TimeUnit& aTime) |
1312 | 0 | { |
1313 | 0 | // Seeks to aTime. Upon success, SeekPromise will be resolved with the |
1314 | 0 | // actual time seeked to. Typically the random access point time |
1315 | 0 | mQueuedSample = nullptr; |
1316 | 0 | TimeUnit seekTime = aTime; |
1317 | 0 | if (mParent->SeekInternal(mType, aTime) == NS_OK) { |
1318 | 0 | RefPtr<MediaRawData> sample(NextSample()); |
1319 | 0 |
|
1320 | 0 | // Check what time we actually seeked to. |
1321 | 0 | if (sample != nullptr) { |
1322 | 0 | seekTime = sample->mTime; |
1323 | 0 | OGG_DEBUG("%p seeked to time %" PRId64, this, seekTime.ToMicroseconds()); |
1324 | 0 | } |
1325 | 0 | mQueuedSample = sample; |
1326 | 0 |
|
1327 | 0 | return SeekPromise::CreateAndResolve(seekTime, __func__); |
1328 | 0 | } else { |
1329 | 0 | return SeekPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, __func__); |
1330 | 0 | } |
1331 | 0 | } |
1332 | | |
1333 | | RefPtr<MediaRawData> |
1334 | | OggTrackDemuxer::NextSample() |
1335 | 0 | { |
1336 | 0 | if (mQueuedSample) { |
1337 | 0 | RefPtr<MediaRawData> nextSample = mQueuedSample; |
1338 | 0 | mQueuedSample = nullptr; |
1339 | 0 | if (mType == TrackInfo::kAudioTrack) { |
1340 | 0 | nextSample->mTrackInfo = mParent->mSharedAudioTrackInfo; |
1341 | 0 | } |
1342 | 0 | return nextSample; |
1343 | 0 | } |
1344 | 0 | ogg_packet* packet = mParent->GetNextPacket(mType); |
1345 | 0 | if (!packet) { |
1346 | 0 | return nullptr; |
1347 | 0 | } |
1348 | 0 | // Check the eos state in case we need to look for chained streams. |
1349 | 0 | bool eos = packet->e_o_s; |
1350 | 0 | OggCodecState* state = mParent->GetTrackCodecState(mType); |
1351 | 0 | RefPtr<MediaRawData> data = state->PacketOutAsMediaRawData(); |
1352 | 0 | if (!data) { |
1353 | 0 | return nullptr; |
1354 | 0 | } |
1355 | 0 | if (mType == TrackInfo::kAudioTrack) { |
1356 | 0 | data->mTrackInfo = mParent->mSharedAudioTrackInfo; |
1357 | 0 | } |
1358 | 0 | // mDecodedAudioDuration gets adjusted during ReadOggChain(). |
1359 | 0 | TimeUnit totalDuration = mParent->mDecodedAudioDuration; |
1360 | 0 | if (eos) { |
1361 | 0 | // We've encountered an end of bitstream packet; check for a chained |
1362 | 0 | // bitstream following this one. |
1363 | 0 | // This will also update mSharedAudioTrackInfo. |
1364 | 0 | mParent->ReadOggChain(data->GetEndTime()); |
1365 | 0 | } |
1366 | 0 | data->mOffset = mParent->Resource(mType)->Tell(); |
1367 | 0 | // We adjust the start time of the sample to account for the potential ogg chaining. |
1368 | 0 | data->mTime += totalDuration; |
1369 | 0 | return data; |
1370 | 0 | } |
1371 | | |
1372 | | RefPtr<OggTrackDemuxer::SamplesPromise> |
1373 | | OggTrackDemuxer::GetSamples(int32_t aNumSamples) |
1374 | 0 | { |
1375 | 0 | RefPtr<SamplesHolder> samples = new SamplesHolder; |
1376 | 0 | if (!aNumSamples) { |
1377 | 0 | return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, __func__); |
1378 | 0 | } |
1379 | 0 | |
1380 | 0 | while (aNumSamples) { |
1381 | 0 | RefPtr<MediaRawData> sample(NextSample()); |
1382 | 0 | if (!sample) { |
1383 | 0 | break; |
1384 | 0 | } |
1385 | 0 | samples->mSamples.AppendElement(sample); |
1386 | 0 | aNumSamples--; |
1387 | 0 | } |
1388 | 0 |
|
1389 | 0 | if (samples->mSamples.IsEmpty()) { |
1390 | 0 | return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__); |
1391 | 0 | } else { |
1392 | 0 | return SamplesPromise::CreateAndResolve(samples, __func__); |
1393 | 0 | } |
1394 | 0 | } |
1395 | | |
1396 | | void |
1397 | | OggTrackDemuxer::Reset() |
1398 | 0 | { |
1399 | 0 | mParent->Reset(mType); |
1400 | 0 | mQueuedSample = nullptr; |
1401 | 0 | } |
1402 | | |
1403 | | RefPtr<OggTrackDemuxer::SkipAccessPointPromise> |
1404 | | OggTrackDemuxer::SkipToNextRandomAccessPoint(const TimeUnit& aTimeThreshold) |
1405 | 0 | { |
1406 | 0 | uint32_t parsed = 0; |
1407 | 0 | bool found = false; |
1408 | 0 | RefPtr<MediaRawData> sample; |
1409 | 0 |
|
1410 | 0 | OGG_DEBUG("TimeThreshold: %f", aTimeThreshold.ToSeconds()); |
1411 | 0 | while (!found && (sample = NextSample())) { |
1412 | 0 | parsed++; |
1413 | 0 | if (sample->mKeyframe && sample->mTime >= aTimeThreshold) { |
1414 | 0 | found = true; |
1415 | 0 | mQueuedSample = sample; |
1416 | 0 | } |
1417 | 0 | } |
1418 | 0 | if (found) { |
1419 | 0 | OGG_DEBUG("next sample: %f (parsed: %d)", |
1420 | 0 | sample->mTime.ToSeconds(), parsed); |
1421 | 0 | return SkipAccessPointPromise::CreateAndResolve(parsed, __func__); |
1422 | 0 | } else { |
1423 | 0 | SkipFailureHolder failure(NS_ERROR_DOM_MEDIA_END_OF_STREAM, parsed); |
1424 | 0 | return SkipAccessPointPromise::CreateAndReject(std::move(failure), __func__); |
1425 | 0 | } |
1426 | 0 | } |
1427 | | |
1428 | | TimeIntervals |
1429 | | OggTrackDemuxer::GetBuffered() |
1430 | 0 | { |
1431 | 0 | return mParent->GetBuffered(mType); |
1432 | 0 | } |
1433 | | |
1434 | | void |
1435 | | OggTrackDemuxer::BreakCycles() |
1436 | 0 | { |
1437 | 0 | mParent = nullptr; |
1438 | 0 | } |
1439 | | |
1440 | | |
1441 | | // Returns an ogg page's checksum. |
1442 | | ogg_uint32_t |
1443 | | OggDemuxer::GetPageChecksum(ogg_page* page) |
1444 | 0 | { |
1445 | 0 | if (page == 0 || page->header == 0 || page->header_len < 25) { |
1446 | 0 | return 0; |
1447 | 0 | } |
1448 | 0 | const unsigned char* p = page->header + 22; |
1449 | 0 | uint32_t c = p[0] + (p[1] << 8) + (p[2] << 16) + (p[3] << 24); |
1450 | 0 | return c; |
1451 | 0 | } |
1452 | | |
1453 | | int64_t |
1454 | | OggDemuxer::RangeStartTime(TrackInfo::TrackType aType, int64_t aOffset) |
1455 | 0 | { |
1456 | 0 | int64_t position = Resource(aType)->Tell(); |
1457 | 0 | nsresult res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, aOffset); |
1458 | 0 | NS_ENSURE_SUCCESS(res, 0); |
1459 | 0 | int64_t startTime = 0; |
1460 | 0 | FindStartTime(aType, startTime); |
1461 | 0 | res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, position); |
1462 | 0 | NS_ENSURE_SUCCESS(res, -1); |
1463 | 0 | return startTime; |
1464 | 0 | } |
1465 | | |
1466 | | struct nsDemuxerAutoOggSyncState |
1467 | | { |
1468 | | nsDemuxerAutoOggSyncState() |
1469 | 0 | { |
1470 | 0 | ogg_sync_init(&mState); |
1471 | 0 | } |
1472 | | ~nsDemuxerAutoOggSyncState() |
1473 | 0 | { |
1474 | 0 | ogg_sync_clear(&mState); |
1475 | 0 | } |
1476 | | ogg_sync_state mState; |
1477 | | }; |
1478 | | |
1479 | | int64_t |
1480 | | OggDemuxer::RangeEndTime(TrackInfo::TrackType aType, int64_t aEndOffset) |
1481 | 0 | { |
1482 | 0 | int64_t position = Resource(aType)->Tell(); |
1483 | 0 | int64_t endTime = RangeEndTime(aType, 0, aEndOffset, false); |
1484 | 0 | nsresult res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, position); |
1485 | 0 | NS_ENSURE_SUCCESS(res, -1); |
1486 | 0 | return endTime; |
1487 | 0 | } |
1488 | | |
1489 | | int64_t |
1490 | | OggDemuxer::RangeEndTime(TrackInfo::TrackType aType, |
1491 | | int64_t aStartOffset, |
1492 | | int64_t aEndOffset, |
1493 | | bool aCachedDataOnly) |
1494 | 0 | { |
1495 | 0 | nsDemuxerAutoOggSyncState sync; |
1496 | 0 |
|
1497 | 0 | // We need to find the last page which ends before aEndOffset that |
1498 | 0 | // has a granulepos that we can convert to a timestamp. We do this by |
1499 | 0 | // backing off from aEndOffset until we encounter a page on which we can |
1500 | 0 | // interpret the granulepos. If while backing off we encounter a page which |
1501 | 0 | // we've previously encountered before, we'll either backoff again if we |
1502 | 0 | // haven't found an end time yet, or return the last end time found. |
1503 | 0 | const int step = 5000; |
1504 | 0 | const int maxOggPageSize = 65306; |
1505 | 0 | int64_t readStartOffset = aEndOffset; |
1506 | 0 | int64_t readLimitOffset = aEndOffset; |
1507 | 0 | int64_t readHead = aEndOffset; |
1508 | 0 | int64_t endTime = -1; |
1509 | 0 | uint32_t checksumAfterSeek = 0; |
1510 | 0 | uint32_t prevChecksumAfterSeek = 0; |
1511 | 0 | bool mustBackOff = false; |
1512 | 0 | while (true) { |
1513 | 0 | ogg_page page; |
1514 | 0 | int ret = ogg_sync_pageseek(&sync.mState, &page); |
1515 | 0 | if (ret == 0) { |
1516 | 0 | // We need more data if we've not encountered a page we've seen before, |
1517 | 0 | // or we've read to the end of file. |
1518 | 0 | if (mustBackOff || readHead == aEndOffset || readHead == aStartOffset) { |
1519 | 0 | if (endTime != -1 || readStartOffset == 0) { |
1520 | 0 | // We have encountered a page before, or we're at the end of file. |
1521 | 0 | break; |
1522 | 0 | } |
1523 | 0 | mustBackOff = false; |
1524 | 0 | prevChecksumAfterSeek = checksumAfterSeek; |
1525 | 0 | checksumAfterSeek = 0; |
1526 | 0 | ogg_sync_reset(&sync.mState); |
1527 | 0 | readStartOffset = std::max(static_cast<int64_t>(0), readStartOffset - step); |
1528 | 0 | // There's no point reading more than the maximum size of |
1529 | 0 | // an Ogg page into data we've previously scanned. Any data |
1530 | 0 | // between readLimitOffset and aEndOffset must be garbage |
1531 | 0 | // and we can ignore it thereafter. |
1532 | 0 | readLimitOffset = std::min(readLimitOffset, |
1533 | 0 | readStartOffset + maxOggPageSize); |
1534 | 0 | readHead = std::max(aStartOffset, readStartOffset); |
1535 | 0 | } |
1536 | 0 |
|
1537 | 0 | int64_t limit = std::min(static_cast<int64_t>(UINT32_MAX), |
1538 | 0 | aEndOffset - readHead); |
1539 | 0 | limit = std::max(static_cast<int64_t>(0), limit); |
1540 | 0 | limit = std::min(limit, static_cast<int64_t>(step)); |
1541 | 0 | uint32_t bytesToRead = static_cast<uint32_t>(limit); |
1542 | 0 | uint32_t bytesRead = 0; |
1543 | 0 | char* buffer = ogg_sync_buffer(&sync.mState, bytesToRead); |
1544 | 0 | MOZ_ASSERT(buffer, "Must have buffer"); |
1545 | 0 | nsresult res; |
1546 | 0 | if (aCachedDataOnly) { |
1547 | 0 | res = Resource(aType)->GetResource()->ReadFromCache(buffer, readHead, bytesToRead); |
1548 | 0 | NS_ENSURE_SUCCESS(res, -1); |
1549 | 0 | bytesRead = bytesToRead; |
1550 | 0 | } else { |
1551 | 0 | MOZ_ASSERT(readHead < aEndOffset, |
1552 | 0 | "resource pos must be before range end"); |
1553 | 0 | res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, readHead); |
1554 | 0 | NS_ENSURE_SUCCESS(res, -1); |
1555 | 0 | res = Resource(aType)->Read(buffer, bytesToRead, &bytesRead); |
1556 | 0 | NS_ENSURE_SUCCESS(res, -1); |
1557 | 0 | } |
1558 | 0 | readHead += bytesRead; |
1559 | 0 | if (readHead > readLimitOffset) { |
1560 | 0 | mustBackOff = true; |
1561 | 0 | } |
1562 | 0 |
|
1563 | 0 | // Update the synchronisation layer with the number |
1564 | 0 | // of bytes written to the buffer |
1565 | 0 | ret = ogg_sync_wrote(&sync.mState, bytesRead); |
1566 | 0 | if (ret != 0) { |
1567 | 0 | endTime = -1; |
1568 | 0 | break; |
1569 | 0 | } |
1570 | 0 | continue; |
1571 | 0 | } |
1572 | 0 | |
1573 | 0 | if (ret < 0 || ogg_page_granulepos(&page) < 0) { |
1574 | 0 | continue; |
1575 | 0 | } |
1576 | 0 | |
1577 | 0 | uint32_t checksum = GetPageChecksum(&page); |
1578 | 0 | if (checksumAfterSeek == 0) { |
1579 | 0 | // This is the first page we've decoded after a backoff/seek. Remember |
1580 | 0 | // the page checksum. If we backoff further and encounter this page |
1581 | 0 | // again, we'll know that we won't find a page with an end time after |
1582 | 0 | // this one, so we'll know to back off again. |
1583 | 0 | checksumAfterSeek = checksum; |
1584 | 0 | } |
1585 | 0 | if (checksum == prevChecksumAfterSeek) { |
1586 | 0 | // This page has the same checksum as the first page we encountered |
1587 | 0 | // after the last backoff/seek. Since we've already scanned after this |
1588 | 0 | // page and failed to find an end time, we may as well backoff again and |
1589 | 0 | // try to find an end time from an earlier page. |
1590 | 0 | mustBackOff = true; |
1591 | 0 | continue; |
1592 | 0 | } |
1593 | 0 | |
1594 | 0 | int64_t granulepos = ogg_page_granulepos(&page); |
1595 | 0 | int serial = ogg_page_serialno(&page); |
1596 | 0 |
|
1597 | 0 | OggCodecState* codecState = nullptr; |
1598 | 0 | codecState = mCodecStore.Get(serial); |
1599 | 0 | if (!codecState) { |
1600 | 0 | // This page is from a bitstream which we haven't encountered yet. |
1601 | 0 | // It's probably from a new "link" in a "chained" ogg. Don't |
1602 | 0 | // bother even trying to find a duration... |
1603 | 0 | SetChained(); |
1604 | 0 | endTime = -1; |
1605 | 0 | break; |
1606 | 0 | } |
1607 | 0 | |
1608 | 0 | int64_t t = codecState->Time(granulepos); |
1609 | 0 | if (t != -1) { |
1610 | 0 | endTime = t; |
1611 | 0 | } |
1612 | 0 | } |
1613 | 0 |
|
1614 | 0 | return endTime; |
1615 | 0 | } |
1616 | | |
1617 | | nsresult |
1618 | | OggDemuxer::GetSeekRanges(TrackInfo::TrackType aType, |
1619 | | nsTArray<SeekRange>& aRanges) |
1620 | 0 | { |
1621 | 0 | AutoPinned<MediaResource> resource(Resource(aType)->GetResource()); |
1622 | 0 | MediaByteRangeSet cached; |
1623 | 0 | nsresult res = resource->GetCachedRanges(cached); |
1624 | 0 | NS_ENSURE_SUCCESS(res, res); |
1625 | 0 |
|
1626 | 0 | for (uint32_t index = 0; index < cached.Length(); index++) { |
1627 | 0 | auto& range = cached[index]; |
1628 | 0 | int64_t startTime = -1; |
1629 | 0 | int64_t endTime = -1; |
1630 | 0 | if (NS_FAILED(Reset(aType))) { |
1631 | 0 | return NS_ERROR_FAILURE; |
1632 | 0 | } |
1633 | 0 | int64_t startOffset = range.mStart; |
1634 | 0 | int64_t endOffset = range.mEnd; |
1635 | 0 | startTime = RangeStartTime(aType, startOffset); |
1636 | 0 | if (startTime != -1 && |
1637 | 0 | ((endTime = RangeEndTime(aType, endOffset)) != -1)) { |
1638 | 0 | NS_WARNING_ASSERTION(startTime < endTime, |
1639 | 0 | "Start time must be before end time"); |
1640 | 0 | aRanges.AppendElement(SeekRange(startOffset, |
1641 | 0 | endOffset, |
1642 | 0 | startTime, |
1643 | 0 | endTime)); |
1644 | 0 | } |
1645 | 0 | } |
1646 | 0 | if (NS_FAILED(Reset(aType))) { |
1647 | 0 | return NS_ERROR_FAILURE; |
1648 | 0 | } |
1649 | 0 | return NS_OK; |
1650 | 0 | } |
1651 | | |
1652 | | OggDemuxer::SeekRange |
1653 | | OggDemuxer::SelectSeekRange(TrackInfo::TrackType aType, |
1654 | | const nsTArray<SeekRange>& ranges, |
1655 | | int64_t aTarget, |
1656 | | int64_t aStartTime, |
1657 | | int64_t aEndTime, |
1658 | | bool aExact) |
1659 | 0 | { |
1660 | 0 | int64_t so = 0; |
1661 | 0 | int64_t eo = Resource(aType)->GetLength(); |
1662 | 0 | int64_t st = aStartTime; |
1663 | 0 | int64_t et = aEndTime; |
1664 | 0 | for (uint32_t i = 0; i < ranges.Length(); i++) { |
1665 | 0 | const SeekRange& r = ranges[i]; |
1666 | 0 | if (r.mTimeStart < aTarget) { |
1667 | 0 | so = r.mOffsetStart; |
1668 | 0 | st = r.mTimeStart; |
1669 | 0 | } |
1670 | 0 | if (r.mTimeEnd >= aTarget && r.mTimeEnd < et) { |
1671 | 0 | eo = r.mOffsetEnd; |
1672 | 0 | et = r.mTimeEnd; |
1673 | 0 | } |
1674 | 0 |
|
1675 | 0 | if (r.mTimeStart < aTarget && aTarget <= r.mTimeEnd) { |
1676 | 0 | // Target lies exactly in this range. |
1677 | 0 | return ranges[i]; |
1678 | 0 | } |
1679 | 0 | } |
1680 | 0 | if (aExact || eo == -1) { |
1681 | 0 | return SeekRange(); |
1682 | 0 | } |
1683 | 0 | return SeekRange(so, eo, st, et); |
1684 | 0 | } |
1685 | | |
1686 | | |
1687 | | nsresult |
1688 | | OggDemuxer::SeekInBufferedRange(TrackInfo::TrackType aType, |
1689 | | int64_t aTarget, |
1690 | | int64_t aAdjustedTarget, |
1691 | | int64_t aStartTime, |
1692 | | int64_t aEndTime, |
1693 | | const nsTArray<SeekRange>& aRanges, |
1694 | | const SeekRange& aRange) |
1695 | 0 | { |
1696 | 0 | OGG_DEBUG("Seeking in buffered data to %" PRId64 " using bisection search", aTarget); |
1697 | 0 | if (aType == TrackInfo::kVideoTrack || aAdjustedTarget >= aTarget) { |
1698 | 0 | // We know the exact byte range in which the target must lie. It must |
1699 | 0 | // be buffered in the media cache. Seek there. |
1700 | 0 | nsresult res = SeekBisection(aType, aTarget, aRange, 0); |
1701 | 0 | if (NS_FAILED(res) || aType != TrackInfo::kVideoTrack) { |
1702 | 0 | return res; |
1703 | 0 | } |
1704 | 0 | |
1705 | 0 | // We have an active Theora bitstream. Peek the next Theora frame, and |
1706 | 0 | // extract its keyframe's time. |
1707 | 0 | DemuxUntilPacketAvailable(aType, mTheoraState); |
1708 | 0 | ogg_packet* packet = mTheoraState->PacketPeek(); |
1709 | 0 | if (packet && !mTheoraState->IsKeyframe(packet)) { |
1710 | 0 | // First post-seek frame isn't a keyframe, seek back to previous keyframe, |
1711 | 0 | // otherwise we'll get visual artifacts. |
1712 | 0 | MOZ_ASSERT(packet->granulepos != -1, "Must have a granulepos"); |
1713 | 0 | int shift = mTheoraState->KeyFrameGranuleJobs(); |
1714 | 0 | int64_t keyframeGranulepos = (packet->granulepos >> shift) << shift; |
1715 | 0 | int64_t keyframeTime = mTheoraState->StartTime(keyframeGranulepos); |
1716 | 0 | SEEK_LOG(LogLevel::Debug, |
1717 | 0 | ("Keyframe for %lld is at %lld, seeking back to it", frameTime, |
1718 | 0 | keyframeTime)); |
1719 | 0 | aAdjustedTarget = std::min(aAdjustedTarget, keyframeTime); |
1720 | 0 | } |
1721 | 0 | } |
1722 | 0 |
|
1723 | 0 | nsresult res = NS_OK; |
1724 | 0 | if (aAdjustedTarget < aTarget) { |
1725 | 0 | SeekRange k = SelectSeekRange(aType, |
1726 | 0 | aRanges, |
1727 | 0 | aAdjustedTarget, |
1728 | 0 | aStartTime, |
1729 | 0 | aEndTime, |
1730 | 0 | false); |
1731 | 0 | res = SeekBisection(aType, aAdjustedTarget, k, OGG_SEEK_FUZZ_USECS); |
1732 | 0 | } |
1733 | 0 | return res; |
1734 | 0 | } |
1735 | | |
1736 | | nsresult |
1737 | | OggDemuxer::SeekInUnbuffered(TrackInfo::TrackType aType, |
1738 | | int64_t aTarget, |
1739 | | int64_t aStartTime, |
1740 | | int64_t aEndTime, |
1741 | | const nsTArray<SeekRange>& aRanges) |
1742 | 0 | { |
1743 | 0 | OGG_DEBUG("Seeking in unbuffered data to %" PRId64 " using bisection search", aTarget); |
1744 | 0 |
|
1745 | 0 | // If we've got an active Theora bitstream, determine the maximum possible |
1746 | 0 | // time in usecs which a keyframe could be before a given interframe. We |
1747 | 0 | // subtract this from our seek target, seek to the new target, and then |
1748 | 0 | // will decode forward to the original seek target. We should encounter a |
1749 | 0 | // keyframe in that interval. This prevents us from needing to run two |
1750 | 0 | // bisections; one for the seek target frame, and another to find its |
1751 | 0 | // keyframe. It's usually faster to just download this extra data, rather |
1752 | 0 | // tham perform two bisections to find the seek target's keyframe. We |
1753 | 0 | // don't do this offsetting when seeking in a buffered range, |
1754 | 0 | // as the extra decoding causes a noticeable speed hit when all the data |
1755 | 0 | // is buffered (compared to just doing a bisection to exactly find the |
1756 | 0 | // keyframe). |
1757 | 0 | int64_t keyframeOffsetMs = 0; |
1758 | 0 | if (aType == TrackInfo::kVideoTrack && mTheoraState) { |
1759 | 0 | keyframeOffsetMs = mTheoraState->MaxKeyframeOffset(); |
1760 | 0 | } |
1761 | 0 | // Add in the Opus pre-roll if necessary, as well. |
1762 | 0 | if (aType == TrackInfo::kAudioTrack && mOpusState) { |
1763 | 0 | keyframeOffsetMs = std::max(keyframeOffsetMs, OGG_SEEK_OPUS_PREROLL); |
1764 | 0 | } |
1765 | 0 | int64_t seekTarget = std::max(aStartTime, aTarget - keyframeOffsetMs); |
1766 | 0 | // Minimize the bisection search space using the known timestamps from the |
1767 | 0 | // buffered ranges. |
1768 | 0 | SeekRange k = |
1769 | 0 | SelectSeekRange(aType, aRanges, seekTarget, aStartTime, aEndTime, false); |
1770 | 0 | return SeekBisection(aType, seekTarget, k, OGG_SEEK_FUZZ_USECS); |
1771 | 0 | } |
1772 | | |
1773 | | nsresult |
1774 | | OggDemuxer::SeekBisection(TrackInfo::TrackType aType, |
1775 | | int64_t aTarget, |
1776 | | const SeekRange& aRange, |
1777 | | uint32_t aFuzz) |
1778 | 0 | { |
1779 | 0 | nsresult res; |
1780 | 0 |
|
1781 | 0 | if (aTarget <= aRange.mTimeStart) { |
1782 | 0 | if (NS_FAILED(Reset(aType))) { |
1783 | 0 | return NS_ERROR_FAILURE; |
1784 | 0 | } |
1785 | 0 | res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, 0); |
1786 | 0 | NS_ENSURE_SUCCESS(res,res); |
1787 | 0 | return NS_OK; |
1788 | 0 | } |
1789 | 0 | |
1790 | 0 | // Bisection search, find start offset of last page with end time less than |
1791 | 0 | // the seek target. |
1792 | 0 | ogg_int64_t startOffset = aRange.mOffsetStart; |
1793 | 0 | ogg_int64_t startTime = aRange.mTimeStart; |
1794 | 0 | ogg_int64_t startLength = 0; // Length of the page at startOffset. |
1795 | 0 | ogg_int64_t endOffset = aRange.mOffsetEnd; |
1796 | 0 | ogg_int64_t endTime = aRange.mTimeEnd; |
1797 | 0 |
|
1798 | 0 | ogg_int64_t seekTarget = aTarget; |
1799 | 0 | int64_t seekLowerBound = std::max(static_cast<int64_t>(0), aTarget - aFuzz); |
1800 | 0 | int hops = 0; |
1801 | 0 | DebugOnly<ogg_int64_t> previousGuess = -1; |
1802 | 0 | int backsteps = 0; |
1803 | 0 | const int maxBackStep = 10; |
1804 | 0 | MOZ_ASSERT(static_cast<uint64_t>(PAGE_STEP) * pow(2.0, maxBackStep) < INT32_MAX, |
1805 | 0 | "Backstep calculation must not overflow"); |
1806 | 0 |
|
1807 | 0 | // Seek via bisection search. Loop until we find the offset where the page |
1808 | 0 | // before the offset is before the seek target, and the page after the offset |
1809 | 0 | // is after the seek target. |
1810 | 0 | while (true) { |
1811 | 0 | ogg_int64_t duration = 0; |
1812 | 0 | double target = 0; |
1813 | 0 | ogg_int64_t interval = 0; |
1814 | 0 | ogg_int64_t guess = 0; |
1815 | 0 | ogg_page page; |
1816 | 0 | int skippedBytes = 0; |
1817 | 0 | ogg_int64_t pageOffset = 0; |
1818 | 0 | ogg_int64_t pageLength = 0; |
1819 | 0 | ogg_int64_t granuleTime = -1; |
1820 | 0 | bool mustBackoff = false; |
1821 | 0 |
|
1822 | 0 | // Guess where we should bisect to, based on the bit rate and the time |
1823 | 0 | // remaining in the interval. Loop until we can determine the time at |
1824 | 0 | // the guess offset. |
1825 | 0 | while (true) { |
1826 | 0 |
|
1827 | 0 | // Discard any previously buffered packets/pages. |
1828 | 0 | if (NS_FAILED(Reset(aType))) { |
1829 | 0 | return NS_ERROR_FAILURE; |
1830 | 0 | } |
1831 | 0 | |
1832 | 0 | interval = endOffset - startOffset - startLength; |
1833 | 0 | if (interval == 0) { |
1834 | 0 | // Our interval is empty, we've found the optimal seek point, as the |
1835 | 0 | // page at the start offset is before the seek target, and the page |
1836 | 0 | // at the end offset is after the seek target. |
1837 | 0 | SEEK_LOG(LogLevel::Debug, ("Interval narrowed, terminating bisection.")); |
1838 | 0 | break; |
1839 | 0 | } |
1840 | 0 | |
1841 | 0 | // Guess bisection point. |
1842 | 0 | duration = endTime - startTime; |
1843 | 0 | target = (double)(seekTarget - startTime) / (double)duration; |
1844 | 0 | guess = startOffset + startLength + |
1845 | 0 | static_cast<ogg_int64_t>((double)interval * target); |
1846 | 0 | guess = std::min(guess, endOffset - PAGE_STEP); |
1847 | 0 | if (mustBackoff) { |
1848 | 0 | // We previously failed to determine the time at the guess offset, |
1849 | 0 | // probably because we ran out of data to decode. This usually happens |
1850 | 0 | // when we guess very close to the end offset. So reduce the guess |
1851 | 0 | // offset using an exponential backoff until we determine the time. |
1852 | 0 | SEEK_LOG(LogLevel::Debug, ("Backing off %d bytes, backsteps=%d", |
1853 | 0 | static_cast<int32_t>(PAGE_STEP * pow(2.0, backsteps)), backsteps)); |
1854 | 0 | guess -= PAGE_STEP * static_cast<ogg_int64_t>(pow(2.0, backsteps)); |
1855 | 0 |
|
1856 | 0 | if (guess <= startOffset) { |
1857 | 0 | // We've tried to backoff to before the start offset of our seek |
1858 | 0 | // range. This means we couldn't find a seek termination position |
1859 | 0 | // near the end of the seek range, so just set the seek termination |
1860 | 0 | // condition, and break out of the bisection loop. We'll begin |
1861 | 0 | // decoding from the start of the seek range. |
1862 | 0 | interval = 0; |
1863 | 0 | break; |
1864 | 0 | } |
1865 | 0 | |
1866 | 0 | backsteps = std::min(backsteps + 1, maxBackStep); |
1867 | 0 | // We reset mustBackoff. If we still need to backoff further, it will |
1868 | 0 | // be set to true again. |
1869 | 0 | mustBackoff = false; |
1870 | 0 | } else { |
1871 | 0 | backsteps = 0; |
1872 | 0 | } |
1873 | 0 | guess = std::max(guess, startOffset + startLength); |
1874 | 0 |
|
1875 | 0 | SEEK_LOG(LogLevel::Debug, ("Seek loop start[o=%lld..%lld t=%lld] " |
1876 | 0 | "end[o=%lld t=%lld] " |
1877 | 0 | "interval=%lld target=%lf guess=%lld", |
1878 | 0 | startOffset, (startOffset+startLength), startTime, |
1879 | 0 | endOffset, endTime, interval, target, guess)); |
1880 | 0 |
|
1881 | 0 | MOZ_ASSERT(guess >= startOffset + startLength, "Guess must be after range start"); |
1882 | 0 | MOZ_ASSERT(guess < endOffset, "Guess must be before range end"); |
1883 | 0 | MOZ_ASSERT(guess != previousGuess, "Guess should be different to previous"); |
1884 | 0 | previousGuess = guess; |
1885 | 0 |
|
1886 | 0 | hops++; |
1887 | 0 |
|
1888 | 0 | // Locate the next page after our seek guess, and then figure out the |
1889 | 0 | // granule time of the audio and video bitstreams there. We can then |
1890 | 0 | // make a bisection decision based on our location in the media. |
1891 | 0 | PageSyncResult pageSyncResult = PageSync(Resource(aType), |
1892 | 0 | OggSyncState(aType), |
1893 | 0 | false, |
1894 | 0 | guess, |
1895 | 0 | endOffset, |
1896 | 0 | &page, |
1897 | 0 | skippedBytes); |
1898 | 0 | NS_ENSURE_TRUE(pageSyncResult != PAGE_SYNC_ERROR, NS_ERROR_FAILURE); |
1899 | 0 |
|
1900 | 0 | if (pageSyncResult == PAGE_SYNC_END_OF_RANGE) { |
1901 | 0 | // Our guess was too close to the end, we've ended up reading the end |
1902 | 0 | // page. Backoff exponentially from the end point, in case the last |
1903 | 0 | // page/frame/sample is huge. |
1904 | 0 | mustBackoff = true; |
1905 | 0 | SEEK_LOG(LogLevel::Debug, ("Hit the end of range, backing off")); |
1906 | 0 | continue; |
1907 | 0 | } |
1908 | 0 | |
1909 | 0 | // We've located a page of length |ret| at |guess + skippedBytes|. |
1910 | 0 | // Remember where the page is located. |
1911 | 0 | pageOffset = guess + skippedBytes; |
1912 | 0 | pageLength = page.header_len + page.body_len; |
1913 | 0 |
|
1914 | 0 | // Read pages until we can determine the granule time of the audio and |
1915 | 0 | // video bitstream. |
1916 | 0 | ogg_int64_t audioTime = -1; |
1917 | 0 | ogg_int64_t videoTime = -1; |
1918 | 0 | do { |
1919 | 0 | // Add the page to its codec state, determine its granule time. |
1920 | 0 | uint32_t serial = ogg_page_serialno(&page); |
1921 | 0 | OggCodecState* codecState = mCodecStore.Get(serial); |
1922 | 0 | if (codecState && GetCodecStateType(codecState) == aType) { |
1923 | 0 | if (codecState->mActive) { |
1924 | 0 | int ret = ogg_stream_pagein(&codecState->mState, &page); |
1925 | 0 | NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE); |
1926 | 0 | } |
1927 | 0 |
|
1928 | 0 | ogg_int64_t granulepos = ogg_page_granulepos(&page); |
1929 | 0 |
|
1930 | 0 | if (aType == TrackInfo::kAudioTrack && |
1931 | 0 | granulepos > 0 && audioTime == -1) { |
1932 | 0 | if (mVorbisState && serial == mVorbisState->mSerial) { |
1933 | 0 | audioTime = mVorbisState->Time(granulepos); |
1934 | 0 | } else if (mOpusState && serial == mOpusState->mSerial) { |
1935 | 0 | audioTime = mOpusState->Time(granulepos); |
1936 | 0 | } else if (mFlacState && serial == mFlacState->mSerial) { |
1937 | 0 | audioTime = mFlacState->Time(granulepos); |
1938 | 0 | } |
1939 | 0 | } |
1940 | 0 |
|
1941 | 0 | if (aType == TrackInfo::kVideoTrack && |
1942 | 0 | granulepos > 0 && serial == mTheoraState->mSerial && |
1943 | 0 | videoTime == -1) { |
1944 | 0 | videoTime = mTheoraState->Time(granulepos); |
1945 | 0 | } |
1946 | 0 |
|
1947 | 0 | if (pageOffset + pageLength >= endOffset) { |
1948 | 0 | // Hit end of readable data. |
1949 | 0 | break; |
1950 | 0 | } |
1951 | 0 | } |
1952 | 0 | if (!ReadOggPage(aType, &page)) { |
1953 | 0 | break; |
1954 | 0 | } |
1955 | 0 | |
1956 | 0 | } while ((aType == TrackInfo::kAudioTrack && audioTime == -1) || |
1957 | 0 | (aType == TrackInfo::kVideoTrack && videoTime == -1)); |
1958 | 0 |
|
1959 | 0 |
|
1960 | 0 | if ((aType == TrackInfo::kAudioTrack && audioTime == -1) || |
1961 | 0 | (aType == TrackInfo::kVideoTrack && videoTime == -1)) { |
1962 | 0 | // We don't have timestamps for all active tracks... |
1963 | 0 | if (pageOffset == startOffset + startLength && |
1964 | 0 | pageOffset + pageLength >= endOffset) { |
1965 | 0 | // We read the entire interval without finding timestamps for all |
1966 | 0 | // active tracks. We know the interval start offset is before the seek |
1967 | 0 | // target, and the interval end is after the seek target, and we can't |
1968 | 0 | // terminate inside the interval, so we terminate the seek at the |
1969 | 0 | // start of the interval. |
1970 | 0 | interval = 0; |
1971 | 0 | break; |
1972 | 0 | } |
1973 | 0 | |
1974 | 0 | // We should backoff; cause the guess to back off from the end, so |
1975 | 0 | // that we've got more room to capture. |
1976 | 0 | mustBackoff = true; |
1977 | 0 | continue; |
1978 | 0 | } |
1979 | 0 | |
1980 | 0 | // We've found appropriate time stamps here. Proceed to bisect |
1981 | 0 | // the search space. |
1982 | 0 | granuleTime = aType == TrackInfo::kAudioTrack ? audioTime : videoTime; |
1983 | 0 | MOZ_ASSERT(granuleTime > 0, "Must get a granuletime"); |
1984 | 0 | break; |
1985 | 0 | } // End of "until we determine time at guess offset" loop. |
1986 | 0 |
|
1987 | 0 | if (interval == 0) { |
1988 | 0 | // Seek termination condition; we've found the page boundary of the |
1989 | 0 | // last page before the target, and the first page after the target. |
1990 | 0 | SEEK_LOG(LogLevel::Debug, ("Terminating seek at offset=%lld", startOffset)); |
1991 | 0 | MOZ_ASSERT(startTime < aTarget, "Start time must always be less than target"); |
1992 | 0 | res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, startOffset); |
1993 | 0 | NS_ENSURE_SUCCESS(res,res); |
1994 | 0 | if (NS_FAILED(Reset(aType))) { |
1995 | 0 | return NS_ERROR_FAILURE; |
1996 | 0 | } |
1997 | 0 | break; |
1998 | 0 | } |
1999 | 0 | |
2000 | 0 | SEEK_LOG(LogLevel::Debug, ("Time at offset %lld is %lld", guess, granuleTime)); |
2001 | 0 | if (granuleTime < seekTarget && granuleTime > seekLowerBound) { |
2002 | 0 | // We're within the fuzzy region in which we want to terminate the search. |
2003 | 0 | res = Resource(aType)->Seek(nsISeekableStream::NS_SEEK_SET, pageOffset); |
2004 | 0 | NS_ENSURE_SUCCESS(res,res); |
2005 | 0 | if (NS_FAILED(Reset(aType))) { |
2006 | 0 | return NS_ERROR_FAILURE; |
2007 | 0 | } |
2008 | 0 | SEEK_LOG(LogLevel::Debug, ("Terminating seek at offset=%lld", pageOffset)); |
2009 | 0 | break; |
2010 | 0 | } |
2011 | 0 | |
2012 | 0 | if (granuleTime >= seekTarget) { |
2013 | 0 | // We've landed after the seek target. |
2014 | 0 | MOZ_ASSERT(pageOffset < endOffset, "offset_end must decrease"); |
2015 | 0 | endOffset = pageOffset; |
2016 | 0 | endTime = granuleTime; |
2017 | 0 | } else if (granuleTime < seekTarget) { |
2018 | 0 | // Landed before seek target. |
2019 | 0 | MOZ_ASSERT(pageOffset >= startOffset + startLength, |
2020 | 0 | "Bisection point should be at or after end of first page in interval"); |
2021 | 0 | startOffset = pageOffset; |
2022 | 0 | startLength = pageLength; |
2023 | 0 | startTime = granuleTime; |
2024 | 0 | } |
2025 | 0 | MOZ_ASSERT(startTime <= seekTarget, "Must be before seek target"); |
2026 | 0 | MOZ_ASSERT(endTime >= seekTarget, "End must be after seek target"); |
2027 | 0 | } |
2028 | 0 |
|
2029 | 0 | SEEK_LOG(LogLevel::Debug, ("Seek complete in %d bisections.", hops)); |
2030 | 0 |
|
2031 | 0 | return NS_OK; |
2032 | 0 | } |
2033 | | |
2034 | | #undef OGG_DEBUG |
2035 | | #undef SEEK_DEBUG |
2036 | | } // namespace mozilla |