Line data Source code
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 "PeerConnectionImpl.h"
8 : #include "PeerConnectionCtx.h"
9 : #include "runnable_utils.h"
10 : #include "prcvar.h"
11 :
12 : #include "mozilla/Telemetry.h"
13 : #include "browser_logging/WebRtcLog.h"
14 :
15 : #include "mozilla/dom/RTCPeerConnectionBinding.h"
16 : #include "mozilla/Preferences.h"
17 : #include <mozilla/Types.h>
18 :
19 : #include "nsNetCID.h" // NS_SOCKETTRANSPORTSERVICE_CONTRACTID
20 : #include "nsServiceManagerUtils.h" // do_GetService
21 : #include "nsIObserverService.h"
22 : #include "nsIObserver.h"
23 : #include "nsIIOService.h" // NS_IOSERVICE_*
24 : #include "mozilla/Services.h"
25 : #include "mozilla/StaticPtr.h"
26 :
27 : #include "nsCRTGlue.h"
28 :
29 : #include "gmp-video-decode.h" // GMP_API_VIDEO_DECODER
30 : #include "gmp-video-encode.h" // GMP_API_VIDEO_ENCODER
31 :
32 : static const char* logTag = "PeerConnectionCtx";
33 :
34 : namespace mozilla {
35 :
36 : using namespace dom;
37 :
38 : class PeerConnectionCtxObserver : public nsIObserver
39 : {
40 : public:
41 : NS_DECL_ISUPPORTS
42 :
43 0 : PeerConnectionCtxObserver() {}
44 :
45 0 : void Init()
46 : {
47 : nsCOMPtr<nsIObserverService> observerService =
48 0 : services::GetObserverService();
49 0 : if (!observerService)
50 0 : return;
51 :
52 0 : nsresult rv = NS_OK;
53 :
54 0 : rv = observerService->AddObserver(this,
55 : NS_XPCOM_SHUTDOWN_OBSERVER_ID,
56 0 : false);
57 0 : MOZ_ALWAYS_SUCCEEDS(rv);
58 0 : rv = observerService->AddObserver(this,
59 : NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
60 0 : false);
61 0 : MOZ_ALWAYS_SUCCEEDS(rv);
62 : (void) rv;
63 : }
64 :
65 0 : NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
66 : const char16_t* aData) override {
67 0 : if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
68 0 : CSFLogDebug(logTag, "Shutting down PeerConnectionCtx");
69 0 : PeerConnectionCtx::Destroy();
70 :
71 : nsCOMPtr<nsIObserverService> observerService =
72 0 : services::GetObserverService();
73 0 : if (!observerService)
74 0 : return NS_ERROR_FAILURE;
75 :
76 0 : nsresult rv = observerService->RemoveObserver(this,
77 0 : NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
78 0 : MOZ_ALWAYS_SUCCEEDS(rv);
79 0 : rv = observerService->RemoveObserver(this,
80 0 : NS_XPCOM_SHUTDOWN_OBSERVER_ID);
81 0 : MOZ_ALWAYS_SUCCEEDS(rv);
82 :
83 : // Make sure we're not deleted while still inside ::Observe()
84 0 : RefPtr<PeerConnectionCtxObserver> kungFuDeathGrip(this);
85 0 : PeerConnectionCtx::gPeerConnectionCtxObserver = nullptr;
86 : }
87 0 : if (strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC) == 0) {
88 0 : if (NS_strcmp(aData, u"" NS_IOSERVICE_OFFLINE) == 0) {
89 0 : CSFLogDebug(logTag, "Updating network state to offline");
90 0 : PeerConnectionCtx::UpdateNetworkState(false);
91 0 : } else if(NS_strcmp(aData, u"" NS_IOSERVICE_ONLINE) == 0) {
92 0 : CSFLogDebug(logTag, "Updating network state to online");
93 0 : PeerConnectionCtx::UpdateNetworkState(true);
94 : } else {
95 0 : CSFLogDebug(logTag, "Received unsupported network state event");
96 0 : MOZ_CRASH();
97 : }
98 : }
99 0 : return NS_OK;
100 : }
101 :
102 : private:
103 0 : virtual ~PeerConnectionCtxObserver()
104 0 : {
105 : nsCOMPtr<nsIObserverService> observerService =
106 0 : services::GetObserverService();
107 0 : if (observerService) {
108 0 : observerService->RemoveObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
109 0 : observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
110 : }
111 0 : }
112 : };
113 :
114 0 : NS_IMPL_ISUPPORTS(PeerConnectionCtxObserver, nsIObserver);
115 : }
116 :
117 : namespace mozilla {
118 :
119 : PeerConnectionCtx* PeerConnectionCtx::gInstance;
120 : nsIThread* PeerConnectionCtx::gMainThread;
121 3 : StaticRefPtr<PeerConnectionCtxObserver> PeerConnectionCtx::gPeerConnectionCtxObserver;
122 :
123 : const std::map<const std::string, PeerConnectionImpl *>&
124 0 : PeerConnectionCtx::mGetPeerConnections()
125 : {
126 0 : return mPeerConnections;
127 : }
128 :
129 0 : nsresult PeerConnectionCtx::InitializeGlobal(nsIThread *mainThread,
130 : nsIEventTarget* stsThread) {
131 0 : if (!gMainThread) {
132 0 : gMainThread = mainThread;
133 : } else {
134 0 : MOZ_ASSERT(gMainThread == mainThread);
135 : }
136 :
137 : nsresult res;
138 :
139 0 : MOZ_ASSERT(NS_IsMainThread());
140 :
141 0 : if (!gInstance) {
142 0 : CSFLogDebug(logTag, "Creating PeerConnectionCtx");
143 0 : PeerConnectionCtx *ctx = new PeerConnectionCtx();
144 :
145 0 : res = ctx->Initialize();
146 0 : PR_ASSERT(NS_SUCCEEDED(res));
147 0 : if (!NS_SUCCEEDED(res))
148 0 : return res;
149 :
150 0 : gInstance = ctx;
151 :
152 0 : if (!PeerConnectionCtx::gPeerConnectionCtxObserver) {
153 0 : PeerConnectionCtx::gPeerConnectionCtxObserver = new PeerConnectionCtxObserver();
154 0 : PeerConnectionCtx::gPeerConnectionCtxObserver->Init();
155 : }
156 : }
157 :
158 0 : EnableWebRtcLog();
159 0 : return NS_OK;
160 : }
161 :
162 0 : PeerConnectionCtx* PeerConnectionCtx::GetInstance() {
163 0 : MOZ_ASSERT(gInstance);
164 0 : return gInstance;
165 : }
166 :
167 0 : bool PeerConnectionCtx::isActive() {
168 0 : return gInstance;
169 : }
170 :
171 0 : void PeerConnectionCtx::Destroy() {
172 0 : CSFLogDebug(logTag, "%s", __FUNCTION__);
173 :
174 0 : if (gInstance) {
175 0 : gInstance->Cleanup();
176 0 : delete gInstance;
177 0 : gInstance = nullptr;
178 : }
179 :
180 0 : StopWebRtcLog();
181 0 : }
182 :
183 : typedef Vector<nsAutoPtr<RTCStatsQuery>> RTCStatsQueries;
184 :
185 : // Telemetry reporting every second after start of first call.
186 : // The threading model around the media pipelines is weird:
187 : // - The pipelines are containers,
188 : // - containers that are only safe on main thread, with members only safe on STS,
189 : // - hence the there and back again approach.
190 :
191 : static auto
192 0 : FindId(const Sequence<RTCInboundRTPStreamStats>& aArray,
193 : const nsString &aId) -> decltype(aArray.Length()) {
194 0 : for (decltype(aArray.Length()) i = 0; i < aArray.Length(); i++) {
195 0 : if (aArray[i].mId.Value() == aId) {
196 0 : return i;
197 : }
198 : }
199 0 : return aArray.NoIndex;
200 : }
201 :
202 : static auto
203 0 : FindId(const nsTArray<nsAutoPtr<RTCStatsReportInternal>>& aArray,
204 : const nsString &aId) -> decltype(aArray.Length()) {
205 0 : for (decltype(aArray.Length()) i = 0; i < aArray.Length(); i++) {
206 0 : if (aArray[i]->mPcid == aId) {
207 0 : return i;
208 : }
209 : }
210 0 : return aArray.NoIndex;
211 : }
212 :
213 : static void
214 0 : FreeOnMain_m(nsAutoPtr<RTCStatsQueries> aQueryList) {
215 0 : MOZ_ASSERT(NS_IsMainThread());
216 0 : }
217 :
218 : static void
219 0 : EverySecondTelemetryCallback_s(nsAutoPtr<RTCStatsQueries> aQueryList) {
220 : using namespace Telemetry;
221 :
222 0 : if(!PeerConnectionCtx::isActive()) {
223 0 : return;
224 : }
225 0 : PeerConnectionCtx *ctx = PeerConnectionCtx::GetInstance();
226 :
227 0 : for (auto & q : *aQueryList) {
228 0 : PeerConnectionImpl::ExecuteStatsQuery_s(q);
229 0 : auto& r = *q->report;
230 0 : if (r.mInboundRTPStreamStats.WasPassed()) {
231 : // First, get reports from a second ago, if any, for calculations below
232 0 : const Sequence<RTCInboundRTPStreamStats> *lastInboundStats = nullptr;
233 : {
234 0 : auto i = FindId(ctx->mLastReports, r.mPcid);
235 0 : if (i != ctx->mLastReports.NoIndex) {
236 0 : lastInboundStats = &ctx->mLastReports[i]->mInboundRTPStreamStats.Value();
237 : }
238 : }
239 : // Then, look for the things we want telemetry on
240 0 : auto& array = r.mInboundRTPStreamStats.Value();
241 0 : for (decltype(array.Length()) i = 0; i < array.Length(); i++) {
242 0 : auto& s = array[i];
243 0 : bool isAudio = (s.mId.Value().Find("audio") != -1);
244 0 : if (s.mPacketsLost.WasPassed() && s.mPacketsReceived.WasPassed() &&
245 0 : (s.mPacketsLost.Value() + s.mPacketsReceived.Value()) != 0) {
246 : HistogramID id;
247 0 : if (s.mIsRemote) {
248 0 : id = isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_PACKETLOSS_RATE :
249 : WEBRTC_VIDEO_QUALITY_OUTBOUND_PACKETLOSS_RATE;
250 : } else {
251 0 : id = isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_PACKETLOSS_RATE :
252 : WEBRTC_VIDEO_QUALITY_INBOUND_PACKETLOSS_RATE;
253 : }
254 : // *1000 so we can read in 10's of a percent (permille)
255 0 : Accumulate(id,
256 0 : (s.mPacketsLost.Value() * 1000) /
257 0 : (s.mPacketsLost.Value() + s.mPacketsReceived.Value()));
258 : }
259 0 : if (s.mJitter.WasPassed()) {
260 : HistogramID id;
261 0 : if (s.mIsRemote) {
262 0 : id = isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_JITTER :
263 : WEBRTC_VIDEO_QUALITY_OUTBOUND_JITTER;
264 : } else {
265 0 : id = isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_JITTER :
266 : WEBRTC_VIDEO_QUALITY_INBOUND_JITTER;
267 : }
268 0 : Accumulate(id, s.mJitter.Value());
269 : }
270 0 : if (s.mRoundTripTime.WasPassed()) {
271 0 : MOZ_ASSERT(s.mIsRemote);
272 0 : HistogramID id = isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_RTT :
273 0 : WEBRTC_VIDEO_QUALITY_OUTBOUND_RTT;
274 0 : Accumulate(id, s.mRoundTripTime.Value());
275 : }
276 0 : if (lastInboundStats && s.mBytesReceived.WasPassed()) {
277 0 : auto& laststats = *lastInboundStats;
278 0 : auto i = FindId(laststats, s.mId.Value());
279 0 : if (i != laststats.NoIndex) {
280 0 : auto& lasts = laststats[i];
281 0 : if (lasts.mBytesReceived.WasPassed()) {
282 0 : auto delta_ms = int32_t(s.mTimestamp.Value() -
283 0 : lasts.mTimestamp.Value());
284 : // In theory we're called every second, so delta *should* be in that range.
285 : // Small deltas could cause errors due to division
286 0 : if (delta_ms > 500 && delta_ms < 60000) {
287 : HistogramID id;
288 0 : if (s.mIsRemote) {
289 0 : id = isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_BANDWIDTH_KBITS :
290 : WEBRTC_VIDEO_QUALITY_OUTBOUND_BANDWIDTH_KBITS;
291 : } else {
292 0 : id = isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_BANDWIDTH_KBITS :
293 : WEBRTC_VIDEO_QUALITY_INBOUND_BANDWIDTH_KBITS;
294 : }
295 0 : Accumulate(id, ((s.mBytesReceived.Value() -
296 0 : lasts.mBytesReceived.Value()) * 8) / delta_ms);
297 : }
298 : // We could accumulate values until enough time has passed
299 : // and then Accumulate() but this isn't that important.
300 : }
301 : }
302 : }
303 : }
304 : }
305 : }
306 : // Steal and hang on to reports for the next second
307 0 : ctx->mLastReports.Clear();
308 0 : for (auto & q : *aQueryList) {
309 0 : ctx->mLastReports.AppendElement(q->report.forget()); // steal avoids copy
310 : }
311 : // Container must be freed back on main thread
312 0 : NS_DispatchToMainThread(WrapRunnableNM(&FreeOnMain_m, aQueryList),
313 0 : NS_DISPATCH_NORMAL);
314 : }
315 :
316 : void
317 0 : PeerConnectionCtx::EverySecondTelemetryCallback_m(nsITimer* timer, void *closure) {
318 0 : MOZ_ASSERT(NS_IsMainThread());
319 0 : MOZ_ASSERT(PeerConnectionCtx::isActive());
320 0 : auto ctx = static_cast<PeerConnectionCtx*>(closure);
321 0 : if (ctx->mPeerConnections.empty()) {
322 0 : return;
323 : }
324 : nsresult rv;
325 : nsCOMPtr<nsIEventTarget> stsThread =
326 0 : do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
327 0 : if (NS_FAILED(rv)) {
328 0 : return;
329 : }
330 0 : MOZ_ASSERT(stsThread);
331 :
332 0 : nsAutoPtr<RTCStatsQueries> queries(new RTCStatsQueries);
333 0 : for (auto p = ctx->mPeerConnections.begin();
334 0 : p != ctx->mPeerConnections.end(); ++p) {
335 0 : if (p->second->HasMedia()) {
336 0 : if (!queries->append(nsAutoPtr<RTCStatsQuery>(new RTCStatsQuery(true)))) {
337 0 : return;
338 : }
339 0 : if (NS_WARN_IF(NS_FAILED(p->second->BuildStatsQuery_m(nullptr, // all tracks
340 : queries->back())))) {
341 0 : queries->popBack();
342 : } else {
343 0 : MOZ_ASSERT(queries->back()->report);
344 : }
345 : }
346 : }
347 0 : if (!queries->empty()) {
348 0 : rv = RUN_ON_THREAD(stsThread,
349 0 : WrapRunnableNM(&EverySecondTelemetryCallback_s, queries),
350 : NS_DISPATCH_NORMAL);
351 0 : NS_ENSURE_SUCCESS_VOID(rv);
352 : }
353 : }
354 :
355 : void
356 0 : PeerConnectionCtx::UpdateNetworkState(bool online) {
357 0 : auto ctx = GetInstance();
358 0 : if (ctx->mPeerConnections.empty()) {
359 0 : return;
360 : }
361 0 : for (auto pc : ctx->mPeerConnections) {
362 0 : pc.second->UpdateNetworkState(online);
363 : }
364 : }
365 :
366 0 : nsresult PeerConnectionCtx::Initialize() {
367 0 : initGMP();
368 :
369 0 : mTelemetryTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
370 0 : MOZ_ASSERT(mTelemetryTimer);
371 0 : nsresult rv = mTelemetryTimer->SetTarget(gMainThread);
372 0 : NS_ENSURE_SUCCESS(rv, rv);
373 0 : mTelemetryTimer->InitWithNamedFuncCallback(EverySecondTelemetryCallback_m, this, 1000,
374 : nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP,
375 0 : "EverySecondTelemetryCallback_m");
376 :
377 0 : if (XRE_IsContentProcess()) {
378 0 : WebrtcGlobalChild::Create();
379 : }
380 :
381 0 : return NS_OK;
382 : }
383 :
384 0 : static void GMPReady_m() {
385 0 : if (PeerConnectionCtx::isActive()) {
386 0 : PeerConnectionCtx::GetInstance()->onGMPReady();
387 : }
388 0 : };
389 :
390 0 : static void GMPReady() {
391 0 : PeerConnectionCtx::gMainThread->Dispatch(WrapRunnableNM(&GMPReady_m),
392 0 : NS_DISPATCH_NORMAL);
393 0 : };
394 :
395 0 : void PeerConnectionCtx::initGMP()
396 : {
397 0 : mGMPService = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
398 :
399 0 : if (!mGMPService) {
400 : CSFLogError(logTag, "%s failed to get the gecko-media-plugin-service",
401 0 : __FUNCTION__);
402 0 : return;
403 : }
404 :
405 0 : nsCOMPtr<nsIThread> thread;
406 0 : nsresult rv = mGMPService->GetThread(getter_AddRefs(thread));
407 :
408 0 : if (NS_FAILED(rv)) {
409 0 : mGMPService = nullptr;
410 : CSFLogError(logTag,
411 : "%s failed to get the gecko-media-plugin thread, err=%u",
412 : __FUNCTION__,
413 0 : static_cast<unsigned>(rv));
414 0 : return;
415 : }
416 :
417 : // presumes that all GMP dir scans have been queued for the GMPThread
418 0 : thread->Dispatch(WrapRunnableNM(&GMPReady), NS_DISPATCH_NORMAL);
419 : }
420 :
421 0 : nsresult PeerConnectionCtx::Cleanup() {
422 0 : CSFLogDebug(logTag, "%s", __FUNCTION__);
423 :
424 0 : mQueuedJSEPOperations.Clear();
425 0 : mGMPService = nullptr;
426 0 : return NS_OK;
427 : }
428 :
429 0 : PeerConnectionCtx::~PeerConnectionCtx() {
430 : // ensure mTelemetryTimer ends on main thread
431 0 : MOZ_ASSERT(NS_IsMainThread());
432 0 : if (mTelemetryTimer) {
433 0 : mTelemetryTimer->Cancel();
434 : }
435 0 : };
436 :
437 0 : void PeerConnectionCtx::queueJSEPOperation(nsIRunnable* aOperation) {
438 0 : mQueuedJSEPOperations.AppendElement(aOperation);
439 0 : }
440 :
441 0 : void PeerConnectionCtx::onGMPReady() {
442 0 : mGMPReady = true;
443 0 : for (size_t i = 0; i < mQueuedJSEPOperations.Length(); ++i) {
444 0 : mQueuedJSEPOperations[i]->Run();
445 : }
446 0 : mQueuedJSEPOperations.Clear();
447 0 : }
448 :
449 0 : bool PeerConnectionCtx::gmpHasH264() {
450 0 : if (!mGMPService) {
451 0 : return false;
452 : }
453 :
454 : // XXX I'd prefer if this was all known ahead of time...
455 :
456 0 : nsTArray<nsCString> tags;
457 0 : tags.AppendElement(NS_LITERAL_CSTRING("h264"));
458 :
459 : bool has_gmp;
460 : nsresult rv;
461 0 : rv = mGMPService->HasPluginForAPI(NS_LITERAL_CSTRING(GMP_API_VIDEO_ENCODER),
462 : &tags,
463 0 : &has_gmp);
464 0 : if (NS_FAILED(rv) || !has_gmp) {
465 0 : return false;
466 : }
467 :
468 0 : rv = mGMPService->HasPluginForAPI(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
469 : &tags,
470 0 : &has_gmp);
471 0 : if (NS_FAILED(rv) || !has_gmp) {
472 0 : return false;
473 : }
474 :
475 0 : return true;
476 : }
477 :
478 9 : } // namespace mozilla
|