Bug 1056187 - Add playout delay to RtspTrackBuffer. r=bechen
authorEthan Tseng <ettseng@mozilla.com>
Tue, 07 Oct 2014 16:44:58 +0800
changeset 209576 c5a41dac572942ed2c1ed14d218529852477c305
parent 209575 759c780b92a24eb1fb997e9ed58cb5447e59482d
child 209577 f589902d878343e52b2acfed6b07128aba4138be
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersbechen
bugs1056187
milestone35.0a1
Bug 1056187 - Add playout delay to RtspTrackBuffer. r=bechen
content/media/RtspMediaResource.cpp
content/media/RtspMediaResource.h
content/media/omx/RtspOmxReader.cpp
content/media/omx/RtspOmxReader.h
--- a/content/media/RtspMediaResource.cpp
+++ b/content/media/RtspMediaResource.cpp
@@ -48,29 +48,38 @@ namespace mozilla {
 #define BUFFER_SLOT_EMPTY 0
 
 struct BufferSlotData {
   int32_t mLength;
   uint64_t mTime;
   int32_t  mFrameType;
 };
 
+// This constant is used to determine if the buffer usage is over a threshold.
+const float kBufferThresholdPerc = 0.8f;
+// The default value of playout delay duration.
+const uint32_t kPlayoutDelayMs = 3000;
+
+//-----------------------------------------------------------------------------
+// RtspTrackBuffer
+//-----------------------------------------------------------------------------
 class RtspTrackBuffer
 {
 public:
   RtspTrackBuffer(const char *aMonitor, int32_t aTrackIdx, uint32_t aSlotSize)
   : mMonitor(aMonitor)
   , mSlotSize(aSlotSize)
   , mTotalBufferSize(BUFFER_SLOT_NUM * mSlotSize)
   , mFrameType(0)
-  , mIsStarted(false) {
+  , mIsStarted(false)
+  , mDuringPlayoutDelay(false)
+  , mPlayoutDelayMs(kPlayoutDelayMs)
+  , mPlayoutDelayTimer(nullptr) {
     MOZ_COUNT_CTOR(RtspTrackBuffer);
-#ifdef PR_LOGGING
     mTrackIdx = aTrackIdx;
-#endif
     MOZ_ASSERT(mSlotSize < UINT32_MAX / BUFFER_SLOT_NUM);
     mRingBuffer = new uint8_t[mTotalBufferSize];
     Reset();
   };
   ~RtspTrackBuffer() {
     MOZ_COUNT_DTOR(RtspTrackBuffer);
     mRingBuffer = nullptr;
   };
@@ -88,16 +97,17 @@ public:
   void Start() {
     MonitorAutoLock monitor(mMonitor);
     mIsStarted = true;
     mFrameType = 0;
   }
   void Stop() {
     MonitorAutoLock monitor(mMonitor);
     mIsStarted = false;
+    StopPlayoutDelay();
   }
 
   // Read the data from mRingBuffer[mConsumerIdx*mSlotSize] into aToBuffer.
   // If the aToBufferSize is smaller than mBufferSlotDataLength[mConsumerIdx],
   // early return and set the aFrameSize to notify the reader the aToBuffer
   // doesn't have enough space. The reader must realloc the aToBuffer if it
   // wishes to read the data.
   nsresult ReadBuffer(uint8_t* aToBuffer, uint32_t aToBufferSize,
@@ -113,29 +123,55 @@ public:
   // We should call SetFrameType first then reset().
   // If we call reset() first, the queue may still has some "garbage" frame
   // from another thread's |OnMediaDataAvailable| before |SetFrameType|.
   void ResetWithFrameType(uint32_t aFrameType) {
     SetFrameType(aFrameType);
     Reset();
   }
 
+  // When RtspTrackBuffer is in playout delay duration, it should suspend
+  // reading data from the buffer until the playout-delay-ended event occurs,
+  // which wil be trigger by mPlayoutDelayTimer.
+  void StartPlayoutDelay() {
+    mDuringPlayoutDelay = true;
+  }
+  void LockStartPlayoutDelay() {
+    MonitorAutoLock monitor(mMonitor);
+    StartPlayoutDelay();
+  }
+
+  // If the playout delay is stopped, mPlayoutDelayTimer should be canceled.
+  void StopPlayoutDelay() {
+    if (mPlayoutDelayTimer) {
+      mPlayoutDelayTimer->Cancel();
+      mPlayoutDelayTimer = nullptr;
+    }
+    mDuringPlayoutDelay = false;
+  }
+  void LockStopPlayoutDelay() {
+    MonitorAutoLock monitor(mMonitor);
+    StopPlayoutDelay();
+  }
+
+  bool IsBufferOverThreshold();
+  void CreatePlayoutDelayTimer(unsigned long delayMs);
+  static void PlayoutDelayTimerCallback(nsITimer *aTimer, void *aClosure);
+
 private:
   // The FrameType is sync to nsIStreamingProtocolController.h
   void SetFrameType(uint32_t aFrameType) {
     MonitorAutoLock monitor(mMonitor);
     mFrameType = mFrameType | aFrameType;
   }
 
   // A monitor lock to prevent racing condition.
   Monitor mMonitor;
-#ifdef PR_LOGGING
   // Indicate the track number for Rtsp.
   int32_t mTrackIdx;
-#endif
   // mProducerIdx: A slot index that we store data from
   // nsIStreamingProtocolController.
   // mConsumerIdx: A slot index that we read when decoder need(from OMX decoder).
   int32_t mProducerIdx;
   int32_t mConsumerIdx;
 
   // Because each slot's size is fixed, we need an array to record the real
   // data length and data time stamp.
@@ -154,16 +190,23 @@ private:
   uint32_t mTotalBufferSize;
   // A flag that that indicate the incoming data should be dropped or stored.
   // When we are seeking, the incoming data should be dropped.
   // Bit definition in |nsIStreamingProtocolController.h|
   uint32_t mFrameType;
 
   // Set true/false when |Start()/Stop()| is called.
   bool mIsStarted;
+
+  // Indicate the buffer is in playout delay duration or not.
+  bool mDuringPlayoutDelay;
+  // Playout delay duration defined in milliseconds.
+  uint32_t mPlayoutDelayMs;
+  // Timer used to fire playout-delay-ended event.
+  nsCOMPtr<nsITimer> mPlayoutDelayTimer;
 };
 
 nsresult RtspTrackBuffer::ReadBuffer(uint8_t* aToBuffer, uint32_t aToBufferSize,
                                      uint32_t& aReadCount, uint64_t& aFrameTime,
                                      uint32_t& aFrameSize)
 {
   MonitorAutoLock monitor(mMonitor);
   RTSPMLOG("ReadBuffer mTrackIdx %d mProducerIdx %d mConsumerIdx %d "
@@ -172,19 +215,26 @@ nsresult RtspTrackBuffer::ReadBuffer(uin
            ,mBufferSlotData[mConsumerIdx].mLength);
   // Reader should skip the slots with mLength==BUFFER_SLOT_INVALID.
   // The loop ends when
   // 1. Read data successfully
   // 2. Fail to read data due to aToBuffer's space
   // 3. No data in this buffer
   // 4. mIsStarted is not set
   while (1) {
+    // Do not read from buffer if we are still in the playout delay duration.
+    if (mDuringPlayoutDelay) {
+      monitor.Wait();
+      continue;
+    }
+
     if (mBufferSlotData[mConsumerIdx].mFrameType & MEDIASTREAM_FRAMETYPE_END_OF_STREAM) {
       return NS_BASE_STREAM_CLOSED;
     }
+
     if (mBufferSlotData[mConsumerIdx].mLength > 0) {
       // Check the aToBuffer space is enough for data copy.
       if ((int32_t)aToBufferSize < mBufferSlotData[mConsumerIdx].mLength) {
         aFrameSize = mBufferSlotData[mConsumerIdx].mLength;
         break;
       }
       uint32_t slots = (mBufferSlotData[mConsumerIdx].mLength / mSlotSize) + 1;
       // we have data, copy to aToBuffer
@@ -266,16 +316,22 @@ void RtspTrackBuffer::WriteBuffer(const 
   // Checking current buffer frame type.
   // If the MEDIASTREAM_FRAMETYPE_DISCONTINUNITY bit is set, imply the
   // RtspTrackBuffer can't receive data now. So we drop the frame until we
   // receive MEDIASTREAM_FRAMETYPE_DISCONTINUNITY.
   if (mFrameType & MEDIASTREAM_FRAMETYPE_DISCONTINUITY) {
     RTSPMLOG("Return because the mFrameType is set");
     return;
   }
+
+  // Create a timer to delay ReadBuffer() for a duration.
+  if (mDuringPlayoutDelay && !mPlayoutDelayTimer) {
+    CreatePlayoutDelayTimer(mPlayoutDelayMs);
+  }
+
   // The flag is true if the incoming data is larger than one slot size.
   bool isMultipleSlots = false;
   // The flag is true if the incoming data is larger than remainder free slots
   bool returnToHead = false;
   // Calculate how many slots the incoming data needed.
   int32_t slots = 1;
   int32_t i;
   RTSPMLOG("WriteBuffer mTrackIdx %d mProducerIdx %d mConsumerIdx %d",
@@ -309,24 +365,31 @@ void RtspTrackBuffer::WriteBuffer(const 
     }
     mProducerIdx = 0;
   }
 
   if (!(aFrameType & MEDIASTREAM_FRAMETYPE_END_OF_STREAM)) {
     memcpy(&(mRingBuffer[mSlotSize * mProducerIdx]), aFromBuffer, aWriteCount);
   }
 
+  // If the buffer is almost full, stop the playout delay to let ReadBuffer()
+  // consume data in the buffer.
+  if (mDuringPlayoutDelay && IsBufferOverThreshold()) {
+    StopPlayoutDelay();
+  }
+
   if (mProducerIdx <= mConsumerIdx && mConsumerIdx < mProducerIdx + slots
       && mBufferSlotData[mConsumerIdx].mLength > 0) {
     // Wrote one or more slots that the decode thread has not yet read.
     RTSPMLOG("overwrite!! %d time %lld"
              ,mTrackIdx,mBufferSlotData[mConsumerIdx].mTime);
     if (aFrameType & MEDIASTREAM_FRAMETYPE_END_OF_STREAM) {
       mBufferSlotData[mProducerIdx].mLength = 0;
       mBufferSlotData[mProducerIdx].mTime = 0;
+      StopPlayoutDelay();
     } else {
       mBufferSlotData[mProducerIdx].mLength = aWriteCount;
       mBufferSlotData[mProducerIdx].mTime = aFrameTime;
     }
     mBufferSlotData[mProducerIdx].mFrameType = aFrameType;
     // Clear the mBufferSlotDataLength except the start slot.
     if (isMultipleSlots) {
       for (i = mProducerIdx + 1; i < mProducerIdx + slots; ++i) {
@@ -337,16 +400,17 @@ void RtspTrackBuffer::WriteBuffer(const 
     // Move the mConsumerIdx forward to ensure that the decoder reads the
     // oldest data available.
     mConsumerIdx = mProducerIdx;
   } else {
     // Normal case, the writer doesn't take over the reader.
     if (aFrameType & MEDIASTREAM_FRAMETYPE_END_OF_STREAM) {
       mBufferSlotData[mProducerIdx].mLength = 0;
       mBufferSlotData[mProducerIdx].mTime = 0;
+      StopPlayoutDelay();
     } else {
       mBufferSlotData[mProducerIdx].mLength = aWriteCount;
       mBufferSlotData[mProducerIdx].mTime = aFrameTime;
     }
     mBufferSlotData[mProducerIdx].mFrameType = aFrameType;
     // Clear the mBufferSlotData[].mLength except the start slot.
     if (isMultipleSlots) {
       for (i = mProducerIdx + 1; i < mProducerIdx + slots; ++i) {
@@ -363,19 +427,68 @@ void RtspTrackBuffer::Reset() {
   MonitorAutoLock monitor(mMonitor);
   mProducerIdx = 0;
   mConsumerIdx = 0;
   for (uint32_t i = 0; i < BUFFER_SLOT_NUM; ++i) {
     mBufferSlotData[i].mLength = BUFFER_SLOT_EMPTY;
     mBufferSlotData[i].mTime = BUFFER_SLOT_EMPTY;
     mBufferSlotData[i].mFrameType = MEDIASTREAM_FRAMETYPE_NORMAL;
   }
+  StopPlayoutDelay();
   mMonitor.NotifyAll();
 }
 
+bool
+RtspTrackBuffer::IsBufferOverThreshold()
+{
+  static int32_t numSlotsThreshold =
+    BUFFER_SLOT_NUM * kBufferThresholdPerc;
+
+  int32_t numSlotsUsed = mProducerIdx - mConsumerIdx;
+  if (numSlotsUsed < 0) {  // wrap-around
+    numSlotsUsed = (BUFFER_SLOT_NUM - mConsumerIdx) + mProducerIdx;
+  }
+  if (numSlotsUsed > numSlotsThreshold) {
+    return true;
+  }
+
+  return false;
+}
+
+void
+RtspTrackBuffer::CreatePlayoutDelayTimer(unsigned long delayMs)
+{
+  if (delayMs <= 0) {
+    return;
+  }
+  mPlayoutDelayTimer = do_CreateInstance("@mozilla.org/timer;1");
+  if (mPlayoutDelayTimer) {
+    mPlayoutDelayTimer->InitWithFuncCallback(PlayoutDelayTimerCallback,
+                                             this, delayMs,
+                                             nsITimer::TYPE_ONE_SHOT);
+  }
+}
+
+// static
+void
+RtspTrackBuffer::PlayoutDelayTimerCallback(nsITimer *aTimer,
+                                           void *aClosure)
+{
+  MOZ_ASSERT(aTimer);
+  MOZ_ASSERT(aClosure);
+
+  RtspTrackBuffer *self = static_cast<RtspTrackBuffer*>(aClosure);
+  MonitorAutoLock lock(self->mMonitor);
+  self->StopPlayoutDelay();
+  lock.NotifyAll();
+}
+
+//-----------------------------------------------------------------------------
+// RtspMediaResource
+//-----------------------------------------------------------------------------
 RtspMediaResource::RtspMediaResource(MediaDecoder* aDecoder,
     nsIChannel* aChannel, nsIURI* aURI, const nsACString& aContentType)
   : BaseMediaResource(aDecoder, aChannel, aURI, aContentType)
   , mIsConnected(false)
   , mRealTime(false)
   , mIsSuspend(true)
 {
 #ifndef NECKO_PROTOCOL_rtsp
@@ -753,10 +866,26 @@ nsresult RtspMediaResource::SeekTime(int
   // Clear buffer and raise the frametype flag.
   for(uint32_t i = 0 ; i < mTrackBuffer.Length(); ++i) {
     mTrackBuffer[i]->ResetWithFrameType(MEDIASTREAM_FRAMETYPE_DISCONTINUITY);
   }
 
   return mMediaStreamController->Seek(aOffset);
 }
 
+void
+RtspMediaResource::EnablePlayoutDelay()
+{
+  for (uint32_t i = 0; i < mTrackBuffer.Length(); ++i) {
+    mTrackBuffer[i]->LockStartPlayoutDelay();
+  }
+}
+
+void
+RtspMediaResource::DisablePlayoutDelay()
+{
+  for (uint32_t i = 0; i < mTrackBuffer.Length(); ++i) {
+    mTrackBuffer[i]->LockStopPlayoutDelay();
+  }
+}
+
 } // namespace mozilla
 
--- a/content/media/RtspMediaResource.h
+++ b/content/media/RtspMediaResource.h
@@ -2,16 +2,18 @@
 /* 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/. */
 
 #if !defined(RtspMediaResource_h_)
 #define RtspMediaResource_h_
 
 #include "MediaResource.h"
+#include "mozilla/Monitor.h"
+#include "nsITimer.h"
 
 namespace mozilla {
 
 class RtspTrackBuffer;
 
 /* RtspMediaResource
  * RtspMediaResource provides an interface to deliver and control RTSP media
  * data to RtspDecoder.
@@ -107,16 +109,22 @@ public:
   //   aFrameSize: actual data size in track.
   nsresult ReadFrameFromTrack(uint8_t* aBuffer, uint32_t aBufferSize,
                               uint32_t aTrackIdx, uint32_t& aBytes,
                               uint64_t& aTime, uint32_t& aFrameSize);
 
   // Seek to the given time offset
   nsresult SeekTime(int64_t aOffset);
 
+  // The idea of playout delay is to hold frames in the playout buffer
+  // (RtspTrackBuffer) for a period of time in order to smooth timing variations
+  // caused by the network.
+  void EnablePlayoutDelay();
+  void DisablePlayoutDelay();
+
   // dummy
   virtual nsresult ReadAt(int64_t aOffset, char* aBuffer,
                           uint32_t aCount, uint32_t* aBytes)  MOZ_OVERRIDE{
     return NS_ERROR_FAILURE;
   }
   // dummy
   virtual void     SetReadMode(MediaCacheStream::ReadMode aMode) MOZ_OVERRIDE {}
   // dummy
--- a/content/media/omx/RtspOmxReader.cpp
+++ b/content/media/omx/RtspOmxReader.cpp
@@ -36,16 +36,17 @@ nsresult RtspOmxReader::Seek(int64_t aTi
                              int64_t aEndTime, int64_t aCurrentTime)
 {
   // The seek function of Rtsp is time-based, we call the SeekTime function in
   // RtspMediaResource. The SeekTime function finally send a seek command to
   // Rtsp stream server through network and also clear the buffer data in
   // RtspMediaResource.
   if (mRtspResource) {
     mRtspResource->SeekTime(aTime);
+    mRtspResource->EnablePlayoutDelay();
   }
 
   // Call |MediaOmxReader::Seek| to notify the OMX decoder we are performing a
   // seek operation. The function will clear the |mVideoQueue| and |mAudioQueue|
   // that store the decoded data and also call the |DecodeToTarget| to pass
   // the seek time to OMX a/v decoders.
   return MediaOmxReader::Seek(aTime, aStartTime, aEndTime, aCurrentTime);
 }
@@ -75,9 +76,27 @@ void RtspOmxReader::EnsureActive() {
     }
     mRtspResource->SetSuspend(false);
   }
 
   // Call parent class to set OMXCodec active.
   MediaOmxReader::EnsureActive();
 }
 
+nsresult RtspOmxReader::ReadMetadata(MediaInfo *aInfo, MetadataTags **aTags)
+{
+  // Send a PLAY command to the RTSP server before reading metadata.
+  // Because we might need some decoded samples to ensure we have configuration.
+  mRtspResource->DisablePlayoutDelay();
+  EnsureActive();
+  nsresult rv = MediaOmxReader::ReadMetadata(aInfo, aTags);
+
+  if (rv == NS_OK && !IsWaitingMediaResources()) {
+    mRtspResource->EnablePlayoutDelay();
+  } else if (IsWaitingMediaResources()) {
+    // Send a PAUSE to the RTSP server because the underlying media resource is
+    // not ready.
+    SetIdle();
+  }
+  return rv;
+}
+
 } // namespace mozilla
--- a/content/media/omx/RtspOmxReader.h
+++ b/content/media/omx/RtspOmxReader.h
@@ -60,16 +60,19 @@ public:
   // data so the |GetBuffered| function can retrieve useful time ranges.
   virtual nsresult GetBuffered(mozilla::dom::TimeRanges* aBuffered,
                                int64_t aStartTime) MOZ_FINAL MOZ_OVERRIDE {
     return NS_OK;
   }
 
   virtual void SetIdle() MOZ_OVERRIDE;
 
+  virtual nsresult ReadMetadata(MediaInfo *aInfo, MetadataTags **aTags)
+    MOZ_FINAL MOZ_OVERRIDE;
+
 private:
   // A pointer to RtspMediaResource for calling the Rtsp specific function.
   // The lifetime of mRtspResource is controlled by MediaDecoder. MediaDecoder
   // holds the MediaDecoderStateMachine and RtspMediaResource.
   // And MediaDecoderStateMachine holds this RtspOmxReader.
   RtspMediaResource* mRtspResource;
 };