Bug 1314533: [MSE] P6. Allow to detect error during preliminary container parsing. r?gerald
MozReview-Commit-ID: KZ858ISWDmu
--- a/dom/media/mediasource/ContainerParser.cpp
+++ b/dom/media/mediasource/ContainerParser.cpp
@@ -57,21 +57,21 @@ ContainerParser::IsMediaSegmentPresent(M
aData->Length(),
aData->Length() > 0 ? (*aData)[0] : 0,
aData->Length() > 1 ? (*aData)[1] : 0,
aData->Length() > 2 ? (*aData)[2] : 0,
aData->Length() > 3 ? (*aData)[3] : 0);
return NS_ERROR_NOT_AVAILABLE;
}
-bool
+MediaResult
ContainerParser::ParseStartAndEndTimestamps(MediaByteBuffer* aData,
int64_t& aStart, int64_t& aEnd)
{
- return false;
+ return NS_ERROR_NOT_AVAILABLE;
}
bool
ContainerParser::TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs)
{
return llabs(aLhs - aRhs) <= GetRoundingError();
}
@@ -175,32 +175,33 @@ public:
// 0x1c53bb6b // Cues
if ((*aData)[0] == 0x1c && (*aData)[1] == 0x53 && (*aData)[2] == 0xbb &&
(*aData)[3] == 0x6b) {
return NS_OK;
}
return MediaResult(NS_ERROR_FAILURE, RESULT_DETAIL("Invalid webm content"));
}
- bool ParseStartAndEndTimestamps(MediaByteBuffer* aData,
- int64_t& aStart, int64_t& aEnd) override
+ MediaResult ParseStartAndEndTimestamps(MediaByteBuffer* aData,
+ int64_t& aStart,
+ int64_t& aEnd) override
{
bool initSegment = NS_SUCCEEDED(IsInitSegmentPresent(aData));
if (mLastMapping &&
(initSegment || NS_SUCCEEDED(IsMediaSegmentPresent(aData)))) {
// The last data contained a complete cluster but we can only detect it
// now that a new one is starting.
// We use mOffset as end position to ensure that any blocks not reported
// by WebMBufferParser are properly skipped.
mCompleteMediaSegmentRange = MediaByteRange(mLastMapping.ref().mSyncOffset,
mOffset);
mLastMapping.reset();
MSE_DEBUG(WebMContainerParser, "New cluster found at start, ending previous one");
- return false;
+ return NS_ERROR_NOT_AVAILABLE;
}
if (initSegment) {
mOffset = 0;
mParser = WebMBufferedParser(0);
mOverlappedMapping.Clear();
mInitData = new MediaByteBuffer();
mResource = new SourceBufferResource(NS_LITERAL_CSTRING("video/webm"));
@@ -222,33 +223,33 @@ public:
// XXX This is a bit of a hack. Assume if there are no timecodes
// present and it's an init segment that it's _just_ an init segment.
// We should be more precise.
if (initSegment || !HasCompleteInitData()) {
if (mParser.mInitEndOffset > 0) {
MOZ_ASSERT(mParser.mInitEndOffset <= mResource->GetLength());
if (!mInitData->SetLength(mParser.mInitEndOffset, fallible)) {
// Super unlikely OOM
- return false;
+ return NS_ERROR_OUT_OF_MEMORY;
}
mCompleteInitSegmentRange = MediaByteRange(0, mParser.mInitEndOffset);
char* buffer = reinterpret_cast<char*>(mInitData->Elements());
mResource->ReadFromCache(buffer, 0, mParser.mInitEndOffset);
MSE_DEBUG(WebMContainerParser, "Stashed init of %u bytes.",
mParser.mInitEndOffset);
mResource = nullptr;
} else {
MSE_DEBUG(WebMContainerParser, "Incomplete init found.");
}
mHasInitData = true;
}
mOffset += aData->Length();
if (mapping.IsEmpty()) {
- return false;
+ return NS_ERROR_NOT_AVAILABLE;
}
// Calculate media range for first media segment.
// Check if we have a cluster finishing in the current data.
uint32_t endIdx = mapping.Length() - 1;
bool foundNewCluster = false;
while (mapping[0].mSyncOffset != mapping[endIdx].mSyncOffset) {
@@ -264,17 +265,17 @@ public:
}
// Save parsed blocks for which we do not have all data yet.
mOverlappedMapping.AppendElements(mapping.Elements() + completeIdx + 1,
mapping.Length() - completeIdx - 1);
if (completeIdx < 0) {
mLastMapping.reset();
- return false;
+ return NS_ERROR_NOT_AVAILABLE;
}
if (mCompleteMediaHeaderRange.IsEmpty()) {
mCompleteMediaHeaderRange = MediaByteRange(mapping[0].mSyncOffset,
mapping[0].mEndOffset);
}
if (foundNewCluster && mOffset >= mapping[endIdx].mEndOffset) {
@@ -299,31 +300,31 @@ public:
previousMapping = mLastMapping;
}
mLastMapping = Some(mapping[completeIdx]);
if (!previousMapping && completeIdx + 1u >= mapping.Length()) {
// We have no previous nor next block available,
// so we can't estimate this block's duration.
- return false;
+ return NS_ERROR_NOT_AVAILABLE;
}
uint64_t frameDuration = (completeIdx + 1u < mapping.Length())
? mapping[completeIdx + 1].mTimecode - mapping[completeIdx].mTimecode
: mapping[completeIdx].mTimecode - previousMapping.ref().mTimecode;
aStart = mapping[0].mTimecode / NS_PER_USEC;
aEnd = (mapping[completeIdx].mTimecode + frameDuration) / NS_PER_USEC;
MSE_DEBUG(WebMContainerParser, "[%lld, %lld] [fso=%lld, leo=%lld, l=%u processedIdx=%u fs=%lld]",
aStart, aEnd, mapping[0].mSyncOffset,
mapping[completeIdx].mEndOffset, mapping.Length(), completeIdx,
mCompleteMediaSegmentRange.mEnd);
- return true;
+ return NS_OK;
}
int64_t GetRoundingError() override
{
int64_t error = mParser.GetTimecodeScale() / NS_PER_USEC;
return error * 2;
}
@@ -455,46 +456,47 @@ private:
private:
Maybe<size_t> mInitOffset;
Maybe<size_t> mMediaOffset;
bool mValid = false;
char mLastInvalidBox[5];
};
public:
- bool ParseStartAndEndTimestamps(MediaByteBuffer* aData,
- int64_t& aStart, int64_t& aEnd) override
+ MediaResult ParseStartAndEndTimestamps(MediaByteBuffer* aData,
+ int64_t& aStart,
+ int64_t& aEnd) override
{
bool initSegment = NS_SUCCEEDED(IsInitSegmentPresent(aData));
if (initSegment) {
mResource = new SourceBufferResource(NS_LITERAL_CSTRING("video/mp4"));
mStream = new MP4Stream(mResource);
// We use a timestampOffset of 0 for ContainerParser, and require
// consumers of ParseStartAndEndTimestamps to add their timestamp offset
// manually. This allows the ContainerParser to be shared across different
// timestampOffsets.
mParser = new mp4_demuxer::MoofParser(mStream, 0, /* aIsAudio = */ false);
mInitData = new MediaByteBuffer();
} else if (!mStream || !mParser) {
- return false;
+ return NS_ERROR_NOT_AVAILABLE;
}
mResource->AppendData(aData);
MediaByteRangeSet byteRanges;
byteRanges +=
MediaByteRange(int64_t(mParser->mOffset), mResource->GetLength());
mParser->RebuildFragmentedIndex(byteRanges);
if (initSegment || !HasCompleteInitData()) {
MediaByteRange& range = mParser->mInitRange;
if (range.Length()) {
mCompleteInitSegmentRange = range;
if (!mInitData->SetLength(range.Length(), fallible)) {
// Super unlikely OOM
- return false;
+ return NS_ERROR_OUT_OF_MEMORY;
}
char* buffer = reinterpret_cast<char*>(mInitData->Elements());
mResource->ReadFromCache(buffer, range.mStart, range.Length());
MSE_DEBUG(MP4ContainerParser ,"Stashed init of %u bytes.",
range.Length());
} else {
MSE_DEBUG(MP4ContainerParser, "Incomplete init found.");
}
@@ -507,27 +509,27 @@ public:
mCompleteMediaHeaderRange = mParser->FirstCompleteMediaHeader();
mCompleteMediaSegmentRange = mParser->FirstCompleteMediaSegment();
ErrorResult rv;
if (HasCompleteInitData()) {
mResource->EvictData(mParser->mOffset, mParser->mOffset, rv);
}
if (NS_WARN_IF(rv.Failed())) {
rv.SuppressException();
- return false;
+ return NS_ERROR_OUT_OF_MEMORY;
}
if (compositionRange.IsNull()) {
- return false;
+ return NS_ERROR_NOT_AVAILABLE;
}
aStart = compositionRange.start;
aEnd = compositionRange.end;
MSE_DEBUG(MP4ContainerParser, "[%lld, %lld]",
aStart, aEnd);
- return true;
+ return NS_OK;
}
// Gaps of up to 35ms (marginally longer than a single frame at 30fps) are considered
// to be sequential frames.
int64_t GetRoundingError() override
{
return 35000;
}
@@ -634,50 +636,51 @@ public:
if (aData->Length() <= header.header_length) {
return NS_ERROR_NOT_AVAILABLE;
}
// We should have at least a partial frame.
return NS_OK;
}
- bool ParseStartAndEndTimestamps(MediaByteBuffer* aData,
- int64_t& aStart, int64_t& aEnd) override
+ MediaResult ParseStartAndEndTimestamps(MediaByteBuffer* aData,
+ int64_t& aStart,
+ int64_t& aEnd) override
{
// ADTS header.
Header header;
if (!Parse(aData, header)) {
- return false;
+ return NS_ERROR_NOT_AVAILABLE;
}
mHasInitData = true;
mCompleteInitSegmentRange = MediaByteRange(0, int64_t(header.header_length));
// Cache raw header in case the caller wants a copy.
mInitData = new MediaByteBuffer(header.header_length);
mInitData->AppendElements(aData->Elements(), header.header_length);
// Check that we have enough data for the frame body.
if (aData->Length() < header.frame_length) {
MSE_DEBUGV(ADTSContainerParser, "Not enough data for %llu byte frame"
" in %llu byte buffer.",
(unsigned long long)header.frame_length,
(unsigned long long)(aData->Length()));
- return false;
+ return NS_ERROR_NOT_AVAILABLE;
}
mCompleteMediaSegmentRange = MediaByteRange(header.header_length,
header.frame_length);
// The ADTS MediaSource Byte Stream Format document doesn't
// define media header. Just treat it the same as the whole
// media segment.
mCompleteMediaHeaderRange = mCompleteMediaSegmentRange;
MSE_DEBUG(ADTSContainerParser, "[%lld, %lld]",
aStart, aEnd);
// We don't update timestamps, regardless.
- return false;
+ return NS_ERROR_NOT_AVAILABLE;
}
// Audio shouldn't have gaps.
// Especially when we generate the timestamps ourselves.
int64_t GetRoundingError() override
{
return 0;
}
--- a/dom/media/mediasource/ContainerParser.h
+++ b/dom/media/mediasource/ContainerParser.h
@@ -34,20 +34,21 @@ public:
// The base implementation exists only for debug logging and is expected
// to be called first from the overriding implementation.
// Return NS_OK if segment is present, NS_ERROR_NOT_AVAILABLE if no sufficient
// data is currently available to make a determination. Any other value
// indicates an error.
virtual MediaResult IsMediaSegmentPresent(MediaByteBuffer* aData);
// Parse aData to extract the start and end frame times from the media
- // segment. aData may not start on a parser sync boundary. Return true
- // if aStart and aEnd have been updated.
- virtual bool ParseStartAndEndTimestamps(MediaByteBuffer* aData,
- int64_t& aStart, int64_t& aEnd);
+ // segment. aData may not start on a parser sync boundary. Return NS_OK
+ // if aStart and aEnd have been updated and NS_ERROR_NOT_AVAILABLE otherwise
+ // when no error were encountered.
+ virtual MediaResult ParseStartAndEndTimestamps(MediaByteBuffer* aData,
+ int64_t& aStart, int64_t& aEnd);
// Compare aLhs and rHs, considering any error that may exist in the
// timestamps from the format's base representation. Return true if aLhs
// == aRhs within the error epsilon.
bool TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs);
virtual int64_t GetRoundingError();
--- a/dom/media/mediasource/TrackBuffersManager.cpp
+++ b/dom/media/mediasource/TrackBuffersManager.cpp
@@ -665,17 +665,22 @@ TrackBuffersManager::SegmentParserLoop()
return;
}
MSE_DEBUG("Found incomplete data.");
NeedMoreData();
return;
}
int64_t start, end;
- bool newData = mParser->ParseStartAndEndTimestamps(mInputBuffer, start, end);
+ MediaResult newData =
+ mParser->ParseStartAndEndTimestamps(mInputBuffer, start, end);
+ if (!NS_SUCCEEDED(newData) && newData.Code() != NS_ERROR_NOT_AVAILABLE) {
+ RejectAppend(newData, __func__);
+ return;
+ }
mProcessedInput += mInputBuffer->Length();
// 5. If the append state equals PARSING_INIT_SEGMENT, then run the
// following steps:
if (mSourceBufferAttributes->GetAppendState() == AppendState::PARSING_INIT_SEGMENT) {
if (mParser->InitSegmentRange().IsEmpty()) {
mInputBuffer = nullptr;
NeedMoreData();
@@ -692,23 +697,23 @@ TrackBuffersManager::SegmentParserLoop()
}
// We can't feed some demuxers (WebMDemuxer) with data that do not have
// monotonizally increasing timestamps. So we check if we have a
// discontinuity from the previous segment parsed.
// If so, recreate a new demuxer to ensure that the demuxer is only fed
// monotonically increasing data.
if (mNewMediaSegmentStarted) {
- if (newData && mLastParsedEndTime.isSome() &&
+ if (NS_SUCCEEDED(newData) && mLastParsedEndTime.isSome() &&
start < mLastParsedEndTime.ref().ToMicroseconds()) {
MSE_DEBUG("Re-creating demuxer");
ResetDemuxingState();
return;
}
- if (newData || !mParser->MediaSegmentRange().IsEmpty()) {
+ if (NS_SUCCEEDED(newData) || !mParser->MediaSegmentRange().IsEmpty()) {
if (mPendingInputBuffer) {
// We now have a complete media segment header. We can resume parsing
// the data.
AppendDataToCurrentInputBuffer(mPendingInputBuffer);
mPendingInputBuffer = nullptr;
}
mNewMediaSegmentStarted = false;
} else {
--- a/dom/media/mediasource/gtest/TestContainerParser.cpp
+++ b/dom/media/mediasource/gtest/TestContainerParser.cpp
@@ -72,17 +72,17 @@ TEST(ContainerParser, ADTSHeader) {
<< "Found media segment when there was just a partial header.";
// Test parse results.
header = make_adts_header();
EXPECT_FALSE(NS_SUCCEEDED(parser->IsMediaSegmentPresent(header)))
<< "Found media segment when there was just a header.";
int64_t start = 0;
int64_t end = 0;
- EXPECT_FALSE(parser->ParseStartAndEndTimestamps(header, start, end));
+ EXPECT_TRUE(NS_FAILED(parser->ParseStartAndEndTimestamps(header, start, end)));
EXPECT_TRUE(parser->HasInitData());
EXPECT_TRUE(parser->HasCompleteInitData());
MediaByteBuffer* init = parser->InitData();
ASSERT_NE(init, nullptr);
EXPECT_EQ(init->Length(), header->Length());
EXPECT_EQ(parser->InitSegmentRange(), MediaByteRange(0, int64_t(header->Length())));