/src/mozilla-central/dom/media/webm/WebMDemuxer.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 "MediaResource.h" |
10 | | #ifdef MOZ_AV1 |
11 | | #include "AOMDecoder.h" |
12 | | #endif |
13 | | #include "OpusDecoder.h" |
14 | | #include "VPXDecoder.h" |
15 | | #include "WebMDemuxer.h" |
16 | | #include "WebMBufferedParser.h" |
17 | | #include "gfx2DGlue.h" |
18 | | #include "mozilla/Atomics.h" |
19 | | #include "mozilla/EndianUtils.h" |
20 | | #include "mozilla/SharedThreadPool.h" |
21 | | #include "MediaDataDemuxer.h" |
22 | | #include "nsAutoRef.h" |
23 | | #include "NesteggPacketHolder.h" |
24 | | #include "XiphExtradata.h" |
25 | | #include "prprf.h" // leaving it for PR_vsnprintf() |
26 | | #include "mozilla/IntegerPrintfMacros.h" |
27 | | #include "mozilla/Sprintf.h" |
28 | | |
29 | | #include <algorithm> |
30 | | #include <numeric> |
31 | | #include <stdint.h> |
32 | | |
33 | | #define WEBM_DEBUG(arg, ...) \ |
34 | 0 | DDMOZ_LOG(gMediaDemuxerLog, \ |
35 | 0 | mozilla::LogLevel::Debug, \ |
36 | 0 | "::%s: " arg, \ |
37 | 0 | __func__, \ |
38 | 0 | ##__VA_ARGS__) |
39 | | extern mozilla::LazyLogModule gMediaDemuxerLog; |
40 | | |
41 | | namespace mozilla { |
42 | | |
43 | | using namespace gfx; |
44 | | using media::TimeUnit; |
45 | | |
46 | | LazyLogModule gNesteggLog("Nestegg"); |
47 | | |
48 | | // How far ahead will we look when searching future keyframe. In microseconds. |
49 | | // This value is based on what appears to be a reasonable value as most webm |
50 | | // files encountered appear to have keyframes located < 4s. |
51 | 0 | #define MAX_LOOK_AHEAD 10000000 |
52 | | |
53 | | static Atomic<uint32_t> sStreamSourceID(0u); |
54 | | |
55 | | // Functions for reading and seeking using WebMDemuxer required for |
56 | | // nestegg_io. The 'user data' passed to these functions is the |
57 | | // demuxer. |
58 | | static int webmdemux_read(void* aBuffer, size_t aLength, void* aUserData) |
59 | 0 | { |
60 | 0 | MOZ_ASSERT(aUserData); |
61 | 0 | MOZ_ASSERT(aLength < UINT32_MAX); |
62 | 0 | WebMDemuxer::NestEggContext* context = |
63 | 0 | reinterpret_cast<WebMDemuxer::NestEggContext*>(aUserData); |
64 | 0 | uint32_t count = aLength; |
65 | 0 | if (context->IsMediaSource()) { |
66 | 0 | int64_t length = context->GetEndDataOffset(); |
67 | 0 | int64_t position = context->GetResource()->Tell(); |
68 | 0 | MOZ_ASSERT(position <= context->GetResource()->GetLength()); |
69 | 0 | MOZ_ASSERT(position <= length); |
70 | 0 | if (length >= 0 && count + position > length) { |
71 | 0 | count = length - position; |
72 | 0 | } |
73 | 0 | MOZ_ASSERT(count <= aLength); |
74 | 0 | } |
75 | 0 | uint32_t bytes = 0; |
76 | 0 | nsresult rv = |
77 | 0 | context->GetResource()->Read(static_cast<char*>(aBuffer), count, &bytes); |
78 | 0 | bool eof = bytes < aLength; |
79 | 0 | return NS_FAILED(rv) ? -1 : eof ? 0 : 1; |
80 | 0 | } |
81 | | |
82 | | static int webmdemux_seek(int64_t aOffset, int aWhence, void* aUserData) |
83 | 0 | { |
84 | 0 | MOZ_ASSERT(aUserData); |
85 | 0 | WebMDemuxer::NestEggContext* context = |
86 | 0 | reinterpret_cast<WebMDemuxer::NestEggContext*>(aUserData); |
87 | 0 | nsresult rv = context->GetResource()->Seek(aWhence, aOffset); |
88 | 0 | return NS_SUCCEEDED(rv) ? 0 : -1; |
89 | 0 | } |
90 | | |
91 | | static int64_t webmdemux_tell(void* aUserData) |
92 | 0 | { |
93 | 0 | MOZ_ASSERT(aUserData); |
94 | 0 | WebMDemuxer::NestEggContext* context = |
95 | 0 | reinterpret_cast<WebMDemuxer::NestEggContext*>(aUserData); |
96 | 0 | return context->GetResource()->Tell(); |
97 | 0 | } |
98 | | |
99 | | static void webmdemux_log(nestegg* aContext, |
100 | | unsigned int aSeverity, |
101 | | char const* aFormat, ...) |
102 | 0 | { |
103 | 0 | if (!MOZ_LOG_TEST(gNesteggLog, LogLevel::Debug)) { |
104 | 0 | return; |
105 | 0 | } |
106 | 0 | |
107 | 0 | va_list args; |
108 | 0 | char msg[256]; |
109 | 0 | const char* sevStr; |
110 | 0 |
|
111 | 0 | switch(aSeverity) { |
112 | 0 | case NESTEGG_LOG_DEBUG: |
113 | 0 | sevStr = "DBG"; |
114 | 0 | break; |
115 | 0 | case NESTEGG_LOG_INFO: |
116 | 0 | sevStr = "INF"; |
117 | 0 | break; |
118 | 0 | case NESTEGG_LOG_WARNING: |
119 | 0 | sevStr = "WRN"; |
120 | 0 | break; |
121 | 0 | case NESTEGG_LOG_ERROR: |
122 | 0 | sevStr = "ERR"; |
123 | 0 | break; |
124 | 0 | case NESTEGG_LOG_CRITICAL: |
125 | 0 | sevStr = "CRT"; |
126 | 0 | break; |
127 | 0 | default: |
128 | 0 | sevStr = "UNK"; |
129 | 0 | break; |
130 | 0 | } |
131 | 0 | |
132 | 0 | va_start(args, aFormat); |
133 | 0 |
|
134 | 0 | SprintfLiteral(msg, "%p [Nestegg-%s] ", aContext, sevStr); |
135 | 0 | PR_vsnprintf(msg+strlen(msg), sizeof(msg)-strlen(msg), aFormat, args); |
136 | 0 | MOZ_LOG(gNesteggLog, LogLevel::Debug, ("%s", msg)); |
137 | 0 |
|
138 | 0 | va_end(args); |
139 | 0 | } |
140 | | |
141 | | WebMDemuxer::NestEggContext::~NestEggContext() |
142 | 0 | { |
143 | 0 | if (mContext) { |
144 | 0 | nestegg_destroy(mContext); |
145 | 0 | } |
146 | 0 | } |
147 | | |
148 | | int |
149 | | WebMDemuxer::NestEggContext::Init() |
150 | 0 | { |
151 | 0 | nestegg_io io; |
152 | 0 | io.read = webmdemux_read; |
153 | 0 | io.seek = webmdemux_seek; |
154 | 0 | io.tell = webmdemux_tell; |
155 | 0 | io.userdata = this; |
156 | 0 |
|
157 | 0 | // While reading the metadata, we do not really care about which nestegg |
158 | 0 | // context is being used so long that they are both initialised. |
159 | 0 | // For reading the metadata however, we will use mVideoContext. |
160 | 0 | return nestegg_init(&mContext, io, &webmdemux_log, |
161 | 0 | mParent->IsMediaSource() ? mResource.GetLength() : -1); |
162 | 0 | } |
163 | | |
164 | | WebMDemuxer::WebMDemuxer(MediaResource* aResource) |
165 | | : WebMDemuxer(aResource, false) |
166 | 0 | { |
167 | 0 | } |
168 | | |
169 | | WebMDemuxer::WebMDemuxer(MediaResource* aResource, bool aIsMediaSource) |
170 | | : mVideoContext(this, aResource) |
171 | | , mAudioContext(this, aResource) |
172 | | , mBufferedState(nullptr) |
173 | | , mInitData(nullptr) |
174 | | , mVideoTrack(0) |
175 | | , mAudioTrack(0) |
176 | | , mSeekPreroll(0) |
177 | | , mAudioCodec(-1) |
178 | | , mVideoCodec(-1) |
179 | | , mHasVideo(false) |
180 | | , mHasAudio(false) |
181 | | , mNeedReIndex(true) |
182 | | , mLastWebMBlockOffset(-1) |
183 | | , mIsMediaSource(aIsMediaSource) |
184 | 0 | { |
185 | 0 | DDLINKCHILD("resource", aResource); |
186 | 0 | // Audio/video contexts hold a MediaResourceIndex. |
187 | 0 | DDLINKCHILD("video context", mVideoContext.GetResource()); |
188 | 0 | DDLINKCHILD("audio context", mAudioContext.GetResource()); |
189 | 0 | } |
190 | | |
191 | | WebMDemuxer::~WebMDemuxer() |
192 | 0 | { |
193 | 0 | Reset(TrackInfo::kVideoTrack); |
194 | 0 | Reset(TrackInfo::kAudioTrack); |
195 | 0 | } |
196 | | |
197 | | RefPtr<WebMDemuxer::InitPromise> |
198 | | WebMDemuxer::Init() |
199 | 0 | { |
200 | 0 | InitBufferedState(); |
201 | 0 |
|
202 | 0 | if (NS_FAILED(ReadMetadata())) { |
203 | 0 | return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_METADATA_ERR, |
204 | 0 | __func__); |
205 | 0 | } |
206 | 0 | |
207 | 0 | if (!GetNumberTracks(TrackInfo::kAudioTrack) && |
208 | 0 | !GetNumberTracks(TrackInfo::kVideoTrack)) { |
209 | 0 | return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_METADATA_ERR, |
210 | 0 | __func__); |
211 | 0 | } |
212 | 0 | |
213 | 0 | return InitPromise::CreateAndResolve(NS_OK, __func__); |
214 | 0 | } |
215 | | |
216 | | void |
217 | | WebMDemuxer::InitBufferedState() |
218 | 0 | { |
219 | 0 | MOZ_ASSERT(!mBufferedState); |
220 | 0 | mBufferedState = new WebMBufferedState; |
221 | 0 | } |
222 | | |
223 | | uint32_t |
224 | | WebMDemuxer::GetNumberTracks(TrackInfo::TrackType aType) const |
225 | 0 | { |
226 | 0 | switch(aType) { |
227 | 0 | case TrackInfo::kAudioTrack: |
228 | 0 | return mHasAudio ? 1 : 0; |
229 | 0 | case TrackInfo::kVideoTrack: |
230 | 0 | return mHasVideo ? 1 : 0; |
231 | 0 | default: |
232 | 0 | return 0; |
233 | 0 | } |
234 | 0 | } |
235 | | |
236 | | UniquePtr<TrackInfo> |
237 | | WebMDemuxer::GetTrackInfo(TrackInfo::TrackType aType, |
238 | | size_t aTrackNumber) const |
239 | | { |
240 | | switch(aType) { |
241 | | case TrackInfo::kAudioTrack: |
242 | | return mInfo.mAudio.Clone(); |
243 | | case TrackInfo::kVideoTrack: |
244 | | return mInfo.mVideo.Clone(); |
245 | | default: |
246 | | return nullptr; |
247 | | } |
248 | | } |
249 | | |
250 | | already_AddRefed<MediaTrackDemuxer> |
251 | | WebMDemuxer::GetTrackDemuxer(TrackInfo::TrackType aType, uint32_t aTrackNumber) |
252 | 0 | { |
253 | 0 | if (GetNumberTracks(aType) <= aTrackNumber) { |
254 | 0 | return nullptr; |
255 | 0 | } |
256 | 0 | RefPtr<WebMTrackDemuxer> e = |
257 | 0 | new WebMTrackDemuxer(this, aType, aTrackNumber); |
258 | 0 | DDLINKCHILD("track demuxer", e.get()); |
259 | 0 | mDemuxers.AppendElement(e); |
260 | 0 |
|
261 | 0 | return e.forget(); |
262 | 0 | } |
263 | | |
264 | | nsresult |
265 | | WebMDemuxer::Reset(TrackInfo::TrackType aType) |
266 | 0 | { |
267 | 0 | if (aType == TrackInfo::kVideoTrack) { |
268 | 0 | mVideoPackets.Reset(); |
269 | 0 | } else { |
270 | 0 | mAudioPackets.Reset(); |
271 | 0 | } |
272 | 0 | return NS_OK; |
273 | 0 | } |
274 | | |
275 | | nsresult |
276 | | WebMDemuxer::ReadMetadata() |
277 | 0 | { |
278 | 0 | int r = mVideoContext.Init(); |
279 | 0 | if (r == -1) { |
280 | 0 | return NS_ERROR_FAILURE; |
281 | 0 | } |
282 | 0 | if (mAudioContext.Init() == -1) { |
283 | 0 | return NS_ERROR_FAILURE; |
284 | 0 | } |
285 | 0 | |
286 | 0 | // For reading the metadata we can only use the video resource/context. |
287 | 0 | MediaResourceIndex& resource = Resource(TrackInfo::kVideoTrack); |
288 | 0 | nestegg* context = Context(TrackInfo::kVideoTrack); |
289 | 0 |
|
290 | 0 | { |
291 | 0 | // Check how much data nestegg read and force feed it to BufferedState. |
292 | 0 | RefPtr<MediaByteBuffer> buffer = resource.MediaReadAt(0, resource.Tell()); |
293 | 0 | if (!buffer) { |
294 | 0 | return NS_ERROR_FAILURE; |
295 | 0 | } |
296 | 0 | mBufferedState->NotifyDataArrived(buffer->Elements(), buffer->Length(), 0); |
297 | 0 | if (mBufferedState->GetInitEndOffset() < 0) { |
298 | 0 | return NS_ERROR_FAILURE; |
299 | 0 | } |
300 | 0 | MOZ_ASSERT(mBufferedState->GetInitEndOffset() <= resource.Tell()); |
301 | 0 | } |
302 | 0 | mInitData = resource.MediaReadAt(0, mBufferedState->GetInitEndOffset()); |
303 | 0 | if (!mInitData || |
304 | 0 | mInitData->Length() != size_t(mBufferedState->GetInitEndOffset())) { |
305 | 0 | return NS_ERROR_FAILURE; |
306 | 0 | } |
307 | 0 | |
308 | 0 | unsigned int ntracks = 0; |
309 | 0 | r = nestegg_track_count(context, &ntracks); |
310 | 0 | if (r == -1) { |
311 | 0 | return NS_ERROR_FAILURE; |
312 | 0 | } |
313 | 0 | |
314 | 0 | for (unsigned int track = 0; track < ntracks; ++track) { |
315 | 0 | int id = nestegg_track_codec_id(context, track); |
316 | 0 | if (id == -1) { |
317 | 0 | return NS_ERROR_FAILURE; |
318 | 0 | } |
319 | 0 | int type = nestegg_track_type(context, track); |
320 | 0 | if (type == NESTEGG_TRACK_VIDEO && !mHasVideo) { |
321 | 0 | nestegg_video_params params; |
322 | 0 | r = nestegg_track_video_params(context, track, ¶ms); |
323 | 0 | if (r == -1) { |
324 | 0 | return NS_ERROR_FAILURE; |
325 | 0 | } |
326 | 0 | mVideoCodec = nestegg_track_codec_id(context, track); |
327 | 0 | switch(mVideoCodec) { |
328 | 0 | case NESTEGG_CODEC_VP8: |
329 | 0 | mInfo.mVideo.mMimeType = "video/vp8"; |
330 | 0 | break; |
331 | 0 | case NESTEGG_CODEC_VP9: |
332 | 0 | mInfo.mVideo.mMimeType = "video/vp9"; |
333 | 0 | break; |
334 | 0 | case NESTEGG_CODEC_AV1: |
335 | 0 | mInfo.mVideo.mMimeType = "video/av1"; |
336 | 0 | break; |
337 | 0 | default: |
338 | 0 | NS_WARNING("Unknown WebM video codec"); |
339 | 0 | return NS_ERROR_FAILURE; |
340 | 0 | } |
341 | 0 | // Picture region, taking into account cropping, before scaling |
342 | 0 | // to the display size. |
343 | 0 | unsigned int cropH = params.crop_right + params.crop_left; |
344 | 0 | unsigned int cropV = params.crop_bottom + params.crop_top; |
345 | 0 | gfx::IntRect pictureRect(params.crop_left, |
346 | 0 | params.crop_top, |
347 | 0 | params.width - cropH, |
348 | 0 | params.height - cropV); |
349 | 0 |
|
350 | 0 | // If the cropping data appears invalid then use the frame data |
351 | 0 | if (pictureRect.width <= 0 || |
352 | 0 | pictureRect.height <= 0 || |
353 | 0 | pictureRect.x < 0 || |
354 | 0 | pictureRect.y < 0) { |
355 | 0 | pictureRect.x = 0; |
356 | 0 | pictureRect.y = 0; |
357 | 0 | pictureRect.width = params.width; |
358 | 0 | pictureRect.height = params.height; |
359 | 0 | } |
360 | 0 |
|
361 | 0 | // Validate the container-reported frame and pictureRect sizes. This |
362 | 0 | // ensures that our video frame creation code doesn't overflow. |
363 | 0 | gfx::IntSize displaySize(params.display_width, params.display_height); |
364 | 0 | gfx::IntSize frameSize(params.width, params.height); |
365 | 0 | if (!IsValidVideoRegion(frameSize, pictureRect, displaySize)) { |
366 | 0 | // Video track's frame sizes will overflow. Ignore the video track. |
367 | 0 | continue; |
368 | 0 | } |
369 | 0 | |
370 | 0 | mVideoTrack = track; |
371 | 0 | mHasVideo = true; |
372 | 0 |
|
373 | 0 | mInfo.mVideo.mDisplay = displaySize; |
374 | 0 | mInfo.mVideo.mImage = frameSize; |
375 | 0 | mInfo.mVideo.SetImageRect(pictureRect); |
376 | 0 | mInfo.mVideo.SetAlpha(params.alpha_mode); |
377 | 0 |
|
378 | 0 | switch (params.stereo_mode) { |
379 | 0 | case NESTEGG_VIDEO_MONO: |
380 | 0 | mInfo.mVideo.mStereoMode = StereoMode::MONO; |
381 | 0 | break; |
382 | 0 | case NESTEGG_VIDEO_STEREO_LEFT_RIGHT: |
383 | 0 | mInfo.mVideo.mStereoMode = StereoMode::LEFT_RIGHT; |
384 | 0 | break; |
385 | 0 | case NESTEGG_VIDEO_STEREO_BOTTOM_TOP: |
386 | 0 | mInfo.mVideo.mStereoMode = StereoMode::BOTTOM_TOP; |
387 | 0 | break; |
388 | 0 | case NESTEGG_VIDEO_STEREO_TOP_BOTTOM: |
389 | 0 | mInfo.mVideo.mStereoMode = StereoMode::TOP_BOTTOM; |
390 | 0 | break; |
391 | 0 | case NESTEGG_VIDEO_STEREO_RIGHT_LEFT: |
392 | 0 | mInfo.mVideo.mStereoMode = StereoMode::RIGHT_LEFT; |
393 | 0 | break; |
394 | 0 | } |
395 | 0 | uint64_t duration = 0; |
396 | 0 | r = nestegg_duration(context, &duration); |
397 | 0 | if (!r) { |
398 | 0 | mInfo.mVideo.mDuration = TimeUnit::FromNanoseconds(duration); |
399 | 0 | } |
400 | 0 | mInfo.mVideo.mCrypto = GetTrackCrypto(TrackInfo::kVideoTrack, track); |
401 | 0 | if (mInfo.mVideo.mCrypto.mValid) { |
402 | 0 | mCrypto.AddInitData(NS_LITERAL_STRING("webm"), |
403 | 0 | mInfo.mVideo.mCrypto.mKeyId); |
404 | 0 | } |
405 | 0 | } else if (type == NESTEGG_TRACK_AUDIO && !mHasAudio) { |
406 | 0 | nestegg_audio_params params; |
407 | 0 | r = nestegg_track_audio_params(context, track, ¶ms); |
408 | 0 | if (r == -1) { |
409 | 0 | return NS_ERROR_FAILURE; |
410 | 0 | } |
411 | 0 | |
412 | 0 | mAudioTrack = track; |
413 | 0 | mHasAudio = true; |
414 | 0 | mAudioCodec = nestegg_track_codec_id(context, track); |
415 | 0 | if (mAudioCodec == NESTEGG_CODEC_VORBIS) { |
416 | 0 | mInfo.mAudio.mMimeType = "audio/vorbis"; |
417 | 0 | } else if (mAudioCodec == NESTEGG_CODEC_OPUS) { |
418 | 0 | mInfo.mAudio.mMimeType = "audio/opus"; |
419 | 0 | OpusDataDecoder::AppendCodecDelay( |
420 | 0 | mInfo.mAudio.mCodecSpecificConfig, |
421 | 0 | TimeUnit::FromNanoseconds(params.codec_delay).ToMicroseconds()); |
422 | 0 | } |
423 | 0 | mSeekPreroll = params.seek_preroll; |
424 | 0 | mInfo.mAudio.mRate = params.rate; |
425 | 0 | mInfo.mAudio.mChannels = params.channels; |
426 | 0 |
|
427 | 0 | unsigned int nheaders = 0; |
428 | 0 | r = nestegg_track_codec_data_count(context, track, &nheaders); |
429 | 0 | if (r == -1) { |
430 | 0 | return NS_ERROR_FAILURE; |
431 | 0 | } |
432 | 0 | |
433 | 0 | AutoTArray<const unsigned char*,4> headers; |
434 | 0 | AutoTArray<size_t,4> headerLens; |
435 | 0 | for (uint32_t header = 0; header < nheaders; ++header) { |
436 | 0 | unsigned char* data = 0; |
437 | 0 | size_t length = 0; |
438 | 0 | r = nestegg_track_codec_data(context, track, header, &data, &length); |
439 | 0 | if (r == -1) { |
440 | 0 | return NS_ERROR_FAILURE; |
441 | 0 | } |
442 | 0 | headers.AppendElement(data); |
443 | 0 | headerLens.AppendElement(length); |
444 | 0 | } |
445 | 0 |
|
446 | 0 | // Vorbis has 3 headers, convert to Xiph extradata format to send them to |
447 | 0 | // the demuxer. |
448 | 0 | // TODO: This is already the format WebM stores them in. Would be nice |
449 | 0 | // to avoid having libnestegg split them only for us to pack them again, |
450 | 0 | // but libnestegg does not give us an API to access this data directly. |
451 | 0 | if (nheaders > 1) { |
452 | 0 | if (!XiphHeadersToExtradata(mInfo.mAudio.mCodecSpecificConfig, |
453 | 0 | headers, headerLens)) { |
454 | 0 | return NS_ERROR_FAILURE; |
455 | 0 | } |
456 | 0 | } |
457 | 0 | else { |
458 | 0 | mInfo.mAudio.mCodecSpecificConfig->AppendElements(headers[0], |
459 | 0 | headerLens[0]); |
460 | 0 | } |
461 | 0 | uint64_t duration = 0; |
462 | 0 | r = nestegg_duration(context, &duration); |
463 | 0 | if (!r) { |
464 | 0 | mInfo.mAudio.mDuration = TimeUnit::FromNanoseconds(duration); |
465 | 0 | } |
466 | 0 | mInfo.mAudio.mCrypto = GetTrackCrypto(TrackInfo::kAudioTrack, track); |
467 | 0 | if (mInfo.mAudio.mCrypto.mValid) { |
468 | 0 | mCrypto.AddInitData(NS_LITERAL_STRING("webm"), |
469 | 0 | mInfo.mAudio.mCrypto.mKeyId); |
470 | 0 | } |
471 | 0 | } |
472 | 0 | } |
473 | 0 | return NS_OK; |
474 | 0 | } |
475 | | |
476 | | bool |
477 | | WebMDemuxer::IsSeekable() const |
478 | 0 | { |
479 | 0 | return Context(TrackInfo::kVideoTrack) && |
480 | 0 | nestegg_has_cues(Context(TrackInfo::kVideoTrack)); |
481 | 0 | } |
482 | | |
483 | | bool |
484 | | WebMDemuxer::IsSeekableOnlyInBufferedRanges() const |
485 | 0 | { |
486 | 0 | return Context(TrackInfo::kVideoTrack) && |
487 | 0 | !nestegg_has_cues(Context(TrackInfo::kVideoTrack)); |
488 | 0 | } |
489 | | |
490 | | void |
491 | | WebMDemuxer::EnsureUpToDateIndex() |
492 | 0 | { |
493 | 0 | if (!mNeedReIndex || !mInitData) { |
494 | 0 | return; |
495 | 0 | } |
496 | 0 | AutoPinned<MediaResource> resource( |
497 | 0 | Resource(TrackInfo::kVideoTrack).GetResource()); |
498 | 0 | MediaByteRangeSet byteRanges; |
499 | 0 | nsresult rv = resource->GetCachedRanges(byteRanges); |
500 | 0 | if (NS_FAILED(rv) || !byteRanges.Length()) { |
501 | 0 | return; |
502 | 0 | } |
503 | 0 | mBufferedState->UpdateIndex(byteRanges, resource); |
504 | 0 |
|
505 | 0 | mNeedReIndex = false; |
506 | 0 |
|
507 | 0 | if (!mIsMediaSource) { |
508 | 0 | return; |
509 | 0 | } |
510 | 0 | mLastWebMBlockOffset = mBufferedState->GetLastBlockOffset(); |
511 | 0 | MOZ_ASSERT(mLastWebMBlockOffset <= resource->GetLength()); |
512 | 0 | } |
513 | | |
514 | | void |
515 | | WebMDemuxer::NotifyDataArrived() |
516 | 0 | { |
517 | 0 | WEBM_DEBUG(""); |
518 | 0 | mNeedReIndex = true; |
519 | 0 | } |
520 | | |
521 | | void |
522 | | WebMDemuxer::NotifyDataRemoved() |
523 | 0 | { |
524 | 0 | mBufferedState->Reset(); |
525 | 0 | if (mInitData) { |
526 | 0 | mBufferedState->NotifyDataArrived(mInitData->Elements(), |
527 | 0 | mInitData->Length(), 0); |
528 | 0 | } |
529 | 0 | mNeedReIndex = true; |
530 | 0 | } |
531 | | |
532 | | UniquePtr<EncryptionInfo> |
533 | | WebMDemuxer::GetCrypto() |
534 | 0 | { |
535 | 0 | return mCrypto.IsEncrypted() ? MakeUnique<EncryptionInfo>(mCrypto) : nullptr; |
536 | 0 | } |
537 | | |
538 | | CryptoTrack |
539 | | WebMDemuxer::GetTrackCrypto(TrackInfo::TrackType aType, size_t aTrackNumber) |
540 | 0 | { |
541 | 0 | const int WEBM_IV_SIZE = 16; |
542 | 0 | const unsigned char * contentEncKeyId; |
543 | 0 | size_t contentEncKeyIdLength; |
544 | 0 | CryptoTrack crypto; |
545 | 0 | nestegg* context = Context(aType); |
546 | 0 |
|
547 | 0 | int r = nestegg_track_content_enc_key_id( |
548 | 0 | context, aTrackNumber, &contentEncKeyId, &contentEncKeyIdLength); |
549 | 0 |
|
550 | 0 | if (r == -1) { |
551 | 0 | WEBM_DEBUG("nestegg_track_content_enc_key_id failed r=%d", r); |
552 | 0 | return crypto; |
553 | 0 | } |
554 | 0 |
|
555 | 0 | uint32_t i; |
556 | 0 | nsTArray<uint8_t> initData; |
557 | 0 | for (i = 0; i < contentEncKeyIdLength; i++) { |
558 | 0 | initData.AppendElement(contentEncKeyId[i]); |
559 | 0 | } |
560 | 0 |
|
561 | 0 | if (!initData.IsEmpty()) { |
562 | 0 | crypto.mValid = true; |
563 | 0 | // crypto.mMode is not used for WebMs |
564 | 0 | crypto.mIVSize = WEBM_IV_SIZE; |
565 | 0 | crypto.mKeyId = std::move(initData); |
566 | 0 | } |
567 | 0 |
|
568 | 0 | return crypto; |
569 | 0 | } |
570 | | |
571 | | nsresult |
572 | | WebMDemuxer::GetNextPacket(TrackInfo::TrackType aType, |
573 | | MediaRawDataQueue *aSamples) |
574 | 0 | { |
575 | 0 | if (mIsMediaSource) { |
576 | 0 | // To ensure mLastWebMBlockOffset is properly up to date. |
577 | 0 | EnsureUpToDateIndex(); |
578 | 0 | } |
579 | 0 |
|
580 | 0 | RefPtr<NesteggPacketHolder> holder; |
581 | 0 | nsresult rv = NextPacket(aType, holder); |
582 | 0 |
|
583 | 0 | if (NS_FAILED(rv)) { |
584 | 0 | return rv; |
585 | 0 | } |
586 | 0 | |
587 | 0 | int r = 0; |
588 | 0 | unsigned int count = 0; |
589 | 0 | r = nestegg_packet_count(holder->Packet(), &count); |
590 | 0 | if (r == -1) { |
591 | 0 | return NS_ERROR_DOM_MEDIA_DEMUXER_ERR; |
592 | 0 | } |
593 | 0 | int64_t tstamp = holder->Timestamp(); |
594 | 0 | int64_t duration = holder->Duration(); |
595 | 0 |
|
596 | 0 | // The end time of this frame is the start time of the next frame. Fetch |
597 | 0 | // the timestamp of the next packet for this track. If we've reached the |
598 | 0 | // end of the resource, use the file's duration as the end time of this |
599 | 0 | // video frame. |
600 | 0 | RefPtr<NesteggPacketHolder> next_holder; |
601 | 0 | rv = NextPacket(aType, next_holder); |
602 | 0 | if (NS_FAILED(rv) && rv != NS_ERROR_DOM_MEDIA_END_OF_STREAM) { |
603 | 0 | return rv; |
604 | 0 | } |
605 | 0 | |
606 | 0 | int64_t next_tstamp = INT64_MIN; |
607 | 0 | auto calculateNextTimestamp = |
608 | 0 | [&] (auto&& pushPacket, auto&& lastFrameTime, auto&& trackEndTime) { |
609 | 0 | if (next_holder) { |
610 | 0 | next_tstamp = next_holder->Timestamp(); |
611 | 0 | (this->*pushPacket)(next_holder); |
612 | 0 | } else if (duration >= 0) { |
613 | 0 | next_tstamp = tstamp + duration; |
614 | 0 | } else if (lastFrameTime.isSome()) { |
615 | 0 | next_tstamp = tstamp + (tstamp - lastFrameTime.ref()); |
616 | 0 | } else if (mIsMediaSource) { |
617 | 0 | (this->*pushPacket)(holder); |
618 | 0 | } else { |
619 | 0 | // If we can't get frame's duration, it means either we need to wait for |
620 | 0 | // more data for MSE case or this is the last frame for file resource case. |
621 | 0 | MOZ_ASSERT(trackEndTime >= tstamp); |
622 | 0 | next_tstamp = trackEndTime; |
623 | 0 | } |
624 | 0 | lastFrameTime = Some(tstamp); |
625 | 0 | }; |
626 | 0 |
|
627 | 0 | if (aType == TrackInfo::kAudioTrack) { |
628 | 0 | calculateNextTimestamp(&WebMDemuxer::PushAudioPacket, |
629 | 0 | mLastAudioFrameTime, |
630 | 0 | mInfo.mAudio.mDuration.ToMicroseconds()); |
631 | 0 | } else { |
632 | 0 | calculateNextTimestamp(&WebMDemuxer::PushVideoPacket, |
633 | 0 | mLastVideoFrameTime, |
634 | 0 | mInfo.mVideo.mDuration.ToMicroseconds()); |
635 | 0 | } |
636 | 0 |
|
637 | 0 | if (mIsMediaSource && next_tstamp == INT64_MIN) { |
638 | 0 | return NS_ERROR_DOM_MEDIA_END_OF_STREAM; |
639 | 0 | } |
640 | 0 | |
641 | 0 | int64_t discardPadding = 0; |
642 | 0 | if (aType == TrackInfo::kAudioTrack) { |
643 | 0 | (void) nestegg_packet_discard_padding(holder->Packet(), &discardPadding); |
644 | 0 | } |
645 | 0 |
|
646 | 0 | int packetEncryption = nestegg_packet_encryption(holder->Packet()); |
647 | 0 |
|
648 | 0 | for (uint32_t i = 0; i < count; ++i) { |
649 | 0 | unsigned char* data = nullptr; |
650 | 0 | size_t length; |
651 | 0 | r = nestegg_packet_data(holder->Packet(), i, &data, &length); |
652 | 0 | if (r == -1) { |
653 | 0 | WEBM_DEBUG("nestegg_packet_data failed r=%d", r); |
654 | 0 | return NS_ERROR_DOM_MEDIA_DEMUXER_ERR; |
655 | 0 | } |
656 | 0 | unsigned char* alphaData = nullptr; |
657 | 0 | size_t alphaLength = 0; |
658 | 0 | // Check packets for alpha information if file has declared alpha frames |
659 | 0 | // may be present. |
660 | 0 | if (mInfo.mVideo.HasAlpha()) { |
661 | 0 | r = nestegg_packet_additional_data(holder->Packet(), |
662 | 0 | 1, |
663 | 0 | &alphaData, |
664 | 0 | &alphaLength); |
665 | 0 | if (r == -1) { |
666 | 0 | WEBM_DEBUG( |
667 | 0 | "nestegg_packet_additional_data failed to retrieve alpha data r=%d", |
668 | 0 | r); |
669 | 0 | } |
670 | 0 | } |
671 | 0 | bool isKeyframe = false; |
672 | 0 | if (aType == TrackInfo::kAudioTrack) { |
673 | 0 | isKeyframe = true; |
674 | 0 | } else if (aType == TrackInfo::kVideoTrack) { |
675 | 0 | if (packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_ENCRYPTED || |
676 | 0 | packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_PARTITIONED) { |
677 | 0 | // Packet is encrypted, can't peek, use packet info |
678 | 0 | isKeyframe = nestegg_packet_has_keyframe(holder->Packet()) |
679 | 0 | == NESTEGG_PACKET_HAS_KEYFRAME_TRUE; |
680 | 0 | } else { |
681 | 0 | MOZ_ASSERT(packetEncryption == |
682 | 0 | NESTEGG_PACKET_HAS_SIGNAL_BYTE_UNENCRYPTED || |
683 | 0 | packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_FALSE, |
684 | 0 | "Unencrypted packet expected"); |
685 | 0 | auto sample = MakeSpan(data, length); |
686 | 0 | auto alphaSample = MakeSpan(alphaData, alphaLength); |
687 | 0 |
|
688 | 0 | switch (mVideoCodec) { |
689 | 0 | case NESTEGG_CODEC_VP8: |
690 | 0 | isKeyframe = VPXDecoder::IsKeyframe(sample, VPXDecoder::Codec::VP8); |
691 | 0 | if (isKeyframe && alphaLength) { |
692 | 0 | isKeyframe = |
693 | 0 | VPXDecoder::IsKeyframe(alphaSample, VPXDecoder::Codec::VP8); |
694 | 0 | } |
695 | 0 | break; |
696 | 0 | case NESTEGG_CODEC_VP9: |
697 | 0 | isKeyframe = VPXDecoder::IsKeyframe(sample, VPXDecoder::Codec::VP9); |
698 | 0 | if (isKeyframe && alphaLength) { |
699 | 0 | isKeyframe = |
700 | 0 | VPXDecoder::IsKeyframe(alphaSample, VPXDecoder::Codec::VP9); |
701 | 0 | } |
702 | 0 | break; |
703 | 0 | #ifdef MOZ_AV1 |
704 | 0 | case NESTEGG_CODEC_AV1: |
705 | 0 | isKeyframe = AOMDecoder::IsKeyframe(sample); |
706 | 0 | if (isKeyframe && alphaLength) { |
707 | 0 | isKeyframe = AOMDecoder::IsKeyframe(alphaSample); |
708 | 0 | } |
709 | 0 | break; |
710 | 0 | #endif |
711 | 0 | default: |
712 | 0 | NS_WARNING("Cannot detect keyframes in unknown WebM video codec"); |
713 | 0 | return NS_ERROR_FAILURE; |
714 | 0 | } |
715 | 0 | if (isKeyframe) { |
716 | 0 | // For both VP8 and VP9, we only look for resolution changes |
717 | 0 | // on keyframes. Other resolution changes are invalid. |
718 | 0 | auto dimensions = gfx::IntSize(0, 0); |
719 | 0 | switch (mVideoCodec) { |
720 | 0 | case NESTEGG_CODEC_VP8: |
721 | 0 | dimensions = VPXDecoder::GetFrameSize(sample, VPXDecoder::Codec::VP8); |
722 | 0 | break; |
723 | 0 | case NESTEGG_CODEC_VP9: |
724 | 0 | dimensions = VPXDecoder::GetFrameSize(sample, VPXDecoder::Codec::VP9); |
725 | 0 | break; |
726 | 0 | #ifdef MOZ_AV1 |
727 | 0 | case NESTEGG_CODEC_AV1: |
728 | 0 | dimensions = AOMDecoder::GetFrameSize(sample); |
729 | 0 | break; |
730 | 0 | #endif |
731 | 0 | } |
732 | 0 | if (mLastSeenFrameSize.isSome() && |
733 | 0 | (dimensions != mLastSeenFrameSize.value())) { |
734 | 0 | mInfo.mVideo.mDisplay = dimensions; |
735 | 0 | mSharedVideoTrackInfo = |
736 | 0 | new TrackInfoSharedPtr(mInfo.mVideo, ++sStreamSourceID); |
737 | 0 | } |
738 | 0 | mLastSeenFrameSize = Some(dimensions); |
739 | 0 | } |
740 | 0 | } |
741 | 0 | } |
742 | 0 |
|
743 | 0 | WEBM_DEBUG("push sample tstamp: %" PRId64 " next_tstamp: %" PRId64 " length: %zu kf: %d", |
744 | 0 | tstamp, next_tstamp, length, isKeyframe); |
745 | 0 | RefPtr<MediaRawData> sample; |
746 | 0 | if (mInfo.mVideo.HasAlpha() && alphaLength != 0) { |
747 | 0 | sample = new MediaRawData(data, length, alphaData, alphaLength); |
748 | 0 | if ((length && !sample->Data()) || (alphaLength && !sample->AlphaData())) { |
749 | 0 | // OOM. |
750 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
751 | 0 | } |
752 | 0 | } else { |
753 | 0 | sample = new MediaRawData(data, length); |
754 | 0 | if (length && !sample->Data()) { |
755 | 0 | // OOM. |
756 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
757 | 0 | } |
758 | 0 | } |
759 | 0 | sample->mTimecode = TimeUnit::FromMicroseconds(tstamp); |
760 | 0 | sample->mTime = TimeUnit::FromMicroseconds(tstamp); |
761 | 0 | sample->mDuration = TimeUnit::FromMicroseconds(next_tstamp - tstamp); |
762 | 0 | sample->mOffset = holder->Offset(); |
763 | 0 | sample->mKeyframe = isKeyframe; |
764 | 0 | if (discardPadding && i == count - 1) { |
765 | 0 | CheckedInt64 discardFrames; |
766 | 0 | if (discardPadding < 0) { |
767 | 0 | // This is an invalid value as discard padding should never be negative. |
768 | 0 | // Set to maximum value so that the decoder will reject it as it's |
769 | 0 | // greater than the number of frames available. |
770 | 0 | discardFrames = INT32_MAX; |
771 | 0 | WEBM_DEBUG("Invalid negative discard padding"); |
772 | 0 | } else { |
773 | 0 | discardFrames = TimeUnitToFrames( |
774 | 0 | TimeUnit::FromNanoseconds(discardPadding), mInfo.mAudio.mRate); |
775 | 0 | } |
776 | 0 | if (discardFrames.isValid()) { |
777 | 0 | sample->mDiscardPadding = discardFrames.value(); |
778 | 0 | } |
779 | 0 | } |
780 | 0 |
|
781 | 0 | if (packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_ENCRYPTED || |
782 | 0 | packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_PARTITIONED) { |
783 | 0 | UniquePtr<MediaRawDataWriter> writer(sample->CreateWriter()); |
784 | 0 | unsigned char const* iv; |
785 | 0 | size_t ivLength; |
786 | 0 | nestegg_packet_iv(holder->Packet(), &iv, &ivLength); |
787 | 0 | writer->mCrypto.mValid = true; |
788 | 0 | writer->mCrypto.mIVSize = ivLength; |
789 | 0 | if (ivLength == 0) { |
790 | 0 | // Frame is not encrypted. This shouldn't happen as it means the |
791 | 0 | // encryption bit is set on a frame with no IV, but we gracefully |
792 | 0 | // handle incase. |
793 | 0 | MOZ_ASSERT_UNREACHABLE( |
794 | 0 | "Unencrypted packets should not have the encryption bit set!"); |
795 | 0 | WEBM_DEBUG("Unencrypted packet with encryption bit set"); |
796 | 0 | writer->mCrypto.mPlainSizes.AppendElement(length); |
797 | 0 | writer->mCrypto.mEncryptedSizes.AppendElement(0); |
798 | 0 | } else { |
799 | 0 | // Frame is encrypted |
800 | 0 | writer->mCrypto.mIV.AppendElements(iv, 8); |
801 | 0 | // Iv from a sample is 64 bits, must be padded with 64 bits more 0s |
802 | 0 | // in compliance with spec |
803 | 0 | for (uint32_t i = 0; i < 8; i++) { |
804 | 0 | writer->mCrypto.mIV.AppendElement(0); |
805 | 0 | } |
806 | 0 |
|
807 | 0 | if (packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_ENCRYPTED) { |
808 | 0 | writer->mCrypto.mPlainSizes.AppendElement(0); |
809 | 0 | writer->mCrypto.mEncryptedSizes.AppendElement(length); |
810 | 0 | } else if (packetEncryption == NESTEGG_PACKET_HAS_SIGNAL_BYTE_PARTITIONED) { |
811 | 0 | uint8_t numPartitions = 0; |
812 | 0 | const uint32_t* partitions = NULL; |
813 | 0 | nestegg_packet_offsets(holder->Packet(), &partitions, &numPartitions); |
814 | 0 |
|
815 | 0 | // WebM stores a list of 'partitions' in the data, which alternate |
816 | 0 | // clear, encrypted. The data in the first partition is always clear. |
817 | 0 | // So, and sample might look as follows: |
818 | 0 | // 00|XXXX|000|XX, where | represents a partition, 0 a clear byte and |
819 | 0 | // X an encrypted byte. If the first bytes in sample are unencrypted, |
820 | 0 | // the first partition will be at zero |XXXX|000|XX. |
821 | 0 | // |
822 | 0 | // As GMP expects the lengths of the clear and encrypted chunks of |
823 | 0 | // data, we calculate these from the difference between the last two |
824 | 0 | // partitions. |
825 | 0 | uint32_t lastOffset = 0; |
826 | 0 | bool encrypted = false; |
827 | 0 |
|
828 | 0 | for (uint8_t i = 0; i < numPartitions; i++) { |
829 | 0 | uint32_t partition = partitions[i]; |
830 | 0 | uint32_t currentLength = partition - lastOffset; |
831 | 0 |
|
832 | 0 | if (encrypted) { |
833 | 0 | writer->mCrypto.mEncryptedSizes.AppendElement(currentLength); |
834 | 0 | } else { |
835 | 0 | writer->mCrypto.mPlainSizes.AppendElement(currentLength); |
836 | 0 | } |
837 | 0 |
|
838 | 0 | encrypted = !encrypted; |
839 | 0 | lastOffset = partition; |
840 | 0 |
|
841 | 0 | assert(lastOffset <= length); |
842 | 0 | } |
843 | 0 |
|
844 | 0 | // Add the data between the last offset and the end of the data. |
845 | 0 | // 000|XXX|000 |
846 | 0 | // ^---^ |
847 | 0 | if (encrypted) { |
848 | 0 | writer->mCrypto.mEncryptedSizes.AppendElement(length - lastOffset); |
849 | 0 | } else { |
850 | 0 | writer->mCrypto.mPlainSizes.AppendElement(length - lastOffset); |
851 | 0 | } |
852 | 0 |
|
853 | 0 | // Make sure we have an equal number of encrypted and plain sizes (GMP |
854 | 0 | // expects this). This simple check is sufficient as there are two |
855 | 0 | // possible cases at this point: |
856 | 0 | // 1. The number of samples are even (so we don't need to do anything) |
857 | 0 | // 2. There is one more clear sample than encrypted samples, so add a |
858 | 0 | // zero length encrypted chunk. |
859 | 0 | // There can never be more encrypted partitions than clear partitions |
860 | 0 | // due to the alternating structure of the WebM samples and the |
861 | 0 | // restriction that the first chunk is always clear. |
862 | 0 | if (numPartitions % 2 == 0) { |
863 | 0 | writer->mCrypto.mEncryptedSizes.AppendElement(0); |
864 | 0 | } |
865 | 0 |
|
866 | 0 | // Assert that the lengths of the encrypted and plain samples add to |
867 | 0 | // the length of the data. |
868 | 0 | assert(((size_t)(std::accumulate(writer->mCrypto.mPlainSizes.begin(), writer->mCrypto.mPlainSizes.end(), 0) \ |
869 | 0 | + std::accumulate(writer->mCrypto.mEncryptedSizes.begin(), writer->mCrypto.mEncryptedSizes.end(), 0)) \ |
870 | 0 | == length)); |
871 | 0 | } |
872 | 0 | } |
873 | 0 | } |
874 | 0 | if (aType == TrackInfo::kVideoTrack) { |
875 | 0 | sample->mTrackInfo = mSharedVideoTrackInfo; |
876 | 0 | } |
877 | 0 | aSamples->Push(sample); |
878 | 0 | } |
879 | 0 | return NS_OK; |
880 | 0 | } |
881 | | |
882 | | nsresult |
883 | | WebMDemuxer::NextPacket(TrackInfo::TrackType aType, |
884 | | RefPtr<NesteggPacketHolder>& aPacket) |
885 | 0 | { |
886 | 0 | bool isVideo = aType == TrackInfo::kVideoTrack; |
887 | 0 |
|
888 | 0 | // Flag to indicate that we do need to playback these types of |
889 | 0 | // packets. |
890 | 0 | bool hasType = isVideo ? mHasVideo : mHasAudio; |
891 | 0 |
|
892 | 0 | if (!hasType) { |
893 | 0 | return NS_ERROR_DOM_MEDIA_DEMUXER_ERR; |
894 | 0 | } |
895 | 0 | |
896 | 0 | // The packet queue for the type that we are interested in. |
897 | 0 | WebMPacketQueue &packets = isVideo ? mVideoPackets : mAudioPackets; |
898 | 0 |
|
899 | 0 | if (packets.GetSize() > 0) { |
900 | 0 | aPacket = packets.PopFront(); |
901 | 0 | return NS_OK; |
902 | 0 | } |
903 | 0 | |
904 | 0 | // Track we are interested in |
905 | 0 | uint32_t ourTrack = isVideo ? mVideoTrack : mAudioTrack; |
906 | 0 |
|
907 | 0 | do { |
908 | 0 | RefPtr<NesteggPacketHolder> holder; |
909 | 0 | nsresult rv = DemuxPacket(aType, holder); |
910 | 0 | if (NS_FAILED(rv)) { |
911 | 0 | return rv; |
912 | 0 | } |
913 | 0 | if (!holder) { |
914 | 0 | return NS_ERROR_DOM_MEDIA_DEMUXER_ERR; |
915 | 0 | } |
916 | 0 | |
917 | 0 | if (ourTrack == holder->Track()) { |
918 | 0 | aPacket = holder; |
919 | 0 | return NS_OK; |
920 | 0 | } |
921 | 0 | } while (true); |
922 | 0 | } |
923 | | |
924 | | nsresult |
925 | | WebMDemuxer::DemuxPacket(TrackInfo::TrackType aType, |
926 | | RefPtr<NesteggPacketHolder>& aPacket) |
927 | 0 | { |
928 | 0 | nestegg_packet* packet; |
929 | 0 | int r = nestegg_read_packet(Context(aType), &packet); |
930 | 0 | if (r == 0) { |
931 | 0 | nestegg_read_reset(Context(aType)); |
932 | 0 | return NS_ERROR_DOM_MEDIA_END_OF_STREAM; |
933 | 0 | } else if (r < 0) { |
934 | 0 | return NS_ERROR_DOM_MEDIA_DEMUXER_ERR; |
935 | 0 | } |
936 | 0 | |
937 | 0 | unsigned int track = 0; |
938 | 0 | r = nestegg_packet_track(packet, &track); |
939 | 0 | if (r == -1) { |
940 | 0 | return NS_ERROR_DOM_MEDIA_DEMUXER_ERR; |
941 | 0 | } |
942 | 0 | |
943 | 0 | int64_t offset = Resource(aType).Tell(); |
944 | 0 | RefPtr<NesteggPacketHolder> holder = new NesteggPacketHolder(); |
945 | 0 | if (!holder->Init(packet, offset, track, false)) { |
946 | 0 | return NS_ERROR_DOM_MEDIA_DEMUXER_ERR; |
947 | 0 | } |
948 | 0 | |
949 | 0 | aPacket = holder; |
950 | 0 | return NS_OK; |
951 | 0 | } |
952 | | |
953 | | void |
954 | | WebMDemuxer::PushAudioPacket(NesteggPacketHolder* aItem) |
955 | 0 | { |
956 | 0 | mAudioPackets.PushFront(aItem); |
957 | 0 | } |
958 | | |
959 | | void |
960 | | WebMDemuxer::PushVideoPacket(NesteggPacketHolder* aItem) |
961 | 0 | { |
962 | 0 | mVideoPackets.PushFront(aItem); |
963 | 0 | } |
964 | | |
965 | | nsresult |
966 | | WebMDemuxer::SeekInternal(TrackInfo::TrackType aType, |
967 | | const TimeUnit& aTarget) |
968 | 0 | { |
969 | 0 | EnsureUpToDateIndex(); |
970 | 0 | uint32_t trackToSeek = mHasVideo ? mVideoTrack : mAudioTrack; |
971 | 0 | uint64_t target = aTarget.ToNanoseconds(); |
972 | 0 |
|
973 | 0 | if (NS_FAILED(Reset(aType))) { |
974 | 0 | return NS_ERROR_FAILURE; |
975 | 0 | } |
976 | 0 | |
977 | 0 | if (mSeekPreroll) { |
978 | 0 | uint64_t startTime = 0; |
979 | 0 | if (!mBufferedState->GetStartTime(&startTime)) { |
980 | 0 | startTime = 0; |
981 | 0 | } |
982 | 0 | WEBM_DEBUG("Seek Target: %f", |
983 | 0 | TimeUnit::FromNanoseconds(target).ToSeconds()); |
984 | 0 | if (target < mSeekPreroll || target - mSeekPreroll < startTime) { |
985 | 0 | target = startTime; |
986 | 0 | } else { |
987 | 0 | target -= mSeekPreroll; |
988 | 0 | } |
989 | 0 | WEBM_DEBUG("SeekPreroll: %f StartTime: %f Adjusted Target: %f", |
990 | 0 | TimeUnit::FromNanoseconds(mSeekPreroll).ToSeconds(), |
991 | 0 | TimeUnit::FromNanoseconds(startTime).ToSeconds(), |
992 | 0 | TimeUnit::FromNanoseconds(target).ToSeconds()); |
993 | 0 | } |
994 | 0 | int r = nestegg_track_seek(Context(aType), trackToSeek, target); |
995 | 0 | if (r == -1) { |
996 | 0 | WEBM_DEBUG("track_seek for track %u to %f failed, r=%d", trackToSeek, |
997 | 0 | TimeUnit::FromNanoseconds(target).ToSeconds(), r); |
998 | 0 | // Try seeking directly based on cluster information in memory. |
999 | 0 | int64_t offset = 0; |
1000 | 0 | bool rv = mBufferedState->GetOffsetForTime(target, &offset); |
1001 | 0 | if (!rv) { |
1002 | 0 | WEBM_DEBUG("mBufferedState->GetOffsetForTime failed too"); |
1003 | 0 | return NS_ERROR_FAILURE; |
1004 | 0 | } |
1005 | 0 |
|
1006 | 0 | r = nestegg_offset_seek(Context(aType), offset); |
1007 | 0 | if (r == -1) { |
1008 | 0 | WEBM_DEBUG("and nestegg_offset_seek to %" PRIu64 " failed", offset); |
1009 | 0 | return NS_ERROR_FAILURE; |
1010 | 0 | } |
1011 | 0 | WEBM_DEBUG("got offset from buffered state: %" PRIu64 "", offset); |
1012 | 0 | } |
1013 | 0 |
|
1014 | 0 | if (aType == TrackInfo::kAudioTrack) { |
1015 | 0 | mLastAudioFrameTime.reset(); |
1016 | 0 | } else { |
1017 | 0 | mLastVideoFrameTime.reset(); |
1018 | 0 | } |
1019 | 0 |
|
1020 | 0 | return NS_OK; |
1021 | 0 | } |
1022 | | |
1023 | | media::TimeIntervals |
1024 | | WebMDemuxer::GetBuffered() |
1025 | 0 | { |
1026 | 0 | EnsureUpToDateIndex(); |
1027 | 0 | AutoPinned<MediaResource> resource( |
1028 | 0 | Resource(TrackInfo::kVideoTrack).GetResource()); |
1029 | 0 |
|
1030 | 0 | media::TimeIntervals buffered; |
1031 | 0 |
|
1032 | 0 | MediaByteRangeSet ranges; |
1033 | 0 | nsresult rv = resource->GetCachedRanges(ranges); |
1034 | 0 | if (NS_FAILED(rv)) { |
1035 | 0 | return media::TimeIntervals(); |
1036 | 0 | } |
1037 | 0 | uint64_t duration = 0; |
1038 | 0 | uint64_t startOffset = 0; |
1039 | 0 | if (!nestegg_duration(Context(TrackInfo::kVideoTrack), &duration)) { |
1040 | 0 | if(mBufferedState->GetStartTime(&startOffset)) { |
1041 | 0 | duration += startOffset; |
1042 | 0 | } |
1043 | 0 | WEBM_DEBUG("Duration: %f StartTime: %f", |
1044 | 0 | TimeUnit::FromNanoseconds(duration).ToSeconds(), |
1045 | 0 | TimeUnit::FromNanoseconds(startOffset).ToSeconds()); |
1046 | 0 | } |
1047 | 0 | for (uint32_t index = 0; index < ranges.Length(); index++) { |
1048 | 0 | uint64_t start, end; |
1049 | 0 | bool rv = mBufferedState->CalculateBufferedForRange(ranges[index].mStart, |
1050 | 0 | ranges[index].mEnd, |
1051 | 0 | &start, &end); |
1052 | 0 | if (rv) { |
1053 | 0 | NS_ASSERTION(startOffset <= start, |
1054 | 0 | "startOffset negative or larger than start time"); |
1055 | 0 |
|
1056 | 0 | if (duration && end > duration) { |
1057 | 0 | WEBM_DEBUG("limit range to duration, end: %f duration: %f", |
1058 | 0 | TimeUnit::FromNanoseconds(end).ToSeconds(), |
1059 | 0 | TimeUnit::FromNanoseconds(duration).ToSeconds()); |
1060 | 0 | end = duration; |
1061 | 0 | } |
1062 | 0 | auto startTime = TimeUnit::FromNanoseconds(start); |
1063 | 0 | auto endTime = TimeUnit::FromNanoseconds(end); |
1064 | 0 | WEBM_DEBUG("add range %f-%f", startTime.ToSeconds(), endTime.ToSeconds()); |
1065 | 0 | buffered += media::TimeInterval(startTime, endTime); |
1066 | 0 | } |
1067 | 0 | } |
1068 | 0 | return buffered; |
1069 | 0 | } |
1070 | | |
1071 | | bool WebMDemuxer::GetOffsetForTime(uint64_t aTime, int64_t* aOffset) |
1072 | 0 | { |
1073 | 0 | EnsureUpToDateIndex(); |
1074 | 0 | return mBufferedState && mBufferedState->GetOffsetForTime(aTime, aOffset); |
1075 | 0 | } |
1076 | | |
1077 | | |
1078 | | //WebMTrackDemuxer |
1079 | | WebMTrackDemuxer::WebMTrackDemuxer(WebMDemuxer* aParent, |
1080 | | TrackInfo::TrackType aType, |
1081 | | uint32_t aTrackNumber) |
1082 | | : mParent(aParent) |
1083 | | , mType(aType) |
1084 | | , mNeedKeyframe(true) |
1085 | 0 | { |
1086 | 0 | mInfo = mParent->GetTrackInfo(aType, aTrackNumber); |
1087 | 0 | MOZ_ASSERT(mInfo); |
1088 | 0 | } |
1089 | | |
1090 | | WebMTrackDemuxer::~WebMTrackDemuxer() |
1091 | 0 | { |
1092 | 0 | mSamples.Reset(); |
1093 | 0 | } |
1094 | | |
1095 | | UniquePtr<TrackInfo> |
1096 | | WebMTrackDemuxer::GetInfo() const |
1097 | 0 | { |
1098 | 0 | return mInfo->Clone(); |
1099 | 0 | } |
1100 | | |
1101 | | RefPtr<WebMTrackDemuxer::SeekPromise> |
1102 | | WebMTrackDemuxer::Seek(const TimeUnit& aTime) |
1103 | 0 | { |
1104 | 0 | // Seeks to aTime. Upon success, SeekPromise will be resolved with the |
1105 | 0 | // actual time seeked to. Typically the random access point time |
1106 | 0 |
|
1107 | 0 | auto seekTime = aTime; |
1108 | 0 | bool keyframe = false; |
1109 | 0 |
|
1110 | 0 | mNeedKeyframe = true; |
1111 | 0 |
|
1112 | 0 | do { |
1113 | 0 | mSamples.Reset(); |
1114 | 0 | mParent->SeekInternal(mType, seekTime); |
1115 | 0 | nsresult rv = mParent->GetNextPacket(mType, &mSamples); |
1116 | 0 | if (NS_FAILED(rv)) { |
1117 | 0 | if (rv == NS_ERROR_DOM_MEDIA_END_OF_STREAM) { |
1118 | 0 | // Ignore the error for now, the next GetSample will be rejected with EOS. |
1119 | 0 | return SeekPromise::CreateAndResolve(TimeUnit::Zero(), __func__); |
1120 | 0 | } |
1121 | 0 | return SeekPromise::CreateAndReject(rv, __func__); |
1122 | 0 | } |
1123 | 0 | |
1124 | 0 | // Check what time we actually seeked to. |
1125 | 0 | if (mSamples.GetSize() == 0) { |
1126 | 0 | // We can't determine if the seek succeeded at this stage, so break the |
1127 | 0 | // loop. |
1128 | 0 | break; |
1129 | 0 | } |
1130 | 0 | |
1131 | 0 | for (const auto& sample : mSamples) { |
1132 | 0 | seekTime = sample->mTime; |
1133 | 0 | keyframe = sample->mKeyframe; |
1134 | 0 | if (keyframe) { |
1135 | 0 | break; |
1136 | 0 | } |
1137 | 0 | } |
1138 | 0 | if (mType == TrackInfo::kVideoTrack && |
1139 | 0 | !mInfo->GetAsVideoInfo()->HasAlpha()) { |
1140 | 0 | // We only perform a search for a keyframe on videos with alpha layer to |
1141 | 0 | // prevent potential regression for normal video (even though invalid) |
1142 | 0 | break; |
1143 | 0 | } |
1144 | 0 | if (!keyframe) { |
1145 | 0 | // We didn't find any keyframe, attempt to seek to the previous cluster. |
1146 | 0 | seekTime = mSamples.First()->mTime - TimeUnit::FromMicroseconds(1); |
1147 | 0 | } |
1148 | 0 | } while (!keyframe && seekTime >= TimeUnit::Zero()); |
1149 | 0 |
|
1150 | 0 | SetNextKeyFrameTime(); |
1151 | 0 |
|
1152 | 0 | return SeekPromise::CreateAndResolve(seekTime, __func__); |
1153 | 0 | } |
1154 | | |
1155 | | nsresult |
1156 | | WebMTrackDemuxer::NextSample(RefPtr<MediaRawData>& aData) |
1157 | 0 | { |
1158 | 0 | nsresult rv = NS_ERROR_DOM_MEDIA_END_OF_STREAM; |
1159 | 0 | while (mSamples.GetSize() < 1 && |
1160 | 0 | NS_SUCCEEDED((rv = mParent->GetNextPacket(mType, &mSamples)))) { |
1161 | 0 | } |
1162 | 0 | if (mSamples.GetSize()) { |
1163 | 0 | aData = mSamples.PopFront(); |
1164 | 0 | return NS_OK; |
1165 | 0 | } |
1166 | 0 | return rv; |
1167 | 0 | } |
1168 | | |
1169 | | RefPtr<WebMTrackDemuxer::SamplesPromise> |
1170 | | WebMTrackDemuxer::GetSamples(int32_t aNumSamples) |
1171 | 0 | { |
1172 | 0 | RefPtr<SamplesHolder> samples = new SamplesHolder; |
1173 | 0 | MOZ_ASSERT(aNumSamples); |
1174 | 0 |
|
1175 | 0 | nsresult rv = NS_ERROR_DOM_MEDIA_END_OF_STREAM; |
1176 | 0 |
|
1177 | 0 | while (aNumSamples) { |
1178 | 0 | RefPtr<MediaRawData> sample; |
1179 | 0 | rv = NextSample(sample); |
1180 | 0 | if (NS_FAILED(rv)) { |
1181 | 0 | break; |
1182 | 0 | } |
1183 | 0 | if (mNeedKeyframe && !sample->mKeyframe) { |
1184 | 0 | continue; |
1185 | 0 | } |
1186 | 0 | mNeedKeyframe = false; |
1187 | 0 | samples->mSamples.AppendElement(sample); |
1188 | 0 | aNumSamples--; |
1189 | 0 | } |
1190 | 0 |
|
1191 | 0 | if (samples->mSamples.IsEmpty()) { |
1192 | 0 | return SamplesPromise::CreateAndReject(rv, __func__); |
1193 | 0 | } else { |
1194 | 0 | UpdateSamples(samples->mSamples); |
1195 | 0 | return SamplesPromise::CreateAndResolve(samples, __func__); |
1196 | 0 | } |
1197 | 0 | } |
1198 | | |
1199 | | void |
1200 | | WebMTrackDemuxer::SetNextKeyFrameTime() |
1201 | 0 | { |
1202 | 0 | if (mType != TrackInfo::kVideoTrack || mParent->IsMediaSource()) { |
1203 | 0 | return; |
1204 | 0 | } |
1205 | 0 | |
1206 | 0 | auto frameTime = TimeUnit::Invalid(); |
1207 | 0 |
|
1208 | 0 | mNextKeyframeTime.reset(); |
1209 | 0 |
|
1210 | 0 | MediaRawDataQueue skipSamplesQueue; |
1211 | 0 | bool foundKeyframe = false; |
1212 | 0 | while (!foundKeyframe && mSamples.GetSize()) { |
1213 | 0 | RefPtr<MediaRawData> sample = mSamples.PopFront(); |
1214 | 0 | if (sample->mKeyframe) { |
1215 | 0 | frameTime = sample->mTime; |
1216 | 0 | foundKeyframe = true; |
1217 | 0 | } |
1218 | 0 | skipSamplesQueue.Push(sample.forget()); |
1219 | 0 | } |
1220 | 0 | Maybe<int64_t> startTime; |
1221 | 0 | if (skipSamplesQueue.GetSize()) { |
1222 | 0 | const RefPtr<MediaRawData>& sample = skipSamplesQueue.First(); |
1223 | 0 | startTime.emplace(sample->mTimecode.ToMicroseconds()); |
1224 | 0 | } |
1225 | 0 | // Demux and buffer frames until we find a keyframe. |
1226 | 0 | RefPtr<MediaRawData> sample; |
1227 | 0 | nsresult rv = NS_OK; |
1228 | 0 | while (!foundKeyframe && NS_SUCCEEDED((rv = NextSample(sample)))) { |
1229 | 0 | if (sample->mKeyframe) { |
1230 | 0 | frameTime = sample->mTime; |
1231 | 0 | foundKeyframe = true; |
1232 | 0 | } |
1233 | 0 | int64_t sampleTimecode = sample->mTimecode.ToMicroseconds(); |
1234 | 0 | skipSamplesQueue.Push(sample.forget()); |
1235 | 0 | if (!startTime) { |
1236 | 0 | startTime.emplace(sampleTimecode); |
1237 | 0 | } else if (!foundKeyframe && |
1238 | 0 | sampleTimecode > startTime.ref() + MAX_LOOK_AHEAD) { |
1239 | 0 | WEBM_DEBUG("Couldn't find keyframe in a reasonable time, aborting"); |
1240 | 0 | break; |
1241 | 0 | } |
1242 | 0 | } |
1243 | 0 | // We may have demuxed more than intended, so ensure that all frames are kept |
1244 | 0 | // in the right order. |
1245 | 0 | mSamples.PushFront(std::move(skipSamplesQueue)); |
1246 | 0 |
|
1247 | 0 | if (frameTime.IsValid()) { |
1248 | 0 | mNextKeyframeTime.emplace(frameTime); |
1249 | 0 | WEBM_DEBUG("Next Keyframe %f (%u queued %.02fs)", |
1250 | 0 | mNextKeyframeTime.value().ToSeconds(), |
1251 | 0 | uint32_t(mSamples.GetSize()), |
1252 | 0 | (mSamples.Last()->mTimecode - mSamples.First()->mTimecode).ToSeconds()); |
1253 | 0 | } else { |
1254 | 0 | WEBM_DEBUG("Couldn't determine next keyframe time (%u queued)", |
1255 | 0 | uint32_t(mSamples.GetSize())); |
1256 | 0 | } |
1257 | 0 | } |
1258 | | |
1259 | | void |
1260 | | WebMTrackDemuxer::Reset() |
1261 | 0 | { |
1262 | 0 | mSamples.Reset(); |
1263 | 0 | media::TimeIntervals buffered = GetBuffered(); |
1264 | 0 | mNeedKeyframe = true; |
1265 | 0 | if (buffered.Length()) { |
1266 | 0 | WEBM_DEBUG("Seek to start point: %f", buffered.Start(0).ToSeconds()); |
1267 | 0 | mParent->SeekInternal(mType, buffered.Start(0)); |
1268 | 0 | SetNextKeyFrameTime(); |
1269 | 0 | } else { |
1270 | 0 | mNextKeyframeTime.reset(); |
1271 | 0 | } |
1272 | 0 | } |
1273 | | |
1274 | | void |
1275 | | WebMTrackDemuxer::UpdateSamples(nsTArray<RefPtr<MediaRawData>>& aSamples) |
1276 | 0 | { |
1277 | 0 | for (const auto& sample : aSamples) { |
1278 | 0 | if (sample->mCrypto.mValid) { |
1279 | 0 | UniquePtr<MediaRawDataWriter> writer(sample->CreateWriter()); |
1280 | 0 | writer->mCrypto.mMode = mInfo->mCrypto.mMode; |
1281 | 0 | writer->mCrypto.mIVSize = mInfo->mCrypto.mIVSize; |
1282 | 0 | writer->mCrypto.mKeyId.AppendElements(mInfo->mCrypto.mKeyId); |
1283 | 0 | } |
1284 | 0 | } |
1285 | 0 | if (mNextKeyframeTime.isNothing() || |
1286 | 0 | aSamples.LastElement()->mTime >= mNextKeyframeTime.value()) { |
1287 | 0 | SetNextKeyFrameTime(); |
1288 | 0 | } |
1289 | 0 | } |
1290 | | |
1291 | | nsresult |
1292 | | WebMTrackDemuxer::GetNextRandomAccessPoint(TimeUnit* aTime) |
1293 | 0 | { |
1294 | 0 | if (mNextKeyframeTime.isNothing()) { |
1295 | 0 | // There's no next key frame. |
1296 | 0 | *aTime = TimeUnit::FromInfinity(); |
1297 | 0 | } else { |
1298 | 0 | *aTime = mNextKeyframeTime.ref(); |
1299 | 0 | } |
1300 | 0 | return NS_OK; |
1301 | 0 | } |
1302 | | |
1303 | | RefPtr<WebMTrackDemuxer::SkipAccessPointPromise> |
1304 | | WebMTrackDemuxer::SkipToNextRandomAccessPoint(const TimeUnit& aTimeThreshold) |
1305 | 0 | { |
1306 | 0 | uint32_t parsed = 0; |
1307 | 0 | bool found = false; |
1308 | 0 | RefPtr<MediaRawData> sample; |
1309 | 0 | nsresult rv = NS_OK; |
1310 | 0 |
|
1311 | 0 | WEBM_DEBUG("TimeThreshold: %f", aTimeThreshold.ToSeconds()); |
1312 | 0 | while (!found && NS_SUCCEEDED((rv = NextSample(sample)))) { |
1313 | 0 | parsed++; |
1314 | 0 | if (sample->mKeyframe && sample->mTime >= aTimeThreshold) { |
1315 | 0 | WEBM_DEBUG("next sample: %f (parsed: %d)", |
1316 | 0 | sample->mTime.ToSeconds(), parsed); |
1317 | 0 | found = true; |
1318 | 0 | mSamples.Reset(); |
1319 | 0 | mSamples.PushFront(sample.forget()); |
1320 | 0 | } |
1321 | 0 | } |
1322 | 0 | if (NS_SUCCEEDED(rv)) { |
1323 | 0 | SetNextKeyFrameTime(); |
1324 | 0 | } |
1325 | 0 | if (found) { |
1326 | 0 | return SkipAccessPointPromise::CreateAndResolve(parsed, __func__); |
1327 | 0 | } else { |
1328 | 0 | SkipFailureHolder failure(NS_ERROR_DOM_MEDIA_END_OF_STREAM, parsed); |
1329 | 0 | return SkipAccessPointPromise::CreateAndReject(std::move(failure), __func__); |
1330 | 0 | } |
1331 | 0 | } |
1332 | | |
1333 | | media::TimeIntervals |
1334 | | WebMTrackDemuxer::GetBuffered() |
1335 | 0 | { |
1336 | 0 | return mParent->GetBuffered(); |
1337 | 0 | } |
1338 | | |
1339 | | void |
1340 | | WebMTrackDemuxer::BreakCycles() |
1341 | 0 | { |
1342 | 0 | mParent = nullptr; |
1343 | 0 | } |
1344 | | |
1345 | | int64_t |
1346 | | WebMTrackDemuxer::GetEvictionOffset(const TimeUnit& aTime) |
1347 | 0 | { |
1348 | 0 | int64_t offset; |
1349 | 0 | if (!mParent->GetOffsetForTime(aTime.ToNanoseconds(), &offset)) { |
1350 | 0 | return 0; |
1351 | 0 | } |
1352 | 0 | |
1353 | 0 | return offset; |
1354 | 0 | } |
1355 | | |
1356 | | #undef WEBM_DEBUG |
1357 | | } // namespace mozilla |