Bug 1125776: Part4. Add support for partial WebM init segment. r=kinetik a=lmandel
authorJean-Yves Avenard <jyavenard@mozilla.com>
Wed, 04 Feb 2015 20:20:15 +1100
changeset 249833 6e0e5679f9efc12cd5435d728cc4366b6c504eb8
parent 249832 c15a499a7eaa0c3145eb11e56b6ccf2e803b7101
child 249834 06832bc0471f4689aafa77751ad2b49a0edc6d8e
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik, lmandel
bugs1125776
milestone37.0a2
Bug 1125776: Part4. Add support for partial WebM init segment. r=kinetik a=lmandel
dom/media/mediasource/ContainerParser.cpp
dom/media/mediasource/ContainerParser.h
dom/media/webm/WebMBufferedParser.cpp
dom/media/webm/WebMBufferedParser.h
--- a/dom/media/mediasource/ContainerParser.cpp
+++ b/dom/media/mediasource/ContainerParser.cpp
@@ -104,17 +104,19 @@ public:
     ContainerParser::IsInitSegmentPresent(aData);
     // XXX: This is overly primitive, needs to collect data as it's appended
     // to the SB and handle, rather than assuming everything is present in a
     // single aData segment.
     // 0x1a45dfa3 // EBML
     // ...
     // DocType == "webm"
     // ...
-    // 0x18538067 // Segment (must be "unknown" size)
+    // 0x18538067 // Segment (must be "unknown" size or contain a value large
+                  // enough to include the Segment Information and Tracks
+                  // elements that follow)
     // 0x1549a966 // -> Segment Info
     // 0x1654ae6b // -> One or more Tracks
     if (aData->Length() >= 4 &&
         (*aData)[0] == 0x1a && (*aData)[1] == 0x45 && (*aData)[2] == 0xdf &&
         (*aData)[3] == 0xa3) {
       return true;
     }
     return false;
@@ -145,41 +147,47 @@ public:
                                   int64_t& aStart, int64_t& aEnd)
   {
     bool initSegment = IsInitSegmentPresent(aData);
     if (initSegment) {
       mOffset = 0;
       mParser = WebMBufferedParser(0);
       mOverlappedMapping.Clear();
       mInitData = new LargeDataBuffer();
+      mResource = new SourceBufferResource(NS_LITERAL_CSTRING("video/webm"));
     }
 
     // XXX if it only adds new mappings, overlapped but not available
     // (e.g. overlap < 0) frames are "lost" from the reported mappings here.
     nsTArray<WebMTimeDataOffset> mapping;
     mapping.AppendElements(mOverlappedMapping);
     mOverlappedMapping.Clear();
     ReentrantMonitor dummy("dummy");
     mParser.Append(aData->Elements(), aData->Length(), mapping, dummy);
+    if (mResource) {
+      mResource->AppendData(aData);
+    }
 
     // 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) {
-      uint32_t length = aData->Length();
-      if (!mapping.IsEmpty()) {
-        length = mapping[0].mSyncOffset;
-        MOZ_ASSERT(length <= aData->Length());
-      }
-      MSE_DEBUG("WebMContainerParser(%p)::ParseStartAndEndTimestamps: Stashed init of %u bytes.",
-                this, length);
-      if (!mInitData->ReplaceElementsAt(0, mInitData->Length(),
-                                        aData->Elements(), length)) {
-        // Unlikely OOM
-        return false;
+    if (initSegment || !HasCompleteInitData()) {
+      if (mParser.mInitEndOffset > 0) {
+        MOZ_ASSERT(mParser.mInitEndOffset <= mResource->GetLength());
+        if (!mInitData->SetLength(mParser.mInitEndOffset)) {
+          // Super unlikely OOM
+          return false;
+        }
+        char* buffer = reinterpret_cast<char*>(mInitData->Elements());
+        mResource->ReadFromCache(buffer, 0, mParser.mInitEndOffset);
+        MSE_DEBUG("WebMContainerParser(%p)::ParseStartAndEndTimestamps: Stashed init of %u bytes.",
+                  this, mParser.mInitEndOffset);
+        mResource = nullptr;
+      } else {
+        MSE_DEBUG("WebMContainerParser(%p)::ParseStartAndEndTimestamps: Incomplete init found.");
       }
       mHasInitData = true;
     }
     mOffset += aData->Length();
 
     if (mapping.IsEmpty()) {
       return false;
     }
@@ -324,17 +332,16 @@ public:
   int64_t GetRoundingError()
   {
     return 20000;
   }
 
 private:
   nsRefPtr<MP4Stream> mStream;
   nsAutoPtr<mp4_demuxer::MoofParser> mParser;
-  nsRefPtr<SourceBufferResource> mResource;
   Monitor mMonitor;
 };
 
 /*static*/ ContainerParser*
 ContainerParser::CreateForMIMEType(const nsACString& aType)
 {
   if (aType.LowerCaseEqualsLiteral("video/webm") || aType.LowerCaseEqualsLiteral("audio/webm")) {
     return new WebMContainerParser();
--- a/dom/media/mediasource/ContainerParser.h
+++ b/dom/media/mediasource/ContainerParser.h
@@ -7,16 +7,17 @@
 #ifndef MOZILLA_CONTAINERPARSER_H_
 #define MOZILLA_CONTAINERPARSER_H_
 
 #include "nsRefPtr.h"
 
 namespace mozilla {
 
 class LargeDataBuffer;
+class SourceBufferResource;
 
 class ContainerParser {
 public:
   ContainerParser();
   virtual ~ContainerParser() {}
 
   // Return true if aData starts with an initialization segment.
   // The base implementation exists only for debug logging and is expected
@@ -49,13 +50,14 @@ public:
   }
 
   bool HasCompleteInitData();
 
   static ContainerParser* CreateForMIMEType(const nsACString& aType);
 
 protected:
   nsRefPtr<LargeDataBuffer> mInitData;
+  nsRefPtr<SourceBufferResource> mResource;
   bool mHasInitData;
 };
 
 } // namespace mozilla
 #endif /* MOZILLA_CONTAINERPARSER_H_ */
--- a/dom/media/webm/WebMBufferedParser.cpp
+++ b/dom/media/webm/WebMBufferedParser.cpp
@@ -32,16 +32,17 @@ VIntLength(unsigned char aFirstByte, uin
 }
 
 void WebMBufferedParser::Append(const unsigned char* aBuffer, uint32_t aLength,
                                 nsTArray<WebMTimeDataOffset>& aMapping,
                                 ReentrantMonitor& aReentrantMonitor)
 {
   static const uint32_t SEGMENT_ID = 0x18538067;
   static const uint32_t SEGINFO_ID = 0x1549a966;
+  static const uint32_t TRACKS_ID = 0x1654AE6B;
   static const uint32_t CLUSTER_ID = 0x1f43b675;
   static const uint32_t TIMECODESCALE_ID = 0x2ad7b1;
   static const unsigned char TIMECODE_ID = 0xe7;
   static const unsigned char BLOCK_ID = 0xa1;
   static const unsigned char SIMPLEBLOCK_ID = 0xa3;
   static const uint32_t BLOCK_TIMECODE_LENGTH = 2;
 
   static const unsigned char CLUSTER_SYNC_ID[] = { 0x1f, 0x43, 0xb6, 0x75 };
@@ -110,16 +111,20 @@ void WebMBufferedParser::Append(const un
         mBlockSize = mElement.mSize.mValue;
         mBlockTimecode = 0;
         mBlockTimecodeLength = BLOCK_TIMECODE_LENGTH;
         mBlockOffset = mCurrentOffset + (p - aBuffer) -
                        (mElement.mID.mLength + mElement.mSize.mLength);
         mState = READ_VINT;
         mNextState = READ_BLOCK_TIMECODE;
         break;
+      case TRACKS_ID:
+        mSkipBytes = mElement.mSize.mValue;
+        mState = CHECK_INIT_FOUND;
+        break;
       default:
         mSkipBytes = mElement.mSize.mValue;
         mState = SKIP_DATA;
         mNextState = READ_ELEMENT_ID;
         break;
       }
       break;
     case READ_VINT: {
@@ -187,16 +192,30 @@ void WebMBufferedParser::Append(const un
         uint32_t left = aLength - (p - aBuffer);
         left = std::min(left, mSkipBytes);
         p += left;
         mSkipBytes -= left;
       } else {
         mState = mNextState;
       }
       break;
+    case CHECK_INIT_FOUND:
+      if (mSkipBytes) {
+        uint32_t left = aLength - (p - aBuffer);
+        left = std::min(left, mSkipBytes);
+        p += left;
+        mSkipBytes -= left;
+      }
+      if (!mSkipBytes) {
+        if (mInitEndOffset < 0) {
+          mInitEndOffset = mCurrentOffset + (p - aBuffer);
+        }
+        mState = READ_ELEMENT_ID;
+      }
+      break;
     }
   }
 
   NS_ASSERTION(p == aBuffer + aLength, "Must have parsed to end of data.");
   mCurrentOffset += aLength;
 }
 
 // SyncOffsetComparator and TimeComparator are slightly confusing, in that
@@ -324,9 +343,8 @@ void WebMBufferedState::NotifyDataArrive
       mRangeParsers.RemoveElementAt(i);
     } else {
       i += 1;
     }
   }
 }
 
 } // namespace mozilla
-
--- a/dom/media/webm/WebMBufferedParser.h
+++ b/dom/media/webm/WebMBufferedParser.h
@@ -45,19 +45,19 @@ struct WebMTimeDataOffset
 // consumes blocks.  A new parser is created for each distinct range of data
 // received and begins parsing from the first WebM cluster within that
 // range.  Old parsers are destroyed when their range merges with a later
 // parser or an already parsed range.  The parser may start at any position
 // within the stream.
 struct WebMBufferedParser
 {
   explicit WebMBufferedParser(int64_t aOffset)
-    : mStartOffset(aOffset), mCurrentOffset(aOffset), mState(READ_ELEMENT_ID),
-      mVIntRaw(false), mClusterSyncPos(0), mTimecodeScale(1000000),
-      mGotTimecodeScale(false)
+    : mStartOffset(aOffset), mCurrentOffset(aOffset), mInitEndOffset(-1),
+      mState(READ_ELEMENT_ID), mVIntRaw(false), mClusterSyncPos(0),
+      mTimecodeScale(1000000), mGotTimecodeScale(false)
   {
     if (mStartOffset != 0) {
       mState = FIND_CLUSTER_SYNC;
     }
   }
 
   uint32_t GetTimecodeScale() {
     MOZ_ASSERT(mGotTimecodeScale);
@@ -90,16 +90,20 @@ struct WebMBufferedParser
   // adjacent parsers, in which case the later parser adopts the earlier
   // parser's mStartOffset.
   int64_t mStartOffset;
 
   // Current offset with the stream.  Updated in chunks as Append() consumes
   // data.
   int64_t mCurrentOffset;
 
+  // Tracks element's end offset. This indicates the end of the init segment.
+  // Will only be set if a Segment Information has been found.
+  int64_t mInitEndOffset;
+
 private:
   enum State {
     // Parser start state.  Expects to begin at a valid EBML element.  Move
     // to READ_VINT with mVIntRaw true, then return to READ_ELEMENT_SIZE.
     READ_ELEMENT_ID,
 
     // Store element ID read into mVInt into mElement.mID.  Move to
     // READ_VINT with mVIntRaw false, then return to PARSE_ELEMENT.
@@ -135,16 +139,21 @@ private:
     // mBlockTimecodeLength holds the remaining length of the block timecode
     // left to read.  Read each byte of the timecode into mBlockTimecode.
     // Once complete, calculate the scaled timecode from the cluster
     // timecode, block timecode, and timecode scale, and insert a
     // WebMTimeDataOffset entry into aMapping if one is not already present
     // for this offset.
     READ_BLOCK_TIMECODE,
 
+    // Will skip the current tracks element and set mInitEndOffset if an init
+    // segment has been found.
+    // Currently, only assumes it's the end of the tracks element.
+    CHECK_INIT_FOUND,
+
     // Skip mSkipBytes of data before resuming parse at mNextState.
     SKIP_DATA,
   };
 
   // Current state machine action.
   State mState;
 
   // Next state machine action.  SKIP_DATA and READ_VINT_REST advance to