Bug 1044498 - Handle overlapped SourceBuffer appends by spinning up a new decoder. Only implemented for WebM so far. r=cajbir
authorMatthew Gregan <kinetik@flim.org>
Tue, 19 Aug 2014 17:13:27 +1200
changeset 223928 03978f8ca2b52afe82775413bdb2169cb360fd6b
parent 223927 dfb1559c2742d681c5a2224b9539792f13384658
child 223929 676e7fc0984e5e7748a8466ef0c3260ba7c2932a
push id3979
push userraliiev@mozilla.com
push dateMon, 13 Oct 2014 16:35:44 +0000
treeherdermozilla-beta@30f2cc610691 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscajbir
bugs1044498
milestone34.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1044498 - Handle overlapped SourceBuffer appends by spinning up a new decoder. Only implemented for WebM so far. r=cajbir
content/media/mediasource/MediaSource.cpp
content/media/mediasource/SourceBuffer.cpp
content/media/mediasource/SourceBuffer.h
content/media/webm/WebMReader.cpp
content/media/webm/moz.build
--- a/content/media/mediasource/MediaSource.cpp
+++ b/content/media/mediasource/MediaSource.cpp
@@ -205,17 +205,17 @@ MediaSource::AddSourceBuffer(const nsASt
   }
   nsContentTypeParser parser(aType);
   nsAutoString mimeType;
   rv = parser.GetType(mimeType);
   if (NS_FAILED(rv)) {
     aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return nullptr;
   }
-  nsRefPtr<SourceBuffer> sourceBuffer = SourceBuffer::Create(this, NS_ConvertUTF16toUTF8(mimeType));
+  nsRefPtr<SourceBuffer> sourceBuffer = new SourceBuffer(this, NS_ConvertUTF16toUTF8(mimeType));
   if (!sourceBuffer) {
     aRv.Throw(NS_ERROR_FAILURE); // XXX need a better error here
     return nullptr;
   }
   mSourceBuffers->Append(sourceBuffer);
   mActiveSourceBuffers->Append(sourceBuffer);
   MSE_DEBUG("MediaSource(%p)::AddSourceBuffer() sourceBuffer=%p", this, sourceBuffer.get());
   return sourceBuffer.forget();
--- a/content/media/mediasource/SourceBuffer.cpp
+++ b/content/media/mediasource/SourceBuffer.cpp
@@ -21,16 +21,18 @@
 #include "nsError.h"
 #include "nsIEventTarget.h"
 #include "nsIRunnable.h"
 #include "nsThreadUtils.h"
 #include "prlog.h"
 #include "SourceBufferDecoder.h"
 #include "mozilla/Preferences.h"
 
+#include "WebMBufferedParser.h"
+
 struct JSContext;
 class JSObject;
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* GetMediaSourceLog();
 extern PRLogModuleInfo* GetMediaSourceAPILog();
 
 #define MSE_DEBUG(...) PR_LOG(GetMediaSourceLog(), PR_LOG_DEBUG, (__VA_ARGS__))
@@ -54,21 +56,31 @@ public:
               aLength,
               aLength > 0 ? aData[0] : 0,
               aLength > 1 ? aData[1] : 0,
               aLength > 2 ? aData[2] : 0,
               aLength > 3 ? aData[3] : 0);
     return false;
   }
 
+  virtual bool ParseStartAndEndTimestamps(const uint8_t* aData, uint32_t aLength,
+                                          double& aStart, double& aEnd)
+  {
+    return false;
+  }
+
   static ContainerParser* CreateForMIMEType(const nsACString& aType);
 };
 
 class WebMContainerParser : public ContainerParser {
 public:
+  WebMContainerParser()
+    : mTimecodeScale(0)
+  {}
+
   bool IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength)
   {
     ContainerParser::IsInitSegmentPresent(aData, aLength);
     // 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
     // ...
@@ -78,16 +90,47 @@ public:
     // 0x1549a966 // -> Segment Info
     // 0x1654ae6b // -> One or more Tracks
     if (aLength >= 4 &&
         aData[0] == 0x1a && aData[1] == 0x45 && aData[2] == 0xdf && aData[3] == 0xa3) {
       return true;
     }
     return false;
   }
+
+  virtual bool ParseStartAndEndTimestamps(const uint8_t* aData, uint32_t aLength,
+                                          double& aStart, double& aEnd)
+  {
+    // 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.
+
+    WebMBufferedParser parser(0);
+    if (mTimecodeScale != 0) {
+      parser.SetTimecodeScale(mTimecodeScale);
+    }
+
+    nsTArray<WebMTimeDataOffset> mapping;
+    ReentrantMonitor dummy("dummy");
+    parser.Append(aData, aLength, mapping, dummy);
+
+    mTimecodeScale = parser.GetTimecodeScale();
+
+    if (mapping.IsEmpty()) {
+      return false;
+    }
+
+    static const double NS_PER_S = 1e9;
+    aStart = mapping[0].mTimecode / NS_PER_S;
+    aEnd = mapping.LastElement().mTimecode / NS_PER_S;
+    return true;
+  }
+
+private:
+  uint32_t mTimecodeScale;
 };
 
 class MP4ContainerParser : public ContainerParser {
 public:
   MP4ContainerParser() : mTimescale(0) {}
 
   bool IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength)
   {
@@ -347,37 +390,31 @@ SourceBuffer::Ended()
     mDecoder->GetResource()->Ended();
   }
 }
 
 SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType)
   : DOMEventTargetHelper(aMediaSource->GetParentObject())
   , mMediaSource(aMediaSource)
   , mType(aType)
+  , mLastParsedTimestamp(UnspecifiedNaN<double>())
   , mAppendWindowStart(0)
   , mAppendWindowEnd(PositiveInfinity<double>())
   , mTimestampOffset(0)
   , mAppendMode(SourceBufferAppendMode::Segments)
   , mUpdating(false)
   , mDecoderInitialized(false)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aMediaSource);
   mParser = ContainerParser::CreateForMIMEType(aType);
   MSE_DEBUG("SourceBuffer(%p)::SourceBuffer: Creating initial decoder.", this);
   InitNewDecoder();
 }
 
-already_AddRefed<SourceBuffer>
-SourceBuffer::Create(MediaSource* aMediaSource, const nsACString& aType)
-{
-  nsRefPtr<SourceBuffer> sourceBuffer = new SourceBuffer(aMediaSource, aType);
-  return sourceBuffer.forget();
-}
-
 SourceBuffer::~SourceBuffer()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MSE_DEBUG("SourceBuffer(%p)::~SourceBuffer", this);
   DiscardDecoder();
 }
 
 MediaSource*
@@ -499,33 +536,53 @@ SourceBuffer::AppendData(const uint8_t* 
   } else if (!mDecoderInitialized) {
     MSE_DEBUG("SourceBuffer(%p)::AppendData: Non-init segment appended during initialization.");
     Optional<MediaSourceEndOfStreamError> decodeError(MediaSourceEndOfStreamError::Decode);
     ErrorResult dummy;
     mMediaSource->EndOfStream(decodeError, dummy);
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
+  double start, end;
+  if (mParser->ParseStartAndEndTimestamps(aData, aLength, start, end)) {
+    if (start <= mLastParsedTimestamp) {
+      // This data is earlier in the timeline than data we have already
+      // processed, so we must create a new decoder to handle the decoding.
+      DiscardDecoder();
+
+      // If we've got a decoder here, it's not initialized, so we can use it
+      // rather than creating a new one.
+      if (!InitNewDecoder()) {
+        aRv.Throw(NS_ERROR_FAILURE); // XXX: Review error handling.
+        return;
+      }
+      MSE_DEBUG("SourceBuffer(%p)::AppendData: Decoder marked as initialized (%f, %f).",
+                this, start, end);
+      mDecoderInitialized = true;
+    }
+    mLastParsedTimestamp = end;
+    MSE_DEBUG("SourceBuffer(%p)::AppendData: Segment start=%f end=%f", this, start, end);
+  }
   // XXX: For future reference: NDA call must run on the main thread.
   mDecoder->NotifyDataArrived(reinterpret_cast<const char*>(aData),
                               aLength,
                               mDecoder->GetResource()->GetLength());
   mDecoder->GetResource()->AppendData(aData, aLength);
 
   // Eviction uses a byte threshold. If the buffer is greater than the
   // number of bytes then data is evicted. The time range for this
   // eviction is reported back to the media source. It will then
   // evict data before that range across all SourceBuffers it knows
   // about.
   // TODO: Make the eviction threshold smaller for audio-only streams.
   // TODO: Drive evictions off memory pressure notifications.
   const uint32_t evict_threshold = 75 * (1 << 20);
   bool evicted = mDecoder->GetResource()->EvictData(evict_threshold);
   if (evicted) {
-    MSE_DEBUG("SourceBuffer(%p)::AppendBuffer Evict; current buffered start=%f",
+    MSE_DEBUG("SourceBuffer(%p)::AppendData Evict; current buffered start=%f",
               this, GetBufferedStart());
 
     // We notify that we've evicted from the time range 0 through to
     // the current start point.
     mMediaSource->NotifyEvicted(0.0, GetBufferedStart());
   }
   StopUpdating();
 
--- a/content/media/mediasource/SourceBuffer.h
+++ b/content/media/mediasource/SourceBuffer.h
@@ -84,17 +84,17 @@ public:
   void Abort(ErrorResult& aRv);
 
   void Remove(double aStart, double aEnd, ErrorResult& aRv);
   /** End WebIDL Methods. */
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SourceBuffer, DOMEventTargetHelper)
 
-  static already_AddRefed<SourceBuffer> Create(MediaSource* aMediaSource, const nsACString& aType);
+  SourceBuffer(MediaSource* aMediaSource, const nsACString& aType);
 
   MediaSource* GetParentObject() const;
 
   JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   // Notify the SourceBuffer that it has been detached from the
   // MediaSource's sourceBuffer list.
   void Detach();
@@ -109,18 +109,16 @@ public:
   void Evict(double aStart, double aEnd);
 
   double GetBufferedStart();
   double GetBufferedEnd();
 
 private:
   ~SourceBuffer();
 
-  SourceBuffer(MediaSource* aMediaSource, const nsACString& aType);
-
   friend class AsyncEventRunner<SourceBuffer>;
   void DispatchSimpleEvent(const char* aName);
   void QueueAsyncSimpleEvent(const char* aName);
 
   // Create a new decoder for mType, and store the result in mDecoder.
   // Returns true if mDecoder was set.
   bool InitNewDecoder();
 
@@ -136,16 +134,18 @@ private:
   void AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv);
 
   nsRefPtr<MediaSource> mMediaSource;
 
   const nsCString mType;
 
   nsAutoPtr<ContainerParser> mParser;
 
+  double mLastParsedTimestamp;
+
   nsRefPtr<SourceBufferDecoder> mDecoder;
   nsTArray<nsRefPtr<SourceBufferDecoder>> mDecoders;
 
   double mAppendWindowStart;
   double mAppendWindowEnd;
 
   double mTimestampOffset;
 
--- a/content/media/webm/WebMReader.cpp
+++ b/content/media/webm/WebMReader.cpp
@@ -1021,62 +1021,55 @@ nsresult WebMReader::Seek(int64_t aTarge
       return NS_ERROR_FAILURE;
     }
   }
   return NS_OK;
 }
 
 nsresult WebMReader::GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime)
 {
+  if (aBuffered->Length() != 0) {
+    return NS_ERROR_FAILURE;
+  }
+
   MediaResource* resource = mDecoder->GetResource();
 
-  uint64_t timecodeScale;
-  if (!mContext || nestegg_tstamp_scale(mContext, &timecodeScale) == -1) {
-    return NS_OK;
-  }
-
   // Special case completely cached files.  This also handles local files.
-  bool isFullyCached = resource->IsDataCachedToEndOfResource(0);
-  if (isFullyCached) {
+  if (mContext && resource->IsDataCachedToEndOfResource(0)) {
     uint64_t duration = 0;
     if (nestegg_duration(mContext, &duration) == 0) {
       aBuffered->Add(0, duration / NS_PER_S);
+      return NS_OK;
     }
   }
 
-  uint32_t bufferedLength = 0;
-  aBuffered->GetLength(&bufferedLength);
-
   // Either we the file is not fully cached, or we couldn't find a duration in
   // the WebM bitstream.
-  if (!isFullyCached || !bufferedLength) {
-    MediaResource* resource = mDecoder->GetResource();
-    nsTArray<MediaByteRange> ranges;
-    nsresult res = resource->GetCachedRanges(ranges);
-    NS_ENSURE_SUCCESS(res, res);
+  nsTArray<MediaByteRange> ranges;
+  nsresult res = resource->GetCachedRanges(ranges);
+  NS_ENSURE_SUCCESS(res, res);
 
-    for (uint32_t index = 0; index < ranges.Length(); index++) {
-      uint64_t start, end;
-      bool rv = mBufferedState->CalculateBufferedForRange(ranges[index].mStart,
-                                                          ranges[index].mEnd,
-                                                          &start, &end);
-      if (rv) {
-        double startTime = start * timecodeScale / NS_PER_S - aStartTime;
-        double endTime = end * timecodeScale / NS_PER_S - aStartTime;
-        // If this range extends to the end of the file, the true end time
-        // is the file's duration.
-        if (resource->IsDataCachedToEndOfResource(ranges[index].mStart)) {
-          uint64_t duration = 0;
-          if (nestegg_duration(mContext, &duration) == 0) {
-            endTime = duration / NS_PER_S;
-          }
+  for (uint32_t index = 0; index < ranges.Length(); index++) {
+    uint64_t start, end;
+    bool rv = mBufferedState->CalculateBufferedForRange(ranges[index].mStart,
+                                                        ranges[index].mEnd,
+                                                        &start, &end);
+    if (rv) {
+      double startTime = start / NS_PER_S - aStartTime;
+      double endTime = end / NS_PER_S - aStartTime;
+      // If this range extends to the end of the file, the true end time
+      // is the file's duration.
+      if (mContext && resource->IsDataCachedToEndOfResource(ranges[index].mStart)) {
+        uint64_t duration = 0;
+        if (nestegg_duration(mContext, &duration) == 0) {
+          endTime = duration / NS_PER_S;
         }
+      }
 
-        aBuffered->Add(startTime, endTime);
-      }
+      aBuffered->Add(startTime, endTime);
     }
   }
 
   return NS_OK;
 }
 
 void WebMReader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset)
 {
--- a/content/media/webm/moz.build
+++ b/content/media/webm/moz.build
@@ -1,15 +1,16 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXPORTS += [
+    'WebMBufferedParser.h',
     'WebMDecoder.h',
     'WebMReader.h',
 ]
 
 UNIFIED_SOURCES += [
     'WebMBufferedParser.cpp',
     'WebMDecoder.cpp',
     'WebMReader.cpp',