Revision a0c94c0314444cca00420448d57485ccbefcef7e authored by Gerald Squelart on 10 February 2016, 07:55:33 UTC, committed by Jean-Yves Avenard on 26 February 2016, 03:43:42 UTC
1 parent 64152cc
Raw File
RtspMediaResource.cpp
/* -*- 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 "mozilla/UniquePtr.h"
#include "nsIScriptSecurityManager.h"
#include "nsIStreamingProtocolService.h"
#include "nsServiceManagerUtils.h"
#ifdef NECKO_PROTOCOL_rtsp
#include "mozilla/net/RtspChannelChild.h"
#endif
using namespace mozilla::net;
using namespace mozilla::media;

mozilla::LazyLogModule gRtspMediaResourceLog("RtspMediaResource");
#define RTSP_LOG(msg, ...) MOZ_LOG(gRtspMediaResourceLog, mozilla::LogLevel::Debug, \
                                  (msg, ##__VA_ARGS__))
// Debug logging macro with object pointer and class name.
#define RTSPMLOG(msg, ...) \
        RTSP_LOG("%p [RtspMediaResource]: " msg, this, ##__VA_ARGS__)

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 = MakeUnique<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 += aMallocSizeOf(mRingBuffer.get());

    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.
  UniquePtr<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) {
    // Make sure the track buffer is started.
    // It could be stopped when RTSP connection is disconnected.
    if (!mIsStarted) {
      RTSPMLOG("ReadBuffer: mIsStarted is false");
      return NS_ERROR_FAILURE;
    }

    // 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;
      if (mBufferSlotData[mConsumerIdx].mLength % mSlotSize > 0) {
        slots++;
      }
      // 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, 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 = aWriteCount / mSlotSize;
  if (aWriteCount % mSlotSize > 0) {
    slots++;
  }
  int32_t i;
  RTSPMLOG("WriteBuffer mTrackIdx %d mProducerIdx %d mConsumerIdx %d",
           mTrackIdx, mProducerIdx,mConsumerIdx);
  if (aWriteCount > mSlotSize) {
    isMultipleSlots = true;
  }
  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(MediaResourceCallback* aCallback,
    nsIChannel* aChannel, nsIURI* aURI, const nsACString& aContentType)
  : BaseMediaResource(aCallback, aChannel, aURI, aContentType)
  , mIsConnected(false)
  , mIsLiveStream(false)
  , mHasTimestamp(true)
  , 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);
#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 (mCallback) {
    mCallback->NotifySuspendedStatusChanged();
  }
}

size_t
RtspMediaResource::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
{
  size_t size = BaseMediaResource::SizeOfExcludingThis(aMallocSizeOf);
  size += mTrackBuffer.ShallowSizeOfExcludingThis(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);

  if (!mIsConnected) {
    RTSPMLOG("ReadFrameFromTrack: RTSP not connected");
    return NS_ERROR_FAILURE;
  }

  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);
  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.
    mCallback->NotifyDecodeError();
    return NS_ERROR_FAILURE;
  }
  uint64_t durationUs = 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(&durationUs);

    // 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 (!mCallback) {
    return NS_ERROR_FAILURE;
  }

  // If the durationUs is 0, imply the stream is live stream.
  if (durationUs) {
    // Not live stream.
    mIsLiveStream = false;
    mCallback->SetInfinite(false);
  } 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.
      mCallback->NotifyDecodeError();
      return NS_ERROR_FAILURE;
    } else {
      mIsLiveStream = true;
      bool seekable = false;
      mCallback->SetInfinite(true);
      mCallback->SetMediaSeekable(seekable);
    }
  }
  MediaDecoderOwner* owner = mCallback->GetMediaOwner();
  NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE);
  // Fires an initial progress event.
  owner->DownloadProgressed();

  nsresult rv = mCallback->FinishDecoderSetup(this);
  NS_ENSURE_SUCCESS(rv, rv);

  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 (mCallback) {
    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);
      mIsConnected = false;
      mCallback->NotifyNetworkError();
    } else {
      // Resetting the decoder and media element when the connection
      // between RTSP client and server goes down.
      mCallback->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(!mCallback)) {
    return;
  }

  MediaDecoderOwner* owner = mCallback->GetMediaOwner();
  NS_ENSURE_TRUE_VOID(owner);
  dom::HTMLMediaElement* element = owner->GetMediaElement();
  NS_ENSURE_TRUE_VOID(element);

  mMediaStreamController->Suspend();
  element->DownloadSuspended();
  mCallback->NotifySuspendedStatusChanged();
}

void RtspMediaResource::Resume()
{
  NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");

  mIsSuspend = false;
  if (NS_WARN_IF(!mCallback)) {
    return;
  }

  MediaDecoderOwner* owner = mCallback->GetMediaOwner();
  NS_ENSURE_TRUE_VOID(owner);
  dom::HTMLMediaElement* element = owner->GetMediaElement();
  NS_ENSURE_TRUE_VOID(element);

  if (mChannel) {
    element->DownloadResumed();
  }
  mMediaStreamController->Resume();
  mCallback->NotifySuspendedStatusChanged();
}

nsresult RtspMediaResource::Open(nsIStreamListener **aStreamListener)
{
  return NS_OK;
}

nsresult RtspMediaResource::Close()
{
  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
  mMediaStreamController->Stop();
  // Since mCallback 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 (mCallback) {
    mCallback = 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, mCallback.get());
  // 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

back to top