michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "CSFLog.h" michael@0: michael@0: #include "base/histogram.h" michael@0: #include "CallControlManager.h" michael@0: #include "CC_Device.h" michael@0: #include "CC_Call.h" michael@0: #include "CC_Observer.h" michael@0: #include "ccapi_call_info.h" michael@0: #include "CC_SIPCCCallInfo.h" michael@0: #include "ccapi_device_info.h" michael@0: #include "CC_SIPCCDeviceInfo.h" michael@0: #include "vcm.h" michael@0: #include "VcmSIPCCBinding.h" michael@0: #include "PeerConnectionImpl.h" michael@0: #include "PeerConnectionCtx.h" michael@0: #include "runnable_utils.h" michael@0: #include "cpr_socket.h" michael@0: #include "debug-psipcc-types.h" michael@0: #include "prcvar.h" michael@0: michael@0: #include "mozilla/Telemetry.h" michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: #include "mozilla/dom/RTCPeerConnectionBinding.h" michael@0: #include "mozilla/Preferences.h" michael@0: #endif michael@0: michael@0: #include "nsIObserverService.h" michael@0: #include "nsIObserver.h" michael@0: #include "mozilla/Services.h" michael@0: #include "StaticPtr.h" michael@0: extern "C" { michael@0: #include "../sipcc/core/common/thread_monitor.h" michael@0: } michael@0: michael@0: static const char* logTag = "PeerConnectionCtx"; michael@0: michael@0: extern "C" { michael@0: extern PRCondVar *ccAppReadyToStartCond; michael@0: extern PRLock *ccAppReadyToStartLock; michael@0: extern char ccAppReadyToStart; michael@0: } michael@0: michael@0: namespace mozilla { michael@0: michael@0: using namespace dom; michael@0: michael@0: // Convert constraints to C structures michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: static void michael@0: Apply(const Optional &aSrc, cc_boolean_constraint_t *aDst, michael@0: bool mandatory = false) { michael@0: if (aSrc.WasPassed() && (mandatory || !aDst->was_passed)) { michael@0: aDst->was_passed = true; michael@0: aDst->value = aSrc.Value(); michael@0: aDst->mandatory = mandatory; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: MediaConstraintsExternal::MediaConstraintsExternal() { michael@0: memset(&mConstraints, 0, sizeof(mConstraints)); michael@0: } michael@0: michael@0: MediaConstraintsExternal::MediaConstraintsExternal( michael@0: const MediaConstraintsInternal &aSrc) { michael@0: cc_media_constraints_t* c = &mConstraints; michael@0: memset(c, 0, sizeof(*c)); michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: Apply(aSrc.mMandatory.mOfferToReceiveAudio, &c->offer_to_receive_audio, true); michael@0: Apply(aSrc.mMandatory.mOfferToReceiveVideo, &c->offer_to_receive_video, true); michael@0: if (!Preferences::GetBool("media.peerconnection.video.enabled", true)) { michael@0: c->offer_to_receive_video.was_passed = true; michael@0: c->offer_to_receive_video.value = false; michael@0: } michael@0: Apply(aSrc.mMandatory.mMozDontOfferDataChannel, &c->moz_dont_offer_datachannel, michael@0: true); michael@0: Apply(aSrc.mMandatory.mMozBundleOnly, &c->moz_bundle_only, true); michael@0: if (aSrc.mOptional.WasPassed()) { michael@0: const Sequence &array = aSrc.mOptional.Value(); michael@0: for (uint32_t i = 0; i < array.Length(); i++) { michael@0: Apply(array[i].mOfferToReceiveAudio, &c->offer_to_receive_audio); michael@0: Apply(array[i].mOfferToReceiveVideo, &c->offer_to_receive_video); michael@0: Apply(array[i].mMozDontOfferDataChannel, &c->moz_dont_offer_datachannel); michael@0: Apply(array[i].mMozBundleOnly, &c->moz_bundle_only); michael@0: } michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: cc_media_constraints_t* michael@0: MediaConstraintsExternal::build() const { michael@0: cc_media_constraints_t* cc = (cc_media_constraints_t*) michael@0: cpr_malloc(sizeof(cc_media_constraints_t)); michael@0: if (cc) { michael@0: *cc = mConstraints; michael@0: } michael@0: return cc; michael@0: } michael@0: michael@0: class PeerConnectionCtxShutdown : public nsIObserver michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: PeerConnectionCtxShutdown() {} michael@0: michael@0: void Init() michael@0: { michael@0: nsCOMPtr observerService = michael@0: services::GetObserverService(); michael@0: if (!observerService) michael@0: return; michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: rv = observerService->AddObserver(this, michael@0: NS_XPCOM_SHUTDOWN_OBSERVER_ID, michael@0: false); michael@0: MOZ_ALWAYS_TRUE(NS_SUCCEEDED(rv)); michael@0: #endif michael@0: (void) rv; michael@0: } michael@0: michael@0: virtual ~PeerConnectionCtxShutdown() michael@0: { michael@0: nsCOMPtr observerService = michael@0: services::GetObserverService(); michael@0: if (observerService) michael@0: observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); michael@0: } michael@0: michael@0: NS_IMETHODIMP Observe(nsISupports* aSubject, const char* aTopic, michael@0: const char16_t* aData) { michael@0: if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { michael@0: CSFLogDebug(logTag, "Shutting down PeerConnectionCtx"); michael@0: sipcc::PeerConnectionCtx::Destroy(); michael@0: michael@0: nsCOMPtr observerService = michael@0: services::GetObserverService(); michael@0: if (!observerService) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsresult rv = observerService->RemoveObserver(this, michael@0: NS_XPCOM_SHUTDOWN_OBSERVER_ID); michael@0: MOZ_ALWAYS_TRUE(NS_SUCCEEDED(rv)); michael@0: michael@0: // Make sure we're not deleted while still inside ::Observe() michael@0: nsRefPtr kungFuDeathGrip(this); michael@0: sipcc::PeerConnectionCtx::gPeerConnectionCtxShutdown = nullptr; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(PeerConnectionCtxShutdown, nsIObserver); michael@0: } michael@0: michael@0: using namespace mozilla; michael@0: namespace sipcc { michael@0: michael@0: PeerConnectionCtx* PeerConnectionCtx::gInstance; michael@0: nsIThread* PeerConnectionCtx::gMainThread; michael@0: StaticRefPtr PeerConnectionCtx::gPeerConnectionCtxShutdown; michael@0: michael@0: // Since we have a pointer to main-thread, help make it safe for lower-level michael@0: // SIPCC threads to use SyncRunnable without deadlocking, by exposing main's michael@0: // dispatcher and waiter functions. See sipcc/core/common/thread_monitor.c. michael@0: michael@0: static void thread_ended_dispatcher(thread_ended_funct func, thread_monitor_id_t id) michael@0: { michael@0: nsresult rv = PeerConnectionCtx::gMainThread->Dispatch(WrapRunnableNM(func, id), michael@0: NS_DISPATCH_NORMAL); michael@0: if (NS_FAILED(rv)) { michael@0: CSFLogError( logTag, "%s(): Could not dispatch to main thread", __FUNCTION__); michael@0: } michael@0: } michael@0: michael@0: static void join_waiter() { michael@0: NS_ProcessPendingEvents(PeerConnectionCtx::gMainThread); michael@0: } michael@0: michael@0: nsresult PeerConnectionCtx::InitializeGlobal(nsIThread *mainThread, michael@0: nsIEventTarget* stsThread) { michael@0: if (!gMainThread) { michael@0: gMainThread = mainThread; michael@0: CSF::VcmSIPCCBinding::setMainThread(gMainThread); michael@0: init_thread_monitor(&thread_ended_dispatcher, &join_waiter); michael@0: } else { michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: MOZ_ASSERT(gMainThread == mainThread); michael@0: #endif michael@0: } michael@0: michael@0: CSF::VcmSIPCCBinding::setSTSThread(stsThread); michael@0: michael@0: nsresult res; michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: // This check fails on the unit tests because they do not michael@0: // have the right thread behavior. michael@0: bool on; michael@0: res = gMainThread->IsOnCurrentThread(&on); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: MOZ_ASSERT(on); michael@0: #endif michael@0: michael@0: if (!gInstance) { michael@0: CSFLogDebug(logTag, "Creating PeerConnectionCtx"); michael@0: PeerConnectionCtx *ctx = new PeerConnectionCtx(); michael@0: michael@0: res = ctx->Initialize(); michael@0: PR_ASSERT(NS_SUCCEEDED(res)); michael@0: if (!NS_SUCCEEDED(res)) michael@0: return res; michael@0: michael@0: gInstance = ctx; michael@0: michael@0: if (!sipcc::PeerConnectionCtx::gPeerConnectionCtxShutdown) { michael@0: sipcc::PeerConnectionCtx::gPeerConnectionCtxShutdown = new PeerConnectionCtxShutdown(); michael@0: sipcc::PeerConnectionCtx::gPeerConnectionCtxShutdown->Init(); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: PeerConnectionCtx* PeerConnectionCtx::GetInstance() { michael@0: MOZ_ASSERT(gInstance); michael@0: return gInstance; michael@0: } michael@0: michael@0: bool PeerConnectionCtx::isActive() { michael@0: return gInstance; michael@0: } michael@0: michael@0: void PeerConnectionCtx::Destroy() { michael@0: CSFLogDebug(logTag, "%s", __FUNCTION__); michael@0: michael@0: if (gInstance) { michael@0: gInstance->Cleanup(); michael@0: delete gInstance; michael@0: gInstance = nullptr; michael@0: } michael@0: } michael@0: michael@0: nsresult PeerConnectionCtx::Initialize() { michael@0: mCCM = CSF::CallControlManager::create(); michael@0: michael@0: NS_ENSURE_TRUE(mCCM.get(), NS_ERROR_FAILURE); michael@0: michael@0: // Add the local audio codecs michael@0: // FIX - Get this list from MediaEngine instead michael@0: int codecMask = 0; michael@0: codecMask |= VCM_CODEC_RESOURCE_G711; michael@0: codecMask |= VCM_CODEC_RESOURCE_OPUS; michael@0: //codecMask |= VCM_CODEC_RESOURCE_LINEAR; michael@0: //codecMask |= VCM_CODEC_RESOURCE_G722; michael@0: //codecMask |= VCM_CODEC_RESOURCE_iLBC; michael@0: //codecMask |= VCM_CODEC_RESOURCE_iSAC; michael@0: mCCM->setAudioCodecs(codecMask); michael@0: michael@0: //Add the local video codecs michael@0: // FIX - Get this list from MediaEngine instead michael@0: // Turning them all on for now michael@0: codecMask = 0; michael@0: // Only adding codecs supported michael@0: //codecMask |= VCM_CODEC_RESOURCE_H263; michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: if (Preferences::GetBool("media.peerconnection.video.h264_enabled")) { michael@0: codecMask |= VCM_CODEC_RESOURCE_H264; michael@0: } michael@0: #endif michael@0: michael@0: codecMask |= VCM_CODEC_RESOURCE_VP8; michael@0: //codecMask |= VCM_CODEC_RESOURCE_I420; michael@0: mCCM->setVideoCodecs(codecMask); michael@0: michael@0: ccAppReadyToStartLock = PR_NewLock(); michael@0: if (!ccAppReadyToStartLock) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: ccAppReadyToStartCond = PR_NewCondVar(ccAppReadyToStartLock); michael@0: if (!ccAppReadyToStartCond) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (!mCCM->startSDPMode()) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: mDevice = mCCM->getActiveDevice(); michael@0: mCCM->addCCObserver(this); michael@0: NS_ENSURE_TRUE(mDevice.get(), NS_ERROR_FAILURE); michael@0: ChangeSipccState(dom::PCImplSipccState::Starting); michael@0: michael@0: // Now that everything is set up, we let the CCApp thread michael@0: // know that it's okay to start processing messages. michael@0: PR_Lock(ccAppReadyToStartLock); michael@0: ccAppReadyToStart = 1; michael@0: PR_NotifyAllCondVar(ccAppReadyToStartCond); michael@0: PR_Unlock(ccAppReadyToStartLock); michael@0: michael@0: mConnectionCounter = 0; michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: Telemetry::GetHistogramById(Telemetry::WEBRTC_CALL_COUNT)->Add(0); michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult PeerConnectionCtx::Cleanup() { michael@0: CSFLogDebug(logTag, "%s", __FUNCTION__); michael@0: michael@0: mCCM->destroy(); michael@0: mCCM->removeCCObserver(this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: CSF::CC_CallPtr PeerConnectionCtx::createCall() { michael@0: return mDevice->createCall(); michael@0: } michael@0: michael@0: void PeerConnectionCtx::onDeviceEvent(ccapi_device_event_e aDeviceEvent, michael@0: CSF::CC_DevicePtr aDevice, michael@0: CSF::CC_DeviceInfoPtr aInfo ) { michael@0: cc_service_state_t state = aInfo->getServiceState(); michael@0: // We are keeping this in a local var to avoid a data race michael@0: // with ChangeSipccState in the debug message and compound if below michael@0: dom::PCImplSipccState currentSipccState = mSipccState; michael@0: michael@0: switch (aDeviceEvent) { michael@0: case CCAPI_DEVICE_EV_STATE: michael@0: CSFLogDebug(logTag, "%s - %d : %d", __FUNCTION__, state, michael@0: static_cast(currentSipccState)); michael@0: michael@0: if (CC_STATE_INS == state) { michael@0: // SIPCC is up michael@0: if (dom::PCImplSipccState::Starting == currentSipccState || michael@0: dom::PCImplSipccState::Idle == currentSipccState) { michael@0: ChangeSipccState(dom::PCImplSipccState::Started); michael@0: } else { michael@0: CSFLogError(logTag, "%s PeerConnection already started", __FUNCTION__); michael@0: } michael@0: } else { michael@0: NS_NOTREACHED("Unsupported Signaling State Transition"); michael@0: } michael@0: break; michael@0: default: michael@0: CSFLogDebug(logTag, "%s: Ignoring event: %s\n",__FUNCTION__, michael@0: device_event_getname(aDeviceEvent)); michael@0: } michael@0: } michael@0: michael@0: static void onCallEvent_m(nsAutoPtr peerconnection, michael@0: ccapi_call_event_e aCallEvent, michael@0: CSF::CC_CallInfoPtr aInfo); michael@0: michael@0: void PeerConnectionCtx::onCallEvent(ccapi_call_event_e aCallEvent, michael@0: CSF::CC_CallPtr aCall, michael@0: CSF::CC_CallInfoPtr aInfo) { michael@0: // This is called on a SIPCC thread. michael@0: // michael@0: // We cannot use SyncRunnable to main thread, as that would deadlock on michael@0: // shutdown. Instead, we dispatch asynchronously. We copy getPeerConnection(), michael@0: // a "weak ref" to the PC, which is safe in shutdown, and CC_CallInfoPtr (an michael@0: // nsRefPtr) is thread-safe and keeps aInfo alive. michael@0: nsAutoPtr pcDuped(new std::string(aCall->getPeerConnection())); michael@0: michael@0: // DISPATCH_NORMAL with duped string michael@0: nsresult rv = gMainThread->Dispatch(WrapRunnableNM(&onCallEvent_m, pcDuped, michael@0: aCallEvent, aInfo), michael@0: NS_DISPATCH_NORMAL); michael@0: if (NS_FAILED(rv)) { michael@0: CSFLogError( logTag, "%s(): Could not dispatch to main thread", __FUNCTION__); michael@0: } michael@0: } michael@0: michael@0: // Demux the call event to the right PeerConnection michael@0: static void onCallEvent_m(nsAutoPtr peerconnection, michael@0: ccapi_call_event_e aCallEvent, michael@0: CSF::CC_CallInfoPtr aInfo) { michael@0: CSFLogDebug(logTag, "onCallEvent()"); michael@0: PeerConnectionWrapper pc(peerconnection->c_str()); michael@0: if (!pc.impl()) // This must be an event on a dead PC. Ignore michael@0: return; michael@0: CSFLogDebug(logTag, "Calling PC"); michael@0: pc.impl()->onCallEvent(OnCallEventArgs(aCallEvent, aInfo)); michael@0: } michael@0: michael@0: } // namespace sipcc