mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-01 17:23:59 +00:00
892 lines
29 KiB
C++
892 lines
29 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
|
/* 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/. */
|
|
|
|
#include "mozilla/DebugOnly.h"
|
|
|
|
#include "RtspMediaResource.h"
|
|
|
|
#include "MediaDecoder.h"
|
|
#include "mozilla/dom/HTMLMediaElement.h"
|
|
#include "mozilla/Monitor.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "nsIScriptSecurityManager.h"
|
|
#include "nsIStreamingProtocolService.h"
|
|
#include "nsServiceManagerUtils.h"
|
|
#ifdef NECKO_PROTOCOL_rtsp
|
|
#include "mozilla/net/RtspChannelChild.h"
|
|
#endif
|
|
using namespace mozilla::net;
|
|
|
|
#ifdef PR_LOGGING
|
|
PRLogModuleInfo* gRtspMediaResourceLog;
|
|
#define RTSP_LOG(msg, ...) PR_LOG(gRtspMediaResourceLog, PR_LOG_DEBUG, \
|
|
(msg, ##__VA_ARGS__))
|
|
// Debug logging macro with object pointer and class name.
|
|
#define RTSPMLOG(msg, ...) \
|
|
RTSP_LOG("%p [RtspMediaResource]: " msg, this, ##__VA_ARGS__)
|
|
#else
|
|
#define RTSP_LOG(msg, ...)
|
|
#define RTSPMLOG(msg, ...)
|
|
#endif
|
|
|
|
namespace mozilla {
|
|
|
|
/* class RtspTrackBuffer: a ring buffer implementation for audio/video track
|
|
* un-decoded data.
|
|
* The ring buffer is divided into BUFFER_SLOT_NUM slots,
|
|
* and each slot's size is fixed(mSlotSize).
|
|
* Even though the ring buffer is divided into fixed size slots, it still can
|
|
* store the data which size is larger than one slot size.
|
|
* */
|
|
#define BUFFER_SLOT_NUM 8192
|
|
#define BUFFER_SLOT_DEFAULT_SIZE 256
|
|
#define BUFFER_SLOT_MAX_SIZE 512
|
|
#define BUFFER_SLOT_INVALID -1
|
|
#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)
|
|
, mDuringPlayoutDelay(false)
|
|
, mPlayoutDelayMs(kPlayoutDelayMs)
|
|
, mPlayoutDelayTimer(nullptr) {
|
|
MOZ_COUNT_CTOR(RtspTrackBuffer);
|
|
mTrackIdx = aTrackIdx;
|
|
MOZ_ASSERT(mSlotSize < UINT32_MAX / BUFFER_SLOT_NUM);
|
|
mRingBuffer = new uint8_t[mTotalBufferSize];
|
|
Reset();
|
|
};
|
|
~RtspTrackBuffer() {
|
|
MOZ_COUNT_DTOR(RtspTrackBuffer);
|
|
mRingBuffer = nullptr;
|
|
};
|
|
|
|
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
|
|
// including this
|
|
size_t size = aMallocSizeOf(this);
|
|
|
|
// excluding this
|
|
size += mRingBuffer.SizeOfExcludingThis(aMallocSizeOf);
|
|
|
|
return size;
|
|
}
|
|
|
|
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,
|
|
uint32_t& aReadCount, uint64_t& aFrameTime,
|
|
uint32_t& aFrameSize);
|
|
// Write the data from aFromBuffer into mRingBuffer[mProducerIdx*mSlotSize].
|
|
void WriteBuffer(const char *aFromBuffer, uint32_t aWriteCount,
|
|
uint64_t aFrameTime, uint32_t aFrameType);
|
|
// Reset the mProducerIdx, mConsumerIdx, mBufferSlotDataLength[],
|
|
// mBufferSlotDataTime[].
|
|
void Reset();
|
|
|
|
// 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;
|
|
// Indicate the track number for Rtsp.
|
|
int32_t mTrackIdx;
|
|
// 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.
|
|
// The value in mBufferSlotData[index].mLength represents:
|
|
// -1(BUFFER_SLOT_INVALID): The index of slot data is invalid, mConsumerIdx
|
|
// should go forward.
|
|
// 0(BUFFER_SLOT_EMPTY): The index slot is empty. mConsumerIdx should wait here.
|
|
// positive value: The index slot contains valid data and the value is data size.
|
|
BufferSlotData mBufferSlotData[BUFFER_SLOT_NUM];
|
|
|
|
// The ring buffer pointer.
|
|
nsAutoArrayPtr<uint8_t> mRingBuffer;
|
|
// Each slot's size.
|
|
uint32_t mSlotSize;
|
|
// Total mRingBuffer's total size.
|
|
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 "
|
|
"mBufferSlotData[mConsumerIdx].mLength %d"
|
|
,mTrackIdx ,mProducerIdx ,mConsumerIdx
|
|
,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
|
|
MOZ_ASSERT(mBufferSlotData[mConsumerIdx].mLength <=
|
|
(int32_t)((BUFFER_SLOT_NUM - mConsumerIdx) * mSlotSize));
|
|
memcpy(aToBuffer,
|
|
(void *)(&mRingBuffer[mSlotSize * mConsumerIdx]),
|
|
mBufferSlotData[mConsumerIdx].mLength);
|
|
|
|
aFrameSize = aReadCount = mBufferSlotData[mConsumerIdx].mLength;
|
|
aFrameTime = mBufferSlotData[mConsumerIdx].mTime;
|
|
RTSPMLOG("DataLength %d, data time %lld"
|
|
,mBufferSlotData[mConsumerIdx].mLength
|
|
,mBufferSlotData[mConsumerIdx].mTime);
|
|
// After reading the data, we set current index of mBufferSlotDataLength
|
|
// to BUFFER_SLOT_EMPTY to indicate these slots are free.
|
|
for (uint32_t i = mConsumerIdx; i < mConsumerIdx + slots; ++i) {
|
|
mBufferSlotData[i].mLength = BUFFER_SLOT_EMPTY;
|
|
mBufferSlotData[i].mTime = BUFFER_SLOT_EMPTY;
|
|
}
|
|
mConsumerIdx = (mConsumerIdx + slots) % BUFFER_SLOT_NUM;
|
|
break;
|
|
} else if (mBufferSlotData[mConsumerIdx].mLength == BUFFER_SLOT_INVALID) {
|
|
mConsumerIdx = (mConsumerIdx + 1) % BUFFER_SLOT_NUM;
|
|
RTSPMLOG("BUFFER_SLOT_INVALID move forward");
|
|
} else {
|
|
// No data, and disconnected.
|
|
if (!mIsStarted) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
// No data, the decode thread is blocked here until we receive
|
|
// OnMediaDataAvailable. The OnMediaDataAvailable will call WriteBuffer()
|
|
// to wake up the decode thread.
|
|
RTSPMLOG("monitor.Wait()");
|
|
monitor.Wait();
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
/* When we perform a WriteBuffer, we check mIsStarted and aFrameType first.
|
|
* These flags prevent "garbage" frames from being written into the buffer.
|
|
*
|
|
* After writing the data into the buffer, we check to see if we wrote over a
|
|
* slot, and update mConsumerIdx if necessary.
|
|
* This ensures that the decoder will get the "oldest" data available in the
|
|
* buffer.
|
|
*
|
|
* If the incoming data is larger than one slot size (isMultipleSlots), we do
|
|
* |mBufferSlotData[].mLength = BUFFER_SLOT_INVALID;| for other slots except the
|
|
* first slot, in order to notify the reader that some slots are unavailable.
|
|
*
|
|
* If the incoming data is isMultipleSlots and crosses the end of
|
|
* BUFFER_SLOT_NUM, returnToHead is set to true and the data will continue to
|
|
* be written from head(index 0).
|
|
*
|
|
* MEDIASTREAM_FRAMETYPE_DISCONTINUITY currently is used when we are seeking.
|
|
* */
|
|
void RtspTrackBuffer::WriteBuffer(const char *aFromBuffer, uint32_t aWriteCount,
|
|
uint64_t aFrameTime, uint32_t aFrameType)
|
|
{
|
|
MonitorAutoLock monitor(mMonitor);
|
|
if (!mIsStarted) {
|
|
RTSPMLOG("mIsStarted is false");
|
|
return;
|
|
}
|
|
if (mTotalBufferSize < aWriteCount) {
|
|
RTSPMLOG("mTotalBufferSize < aWriteCount, incoming data is too large");
|
|
return;
|
|
}
|
|
// Checking the incoming data's frame type.
|
|
// If we receive MEDIASTREAM_FRAMETYPE_DISCONTINUITY, clear the mFrameType
|
|
// imply the RtspTrackBuffer is ready for receive data.
|
|
if (aFrameType & MEDIASTREAM_FRAMETYPE_DISCONTINUITY) {
|
|
mFrameType = mFrameType & (~MEDIASTREAM_FRAMETYPE_DISCONTINUITY);
|
|
RTSPMLOG("Clear mFrameType");
|
|
return;
|
|
}
|
|
// 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",
|
|
mTrackIdx, mProducerIdx,mConsumerIdx);
|
|
if (aWriteCount > mSlotSize) {
|
|
isMultipleSlots = true;
|
|
slots = (aWriteCount / mSlotSize) + 1;
|
|
}
|
|
if (isMultipleSlots &&
|
|
(aWriteCount > (BUFFER_SLOT_NUM - mProducerIdx) * mSlotSize)) {
|
|
returnToHead = true;
|
|
}
|
|
RTSPMLOG("slots %d isMultipleSlots %d returnToHead %d",
|
|
slots, isMultipleSlots, returnToHead);
|
|
if (returnToHead) {
|
|
// Clear the rest index of mBufferSlotData[].mLength
|
|
for (i = mProducerIdx; i < BUFFER_SLOT_NUM; ++i) {
|
|
mBufferSlotData[i].mLength = BUFFER_SLOT_INVALID;
|
|
}
|
|
// We wrote one or more slots that the decode thread has not yet read.
|
|
// So the mConsumerIdx returns to the head of slot buffer and moves forward
|
|
// to the oldest slot.
|
|
if (mProducerIdx <= mConsumerIdx && mConsumerIdx < mProducerIdx + slots) {
|
|
mConsumerIdx = 0;
|
|
for (i = mConsumerIdx; i < BUFFER_SLOT_NUM; ++i) {
|
|
if (mBufferSlotData[i].mLength > 0) {
|
|
mConsumerIdx = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
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) {
|
|
mBufferSlotData[i].mLength = BUFFER_SLOT_INVALID;
|
|
}
|
|
}
|
|
mProducerIdx = (mProducerIdx + slots) % BUFFER_SLOT_NUM;
|
|
// 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) {
|
|
mBufferSlotData[i].mLength = BUFFER_SLOT_INVALID;
|
|
}
|
|
}
|
|
mProducerIdx = (mProducerIdx + slots) % BUFFER_SLOT_NUM;
|
|
}
|
|
|
|
mMonitor.NotifyAll();
|
|
}
|
|
|
|
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
|
|
MOZ_CRASH("Should not be called except for B2G platform");
|
|
#else
|
|
MOZ_ASSERT(aChannel);
|
|
mMediaStreamController =
|
|
static_cast<RtspChannelChild*>(aChannel)->GetController();
|
|
MOZ_ASSERT(mMediaStreamController);
|
|
mListener = new Listener(this);
|
|
mMediaStreamController->AsyncOpen(mListener);
|
|
#ifdef PR_LOGGING
|
|
if (!gRtspMediaResourceLog) {
|
|
gRtspMediaResourceLog = PR_NewLogModule("RtspMediaResource");
|
|
}
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
RtspMediaResource::~RtspMediaResource()
|
|
{
|
|
RTSPMLOG("~RtspMediaResource");
|
|
if (mListener) {
|
|
// Kill its reference to us since we're going away
|
|
mListener->Revoke();
|
|
}
|
|
}
|
|
|
|
void RtspMediaResource::SetSuspend(bool aIsSuspend)
|
|
{
|
|
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
|
|
RTSPMLOG("SetSuspend %d",aIsSuspend);
|
|
|
|
nsCOMPtr<nsIRunnable> runnable =
|
|
NS_NewRunnableMethodWithArg<bool>(this, &RtspMediaResource::NotifySuspend,
|
|
aIsSuspend);
|
|
NS_DispatchToMainThread(runnable);
|
|
}
|
|
|
|
void RtspMediaResource::NotifySuspend(bool aIsSuspend)
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
|
|
RTSPMLOG("NotifySuspend %d",aIsSuspend);
|
|
|
|
mIsSuspend = aIsSuspend;
|
|
if (mDecoder) {
|
|
mDecoder->NotifySuspendedStatusChanged();
|
|
}
|
|
}
|
|
|
|
size_t
|
|
RtspMediaResource::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
|
|
{
|
|
size_t size = BaseMediaResource::SizeOfExcludingThis(aMallocSizeOf);
|
|
size += mTrackBuffer.SizeOfExcludingThis(aMallocSizeOf);
|
|
|
|
// Include the size of each track buffer.
|
|
for (size_t i = 0; i < mTrackBuffer.Length(); i++) {
|
|
size += mTrackBuffer[i]->SizeOfIncludingThis(aMallocSizeOf);
|
|
}
|
|
|
|
// Could add in the future:
|
|
// - mMediaStreamController
|
|
|
|
return size;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// RtspMediaResource::Listener
|
|
//----------------------------------------------------------------------------
|
|
NS_IMPL_ISUPPORTS(RtspMediaResource::Listener,
|
|
nsIInterfaceRequestor, nsIStreamingProtocolListener);
|
|
|
|
nsresult
|
|
RtspMediaResource::Listener::OnMediaDataAvailable(uint8_t aTrackIdx,
|
|
const nsACString &data,
|
|
uint32_t length,
|
|
uint32_t offset,
|
|
nsIStreamingProtocolMetaData *meta)
|
|
{
|
|
if (!mResource)
|
|
return NS_OK;
|
|
return mResource->OnMediaDataAvailable(aTrackIdx, data, length, offset, meta);
|
|
}
|
|
|
|
nsresult
|
|
RtspMediaResource::Listener::OnConnected(uint8_t aTrackIdx,
|
|
nsIStreamingProtocolMetaData *meta)
|
|
{
|
|
if (!mResource)
|
|
return NS_OK;
|
|
return mResource->OnConnected(aTrackIdx, meta);
|
|
}
|
|
|
|
nsresult
|
|
RtspMediaResource::Listener::OnDisconnected(uint8_t aTrackIdx, nsresult reason)
|
|
{
|
|
if (!mResource)
|
|
return NS_OK;
|
|
return mResource->OnDisconnected(aTrackIdx, reason);
|
|
}
|
|
|
|
nsresult
|
|
RtspMediaResource::Listener::GetInterface(const nsIID & aIID, void **aResult)
|
|
{
|
|
return QueryInterface(aIID, aResult);
|
|
}
|
|
|
|
void
|
|
RtspMediaResource::Listener::Revoke()
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
|
|
if (mResource) {
|
|
mResource = nullptr;
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
RtspMediaResource::ReadFrameFromTrack(uint8_t* aBuffer, uint32_t aBufferSize,
|
|
uint32_t aTrackIdx, uint32_t& aBytes,
|
|
uint64_t& aTime, uint32_t& aFrameSize)
|
|
{
|
|
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
|
|
NS_ASSERTION(aTrackIdx < mTrackBuffer.Length(),
|
|
"ReadTrack index > mTrackBuffer");
|
|
MOZ_ASSERT(aBuffer);
|
|
|
|
return mTrackBuffer[aTrackIdx]->ReadBuffer(aBuffer, aBufferSize, aBytes,
|
|
aTime, aFrameSize);
|
|
}
|
|
|
|
nsresult
|
|
RtspMediaResource::OnMediaDataAvailable(uint8_t aTrackIdx,
|
|
const nsACString &data,
|
|
uint32_t length,
|
|
uint32_t offset,
|
|
nsIStreamingProtocolMetaData *meta)
|
|
{
|
|
uint64_t time;
|
|
uint32_t frameType;
|
|
meta->GetTimeStamp(&time);
|
|
meta->GetFrameType(&frameType);
|
|
if (mRealTime) {
|
|
time = 0;
|
|
}
|
|
mTrackBuffer[aTrackIdx]->WriteBuffer(data.BeginReading(), length, time,
|
|
frameType);
|
|
return NS_OK;
|
|
}
|
|
|
|
// Bug 962309 - Video RTSP support should be disabled in 1.3
|
|
bool
|
|
RtspMediaResource::IsVideoEnabled()
|
|
{
|
|
return Preferences::GetBool("media.rtsp.video.enabled", false);
|
|
}
|
|
|
|
bool
|
|
RtspMediaResource::IsVideo(uint8_t tracks, nsIStreamingProtocolMetaData *meta)
|
|
{
|
|
bool isVideo = false;
|
|
for (int i = 0; i < tracks; ++i) {
|
|
nsCOMPtr<nsIStreamingProtocolMetaData> trackMeta;
|
|
mMediaStreamController->GetTrackMetaData(i, getter_AddRefs(trackMeta));
|
|
MOZ_ASSERT(trackMeta);
|
|
uint32_t w = 0, h = 0;
|
|
trackMeta->GetWidth(&w);
|
|
trackMeta->GetHeight(&h);
|
|
if (w > 0 || h > 0) {
|
|
isVideo = true;
|
|
break;
|
|
}
|
|
}
|
|
return isVideo;
|
|
}
|
|
|
|
nsresult
|
|
RtspMediaResource::OnConnected(uint8_t aTrackIdx,
|
|
nsIStreamingProtocolMetaData *meta)
|
|
{
|
|
if (mIsConnected) {
|
|
for (uint32_t i = 0 ; i < mTrackBuffer.Length(); ++i) {
|
|
mTrackBuffer[i]->Start();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
uint8_t tracks;
|
|
mMediaStreamController->GetTotalTracks(&tracks);
|
|
|
|
// If the preference of RTSP video feature is not enabled and the streaming is
|
|
// video, we give up moving forward.
|
|
if (!IsVideoEnabled() && IsVideo(tracks, meta)) {
|
|
// Give up, report error to media element.
|
|
nsCOMPtr<nsIRunnable> event =
|
|
NS_NewRunnableMethod(mDecoder, &MediaDecoder::DecodeError);
|
|
NS_DispatchToMainThread(event);
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
uint64_t duration = 0;
|
|
for (int i = 0; i < tracks; ++i) {
|
|
nsCString rtspTrackId("RtspTrack");
|
|
rtspTrackId.AppendInt(i);
|
|
nsCOMPtr<nsIStreamingProtocolMetaData> trackMeta;
|
|
mMediaStreamController->GetTrackMetaData(i, getter_AddRefs(trackMeta));
|
|
MOZ_ASSERT(trackMeta);
|
|
trackMeta->GetDuration(&duration);
|
|
|
|
// Here is a heuristic to estimate the slot size.
|
|
// For video track, calculate the width*height.
|
|
// For audio track, use the BUFFER_SLOT_DEFAULT_SIZE because the w*h is 0.
|
|
// Finally clamp them into (BUFFER_SLOT_DEFAULT_SIZE,BUFFER_SLOT_MAX_SIZE)
|
|
uint32_t w, h;
|
|
uint32_t slotSize;
|
|
trackMeta->GetWidth(&w);
|
|
trackMeta->GetHeight(&h);
|
|
slotSize = clamped((int32_t)(w * h), BUFFER_SLOT_DEFAULT_SIZE,
|
|
BUFFER_SLOT_MAX_SIZE);
|
|
mTrackBuffer.AppendElement(new RtspTrackBuffer(rtspTrackId.get(),
|
|
i, slotSize));
|
|
mTrackBuffer[i]->Start();
|
|
}
|
|
|
|
if (!mDecoder) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// If the duration is 0, imply the stream is live stream.
|
|
if (duration) {
|
|
// Not live stream.
|
|
mRealTime = false;
|
|
mDecoder->SetInfinite(false);
|
|
mDecoder->SetDuration(duration);
|
|
} else {
|
|
// Live stream.
|
|
// Check the preference "media.realtime_decoder.enabled".
|
|
if (!Preferences::GetBool("media.realtime_decoder.enabled", false)) {
|
|
// Give up, report error to media element.
|
|
nsCOMPtr<nsIRunnable> event =
|
|
NS_NewRunnableMethod(mDecoder, &MediaDecoder::DecodeError);
|
|
NS_DispatchToMainThread(event);
|
|
return NS_ERROR_FAILURE;
|
|
} else {
|
|
mRealTime = true;
|
|
bool seekable = false;
|
|
mDecoder->SetInfinite(true);
|
|
mDecoder->SetMediaSeekable(seekable);
|
|
}
|
|
}
|
|
// Fires an initial progress event and sets up the stall counter so stall events
|
|
// fire if no download occurs within the required time frame.
|
|
mDecoder->Progress(false);
|
|
|
|
MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
|
|
NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE);
|
|
dom::HTMLMediaElement* element = owner->GetMediaElement();
|
|
NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
|
|
|
|
element->FinishDecoderSetup(mDecoder, this);
|
|
mIsConnected = true;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
RtspMediaResource::OnDisconnected(uint8_t aTrackIdx, nsresult aReason)
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
|
|
|
|
for (uint32_t i = 0 ; i < mTrackBuffer.Length(); ++i) {
|
|
mTrackBuffer[i]->Stop();
|
|
mTrackBuffer[i]->Reset();
|
|
}
|
|
|
|
if (mDecoder) {
|
|
if (aReason == NS_ERROR_NOT_INITIALIZED ||
|
|
aReason == NS_ERROR_CONNECTION_REFUSED ||
|
|
aReason == NS_ERROR_NOT_CONNECTED ||
|
|
aReason == NS_ERROR_NET_TIMEOUT) {
|
|
// Report error code to Decoder.
|
|
RTSPMLOG("Error in OnDisconnected 0x%x", aReason);
|
|
mDecoder->NetworkError();
|
|
} else {
|
|
// Resetting the decoder and media element when the connection
|
|
// between RTSP client and server goes down.
|
|
mDecoder->ResetConnectionState();
|
|
}
|
|
}
|
|
|
|
if (mListener) {
|
|
// Note: Listener's Revoke() kills its reference to us, which means it would
|
|
// release |this| object. So, ensure it is called in the end of this method.
|
|
mListener->Revoke();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void RtspMediaResource::Suspend(bool aCloseImmediately)
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
|
|
|
|
mIsSuspend = true;
|
|
if (NS_WARN_IF(!mDecoder)) {
|
|
return;
|
|
}
|
|
|
|
MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
|
|
NS_ENSURE_TRUE_VOID(owner);
|
|
dom::HTMLMediaElement* element = owner->GetMediaElement();
|
|
NS_ENSURE_TRUE_VOID(element);
|
|
|
|
mMediaStreamController->Suspend();
|
|
element->DownloadSuspended();
|
|
mDecoder->NotifySuspendedStatusChanged();
|
|
}
|
|
|
|
void RtspMediaResource::Resume()
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
|
|
|
|
mIsSuspend = false;
|
|
if (NS_WARN_IF(!mDecoder)) {
|
|
return;
|
|
}
|
|
|
|
MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
|
|
NS_ENSURE_TRUE_VOID(owner);
|
|
dom::HTMLMediaElement* element = owner->GetMediaElement();
|
|
NS_ENSURE_TRUE_VOID(element);
|
|
|
|
if (mChannel) {
|
|
element->DownloadResumed();
|
|
}
|
|
mMediaStreamController->Resume();
|
|
mDecoder->NotifySuspendedStatusChanged();
|
|
}
|
|
|
|
nsresult RtspMediaResource::Open(nsIStreamListener **aStreamListener)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult RtspMediaResource::Close()
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
|
|
mMediaStreamController->Stop();
|
|
// Since mDecoder is not an nsCOMPtr in BaseMediaResource, we have to
|
|
// explicitly set it as null pointer in order to prevent misuse from this
|
|
// object (RtspMediaResource).
|
|
if (mDecoder) {
|
|
mDecoder = nullptr;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
already_AddRefed<nsIPrincipal> RtspMediaResource::GetCurrentPrincipal()
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
|
|
|
|
nsCOMPtr<nsIPrincipal> principal;
|
|
nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
|
|
if (!secMan || !mChannel)
|
|
return nullptr;
|
|
secMan->GetChannelResultPrincipal(mChannel, getter_AddRefs(principal));
|
|
return principal.forget();
|
|
}
|
|
|
|
nsresult RtspMediaResource::SeekTime(int64_t aOffset)
|
|
{
|
|
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
|
|
|
|
RTSPMLOG("Seek requested for aOffset [%lld] for decoder [%p]",
|
|
aOffset, mDecoder);
|
|
// 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
|
|
|