summaryrefslogtreecommitdiffstats
path: root/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'media/webrtc/signaling/src/media-conduit/VideoConduit.cpp')
-rwxr-xr-xmedia/webrtc/signaling/src/media-conduit/VideoConduit.cpp2129
1 files changed, 2129 insertions, 0 deletions
diff --git a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
new file mode 100755
index 000000000..3f0445122
--- /dev/null
+++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
@@ -0,0 +1,2129 @@
+/* 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 "CSFLog.h"
+#include "nspr.h"
+#include "plstr.h"
+
+#include "VideoConduit.h"
+#include "AudioConduit.h"
+#include "nsThreadUtils.h"
+#include "LoadManager.h"
+#include "YuvStamper.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "mozilla/media/MediaUtils.h"
+#include "mozilla/TemplateLib.h"
+
+#include "webrtc/common_types.h"
+#include "webrtc/common_video/interface/native_handle.h"
+#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
+#include "webrtc/video_engine/include/vie_errors.h"
+#include "webrtc/video_engine/vie_defines.h"
+
+#include "mozilla/Unused.h"
+
+#ifdef MOZ_WIDGET_ANDROID
+#include "AndroidJNIWrapper.h"
+#endif
+
+// for ntohs
+#ifdef _MSC_VER
+#include "Winsock2.h"
+#else
+#include <netinet/in.h>
+#endif
+
+#include <algorithm>
+#include <math.h>
+
+#define DEFAULT_VIDEO_MAX_FRAMERATE 30
+#define INVALID_RTP_PAYLOAD 255 //valid payload types are 0 to 127
+
+namespace mozilla {
+
+static const char* logTag ="WebrtcVideoSessionConduit";
+
+// 32 bytes is what WebRTC CodecInst expects
+const unsigned int WebrtcVideoConduit::CODEC_PLNAME_SIZE = 32;
+
+/**
+ * Factory Method for VideoConduit
+ */
+RefPtr<VideoSessionConduit>
+VideoSessionConduit::Create()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+ CSFLogDebug(logTag, "%s ", __FUNCTION__);
+
+ WebrtcVideoConduit* obj = new WebrtcVideoConduit();
+ if(obj->Init() != kMediaConduitNoError)
+ {
+ CSFLogError(logTag, "%s VideoConduit Init Failed ", __FUNCTION__);
+ delete obj;
+ return nullptr;
+ }
+ CSFLogDebug(logTag, "%s Successfully created VideoConduit ", __FUNCTION__);
+ return obj;
+}
+
+WebrtcVideoConduit::WebrtcVideoConduit():
+ mVideoEngine(nullptr),
+ mTransportMonitor("WebrtcVideoConduit"),
+ mTransmitterTransport(nullptr),
+ mReceiverTransport(nullptr),
+ mRenderer(nullptr),
+ mPtrExtCapture(nullptr),
+ mEngineTransmitting(false),
+ mEngineReceiving(false),
+ mChannel(-1),
+ mCapId(-1),
+ mCodecMutex("VideoConduit codec db"),
+ mInReconfig(false),
+ mLastWidth(0), // forces a check for reconfig at start
+ mLastHeight(0),
+ mSendingWidth(0),
+ mSendingHeight(0),
+ mReceivingWidth(0),
+ mReceivingHeight(0),
+ mSendingFramerate(DEFAULT_VIDEO_MAX_FRAMERATE),
+ mLastFramerateTenths(DEFAULT_VIDEO_MAX_FRAMERATE*10),
+ mNumReceivingStreams(1),
+ mVideoLatencyTestEnable(false),
+ mVideoLatencyAvg(0),
+ mMinBitrate(0),
+ mStartBitrate(0),
+ mMaxBitrate(0),
+ mMinBitrateEstimate(0),
+ mRtpStreamIdEnabled(false),
+ mRtpStreamIdExtId(0),
+ mCodecMode(webrtc::kRealtimeVideo)
+{}
+
+WebrtcVideoConduit::~WebrtcVideoConduit()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+ CSFLogDebug(logTag, "%s ", __FUNCTION__);
+
+ // Release AudioConduit first by dropping reference on MainThread, where it expects to be
+ SyncTo(nullptr);
+ Destroy();
+}
+
+bool WebrtcVideoConduit::SetLocalSSRC(unsigned int ssrc)
+{
+ unsigned int oldSsrc;
+ if (!GetLocalSSRC(&oldSsrc)) {
+ MOZ_ASSERT(false, "GetLocalSSRC failed");
+ return false;
+ }
+
+ if (oldSsrc == ssrc) {
+ return true;
+ }
+
+ bool wasTransmitting = mEngineTransmitting;
+ if (StopTransmitting() != kMediaConduitNoError) {
+ return false;
+ }
+
+ if (mPtrRTP->SetLocalSSRC(mChannel, ssrc)) {
+ return false;
+ }
+
+ if (wasTransmitting) {
+ if (StartTransmitting() != kMediaConduitNoError) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool WebrtcVideoConduit::GetLocalSSRC(unsigned int* ssrc)
+{
+ return !mPtrRTP->GetLocalSSRC(mChannel, *ssrc);
+}
+
+bool WebrtcVideoConduit::GetRemoteSSRC(unsigned int* ssrc)
+{
+ return !mPtrRTP->GetRemoteSSRC(mChannel, *ssrc);
+}
+
+bool WebrtcVideoConduit::SetLocalCNAME(const char* cname)
+{
+ char temp[256];
+ strncpy(temp, cname, sizeof(temp) - 1);
+ temp[sizeof(temp) - 1] = 0;
+ return !mPtrRTP->SetRTCPCName(mChannel, temp);
+}
+
+bool WebrtcVideoConduit::GetVideoEncoderStats(double* framerateMean,
+ double* framerateStdDev,
+ double* bitrateMean,
+ double* bitrateStdDev,
+ uint32_t* droppedFrames)
+{
+ if (!mEngineTransmitting) {
+ return false;
+ }
+ MOZ_ASSERT(mVideoCodecStat);
+ mVideoCodecStat->GetEncoderStats(framerateMean, framerateStdDev,
+ bitrateMean, bitrateStdDev,
+ droppedFrames);
+
+ // See if we need to adjust bandwidth.
+ // Avoid changing bandwidth constantly; use hysteresis.
+
+ // Note: mLastFramerate is a relaxed Atomic because we're setting it here, and
+ // reading it on whatever thread calls DeliverFrame/SendVideoFrame. Alternately
+ // we could use a lock. Note that we don't change it often, and read it once per frame.
+ // We scale by *10 because mozilla::Atomic<> doesn't do 'double' or 'float'.
+ double framerate = mLastFramerateTenths/10.0; // fetch once
+ if (std::abs(*framerateMean - framerate)/framerate > 0.1 &&
+ *framerateMean >= 0.5) {
+ // unchanged resolution, but adjust bandwidth limits to match camera fps
+ CSFLogDebug(logTag, "Encoder frame rate changed from %f to %f",
+ (mLastFramerateTenths/10.0), *framerateMean);
+ MutexAutoLock lock(mCodecMutex);
+ mLastFramerateTenths = *framerateMean * 10;
+ SelectSendResolution(mSendingWidth, mSendingHeight, nullptr);
+ }
+ return true;
+}
+
+bool WebrtcVideoConduit::GetVideoDecoderStats(double* framerateMean,
+ double* framerateStdDev,
+ double* bitrateMean,
+ double* bitrateStdDev,
+ uint32_t* discardedPackets)
+{
+ if (!mEngineReceiving) {
+ return false;
+ }
+ MOZ_ASSERT(mVideoCodecStat);
+ mVideoCodecStat->GetDecoderStats(framerateMean, framerateStdDev,
+ bitrateMean, bitrateStdDev,
+ discardedPackets);
+ return true;
+}
+
+bool WebrtcVideoConduit::GetAVStats(int32_t* jitterBufferDelayMs,
+ int32_t* playoutBufferDelayMs,
+ int32_t* avSyncOffsetMs) {
+ return false;
+}
+
+bool WebrtcVideoConduit::GetRTPStats(unsigned int* jitterMs,
+ unsigned int* cumulativeLost) {
+ unsigned short fractionLost;
+ unsigned extendedMax;
+ int64_t rttMs;
+ // GetReceivedRTCPStatistics is a poorly named GetRTPStatistics variant
+ return !mPtrRTP->GetReceivedRTCPStatistics(mChannel, fractionLost,
+ *cumulativeLost,
+ extendedMax,
+ *jitterMs,
+ rttMs);
+}
+
+bool WebrtcVideoConduit::GetRTCPReceiverReport(DOMHighResTimeStamp* timestamp,
+ uint32_t* jitterMs,
+ uint32_t* packetsReceived,
+ uint64_t* bytesReceived,
+ uint32_t* cumulativeLost,
+ int32_t* rttMs) {
+ uint32_t ntpHigh, ntpLow;
+ uint16_t fractionLost;
+ bool result = !mPtrRTP->GetRemoteRTCPReceiverInfo(mChannel, ntpHigh, ntpLow,
+ *packetsReceived,
+ *bytesReceived,
+ jitterMs,
+ &fractionLost,
+ cumulativeLost,
+ rttMs);
+ if (result) {
+ *timestamp = NTPtoDOMHighResTimeStamp(ntpHigh, ntpLow);
+ }
+ return result;
+}
+
+bool WebrtcVideoConduit::GetRTCPSenderReport(DOMHighResTimeStamp* timestamp,
+ unsigned int* packetsSent,
+ uint64_t* bytesSent) {
+ struct webrtc::SenderInfo senderInfo;
+ bool result = !mPtrRTP->GetRemoteRTCPSenderInfo(mChannel, &senderInfo);
+ if (result) {
+ *timestamp = NTPtoDOMHighResTimeStamp(senderInfo.NTP_timestamp_high,
+ senderInfo.NTP_timestamp_low);
+ *packetsSent = senderInfo.sender_packet_count;
+ *bytesSent = senderInfo.sender_octet_count;
+ }
+ return result;
+}
+
+MediaConduitErrorCode
+WebrtcVideoConduit::InitMain()
+{
+#if defined(MOZILLA_INTERNAL_API)
+ // already know we must be on MainThread barring unit test weirdness
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv);
+ if (!NS_WARN_IF(NS_FAILED(rv)))
+ {
+ nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);
+
+ if (branch)
+ {
+ int32_t temp;
+ Unused << NS_WARN_IF(NS_FAILED(branch->GetBoolPref("media.video.test_latency", &mVideoLatencyTestEnable)));
+ if (!NS_WARN_IF(NS_FAILED(branch->GetIntPref("media.peerconnection.video.min_bitrate", &temp))))
+ {
+ if (temp >= 0) {
+ mMinBitrate = temp;
+ }
+ }
+ if (!NS_WARN_IF(NS_FAILED(branch->GetIntPref("media.peerconnection.video.start_bitrate", &temp))))
+ {
+ if (temp >= 0) {
+ mStartBitrate = temp;
+ }
+ }
+ if (!NS_WARN_IF(NS_FAILED(branch->GetIntPref("media.peerconnection.video.max_bitrate", &temp))))
+ {
+ if (temp >= 0) {
+ mMaxBitrate = temp;
+ }
+ }
+ if (mMinBitrate != 0 && mMinBitrate < webrtc::kViEMinCodecBitrate) {
+ mMinBitrate = webrtc::kViEMinCodecBitrate;
+ }
+ if (mStartBitrate < mMinBitrate) {
+ mStartBitrate = mMinBitrate;
+ }
+ if (mStartBitrate > mMaxBitrate) {
+ mStartBitrate = mMaxBitrate;
+ }
+ if (!NS_WARN_IF(NS_FAILED(branch->GetIntPref("media.peerconnection.video.min_bitrate_estimate", &temp))))
+ {
+ if (temp >= 0) {
+ mMinBitrateEstimate = temp;
+ }
+ }
+ bool use_loadmanager = false;
+ if (!NS_WARN_IF(NS_FAILED(branch->GetBoolPref("media.navigator.load_adapt", &use_loadmanager))))
+ {
+ if (use_loadmanager) {
+ mLoadManager = LoadManagerBuild();
+ }
+ }
+ }
+ }
+
+#ifdef MOZ_WIDGET_ANDROID
+ // get the JVM
+ JavaVM *jvm = jsjni_GetVM();
+
+ if (webrtc::VideoEngine::SetAndroidObjects(jvm) != 0) {
+ CSFLogError(logTag, "%s: could not set Android objects", __FUNCTION__);
+ return kMediaConduitSessionNotInited;
+ }
+#endif
+#endif
+ return kMediaConduitNoError;
+}
+
+/**
+ * Performs initialization of the MANDATORY components of the Video Engine
+ */
+MediaConduitErrorCode
+WebrtcVideoConduit::Init()
+{
+ CSFLogDebug(logTag, "%s this=%p", __FUNCTION__, this);
+ MediaConduitErrorCode result;
+ // Run code that must run on MainThread first
+ MOZ_ASSERT(NS_IsMainThread());
+ result = InitMain();
+ if (result != kMediaConduitNoError) {
+ return result;
+ }
+
+ // Per WebRTC APIs below function calls return nullptr on failure
+ mVideoEngine = webrtc::VideoEngine::Create();
+ if(!mVideoEngine)
+ {
+ CSFLogError(logTag, "%s Unable to create video engine ", __FUNCTION__);
+ return kMediaConduitSessionNotInited;
+ }
+
+ if( !(mPtrViEBase = ViEBase::GetInterface(mVideoEngine)))
+ {
+ CSFLogError(logTag, "%s Unable to get video base interface ", __FUNCTION__);
+ return kMediaConduitSessionNotInited;
+ }
+
+ if( !(mPtrViECapture = ViECapture::GetInterface(mVideoEngine)))
+ {
+ CSFLogError(logTag, "%s Unable to get video capture interface", __FUNCTION__);
+ return kMediaConduitSessionNotInited;
+ }
+
+ if( !(mPtrViECodec = ViECodec::GetInterface(mVideoEngine)))
+ {
+ CSFLogError(logTag, "%s Unable to get video codec interface ", __FUNCTION__);
+ return kMediaConduitSessionNotInited;
+ }
+
+ if( !(mPtrViENetwork = ViENetwork::GetInterface(mVideoEngine)))
+ {
+ CSFLogError(logTag, "%s Unable to get video network interface ", __FUNCTION__);
+ return kMediaConduitSessionNotInited;
+ }
+
+ if( !(mPtrViERender = ViERender::GetInterface(mVideoEngine)))
+ {
+ CSFLogError(logTag, "%s Unable to get video render interface ", __FUNCTION__);
+ return kMediaConduitSessionNotInited;
+ }
+
+ mPtrExtCodec = webrtc::ViEExternalCodec::GetInterface(mVideoEngine);
+ if (!mPtrExtCodec) {
+ CSFLogError(logTag, "%s Unable to get external codec interface: %d ",
+ __FUNCTION__,mPtrViEBase->LastError());
+ return kMediaConduitSessionNotInited;
+ }
+
+ if( !(mPtrRTP = webrtc::ViERTP_RTCP::GetInterface(mVideoEngine)))
+ {
+ CSFLogError(logTag, "%s Unable to get video RTCP interface ", __FUNCTION__);
+ return kMediaConduitSessionNotInited;
+ }
+
+ if ( !(mPtrExtCodec = webrtc::ViEExternalCodec::GetInterface(mVideoEngine)))
+ {
+ CSFLogError(logTag, "%s Unable to get external codec interface %d ",
+ __FUNCTION__, mPtrViEBase->LastError());
+ return kMediaConduitSessionNotInited;
+ }
+
+ CSFLogDebug(logTag, "%s Engine Created: Init'ng the interfaces ",__FUNCTION__);
+
+ if(mPtrViEBase->Init() == -1)
+ {
+ CSFLogError(logTag, " %s Video Engine Init Failed %d ",__FUNCTION__,
+ mPtrViEBase->LastError());
+ return kMediaConduitSessionNotInited;
+ }
+
+ if(mPtrViEBase->CreateChannel(mChannel) == -1)
+ {
+ CSFLogError(logTag, " %s Channel creation Failed %d ",__FUNCTION__,
+ mPtrViEBase->LastError());
+ return kMediaConduitChannelError;
+ }
+
+ if(mPtrViENetwork->RegisterSendTransport(mChannel, *this) == -1)
+ {
+ CSFLogError(logTag, "%s ViENetwork Failed %d ", __FUNCTION__,
+ mPtrViEBase->LastError());
+ return kMediaConduitTransportRegistrationFail;
+ }
+
+ if(mPtrViECapture->AllocateExternalCaptureDevice(mCapId,
+ mPtrExtCapture) == -1)
+ {
+ CSFLogError(logTag, "%s Unable to Allocate capture module: %d ",
+ __FUNCTION__, mPtrViEBase->LastError());
+ return kMediaConduitCaptureError;
+ }
+
+ if(mPtrViECapture->ConnectCaptureDevice(mCapId,mChannel) == -1)
+ {
+ CSFLogError(logTag, "%s Unable to Connect capture module: %d ",
+ __FUNCTION__,mPtrViEBase->LastError());
+ return kMediaConduitCaptureError;
+ }
+ // Set up some parameters, per juberti. Set MTU.
+ if(mPtrViENetwork->SetMTU(mChannel, 1200) != 0)
+ {
+ CSFLogError(logTag, "%s MTU Failed %d ", __FUNCTION__,
+ mPtrViEBase->LastError());
+ return kMediaConduitMTUError;
+ }
+ // Turn on RTCP and loss feedback reporting.
+ if(mPtrRTP->SetRTCPStatus(mChannel, webrtc::kRtcpCompound_RFC4585) != 0)
+ {
+ CSFLogError(logTag, "%s RTCPStatus Failed %d ", __FUNCTION__,
+ mPtrViEBase->LastError());
+ return kMediaConduitRTCPStatusError;
+ }
+
+ if (mPtrViERender->AddRenderer(mChannel,
+ webrtc::kVideoI420,
+ (webrtc::ExternalRenderer*) this) == -1) {
+ CSFLogError(logTag, "%s Failed to added external renderer ", __FUNCTION__);
+ return kMediaConduitInvalidRenderer;
+ }
+
+ if (mLoadManager) {
+ mPtrViEBase->RegisterCpuOveruseObserver(mChannel, mLoadManager);
+ mPtrViEBase->SetLoadManager(mLoadManager);
+ }
+
+ CSFLogError(logTag, "%s Initialization Done", __FUNCTION__);
+ return kMediaConduitNoError;
+}
+
+void
+WebrtcVideoConduit::Destroy()
+{
+ // The first one of a pair to be deleted shuts down media for both
+ //Deal with External Capturer
+ if(mPtrViECapture)
+ {
+ mPtrViECapture->DisconnectCaptureDevice(mCapId);
+ mPtrViECapture->ReleaseCaptureDevice(mCapId);
+ mPtrExtCapture = nullptr;
+ }
+
+ if (mPtrExtCodec) {
+ mPtrExtCodec->Release();
+ mPtrExtCodec = NULL;
+ }
+
+ //Deal with External Renderer
+ if(mPtrViERender)
+ {
+ if(mRenderer) {
+ mPtrViERender->StopRender(mChannel);
+ }
+ mPtrViERender->RemoveRenderer(mChannel);
+ }
+
+ //Deal with the transport
+ if(mPtrViENetwork)
+ {
+ mPtrViENetwork->DeregisterSendTransport(mChannel);
+ }
+
+ if(mPtrViEBase)
+ {
+ mPtrViEBase->StopSend(mChannel);
+ mPtrViEBase->StopReceive(mChannel);
+ mPtrViEBase->DeleteChannel(mChannel);
+ }
+
+ // mVideoCodecStat has a back-ptr to mPtrViECodec that must be released first
+ if (mVideoCodecStat) {
+ mVideoCodecStat->EndOfCallStats();
+ }
+ mVideoCodecStat = nullptr;
+ // We can't delete the VideoEngine until all these are released!
+ // And we can't use a Scoped ptr, since the order is arbitrary
+ mPtrViEBase = nullptr;
+ mPtrViECapture = nullptr;
+ mPtrViECodec = nullptr;
+ mPtrViENetwork = nullptr;
+ mPtrViERender = nullptr;
+ mPtrRTP = nullptr;
+ mPtrExtCodec = nullptr;
+
+ // only one opener can call Delete. Have it be the last to close.
+ if(mVideoEngine)
+ {
+ webrtc::VideoEngine::Delete(mVideoEngine);
+ }
+}
+
+void
+WebrtcVideoConduit::SyncTo(WebrtcAudioConduit *aConduit)
+{
+ CSFLogDebug(logTag, "%s Synced to %p", __FUNCTION__, aConduit);
+
+ // SyncTo(value) syncs to the AudioConduit, and if already synced replaces
+ // the current sync target. SyncTo(nullptr) cancels any existing sync and
+ // releases the strong ref to AudioConduit.
+ if (aConduit) {
+ mPtrViEBase->SetVoiceEngine(aConduit->GetVoiceEngine());
+ mPtrViEBase->ConnectAudioChannel(mChannel, aConduit->GetChannel());
+ // NOTE: this means the VideoConduit will keep the AudioConduit alive!
+ } else {
+ mPtrViEBase->DisconnectAudioChannel(mChannel);
+ mPtrViEBase->SetVoiceEngine(nullptr);
+ }
+
+ mSyncedTo = aConduit;
+}
+
+MediaConduitErrorCode
+WebrtcVideoConduit::AttachRenderer(RefPtr<VideoRenderer> aVideoRenderer)
+{
+ CSFLogDebug(logTag, "%s ", __FUNCTION__);
+
+ //null renderer
+ if(!aVideoRenderer)
+ {
+ CSFLogError(logTag, "%s NULL Renderer", __FUNCTION__);
+ MOZ_ASSERT(false);
+ return kMediaConduitInvalidRenderer;
+ }
+
+ // This function is called only from main, so we only need to protect against
+ // modifying mRenderer while any webrtc.org code is trying to use it.
+ bool wasRendering;
+ {
+ ReentrantMonitorAutoEnter enter(mTransportMonitor);
+ wasRendering = !!mRenderer;
+ mRenderer = aVideoRenderer;
+ // Make sure the renderer knows the resolution
+ mRenderer->FrameSizeChange(mReceivingWidth,
+ mReceivingHeight,
+ mNumReceivingStreams);
+ }
+
+ if (!wasRendering) {
+ if(mPtrViERender->StartRender(mChannel) == -1)
+ {
+ CSFLogError(logTag, "%s Starting the Renderer Failed %d ", __FUNCTION__,
+ mPtrViEBase->LastError());
+ ReentrantMonitorAutoEnter enter(mTransportMonitor);
+ mRenderer = nullptr;
+ return kMediaConduitRendererFail;
+ }
+ }
+
+ return kMediaConduitNoError;
+}
+
+void
+WebrtcVideoConduit::DetachRenderer()
+{
+ {
+ ReentrantMonitorAutoEnter enter(mTransportMonitor);
+ if(mRenderer)
+ {
+ mRenderer = nullptr;
+ }
+ }
+
+ mPtrViERender->StopRender(mChannel);
+}
+
+MediaConduitErrorCode
+WebrtcVideoConduit::SetTransmitterTransport(RefPtr<TransportInterface> aTransport)
+{
+ CSFLogDebug(logTag, "%s ", __FUNCTION__);
+
+ ReentrantMonitorAutoEnter enter(mTransportMonitor);
+ // set the transport
+ mTransmitterTransport = aTransport;
+ return kMediaConduitNoError;
+}
+
+MediaConduitErrorCode
+WebrtcVideoConduit::SetReceiverTransport(RefPtr<TransportInterface> aTransport)
+{
+ CSFLogDebug(logTag, "%s ", __FUNCTION__);
+
+ ReentrantMonitorAutoEnter enter(mTransportMonitor);
+ // set the transport
+ mReceiverTransport = aTransport;
+ return kMediaConduitNoError;
+}
+MediaConduitErrorCode
+WebrtcVideoConduit::ConfigureCodecMode(webrtc::VideoCodecMode mode)
+{
+ CSFLogDebug(logTag, "%s ", __FUNCTION__);
+ mCodecMode = mode;
+ return kMediaConduitNoError;
+}
+/**
+ * Note: Setting the send-codec on the Video Engine will restart the encoder,
+ * sets up new SSRC and reset RTP_RTCP module with the new codec setting.
+ *
+ * Note: this is called from MainThread, and the codec settings are read on
+ * videoframe delivery threads (i.e in SendVideoFrame(). With
+ * renegotiation/reconfiguration, this now needs a lock! Alternatively
+ * changes could be queued until the next frame is delivered using an
+ * Atomic pointer and swaps.
+ */
+MediaConduitErrorCode
+WebrtcVideoConduit::ConfigureSendMediaCodec(const VideoCodecConfig* codecConfig)
+{
+ CSFLogDebug(logTag, "%s for %s", __FUNCTION__, codecConfig ? codecConfig->mName.c_str() : "<null>");
+ bool codecFound = false;
+ MediaConduitErrorCode condError = kMediaConduitNoError;
+ int error = 0; //webrtc engine errors
+ webrtc::VideoCodec video_codec;
+ std::string payloadName;
+
+ memset(&video_codec, 0, sizeof(video_codec));
+
+ {
+ //validate basic params
+ if((condError = ValidateCodecConfig(codecConfig,true)) != kMediaConduitNoError)
+ {
+ return condError;
+ }
+ }
+
+ condError = StopTransmitting();
+ if (condError != kMediaConduitNoError) {
+ return condError;
+ }
+
+ if (mRtpStreamIdEnabled) {
+ video_codec.ridId = mRtpStreamIdExtId;
+ }
+ if (mExternalSendCodec &&
+ codecConfig->mType == mExternalSendCodec->mType) {
+ CSFLogError(logTag, "%s Configuring External H264 Send Codec", __FUNCTION__);
+
+ // width/height will be overridden on the first frame
+ video_codec.width = 320;
+ video_codec.height = 240;
+#ifdef MOZ_WEBRTC_OMX
+ if (codecConfig->mType == webrtc::kVideoCodecH264) {
+ video_codec.resolution_divisor = 16;
+ } else {
+ video_codec.resolution_divisor = 1; // We could try using it to handle odd resolutions
+ }
+#else
+ video_codec.resolution_divisor = 1; // We could try using it to handle odd resolutions
+#endif
+ video_codec.qpMax = 56;
+ video_codec.numberOfSimulcastStreams = 1;
+ video_codec.simulcastStream[0].jsScaleDownBy =
+ codecConfig->mEncodingConstraints.scaleDownBy;
+ video_codec.mode = mCodecMode;
+
+ codecFound = true;
+ } else {
+ // we should be good here to set the new codec.
+ for(int idx=0; idx < mPtrViECodec->NumberOfCodecs(); idx++)
+ {
+ if(0 == mPtrViECodec->GetCodec(idx, video_codec))
+ {
+ payloadName = video_codec.plName;
+ if(codecConfig->mName.compare(payloadName) == 0)
+ {
+ // Note: side-effect of this is that video_codec is filled in
+ // by GetCodec()
+ codecFound = true;
+ break;
+ }
+ }
+ }//for
+ }
+
+ if(codecFound == false)
+ {
+ CSFLogError(logTag, "%s Codec Mismatch ", __FUNCTION__);
+ return kMediaConduitInvalidSendCodec;
+ }
+ // Note: only for overriding parameters from GetCodec()!
+ CodecConfigToWebRTCCodec(codecConfig, video_codec);
+ if (mSendingWidth != 0) {
+ // We're already in a call and are reconfiguring (perhaps due to
+ // ReplaceTrack). Set to match the last frame we sent.
+
+ // We could also set mLastWidth to 0, to force immediate reconfig -
+ // more expensive, but perhaps less risk of missing something. Really
+ // on ReplaceTrack we should just call ConfigureCodecMode(), and if the
+ // mode changed, we re-configure.
+ // Do this after CodecConfigToWebRTCCodec() to avoid messing up simulcast
+ video_codec.width = mSendingWidth;
+ video_codec.height = mSendingHeight;
+ video_codec.maxFramerate = mSendingFramerate;
+ } else {
+ mSendingWidth = 0;
+ mSendingHeight = 0;
+ mSendingFramerate = video_codec.maxFramerate;
+ }
+
+ video_codec.mode = mCodecMode;
+
+ if(mPtrViECodec->SetSendCodec(mChannel, video_codec) == -1)
+ {
+ error = mPtrViEBase->LastError();
+ if(error == kViECodecInvalidCodec)
+ {
+ CSFLogError(logTag, "%s Invalid Send Codec", __FUNCTION__);
+ return kMediaConduitInvalidSendCodec;
+ }
+ CSFLogError(logTag, "%s SetSendCodec Failed %d ", __FUNCTION__,
+ mPtrViEBase->LastError());
+ return kMediaConduitUnknownError;
+ }
+
+ if (mMinBitrateEstimate != 0) {
+ mPtrViENetwork->SetBitrateConfig(mChannel,
+ mMinBitrateEstimate,
+ std::max(video_codec.startBitrate,
+ mMinBitrateEstimate),
+ std::max(video_codec.maxBitrate,
+ mMinBitrateEstimate));
+ }
+
+ if (!mVideoCodecStat) {
+ mVideoCodecStat = new VideoCodecStatistics(mChannel, mPtrViECodec);
+ }
+ mVideoCodecStat->Register(true);
+
+ // See Bug 1297058, enabling FEC when NACK is set on H.264 is problematic
+ bool use_fec = codecConfig->RtcpFbFECIsSet();
+ if ((mExternalSendCodec && codecConfig->mType == mExternalSendCodec->mType)
+ || codecConfig->mType == webrtc::kVideoCodecH264) {
+ if(codecConfig->RtcpFbNackIsSet("")) {
+ use_fec = false;
+ }
+ }
+
+ if (use_fec)
+ {
+ uint8_t payload_type_red = INVALID_RTP_PAYLOAD;
+ uint8_t payload_type_ulpfec = INVALID_RTP_PAYLOAD;
+ if (!DetermineREDAndULPFECPayloadTypes(payload_type_red, payload_type_ulpfec)) {
+ CSFLogError(logTag, "%s Unable to set FEC status: could not determine"
+ "payload type: red %u ulpfec %u",
+ __FUNCTION__, payload_type_red, payload_type_ulpfec);
+ return kMediaConduitFECStatusError;
+ }
+
+ if(codecConfig->RtcpFbNackIsSet("")) {
+ CSFLogDebug(logTag, "Enabling NACK/FEC (send) for video stream\n");
+ if (mPtrRTP->SetHybridNACKFECStatus(mChannel, true,
+ payload_type_red,
+ payload_type_ulpfec) != 0) {
+ CSFLogError(logTag, "%s SetHybridNACKFECStatus Failed %d ",
+ __FUNCTION__, mPtrViEBase->LastError());
+ return kMediaConduitHybridNACKFECStatusError;
+ }
+ } else {
+ CSFLogDebug(logTag, "Enabling FEC (send) for video stream\n");
+ if (mPtrRTP->SetFECStatus(mChannel, true,
+ payload_type_red, payload_type_ulpfec) != 0)
+ {
+ CSFLogError(logTag, "%s SetFECStatus Failed %d ", __FUNCTION__,
+ mPtrViEBase->LastError());
+ return kMediaConduitFECStatusError;
+ }
+ }
+ } else if(codecConfig->RtcpFbNackIsSet("")) {
+ CSFLogDebug(logTag, "Enabling NACK (send) for video stream\n");
+ if (mPtrRTP->SetNACKStatus(mChannel, true) != 0)
+ {
+ CSFLogError(logTag, "%s NACKStatus Failed %d ", __FUNCTION__,
+ mPtrViEBase->LastError());
+ return kMediaConduitNACKStatusError;
+ }
+ }
+
+ {
+ MutexAutoLock lock(mCodecMutex);
+
+ //Copy the applied config for future reference.
+ mCurSendCodecConfig = new VideoCodecConfig(*codecConfig);
+ }
+
+ bool remb_requested = codecConfig->RtcpFbRembIsSet();
+ mPtrRTP->SetRembStatus(mChannel, true, remb_requested);
+
+ return kMediaConduitNoError;
+}
+
+MediaConduitErrorCode
+WebrtcVideoConduit::ConfigureRecvMediaCodecs(
+ const std::vector<VideoCodecConfig* >& codecConfigList)
+{
+ CSFLogDebug(logTag, "%s ", __FUNCTION__);
+ MediaConduitErrorCode condError = kMediaConduitNoError;
+ bool success = false;
+ std::string payloadName;
+
+ condError = StopReceiving();
+ if (condError != kMediaConduitNoError) {
+ return condError;
+ }
+
+ if(codecConfigList.empty())
+ {
+ CSFLogError(logTag, "%s Zero number of codecs to configure", __FUNCTION__);
+ return kMediaConduitMalformedArgument;
+ }
+
+ webrtc::ViEKeyFrameRequestMethod kf_request = webrtc::kViEKeyFrameRequestNone;
+ bool use_nack_basic = false;
+ bool use_tmmbr = false;
+ bool use_remb = false;
+ bool use_fec = false;
+
+ //Try Applying the codecs in the list
+ // we treat as success if atleast one codec was applied and reception was
+ // started successfully.
+ for(std::vector<VideoCodecConfig*>::size_type i=0;i < codecConfigList.size();i++)
+ {
+ //if the codec param is invalid or diplicate, return error
+ if((condError = ValidateCodecConfig(codecConfigList[i],false)) != kMediaConduitNoError)
+ {
+ return condError;
+ }
+
+ // Check for the keyframe request type: PLI is preferred
+ // over FIR, and FIR is preferred over none.
+ if (codecConfigList[i]->RtcpFbNackIsSet("pli"))
+ {
+ kf_request = webrtc::kViEKeyFrameRequestPliRtcp;
+ } else if(kf_request == webrtc::kViEKeyFrameRequestNone &&
+ codecConfigList[i]->RtcpFbCcmIsSet("fir"))
+ {
+ kf_request = webrtc::kViEKeyFrameRequestFirRtcp;
+ }
+
+ // Check whether NACK is requested
+ if(codecConfigList[i]->RtcpFbNackIsSet(""))
+ {
+ use_nack_basic = true;
+ }
+
+ // Check whether TMMBR is requested
+ if (codecConfigList[i]->RtcpFbCcmIsSet("tmmbr")) {
+ use_tmmbr = true;
+ }
+
+ // Check whether REMB is requested
+ if (codecConfigList[i]->RtcpFbRembIsSet()) {
+ use_remb = true;
+ }
+
+ // Check whether FEC is requested
+ if (codecConfigList[i]->RtcpFbFECIsSet()) {
+ use_fec = true;
+ }
+
+ webrtc::VideoCodec video_codec;
+
+ memset(&video_codec, 0, sizeof(webrtc::VideoCodec));
+
+ if (mExternalRecvCodec &&
+ codecConfigList[i]->mType == mExternalRecvCodec->mType) {
+ CSFLogError(logTag, "%s Configuring External H264 Receive Codec", __FUNCTION__);
+
+ // XXX Do we need a separate setting for receive maxbitrate? Is it
+ // different for hardware codecs? For now assume symmetry.
+ CodecConfigToWebRTCCodec(codecConfigList[i], video_codec);
+
+ // values SetReceiveCodec() cares about are name, type, maxbitrate
+ if(mPtrViECodec->SetReceiveCodec(mChannel,video_codec) == -1)
+ {
+ CSFLogError(logTag, "%s Invalid Receive Codec %d ", __FUNCTION__,
+ mPtrViEBase->LastError());
+ } else {
+ CSFLogError(logTag, "%s Successfully Set the codec %s", __FUNCTION__,
+ codecConfigList[i]->mName.c_str());
+ success = true;
+ }
+ } else {
+ //Retrieve pre-populated codec structure for our codec.
+ for(int idx=0; idx < mPtrViECodec->NumberOfCodecs(); idx++)
+ {
+ if(mPtrViECodec->GetCodec(idx, video_codec) == 0)
+ {
+ payloadName = video_codec.plName;
+ if(codecConfigList[i]->mName.compare(payloadName) == 0)
+ {
+ CodecConfigToWebRTCCodec(codecConfigList[i], video_codec);
+ if(mPtrViECodec->SetReceiveCodec(mChannel,video_codec) == -1)
+ {
+ CSFLogError(logTag, "%s Invalid Receive Codec %d ", __FUNCTION__,
+ mPtrViEBase->LastError());
+ } else {
+ CSFLogError(logTag, "%s Successfully Set the codec %s", __FUNCTION__,
+ codecConfigList[i]->mName.c_str());
+ success = true;
+ }
+ break; //we found a match
+ }
+ }
+ }//end for codeclist
+ }
+ }//end for
+
+ if(!success)
+ {
+ CSFLogError(logTag, "%s Setting Receive Codec Failed ", __FUNCTION__);
+ return kMediaConduitInvalidReceiveCodec;
+ }
+
+ if (!mVideoCodecStat) {
+ mVideoCodecStat = new VideoCodecStatistics(mChannel, mPtrViECodec);
+ }
+ mVideoCodecStat->Register(false);
+
+ // XXX Currently, we gather up all of the feedback types that the remote
+ // party indicated it supports for all video codecs and configure the entire
+ // conduit based on those capabilities. This is technically out of spec,
+ // as these values should be configured on a per-codec basis. However,
+ // the video engine only provides this API on a per-conduit basis, so that's
+ // how we have to do it. The approach of considering the remote capablities
+ // for the entire conduit to be a union of all remote codec capabilities
+ // (rather than the more conservative approach of using an intersection)
+ // is made to provide as many feedback mechanisms as are likely to be
+ // processed by the remote party (and should be relatively safe, since the
+ // remote party is required to ignore feedback types that it does not
+ // understand).
+ //
+ // Note that our configuration uses this union of remote capabilites as
+ // input to the configuration. It is not isomorphic to the configuration.
+ // For example, it only makes sense to have one frame request mechanism
+ // active at a time; so, if the remote party indicates more than one
+ // supported mechanism, we're only configuring the one we most prefer.
+ //
+ // See http://code.google.com/p/webrtc/issues/detail?id=2331
+
+ if (kf_request != webrtc::kViEKeyFrameRequestNone)
+ {
+ CSFLogDebug(logTag, "Enabling %s frame requests for video stream\n",
+ (kf_request == webrtc::kViEKeyFrameRequestPliRtcp ?
+ "PLI" : "FIR"));
+ if(mPtrRTP->SetKeyFrameRequestMethod(mChannel, kf_request) != 0)
+ {
+ CSFLogError(logTag, "%s KeyFrameRequest Failed %d ", __FUNCTION__,
+ mPtrViEBase->LastError());
+ return kMediaConduitKeyFrameRequestError;
+ }
+ }
+
+ switch (kf_request) {
+ case webrtc::kViEKeyFrameRequestNone:
+ mFrameRequestMethod = FrameRequestNone;
+ break;
+ case webrtc::kViEKeyFrameRequestPliRtcp:
+ mFrameRequestMethod = FrameRequestPli;
+ break;
+ case webrtc::kViEKeyFrameRequestFirRtcp:
+ mFrameRequestMethod = FrameRequestFir;
+ break;
+ default:
+ MOZ_ASSERT(false);
+ mFrameRequestMethod = FrameRequestUnknown;
+ }
+
+ if (use_fec)
+ {
+ uint8_t payload_type_red = INVALID_RTP_PAYLOAD;
+ uint8_t payload_type_ulpfec = INVALID_RTP_PAYLOAD;
+ if (!DetermineREDAndULPFECPayloadTypes(payload_type_red, payload_type_ulpfec)) {
+ CSFLogError(logTag, "%s Unable to set FEC status: could not determine"
+ "payload type: red %u ulpfec %u",
+ __FUNCTION__, payload_type_red, payload_type_ulpfec);
+ return kMediaConduitFECStatusError;
+ }
+
+ // We also need to call SetReceiveCodec for RED and ULPFEC codecs
+ for(int idx=0; idx < mPtrViECodec->NumberOfCodecs(); idx++) {
+ webrtc::VideoCodec video_codec;
+ if(mPtrViECodec->GetCodec(idx, video_codec) == 0) {
+ payloadName = video_codec.plName;
+ if(video_codec.codecType == webrtc::VideoCodecType::kVideoCodecRED ||
+ video_codec.codecType == webrtc::VideoCodecType::kVideoCodecULPFEC) {
+ if(mPtrViECodec->SetReceiveCodec(mChannel,video_codec) == -1) {
+ CSFLogError(logTag, "%s Invalid Receive Codec %d ", __FUNCTION__,
+ mPtrViEBase->LastError());
+ } else {
+ CSFLogDebug(logTag, "%s Successfully Set the codec %s", __FUNCTION__,
+ video_codec.plName);
+ }
+ }
+ }
+ }
+
+ if (use_nack_basic) {
+ CSFLogDebug(logTag, "Enabling NACK/FEC (recv) for video stream\n");
+ if (mPtrRTP->SetHybridNACKFECStatus(mChannel, true,
+ payload_type_red,
+ payload_type_ulpfec) != 0) {
+ CSFLogError(logTag, "%s SetHybridNACKFECStatus Failed %d ",
+ __FUNCTION__, mPtrViEBase->LastError());
+ return kMediaConduitNACKStatusError;
+ }
+ } else {
+ CSFLogDebug(logTag, "Enabling FEC (recv) for video stream\n");
+ if (mPtrRTP->SetFECStatus(mChannel, true,
+ payload_type_red, payload_type_ulpfec) != 0)
+ {
+ CSFLogError(logTag, "%s SetFECStatus Failed %d ", __FUNCTION__,
+ mPtrViEBase->LastError());
+ return kMediaConduitNACKStatusError;
+ }
+ }
+ } else if(use_nack_basic) {
+ CSFLogDebug(logTag, "Enabling NACK (recv) for video stream\n");
+ if (mPtrRTP->SetNACKStatus(mChannel, true) != 0)
+ {
+ CSFLogError(logTag, "%s NACKStatus Failed %d ", __FUNCTION__,
+ mPtrViEBase->LastError());
+ return kMediaConduitNACKStatusError;
+ }
+ }
+ mUsingNackBasic = use_nack_basic;
+ mUsingFEC = use_fec;
+
+ if (use_tmmbr) {
+ CSFLogDebug(logTag, "Enabling TMMBR for video stream");
+ if (mPtrRTP->SetTMMBRStatus(mChannel, true) != 0) {
+ CSFLogError(logTag, "%s SetTMMBRStatus Failed %d ", __FUNCTION__,
+ mPtrViEBase->LastError());
+ return kMediaConduitTMMBRStatusError;
+ }
+ }
+ mUsingTmmbr = use_tmmbr;
+
+ condError = StartReceiving();
+ if (condError != kMediaConduitNoError) {
+ return condError;
+ }
+
+ // by now we should be successfully started the reception
+ CSFLogDebug(logTag, "REMB enabled for video stream %s",
+ (use_remb ? "yes" : "no"));
+ mPtrRTP->SetRembStatus(mChannel, use_remb, true);
+ return kMediaConduitNoError;
+}
+
+template<typename T>
+T MinIgnoreZero(const T& a, const T& b)
+{
+ return std::min(a? a:b, b? b:a);
+}
+
+struct ResolutionAndBitrateLimits {
+ uint32_t resolution_in_mb;
+ uint16_t min_bitrate;
+ uint16_t start_bitrate;
+ uint16_t max_bitrate;
+};
+
+#define MB_OF(w,h) ((unsigned int)((((w+15)>>4))*((unsigned int)((h+15)>>4))))
+
+// For now, try to set the max rates well above the knee in the curve.
+// Chosen somewhat arbitrarily; it's hard to find good data oriented for
+// realtime interactive/talking-head recording. These rates assume
+// 30fps.
+
+// XXX Populate this based on a pref (which we should consider sorting because
+// people won't assume they need to).
+static ResolutionAndBitrateLimits kResolutionAndBitrateLimits[] = {
+ {MB_OF(1920, 1200), 1500, 2000, 10000}, // >HD (3K, 4K, etc)
+ {MB_OF(1280, 720), 1200, 1500, 5000}, // HD ~1080-1200
+ {MB_OF(800, 480), 600, 800, 2500}, // HD ~720
+ {tl::Max<MB_OF(400, 240), MB_OF(352, 288)>::value, 200, 300, 1300}, // VGA, WVGA
+ {MB_OF(176, 144), 100, 150, 500}, // WQVGA, CIF
+ {0 , 40, 80, 250} // QCIF and below
+};
+
+void
+WebrtcVideoConduit::SelectBitrates(unsigned short width,
+ unsigned short height,
+ unsigned int cap,
+ mozilla::Atomic<int32_t, mozilla::Relaxed>& aLastFramerateTenths,
+ unsigned int& out_min,
+ unsigned int& out_start,
+ unsigned int& out_max)
+{
+ // max bandwidth should be proportional (not linearly!) to resolution, and
+ // proportional (perhaps linearly, or close) to current frame rate.
+ unsigned int fs = MB_OF(width, height);
+
+ for (ResolutionAndBitrateLimits resAndLimits : kResolutionAndBitrateLimits) {
+ if (fs > resAndLimits.resolution_in_mb &&
+ // pick the highest range where at least start rate is within cap
+ // (or if we're at the end of the array).
+ (!cap || resAndLimits.start_bitrate <= cap ||
+ resAndLimits.resolution_in_mb == 0)) {
+ out_min = MinIgnoreZero((unsigned int)resAndLimits.min_bitrate, cap);
+ out_start = MinIgnoreZero((unsigned int)resAndLimits.start_bitrate, cap);
+ out_max = MinIgnoreZero((unsigned int)resAndLimits.max_bitrate, cap);
+ break;
+ }
+ }
+
+ // mLastFramerateTenths is an atomic, and scaled by *10
+ double framerate = std::min((aLastFramerateTenths/10.),60.0);
+ MOZ_ASSERT(framerate > 0);
+ // Now linear reduction/increase based on fps (max 60fps i.e. doubling)
+ if (framerate >= 10) {
+ out_min = out_min * (framerate/30);
+ out_start = out_start * (framerate/30);
+ out_max = std::max((unsigned int)(out_max * (framerate/30)), cap);
+ } else {
+ // At low framerates, don't reduce bandwidth as much - cut slope to 1/2.
+ // Mostly this would be ultra-low-light situations/mobile or screensharing.
+ out_min = out_min * ((10-(framerate/2))/30);
+ out_start = out_start * ((10-(framerate/2))/30);
+ out_max = std::max((unsigned int)(out_max * ((10-(framerate/2))/30)), cap);
+ }
+
+ if (mMinBitrate && mMinBitrate > out_min) {
+ out_min = mMinBitrate;
+ }
+ // If we try to set a minimum bitrate that is too low, ViE will reject it.
+ out_min = std::max((unsigned int) webrtc::kViEMinCodecBitrate,
+ out_min);
+ if (mStartBitrate && mStartBitrate > out_start) {
+ out_start = mStartBitrate;
+ }
+ out_start = std::max(out_start, out_min);
+
+ // Note: mMaxBitrate is the max transport bitrate - it applies to a
+ // single codec encoding, but should also apply to the sum of all
+ // simulcast layers in this encoding!
+ // So sum(layers.maxBitrate) <= mMaxBitrate
+ if (mMaxBitrate && mMaxBitrate > out_max) {
+ out_max = mMaxBitrate;
+ }
+}
+
+static void ConstrainPreservingAspectRatioExact(uint32_t max_fs,
+ unsigned short* width,
+ unsigned short* height)
+{
+ // We could try to pick a better starting divisor, but it won't make any real
+ // performance difference.
+ for (size_t d = 1; d < std::min(*width, *height); ++d) {
+ if ((*width % d) || (*height % d)) {
+ continue; // Not divisible
+ }
+
+ if (((*width) * (*height))/(d*d) <= max_fs) {
+ *width /= d;
+ *height /= d;
+ return;
+ }
+ }
+
+ *width = 0;
+ *height = 0;
+}
+
+static void ConstrainPreservingAspectRatio(uint16_t max_width,
+ uint16_t max_height,
+ unsigned short* width,
+ unsigned short* height)
+{
+ if (((*width) <= max_width) && ((*height) <= max_height)) {
+ return;
+ }
+
+ if ((*width) * max_height > max_width * (*height))
+ {
+ (*height) = max_width * (*height) / (*width);
+ (*width) = max_width;
+ }
+ else
+ {
+ (*width) = max_height * (*width) / (*height);
+ (*height) = max_height;
+ }
+}
+
+// XXX we need to figure out how to feed back changes in preferred capture
+// resolution to the getUserMedia source.
+// Returns boolean if we've submitted an async change (and took ownership
+// of *frame's data)
+bool
+WebrtcVideoConduit::SelectSendResolution(unsigned short width,
+ unsigned short height,
+ webrtc::I420VideoFrame *frame) // may be null
+{
+ mCodecMutex.AssertCurrentThreadOwns();
+ // XXX This will do bandwidth-resolution adaptation as well - bug 877954
+
+ mLastWidth = width;
+ mLastHeight = height;
+ // Enforce constraints
+ if (mCurSendCodecConfig) {
+ uint16_t max_width = mCurSendCodecConfig->mEncodingConstraints.maxWidth;
+ uint16_t max_height = mCurSendCodecConfig->mEncodingConstraints.maxHeight;
+ if (max_width || max_height) {
+ max_width = max_width ? max_width : UINT16_MAX;
+ max_height = max_height ? max_height : UINT16_MAX;
+ ConstrainPreservingAspectRatio(max_width, max_height, &width, &height);
+ }
+
+ // Limit resolution to max-fs while keeping same aspect ratio as the
+ // incoming image.
+ if (mCurSendCodecConfig->mEncodingConstraints.maxFs)
+ {
+ uint32_t max_fs = mCurSendCodecConfig->mEncodingConstraints.maxFs;
+ unsigned int cur_fs, mb_width, mb_height, mb_max;
+
+ // Could we make this simpler by picking the larger of width and height,
+ // calculating a max for just that value based on the scale parameter,
+ // and then let ConstrainPreservingAspectRatio do the rest?
+ mb_width = (width + 15) >> 4;
+ mb_height = (height + 15) >> 4;
+
+ cur_fs = mb_width * mb_height;
+
+ // Limit resolution to max_fs, but don't scale up.
+ if (cur_fs > max_fs)
+ {
+ double scale_ratio;
+
+ scale_ratio = sqrt((double) max_fs / (double) cur_fs);
+
+ mb_width = mb_width * scale_ratio;
+ mb_height = mb_height * scale_ratio;
+
+ // Adjust mb_width and mb_height if they were truncated to zero.
+ if (mb_width == 0) {
+ mb_width = 1;
+ mb_height = std::min(mb_height, max_fs);
+ }
+ if (mb_height == 0) {
+ mb_height = 1;
+ mb_width = std::min(mb_width, max_fs);
+ }
+ }
+
+ // Limit width/height seperately to limit effect of extreme aspect ratios.
+ mb_max = (unsigned) sqrt(8 * (double) max_fs);
+
+ max_width = 16 * std::min(mb_width, mb_max);
+ max_height = 16 * std::min(mb_height, mb_max);
+ ConstrainPreservingAspectRatio(max_width, max_height, &width, &height);
+ }
+ }
+
+
+ // Adapt to getUserMedia resolution changes
+ // check if we need to reconfigure the sending resolution.
+ bool changed = false;
+ if (mSendingWidth != width || mSendingHeight != height)
+ {
+ CSFLogDebug(logTag, "%s: resolution changing to %ux%u (from %ux%u)",
+ __FUNCTION__, width, height, mSendingWidth, mSendingHeight);
+ // This will avoid us continually retrying this operation if it fails.
+ // If the resolution changes, we'll try again. In the meantime, we'll
+ // keep using the old size in the encoder.
+ mSendingWidth = width;
+ mSendingHeight = height;
+ changed = true;
+ }
+
+ // uses mSendingWidth/Height
+ unsigned int framerate = SelectSendFrameRate(mSendingFramerate);
+ if (mSendingFramerate != framerate) {
+ CSFLogDebug(logTag, "%s: framerate changing to %u (from %u)",
+ __FUNCTION__, framerate, mSendingFramerate);
+ mSendingFramerate = framerate;
+ changed = true;
+ }
+
+ if (changed) {
+ // On a resolution change, bounce this to the correct thread to
+ // re-configure (same as used for Init(). Do *not* block the calling
+ // thread since that may be the MSG thread.
+
+ // MUST run on the same thread as Init()/etc
+ if (!NS_IsMainThread()) {
+ // Note: on *initial* config (first frame), best would be to drop
+ // frames until the config is done, then encode the most recent frame
+ // provided and continue from there. We don't do this, but we do drop
+ // all frames while in the process of a reconfig and then encode the
+ // frame that started the reconfig, which is close. There may be
+ // barely perceptible glitch in the video due to the dropped frame(s).
+ mInReconfig = true;
+
+ // We can't pass a UniquePtr<> or unique_ptr<> to a lambda directly
+ webrtc::I420VideoFrame *new_frame = nullptr;
+ if (frame) {
+ new_frame = new webrtc::I420VideoFrame();
+ // the internal buffer pointer is refcounted, so we don't have 2 copies here
+ new_frame->ShallowCopy(*frame);
+ }
+ RefPtr<WebrtcVideoConduit> self(this);
+ RefPtr<Runnable> webrtc_runnable =
+ media::NewRunnableFrom([self, width, height, new_frame]() -> nsresult {
+ UniquePtr<webrtc::I420VideoFrame> local_frame(new_frame); // Simplify cleanup
+
+ MutexAutoLock lock(self->mCodecMutex);
+ return self->ReconfigureSendCodec(width, height, new_frame);
+ });
+ // new_frame now owned by lambda
+ CSFLogDebug(logTag, "%s: proxying lambda to WebRTC thread for reconfig (width %u/%u, height %u/%u",
+ __FUNCTION__, width, mLastWidth, height, mLastHeight);
+ NS_DispatchToMainThread(webrtc_runnable.forget());
+ if (new_frame) {
+ return true; // queued it
+ }
+ } else {
+ // already on the right thread
+ ReconfigureSendCodec(width, height, frame);
+ }
+ }
+ return false;
+}
+
+nsresult
+WebrtcVideoConduit::ReconfigureSendCodec(unsigned short width,
+ unsigned short height,
+ webrtc::I420VideoFrame *frame)
+{
+ mCodecMutex.AssertCurrentThreadOwns();
+
+ // Get current vie codec.
+ webrtc::VideoCodec vie_codec;
+ int32_t err;
+
+ mInReconfig = false;
+ if ((err = mPtrViECodec->GetSendCodec(mChannel, vie_codec)) != 0)
+ {
+ CSFLogError(logTag, "%s: GetSendCodec failed, err %d", __FUNCTION__, err);
+ return NS_ERROR_FAILURE;
+ }
+
+ CSFLogDebug(logTag,
+ "%s: Requesting resolution change to %ux%u (from %ux%u)",
+ __FUNCTION__, width, height, vie_codec.width, vie_codec.height);
+
+ if (mRtpStreamIdEnabled) {
+ vie_codec.ridId = mRtpStreamIdExtId;
+ }
+
+ vie_codec.width = width;
+ vie_codec.height = height;
+ vie_codec.maxFramerate = mSendingFramerate;
+ SelectBitrates(vie_codec.width, vie_codec.height, 0,
+ mLastFramerateTenths,
+ vie_codec.minBitrate,
+ vie_codec.startBitrate,
+ vie_codec.maxBitrate);
+
+ // These are based on lowest-fidelity, because if there is insufficient
+ // bandwidth for all streams, only the lowest fidelity one will be sent.
+ uint32_t minMinBitrate = 0;
+ uint32_t minStartBitrate = 0;
+ // Total for all simulcast streams.
+ uint32_t totalMaxBitrate = 0;
+
+ for (size_t i = vie_codec.numberOfSimulcastStreams; i > 0; --i) {
+ webrtc::SimulcastStream& stream(vie_codec.simulcastStream[i - 1]);
+ stream.width = width;
+ stream.height = height;
+ MOZ_ASSERT(stream.jsScaleDownBy >= 1.0);
+ uint32_t new_width = uint32_t(width / stream.jsScaleDownBy);
+ uint32_t new_height = uint32_t(height / stream.jsScaleDownBy);
+ // TODO: If two layers are similar, only alloc bits to one (Bug 1249859)
+ if (new_width != width || new_height != height) {
+ if (vie_codec.numberOfSimulcastStreams == 1) {
+ // Use less strict scaling in unicast. That way 320x240 / 3 = 106x79.
+ ConstrainPreservingAspectRatio(new_width, new_height,
+ &stream.width, &stream.height);
+ } else {
+ // webrtc.org supposedly won't tolerate simulcast unless every stream
+ // is exactly the same aspect ratio. 320x240 / 3 = 80x60.
+ ConstrainPreservingAspectRatioExact(new_width*new_height,
+ &stream.width, &stream.height);
+ }
+ }
+ // Give each layer default appropriate bandwidth limits based on the
+ // resolution/framerate of that layer
+ SelectBitrates(stream.width, stream.height,
+ MinIgnoreZero(stream.jsMaxBitrate, vie_codec.maxBitrate),
+ mLastFramerateTenths,
+ stream.minBitrate,
+ stream.targetBitrate,
+ stream.maxBitrate);
+
+ // webrtc.org expects the last, highest fidelity, simulcast stream to
+ // always have the same resolution as vie_codec
+ // Also set the least user-constrained of the stream bitrates on vie_codec.
+ if (i == vie_codec.numberOfSimulcastStreams) {
+ vie_codec.width = stream.width;
+ vie_codec.height = stream.height;
+ }
+ minMinBitrate = MinIgnoreZero(stream.minBitrate, minMinBitrate);
+ minStartBitrate = MinIgnoreZero(stream.targetBitrate, minStartBitrate);
+ totalMaxBitrate += stream.maxBitrate;
+ }
+ if (vie_codec.numberOfSimulcastStreams != 0) {
+ vie_codec.minBitrate = std::max(minMinBitrate, vie_codec.minBitrate);
+ vie_codec.maxBitrate = std::min(totalMaxBitrate, vie_codec.maxBitrate);
+ vie_codec.startBitrate = std::max(vie_codec.minBitrate,
+ std::min(minStartBitrate,
+ vie_codec.maxBitrate));
+ }
+ vie_codec.mode = mCodecMode;
+ if ((err = mPtrViECodec->SetSendCodec(mChannel, vie_codec)) != 0)
+ {
+ CSFLogError(logTag, "%s: SetSendCodec(%ux%u) failed, err %d",
+ __FUNCTION__, width, height, err);
+ return NS_ERROR_FAILURE;
+ }
+ if (mMinBitrateEstimate != 0) {
+ mPtrViENetwork->SetBitrateConfig(mChannel,
+ mMinBitrateEstimate,
+ std::max(vie_codec.startBitrate,
+ mMinBitrateEstimate),
+ std::max(vie_codec.maxBitrate,
+ mMinBitrateEstimate));
+ }
+
+ CSFLogDebug(logTag, "%s: Encoder resolution changed to %ux%u @ %ufps, bitrate %u:%u",
+ __FUNCTION__, width, height, mSendingFramerate,
+ vie_codec.minBitrate, vie_codec.maxBitrate);
+ if (frame) {
+ // XXX I really don't like doing this from MainThread...
+ mPtrExtCapture->IncomingFrame(*frame);
+ mVideoCodecStat->SentFrame();
+ CSFLogDebug(logTag, "%s Inserted a frame from reconfig lambda", __FUNCTION__);
+ }
+ return NS_OK;
+}
+
+// Invoked under lock of mCodecMutex!
+unsigned int
+WebrtcVideoConduit::SelectSendFrameRate(unsigned int framerate) const
+{
+ mCodecMutex.AssertCurrentThreadOwns();
+ unsigned int new_framerate = framerate;
+
+ // Limit frame rate based on max-mbps
+ if (mCurSendCodecConfig && mCurSendCodecConfig->mEncodingConstraints.maxMbps)
+ {
+ unsigned int cur_fs, mb_width, mb_height, max_fps;
+
+ mb_width = (mSendingWidth + 15) >> 4;
+ mb_height = (mSendingHeight + 15) >> 4;
+
+ cur_fs = mb_width * mb_height;
+ if (cur_fs > 0) { // in case no frames have been sent
+ max_fps = mCurSendCodecConfig->mEncodingConstraints.maxMbps/cur_fs;
+ if (max_fps < mSendingFramerate) {
+ new_framerate = max_fps;
+ }
+
+ if (mCurSendCodecConfig->mEncodingConstraints.maxFps != 0 &&
+ mCurSendCodecConfig->mEncodingConstraints.maxFps < mSendingFramerate) {
+ new_framerate = mCurSendCodecConfig->mEncodingConstraints.maxFps;
+ }
+ }
+ }
+ return new_framerate;
+}
+
+MediaConduitErrorCode
+WebrtcVideoConduit::SetExternalSendCodec(VideoCodecConfig* config,
+ VideoEncoder* encoder) {
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+ if (!mPtrExtCodec->RegisterExternalSendCodec(mChannel,
+ config->mType,
+ static_cast<WebrtcVideoEncoder*>(encoder),
+ false)) {
+ mExternalSendCodecHandle = encoder;
+ mExternalSendCodec = new VideoCodecConfig(*config);
+ return kMediaConduitNoError;
+ }
+ return kMediaConduitInvalidSendCodec;
+}
+
+MediaConduitErrorCode
+WebrtcVideoConduit::SetExternalRecvCodec(VideoCodecConfig* config,
+ VideoDecoder* decoder) {
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+ if (!mPtrExtCodec->RegisterExternalReceiveCodec(mChannel,
+ config->mType,
+ static_cast<WebrtcVideoDecoder*>(decoder))) {
+ mExternalRecvCodecHandle = decoder;
+ mExternalRecvCodec = new VideoCodecConfig(*config);
+ return kMediaConduitNoError;
+ }
+ return kMediaConduitInvalidReceiveCodec;
+}
+
+MediaConduitErrorCode
+WebrtcVideoConduit::EnableRTPStreamIdExtension(bool enabled, uint8_t id) {
+ mRtpStreamIdEnabled = enabled;
+ mRtpStreamIdExtId = id;
+ return kMediaConduitNoError;
+}
+
+MediaConduitErrorCode
+WebrtcVideoConduit::SendVideoFrame(unsigned char* video_frame,
+ unsigned int video_frame_length,
+ unsigned short width,
+ unsigned short height,
+ VideoType video_type,
+ uint64_t capture_time)
+{
+
+ //check for the parameters sanity
+ if(!video_frame || video_frame_length == 0 ||
+ width == 0 || height == 0)
+ {
+ CSFLogError(logTag, "%s Invalid Parameters ",__FUNCTION__);
+ MOZ_ASSERT(false);
+ return kMediaConduitMalformedArgument;
+ }
+ MOZ_ASSERT(video_type == VideoType::kVideoI420);
+ MOZ_ASSERT(mPtrExtCapture);
+
+ // Transmission should be enabled before we insert any frames.
+ if(!mEngineTransmitting)
+ {
+ CSFLogError(logTag, "%s Engine not transmitting ", __FUNCTION__);
+ return kMediaConduitSessionNotInited;
+ }
+
+ // insert the frame to video engine in I420 format only
+ webrtc::I420VideoFrame i420_frame;
+ i420_frame.CreateFrame(video_frame, width, height, webrtc::kVideoRotation_0);
+ i420_frame.set_timestamp(capture_time);
+ i420_frame.set_render_time_ms(capture_time);
+
+ return SendVideoFrame(i420_frame);
+}
+
+MediaConduitErrorCode
+WebrtcVideoConduit::SendVideoFrame(webrtc::I420VideoFrame& frame)
+{
+ CSFLogDebug(logTag, "%s ", __FUNCTION__);
+ // See if we need to recalculate what we're sending.
+ // Don't compare mSendingWidth/Height, since those may not be the same as the input.
+ {
+ MutexAutoLock lock(mCodecMutex);
+ if (mInReconfig) {
+ // Waiting for it to finish
+ return kMediaConduitNoError;
+ }
+ if (frame.width() != mLastWidth || frame.height() != mLastHeight) {
+ CSFLogDebug(logTag, "%s: call SelectSendResolution with %ux%u",
+ __FUNCTION__, frame.width(), frame.height());
+ if (SelectSendResolution(frame.width(), frame.height(), &frame)) {
+ // SelectSendResolution took ownership of the data in i420_frame.
+ // Submit the frame after reconfig is done
+ return kMediaConduitNoError;
+ }
+ }
+ }
+ mPtrExtCapture->IncomingFrame(frame);
+
+ mVideoCodecStat->SentFrame();
+ CSFLogDebug(logTag, "%s Inserted a frame", __FUNCTION__);
+ return kMediaConduitNoError;
+}
+
+// Transport Layer Callbacks
+MediaConduitErrorCode
+WebrtcVideoConduit::ReceivedRTPPacket(const void *data, int len)
+{
+ CSFLogDebug(logTag, "%s: seq# %u, Channel %d, Len %d ", __FUNCTION__,
+ (uint16_t) ntohs(((uint16_t*) data)[1]), mChannel, len);
+
+ // Media Engine should be receiving already.
+ if(mEngineReceiving)
+ {
+ // let the engine know of a RTP packet to decode
+ // XXX we need to get passed the time the packet was received
+ if(mPtrViENetwork->ReceivedRTPPacket(mChannel, data, len, webrtc::PacketTime()) == -1)
+ {
+ int error = mPtrViEBase->LastError();
+ CSFLogError(logTag, "%s RTP Processing Failed %d ", __FUNCTION__, error);
+ if(error >= kViERtpRtcpInvalidChannelId && error <= kViERtpRtcpRtcpDisabled)
+ {
+ return kMediaConduitRTPProcessingFailed;
+ }
+ return kMediaConduitRTPRTCPModuleError;
+ }
+ } else {
+ CSFLogError(logTag, "Error: %s when not receiving", __FUNCTION__);
+ return kMediaConduitSessionNotInited;
+ }
+
+ return kMediaConduitNoError;
+}
+
+MediaConduitErrorCode
+WebrtcVideoConduit::ReceivedRTCPPacket(const void *data, int len)
+{
+ CSFLogDebug(logTag, " %s Channel %d, Len %d ", __FUNCTION__, mChannel, len);
+
+ //Media Engine should be receiving already
+ if(mPtrViENetwork->ReceivedRTCPPacket(mChannel,data,len) == -1)
+ {
+ int error = mPtrViEBase->LastError();
+ CSFLogError(logTag, "%s RTCP Processing Failed %d", __FUNCTION__, error);
+ if(error >= kViERtpRtcpInvalidChannelId && error <= kViERtpRtcpRtcpDisabled)
+ {
+ return kMediaConduitRTPProcessingFailed;
+ }
+ return kMediaConduitRTPRTCPModuleError;
+ }
+ return kMediaConduitNoError;
+}
+
+MediaConduitErrorCode
+WebrtcVideoConduit::StopTransmitting()
+{
+ if(mEngineTransmitting)
+ {
+ CSFLogDebug(logTag, "%s Engine Already Sending. Attemping to Stop ", __FUNCTION__);
+ if(mPtrViEBase->StopSend(mChannel) == -1)
+ {
+ CSFLogError(logTag, "%s StopSend() Failed %d ",__FUNCTION__,
+ mPtrViEBase->LastError());
+ return kMediaConduitUnknownError;
+ }
+
+ mEngineTransmitting = false;
+ }
+
+ return kMediaConduitNoError;
+}
+
+MediaConduitErrorCode
+WebrtcVideoConduit::StartTransmitting()
+{
+ if (!mEngineTransmitting) {
+ if(mPtrViEBase->StartSend(mChannel) == -1)
+ {
+ CSFLogError(logTag, "%s Start Send Error %d ", __FUNCTION__,
+ mPtrViEBase->LastError());
+ return kMediaConduitUnknownError;
+ }
+
+ mEngineTransmitting = true;
+ }
+
+ return kMediaConduitNoError;
+}
+
+MediaConduitErrorCode
+WebrtcVideoConduit::StopReceiving()
+{
+ NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+ // Are we receiving already? If so, stop receiving and playout
+ // since we can't apply new recv codec when the engine is playing.
+ if(mEngineReceiving)
+ {
+ CSFLogDebug(logTag, "%s Engine Already Receiving . Attemping to Stop ", __FUNCTION__);
+ if(mPtrViEBase->StopReceive(mChannel) == -1)
+ {
+ int error = mPtrViEBase->LastError();
+ if(error == kViEBaseUnknownError)
+ {
+ CSFLogDebug(logTag, "%s StopReceive() Success ", __FUNCTION__);
+ } else {
+ CSFLogError(logTag, "%s StopReceive() Failed %d ", __FUNCTION__,
+ mPtrViEBase->LastError());
+ return kMediaConduitUnknownError;
+ }
+ }
+ mEngineReceiving = false;
+ }
+
+ return kMediaConduitNoError;
+}
+
+MediaConduitErrorCode
+WebrtcVideoConduit::StartReceiving()
+{
+ if (!mEngineReceiving) {
+ CSFLogDebug(logTag, "%s Attemping to start... ", __FUNCTION__);
+ //Start Receive on the video engine
+ if(mPtrViEBase->StartReceive(mChannel) == -1)
+ {
+ int error = mPtrViEBase->LastError();
+ CSFLogError(logTag, "%s Start Receive Error %d ", __FUNCTION__, error);
+
+ return kMediaConduitUnknownError;
+ }
+
+ mEngineReceiving = true;
+ }
+
+ return kMediaConduitNoError;
+}
+
+//WebRTC::RTP Callback Implementation
+// Called on MSG thread
+int WebrtcVideoConduit::SendPacket(int channel, const void* data, size_t len)
+{
+ CSFLogDebug(logTag, "%s : channel %d len %lu", __FUNCTION__, channel, (unsigned long) len);
+
+ ReentrantMonitorAutoEnter enter(mTransportMonitor);
+ if(mTransmitterTransport &&
+ (mTransmitterTransport->SendRtpPacket(data, len) == NS_OK))
+ {
+ CSFLogDebug(logTag, "%s Sent RTP Packet ", __FUNCTION__);
+ return len;
+ } else {
+ CSFLogError(logTag, "%s RTP Packet Send Failed ", __FUNCTION__);
+ return -1;
+ }
+}
+
+// Called from multiple threads including webrtc Process thread
+int WebrtcVideoConduit::SendRTCPPacket(int channel, const void* data, size_t len)
+{
+ CSFLogDebug(logTag, "%s : channel %d , len %lu ", __FUNCTION__, channel, (unsigned long) len);
+
+ // We come here if we have only one pipeline/conduit setup,
+ // such as for unidirectional streams.
+ // We also end up here if we are receiving
+ ReentrantMonitorAutoEnter enter(mTransportMonitor);
+ if(mReceiverTransport &&
+ mReceiverTransport->SendRtcpPacket(data, len) == NS_OK)
+ {
+ // Might be a sender report, might be a receiver report, we don't know.
+ CSFLogDebug(logTag, "%s Sent RTCP Packet ", __FUNCTION__);
+ return len;
+ } else if(mTransmitterTransport &&
+ (mTransmitterTransport->SendRtcpPacket(data, len) == NS_OK)) {
+ CSFLogDebug(logTag, "%s Sent RTCP Packet (sender report) ", __FUNCTION__);
+ return len;
+ } else {
+ CSFLogError(logTag, "%s RTCP Packet Send Failed ", __FUNCTION__);
+ return -1;
+ }
+}
+
+// WebRTC::ExternalMedia Implementation
+int
+WebrtcVideoConduit::FrameSizeChange(unsigned int width,
+ unsigned int height,
+ unsigned int numStreams)
+{
+ CSFLogDebug(logTag, "%s ", __FUNCTION__);
+
+
+ ReentrantMonitorAutoEnter enter(mTransportMonitor);
+ mReceivingWidth = width;
+ mReceivingHeight = height;
+ mNumReceivingStreams = numStreams;
+
+ if(mRenderer)
+ {
+ mRenderer->FrameSizeChange(width, height, numStreams);
+ return 0;
+ }
+
+ CSFLogError(logTag, "%s Renderer is NULL ", __FUNCTION__);
+ return -1;
+}
+
+int
+WebrtcVideoConduit::DeliverFrame(unsigned char* buffer,
+ size_t buffer_size,
+ uint32_t time_stamp,
+ int64_t ntp_time_ms,
+ int64_t render_time,
+ void *handle)
+{
+ return DeliverFrame(buffer, buffer_size, mReceivingWidth, (mReceivingWidth+1)>>1,
+ time_stamp, ntp_time_ms, render_time, handle);
+}
+
+int
+WebrtcVideoConduit::DeliverFrame(unsigned char* buffer,
+ size_t buffer_size,
+ uint32_t y_stride,
+ uint32_t cbcr_stride,
+ uint32_t time_stamp,
+ int64_t ntp_time_ms,
+ int64_t render_time,
+ void *handle)
+{
+ CSFLogDebug(logTag, "%s Buffer Size %lu", __FUNCTION__, (unsigned long) buffer_size);
+
+ ReentrantMonitorAutoEnter enter(mTransportMonitor);
+ if(mRenderer)
+ {
+ layers::Image* img = nullptr;
+ // |handle| should be a webrtc::NativeHandle if available.
+ if (handle) {
+ webrtc::NativeHandle* native_h = static_cast<webrtc::NativeHandle*>(handle);
+ // In the handle, there should be a layers::Image.
+ img = static_cast<layers::Image*>(native_h->GetHandle());
+ }
+
+ if (mVideoLatencyTestEnable && mReceivingWidth && mReceivingHeight) {
+ uint64_t now = PR_Now();
+ uint64_t timestamp = 0;
+ bool ok = YuvStamper::Decode(mReceivingWidth, mReceivingHeight, mReceivingWidth,
+ buffer,
+ reinterpret_cast<unsigned char*>(&timestamp),
+ sizeof(timestamp), 0, 0);
+ if (ok) {
+ VideoLatencyUpdate(now - timestamp);
+ }
+ }
+
+ const ImageHandle img_h(img);
+ mRenderer->RenderVideoFrame(buffer, buffer_size, y_stride, cbcr_stride,
+ time_stamp, render_time, img_h);
+ return 0;
+ }
+
+ CSFLogError(logTag, "%s Renderer is NULL ", __FUNCTION__);
+ return -1;
+}
+
+int
+WebrtcVideoConduit::DeliverI420Frame(const webrtc::I420VideoFrame& webrtc_frame)
+{
+ if (!webrtc_frame.native_handle()) {
+ uint32_t y_stride = webrtc_frame.stride(static_cast<webrtc::PlaneType>(0));
+ return DeliverFrame(const_cast<uint8_t*>(webrtc_frame.buffer(webrtc::kYPlane)),
+ CalcBufferSize(webrtc::kI420, y_stride, webrtc_frame.height()),
+ y_stride,
+ webrtc_frame.stride(static_cast<webrtc::PlaneType>(1)),
+ webrtc_frame.timestamp(),
+ webrtc_frame.ntp_time_ms(),
+ webrtc_frame.render_time_ms(), nullptr);
+ }
+ size_t buffer_size = CalcBufferSize(webrtc::kI420, webrtc_frame.width(), webrtc_frame.height());
+ CSFLogDebug(logTag, "%s Buffer Size %lu", __FUNCTION__, (unsigned long) buffer_size);
+
+ ReentrantMonitorAutoEnter enter(mTransportMonitor);
+ if(mRenderer)
+ {
+ layers::Image* img = nullptr;
+ // |handle| should be a webrtc::NativeHandle if available.
+ webrtc::NativeHandle* native_h = static_cast<webrtc::NativeHandle*>(webrtc_frame.native_handle());
+ if (native_h) {
+ // In the handle, there should be a layers::Image.
+ img = static_cast<layers::Image*>(native_h->GetHandle());
+ }
+
+#if 0
+ //#ifndef MOZ_WEBRTC_OMX
+ // XXX - this may not be possible on GONK with textures!
+ if (mVideoLatencyTestEnable && mReceivingWidth && mReceivingHeight) {
+ uint64_t now = PR_Now();
+ uint64_t timestamp = 0;
+ bool ok = YuvStamper::Decode(mReceivingWidth, mReceivingHeight, mReceivingWidth,
+ buffer,
+ reinterpret_cast<unsigned char*>(&timestamp),
+ sizeof(timestamp), 0, 0);
+ if (ok) {
+ VideoLatencyUpdate(now - timestamp);
+ }
+ }
+#endif
+
+ const ImageHandle img_h(img);
+ mRenderer->RenderVideoFrame(nullptr, buffer_size, webrtc_frame.timestamp(),
+ webrtc_frame.render_time_ms(), img_h);
+ return 0;
+ }
+
+ CSFLogError(logTag, "%s Renderer is NULL ", __FUNCTION__);
+ return -1;
+}
+
+/**
+ * Copy the codec passed into Conduit's database
+ */
+
+void
+WebrtcVideoConduit::CodecConfigToWebRTCCodec(const VideoCodecConfig* codecInfo,
+ webrtc::VideoCodec& cinst)
+{
+ // Note: this assumes cinst is initialized to a base state either by
+ // hand or from a config fetched with GetConfig(); this modifies the config
+ // to match parameters from VideoCodecConfig
+ cinst.plType = codecInfo->mType;
+ if (codecInfo->mName == "H264") {
+ cinst.codecType = webrtc::kVideoCodecH264;
+ PL_strncpyz(cinst.plName, "H264", sizeof(cinst.plName));
+ } else if (codecInfo->mName == "VP8") {
+ cinst.codecType = webrtc::kVideoCodecVP8;
+ PL_strncpyz(cinst.plName, "VP8", sizeof(cinst.plName));
+ } else if (codecInfo->mName == "VP9") {
+ cinst.codecType = webrtc::kVideoCodecVP9;
+ PL_strncpyz(cinst.plName, "VP9", sizeof(cinst.plName));
+ } else if (codecInfo->mName == "I420") {
+ cinst.codecType = webrtc::kVideoCodecI420;
+ PL_strncpyz(cinst.plName, "I420", sizeof(cinst.plName));
+ } else {
+ cinst.codecType = webrtc::kVideoCodecUnknown;
+ PL_strncpyz(cinst.plName, "Unknown", sizeof(cinst.plName));
+ }
+
+ // width/height will be overridden on the first frame; they must be 'sane' for
+ // SetSendCodec()
+ if (codecInfo->mEncodingConstraints.maxFps > 0) {
+ cinst.maxFramerate = codecInfo->mEncodingConstraints.maxFps;
+ } else {
+ cinst.maxFramerate = DEFAULT_VIDEO_MAX_FRAMERATE;
+ }
+
+ // Defaults if rates aren't forced by pref. Typically defaults are
+ // overridden on the first video frame.
+ cinst.minBitrate = mMinBitrate ? mMinBitrate : 200;
+ cinst.startBitrate = mStartBitrate ? mStartBitrate : 300;
+ cinst.targetBitrate = cinst.startBitrate;
+ cinst.maxBitrate = mMaxBitrate ? mMaxBitrate : 2000;
+
+ if (cinst.codecType == webrtc::kVideoCodecH264)
+ {
+#ifdef MOZ_WEBRTC_OMX
+ cinst.resolution_divisor = 16;
+#endif
+ // cinst.codecSpecific.H264.profile = ?
+ cinst.codecSpecific.H264.profile_byte = codecInfo->mProfile;
+ cinst.codecSpecific.H264.constraints = codecInfo->mConstraints;
+ cinst.codecSpecific.H264.level = codecInfo->mLevel;
+ cinst.codecSpecific.H264.packetizationMode = codecInfo->mPacketizationMode;
+ if (codecInfo->mEncodingConstraints.maxBr > 0) {
+ // webrtc.org uses kbps, we use bps
+ cinst.maxBitrate =
+ MinIgnoreZero(cinst.maxBitrate,
+ codecInfo->mEncodingConstraints.maxBr)/1000;
+ }
+ if (codecInfo->mEncodingConstraints.maxMbps > 0) {
+ // Not supported yet!
+ CSFLogError(logTag, "%s H.264 max_mbps not supported yet ", __FUNCTION__);
+ }
+ // XXX parse the encoded SPS/PPS data
+ // paranoia
+ cinst.codecSpecific.H264.spsData = nullptr;
+ cinst.codecSpecific.H264.spsLen = 0;
+ cinst.codecSpecific.H264.ppsData = nullptr;
+ cinst.codecSpecific.H264.ppsLen = 0;
+ }
+ // Init mSimulcastEncodings always since they hold info from setParameters.
+ // TODO(bug 1210175): H264 doesn't support simulcast yet.
+ size_t numberOfSimulcastEncodings = std::min(codecInfo->mSimulcastEncodings.size(), (size_t)webrtc::kMaxSimulcastStreams);
+ for (size_t i = 0; i < numberOfSimulcastEncodings; ++i) {
+ const VideoCodecConfig::SimulcastEncoding& encoding =
+ codecInfo->mSimulcastEncodings[i];
+ // Make sure the constraints on the whole stream are reflected.
+ webrtc::SimulcastStream stream;
+ memset(&stream, 0, sizeof(stream));
+ stream.width = cinst.width;
+ stream.height = cinst.height;
+ stream.numberOfTemporalLayers = 1;
+ stream.maxBitrate = cinst.maxBitrate;
+ stream.targetBitrate = cinst.targetBitrate;
+ stream.minBitrate = cinst.minBitrate;
+ stream.qpMax = cinst.qpMax;
+ strncpy(stream.rid, encoding.rid.c_str(), sizeof(stream.rid)-1);
+ stream.rid[sizeof(stream.rid) - 1] = 0;
+
+ // Apply encoding-specific constraints.
+ stream.width = MinIgnoreZero(
+ stream.width,
+ (unsigned short)encoding.constraints.maxWidth);
+ stream.height = MinIgnoreZero(
+ stream.height,
+ (unsigned short)encoding.constraints.maxHeight);
+
+ // webrtc.org uses kbps, we use bps
+ stream.jsMaxBitrate = encoding.constraints.maxBr/1000;
+ stream.jsScaleDownBy = encoding.constraints.scaleDownBy;
+
+ MOZ_ASSERT(stream.jsScaleDownBy >= 1.0);
+ uint32_t width = stream.width? stream.width : 640;
+ uint32_t height = stream.height? stream.height : 480;
+ uint32_t new_width = uint32_t(width / stream.jsScaleDownBy);
+ uint32_t new_height = uint32_t(height / stream.jsScaleDownBy);
+
+ if (new_width != width || new_height != height) {
+ // Estimate. Overridden on first frame.
+ SelectBitrates(new_width, new_height, stream.jsMaxBitrate,
+ mLastFramerateTenths,
+ stream.minBitrate,
+ stream.targetBitrate,
+ stream.maxBitrate);
+ }
+ // webrtc.org expects simulcast streams to be ordered by increasing
+ // fidelity, our jsep code does the opposite.
+ cinst.simulcastStream[numberOfSimulcastEncodings-i-1] = stream;
+ }
+
+ cinst.numberOfSimulcastStreams = numberOfSimulcastEncodings;
+}
+
+/**
+ * Perform validation on the codecConfig to be applied
+ * Verifies if the codec is already applied.
+ */
+MediaConduitErrorCode
+WebrtcVideoConduit::ValidateCodecConfig(const VideoCodecConfig* codecInfo,
+ bool send)
+{
+ if(!codecInfo)
+ {
+ CSFLogError(logTag, "%s Null CodecConfig ", __FUNCTION__);
+ return kMediaConduitMalformedArgument;
+ }
+
+ if((codecInfo->mName.empty()) ||
+ (codecInfo->mName.length() >= CODEC_PLNAME_SIZE))
+ {
+ CSFLogError(logTag, "%s Invalid Payload Name Length ", __FUNCTION__);
+ return kMediaConduitMalformedArgument;
+ }
+
+ return kMediaConduitNoError;
+}
+
+void
+WebrtcVideoConduit::VideoLatencyUpdate(uint64_t newSample)
+{
+ mVideoLatencyAvg = (sRoundingPadding * newSample + sAlphaNum * mVideoLatencyAvg) / sAlphaDen;
+}
+
+uint64_t
+WebrtcVideoConduit::MozVideoLatencyAvg()
+{
+ return mVideoLatencyAvg / sRoundingPadding;
+}
+
+uint64_t
+WebrtcVideoConduit::CodecPluginID()
+{
+ if (mExternalSendCodecHandle) {
+ return mExternalSendCodecHandle->PluginID();
+ } else if (mExternalRecvCodecHandle) {
+ return mExternalRecvCodecHandle->PluginID();
+ }
+ return 0;
+}
+
+bool
+WebrtcVideoConduit::DetermineREDAndULPFECPayloadTypes(uint8_t &payload_type_red, uint8_t &payload_type_ulpfec)
+{
+ webrtc::VideoCodec video_codec;
+ payload_type_red = INVALID_RTP_PAYLOAD;
+ payload_type_ulpfec = INVALID_RTP_PAYLOAD;
+
+ for(int idx=0; idx < mPtrViECodec->NumberOfCodecs(); idx++)
+ {
+ if(mPtrViECodec->GetCodec(idx, video_codec) == 0)
+ {
+ switch(video_codec.codecType) {
+ case webrtc::VideoCodecType::kVideoCodecRED:
+ payload_type_red = video_codec.plType;
+ break;
+ case webrtc::VideoCodecType::kVideoCodecULPFEC:
+ payload_type_ulpfec = video_codec.plType;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ return payload_type_red != INVALID_RTP_PAYLOAD
+ && payload_type_ulpfec != INVALID_RTP_PAYLOAD;
+}
+
+}// end namespace