Bug 1314533: [MSE] P6. Allow to detect error during preliminary container parsing. r=gerald
authorJean-Yves Avenard <jyavenard@mozilla.com>
Fri, 04 Nov 2016 23:09:47 +1100
changeset 321372 ca6e6783a37994cea645cb95308c52b0851becbe
parent 321371 ab4d4dfee0743f13ec660d757b7a5d5d97b8879d
child 321373 87882fa5ef49414b7dd978e35a01aa21a7166095
push id21
push usermaklebus@msu.edu
push dateThu, 01 Dec 2016 06:22:08 +0000
reviewersgerald
bugs1314533
milestone52.0a1
Bug 1314533: [MSE] P6. Allow to detect error during preliminary container parsing. r=gerald MozReview-Commit-ID: KZ858ISWDmu
dom/media/mediasource/ContainerParser.cpp
dom/media/mediasource/ContainerParser.h
dom/media/mediasource/TrackBuffersManager.cpp
dom/media/mediasource/gtest/TestContainerParser.cpp
--- 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())));