|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 #include "CSFLog.h" |
|
6 |
|
7 #include "base/histogram.h" |
|
8 #include "CallControlManager.h" |
|
9 #include "CC_Device.h" |
|
10 #include "CC_Call.h" |
|
11 #include "CC_Observer.h" |
|
12 #include "ccapi_call_info.h" |
|
13 #include "CC_SIPCCCallInfo.h" |
|
14 #include "ccapi_device_info.h" |
|
15 #include "CC_SIPCCDeviceInfo.h" |
|
16 #include "vcm.h" |
|
17 #include "VcmSIPCCBinding.h" |
|
18 #include "PeerConnectionImpl.h" |
|
19 #include "PeerConnectionCtx.h" |
|
20 #include "runnable_utils.h" |
|
21 #include "cpr_socket.h" |
|
22 #include "debug-psipcc-types.h" |
|
23 #include "prcvar.h" |
|
24 |
|
25 #include "mozilla/Telemetry.h" |
|
26 |
|
27 #ifdef MOZILLA_INTERNAL_API |
|
28 #include "mozilla/dom/RTCPeerConnectionBinding.h" |
|
29 #include "mozilla/Preferences.h" |
|
30 #endif |
|
31 |
|
32 #include "nsIObserverService.h" |
|
33 #include "nsIObserver.h" |
|
34 #include "mozilla/Services.h" |
|
35 #include "StaticPtr.h" |
|
36 extern "C" { |
|
37 #include "../sipcc/core/common/thread_monitor.h" |
|
38 } |
|
39 |
|
40 static const char* logTag = "PeerConnectionCtx"; |
|
41 |
|
42 extern "C" { |
|
43 extern PRCondVar *ccAppReadyToStartCond; |
|
44 extern PRLock *ccAppReadyToStartLock; |
|
45 extern char ccAppReadyToStart; |
|
46 } |
|
47 |
|
48 namespace mozilla { |
|
49 |
|
50 using namespace dom; |
|
51 |
|
52 // Convert constraints to C structures |
|
53 |
|
54 #ifdef MOZILLA_INTERNAL_API |
|
55 static void |
|
56 Apply(const Optional<bool> &aSrc, cc_boolean_constraint_t *aDst, |
|
57 bool mandatory = false) { |
|
58 if (aSrc.WasPassed() && (mandatory || !aDst->was_passed)) { |
|
59 aDst->was_passed = true; |
|
60 aDst->value = aSrc.Value(); |
|
61 aDst->mandatory = mandatory; |
|
62 } |
|
63 } |
|
64 #endif |
|
65 |
|
66 MediaConstraintsExternal::MediaConstraintsExternal() { |
|
67 memset(&mConstraints, 0, sizeof(mConstraints)); |
|
68 } |
|
69 |
|
70 MediaConstraintsExternal::MediaConstraintsExternal( |
|
71 const MediaConstraintsInternal &aSrc) { |
|
72 cc_media_constraints_t* c = &mConstraints; |
|
73 memset(c, 0, sizeof(*c)); |
|
74 #ifdef MOZILLA_INTERNAL_API |
|
75 Apply(aSrc.mMandatory.mOfferToReceiveAudio, &c->offer_to_receive_audio, true); |
|
76 Apply(aSrc.mMandatory.mOfferToReceiveVideo, &c->offer_to_receive_video, true); |
|
77 if (!Preferences::GetBool("media.peerconnection.video.enabled", true)) { |
|
78 c->offer_to_receive_video.was_passed = true; |
|
79 c->offer_to_receive_video.value = false; |
|
80 } |
|
81 Apply(aSrc.mMandatory.mMozDontOfferDataChannel, &c->moz_dont_offer_datachannel, |
|
82 true); |
|
83 Apply(aSrc.mMandatory.mMozBundleOnly, &c->moz_bundle_only, true); |
|
84 if (aSrc.mOptional.WasPassed()) { |
|
85 const Sequence<MediaConstraintSet> &array = aSrc.mOptional.Value(); |
|
86 for (uint32_t i = 0; i < array.Length(); i++) { |
|
87 Apply(array[i].mOfferToReceiveAudio, &c->offer_to_receive_audio); |
|
88 Apply(array[i].mOfferToReceiveVideo, &c->offer_to_receive_video); |
|
89 Apply(array[i].mMozDontOfferDataChannel, &c->moz_dont_offer_datachannel); |
|
90 Apply(array[i].mMozBundleOnly, &c->moz_bundle_only); |
|
91 } |
|
92 } |
|
93 #endif |
|
94 } |
|
95 |
|
96 cc_media_constraints_t* |
|
97 MediaConstraintsExternal::build() const { |
|
98 cc_media_constraints_t* cc = (cc_media_constraints_t*) |
|
99 cpr_malloc(sizeof(cc_media_constraints_t)); |
|
100 if (cc) { |
|
101 *cc = mConstraints; |
|
102 } |
|
103 return cc; |
|
104 } |
|
105 |
|
106 class PeerConnectionCtxShutdown : public nsIObserver |
|
107 { |
|
108 public: |
|
109 NS_DECL_ISUPPORTS |
|
110 |
|
111 PeerConnectionCtxShutdown() {} |
|
112 |
|
113 void Init() |
|
114 { |
|
115 nsCOMPtr<nsIObserverService> observerService = |
|
116 services::GetObserverService(); |
|
117 if (!observerService) |
|
118 return; |
|
119 |
|
120 nsresult rv = NS_OK; |
|
121 |
|
122 #ifdef MOZILLA_INTERNAL_API |
|
123 rv = observerService->AddObserver(this, |
|
124 NS_XPCOM_SHUTDOWN_OBSERVER_ID, |
|
125 false); |
|
126 MOZ_ALWAYS_TRUE(NS_SUCCEEDED(rv)); |
|
127 #endif |
|
128 (void) rv; |
|
129 } |
|
130 |
|
131 virtual ~PeerConnectionCtxShutdown() |
|
132 { |
|
133 nsCOMPtr<nsIObserverService> observerService = |
|
134 services::GetObserverService(); |
|
135 if (observerService) |
|
136 observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); |
|
137 } |
|
138 |
|
139 NS_IMETHODIMP Observe(nsISupports* aSubject, const char* aTopic, |
|
140 const char16_t* aData) { |
|
141 if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { |
|
142 CSFLogDebug(logTag, "Shutting down PeerConnectionCtx"); |
|
143 sipcc::PeerConnectionCtx::Destroy(); |
|
144 |
|
145 nsCOMPtr<nsIObserverService> observerService = |
|
146 services::GetObserverService(); |
|
147 if (!observerService) |
|
148 return NS_ERROR_FAILURE; |
|
149 |
|
150 nsresult rv = observerService->RemoveObserver(this, |
|
151 NS_XPCOM_SHUTDOWN_OBSERVER_ID); |
|
152 MOZ_ALWAYS_TRUE(NS_SUCCEEDED(rv)); |
|
153 |
|
154 // Make sure we're not deleted while still inside ::Observe() |
|
155 nsRefPtr<PeerConnectionCtxShutdown> kungFuDeathGrip(this); |
|
156 sipcc::PeerConnectionCtx::gPeerConnectionCtxShutdown = nullptr; |
|
157 } |
|
158 return NS_OK; |
|
159 } |
|
160 }; |
|
161 |
|
162 NS_IMPL_ISUPPORTS(PeerConnectionCtxShutdown, nsIObserver); |
|
163 } |
|
164 |
|
165 using namespace mozilla; |
|
166 namespace sipcc { |
|
167 |
|
168 PeerConnectionCtx* PeerConnectionCtx::gInstance; |
|
169 nsIThread* PeerConnectionCtx::gMainThread; |
|
170 StaticRefPtr<PeerConnectionCtxShutdown> PeerConnectionCtx::gPeerConnectionCtxShutdown; |
|
171 |
|
172 // Since we have a pointer to main-thread, help make it safe for lower-level |
|
173 // SIPCC threads to use SyncRunnable without deadlocking, by exposing main's |
|
174 // dispatcher and waiter functions. See sipcc/core/common/thread_monitor.c. |
|
175 |
|
176 static void thread_ended_dispatcher(thread_ended_funct func, thread_monitor_id_t id) |
|
177 { |
|
178 nsresult rv = PeerConnectionCtx::gMainThread->Dispatch(WrapRunnableNM(func, id), |
|
179 NS_DISPATCH_NORMAL); |
|
180 if (NS_FAILED(rv)) { |
|
181 CSFLogError( logTag, "%s(): Could not dispatch to main thread", __FUNCTION__); |
|
182 } |
|
183 } |
|
184 |
|
185 static void join_waiter() { |
|
186 NS_ProcessPendingEvents(PeerConnectionCtx::gMainThread); |
|
187 } |
|
188 |
|
189 nsresult PeerConnectionCtx::InitializeGlobal(nsIThread *mainThread, |
|
190 nsIEventTarget* stsThread) { |
|
191 if (!gMainThread) { |
|
192 gMainThread = mainThread; |
|
193 CSF::VcmSIPCCBinding::setMainThread(gMainThread); |
|
194 init_thread_monitor(&thread_ended_dispatcher, &join_waiter); |
|
195 } else { |
|
196 #ifdef MOZILLA_INTERNAL_API |
|
197 MOZ_ASSERT(gMainThread == mainThread); |
|
198 #endif |
|
199 } |
|
200 |
|
201 CSF::VcmSIPCCBinding::setSTSThread(stsThread); |
|
202 |
|
203 nsresult res; |
|
204 |
|
205 #ifdef MOZILLA_INTERNAL_API |
|
206 // This check fails on the unit tests because they do not |
|
207 // have the right thread behavior. |
|
208 bool on; |
|
209 res = gMainThread->IsOnCurrentThread(&on); |
|
210 NS_ENSURE_SUCCESS(res, res); |
|
211 MOZ_ASSERT(on); |
|
212 #endif |
|
213 |
|
214 if (!gInstance) { |
|
215 CSFLogDebug(logTag, "Creating PeerConnectionCtx"); |
|
216 PeerConnectionCtx *ctx = new PeerConnectionCtx(); |
|
217 |
|
218 res = ctx->Initialize(); |
|
219 PR_ASSERT(NS_SUCCEEDED(res)); |
|
220 if (!NS_SUCCEEDED(res)) |
|
221 return res; |
|
222 |
|
223 gInstance = ctx; |
|
224 |
|
225 if (!sipcc::PeerConnectionCtx::gPeerConnectionCtxShutdown) { |
|
226 sipcc::PeerConnectionCtx::gPeerConnectionCtxShutdown = new PeerConnectionCtxShutdown(); |
|
227 sipcc::PeerConnectionCtx::gPeerConnectionCtxShutdown->Init(); |
|
228 } |
|
229 } |
|
230 |
|
231 return NS_OK; |
|
232 } |
|
233 |
|
234 PeerConnectionCtx* PeerConnectionCtx::GetInstance() { |
|
235 MOZ_ASSERT(gInstance); |
|
236 return gInstance; |
|
237 } |
|
238 |
|
239 bool PeerConnectionCtx::isActive() { |
|
240 return gInstance; |
|
241 } |
|
242 |
|
243 void PeerConnectionCtx::Destroy() { |
|
244 CSFLogDebug(logTag, "%s", __FUNCTION__); |
|
245 |
|
246 if (gInstance) { |
|
247 gInstance->Cleanup(); |
|
248 delete gInstance; |
|
249 gInstance = nullptr; |
|
250 } |
|
251 } |
|
252 |
|
253 nsresult PeerConnectionCtx::Initialize() { |
|
254 mCCM = CSF::CallControlManager::create(); |
|
255 |
|
256 NS_ENSURE_TRUE(mCCM.get(), NS_ERROR_FAILURE); |
|
257 |
|
258 // Add the local audio codecs |
|
259 // FIX - Get this list from MediaEngine instead |
|
260 int codecMask = 0; |
|
261 codecMask |= VCM_CODEC_RESOURCE_G711; |
|
262 codecMask |= VCM_CODEC_RESOURCE_OPUS; |
|
263 //codecMask |= VCM_CODEC_RESOURCE_LINEAR; |
|
264 //codecMask |= VCM_CODEC_RESOURCE_G722; |
|
265 //codecMask |= VCM_CODEC_RESOURCE_iLBC; |
|
266 //codecMask |= VCM_CODEC_RESOURCE_iSAC; |
|
267 mCCM->setAudioCodecs(codecMask); |
|
268 |
|
269 //Add the local video codecs |
|
270 // FIX - Get this list from MediaEngine instead |
|
271 // Turning them all on for now |
|
272 codecMask = 0; |
|
273 // Only adding codecs supported |
|
274 //codecMask |= VCM_CODEC_RESOURCE_H263; |
|
275 |
|
276 #ifdef MOZILLA_INTERNAL_API |
|
277 if (Preferences::GetBool("media.peerconnection.video.h264_enabled")) { |
|
278 codecMask |= VCM_CODEC_RESOURCE_H264; |
|
279 } |
|
280 #endif |
|
281 |
|
282 codecMask |= VCM_CODEC_RESOURCE_VP8; |
|
283 //codecMask |= VCM_CODEC_RESOURCE_I420; |
|
284 mCCM->setVideoCodecs(codecMask); |
|
285 |
|
286 ccAppReadyToStartLock = PR_NewLock(); |
|
287 if (!ccAppReadyToStartLock) { |
|
288 return NS_ERROR_FAILURE; |
|
289 } |
|
290 |
|
291 ccAppReadyToStartCond = PR_NewCondVar(ccAppReadyToStartLock); |
|
292 if (!ccAppReadyToStartCond) { |
|
293 return NS_ERROR_FAILURE; |
|
294 } |
|
295 |
|
296 if (!mCCM->startSDPMode()) |
|
297 return NS_ERROR_FAILURE; |
|
298 |
|
299 mDevice = mCCM->getActiveDevice(); |
|
300 mCCM->addCCObserver(this); |
|
301 NS_ENSURE_TRUE(mDevice.get(), NS_ERROR_FAILURE); |
|
302 ChangeSipccState(dom::PCImplSipccState::Starting); |
|
303 |
|
304 // Now that everything is set up, we let the CCApp thread |
|
305 // know that it's okay to start processing messages. |
|
306 PR_Lock(ccAppReadyToStartLock); |
|
307 ccAppReadyToStart = 1; |
|
308 PR_NotifyAllCondVar(ccAppReadyToStartCond); |
|
309 PR_Unlock(ccAppReadyToStartLock); |
|
310 |
|
311 mConnectionCounter = 0; |
|
312 #ifdef MOZILLA_INTERNAL_API |
|
313 Telemetry::GetHistogramById(Telemetry::WEBRTC_CALL_COUNT)->Add(0); |
|
314 #endif |
|
315 |
|
316 return NS_OK; |
|
317 } |
|
318 |
|
319 nsresult PeerConnectionCtx::Cleanup() { |
|
320 CSFLogDebug(logTag, "%s", __FUNCTION__); |
|
321 |
|
322 mCCM->destroy(); |
|
323 mCCM->removeCCObserver(this); |
|
324 return NS_OK; |
|
325 } |
|
326 |
|
327 CSF::CC_CallPtr PeerConnectionCtx::createCall() { |
|
328 return mDevice->createCall(); |
|
329 } |
|
330 |
|
331 void PeerConnectionCtx::onDeviceEvent(ccapi_device_event_e aDeviceEvent, |
|
332 CSF::CC_DevicePtr aDevice, |
|
333 CSF::CC_DeviceInfoPtr aInfo ) { |
|
334 cc_service_state_t state = aInfo->getServiceState(); |
|
335 // We are keeping this in a local var to avoid a data race |
|
336 // with ChangeSipccState in the debug message and compound if below |
|
337 dom::PCImplSipccState currentSipccState = mSipccState; |
|
338 |
|
339 switch (aDeviceEvent) { |
|
340 case CCAPI_DEVICE_EV_STATE: |
|
341 CSFLogDebug(logTag, "%s - %d : %d", __FUNCTION__, state, |
|
342 static_cast<uint32_t>(currentSipccState)); |
|
343 |
|
344 if (CC_STATE_INS == state) { |
|
345 // SIPCC is up |
|
346 if (dom::PCImplSipccState::Starting == currentSipccState || |
|
347 dom::PCImplSipccState::Idle == currentSipccState) { |
|
348 ChangeSipccState(dom::PCImplSipccState::Started); |
|
349 } else { |
|
350 CSFLogError(logTag, "%s PeerConnection already started", __FUNCTION__); |
|
351 } |
|
352 } else { |
|
353 NS_NOTREACHED("Unsupported Signaling State Transition"); |
|
354 } |
|
355 break; |
|
356 default: |
|
357 CSFLogDebug(logTag, "%s: Ignoring event: %s\n",__FUNCTION__, |
|
358 device_event_getname(aDeviceEvent)); |
|
359 } |
|
360 } |
|
361 |
|
362 static void onCallEvent_m(nsAutoPtr<std::string> peerconnection, |
|
363 ccapi_call_event_e aCallEvent, |
|
364 CSF::CC_CallInfoPtr aInfo); |
|
365 |
|
366 void PeerConnectionCtx::onCallEvent(ccapi_call_event_e aCallEvent, |
|
367 CSF::CC_CallPtr aCall, |
|
368 CSF::CC_CallInfoPtr aInfo) { |
|
369 // This is called on a SIPCC thread. |
|
370 // |
|
371 // We cannot use SyncRunnable to main thread, as that would deadlock on |
|
372 // shutdown. Instead, we dispatch asynchronously. We copy getPeerConnection(), |
|
373 // a "weak ref" to the PC, which is safe in shutdown, and CC_CallInfoPtr (an |
|
374 // nsRefPtr) is thread-safe and keeps aInfo alive. |
|
375 nsAutoPtr<std::string> pcDuped(new std::string(aCall->getPeerConnection())); |
|
376 |
|
377 // DISPATCH_NORMAL with duped string |
|
378 nsresult rv = gMainThread->Dispatch(WrapRunnableNM(&onCallEvent_m, pcDuped, |
|
379 aCallEvent, aInfo), |
|
380 NS_DISPATCH_NORMAL); |
|
381 if (NS_FAILED(rv)) { |
|
382 CSFLogError( logTag, "%s(): Could not dispatch to main thread", __FUNCTION__); |
|
383 } |
|
384 } |
|
385 |
|
386 // Demux the call event to the right PeerConnection |
|
387 static void onCallEvent_m(nsAutoPtr<std::string> peerconnection, |
|
388 ccapi_call_event_e aCallEvent, |
|
389 CSF::CC_CallInfoPtr aInfo) { |
|
390 CSFLogDebug(logTag, "onCallEvent()"); |
|
391 PeerConnectionWrapper pc(peerconnection->c_str()); |
|
392 if (!pc.impl()) // This must be an event on a dead PC. Ignore |
|
393 return; |
|
394 CSFLogDebug(logTag, "Calling PC"); |
|
395 pc.impl()->onCallEvent(OnCallEventArgs(aCallEvent, aInfo)); |
|
396 } |
|
397 |
|
398 } // namespace sipcc |