/src/mozilla-central/dom/media/mp4/MP4Demuxer.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 <algorithm> |
8 | | #include <limits> |
9 | | #include <stdint.h> |
10 | | |
11 | | #include "MP4Demuxer.h" |
12 | | |
13 | | #include "mozilla/StaticPrefs.h" |
14 | | // Used for telemetry |
15 | | #include "mozilla/Telemetry.h" |
16 | | #include "AnnexB.h" |
17 | | #include "H264.h" |
18 | | #include "MoofParser.h" |
19 | | #include "MP4Metadata.h" |
20 | | #include "ResourceStream.h" |
21 | | #include "BufferStream.h" |
22 | | #include "Index.h" |
23 | | #include "nsAutoPtr.h" |
24 | | #include "nsPrintfCString.h" |
25 | | |
26 | | extern mozilla::LazyLogModule gMediaDemuxerLog; |
27 | | mozilla::LogModule* GetDemuxerLog() |
28 | 0 | { |
29 | 0 | return gMediaDemuxerLog; |
30 | 0 | } |
31 | | |
32 | | #define LOG(arg, ...) \ |
33 | | DDMOZ_LOG(gMediaDemuxerLog, \ |
34 | | mozilla::LogLevel::Debug, \ |
35 | | "::%s: " arg, \ |
36 | | __func__, \ |
37 | | ##__VA_ARGS__) |
38 | | |
39 | | namespace mozilla { |
40 | | |
41 | | DDLoggedTypeDeclNameAndBase(MP4TrackDemuxer, MediaTrackDemuxer); |
42 | | |
43 | | class MP4TrackDemuxer |
44 | | : public MediaTrackDemuxer |
45 | | , public DecoderDoctorLifeLogger<MP4TrackDemuxer> |
46 | | { |
47 | | public: |
48 | | MP4TrackDemuxer(MP4Demuxer* aParent, |
49 | | UniquePtr<TrackInfo>&& aInfo, |
50 | | const IndiceWrapper& aIndices); |
51 | | |
52 | | UniquePtr<TrackInfo> GetInfo() const override; |
53 | | |
54 | | RefPtr<SeekPromise> Seek(const media::TimeUnit& aTime) override; |
55 | | |
56 | | RefPtr<SamplesPromise> GetSamples(int32_t aNumSamples = 1) override; |
57 | | |
58 | | void Reset() override; |
59 | | |
60 | | nsresult GetNextRandomAccessPoint(media::TimeUnit* aTime) override; |
61 | | |
62 | | RefPtr<SkipAccessPointPromise> |
63 | | SkipToNextRandomAccessPoint(const media::TimeUnit& aTimeThreshold) override; |
64 | | |
65 | | media::TimeIntervals GetBuffered() override; |
66 | | |
67 | | void BreakCycles() override; |
68 | | |
69 | | void NotifyDataRemoved(); |
70 | | |
71 | | private: |
72 | | friend class MP4Demuxer; |
73 | | void NotifyDataArrived(); |
74 | | already_AddRefed<MediaRawData> GetNextSample(); |
75 | | void EnsureUpToDateIndex(); |
76 | | void SetNextKeyFrameTime(); |
77 | | RefPtr<MP4Demuxer> mParent; |
78 | | RefPtr<ResourceStream> mStream; |
79 | | UniquePtr<TrackInfo> mInfo; |
80 | | RefPtr<Index> mIndex; |
81 | | UniquePtr<SampleIterator> mIterator; |
82 | | Maybe<media::TimeUnit> mNextKeyframeTime; |
83 | | // Queued samples extracted by the demuxer, but not yet returned. |
84 | | RefPtr<MediaRawData> mQueuedSample; |
85 | | bool mNeedReIndex; |
86 | | bool mNeedSPSForTelemetry; |
87 | | bool mIsH264 = false; |
88 | | }; |
89 | | |
90 | | |
91 | | // Returns true if no SPS was found and search for it should continue. |
92 | | bool |
93 | | AccumulateSPSTelemetry(const MediaByteBuffer* aExtradata) |
94 | 0 | { |
95 | 0 | SPSData spsdata; |
96 | 0 | if (H264::DecodeSPSFromExtraData(aExtradata, spsdata)) { |
97 | 0 | uint8_t constraints = (spsdata.constraint_set0_flag ? (1 << 0) : 0) |
98 | 0 | | (spsdata.constraint_set1_flag ? (1 << 1) : 0) |
99 | 0 | | (spsdata.constraint_set2_flag ? (1 << 2) : 0) |
100 | 0 | | (spsdata.constraint_set3_flag ? (1 << 3) : 0) |
101 | 0 | | (spsdata.constraint_set4_flag ? (1 << 4) : 0) |
102 | 0 | | (spsdata.constraint_set5_flag ? (1 << 5) : 0); |
103 | 0 | Telemetry::Accumulate(Telemetry::VIDEO_DECODED_H264_SPS_CONSTRAINT_SET_FLAG, |
104 | 0 | constraints); |
105 | 0 |
|
106 | 0 | // Collect profile_idc values up to 244, otherwise 0 for unknown. |
107 | 0 | Telemetry::Accumulate(Telemetry::VIDEO_DECODED_H264_SPS_PROFILE, |
108 | 0 | spsdata.profile_idc <= 244 ? spsdata.profile_idc : 0); |
109 | 0 |
|
110 | 0 | // Make sure level_idc represents a value between levels 1 and 5.2, |
111 | 0 | // otherwise collect 0 for unknown level. |
112 | 0 | Telemetry::Accumulate(Telemetry::VIDEO_DECODED_H264_SPS_LEVEL, |
113 | 0 | (spsdata.level_idc >= 10 && spsdata.level_idc <= 52) |
114 | 0 | ? spsdata.level_idc |
115 | 0 | : 0); |
116 | 0 |
|
117 | 0 | // max_num_ref_frames should be between 0 and 16, anything larger will |
118 | 0 | // be treated as invalid. |
119 | 0 | Telemetry::Accumulate(Telemetry::VIDEO_H264_SPS_MAX_NUM_REF_FRAMES, |
120 | 0 | std::min(spsdata.max_num_ref_frames, 17u)); |
121 | 0 |
|
122 | 0 | return false; |
123 | 0 | } |
124 | 0 |
|
125 | 0 | return true; |
126 | 0 | } |
127 | | |
128 | | MP4Demuxer::MP4Demuxer(MediaResource* aResource) |
129 | | : mResource(aResource) |
130 | | , mStream(new ResourceStream(aResource)) |
131 | | , mIsSeekable(false) |
132 | 0 | { |
133 | 0 | DDLINKCHILD("resource", aResource); |
134 | 0 | DDLINKCHILD("stream", mStream.get()); |
135 | 0 | } |
136 | | |
137 | | RefPtr<MP4Demuxer::InitPromise> |
138 | | MP4Demuxer::Init() |
139 | 0 | { |
140 | 0 | AutoPinned<ResourceStream> stream(mStream); |
141 | 0 |
|
142 | 0 | // 'result' will capture the first warning, if any. |
143 | 0 | MediaResult result{NS_OK}; |
144 | 0 |
|
145 | 0 | MP4Metadata::ResultAndByteBuffer initData = |
146 | 0 | MP4Metadata::Metadata(stream); |
147 | 0 | if (!initData.Ref()) { |
148 | 0 | return InitPromise::CreateAndReject( |
149 | 0 | NS_FAILED(initData.Result()) |
150 | 0 | ? std::move(initData.Result()) |
151 | 0 | : MediaResult(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, |
152 | 0 | RESULT_DETAIL("Invalid MP4 metadata or OOM")), |
153 | 0 | __func__); |
154 | 0 | } else if (NS_FAILED(initData.Result()) && result == NS_OK) { |
155 | 0 | result = std::move(initData.Result()); |
156 | 0 | } |
157 | 0 |
|
158 | 0 | RefPtr<BufferStream> bufferstream = |
159 | 0 | new BufferStream(initData.Ref()); |
160 | 0 |
|
161 | 0 | MP4Metadata metadata{bufferstream}; |
162 | 0 | DDLINKCHILD("metadata", &metadata); |
163 | 0 | nsresult rv = metadata.Parse(); |
164 | 0 | if (NS_FAILED(rv)) { |
165 | 0 | return InitPromise::CreateAndReject( |
166 | 0 | MediaResult(rv, RESULT_DETAIL("Parse MP4 metadata failed")), __func__); |
167 | 0 | } |
168 | 0 |
|
169 | 0 | auto audioTrackCount = metadata.GetNumberTracks(TrackInfo::kAudioTrack); |
170 | 0 | if (audioTrackCount.Ref() == MP4Metadata::NumberTracksError()) { |
171 | 0 | if (StaticPrefs::MediaPlaybackWarningsAsErrors()) { |
172 | 0 | return InitPromise::CreateAndReject( |
173 | 0 | MediaResult(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, |
174 | 0 | RESULT_DETAIL("Invalid audio track (%s)", |
175 | 0 | audioTrackCount.Result().Description().get())), |
176 | 0 | __func__); |
177 | 0 | } |
178 | 0 | audioTrackCount.Ref() = 0; |
179 | 0 | } |
180 | 0 |
|
181 | 0 | auto videoTrackCount = metadata.GetNumberTracks(TrackInfo::kVideoTrack); |
182 | 0 | if (videoTrackCount.Ref() == MP4Metadata::NumberTracksError()) { |
183 | 0 | if (StaticPrefs::MediaPlaybackWarningsAsErrors()) { |
184 | 0 | return InitPromise::CreateAndReject( |
185 | 0 | MediaResult(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, |
186 | 0 | RESULT_DETAIL("Invalid video track (%s)", |
187 | 0 | videoTrackCount.Result().Description().get())), |
188 | 0 | __func__); |
189 | 0 | } |
190 | 0 | videoTrackCount.Ref() = 0; |
191 | 0 | } |
192 | 0 |
|
193 | 0 | if (audioTrackCount.Ref() == 0 && videoTrackCount.Ref() == 0) { |
194 | 0 | return InitPromise::CreateAndReject( |
195 | 0 | MediaResult(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, |
196 | 0 | RESULT_DETAIL("No MP4 audio (%s) or video (%s) tracks", |
197 | 0 | audioTrackCount.Result().Description().get(), |
198 | 0 | videoTrackCount.Result().Description().get())), |
199 | 0 | __func__); |
200 | 0 | } |
201 | 0 |
|
202 | 0 | if (NS_FAILED(audioTrackCount.Result()) && result == NS_OK) { |
203 | 0 | result = std::move(audioTrackCount.Result()); |
204 | 0 | } |
205 | 0 | if (NS_FAILED(videoTrackCount.Result()) && result == NS_OK) { |
206 | 0 | result = std::move(videoTrackCount.Result()); |
207 | 0 | } |
208 | 0 |
|
209 | 0 | if (audioTrackCount.Ref() != 0) { |
210 | 0 | for (size_t i = 0; i < audioTrackCount.Ref(); i++) { |
211 | 0 | MP4Metadata::ResultAndTrackInfo info = |
212 | 0 | metadata.GetTrackInfo(TrackInfo::kAudioTrack, i); |
213 | 0 | if (!info.Ref()) { |
214 | 0 | if (StaticPrefs::MediaPlaybackWarningsAsErrors()) { |
215 | 0 | return InitPromise::CreateAndReject( |
216 | 0 | MediaResult(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, |
217 | 0 | RESULT_DETAIL("Invalid MP4 audio track (%s)", |
218 | 0 | info.Result().Description().get())), |
219 | 0 | __func__); |
220 | 0 | } |
221 | 0 | if (result == NS_OK) { |
222 | 0 | result = MediaResult(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, |
223 | 0 | RESULT_DETAIL("Invalid MP4 audio track (%s)", |
224 | 0 | info.Result().Description().get())); |
225 | 0 | } |
226 | 0 | continue; |
227 | 0 | } else if (NS_FAILED(info.Result()) && result == NS_OK) { |
228 | 0 | result = std::move(info.Result()); |
229 | 0 | } |
230 | 0 | MP4Metadata::ResultAndIndice indices = |
231 | 0 | metadata.GetTrackIndice(info.Ref()->mTrackId); |
232 | 0 | if (!indices.Ref()) { |
233 | 0 | if (NS_FAILED(info.Result()) && result == NS_OK) { |
234 | 0 | result = std::move(indices.Result()); |
235 | 0 | } |
236 | 0 | continue; |
237 | 0 | } |
238 | 0 | RefPtr<MP4TrackDemuxer> demuxer = |
239 | 0 | new MP4TrackDemuxer(this, std::move(info.Ref()), *indices.Ref().get()); |
240 | 0 | DDLINKCHILD("audio demuxer", demuxer.get()); |
241 | 0 | mAudioDemuxers.AppendElement(std::move(demuxer)); |
242 | 0 | } |
243 | 0 | } |
244 | 0 |
|
245 | 0 | if (videoTrackCount.Ref() != 0) { |
246 | 0 | for (size_t i = 0; i < videoTrackCount.Ref(); i++) { |
247 | 0 | MP4Metadata::ResultAndTrackInfo info = |
248 | 0 | metadata.GetTrackInfo(TrackInfo::kVideoTrack, i); |
249 | 0 | if (!info.Ref()) { |
250 | 0 | if (StaticPrefs::MediaPlaybackWarningsAsErrors()) { |
251 | 0 | return InitPromise::CreateAndReject( |
252 | 0 | MediaResult(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, |
253 | 0 | RESULT_DETAIL("Invalid MP4 video track (%s)", |
254 | 0 | info.Result().Description().get())), |
255 | 0 | __func__); |
256 | 0 | } |
257 | 0 | if (result == NS_OK) { |
258 | 0 | result = MediaResult(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, |
259 | 0 | RESULT_DETAIL("Invalid MP4 video track (%s)", |
260 | 0 | info.Result().Description().get())); |
261 | 0 | } |
262 | 0 | continue; |
263 | 0 | } else if (NS_FAILED(info.Result()) && result == NS_OK) { |
264 | 0 | result = std::move(info.Result()); |
265 | 0 | } |
266 | 0 | MP4Metadata::ResultAndIndice indices = |
267 | 0 | metadata.GetTrackIndice(info.Ref()->mTrackId); |
268 | 0 | if (!indices.Ref()) { |
269 | 0 | if (NS_FAILED(info.Result()) && result == NS_OK) { |
270 | 0 | result = std::move(indices.Result()); |
271 | 0 | } |
272 | 0 | continue; |
273 | 0 | } |
274 | 0 | RefPtr<MP4TrackDemuxer> demuxer = |
275 | 0 | new MP4TrackDemuxer(this, std::move(info.Ref()), *indices.Ref().get()); |
276 | 0 | DDLINKCHILD("video demuxer", demuxer.get()); |
277 | 0 | mVideoDemuxers.AppendElement(std::move(demuxer)); |
278 | 0 | } |
279 | 0 | } |
280 | 0 |
|
281 | 0 | MP4Metadata::ResultAndCryptoFile cryptoFile = |
282 | 0 | metadata.Crypto(); |
283 | 0 | if (NS_FAILED(cryptoFile.Result()) && result == NS_OK) { |
284 | 0 | result = std::move(cryptoFile.Result()); |
285 | 0 | } |
286 | 0 | MOZ_ASSERT(cryptoFile.Ref()); |
287 | 0 | if (cryptoFile.Ref()->valid) { |
288 | 0 | const nsTArray<PsshInfo>& psshs = cryptoFile.Ref()->pssh; |
289 | 0 | for (uint32_t i = 0; i < psshs.Length(); i++) { |
290 | 0 | mCryptoInitData.AppendElements(psshs[i].data); |
291 | 0 | } |
292 | 0 | } |
293 | 0 |
|
294 | 0 | mIsSeekable = metadata.CanSeek(); |
295 | 0 |
|
296 | 0 | return InitPromise::CreateAndResolve(result, __func__); |
297 | 0 | } |
298 | | |
299 | | uint32_t |
300 | | MP4Demuxer::GetNumberTracks(TrackInfo::TrackType aType) const |
301 | | { |
302 | | switch (aType) { |
303 | | case TrackInfo::kAudioTrack: return uint32_t(mAudioDemuxers.Length()); |
304 | | case TrackInfo::kVideoTrack: return uint32_t(mVideoDemuxers.Length()); |
305 | | default: return 0; |
306 | | } |
307 | | } |
308 | | |
309 | | already_AddRefed<MediaTrackDemuxer> |
310 | | MP4Demuxer::GetTrackDemuxer(TrackInfo::TrackType aType, uint32_t aTrackNumber) |
311 | 0 | { |
312 | 0 | switch (aType) { |
313 | 0 | case TrackInfo::kAudioTrack: |
314 | 0 | if (aTrackNumber >= uint32_t(mAudioDemuxers.Length())) { |
315 | 0 | return nullptr; |
316 | 0 | } |
317 | 0 | return RefPtr<MediaTrackDemuxer>(mAudioDemuxers[aTrackNumber]).forget(); |
318 | 0 | case TrackInfo::kVideoTrack: |
319 | 0 | if (aTrackNumber >= uint32_t(mVideoDemuxers.Length())) { |
320 | 0 | return nullptr; |
321 | 0 | } |
322 | 0 | return RefPtr<MediaTrackDemuxer>(mVideoDemuxers[aTrackNumber]).forget(); |
323 | 0 | default: |
324 | 0 | return nullptr; |
325 | 0 | } |
326 | 0 | } |
327 | | |
328 | | bool |
329 | | MP4Demuxer::IsSeekable() const |
330 | 0 | { |
331 | 0 | return mIsSeekable; |
332 | 0 | } |
333 | | |
334 | | void |
335 | | MP4Demuxer::NotifyDataArrived() |
336 | 0 | { |
337 | 0 | for (auto& dmx : mAudioDemuxers) { |
338 | 0 | dmx->NotifyDataArrived(); |
339 | 0 | } |
340 | 0 | for (auto& dmx : mVideoDemuxers) { |
341 | 0 | dmx->NotifyDataArrived(); |
342 | 0 | } |
343 | 0 | } |
344 | | |
345 | | void |
346 | | MP4Demuxer::NotifyDataRemoved() |
347 | 0 | { |
348 | 0 | for (auto& dmx : mAudioDemuxers) { |
349 | 0 | dmx->NotifyDataRemoved(); |
350 | 0 | } |
351 | 0 | for (auto& dmx : mVideoDemuxers) { |
352 | 0 | dmx->NotifyDataRemoved(); |
353 | 0 | } |
354 | 0 | } |
355 | | |
356 | | UniquePtr<EncryptionInfo> |
357 | | MP4Demuxer::GetCrypto() |
358 | 0 | { |
359 | 0 | UniquePtr<EncryptionInfo> crypto; |
360 | 0 | if (!mCryptoInitData.IsEmpty()) { |
361 | 0 | crypto.reset(new EncryptionInfo{}); |
362 | 0 | crypto->AddInitData(NS_LITERAL_STRING("cenc"), mCryptoInitData); |
363 | 0 | } |
364 | 0 | return crypto; |
365 | 0 | } |
366 | | |
367 | | MP4TrackDemuxer::MP4TrackDemuxer(MP4Demuxer* aParent, |
368 | | UniquePtr<TrackInfo>&& aInfo, |
369 | | const IndiceWrapper& aIndices) |
370 | | : mParent(aParent) |
371 | | , mStream(new ResourceStream(mParent->mResource)) |
372 | | , mInfo(std::move(aInfo)) |
373 | | , mIndex(new Index(aIndices, |
374 | | mStream, |
375 | | mInfo->mTrackId, |
376 | | mInfo->IsAudio())) |
377 | | , mIterator(MakeUnique<SampleIterator>(mIndex)) |
378 | | , mNeedReIndex(true) |
379 | 0 | { |
380 | 0 | EnsureUpToDateIndex(); // Force update of index |
381 | 0 |
|
382 | 0 | VideoInfo* videoInfo = mInfo->GetAsVideoInfo(); |
383 | 0 | // Collect telemetry from h264 AVCC SPS. |
384 | 0 | if (videoInfo && (mInfo->mMimeType.EqualsLiteral("video/mp4") || |
385 | 0 | mInfo->mMimeType.EqualsLiteral("video/avc"))) { |
386 | 0 | mIsH264 = true; |
387 | 0 | RefPtr<MediaByteBuffer> extraData = videoInfo->mExtraData; |
388 | 0 | mNeedSPSForTelemetry = AccumulateSPSTelemetry(extraData); |
389 | 0 | SPSData spsdata; |
390 | 0 | if (H264::DecodeSPSFromExtraData(extraData, spsdata) && |
391 | 0 | spsdata.pic_width > 0 && spsdata.pic_height > 0 && |
392 | 0 | H264::EnsureSPSIsSane(spsdata)) { |
393 | 0 | videoInfo->mImage.width = spsdata.pic_width; |
394 | 0 | videoInfo->mImage.height = spsdata.pic_height; |
395 | 0 | videoInfo->mDisplay.width = spsdata.display_width; |
396 | 0 | videoInfo->mDisplay.height = spsdata.display_height; |
397 | 0 | } |
398 | 0 | } else { |
399 | 0 | // No SPS to be found. |
400 | 0 | mNeedSPSForTelemetry = false; |
401 | 0 | } |
402 | 0 | } |
403 | | |
404 | | UniquePtr<TrackInfo> |
405 | | MP4TrackDemuxer::GetInfo() const |
406 | 0 | { |
407 | 0 | return mInfo->Clone(); |
408 | 0 | } |
409 | | |
410 | | void |
411 | | MP4TrackDemuxer::EnsureUpToDateIndex() |
412 | 0 | { |
413 | 0 | if (!mNeedReIndex) { |
414 | 0 | return; |
415 | 0 | } |
416 | 0 | AutoPinned<MediaResource> resource(mParent->mResource); |
417 | 0 | MediaByteRangeSet byteRanges; |
418 | 0 | nsresult rv = resource->GetCachedRanges(byteRanges); |
419 | 0 | if (NS_FAILED(rv)) { |
420 | 0 | return; |
421 | 0 | } |
422 | 0 | mIndex->UpdateMoofIndex(byteRanges); |
423 | 0 | mNeedReIndex = false; |
424 | 0 | } |
425 | | |
426 | | RefPtr<MP4TrackDemuxer::SeekPromise> |
427 | | MP4TrackDemuxer::Seek(const media::TimeUnit& aTime) |
428 | 0 | { |
429 | 0 | auto seekTime = aTime; |
430 | 0 | mQueuedSample = nullptr; |
431 | 0 |
|
432 | 0 | mIterator->Seek(seekTime.ToMicroseconds()); |
433 | 0 |
|
434 | 0 | // Check what time we actually seeked to. |
435 | 0 | do { |
436 | 0 | RefPtr<MediaRawData> sample = GetNextSample(); |
437 | 0 | if (!sample) { |
438 | 0 | return SeekPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_END_OF_STREAM, |
439 | 0 | __func__); |
440 | 0 | } |
441 | 0 | if (!sample->Size()) { |
442 | 0 | // This sample can't be decoded, continue searching. |
443 | 0 | continue; |
444 | 0 | } |
445 | 0 | if (sample->mKeyframe) { |
446 | 0 | mQueuedSample = sample; |
447 | 0 | seekTime = mQueuedSample->mTime; |
448 | 0 | } |
449 | 0 | } while (!mQueuedSample); |
450 | 0 |
|
451 | 0 | SetNextKeyFrameTime(); |
452 | 0 |
|
453 | 0 | return SeekPromise::CreateAndResolve(seekTime, __func__); |
454 | 0 | } |
455 | | |
456 | | already_AddRefed<MediaRawData> |
457 | | MP4TrackDemuxer::GetNextSample() |
458 | 0 | { |
459 | 0 | RefPtr<MediaRawData> sample = mIterator->GetNext(); |
460 | 0 | if (!sample) { |
461 | 0 | return nullptr; |
462 | 0 | } |
463 | 0 | if (mInfo->GetAsVideoInfo()) { |
464 | 0 | sample->mExtraData = mInfo->GetAsVideoInfo()->mExtraData; |
465 | 0 | if (mIsH264 && !sample->mCrypto.mValid) { |
466 | 0 | H264::FrameType type = H264::GetFrameType(sample); |
467 | 0 | switch (type) { |
468 | 0 | case H264::FrameType::I_FRAME: MOZ_FALLTHROUGH; |
469 | 0 | case H264::FrameType::OTHER: |
470 | 0 | { |
471 | 0 | bool keyframe = type == H264::FrameType::I_FRAME; |
472 | 0 | if (sample->mKeyframe != keyframe) { |
473 | 0 | NS_WARNING(nsPrintfCString("Frame incorrectly marked as %skeyframe " |
474 | 0 | "@ pts:%" PRId64 " dur:%" PRId64 |
475 | 0 | " dts:%" PRId64, |
476 | 0 | keyframe ? "" : "non-", |
477 | 0 | sample->mTime.ToMicroseconds(), |
478 | 0 | sample->mDuration.ToMicroseconds(), |
479 | 0 | sample->mTimecode.ToMicroseconds()) |
480 | 0 | .get()); |
481 | 0 | sample->mKeyframe = keyframe; |
482 | 0 | } |
483 | 0 | break; |
484 | 0 | } |
485 | 0 | case H264::FrameType::INVALID: |
486 | 0 | NS_WARNING( |
487 | 0 | nsPrintfCString("Invalid H264 frame @ pts:%" PRId64 " dur:%" PRId64 |
488 | 0 | " dts:%" PRId64, |
489 | 0 | sample->mTime.ToMicroseconds(), |
490 | 0 | sample->mDuration.ToMicroseconds(), |
491 | 0 | sample->mTimecode.ToMicroseconds()) |
492 | 0 | .get()); |
493 | 0 | // We could reject the sample now, however demuxer errors are fatal. |
494 | 0 | // So we keep the invalid frame, relying on the H264 decoder to |
495 | 0 | // handle the error later. |
496 | 0 | // TODO: make demuxer errors non-fatal. |
497 | 0 | break; |
498 | 0 | } |
499 | 0 | } |
500 | 0 | } |
501 | 0 |
|
502 | 0 | if (sample->mCrypto.mValid) { |
503 | 0 | UniquePtr<MediaRawDataWriter> writer(sample->CreateWriter()); |
504 | 0 | writer->mCrypto.mMode = mInfo->mCrypto.mMode; |
505 | 0 |
|
506 | 0 | // Only use the default key parsed from the moov if we haven't already got |
507 | 0 | // one from the sample group description. |
508 | 0 | if (writer->mCrypto.mKeyId.Length() == 0) { |
509 | 0 | writer->mCrypto.mIVSize = mInfo->mCrypto.mIVSize; |
510 | 0 | writer->mCrypto.mKeyId.AppendElements(mInfo->mCrypto.mKeyId); |
511 | 0 | } |
512 | 0 | } |
513 | 0 | return sample.forget(); |
514 | 0 | } |
515 | | |
516 | | RefPtr<MP4TrackDemuxer::SamplesPromise> |
517 | | MP4TrackDemuxer::GetSamples(int32_t aNumSamples) |
518 | 0 | { |
519 | 0 | EnsureUpToDateIndex(); |
520 | 0 | RefPtr<SamplesHolder> samples = new SamplesHolder; |
521 | 0 | if (!aNumSamples) { |
522 | 0 | return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, |
523 | 0 | __func__); |
524 | 0 | } |
525 | 0 | |
526 | 0 | if (mQueuedSample) { |
527 | 0 | NS_ASSERTION(mQueuedSample->mKeyframe, |
528 | 0 | "mQueuedSample must be a keyframe"); |
529 | 0 | samples->mSamples.AppendElement(mQueuedSample); |
530 | 0 | mQueuedSample = nullptr; |
531 | 0 | aNumSamples--; |
532 | 0 | } |
533 | 0 | RefPtr<MediaRawData> sample; |
534 | 0 | while (aNumSamples && (sample = GetNextSample())) { |
535 | 0 | if (!sample->Size()) { |
536 | 0 | continue; |
537 | 0 | } |
538 | 0 | samples->mSamples.AppendElement(sample); |
539 | 0 | aNumSamples--; |
540 | 0 | } |
541 | 0 |
|
542 | 0 | if (samples->mSamples.IsEmpty()) { |
543 | 0 | return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_END_OF_STREAM, |
544 | 0 | __func__); |
545 | 0 | } |
546 | 0 | for (const auto& sample : samples->mSamples) { |
547 | 0 | // Collect telemetry from h264 Annex B SPS. |
548 | 0 | if (mNeedSPSForTelemetry && mIsH264 && |
549 | 0 | AnnexB::IsAVCC(sample)) { |
550 | 0 | RefPtr<MediaByteBuffer> extradata = |
551 | 0 | H264::ExtractExtraData(sample); |
552 | 0 | if (H264::HasSPS(extradata)) { |
553 | 0 | RefPtr<MediaByteBuffer> extradata = |
554 | 0 | H264::ExtractExtraData(sample); |
555 | 0 | mNeedSPSForTelemetry = AccumulateSPSTelemetry(extradata); |
556 | 0 | } |
557 | 0 | } |
558 | 0 | } |
559 | 0 |
|
560 | 0 | if (mNextKeyframeTime.isNothing() || |
561 | 0 | samples->mSamples.LastElement()->mTime >= mNextKeyframeTime.value()) { |
562 | 0 | SetNextKeyFrameTime(); |
563 | 0 | } |
564 | 0 | return SamplesPromise::CreateAndResolve(samples, __func__); |
565 | 0 | } |
566 | | |
567 | | void |
568 | | MP4TrackDemuxer::SetNextKeyFrameTime() |
569 | 0 | { |
570 | 0 | mNextKeyframeTime.reset(); |
571 | 0 | Microseconds frameTime = mIterator->GetNextKeyframeTime(); |
572 | 0 | if (frameTime != -1) { |
573 | 0 | mNextKeyframeTime.emplace( |
574 | 0 | media::TimeUnit::FromMicroseconds(frameTime)); |
575 | 0 | } |
576 | 0 | } |
577 | | |
578 | | void |
579 | | MP4TrackDemuxer::Reset() |
580 | 0 | { |
581 | 0 | mQueuedSample = nullptr; |
582 | 0 | // TODO, Seek to first frame available, which isn't always 0. |
583 | 0 | mIterator->Seek(0); |
584 | 0 | SetNextKeyFrameTime(); |
585 | 0 | } |
586 | | |
587 | | nsresult |
588 | | MP4TrackDemuxer::GetNextRandomAccessPoint(media::TimeUnit* aTime) |
589 | 0 | { |
590 | 0 | if (mNextKeyframeTime.isNothing()) { |
591 | 0 | // There's no next key frame. |
592 | 0 | *aTime = media::TimeUnit::FromInfinity(); |
593 | 0 | } else { |
594 | 0 | *aTime = mNextKeyframeTime.value(); |
595 | 0 | } |
596 | 0 | return NS_OK; |
597 | 0 | } |
598 | | |
599 | | RefPtr<MP4TrackDemuxer::SkipAccessPointPromise> |
600 | | MP4TrackDemuxer::SkipToNextRandomAccessPoint( |
601 | | const media::TimeUnit& aTimeThreshold) |
602 | 0 | { |
603 | 0 | mQueuedSample = nullptr; |
604 | 0 | // Loop until we reach the next keyframe after the threshold. |
605 | 0 | uint32_t parsed = 0; |
606 | 0 | bool found = false; |
607 | 0 | RefPtr<MediaRawData> sample; |
608 | 0 | while (!found && (sample = GetNextSample())) { |
609 | 0 | parsed++; |
610 | 0 | if (sample->mKeyframe && sample->mTime >= aTimeThreshold) { |
611 | 0 | found = true; |
612 | 0 | mQueuedSample = sample; |
613 | 0 | } |
614 | 0 | } |
615 | 0 | SetNextKeyFrameTime(); |
616 | 0 | if (found) { |
617 | 0 | return SkipAccessPointPromise::CreateAndResolve(parsed, __func__); |
618 | 0 | } |
619 | 0 | SkipFailureHolder failure(NS_ERROR_DOM_MEDIA_END_OF_STREAM, parsed); |
620 | 0 | return SkipAccessPointPromise::CreateAndReject(std::move(failure), __func__); |
621 | 0 | } |
622 | | |
623 | | media::TimeIntervals |
624 | | MP4TrackDemuxer::GetBuffered() |
625 | 0 | { |
626 | 0 | EnsureUpToDateIndex(); |
627 | 0 | AutoPinned<MediaResource> resource(mParent->mResource); |
628 | 0 | MediaByteRangeSet byteRanges; |
629 | 0 | nsresult rv = resource->GetCachedRanges(byteRanges); |
630 | 0 |
|
631 | 0 | if (NS_FAILED(rv)) { |
632 | 0 | return media::TimeIntervals(); |
633 | 0 | } |
634 | 0 | |
635 | 0 | return mIndex->ConvertByteRangesToTimeRanges(byteRanges); |
636 | 0 | } |
637 | | |
638 | | void |
639 | | MP4TrackDemuxer::NotifyDataArrived() |
640 | 0 | { |
641 | 0 | mNeedReIndex = true; |
642 | 0 | } |
643 | | |
644 | | void |
645 | | MP4TrackDemuxer::NotifyDataRemoved() |
646 | 0 | { |
647 | 0 | AutoPinned<MediaResource> resource(mParent->mResource); |
648 | 0 | MediaByteRangeSet byteRanges; |
649 | 0 | nsresult rv = resource->GetCachedRanges(byteRanges); |
650 | 0 | if (NS_FAILED(rv)) { |
651 | 0 | return; |
652 | 0 | } |
653 | 0 | mIndex->UpdateMoofIndex(byteRanges, true /* can evict */); |
654 | 0 | mNeedReIndex = false; |
655 | 0 | } |
656 | | |
657 | | void |
658 | | MP4TrackDemuxer::BreakCycles() |
659 | 0 | { |
660 | 0 | mParent = nullptr; |
661 | 0 | } |
662 | | |
663 | | } // namespace mozilla |
664 | | |
665 | | #undef LOG |