Bug 1125776: Part3. Add support for partial init segment. r=cajbir
authorJean-Yves Avenard <jyavenard@mozilla.com>
Mon, 02 Feb 2015 11:41:43 +1100
changeset 244923 71dd88a5ad5f587aa76d6e70c818c4ab9d8c98c5
parent 244922 52dc02feb987adb957c606c85a02d91b38726131
child 244924 f51f8fcad8e6c11d22ac2b64ad1b524c48f2f176
push id7677
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 18:11:24 +0000
treeherdermozilla-aurora@f531d838c055 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscajbir
bugs1125776
milestone38.0a1
Bug 1125776: Part3. Add support for partial init segment. r=cajbir Now that we rely on appendBuffer to scan the init segment, it has become imperative to handle partial segments as it could otherwise lead to appendBuffer never firing update/updateend
dom/media/mediasource/ContainerParser.cpp
dom/media/mediasource/ContainerParser.h
dom/media/mediasource/SourceBufferResource.h
dom/media/mediasource/TrackBuffer.cpp
--- a/dom/media/mediasource/ContainerParser.cpp
+++ b/dom/media/mediasource/ContainerParser.cpp
@@ -27,18 +27,17 @@ extern PRLogModuleInfo* GetMediaSourceAP
 #define MSE_DEBUG(...)
 #define MSE_DEBUGV(...)
 #define MSE_API(...)
 #endif
 
 namespace mozilla {
 
 ContainerParser::ContainerParser()
-  : mInitData(new LargeDataBuffer())
-  , mHasInitData(false)
+  : mHasInitData(false)
 {
 }
 
 bool
 ContainerParser::IsInitSegmentPresent(LargeDataBuffer* aData)
 {
 MSE_DEBUG("ContainerParser(%p)::IsInitSegmentPresent aLength=%u [%x%x%x%x]",
             this, aData->Length(),
@@ -76,20 +75,25 @@ ContainerParser::TimestampsFuzzyEqual(in
 
 int64_t
 ContainerParser::GetRoundingError()
 {
   NS_WARNING("Using default ContainerParser::GetRoundingError implementation");
   return 0;
 }
 
+bool
+ContainerParser::HasCompleteInitData()
+{
+  return mHasInitData && !!mInitData->Length();
+}
+
 LargeDataBuffer*
 ContainerParser::InitData()
 {
-  MOZ_ASSERT(mHasInitData);
   return mInitData;
 }
 
 class WebMContainerParser : public ContainerParser {
 public:
   WebMContainerParser()
     : mParser(0), mOffset(0)
   {}
@@ -142,16 +146,17 @@ public:
   bool ParseStartAndEndTimestamps(LargeDataBuffer* aData,
                                   int64_t& aStart, int64_t& aEnd)
   {
     bool initSegment = IsInitSegmentPresent(aData);
     if (initSegment) {
       mOffset = 0;
       mParser = WebMBufferedParser(0);
       mOverlappedMapping.Clear();
+      mInitData = new LargeDataBuffer();
     }
 
     // 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");
@@ -268,37 +273,42 @@ public:
     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, 0, &mMonitor);
+      mInitData = new LargeDataBuffer();
     } else if (!mStream || !mParser) {
       return false;
     }
 
     mResource->AppendData(aData);
     nsTArray<MediaByteRange> byteRanges;
     MediaByteRange mbr =
       MediaByteRange(mParser->mOffset, mResource->GetLength());
     byteRanges.AppendElement(mbr);
     mParser->RebuildFragmentedIndex(byteRanges);
 
-    if (initSegment) {
+    if (initSegment || !HasCompleteInitData()) {
       const MediaByteRange& range = mParser->mInitRange;
-      MSE_DEBUG("MP4ContainerParser(%p)::ParseStartAndEndTimestamps: Stashed init of %u bytes.",
-                this, range.mEnd - range.mStart);
-
-      if (!mInitData->ReplaceElementsAt(0, mInitData->Length(),
-                                        aData->Elements() + range.mStart,
-                                        range.mEnd - range.mStart)) {
-        // Super unlikely OOM
-        return false;
+      uint32_t length = range.mEnd - range.mStart;
+      if (length) {
+        if (!mInitData->SetLength(length)) {
+          // Super unlikely OOM
+          return false;
+        }
+        char* buffer = reinterpret_cast<char*>(mInitData->Elements());
+        mResource->ReadFromCache(buffer, range.mStart, length);
+        MSE_DEBUG("MP4ContainerParser(%p)::ParseStartAndEndTimestamps: Stashed init of %u bytes.",
+                  this, length);
+      } else {
+        MSE_DEBUG("MP4ContainerParser(%p)::ParseStartAndEndTimestamps: Incomplete init found.");
       }
       mHasInitData = true;
     }
 
     mp4_demuxer::Interval<mp4_demuxer::Microseconds> compositionRange =
       mParser->GetCompositionRange(byteRanges);
     mResource->EvictData(mParser->mOffset, mParser->mOffset);
 
--- a/dom/media/mediasource/ContainerParser.h
+++ b/dom/media/mediasource/ContainerParser.h
@@ -43,16 +43,18 @@ public:
 
   LargeDataBuffer* InitData();
 
   bool HasInitData()
   {
     return mHasInitData;
   }
 
+  bool HasCompleteInitData();
+
   static ContainerParser* CreateForMIMEType(const nsACString& aType);
 
 protected:
   nsRefPtr<LargeDataBuffer> mInitData;
   bool mHasInitData;
 };
 
 } // namespace mozilla
--- a/dom/media/mediasource/SourceBufferResource.h
+++ b/dom/media/mediasource/SourceBufferResource.h
@@ -109,16 +109,21 @@ public:
                       MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
   {
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
   // Used by SourceBuffer.
   void AppendData(LargeDataBuffer* aData);
   void Ended();
+  bool IsEnded()
+  {
+    ReentrantMonitorAutoEnter mon(mMonitor);
+    return mEnded;
+  }
   // Remove data from resource if it holds more than the threshold
   // number of bytes. Returns amount evicted.
   uint32_t EvictData(uint64_t aPlaybackOffset, uint32_t aThreshold);
 
   // Remove data from resource before the given offset.
   void EvictBefore(uint64_t aOffset);
 
   // Remove all data from the resource
--- a/dom/media/mediasource/TrackBuffer.cpp
+++ b/dom/media/mediasource/TrackBuffer.cpp
@@ -102,19 +102,24 @@ public:
     return true;
   }
 
   size_t Length()
   {
     return mDecoders.Length();
   }
 
+  void AppendElement(SourceBufferDecoder* aDecoder)
+  {
+    mDecoders.AppendElement(aDecoder);
+  }
+
 private:
   TrackBuffer* mOwner;
-  nsAutoTArray<nsRefPtr<SourceBufferDecoder>,2> mDecoders;
+  nsAutoTArray<nsRefPtr<SourceBufferDecoder>,1> mDecoders;
 };
 
 nsRefPtr<ShutdownPromise>
 TrackBuffer::Shutdown()
 {
   mParentDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
   mShutdown = true;
 
@@ -150,76 +155,105 @@ TrackBuffer::ContinueShutdown()
 }
 
 nsRefPtr<TrackBuffer::InitializationPromise>
 TrackBuffer::AppendData(LargeDataBuffer* aData, int64_t aTimestampOffset)
 {
   MOZ_ASSERT(NS_IsMainThread());
   DecodersToInitialize decoders(this);
   nsRefPtr<InitializationPromise> p = mInitializationPromise.Ensure(__func__);
+  bool hadInitData = mParser->HasInitData();
+  bool hadCompleteInitData = mParser->HasCompleteInitData();
+  nsRefPtr<LargeDataBuffer> oldInit = mParser->InitData();
+  bool newInitData = mParser->IsInitSegmentPresent(aData);
 
   // TODO: Run more of the buffer append algorithm asynchronously.
-  if (mParser->IsInitSegmentPresent(aData)) {
+  if (newInitData) {
     MSE_DEBUG("TrackBuffer(%p)::AppendData: New initialization segment.", this);
-    if (!decoders.NewDecoder(aTimestampOffset)) {
-      mInitializationPromise.Reject(NS_ERROR_FAILURE, __func__);
-      return p;
-    }
-  } else if (!mParser->HasInitData()) {
+  } else if (!hadInitData) {
     MSE_DEBUG("TrackBuffer(%p)::AppendData: Non-init segment appended during initialization.", this);
     mInitializationPromise.Reject(NS_ERROR_FAILURE, __func__);
     return p;
   }
 
   int64_t start = 0, end = 0;
-  if (mParser->ParseStartAndEndTimestamps(aData, start, end)) {
-    start += aTimestampOffset;
-    end += aTimestampOffset;
-    if (mParser->IsMediaSegmentPresent(aData) &&
-        mLastEndTimestamp &&
-        (!mParser->TimestampsFuzzyEqual(start, mLastEndTimestamp.value()) ||
-         mLastTimestampOffset != aTimestampOffset ||
-         mDecoderPerSegment || (mCurrentDecoder && mCurrentDecoder->WasTrimmed()))) {
-      MSE_DEBUG("TrackBuffer(%p)::AppendData: Data last=[%lld, %lld] overlaps [%lld, %lld]",
-                this, mLastStartTimestamp, mLastEndTimestamp.value(), start, end);
+  bool gotMedia = mParser->ParseStartAndEndTimestamps(aData, start, end);
+  bool gotInit = mParser->HasCompleteInitData();
 
-      // This data is earlier in the timeline than data we have already
-      // processed, so we must create a new decoder to handle the decoding.
+  if (newInitData) {
+    if (!gotInit) {
+      // We need a new decoder, but we can't initialize it yet.
+      nsRefPtr<SourceBufferDecoder> decoder = NewDecoder(aTimestampOffset);
+      // The new decoder is stored in mDecoders/mCurrentDecoder, so we
+      // don't need to do anything with 'decoder'. It's only a placeholder.
+      if (!decoder) {
+        mInitializationPromise.Reject(NS_ERROR_FAILURE, __func__);
+        return p;
+      }
+    } else {
       if (!decoders.NewDecoder(aTimestampOffset)) {
         mInitializationPromise.Reject(NS_ERROR_FAILURE, __func__);
         return p;
       }
-      MSE_DEBUG("TrackBuffer(%p)::AppendData: Decoder marked as initialized.", this);
-      nsRefPtr<LargeDataBuffer> initData = mParser->InitData();
-      AppendDataToCurrentResource(initData, end - start);
+    }
+  } else if (!hadCompleteInitData && gotInit) {
+    MOZ_ASSERT(mCurrentDecoder);
+    // Queue pending decoder for initialization now that we have a full
+    // init segment.
+    decoders.AppendElement(mCurrentDecoder);
+  }
+
+  if (gotMedia) {
+    start += aTimestampOffset;
+    end += aTimestampOffset;
+    if (mLastEndTimestamp &&
+        (!mParser->TimestampsFuzzyEqual(start, mLastEndTimestamp.value()) ||
+         mLastTimestampOffset != aTimestampOffset ||
+         mDecoderPerSegment ||
+         (mCurrentDecoder && mCurrentDecoder->WasTrimmed()))) {
+      MSE_DEBUG("TrackBuffer(%p)::AppendData: Data last=[%lld, %lld] overlaps [%lld, %lld]",
+                this, mLastStartTimestamp, mLastEndTimestamp.value(), start, end);
+
+      if (!newInitData) {
+        // This data is earlier in the timeline than data we have already
+        // processed or not continuous, so we must create a new decoder
+        // to handle the decoding.
+        if (!decoders.NewDecoder(aTimestampOffset)) {
+          mInitializationPromise.Reject(NS_ERROR_FAILURE, __func__);
+          return p;
+        }
+        if (hadCompleteInitData) {
+          MSE_DEBUG("TrackBuffer(%p)::AppendData: Decoder marked as initialized.", this);
+          AppendDataToCurrentResource(oldInit, 0);
+        }
+      }
       mLastStartTimestamp = start;
     } else {
       MSE_DEBUG("TrackBuffer(%p)::AppendData: Segment last=[%lld, %lld] [%lld, %lld]",
                 this, mLastStartTimestamp, mLastEndTimestamp ? mLastEndTimestamp.value() : 0, start, end);
     }
     mLastEndTimestamp.reset();
     mLastEndTimestamp.emplace(end);
   }
 
   if (!AppendDataToCurrentResource(aData, end - start)) {
     mInitializationPromise.Reject(NS_ERROR_FAILURE, __func__);
     return p;
   }
 
   if (decoders.Length()) {
-    // TODO: the theory is that we should only ever have one decoder to
-    // initialize in common use. We can't properly handle the condition where
-    // the source buffer needs to wait on two decoders to initialize.
+    // We're going to have to wait for the decoder to initialize, the promise
+    // will be resolved once initialization completes.
     return p;
   }
   // Tell our reader that we have more data to ensure that playback starts if
   // required when data is appended.
   mParentDecoder->GetReader()->MaybeNotifyHaveData();
 
-  mInitializationPromise.Resolve(end - start > 0, __func__);
+  mInitializationPromise.Resolve(gotMedia, __func__);
   return p;
 }
 
 bool
 TrackBuffer::AppendDataToCurrentResource(LargeDataBuffer* aData, uint32_t aDuration)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!mCurrentDecoder) {
@@ -384,19 +418,19 @@ TrackBuffer::EvictBefore(double aTime)
 
 double
 TrackBuffer::Buffered(dom::TimeRanges* aRanges)
 {
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
 
   double highestEndTime = 0;
 
-  for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
+  for (uint32_t i = 0; i < mInitializedDecoders.Length(); ++i) {
     nsRefPtr<dom::TimeRanges> r = new dom::TimeRanges();
-    mDecoders[i]->GetBuffered(r);
+    mInitializedDecoders[i]->GetBuffered(r);
     if (r->Length() > 0) {
       highestEndTime = std::max(highestEndTime, r->GetEndTime());
       aRanges->Union(r, double(mParser->GetRoundingError()) / USECS_PER_S);
     }
   }
 
   return highestEndTime;
 }
@@ -469,20 +503,38 @@ TrackBuffer::InitializeDecoder(SourceBuf
   MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
   MediaDecoderReader* reader = aDecoder->GetReader();
   MSE_DEBUG("TrackBuffer(%p): Initializing subdecoder %p reader %p",
             this, aDecoder, reader);
 
   MediaInfo mi;
   nsAutoPtr<MetadataTags> tags; // TODO: Handle metadata.
   nsresult rv;
+
+  // HACK WARNING:
+  // We only reach this point once we know that we have a complete init segment.
+  // We don't want the demuxer to do a blocking read as no more data can be
+  // appended while this routine is running. Marking the SourceBufferResource
+  // as ended will cause any incomplete reads to abort.
+  // As this decoder hasn't been initialized yet, the resource isn't yet in use
+  // and so it is safe to do so.
+  bool wasEnded = aDecoder->GetResource()->IsEnded();
+  if (!wasEnded) {
+    aDecoder->GetResource()->Ended();
+  }
   {
     ReentrantMonitorAutoExit mon(mParentDecoder->GetReentrantMonitor());
     rv = reader->ReadMetadata(&mi, getter_Transfers(tags));
   }
+  if (!wasEnded) {
+    // Adding an empty buffer will reopen the SourceBufferResource
+    nsRefPtr<LargeDataBuffer> emptyBuffer = new LargeDataBuffer;
+    aDecoder->GetResource()->AppendData(emptyBuffer);
+  }
+  // HACK END.
 
   reader->SetIdle();
   if (mShutdown) {
     MSE_DEBUG("TrackBuffer(%p) was shut down while reading metadata. Aborting initialization.", this);
     mInitializationPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
     return;
   }
 
@@ -618,23 +670,25 @@ TrackBuffer::EndCurrentDecoder()
 
 void
 TrackBuffer::Detach()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mCurrentDecoder) {
     DiscardDecoder();
   }
+  // Cancel the promise should the current decoder hadn't be initialized yet.
+  mInitializationPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
 }
 
 bool
 TrackBuffer::HasInitSegment()
 {
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
-  return mParser->HasInitData();
+  return mParser->HasCompleteInitData();
 }
 
 bool
 TrackBuffer::IsReady()
 {
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
   MOZ_ASSERT((mInfo.HasAudio() || mInfo.HasVideo()) || mInitializedDecoders.IsEmpty());
   return mParser->HasInitData() && (mInfo.HasAudio() || mInfo.HasVideo());