Bug 1227396: P13. Refactor how MP4 buffered range is calculated. r=cpearce

We now use an index of samples made of block of samples delimited by keyframes. The search is performed using a binary search. This allows to quickly find which blocks are found within the media cache.
On a 8 core mac pro, this leads to a 67% improvement on CPU time spent playing a long MP4 video (from 112s CPU to 45s CPU)

The optimisation is only possible if all mp4 data chunks are continuous (which they almost always are)
This commit is contained in:
Jean-Yves Avenard 2015-11-27 15:03:19 +11:00
parent ead53cc75e
commit bb0aacbf79
3 changed files with 146 additions and 27 deletions

View File

@ -409,17 +409,8 @@ MP4TrackDemuxer::GetBuffered()
if (NS_FAILED(rv)) {
return media::TimeIntervals();
}
nsTArray<mp4_demuxer::Interval<int64_t>> timeRanges;
mIndex->ConvertByteRangesToTimeRanges(byteRanges, &timeRanges);
// convert timeRanges.
media::TimeIntervals ranges = media::TimeIntervals();
for (size_t i = 0; i < timeRanges.Length(); i++) {
ranges +=
media::TimeInterval(media::TimeUnit::FromMicroseconds(timeRanges[i].start),
media::TimeUnit::FromMicroseconds(timeRanges[i].end));
}
return ranges;
return mIndex->ConvertByteRangesToTimeRanges(byteRanges);
}
void

View File

@ -5,7 +5,6 @@
#include "mp4_demuxer/ByteReader.h"
#include "mp4_demuxer/Index.h"
#include "mp4_demuxer/Interval.h"
#include "mp4_demuxer/MoofParser.h"
#include "mp4_demuxer/SinfParser.h"
#include "nsAutoPtr.h"
#include "mozilla/RefPtr.h"
@ -15,6 +14,7 @@
using namespace stagefright;
using namespace mozilla;
using namespace mozilla::media;
namespace mp4_demuxer
{
@ -45,7 +45,7 @@ RangeFinder::Contains(MediaByteRange aByteRange)
return false;
}
if (mRanges[mIndex].Contains(aByteRange)) {
if (mRanges[mIndex].ContainsStrict(aByteRange)) {
return true;
}
@ -56,7 +56,7 @@ RangeFinder::Contains(MediaByteRange aByteRange)
return false;
}
--mIndex;
if (mRanges[mIndex].Contains(aByteRange)) {
if (mRanges[mIndex].ContainsStrict(aByteRange)) {
return true;
}
} while (aByteRange.mStart < mRanges[mIndex].mStart);
@ -69,7 +69,7 @@ RangeFinder::Contains(MediaByteRange aByteRange)
return false;
}
++mIndex;
if (mRanges[mIndex].Contains(aByteRange)) {
if (mRanges[mIndex].ContainsStrict(aByteRange)) {
return true;
}
}
@ -246,8 +246,19 @@ Index::Index(const nsTArray<Indice>& aIndex,
// OOM.
return;
}
for (size_t i = 0; i < aIndex.Length(); i++) {
const Indice& indice = aIndex[i];
media::IntervalSet<int64_t> intervalTime;
MediaByteRange intervalRange;
bool haveSync = false;
bool progressive = true;
int64_t lastOffset = 0;
for (const Indice& indice : aIndex) {
if (indice.sync) {
haveSync = true;
}
if (!haveSync) {
continue;
}
Sample sample;
sample.mByteRange = MediaByteRange(indice.start_offset,
indice.end_offset);
@ -257,6 +268,40 @@ Index::Index(const nsTArray<Indice>& aIndex,
sample.mSync = indice.sync;
// FIXME: Make this infallible after bug 968520 is done.
MOZ_ALWAYS_TRUE(mIndex.AppendElement(sample, fallible));
if (indice.start_offset < lastOffset) {
NS_WARNING("Chunks in MP4 out of order, expect slow down");
progressive = false;
}
lastOffset = indice.end_offset;
if (sample.mSync && progressive) {
if (mDataOffset.Length()) {
auto& last = mDataOffset.LastElement();
last.mEndOffset = intervalRange.mEnd;
NS_ASSERTION(intervalTime.Length() == 1, "Discontinuous samples between keyframes");
last.mTime.start = intervalTime.GetStart();
last.mTime.end = intervalTime.GetEnd();
}
if (!mDataOffset.AppendElement(MP4DataOffset(mIndex.Length() - 1,
indice.start_offset),
fallible)) {
// OOM.
return;
}
intervalTime = media::IntervalSet<int64_t>();
intervalRange = MediaByteRange();
}
intervalTime += media::Interval<int64_t>(sample.mCompositionRange.start,
sample.mCompositionRange.end);
intervalRange = intervalRange.Span(sample.mByteRange);
}
if (mDataOffset.Length() && progressive) {
auto& last = mDataOffset.LastElement();
last.mEndOffset = aIndex.LastElement().end_offset;
last.mTime = Interval<int64_t>(intervalTime.GetStart(), intervalTime.GetEnd());
} else {
mDataOffset.Clear();
}
}
}
@ -301,14 +346,47 @@ Index::GetEndCompositionIfBuffered(const MediaByteRangeSet& aByteRanges)
return 0;
}
void
Index::ConvertByteRangesToTimeRanges(
const MediaByteRangeSet& aByteRanges,
nsTArray<Interval<Microseconds>>* aTimeRanges)
TimeIntervals
Index::ConvertByteRangesToTimeRanges(const MediaByteRangeSet& aByteRanges)
{
if (aByteRanges == mLastCachedRanges) {
return mLastBufferedRanges;
}
mLastCachedRanges = aByteRanges;
if (mDataOffset.Length()) {
TimeIntervals timeRanges;
for (const auto& range : aByteRanges) {
uint32_t start = mDataOffset.IndexOfFirstElementGt(range.mStart - 1);
if (start == mDataOffset.Length()) {
continue;
}
uint32_t end = mDataOffset.IndexOfFirstElementGt(range.mEnd, MP4DataOffset::EndOffsetComparator());
if (end < start) {
continue;
}
if (end > start) {
timeRanges +=
TimeInterval(TimeUnit::FromMicroseconds(mDataOffset[start].mTime.start),
TimeUnit::FromMicroseconds(mDataOffset[end-1].mTime.end));
}
if (end < mDataOffset.Length()) {
// Find samples in partial block contained in the byte range.
for (size_t i = mDataOffset[end].mIndex;
i < mIndex.Length() && range.ContainsStrict(mIndex[i].mByteRange);
i++) {
timeRanges +=
TimeInterval(TimeUnit::FromMicroseconds(mIndex[i].mCompositionRange.start),
TimeUnit::FromMicroseconds(mIndex[i].mCompositionRange.end));
}
}
}
mLastBufferedRanges = timeRanges;
return timeRanges;
}
RangeFinder rangeFinder(aByteRanges);
nsTArray<Interval<Microseconds>> timeRanges;
nsTArray<FallibleTArray<Sample>*> indexes;
if (mMoofParser) {
// We take the index out of the moof parser and move it into a local
@ -353,7 +431,17 @@ Index::ConvertByteRangesToTimeRanges(
}
// This fixes up when the compositon order differs from the byte range order
Interval<Microseconds>::Normalize(timeRanges, aTimeRanges);
nsTArray<Interval<Microseconds>> timeRangesNormalized;
Interval<Microseconds>::Normalize(timeRanges, &timeRangesNormalized);
// convert timeRanges.
media::TimeIntervals ranges;
for (size_t i = 0; i < timeRangesNormalized.Length(); i++) {
ranges +=
media::TimeInterval(media::TimeUnit::FromMicroseconds(timeRangesNormalized[i].start),
media::TimeUnit::FromMicroseconds(timeRangesNormalized[i].end));
}
mLastBufferedRanges = ranges;
return ranges;
}
uint64_t

View File

@ -7,6 +7,8 @@
#include "MediaData.h"
#include "MediaResource.h"
#include "TimeUnits.h"
#include "mp4_demuxer/MoofParser.h"
#include "mp4_demuxer/Interval.h"
#include "mp4_demuxer/Stream.h"
#include "nsISupportsImpl.h"
@ -17,8 +19,6 @@ namespace mp4_demuxer
{
class Index;
class MoofParser;
struct Sample;
typedef int64_t Microseconds;
@ -53,6 +53,42 @@ public:
bool sync;
};
struct MP4DataOffset
{
MP4DataOffset(uint32_t aIndex, int64_t aStartOffset)
: mIndex(aIndex)
, mStartOffset(aStartOffset)
, mEndOffset(0)
{}
bool operator==(int64_t aStartOffset) const {
return mStartOffset == aStartOffset;
}
bool operator!=(int64_t aStartOffset) const {
return mStartOffset != aStartOffset;
}
bool operator<(int64_t aStartOffset) const {
return mStartOffset < aStartOffset;
}
struct EndOffsetComparator {
bool Equals(const MP4DataOffset& a, const int64_t& b) const {
return a.mEndOffset == b;
}
bool LessThan(const MP4DataOffset& a, const int64_t& b) const {
return a.mEndOffset < b;
}
};
uint32_t mIndex;
int64_t mStartOffset;
int64_t mEndOffset;
Interval<Microseconds> mTime;
};
Index(const nsTArray<Indice>& aIndex,
Stream* aSource,
uint32_t aTrackId,
@ -61,9 +97,8 @@ public:
void UpdateMoofIndex(const mozilla::MediaByteRangeSet& aByteRanges);
Microseconds GetEndCompositionIfBuffered(
const mozilla::MediaByteRangeSet& aByteRanges);
void ConvertByteRangesToTimeRanges(
const mozilla::MediaByteRangeSet& aByteRanges,
nsTArray<Interval<Microseconds>>* aTimeRanges);
mozilla::media::TimeIntervals ConvertByteRangesToTimeRanges(
const mozilla::MediaByteRangeSet& aByteRanges);
uint64_t GetEvictionOffset(Microseconds aTime);
bool IsFragmented() { return mMoofParser; }
@ -74,7 +109,12 @@ private:
Stream* mSource;
FallibleTArray<Sample> mIndex;
FallibleTArray<MP4DataOffset> mDataOffset;
nsAutoPtr<MoofParser> mMoofParser;
// ConvertByteRangesToTimeRanges cache
mozilla::MediaByteRangeSet mLastCachedRanges;
mozilla::media::TimeIntervals mLastBufferedRanges;
};
}