/src/mozilla-central/dom/media/mediasource/ContainerParser.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
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 "ContainerParser.h" |
8 | | |
9 | | #include "WebMBufferedParser.h" |
10 | | #include "mozilla/EndianUtils.h" |
11 | | #include "mozilla/IntegerPrintfMacros.h" |
12 | | #include "mozilla/ErrorResult.h" |
13 | | #include "MoofParser.h" |
14 | | #include "mozilla/Logging.h" |
15 | | #include "mozilla/Maybe.h" |
16 | | #include "mozilla/Result.h" |
17 | | #include "MediaData.h" |
18 | | #include "nsMimeTypes.h" |
19 | | #ifdef MOZ_FMP4 |
20 | | #include "AtomType.h" |
21 | | #include "BufferReader.h" |
22 | | #include "Index.h" |
23 | | #include "MP4Interval.h" |
24 | | #include "ByteStream.h" |
25 | | #endif |
26 | | #include "nsAutoPtr.h" |
27 | | #include "SourceBufferResource.h" |
28 | | #include <algorithm> |
29 | | |
30 | | extern mozilla::LogModule* GetMediaSourceSamplesLog(); |
31 | | |
32 | | #define STRINGIFY(x) #x |
33 | | #define TOSTRING(x) STRINGIFY(x) |
34 | | #define MSE_DEBUG(arg, ...) \ |
35 | 0 | DDMOZ_LOG(GetMediaSourceSamplesLog(), \ |
36 | 0 | mozilla::LogLevel::Debug, \ |
37 | 0 | "(%s)::%s: " arg, \ |
38 | 0 | mType.OriginalString().Data(), \ |
39 | 0 | __func__, \ |
40 | 0 | ##__VA_ARGS__) |
41 | | #define MSE_DEBUGV(arg, ...) \ |
42 | 0 | DDMOZ_LOG(GetMediaSourceSamplesLog(), \ |
43 | 0 | mozilla::LogLevel::Verbose, \ |
44 | 0 | "(%s)::%s: " arg, \ |
45 | 0 | mType.OriginalString().Data(), \ |
46 | 0 | __func__, \ |
47 | 0 | ##__VA_ARGS__) |
48 | | #define MSE_DEBUGVEX(_this, arg, ...) \ |
49 | 0 | DDMOZ_LOGEX(_this, \ |
50 | 0 | GetMediaSourceSamplesLog(), \ |
51 | 0 | mozilla::LogLevel::Verbose, \ |
52 | 0 | "(%s)::%s: " arg, \ |
53 | 0 | mType.OriginalString().Data(), \ |
54 | 0 | __func__, \ |
55 | 0 | ##__VA_ARGS__) |
56 | | |
57 | | namespace mozilla { |
58 | | |
59 | | ContainerParser::ContainerParser(const MediaContainerType& aType) |
60 | | : mHasInitData(false) |
61 | | , mTotalParsed(0) |
62 | | , mGlobalOffset(0) |
63 | | , mType(aType) |
64 | 0 | { |
65 | 0 | } |
66 | | |
67 | 0 | ContainerParser::~ContainerParser() = default; |
68 | | |
69 | | MediaResult |
70 | | ContainerParser::IsInitSegmentPresent(MediaByteBuffer* aData) |
71 | 0 | { |
72 | 0 | MSE_DEBUG("aLength=%zu [%x%x%x%x]", |
73 | 0 | aData->Length(), |
74 | 0 | aData->Length() > 0 ? (*aData)[0] : 0, |
75 | 0 | aData->Length() > 1 ? (*aData)[1] : 0, |
76 | 0 | aData->Length() > 2 ? (*aData)[2] : 0, |
77 | 0 | aData->Length() > 3 ? (*aData)[3] : 0); |
78 | 0 | return NS_ERROR_NOT_AVAILABLE; |
79 | 0 | } |
80 | | |
81 | | MediaResult |
82 | | ContainerParser::IsMediaSegmentPresent(MediaByteBuffer* aData) |
83 | 0 | { |
84 | 0 | MSE_DEBUG("aLength=%zu [%x%x%x%x]", |
85 | 0 | aData->Length(), |
86 | 0 | aData->Length() > 0 ? (*aData)[0] : 0, |
87 | 0 | aData->Length() > 1 ? (*aData)[1] : 0, |
88 | 0 | aData->Length() > 2 ? (*aData)[2] : 0, |
89 | 0 | aData->Length() > 3 ? (*aData)[3] : 0); |
90 | 0 | return NS_ERROR_NOT_AVAILABLE; |
91 | 0 | } |
92 | | |
93 | | MediaResult |
94 | | ContainerParser::ParseStartAndEndTimestamps(MediaByteBuffer* aData, |
95 | | int64_t& aStart, int64_t& aEnd) |
96 | 0 | { |
97 | 0 | return NS_ERROR_NOT_AVAILABLE; |
98 | 0 | } |
99 | | |
100 | | bool |
101 | | ContainerParser::TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs) |
102 | 0 | { |
103 | 0 | return llabs(aLhs - aRhs) <= GetRoundingError(); |
104 | 0 | } |
105 | | |
106 | | int64_t |
107 | | ContainerParser::GetRoundingError() |
108 | 0 | { |
109 | 0 | NS_WARNING("Using default ContainerParser::GetRoundingError implementation"); |
110 | 0 | return 0; |
111 | 0 | } |
112 | | |
113 | | bool |
114 | | ContainerParser::HasCompleteInitData() |
115 | 0 | { |
116 | 0 | return mHasInitData && !!mInitData->Length(); |
117 | 0 | } |
118 | | |
119 | | MediaByteBuffer* |
120 | | ContainerParser::InitData() |
121 | 0 | { |
122 | 0 | return mInitData; |
123 | 0 | } |
124 | | |
125 | | MediaByteRange |
126 | | ContainerParser::InitSegmentRange() |
127 | 0 | { |
128 | 0 | return mCompleteInitSegmentRange; |
129 | 0 | } |
130 | | |
131 | | MediaByteRange |
132 | | ContainerParser::MediaHeaderRange() |
133 | 0 | { |
134 | 0 | return mCompleteMediaHeaderRange; |
135 | 0 | } |
136 | | |
137 | | MediaByteRange |
138 | | ContainerParser::MediaSegmentRange() |
139 | 0 | { |
140 | 0 | return mCompleteMediaSegmentRange; |
141 | 0 | } |
142 | | |
143 | | DDLoggedTypeDeclNameAndBase(WebMContainerParser, ContainerParser); |
144 | | |
145 | | class WebMContainerParser |
146 | | : public ContainerParser |
147 | | , public DecoderDoctorLifeLogger<WebMContainerParser> |
148 | | { |
149 | | public: |
150 | | explicit WebMContainerParser(const MediaContainerType& aType) |
151 | | : ContainerParser(aType) |
152 | | , mParser(0) |
153 | | , mOffset(0) |
154 | 0 | { |
155 | 0 | } |
156 | | |
157 | | static const unsigned NS_PER_USEC = 1000; |
158 | | |
159 | | MediaResult IsInitSegmentPresent(MediaByteBuffer* aData) override |
160 | 0 | { |
161 | 0 | ContainerParser::IsInitSegmentPresent(aData); |
162 | 0 | if (aData->Length() < 4) { |
163 | 0 | return NS_ERROR_NOT_AVAILABLE; |
164 | 0 | } |
165 | 0 | |
166 | 0 | WebMBufferedParser parser(0); |
167 | 0 | nsTArray<WebMTimeDataOffset> mapping; |
168 | 0 | ReentrantMonitor dummy("dummy"); |
169 | 0 | bool result = parser.Append(aData->Elements(), aData->Length(), mapping, |
170 | 0 | dummy); |
171 | 0 | if (!result) { |
172 | 0 | return MediaResult(NS_ERROR_FAILURE, RESULT_DETAIL("Invalid webm content")); |
173 | 0 | } |
174 | 0 | return parser.mInitEndOffset > 0 ? NS_OK : NS_ERROR_NOT_AVAILABLE; |
175 | 0 | } |
176 | | |
177 | | MediaResult IsMediaSegmentPresent(MediaByteBuffer* aData) override |
178 | 0 | { |
179 | 0 | ContainerParser::IsMediaSegmentPresent(aData); |
180 | 0 | if (aData->Length() < 4) { |
181 | 0 | return NS_ERROR_NOT_AVAILABLE; |
182 | 0 | } |
183 | 0 | |
184 | 0 | WebMBufferedParser parser(0); |
185 | 0 | nsTArray<WebMTimeDataOffset> mapping; |
186 | 0 | ReentrantMonitor dummy("dummy"); |
187 | 0 | parser.AppendMediaSegmentOnly(); |
188 | 0 | bool result = parser.Append(aData->Elements(), aData->Length(), mapping, |
189 | 0 | dummy); |
190 | 0 | if (!result) { |
191 | 0 | return MediaResult(NS_ERROR_FAILURE, RESULT_DETAIL("Invalid webm content")); |
192 | 0 | } |
193 | 0 | return parser.GetClusterOffset() >= 0 ? NS_OK : NS_ERROR_NOT_AVAILABLE; |
194 | 0 | } |
195 | | |
196 | | MediaResult ParseStartAndEndTimestamps(MediaByteBuffer* aData, |
197 | | int64_t& aStart, |
198 | | int64_t& aEnd) override |
199 | 0 | { |
200 | 0 | bool initSegment = NS_SUCCEEDED(IsInitSegmentPresent(aData)); |
201 | 0 |
|
202 | 0 | if (mLastMapping && |
203 | 0 | (initSegment || NS_SUCCEEDED(IsMediaSegmentPresent(aData)))) { |
204 | 0 | // The last data contained a complete cluster but we can only detect it |
205 | 0 | // now that a new one is starting. |
206 | 0 | // We use mOffset as end position to ensure that any blocks not reported |
207 | 0 | // by WebMBufferParser are properly skipped. |
208 | 0 | mCompleteMediaSegmentRange = |
209 | 0 | MediaByteRange(mLastMapping.ref().mSyncOffset, mOffset) + mGlobalOffset; |
210 | 0 | mLastMapping.reset(); |
211 | 0 | MSE_DEBUG("New cluster found at start, ending previous one"); |
212 | 0 | return NS_ERROR_NOT_AVAILABLE; |
213 | 0 | } |
214 | 0 |
|
215 | 0 | if (initSegment) { |
216 | 0 | mOffset = 0; |
217 | 0 | mParser = WebMBufferedParser(0); |
218 | 0 | mOverlappedMapping.Clear(); |
219 | 0 | mInitData = new MediaByteBuffer(); |
220 | 0 | mResource = new SourceBufferResource(); |
221 | 0 | DDLINKCHILD("resource", mResource.get()); |
222 | 0 | mCompleteInitSegmentRange = MediaByteRange(); |
223 | 0 | mCompleteMediaHeaderRange = MediaByteRange(); |
224 | 0 | mCompleteMediaSegmentRange = MediaByteRange(); |
225 | 0 | mGlobalOffset = mTotalParsed; |
226 | 0 | } |
227 | 0 |
|
228 | 0 | // XXX if it only adds new mappings, overlapped but not available |
229 | 0 | // (e.g. overlap < 0) frames are "lost" from the reported mappings here. |
230 | 0 | nsTArray<WebMTimeDataOffset> mapping; |
231 | 0 | mapping.AppendElements(mOverlappedMapping); |
232 | 0 | mOverlappedMapping.Clear(); |
233 | 0 | ReentrantMonitor dummy("dummy"); |
234 | 0 | mParser.Append(aData->Elements(), aData->Length(), mapping, dummy); |
235 | 0 | if (mResource) { |
236 | 0 | mResource->AppendData(aData); |
237 | 0 | } |
238 | 0 |
|
239 | 0 | // XXX This is a bit of a hack. Assume if there are no timecodes |
240 | 0 | // present and it's an init segment that it's _just_ an init segment. |
241 | 0 | // We should be more precise. |
242 | 0 | if (initSegment || !HasCompleteInitData()) { |
243 | 0 | if (mParser.mInitEndOffset > 0) { |
244 | 0 | MOZ_ASSERT(mParser.mInitEndOffset <= mResource->GetLength()); |
245 | 0 | if (!mInitData->SetLength(mParser.mInitEndOffset, fallible)) { |
246 | 0 | // Super unlikely OOM |
247 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
248 | 0 | } |
249 | 0 | mCompleteInitSegmentRange = |
250 | 0 | MediaByteRange(0, mParser.mInitEndOffset) + mGlobalOffset; |
251 | 0 | char* buffer = reinterpret_cast<char*>(mInitData->Elements()); |
252 | 0 | mResource->ReadFromCache(buffer, 0, mParser.mInitEndOffset); |
253 | 0 | MSE_DEBUG("Stashed init of %" PRId64 " bytes.", mParser.mInitEndOffset); |
254 | 0 | mResource = nullptr; |
255 | 0 | } else { |
256 | 0 | MSE_DEBUG("Incomplete init found."); |
257 | 0 | } |
258 | 0 | mHasInitData = true; |
259 | 0 | } |
260 | 0 | mOffset += aData->Length(); |
261 | 0 | mTotalParsed += aData->Length(); |
262 | 0 |
|
263 | 0 | if (mapping.IsEmpty()) { |
264 | 0 | return NS_ERROR_NOT_AVAILABLE; |
265 | 0 | } |
266 | 0 | |
267 | 0 | // Calculate media range for first media segment. |
268 | 0 | |
269 | 0 | // Check if we have a cluster finishing in the current data. |
270 | 0 | uint32_t endIdx = mapping.Length() - 1; |
271 | 0 | bool foundNewCluster = false; |
272 | 0 | while (mapping[0].mSyncOffset != mapping[endIdx].mSyncOffset) { |
273 | 0 | endIdx -= 1; |
274 | 0 | foundNewCluster = true; |
275 | 0 | } |
276 | 0 |
|
277 | 0 | int32_t completeIdx = endIdx; |
278 | 0 | while (completeIdx >= 0 && mOffset < mapping[completeIdx].mEndOffset) { |
279 | 0 | MSE_DEBUG("block is incomplete, missing: %" PRId64, |
280 | 0 | mapping[completeIdx].mEndOffset - mOffset); |
281 | 0 | completeIdx -= 1; |
282 | 0 | } |
283 | 0 |
|
284 | 0 | // Save parsed blocks for which we do not have all data yet. |
285 | 0 | mOverlappedMapping.AppendElements(mapping.Elements() + completeIdx + 1, |
286 | 0 | mapping.Length() - completeIdx - 1); |
287 | 0 |
|
288 | 0 | if (completeIdx < 0) { |
289 | 0 | mLastMapping.reset(); |
290 | 0 | return NS_ERROR_NOT_AVAILABLE; |
291 | 0 | } |
292 | 0 | |
293 | 0 | if (mCompleteMediaHeaderRange.IsEmpty()) { |
294 | 0 | mCompleteMediaHeaderRange = |
295 | 0 | MediaByteRange(mapping[0].mSyncOffset, mapping[0].mEndOffset) + |
296 | 0 | mGlobalOffset; |
297 | 0 | } |
298 | 0 |
|
299 | 0 | if (foundNewCluster && mOffset >= mapping[endIdx].mEndOffset) { |
300 | 0 | // We now have all information required to delimit a complete cluster. |
301 | 0 | int64_t endOffset = mapping[endIdx+1].mSyncOffset; |
302 | 0 | if (mapping[endIdx+1].mInitOffset > mapping[endIdx].mInitOffset) { |
303 | 0 | // We have a new init segment before this cluster. |
304 | 0 | endOffset = mapping[endIdx+1].mInitOffset; |
305 | 0 | } |
306 | 0 | mCompleteMediaSegmentRange = |
307 | 0 | MediaByteRange(mapping[endIdx].mSyncOffset, endOffset) + mGlobalOffset; |
308 | 0 | } else if (mapping[endIdx].mClusterEndOffset >= 0 && |
309 | 0 | mOffset >= mapping[endIdx].mClusterEndOffset) { |
310 | 0 | mCompleteMediaSegmentRange = |
311 | 0 | MediaByteRange( |
312 | 0 | mapping[endIdx].mSyncOffset, |
313 | 0 | mParser.EndSegmentOffset(mapping[endIdx].mClusterEndOffset)) |
314 | 0 | + mGlobalOffset; |
315 | 0 | } |
316 | 0 |
|
317 | 0 | Maybe<WebMTimeDataOffset> previousMapping; |
318 | 0 | if (completeIdx) { |
319 | 0 | previousMapping = Some(mapping[completeIdx - 1]); |
320 | 0 | } else { |
321 | 0 | previousMapping = mLastMapping; |
322 | 0 | } |
323 | 0 |
|
324 | 0 | mLastMapping = Some(mapping[completeIdx]); |
325 | 0 |
|
326 | 0 | if (!previousMapping && completeIdx + 1u >= mapping.Length()) { |
327 | 0 | // We have no previous nor next block available, |
328 | 0 | // so we can't estimate this block's duration. |
329 | 0 | return NS_ERROR_NOT_AVAILABLE; |
330 | 0 | } |
331 | 0 | |
332 | 0 | uint64_t frameDuration = |
333 | 0 | (completeIdx + 1u < mapping.Length()) |
334 | 0 | ? mapping[completeIdx + 1].mTimecode - mapping[completeIdx].mTimecode |
335 | 0 | : mapping[completeIdx].mTimecode - previousMapping.ref().mTimecode; |
336 | 0 | aStart = mapping[0].mTimecode / NS_PER_USEC; |
337 | 0 | aEnd = (mapping[completeIdx].mTimecode + frameDuration) / NS_PER_USEC; |
338 | 0 |
|
339 | 0 | MSE_DEBUG("[%" PRId64 ", %" PRId64 "] [fso=%" PRId64 ", leo=%" PRId64 |
340 | 0 | ", l=%zu processedIdx=%u fs=%" PRId64 "]", |
341 | 0 | aStart, |
342 | 0 | aEnd, |
343 | 0 | mapping[0].mSyncOffset, |
344 | 0 | mapping[completeIdx].mEndOffset, |
345 | 0 | mapping.Length(), |
346 | 0 | completeIdx, |
347 | 0 | mCompleteMediaSegmentRange.mEnd); |
348 | 0 |
|
349 | 0 | return NS_OK; |
350 | 0 | } |
351 | | |
352 | | int64_t GetRoundingError() override |
353 | 0 | { |
354 | 0 | int64_t error = mParser.GetTimecodeScale() / NS_PER_USEC; |
355 | 0 | return error * 2; |
356 | 0 | } |
357 | | |
358 | | private: |
359 | | WebMBufferedParser mParser; |
360 | | nsTArray<WebMTimeDataOffset> mOverlappedMapping; |
361 | | int64_t mOffset; |
362 | | Maybe<WebMTimeDataOffset> mLastMapping; |
363 | | }; |
364 | | |
365 | | #ifdef MOZ_FMP4 |
366 | | |
367 | | DDLoggedTypeDeclNameAndBase(MP4Stream, ByteStream); |
368 | | |
369 | | class MP4Stream |
370 | | : public ByteStream |
371 | | , public DecoderDoctorLifeLogger<MP4Stream> |
372 | | { |
373 | | public: |
374 | | explicit MP4Stream(SourceBufferResource* aResource); |
375 | | virtual ~MP4Stream(); |
376 | | bool ReadAt(int64_t aOffset, |
377 | | void* aBuffer, |
378 | | size_t aCount, |
379 | | size_t* aBytesRead) override; |
380 | | bool CachedReadAt(int64_t aOffset, |
381 | | void* aBuffer, |
382 | | size_t aCount, |
383 | | size_t* aBytesRead) override; |
384 | | bool Length(int64_t* aSize) override; |
385 | | |
386 | | private: |
387 | | RefPtr<SourceBufferResource> mResource; |
388 | | }; |
389 | | |
390 | | MP4Stream::MP4Stream(SourceBufferResource* aResource) |
391 | | : mResource(aResource) |
392 | 0 | { |
393 | 0 | MOZ_COUNT_CTOR(MP4Stream); |
394 | 0 | MOZ_ASSERT(aResource); |
395 | 0 | DDLINKCHILD("resource", aResource); |
396 | 0 | } |
397 | | |
398 | | MP4Stream::~MP4Stream() |
399 | 0 | { |
400 | 0 | MOZ_COUNT_DTOR(MP4Stream); |
401 | 0 | } |
402 | | |
403 | | bool |
404 | | MP4Stream::ReadAt(int64_t aOffset, |
405 | | void* aBuffer, |
406 | | size_t aCount, |
407 | | size_t* aBytesRead) |
408 | 0 | { |
409 | 0 | return CachedReadAt(aOffset, aBuffer, aCount, aBytesRead); |
410 | 0 | } |
411 | | |
412 | | bool |
413 | | MP4Stream::CachedReadAt(int64_t aOffset, |
414 | | void* aBuffer, |
415 | | size_t aCount, |
416 | | size_t* aBytesRead) |
417 | 0 | { |
418 | 0 | nsresult rv = mResource->ReadFromCache( |
419 | 0 | reinterpret_cast<char*>(aBuffer), aOffset, aCount); |
420 | 0 | if (NS_FAILED(rv)) { |
421 | 0 | *aBytesRead = 0; |
422 | 0 | return false; |
423 | 0 | } |
424 | 0 | *aBytesRead = aCount; |
425 | 0 | return true; |
426 | 0 | } |
427 | | |
428 | | bool |
429 | | MP4Stream::Length(int64_t* aSize) |
430 | 0 | { |
431 | 0 | if (mResource->GetLength() < 0) |
432 | 0 | return false; |
433 | 0 | *aSize = mResource->GetLength(); |
434 | 0 | return true; |
435 | 0 | } |
436 | | |
437 | | DDLoggedTypeDeclNameAndBase(MP4ContainerParser, ContainerParser); |
438 | | |
439 | | class MP4ContainerParser |
440 | | : public ContainerParser |
441 | | , public DecoderDoctorLifeLogger<MP4ContainerParser> |
442 | | { |
443 | | public: |
444 | | explicit MP4ContainerParser(const MediaContainerType& aType) |
445 | | : ContainerParser(aType) |
446 | 0 | { |
447 | 0 | } |
448 | | |
449 | | MediaResult IsInitSegmentPresent(MediaByteBuffer* aData) override |
450 | 0 | { |
451 | 0 | ContainerParser::IsInitSegmentPresent(aData); |
452 | 0 | // Each MP4 atom has a chunk size and chunk type. The root chunk in an MP4 |
453 | 0 | // file is the 'ftyp' atom followed by a file type. We just check for a |
454 | 0 | // vaguely valid 'ftyp' atom. |
455 | 0 | if (aData->Length() < 8) { |
456 | 0 | return NS_ERROR_NOT_AVAILABLE; |
457 | 0 | } |
458 | 0 | AtomParser parser(*this, aData, AtomParser::StopAt::eInitSegment); |
459 | 0 | if (!parser.IsValid()) { |
460 | 0 | return MediaResult( |
461 | 0 | NS_ERROR_FAILURE, |
462 | 0 | RESULT_DETAIL("Invalid Top-Level Box:%s", parser.LastInvalidBox())); |
463 | 0 | } |
464 | 0 | return parser.StartWithInitSegment() ? NS_OK : NS_ERROR_NOT_AVAILABLE; |
465 | 0 | } |
466 | | |
467 | | MediaResult IsMediaSegmentPresent(MediaByteBuffer* aData) override |
468 | 0 | { |
469 | 0 | if (aData->Length() < 8) { |
470 | 0 | return NS_ERROR_NOT_AVAILABLE; |
471 | 0 | } |
472 | 0 | AtomParser parser(*this, aData, AtomParser::StopAt::eMediaSegment); |
473 | 0 | if (!parser.IsValid()) { |
474 | 0 | return MediaResult( |
475 | 0 | NS_ERROR_FAILURE, |
476 | 0 | RESULT_DETAIL("Invalid Box:%s", parser.LastInvalidBox())); |
477 | 0 | } |
478 | 0 | return parser.StartWithMediaSegment() ? NS_OK : NS_ERROR_NOT_AVAILABLE; |
479 | 0 | } |
480 | | |
481 | | private: |
482 | | class AtomParser |
483 | | { |
484 | | public: |
485 | | enum class StopAt |
486 | | { |
487 | | eInitSegment, |
488 | | eMediaSegment, |
489 | | eEnd |
490 | | }; |
491 | | |
492 | | AtomParser(const MP4ContainerParser& aParser, |
493 | | const MediaByteBuffer* aData, |
494 | | StopAt aStop = StopAt::eEnd) |
495 | 0 | { |
496 | 0 | mValid = Init(aParser, aData, aStop).isOk(); |
497 | 0 | } |
498 | | |
499 | | Result<Ok, nsresult> Init(const MP4ContainerParser& aParser, |
500 | | const MediaByteBuffer* aData, |
501 | | StopAt aStop) |
502 | 0 | { |
503 | 0 | const MediaContainerType mType( |
504 | 0 | aParser.ContainerType()); // for logging macro. |
505 | 0 | BufferReader reader(aData); |
506 | 0 | AtomType initAtom("moov"); |
507 | 0 | AtomType mediaAtom("moof"); |
508 | 0 | AtomType dataAtom("mdat"); |
509 | 0 |
|
510 | 0 | // Valid top-level boxes defined in ISO/IEC 14496-12 (Table 1) |
511 | 0 | static const AtomType validBoxes[] = { |
512 | 0 | "ftyp", "moov", // init segment |
513 | 0 | "pdin", "free", "sidx", // optional prior moov box |
514 | 0 | "styp", "moof", "mdat", // media segment |
515 | 0 | "mfra", "skip", "meta", "meco", "ssix", "prft", // others. |
516 | 0 | "pssh", // optional with encrypted EME, though ignored. |
517 | 0 | "emsg", // ISO23009-1:2014 Section 5.10.3.3 |
518 | 0 | "bloc", "uuid" // boxes accepted by chrome. |
519 | 0 | }; |
520 | 0 |
|
521 | 0 | while (reader.Remaining() >= 8) { |
522 | 0 | uint32_t tmp; |
523 | 0 | MOZ_TRY_VAR(tmp, reader.ReadU32()); |
524 | 0 | uint64_t size = tmp; |
525 | 0 | const uint8_t* typec = reader.Peek(4); |
526 | 0 | MOZ_TRY_VAR(tmp, reader.ReadU32()); |
527 | 0 | AtomType type(tmp); |
528 | 0 | MSE_DEBUGVEX(&aParser, |
529 | 0 | "Checking atom:'%c%c%c%c' @ %u", |
530 | 0 | typec[0], |
531 | 0 | typec[1], |
532 | 0 | typec[2], |
533 | 0 | typec[3], |
534 | 0 | (uint32_t)reader.Offset() - 8); |
535 | 0 | if (std::find(std::begin(validBoxes), std::end(validBoxes), type) |
536 | 0 | == std::end(validBoxes)) { |
537 | 0 | // No valid box found, no point continuing. |
538 | 0 | mLastInvalidBox[0] = typec[0]; |
539 | 0 | mLastInvalidBox[1] = typec[1]; |
540 | 0 | mLastInvalidBox[2] = typec[2]; |
541 | 0 | mLastInvalidBox[3] = typec[3]; |
542 | 0 | mLastInvalidBox[4] = '\0'; |
543 | 0 | return Err(NS_ERROR_FAILURE); |
544 | 0 | } |
545 | 0 | if (mInitOffset.isNothing() && |
546 | 0 | AtomType(type) == initAtom) { |
547 | 0 | mInitOffset = Some(reader.Offset()); |
548 | 0 | } |
549 | 0 | if (mMediaOffset.isNothing() && |
550 | 0 | AtomType(type) == mediaAtom) { |
551 | 0 | mMediaOffset = Some(reader.Offset()); |
552 | 0 | } |
553 | 0 | if (mDataOffset.isNothing() && |
554 | 0 | AtomType(type) == dataAtom) { |
555 | 0 | mDataOffset = Some(reader.Offset()); |
556 | 0 | } |
557 | 0 | if (size == 1) { |
558 | 0 | // 64 bits size. |
559 | 0 | MOZ_TRY_VAR(size, reader.ReadU64()); |
560 | 0 | } else if (size == 0) { |
561 | 0 | // Atom extends to the end of the buffer, it can't have what we're |
562 | 0 | // looking for. |
563 | 0 | break; |
564 | 0 | } |
565 | 0 | if (reader.Remaining() < size - 8) { |
566 | 0 | // Incomplete atom. |
567 | 0 | break; |
568 | 0 | } |
569 | 0 | reader.Read(size - 8); |
570 | 0 |
|
571 | 0 | if (aStop == StopAt::eInitSegment && (mInitOffset || mMediaOffset)) { |
572 | 0 | // When we're looking for an init segment, if we encountered a media |
573 | 0 | // segment, it we will need to be processed first. So we can stop |
574 | 0 | // right away if we have found a media segment. |
575 | 0 | break; |
576 | 0 | } |
577 | 0 | if (aStop == StopAt::eMediaSegment && |
578 | 0 | (mInitOffset || (mMediaOffset && mDataOffset))) { |
579 | 0 | // When we're looking for a media segment, if we encountered an init |
580 | 0 | // segment, it we will need to be processed first. So we can stop |
581 | 0 | // right away if we have found an init segment. |
582 | 0 | break; |
583 | 0 | } |
584 | 0 | } |
585 | 0 |
|
586 | 0 | return Ok(); |
587 | 0 | } |
588 | | |
589 | | bool StartWithInitSegment() const |
590 | 0 | { |
591 | 0 | return mInitOffset.isSome() && |
592 | 0 | (mMediaOffset.isNothing() || mInitOffset.ref() < mMediaOffset.ref()); |
593 | 0 | } |
594 | | bool StartWithMediaSegment() const |
595 | 0 | { |
596 | 0 | return mMediaOffset.isSome() && |
597 | 0 | (mInitOffset.isNothing() || mMediaOffset.ref() < mInitOffset.ref()); |
598 | 0 | } |
599 | 0 | bool IsValid() const { return mValid; } |
600 | 0 | const char* LastInvalidBox() const { return mLastInvalidBox; } |
601 | | private: |
602 | | Maybe<size_t> mInitOffset; |
603 | | Maybe<size_t> mMediaOffset; |
604 | | Maybe<size_t> mDataOffset; |
605 | | bool mValid; |
606 | | char mLastInvalidBox[5]; |
607 | | }; |
608 | | |
609 | | public: |
610 | | MediaResult ParseStartAndEndTimestamps(MediaByteBuffer* aData, |
611 | | int64_t& aStart, |
612 | | int64_t& aEnd) override |
613 | 0 | { |
614 | 0 | bool initSegment = NS_SUCCEEDED(IsInitSegmentPresent(aData)); |
615 | 0 | if (initSegment) { |
616 | 0 | mResource = new SourceBufferResource(); |
617 | 0 | DDLINKCHILD("resource", mResource.get()); |
618 | 0 | mStream = new MP4Stream(mResource); |
619 | 0 | // We use a timestampOffset of 0 for ContainerParser, and require |
620 | 0 | // consumers of ParseStartAndEndTimestamps to add their timestamp offset |
621 | 0 | // manually. This allows the ContainerParser to be shared across different |
622 | 0 | // timestampOffsets. |
623 | 0 | mParser = new MoofParser(mStream, 0, /* aIsAudio = */ false); |
624 | 0 | DDLINKCHILD("parser", mParser.get()); |
625 | 0 | mInitData = new MediaByteBuffer(); |
626 | 0 | mCompleteInitSegmentRange = MediaByteRange(); |
627 | 0 | mCompleteMediaHeaderRange = MediaByteRange(); |
628 | 0 | mCompleteMediaSegmentRange = MediaByteRange(); |
629 | 0 | mGlobalOffset = mTotalParsed; |
630 | 0 | } else if (!mStream || !mParser) { |
631 | 0 | mTotalParsed += aData->Length(); |
632 | 0 | return NS_ERROR_NOT_AVAILABLE; |
633 | 0 | } |
634 | 0 | |
635 | 0 | mResource->AppendData(aData); |
636 | 0 | MediaByteRangeSet byteRanges; |
637 | 0 | byteRanges += |
638 | 0 | MediaByteRange(int64_t(mParser->mOffset), mResource->GetLength()); |
639 | 0 | mParser->RebuildFragmentedIndex(byteRanges); |
640 | 0 |
|
641 | 0 | if (initSegment || !HasCompleteInitData()) { |
642 | 0 | MediaByteRange& range = mParser->mInitRange; |
643 | 0 | if (range.Length()) { |
644 | 0 | mCompleteInitSegmentRange = range + mGlobalOffset; |
645 | 0 | if (!mInitData->SetLength(range.Length(), fallible)) { |
646 | 0 | // Super unlikely OOM |
647 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
648 | 0 | } |
649 | 0 | char* buffer = reinterpret_cast<char*>(mInitData->Elements()); |
650 | 0 | mResource->ReadFromCache(buffer, range.mStart, range.Length()); |
651 | 0 | MSE_DEBUG("Stashed init of %" PRIu64 " bytes.", range.Length()); |
652 | 0 | } else { |
653 | 0 | MSE_DEBUG("Incomplete init found."); |
654 | 0 | } |
655 | 0 | mHasInitData = true; |
656 | 0 | } |
657 | 0 | mTotalParsed += aData->Length(); |
658 | 0 |
|
659 | 0 | MP4Interval<Microseconds> compositionRange = |
660 | 0 | mParser->GetCompositionRange(byteRanges); |
661 | 0 |
|
662 | 0 | mCompleteMediaHeaderRange = |
663 | 0 | mParser->FirstCompleteMediaHeader() + mGlobalOffset; |
664 | 0 | mCompleteMediaSegmentRange = |
665 | 0 | mParser->FirstCompleteMediaSegment() + mGlobalOffset; |
666 | 0 |
|
667 | 0 | ErrorResult rv; |
668 | 0 | if (HasCompleteInitData()) { |
669 | 0 | mResource->EvictData(mParser->mOffset, mParser->mOffset, rv); |
670 | 0 | } |
671 | 0 | if (NS_WARN_IF(rv.Failed())) { |
672 | 0 | rv.SuppressException(); |
673 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
674 | 0 | } |
675 | 0 | |
676 | 0 | if (compositionRange.IsNull()) { |
677 | 0 | return NS_ERROR_NOT_AVAILABLE; |
678 | 0 | } |
679 | 0 | aStart = compositionRange.start; |
680 | 0 | aEnd = compositionRange.end; |
681 | 0 | MSE_DEBUG("[%" PRId64 ", %" PRId64 "]", aStart, aEnd); |
682 | 0 | return NS_OK; |
683 | 0 | } |
684 | | |
685 | | // Gaps of up to 35ms (marginally longer than a single frame at 30fps) are |
686 | | // considered to be sequential frames. |
687 | | int64_t GetRoundingError() override |
688 | 0 | { |
689 | 0 | return 35000; |
690 | 0 | } |
691 | | |
692 | | private: |
693 | | RefPtr<MP4Stream> mStream; |
694 | | nsAutoPtr<MoofParser> mParser; |
695 | | }; |
696 | | #endif // MOZ_FMP4 |
697 | | |
698 | | #ifdef MOZ_FMP4 |
699 | | DDLoggedTypeDeclNameAndBase(ADTSContainerParser, ContainerParser); |
700 | | |
701 | | class ADTSContainerParser |
702 | | : public ContainerParser |
703 | | , public DecoderDoctorLifeLogger<ADTSContainerParser> |
704 | | { |
705 | | public: |
706 | | explicit ADTSContainerParser(const MediaContainerType& aType) |
707 | | : ContainerParser(aType) |
708 | 0 | { |
709 | 0 | } |
710 | | |
711 | | typedef struct |
712 | | { |
713 | | size_t header_length; // Length of just the initialization data. |
714 | | size_t frame_length; // Includes header_length; |
715 | | uint8_t aac_frames; // Number of AAC frames in the ADTS frame. |
716 | | bool have_crc; |
717 | | } Header; |
718 | | |
719 | | /// Helper to parse the ADTS header, returning data we care about. |
720 | | /// Returns true if the header is parsed successfully. |
721 | | /// Returns false if the header is invalid or incomplete, |
722 | | /// without modifying the passed-in Header object. |
723 | | bool Parse(MediaByteBuffer* aData, Header& header) |
724 | 0 | { |
725 | 0 | MOZ_ASSERT(aData); |
726 | 0 |
|
727 | 0 | // ADTS initialization segments are just the packet header. |
728 | 0 | if (aData->Length() < 7) { |
729 | 0 | MSE_DEBUG("buffer too short for header."); |
730 | 0 | return false; |
731 | 0 | } |
732 | 0 | // Check 0xfffx sync word plus layer 0. |
733 | 0 | if (((*aData)[0] != 0xff) || (((*aData)[1] & 0xf6) != 0xf0)) { |
734 | 0 | MSE_DEBUG("no syncword."); |
735 | 0 | return false; |
736 | 0 | } |
737 | 0 | bool have_crc = !((*aData)[1] & 0x01); |
738 | 0 | if (have_crc && aData->Length() < 9) { |
739 | 0 | MSE_DEBUG("buffer too short for header with crc."); |
740 | 0 | return false; |
741 | 0 | } |
742 | 0 | uint8_t frequency_index = ((*aData)[2] & 0x3c) >> 2; |
743 | 0 | MOZ_ASSERT(frequency_index < 16); |
744 | 0 | if (frequency_index == 15) { |
745 | 0 | MSE_DEBUG("explicit frequency disallowed."); |
746 | 0 | return false; |
747 | 0 | } |
748 | 0 | size_t header_length = have_crc ? 9 : 7; |
749 | 0 | size_t data_length = (((*aData)[3] & 0x03) << 11) | |
750 | 0 | (((*aData)[4] & 0xff) << 3) | |
751 | 0 | (((*aData)[5] & 0xe0) >> 5); |
752 | 0 | uint8_t frames = ((*aData)[6] & 0x03) + 1; |
753 | 0 | MOZ_ASSERT(frames > 0); |
754 | 0 | MOZ_ASSERT(frames < 4); |
755 | 0 |
|
756 | 0 | // Return successfully parsed data. |
757 | 0 | header.header_length = header_length; |
758 | 0 | header.frame_length = header_length + data_length; |
759 | 0 | header.aac_frames = frames; |
760 | 0 | header.have_crc = have_crc; |
761 | 0 | return true; |
762 | 0 | } |
763 | | |
764 | | MediaResult IsInitSegmentPresent(MediaByteBuffer* aData) override |
765 | 0 | { |
766 | 0 | // Call superclass for logging. |
767 | 0 | ContainerParser::IsInitSegmentPresent(aData); |
768 | 0 |
|
769 | 0 | Header header; |
770 | 0 | if (!Parse(aData, header)) { |
771 | 0 | return NS_ERROR_NOT_AVAILABLE; |
772 | 0 | } |
773 | 0 | |
774 | 0 | MSE_DEBUGV("%llu byte frame %d aac frames%s", |
775 | 0 | (unsigned long long)header.frame_length, |
776 | 0 | (int)header.aac_frames, |
777 | 0 | header.have_crc ? " crc" : ""); |
778 | 0 |
|
779 | 0 | return NS_OK; |
780 | 0 | } |
781 | | |
782 | | MediaResult IsMediaSegmentPresent(MediaByteBuffer* aData) override |
783 | 0 | { |
784 | 0 | // Call superclass for logging. |
785 | 0 | ContainerParser::IsMediaSegmentPresent(aData); |
786 | 0 |
|
787 | 0 | // Make sure we have a header so we know how long the frame is. |
788 | 0 | // NB this assumes the media segment buffer starts with an |
789 | 0 | // initialization segment. Since every frame has an ADTS header |
790 | 0 | // this is a normal place to divide packets, but we can re-parse |
791 | 0 | // mInitData if we need to handle separate media segments. |
792 | 0 | Header header; |
793 | 0 | if (!Parse(aData, header)) { |
794 | 0 | return NS_ERROR_NOT_AVAILABLE; |
795 | 0 | } |
796 | 0 | // We're supposed to return true as long as aData contains the |
797 | 0 | // start of a media segment, whether or not it's complete. So |
798 | 0 | // return true if we have any data beyond the header. |
799 | 0 | if (aData->Length() <= header.header_length) { |
800 | 0 | return NS_ERROR_NOT_AVAILABLE; |
801 | 0 | } |
802 | 0 | |
803 | 0 | // We should have at least a partial frame. |
804 | 0 | return NS_OK; |
805 | 0 | } |
806 | | |
807 | | MediaResult ParseStartAndEndTimestamps(MediaByteBuffer* aData, |
808 | | int64_t& aStart, |
809 | | int64_t& aEnd) override |
810 | 0 | { |
811 | 0 | // ADTS header. |
812 | 0 | Header header; |
813 | 0 | if (!Parse(aData, header)) { |
814 | 0 | return NS_ERROR_NOT_AVAILABLE; |
815 | 0 | } |
816 | 0 | mHasInitData = true; |
817 | 0 | mCompleteInitSegmentRange = |
818 | 0 | MediaByteRange(0, int64_t(header.header_length)); |
819 | 0 |
|
820 | 0 | // Cache raw header in case the caller wants a copy. |
821 | 0 | mInitData = new MediaByteBuffer(header.header_length); |
822 | 0 | mInitData->AppendElements(aData->Elements(), header.header_length); |
823 | 0 |
|
824 | 0 | // Check that we have enough data for the frame body. |
825 | 0 | if (aData->Length() < header.frame_length) { |
826 | 0 | MSE_DEBUGV("Not enough data for %llu byte frame" |
827 | 0 | " in %llu byte buffer.", |
828 | 0 | (unsigned long long)header.frame_length, |
829 | 0 | (unsigned long long)(aData->Length())); |
830 | 0 | return NS_ERROR_NOT_AVAILABLE; |
831 | 0 | } |
832 | 0 | mCompleteMediaSegmentRange = MediaByteRange(header.header_length, |
833 | 0 | header.frame_length); |
834 | 0 | // The ADTS MediaSource Byte Stream Format document doesn't |
835 | 0 | // define media header. Just treat it the same as the whole |
836 | 0 | // media segment. |
837 | 0 | mCompleteMediaHeaderRange = mCompleteMediaSegmentRange; |
838 | 0 |
|
839 | 0 | MSE_DEBUG("[%" PRId64 ", %" PRId64 "]", aStart, aEnd); |
840 | 0 | // We don't update timestamps, regardless. |
841 | 0 | return NS_ERROR_NOT_AVAILABLE; |
842 | 0 | } |
843 | | |
844 | | // Audio shouldn't have gaps. |
845 | | // Especially when we generate the timestamps ourselves. |
846 | | int64_t GetRoundingError() override |
847 | 0 | { |
848 | 0 | return 0; |
849 | 0 | } |
850 | | }; |
851 | | #endif // MOZ_FMP4 |
852 | | |
853 | | /*static*/ ContainerParser* |
854 | | ContainerParser::CreateForMIMEType(const MediaContainerType& aType) |
855 | 0 | { |
856 | 0 | if (aType.Type() == MEDIAMIMETYPE(VIDEO_WEBM) || |
857 | 0 | aType.Type() == MEDIAMIMETYPE(AUDIO_WEBM)) { |
858 | 0 | return new WebMContainerParser(aType); |
859 | 0 | } |
860 | 0 | |
861 | 0 | #ifdef MOZ_FMP4 |
862 | 0 | if (aType.Type() == MEDIAMIMETYPE(VIDEO_MP4) || |
863 | 0 | aType.Type() == MEDIAMIMETYPE(AUDIO_MP4)) { |
864 | 0 | return new MP4ContainerParser(aType); |
865 | 0 | } |
866 | 0 | if (aType.Type() == MEDIAMIMETYPE("audio/aac")) { |
867 | 0 | return new ADTSContainerParser(aType); |
868 | 0 | } |
869 | 0 | #endif |
870 | 0 | |
871 | 0 | return new ContainerParser(aType); |
872 | 0 | } |
873 | | |
874 | | #undef MSE_DEBUG |
875 | | #undef MSE_DEBUGV |
876 | | #undef MSE_DEBUGVEX |
877 | | |
878 | | } // namespace mozilla |