Bug 881512 - Crude attempt at segment parser for WebM. r=cajbir
authorMatthew Gregan <kinetik@flim.org>
Tue, 22 Apr 2014 01:31:00 +1200
changeset 180741 a14f9763bf8f8a0ccd0320149514696c1c43197e
parent 180740 cf3e31d2594483d0e79fd33b592cd4383543fa2c
child 180742 5ea033583b20024305a1c85cbb642f12be593963
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewerscajbir
bugs881512
milestone31.0a1
Bug 881512 - Crude attempt at segment parser for WebM. r=cajbir
content/media/mediasource/MediaSource.cpp
content/media/mediasource/SourceBuffer.cpp
content/media/mediasource/SourceBuffer.h
--- a/content/media/mediasource/MediaSource.cpp
+++ b/content/media/mediasource/MediaSource.cpp
@@ -164,18 +164,18 @@ 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 = new SourceBuffer(this, NS_ConvertUTF16toUTF8(mimeType));
-  if (!sourceBuffer->Init()) {
+  nsRefPtr<SourceBuffer> sourceBuffer = SourceBuffer::Create(this, NS_ConvertUTF16toUTF8(mimeType));
+  if (!sourceBuffer) {
     aRv.Throw(NS_ERROR_FAILURE); // XXX need a better error here
     return nullptr;
   }
   mSourceBuffers->Append(sourceBuffer);
   MSE_DEBUG("%p AddSourceBuffer(Type=%s) -> %p", this,
             NS_ConvertUTF16toUTF8(mimeType).get(), sourceBuffer.get());
   return sourceBuffer.forget();
 }
--- a/content/media/mediasource/SourceBuffer.cpp
+++ b/content/media/mediasource/SourceBuffer.cpp
@@ -107,16 +107,48 @@ SubBufferDecoder::ConvertToByteOffset(do
     return -1;
   }
   int64_t length = GetResource()->GetLength();
   MOZ_ASSERT(length > 0);
   int64_t offset = (aTime / (double(mMediaDuration) / USECS_PER_S)) * length;
   return offset;
 }
 
+class ContainerParser {
+public:
+  virtual ~ContainerParser() {}
+
+  virtual bool IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength)
+  {
+    return false;
+  }
+};
+
+class WebMContainerParser : public ContainerParser {
+public:
+  bool IsInitSegmentPresent(const uint8_t* aData, uint32_t 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
+    // ...
+    // DocType == "webm"
+    // ...
+    // 0x18538067 // Segment (must be "unknown" size)
+    // 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;
+  }
+};
+
 namespace dom {
 
 void
 SourceBuffer::SetMode(SourceBufferAppendMode aMode, ErrorResult& aRv)
 {
   if (!IsAttached() || mUpdating) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
@@ -227,20 +259,20 @@ SourceBuffer::Abort(ErrorResult& aRv)
   if (mUpdating) {
     // TODO: Abort segment parser loop, buffer append, and stream append loop algorithms.
     AbortUpdating();
   }
   // TODO: Run reset parser algorithm.
   mAppendWindowStart = 0;
   mAppendWindowEnd = PositiveInfinity<double>();
 
-  MSE_DEBUG("%p Abort: Switching decoders.", this);
-  mCurrentDecoder->GetResource()->Ended();
-  if (!InitNewDecoder()) {
-    aRv.Throw(NS_ERROR_FAILURE); // XXX: Review error handling.
+  MSE_DEBUG("%p Abort: Discarding decoders.", this);
+  if (mCurrentDecoder) {
+    mCurrentDecoder->GetResource()->Ended();
+    mCurrentDecoder = nullptr;
   }
 }
 
 void
 SourceBuffer::Remove(double aStart, double aEnd, ErrorResult& aRv)
 {
   MSE_DEBUG("%p Remove(Start=%f End=%f)", this, aStart, aEnd);
   if (!IsAttached() || mUpdating ||
@@ -281,16 +313,29 @@ SourceBuffer::SourceBuffer(MediaSource* 
   , mType(aType)
   , mAppendWindowStart(0)
   , mAppendWindowEnd(PositiveInfinity<double>())
   , mTimestampOffset(0)
   , mAppendMode(SourceBufferAppendMode::Segments)
   , mUpdating(false)
 {
   MOZ_ASSERT(aMediaSource);
+  if (mType.EqualsIgnoreCase("video/webm") || mType.EqualsIgnoreCase("audio/webm")) {
+    mParser = new WebMContainerParser();
+  } else {
+    // XXX: Plug in parsers for MPEG4, etc. here.
+    mParser = new ContainerParser();
+  }
+}
+
+already_AddRefed<SourceBuffer>
+SourceBuffer::Create(MediaSource* aMediaSource, const nsACString& aType)
+{
+  nsRefPtr<SourceBuffer> sourceBuffer = new SourceBuffer(aMediaSource, aType);
+  return sourceBuffer.forget();
 }
 
 SourceBuffer::~SourceBuffer()
 {
   for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
     mDecoders[i]->GetResource()->Ended();
   }
 }
@@ -373,21 +418,31 @@ SourceBuffer::AppendData(const uint8_t* 
     return;
   }
   if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
     mMediaSource->SetReadyState(MediaSourceReadyState::Open);
   }
   // TODO: Run coded frame eviction algorithm.
   // TODO: Test buffer full flag.
   StartUpdating();
+  // TODO: Run buffer append algorithm asynchronously (would call StopUpdating()).
+  if (mParser->IsInitSegmentPresent(aData, aLength) || !mCurrentDecoder) {
+    MSE_DEBUG("%p AppendBuffer: New initialization segment, switching decoders.", this);
+    if (mCurrentDecoder) {
+      mCurrentDecoder->GetResource()->Ended();
+    }
+    if (!InitNewDecoder()) {
+      aRv.Throw(NS_ERROR_FAILURE); // XXX: Review error handling.
+      return;
+    }
+  }
   // XXX: For future reference: NDA call must run on the main thread.
   mCurrentDecoder->NotifyDataArrived(reinterpret_cast<const char*>(aData),
                                      aLength,
                                      mCurrentDecoder->GetResource()->GetLength());
-  // TODO: Run buffer append algorithm asynchronously (would call StopUpdating()).
   mCurrentDecoder->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 SourceBuffer's it knows
   // about.
   const int evict_threshold = 1000000;
--- a/content/media/mediasource/SourceBuffer.h
+++ b/content/media/mediasource/SourceBuffer.h
@@ -24,16 +24,17 @@
 #include "nsStringGlue.h"
 #include "nscore.h"
 
 class JSObject;
 struct JSContext;
 
 namespace mozilla {
 
+class ContainerParser;
 class ErrorResult;
 class SourceBufferResource;
 class SubBufferDecoder;
 template <typename T> class AsyncEventRunner;
 
 namespace dom {
 
 class TimeRanges;
@@ -83,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)
 
-  explicit SourceBuffer(MediaSource* aMediaSource, const nsACString& aType);
+  static already_AddRefed<SourceBuffer> Create(MediaSource* aMediaSource, const nsACString& aType);
   ~SourceBuffer();
 
   MediaSource* GetParentObject() const;
 
   JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   // Notify the SourceBuffer that it has been detached from the
   // MediaSource's sourceBuffer list.
@@ -103,24 +104,19 @@ public:
     return mMediaSource != nullptr;
   }
 
   void Ended();
 
   // Evict data in the source buffer in the given time range.
   void Evict(double aStart, double aEnd);
 
-  // Initialize this SourceBuffer.  Must be called before use.
-  bool Init()
-  {
-    MOZ_ASSERT(!mCurrentDecoder);
-    return InitNewDecoder();
-  }
+private:
+  SourceBuffer(MediaSource* aMediaSource, const nsACString& aType);
 
-private:
   friend class AsyncEventRunner<SourceBuffer>;
   void DispatchSimpleEvent(const char* aName);
   void QueueAsyncSimpleEvent(const char* aName);
 
   // Create a new decoder for mType, add it to mDecoders and update mCurrentDecoder.
   bool InitNewDecoder();
 
   // Update mUpdating and fire the appropriate events.
@@ -134,16 +130,18 @@ private:
   // Provide the minimum start time and maximum end time that is available
   // in the data buffered by this SourceBuffer.
   void GetBufferedStartEndTime(double* aStart, double* aEnd);
 
   nsRefPtr<MediaSource> mMediaSource;
 
   const nsAutoCString mType;
 
+  nsAutoPtr<ContainerParser> mParser;
+
   // XXX: We only want to keep the current decoder alive, but need a way to
   // query @buffered for everything this SourceBuffer is responsible for.
   nsTArray<nsRefPtr<SubBufferDecoder>> mDecoders;
   nsRefPtr<SubBufferDecoder> mCurrentDecoder;
 
   double mAppendWindowStart;
   double mAppendWindowEnd;