Bug 1056187 - Add playout delay to RtspTrackBuffer. r=bechen
--- 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;
};