From 61991917829e39029edf99298f8c566c29c99617 Mon Sep 17 00:00:00 2001 From: Anthony Jones Date: Fri, 29 Aug 2014 14:29:11 +1200 Subject: [PATCH] Bug 1057225 - Fix MP4 timestamp discontinuities due to rounding; r=edwin --- media/libstagefright/binding/Index.cpp | 4 +- media/libstagefright/binding/MoofParser.cpp | 66 +++++++++++++++---- .../binding/include/mp4_demuxer/Index.h | 1 - .../binding/include/mp4_demuxer/MoofParser.h | 15 ++++- media/libstagefright/binding/mp4_demuxer.cpp | 2 +- 5 files changed, 70 insertions(+), 18 deletions(-) diff --git a/media/libstagefright/binding/Index.cpp b/media/libstagefright/binding/Index.cpp index a31c20d1bfd..8f19358b31b 100644 --- a/media/libstagefright/binding/Index.cpp +++ b/media/libstagefright/binding/Index.cpp @@ -153,7 +153,7 @@ Index::ConvertByteRangesToTimeRanges( // We need the entire moof in order to play anything if (rangeFinder.Contains(moof.mRange)) { if (rangeFinder.Contains(moof.mMdatRange)) { - timeRanges.AppendElements(moof.mTimeRanges); + Interval::SemiNormalAppend(timeRanges, moof.mTimeRange); } else { indexes.AppendElement(&moof.mIndex); } @@ -199,7 +199,7 @@ Index::GetEvictionOffset(Microseconds aTime) for (int i = 0; i < mMoofParser->mMoofs.Length(); i++) { Moof& moof = mMoofParser->mMoofs[i]; - if (!moof.mTimeRanges.IsEmpty() && moof.mTimeRanges[0].end > aTime) { + if (moof.mTimeRange.Length() && moof.mTimeRange.end > aTime) { offset = std::min(offset, uint64_t(std::min(moof.mRange.mStart, moof.mMdatRange.mStart))); } diff --git a/media/libstagefright/binding/MoofParser.cpp b/media/libstagefright/binding/MoofParser.cpp index 151a1156658..7d654f4f755 100644 --- a/media/libstagefright/binding/MoofParser.cpp +++ b/media/libstagefright/binding/MoofParser.cpp @@ -21,7 +21,15 @@ MoofParser::RebuildFragmentedIndex(const nsTArray& aByteRanges) if (box.IsType("moov")) { ParseMoov(box); } else if (box.IsType("moof")) { - mMoofs.AppendElement(Moof(box, mTrex, mMdhd)); + Moof moof(box, mTrex, mMdhd); + + 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); } mOffset = box.NextOffset(); } @@ -32,10 +40,7 @@ MoofParser::GetCompositionRange() { Interval compositionRange; for (size_t i = 0; i < mMoofs.Length(); i++) { - nsTArray>& compositionRanges = mMoofs[i].mTimeRanges; - for (int j = 0; j < compositionRanges.Length(); j++) { - compositionRange = compositionRange.Extents(compositionRanges[j]); - } + compositionRange = compositionRange.Extents(mMoofs[i].mTimeRange); } return compositionRange; } @@ -97,7 +102,8 @@ MoofParser::ParseMvex(Box& aBox) } } -Moof::Moof(Box& aBox, Trex& aTrex, Mdhd& aMdhd) : mRange(aBox.Range()) +Moof::Moof(Box& aBox, Trex& aTrex, Mdhd& aMdhd) : + mRange(aBox.Range()), mMaxRoundingError(0) { for (Box box = aBox.FirstChild(); box.IsAvailable(); box = box.Next()) { if (box.IsType("traf")) { @@ -126,6 +132,28 @@ Moof::ParseTraf(Box& aBox, Trex& aTrex, Mdhd& aMdhd) } } +void +Moof::FixRounding(const Moof& aMoof) { + Microseconds gap = aMoof.mTimeRange.start - mTimeRange.end; + if (gap > 0 && gap <= mMaxRoundingError) { + mTimeRange.end = aMoof.mTimeRange.start; + } +} + +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; + } +}; + void Moof::ParseTrun(Box& aBox, Tfhd& aTfhd, Tfdt& aTfdt, Mdhd& aMdhd) { @@ -142,6 +170,10 @@ Moof::ParseTrun(Box& aBox, Tfhd& aTfhd, Tfdt& aTfdt, Mdhd& aMdhd) } uint32_t sampleCount = reader->ReadU32(); + if (sampleCount == 0) { + return; + } + uint64_t offset = aTfhd.mBaseDataOffset + (flags & 1 ? reader->ReadU32() : 0); bool hasFirstSampleFlags = flags & 4; uint32_t firstSampleFlags = hasFirstSampleFlags ? reader->ReadU32() : 0; @@ -163,8 +195,8 @@ Moof::ParseTrun(Box& aBox, Tfhd& aTfhd, Tfdt& aTfdt, Mdhd& aMdhd) offset += sampleSize; sample.mCompositionRange = Interval( - ((decodeTime + ctsOffset) * 1000000ll) / aMdhd.mTimescale, - ((decodeTime + sampleDuration + ctsOffset) * 1000000ll) / aMdhd.mTimescale); + aMdhd.ToMicroseconds(decodeTime + ctsOffset), + aMdhd.ToMicroseconds(decodeTime + ctsOffset + sampleDuration)); decodeTime += sampleDuration; sample.mSync = !(sampleFlags & 0x1010000); @@ -172,10 +204,22 @@ Moof::ParseTrun(Box& aBox, Tfhd& aTfhd, Tfdt& aTfdt, Mdhd& aMdhd) mIndex.AppendElement(sample); mMdatRange = mMdatRange.Extents(sample.mByteRange); - Interval::SemiNormalAppend(timeRanges, - sample.mCompositionRange); } - Interval::Normalize(timeRanges, &mTimeRanges); + mMaxRoundingError += aMdhd.ToMicroseconds(sampleCount); + + nsTArray ctsOrder; + for (int i = 0; i < mIndex.Length(); i++) { + ctsOrder.AppendElement(&mIndex[i]); + } + ctsOrder.Sort(CtsComparator()); + + for (int i = 0; i < ctsOrder.Length(); i++) { + if (i + 1 < ctsOrder.Length()) { + ctsOrder[i]->mCompositionRange.end = ctsOrder[i + 1]->mCompositionRange.start; + } + } + mTimeRange = Interval(ctsOrder[0]->mCompositionRange.start, + ctsOrder.LastElement()->mCompositionRange.end); } Tkhd::Tkhd(Box& aBox) diff --git a/media/libstagefright/binding/include/mp4_demuxer/Index.h b/media/libstagefright/binding/include/mp4_demuxer/Index.h index f9bb1cfe634..23156035473 100644 --- a/media/libstagefright/binding/include/mp4_demuxer/Index.h +++ b/media/libstagefright/binding/include/mp4_demuxer/Index.h @@ -6,7 +6,6 @@ #define INDEX_H_ #include "media/stagefright/MediaSource.h" -#include "mozilla/Monitor.h" #include "mp4_demuxer/mp4_demuxer.h" namespace mp4_demuxer diff --git a/media/libstagefright/binding/include/mp4_demuxer/MoofParser.h b/media/libstagefright/binding/include/mp4_demuxer/MoofParser.h index 23a1837ceb8..8988c8fc911 100644 --- a/media/libstagefright/binding/include/mp4_demuxer/MoofParser.h +++ b/media/libstagefright/binding/include/mp4_demuxer/MoofParser.h @@ -44,6 +44,11 @@ public: } Mdhd(Box& aBox); + Microseconds ToMicroseconds(uint64_t aTimescaleUnits) + { + return aTimescaleUnits * 1000000ll / mTimescale; + } + uint64_t mCreationTime; uint64_t mModificationTime; uint32_t mTimescale; @@ -102,13 +107,17 @@ class Moof { public: Moof(Box& aBox, Trex& aTrex, Mdhd& aMdhd); - void ParseTraf(Box& aBox, Trex& aTrex, Mdhd& aMdhd); - void ParseTrun(Box& aBox, Tfhd& aTfhd, Tfdt& aTfdt, Mdhd& aMdhd); + void FixRounding(const Moof& aMoof); mozilla::MediaByteRange mRange; mozilla::MediaByteRange mMdatRange; - nsTArray> mTimeRanges; + Interval mTimeRange; nsTArray mIndex; + +private: + void ParseTraf(Box& aBox, Trex& aTrex, Mdhd& aMdhd); + void ParseTrun(Box& aBox, Tfhd& aTfhd, Tfdt& aTfdt, Mdhd& aMdhd); + uint64_t mMaxRoundingError; }; class MoofParser diff --git a/media/libstagefright/binding/mp4_demuxer.cpp b/media/libstagefright/binding/mp4_demuxer.cpp index b54d170b6e9..87bf8e2c8e9 100644 --- a/media/libstagefright/binding/mp4_demuxer.cpp +++ b/media/libstagefright/binding/mp4_demuxer.cpp @@ -253,7 +253,7 @@ MP4Demuxer::GetEvictionOffset(Microseconds aTime) for (int i = 0; i < mPrivate->mIndexes.Length(); i++) { offset = std::min(offset, mPrivate->mIndexes[i]->GetEvictionOffset(aTime)); } - return offset; + return offset == std::numeric_limits::max() ? -1 : offset; } } // namespace mp4_demuxer