summaryrefslogtreecommitdiff
path: root/media/libstagefright/binding/MoofParser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'media/libstagefright/binding/MoofParser.cpp')
-rw-r--r--media/libstagefright/binding/MoofParser.cpp925
1 files changed, 925 insertions, 0 deletions
diff --git a/media/libstagefright/binding/MoofParser.cpp b/media/libstagefright/binding/MoofParser.cpp
new file mode 100644
index 0000000000..ced054282d
--- /dev/null
+++ b/media/libstagefright/binding/MoofParser.cpp
@@ -0,0 +1,925 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mp4_demuxer/MoofParser.h"
+#include "mp4_demuxer/Box.h"
+#include "mp4_demuxer/SinfParser.h"
+#include <limits>
+#include "Intervals.h"
+
+#include "mozilla/CheckedInt.h"
+#include "mozilla/Logging.h"
+
+#if defined(MOZ_FMP4)
+extern mozilla::LogModule* GetDemuxerLog();
+
+#define STRINGIFY(x) #x
+#define TOSTRING(x) STRINGIFY(x)
+#define LOG(name, arg, ...) MOZ_LOG(GetDemuxerLog(), mozilla::LogLevel::Debug, (TOSTRING(name) "(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
+#else
+#define LOG(...)
+#endif
+
+namespace mp4_demuxer
+{
+
+using namespace stagefright;
+using namespace mozilla;
+
+bool
+MoofParser::RebuildFragmentedIndex(const MediaByteRangeSet& aByteRanges)
+{
+ BoxContext context(mSource, aByteRanges);
+ return RebuildFragmentedIndex(context);
+}
+
+bool
+MoofParser::RebuildFragmentedIndex(
+ const MediaByteRangeSet& aByteRanges, bool* aCanEvict)
+{
+ MOZ_ASSERT(aCanEvict);
+ if (*aCanEvict && mMoofs.Length() > 1) {
+ MOZ_ASSERT(mMoofs.Length() == mMediaRanges.Length());
+ mMoofs.RemoveElementsAt(0, mMoofs.Length() - 1);
+ mMediaRanges.RemoveElementsAt(0, mMediaRanges.Length() - 1);
+ *aCanEvict = true;
+ } else {
+ *aCanEvict = false;
+ }
+ return RebuildFragmentedIndex(aByteRanges);
+}
+
+bool
+MoofParser::RebuildFragmentedIndex(BoxContext& aContext)
+{
+ bool foundValidMoof = false;
+ bool foundMdat = false;
+
+ for (Box box(&aContext, mOffset); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("moov") && mInitRange.IsEmpty()) {
+ mInitRange = MediaByteRange(0, box.Range().mEnd);
+ ParseMoov(box);
+ } else if (box.IsType("moof")) {
+ Moof moof(box, mTrex, mMvhd, mMdhd, mEdts, mSinf, &mLastDecodeTime, mIsAudio);
+
+ if (!moof.IsValid() && !box.Next().IsAvailable()) {
+ // Moof isn't valid abort search for now.
+ break;
+ }
+
+ if (!mMoofs.IsEmpty()) {
+ // Stitch time ranges together in the case of a (hopefully small) time
+ // range gap between moofs.
+ mMoofs.LastElement().FixRounding(moof);
+ }
+
+ mMoofs.AppendElement(moof);
+ mMediaRanges.AppendElement(moof.mRange);
+ foundValidMoof = true;
+ } else if (box.IsType("mdat") && !Moofs().IsEmpty()) {
+ // Check if we have all our data from last moof.
+ Moof& moof = Moofs().LastElement();
+ media::Interval<int64_t> datarange(moof.mMdatRange.mStart, moof.mMdatRange.mEnd, 0);
+ media::Interval<int64_t> mdat(box.Range().mStart, box.Range().mEnd, 0);
+ if (datarange.Intersects(mdat)) {
+ mMediaRanges.LastElement() =
+ mMediaRanges.LastElement().Span(box.Range());
+ }
+ }
+ mOffset = box.NextOffset();
+ }
+ return foundValidMoof;
+}
+
+MediaByteRange
+MoofParser::FirstCompleteMediaHeader()
+{
+ if (Moofs().IsEmpty()) {
+ return MediaByteRange();
+ }
+ return Moofs()[0].mRange;
+}
+
+MediaByteRange
+MoofParser::FirstCompleteMediaSegment()
+{
+ for (uint32_t i = 0 ; i < mMediaRanges.Length(); i++) {
+ if (mMediaRanges[i].Contains(Moofs()[i].mMdatRange)) {
+ return mMediaRanges[i];
+ }
+ }
+ return MediaByteRange();
+}
+
+class BlockingStream : public Stream {
+public:
+ explicit BlockingStream(Stream* aStream) : mStream(aStream)
+ {
+ }
+
+ bool ReadAt(int64_t offset, void* data, size_t size, size_t* bytes_read)
+ override
+ {
+ return mStream->ReadAt(offset, data, size, bytes_read);
+ }
+
+ bool CachedReadAt(int64_t offset, void* data, size_t size, size_t* bytes_read)
+ override
+ {
+ return mStream->ReadAt(offset, data, size, bytes_read);
+ }
+
+ virtual bool Length(int64_t* size) override
+ {
+ return mStream->Length(size);
+ }
+
+private:
+ RefPtr<Stream> mStream;
+};
+
+bool
+MoofParser::BlockingReadNextMoof()
+{
+ int64_t length = std::numeric_limits<int64_t>::max();
+ mSource->Length(&length);
+ MediaByteRangeSet byteRanges;
+ byteRanges += MediaByteRange(0, length);
+ RefPtr<mp4_demuxer::BlockingStream> stream = new BlockingStream(mSource);
+
+ BoxContext context(stream, byteRanges);
+ for (Box box(&context, mOffset); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("moof")) {
+ byteRanges.Clear();
+ byteRanges += MediaByteRange(mOffset, box.Range().mEnd);
+ return RebuildFragmentedIndex(context);
+ }
+ }
+ return false;
+}
+
+void
+MoofParser::ScanForMetadata(mozilla::MediaByteRange& aFtyp,
+ mozilla::MediaByteRange& aMoov)
+{
+ int64_t length = std::numeric_limits<int64_t>::max();
+ mSource->Length(&length);
+ MediaByteRangeSet byteRanges;
+ byteRanges += MediaByteRange(0, length);
+ RefPtr<mp4_demuxer::BlockingStream> stream = new BlockingStream(mSource);
+
+ BoxContext context(stream, byteRanges);
+ for (Box box(&context, mOffset); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("ftyp")) {
+ aFtyp = box.Range();
+ continue;
+ }
+ if (box.IsType("moov")) {
+ aMoov = box.Range();
+ break;
+ }
+ }
+ mInitRange = aFtyp.Span(aMoov);
+}
+
+bool
+MoofParser::HasMetadata()
+{
+ MediaByteRange ftyp;
+ MediaByteRange moov;
+ ScanForMetadata(ftyp, moov);
+ return !!ftyp.Length() && !!moov.Length();
+}
+
+already_AddRefed<mozilla::MediaByteBuffer>
+MoofParser::Metadata()
+{
+ MediaByteRange ftyp;
+ MediaByteRange moov;
+ ScanForMetadata(ftyp, moov);
+ CheckedInt<MediaByteBuffer::size_type> ftypLength = ftyp.Length();
+ CheckedInt<MediaByteBuffer::size_type> moovLength = moov.Length();
+ if (!ftypLength.isValid() || !moovLength.isValid()
+ || !ftypLength.value() || !moovLength.value()) {
+ // No ftyp or moov, or they cannot be used as array size.
+ return nullptr;
+ }
+ CheckedInt<MediaByteBuffer::size_type> totalLength = ftypLength + moovLength;
+ if (!totalLength.isValid()) {
+ // Addition overflow, or sum cannot be used as array size.
+ return nullptr;
+ }
+ RefPtr<MediaByteBuffer> metadata = new MediaByteBuffer();
+ if (!metadata->SetLength(totalLength.value(), fallible)) {
+ // OOM
+ return nullptr;
+ }
+
+ RefPtr<mp4_demuxer::BlockingStream> stream = new BlockingStream(mSource);
+ size_t read;
+ bool rv =
+ stream->ReadAt(ftyp.mStart, metadata->Elements(), ftypLength.value(), &read);
+ if (!rv || read != ftypLength.value()) {
+ return nullptr;
+ }
+ rv =
+ stream->ReadAt(moov.mStart, metadata->Elements() + ftypLength.value(), moovLength.value(), &read);
+ if (!rv || read != moovLength.value()) {
+ return nullptr;
+ }
+ return metadata.forget();
+}
+
+Interval<Microseconds>
+MoofParser::GetCompositionRange(const MediaByteRangeSet& aByteRanges)
+{
+ Interval<Microseconds> compositionRange;
+ BoxContext context(mSource, aByteRanges);
+ for (size_t i = 0; i < mMoofs.Length(); i++) {
+ Moof& moof = mMoofs[i];
+ Box box(&context, moof.mRange.mStart);
+ if (box.IsAvailable()) {
+ compositionRange = compositionRange.Extents(moof.mTimeRange);
+ }
+ }
+ return compositionRange;
+}
+
+bool
+MoofParser::ReachedEnd()
+{
+ int64_t length;
+ return mSource->Length(&length) && mOffset == length;
+}
+
+void
+MoofParser::ParseMoov(Box& aBox)
+{
+ for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("mvhd")) {
+ mMvhd = Mvhd(box);
+ } else if (box.IsType("trak")) {
+ ParseTrak(box);
+ } else if (box.IsType("mvex")) {
+ ParseMvex(box);
+ }
+ }
+}
+
+void
+MoofParser::ParseTrak(Box& aBox)
+{
+ Tkhd tkhd;
+ for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("tkhd")) {
+ tkhd = Tkhd(box);
+ } else if (box.IsType("mdia")) {
+ if (!mTrex.mTrackId || tkhd.mTrackId == mTrex.mTrackId) {
+ ParseMdia(box, tkhd);
+ }
+ } else if (box.IsType("edts") &&
+ (!mTrex.mTrackId || tkhd.mTrackId == mTrex.mTrackId)) {
+ mEdts = Edts(box);
+ }
+ }
+}
+
+void
+MoofParser::ParseMdia(Box& aBox, Tkhd& aTkhd)
+{
+ for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("mdhd")) {
+ mMdhd = Mdhd(box);
+ } else if (box.IsType("minf")) {
+ ParseMinf(box);
+ }
+ }
+}
+
+void
+MoofParser::ParseMvex(Box& aBox)
+{
+ for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("trex")) {
+ Trex trex = Trex(box);
+ if (!mTrex.mTrackId || trex.mTrackId == mTrex.mTrackId) {
+ auto trackId = mTrex.mTrackId;
+ mTrex = trex;
+ // Keep the original trackId, as should it be 0 we want to continue
+ // parsing all tracks.
+ mTrex.mTrackId = trackId;
+ }
+ }
+ }
+}
+
+void
+MoofParser::ParseMinf(Box& aBox)
+{
+ for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("stbl")) {
+ ParseStbl(box);
+ }
+ }
+}
+
+void
+MoofParser::ParseStbl(Box& aBox)
+{
+ for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("stsd")) {
+ ParseStsd(box);
+ }
+ }
+}
+
+void
+MoofParser::ParseStsd(Box& aBox)
+{
+ for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("encv") || box.IsType("enca")) {
+ ParseEncrypted(box);
+ }
+ }
+}
+
+void
+MoofParser::ParseEncrypted(Box& aBox)
+{
+ for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
+ // Some MP4 files have been found to have multiple sinf boxes in the same
+ // enc* box. This does not match spec anyway, so just choose the first
+ // one that parses properly.
+ if (box.IsType("sinf")) {
+ mSinf = Sinf(box);
+
+ if (mSinf.IsValid()) {
+ break;
+ }
+ }
+ }
+}
+
+class CtsComparator
+{
+public:
+ bool Equals(Sample* const aA, Sample* const aB) const
+ {
+ return aA->mCompositionRange.start == aB->mCompositionRange.start;
+ }
+ bool
+ LessThan(Sample* const aA, Sample* const aB) const
+ {
+ return aA->mCompositionRange.start < aB->mCompositionRange.start;
+ }
+};
+
+Moof::Moof(Box& aBox, Trex& aTrex, Mvhd& aMvhd, Mdhd& aMdhd, Edts& aEdts, Sinf& aSinf, uint64_t* aDecodeTime, bool aIsAudio)
+ : mRange(aBox.Range())
+ , mMaxRoundingError(35000)
+{
+ for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("traf")) {
+ ParseTraf(box, aTrex, aMvhd, aMdhd, aEdts, aSinf, aDecodeTime, aIsAudio);
+ }
+ }
+ if (IsValid()) {
+ if (mIndex.Length()) {
+ // Ensure the samples are contiguous with no gaps.
+ nsTArray<Sample*> ctsOrder;
+ for (auto& sample : mIndex) {
+ ctsOrder.AppendElement(&sample);
+ }
+ ctsOrder.Sort(CtsComparator());
+
+ for (size_t i = 1; i < ctsOrder.Length(); i++) {
+ ctsOrder[i-1]->mCompositionRange.end = ctsOrder[i]->mCompositionRange.start;
+ }
+
+ // In MP4, the duration of a sample is defined as the delta between two decode
+ // timestamps. The operation above has updated the duration of each sample
+ // as a Sample's duration is mCompositionRange.end - mCompositionRange.start
+ // MSE's TrackBuffersManager expects dts that increased by the sample's
+ // duration, so we rewrite the dts accordingly.
+ int64_t presentationDuration =
+ ctsOrder.LastElement()->mCompositionRange.end
+ - ctsOrder[0]->mCompositionRange.start;
+ int64_t endDecodeTime =
+ aMdhd.ToMicroseconds((int64_t)*aDecodeTime - aEdts.mMediaStart)
+ + aMvhd.ToMicroseconds(aEdts.mEmptyOffset);
+ int64_t decodeDuration = endDecodeTime - mIndex[0].mDecodeTime;
+ double adjust = (double)decodeDuration / presentationDuration;
+ int64_t dtsOffset = mIndex[0].mDecodeTime;
+ int64_t compositionDuration = 0;
+ // Adjust the dts, ensuring that the new adjusted dts will never be greater
+ // than decodeTime (the next moof's decode start time).
+ for (auto& sample : mIndex) {
+ sample.mDecodeTime = dtsOffset + int64_t(compositionDuration * adjust);
+ compositionDuration += sample.mCompositionRange.Length();
+ }
+ mTimeRange = Interval<Microseconds>(ctsOrder[0]->mCompositionRange.start,
+ ctsOrder.LastElement()->mCompositionRange.end);
+ }
+ ProcessCenc();
+ }
+}
+
+bool
+Moof::GetAuxInfo(AtomType aType, nsTArray<MediaByteRange>* aByteRanges)
+{
+ aByteRanges->Clear();
+
+ Saiz* saiz = nullptr;
+ for (int i = 0; ; i++) {
+ if (i == mSaizs.Length()) {
+ return false;
+ }
+ if (mSaizs[i].mAuxInfoType == aType) {
+ saiz = &mSaizs[i];
+ break;
+ }
+ }
+ Saio* saio = nullptr;
+ for (int i = 0; ; i++) {
+ if (i == mSaios.Length()) {
+ return false;
+ }
+ if (mSaios[i].mAuxInfoType == aType) {
+ saio = &mSaios[i];
+ break;
+ }
+ }
+
+ if (saio->mOffsets.Length() == 1) {
+ aByteRanges->SetCapacity(saiz->mSampleInfoSize.Length());
+ uint64_t offset = mRange.mStart + saio->mOffsets[0];
+ for (size_t i = 0; i < saiz->mSampleInfoSize.Length(); i++) {
+ aByteRanges->AppendElement(
+ MediaByteRange(offset, offset + saiz->mSampleInfoSize[i]));
+ offset += saiz->mSampleInfoSize[i];
+ }
+ return true;
+ }
+
+ if (saio->mOffsets.Length() == saiz->mSampleInfoSize.Length()) {
+ aByteRanges->SetCapacity(saiz->mSampleInfoSize.Length());
+ for (size_t i = 0; i < saio->mOffsets.Length(); i++) {
+ uint64_t offset = mRange.mStart + saio->mOffsets[i];
+ aByteRanges->AppendElement(
+ MediaByteRange(offset, offset + saiz->mSampleInfoSize[i]));
+ }
+ return true;
+ }
+
+ return false;
+}
+
+bool
+Moof::ProcessCenc()
+{
+ nsTArray<MediaByteRange> cencRanges;
+ if (!GetAuxInfo(AtomType("cenc"), &cencRanges) ||
+ cencRanges.Length() != mIndex.Length()) {
+ return false;
+ }
+ for (int i = 0; i < cencRanges.Length(); i++) {
+ mIndex[i].mCencRange = cencRanges[i];
+ }
+ return true;
+}
+
+void
+Moof::ParseTraf(Box& aBox, Trex& aTrex, Mvhd& aMvhd, Mdhd& aMdhd, Edts& aEdts, Sinf& aSinf, uint64_t* aDecodeTime, bool aIsAudio)
+{
+ MOZ_ASSERT(aDecodeTime);
+ Tfhd tfhd(aTrex);
+ Tfdt tfdt;
+ for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("tfhd")) {
+ tfhd = Tfhd(box, aTrex);
+ } else if (!aTrex.mTrackId || tfhd.mTrackId == aTrex.mTrackId) {
+ if (box.IsType("tfdt")) {
+ tfdt = Tfdt(box);
+ } else if (box.IsType("saiz")) {
+ mSaizs.AppendElement(Saiz(box, aSinf.mDefaultEncryptionType));
+ } else if (box.IsType("saio")) {
+ mSaios.AppendElement(Saio(box, aSinf.mDefaultEncryptionType));
+ }
+ }
+ }
+ if (aTrex.mTrackId && tfhd.mTrackId != aTrex.mTrackId) {
+ return;
+ }
+ // Now search for TRUN boxes.
+ uint64_t decodeTime =
+ tfdt.IsValid() ? tfdt.mBaseMediaDecodeTime : *aDecodeTime;
+ for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) {
+ if (box.IsType("trun")) {
+ if (ParseTrun(box, tfhd, aMvhd, aMdhd, aEdts, &decodeTime, aIsAudio)) {
+ mValid = true;
+ } else {
+ mValid = false;
+ break;
+ }
+ }
+ }
+ *aDecodeTime = decodeTime;
+}
+
+void
+Moof::FixRounding(const Moof& aMoof) {
+ Microseconds gap = aMoof.mTimeRange.start - mTimeRange.end;
+ if (gap > 0 && gap <= mMaxRoundingError) {
+ mTimeRange.end = aMoof.mTimeRange.start;
+ }
+}
+
+bool
+Moof::ParseTrun(Box& aBox, Tfhd& aTfhd, Mvhd& aMvhd, Mdhd& aMdhd, Edts& aEdts, uint64_t* aDecodeTime, bool aIsAudio)
+{
+ if (!aTfhd.IsValid() || !aMvhd.IsValid() || !aMdhd.IsValid() ||
+ !aEdts.IsValid()) {
+ LOG(Moof, "Invalid dependencies: aTfhd(%d) aMvhd(%d) aMdhd(%d) aEdts(%d)",
+ aTfhd.IsValid(), aMvhd.IsValid(), aMdhd.IsValid(), !aEdts.IsValid());
+ return false;
+ }
+
+ BoxReader reader(aBox);
+ if (!reader->CanReadType<uint32_t>()) {
+ LOG(Moof, "Incomplete Box (missing flags)");
+ return false;
+ }
+ uint32_t flags = reader->ReadU32();
+ uint8_t version = flags >> 24;
+
+ if (!reader->CanReadType<uint32_t>()) {
+ LOG(Moof, "Incomplete Box (missing sampleCount)");
+ return false;
+ }
+ uint32_t sampleCount = reader->ReadU32();
+ if (sampleCount == 0) {
+ return true;
+ }
+
+ size_t need =
+ ((flags & 1) ? sizeof(uint32_t) : 0) +
+ ((flags & 4) ? sizeof(uint32_t) : 0);
+ uint16_t flag[] = { 0x100, 0x200, 0x400, 0x800, 0 };
+ for (size_t i = 0; flag[i]; i++) {
+ if (flags & flag[i]) {
+ need += sizeof(uint32_t) * sampleCount;
+ }
+ }
+ if (reader->Remaining() < need) {
+ LOG(Moof, "Incomplete Box (have:%lld need:%lld)",
+ reader->Remaining(), need);
+ return false;
+ }
+
+ uint64_t offset = aTfhd.mBaseDataOffset + (flags & 1 ? reader->ReadU32() : 0);
+ uint32_t firstSampleFlags =
+ flags & 4 ? reader->ReadU32() : aTfhd.mDefaultSampleFlags;
+ uint64_t decodeTime = *aDecodeTime;
+ nsTArray<Interval<Microseconds>> timeRanges;
+
+ if (!mIndex.SetCapacity(sampleCount, fallible)) {
+ LOG(Moof, "Out of Memory");
+ return false;
+ }
+
+ for (size_t i = 0; i < sampleCount; i++) {
+ uint32_t sampleDuration =
+ flags & 0x100 ? reader->ReadU32() : aTfhd.mDefaultSampleDuration;
+ uint32_t sampleSize =
+ flags & 0x200 ? reader->ReadU32() : aTfhd.mDefaultSampleSize;
+ uint32_t sampleFlags =
+ flags & 0x400 ? reader->ReadU32()
+ : i ? aTfhd.mDefaultSampleFlags : firstSampleFlags;
+ int32_t ctsOffset = 0;
+ if (flags & 0x800) {
+ ctsOffset = reader->Read32();
+ }
+
+ Sample sample;
+ sample.mByteRange = MediaByteRange(offset, offset + sampleSize);
+ offset += sampleSize;
+
+ sample.mDecodeTime =
+ aMdhd.ToMicroseconds((int64_t)decodeTime - aEdts.mMediaStart) + aMvhd.ToMicroseconds(aEdts.mEmptyOffset);
+ sample.mCompositionRange = Interval<Microseconds>(
+ aMdhd.ToMicroseconds((int64_t)decodeTime + ctsOffset - aEdts.mMediaStart) + aMvhd.ToMicroseconds(aEdts.mEmptyOffset),
+ aMdhd.ToMicroseconds((int64_t)decodeTime + ctsOffset + sampleDuration - aEdts.mMediaStart) + aMvhd.ToMicroseconds(aEdts.mEmptyOffset));
+ decodeTime += sampleDuration;
+
+ // Sometimes audio streams don't properly mark their samples as keyframes,
+ // because every audio sample is a keyframe.
+ sample.mSync = !(sampleFlags & 0x1010000) || aIsAudio;
+
+ // FIXME: Make this infallible after bug 968520 is done.
+ MOZ_ALWAYS_TRUE(mIndex.AppendElement(sample, fallible));
+
+ mMdatRange = mMdatRange.Span(sample.mByteRange);
+ }
+ mMaxRoundingError += aMdhd.ToMicroseconds(sampleCount);
+
+ *aDecodeTime = decodeTime;
+
+ return true;
+}
+
+Tkhd::Tkhd(Box& aBox)
+{
+ BoxReader reader(aBox);
+ if (!reader->CanReadType<uint32_t>()) {
+ LOG(Tkhd, "Incomplete Box (missing flags)");
+ return;
+ }
+ uint32_t flags = reader->ReadU32();
+ uint8_t version = flags >> 24;
+ size_t need =
+ 3*(version ? sizeof(int64_t) : sizeof(int32_t)) + 2*sizeof(int32_t);
+ if (reader->Remaining() < need) {
+ LOG(Tkhd, "Incomplete Box (have:%lld need:%lld)",
+ (uint64_t)reader->Remaining(), (uint64_t)need);
+ return;
+ }
+ if (version == 0) {
+ mCreationTime = reader->ReadU32();
+ mModificationTime = reader->ReadU32();
+ mTrackId = reader->ReadU32();
+ uint32_t reserved = reader->ReadU32();
+ NS_ASSERTION(!reserved, "reserved should be 0");
+ mDuration = reader->ReadU32();
+ } else if (version == 1) {
+ mCreationTime = reader->ReadU64();
+ mModificationTime = reader->ReadU64();
+ mTrackId = reader->ReadU32();
+ uint32_t reserved = reader->ReadU32();
+ NS_ASSERTION(!reserved, "reserved should be 0");
+ mDuration = reader->ReadU64();
+ }
+ // We don't care about whatever else may be in the box.
+ mValid = true;
+}
+
+Mvhd::Mvhd(Box& aBox)
+{
+ BoxReader reader(aBox);
+ if (!reader->CanReadType<uint32_t>()) {
+ LOG(Mdhd, "Incomplete Box (missing flags)");
+ return;
+ }
+ uint32_t flags = reader->ReadU32();
+ uint8_t version = flags >> 24;
+ size_t need =
+ 3*(version ? sizeof(int64_t) : sizeof(int32_t)) + sizeof(uint32_t);
+ if (reader->Remaining() < need) {
+ LOG(Mvhd, "Incomplete Box (have:%lld need:%lld)",
+ (uint64_t)reader->Remaining(), (uint64_t)need);
+ return;
+ }
+
+ if (version == 0) {
+ mCreationTime = reader->ReadU32();
+ mModificationTime = reader->ReadU32();
+ mTimescale = reader->ReadU32();
+ mDuration = reader->ReadU32();
+ } else if (version == 1) {
+ mCreationTime = reader->ReadU64();
+ mModificationTime = reader->ReadU64();
+ mTimescale = reader->ReadU32();
+ mDuration = reader->ReadU64();
+ } else {
+ return;
+ }
+ // We don't care about whatever else may be in the box.
+ if (mTimescale) {
+ mValid = true;
+ }
+}
+
+Mdhd::Mdhd(Box& aBox)
+ : Mvhd(aBox)
+{
+}
+
+Trex::Trex(Box& aBox)
+{
+ BoxReader reader(aBox);
+ if (reader->Remaining() < 6*sizeof(uint32_t)) {
+ LOG(Trex, "Incomplete Box (have:%lld need:%lld)",
+ (uint64_t)reader->Remaining(), (uint64_t)6*sizeof(uint32_t));
+ return;
+ }
+ mFlags = reader->ReadU32();
+ mTrackId = reader->ReadU32();
+ mDefaultSampleDescriptionIndex = reader->ReadU32();
+ mDefaultSampleDuration = reader->ReadU32();
+ mDefaultSampleSize = reader->ReadU32();
+ mDefaultSampleFlags = reader->ReadU32();
+ mValid = true;
+}
+
+Tfhd::Tfhd(Box& aBox, Trex& aTrex)
+ : Trex(aTrex)
+{
+ MOZ_ASSERT(aBox.IsType("tfhd"));
+ MOZ_ASSERT(aBox.Parent()->IsType("traf"));
+ MOZ_ASSERT(aBox.Parent()->Parent()->IsType("moof"));
+
+ BoxReader reader(aBox);
+ if (!reader->CanReadType<uint32_t>()) {
+ LOG(Tfhd, "Incomplete Box (missing flags)");
+ return;
+ }
+ mFlags = reader->ReadU32();
+ size_t need = sizeof(uint32_t) /* trackid */;
+ uint8_t flag[] = { 1, 2, 8, 0x10, 0x20, 0 };
+ uint8_t flagSize[] = { sizeof(uint64_t), sizeof(uint32_t), sizeof(uint32_t), sizeof(uint32_t), sizeof(uint32_t) };
+ for (size_t i = 0; flag[i]; i++) {
+ if (mFlags & flag[i]) {
+ need += flagSize[i];
+ }
+ }
+ if (reader->Remaining() < need) {
+ LOG(Tfhd, "Incomplete Box (have:%lld need:%lld)",
+ (uint64_t)reader->Remaining(), (uint64_t)need);
+ return;
+ }
+ mTrackId = reader->ReadU32();
+ mBaseDataOffset =
+ mFlags & 1 ? reader->ReadU64() : aBox.Parent()->Parent()->Offset();
+ if (mFlags & 2) {
+ mDefaultSampleDescriptionIndex = reader->ReadU32();
+ }
+ if (mFlags & 8) {
+ mDefaultSampleDuration = reader->ReadU32();
+ }
+ if (mFlags & 0x10) {
+ mDefaultSampleSize = reader->ReadU32();
+ }
+ if (mFlags & 0x20) {
+ mDefaultSampleFlags = reader->ReadU32();
+ }
+ mValid = true;
+}
+
+Tfdt::Tfdt(Box& aBox)
+{
+ BoxReader reader(aBox);
+ if (!reader->CanReadType<uint32_t>()) {
+ LOG(Tfdt, "Incomplete Box (missing flags)");
+ return;
+ }
+ uint32_t flags = reader->ReadU32();
+ uint8_t version = flags >> 24;
+ size_t need = version ? sizeof(uint64_t) : sizeof(uint32_t) ;
+ if (reader->Remaining() < need) {
+ LOG(Tfdt, "Incomplete Box (have:%lld need:%lld)",
+ (uint64_t)reader->Remaining(), (uint64_t)need);
+ return;
+ }
+ if (version == 0) {
+ mBaseMediaDecodeTime = reader->ReadU32();
+ } else if (version == 1) {
+ mBaseMediaDecodeTime = reader->ReadU64();
+ }
+ mValid = true;
+}
+
+Edts::Edts(Box& aBox)
+ : mMediaStart(0)
+ , mEmptyOffset(0)
+{
+ Box child = aBox.FirstChild();
+ if (!child.IsType("elst")) {
+ return;
+ }
+
+ BoxReader reader(child);
+ if (!reader->CanReadType<uint32_t>()) {
+ LOG(Edts, "Incomplete Box (missing flags)");
+ return;
+ }
+ uint32_t flags = reader->ReadU32();
+ uint8_t version = flags >> 24;
+ size_t need =
+ sizeof(uint32_t) + 2*(version ? sizeof(int64_t) : sizeof(uint32_t));
+ if (reader->Remaining() < need) {
+ LOG(Edts, "Incomplete Box (have:%lld need:%lld)",
+ (uint64_t)reader->Remaining(), (uint64_t)need);
+ return;
+ }
+ bool emptyEntry = false;
+ uint32_t entryCount = reader->ReadU32();
+ for (uint32_t i = 0; i < entryCount; i++) {
+ uint64_t segment_duration;
+ int64_t media_time;
+ if (version == 1) {
+ segment_duration = reader->ReadU64();
+ media_time = reader->Read64();
+ } else {
+ segment_duration = reader->ReadU32();
+ media_time = reader->Read32();
+ }
+ if (media_time == -1 && i) {
+ LOG(Edts, "Multiple empty edit, not handled");
+ } else if (media_time == -1) {
+ mEmptyOffset = segment_duration;
+ emptyEntry = true;
+ } else if (i > 1 || (i > 0 && !emptyEntry)) {
+ LOG(Edts, "More than one edit entry, not handled. A/V sync will be wrong");
+ break;
+ } else {
+ mMediaStart = media_time;
+ }
+ reader->ReadU32(); // media_rate_integer and media_rate_fraction
+ }
+}
+
+Saiz::Saiz(Box& aBox, AtomType aDefaultType)
+ : mAuxInfoType(aDefaultType)
+ , mAuxInfoTypeParameter(0)
+{
+ BoxReader reader(aBox);
+ if (!reader->CanReadType<uint32_t>()) {
+ LOG(Saiz, "Incomplete Box (missing flags)");
+ return;
+ }
+ uint32_t flags = reader->ReadU32();
+ uint8_t version = flags >> 24;
+ size_t need =
+ ((flags & 1) ? 2*sizeof(uint32_t) : 0) + sizeof(uint8_t) + sizeof(uint32_t);
+ if (reader->Remaining() < need) {
+ LOG(Saiz, "Incomplete Box (have:%lld need:%lld)",
+ (uint64_t)reader->Remaining(), (uint64_t)need);
+ return;
+ }
+ if (flags & 1) {
+ mAuxInfoType = reader->ReadU32();
+ mAuxInfoTypeParameter = reader->ReadU32();
+ }
+ uint8_t defaultSampleInfoSize = reader->ReadU8();
+ uint32_t count = reader->ReadU32();
+ if (defaultSampleInfoSize) {
+ if (!mSampleInfoSize.SetLength(count, fallible)) {
+ LOG(Saiz, "OOM");
+ return;
+ }
+ memset(mSampleInfoSize.Elements(), defaultSampleInfoSize, mSampleInfoSize.Length());
+ } else {
+ if (!reader->ReadArray(mSampleInfoSize, count)) {
+ LOG(Saiz, "Incomplete Box (OOM or missing count:%u)", count);
+ return;
+ }
+ }
+ mValid = true;
+}
+
+Saio::Saio(Box& aBox, AtomType aDefaultType)
+ : mAuxInfoType(aDefaultType)
+ , mAuxInfoTypeParameter(0)
+{
+ BoxReader reader(aBox);
+ if (!reader->CanReadType<uint32_t>()) {
+ LOG(Saio, "Incomplete Box (missing flags)");
+ return;
+ }
+ uint32_t flags = reader->ReadU32();
+ uint8_t version = flags >> 24;
+ size_t need = ((flags & 1) ? (2*sizeof(uint32_t)) : 0) + sizeof(uint32_t);
+ if (reader->Remaining() < need) {
+ LOG(Saio, "Incomplete Box (have:%lld need:%lld)",
+ (uint64_t)reader->Remaining(), (uint64_t)need);
+ return;
+ }
+ if (flags & 1) {
+ mAuxInfoType = reader->ReadU32();
+ mAuxInfoTypeParameter = reader->ReadU32();
+ }
+ size_t count = reader->ReadU32();
+ need = (version ? sizeof(uint64_t) : sizeof(uint32_t)) * count;
+ if (reader->Remaining() < need) {
+ LOG(Saio, "Incomplete Box (have:%lld need:%lld)",
+ (uint64_t)reader->Remaining(), (uint64_t)need);
+ return;
+ }
+ if (!mOffsets.SetCapacity(count, fallible)) {
+ LOG(Saiz, "OOM");
+ return;
+ }
+ if (version == 0) {
+ for (size_t i = 0; i < count; i++) {
+ MOZ_ALWAYS_TRUE(mOffsets.AppendElement(reader->ReadU32(), fallible));
+ }
+ } else {
+ for (size_t i = 0; i < count; i++) {
+ MOZ_ALWAYS_TRUE(mOffsets.AppendElement(reader->ReadU64(), fallible));
+ }
+ }
+ mValid = true;
+}
+
+#undef LOG
+}