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