From 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:47:29 +0200 Subject: Adding upstream version 115.8.0esr. Signed-off-by: Daniel Baumann --- dom/media/webrtc/CubebDeviceEnumerator.cpp | 334 ++ dom/media/webrtc/CubebDeviceEnumerator.h | 87 + dom/media/webrtc/MediaEngine.h | 66 + dom/media/webrtc/MediaEngineFake.cpp | 653 +++ dom/media/webrtc/MediaEngineFake.h | 40 + dom/media/webrtc/MediaEnginePrefs.h | 101 + dom/media/webrtc/MediaEngineRemoteVideoSource.cpp | 907 ++++ dom/media/webrtc/MediaEngineRemoteVideoSource.h | 242 + dom/media/webrtc/MediaEngineSource.cpp | 69 + dom/media/webrtc/MediaEngineSource.h | 255 ++ dom/media/webrtc/MediaEngineWebRTC.cpp | 299 ++ dom/media/webrtc/MediaEngineWebRTC.h | 53 + dom/media/webrtc/MediaEngineWebRTCAudio.cpp | 1329 ++++++ dom/media/webrtc/MediaEngineWebRTCAudio.h | 295 ++ dom/media/webrtc/MediaTrackConstraints.cpp | 560 +++ dom/media/webrtc/MediaTrackConstraints.h | 371 ++ dom/media/webrtc/MediaTransportChild.h | 38 + dom/media/webrtc/MediaTransportParent.h | 69 + dom/media/webrtc/PMediaTransport.ipdl | 105 + dom/media/webrtc/PWebrtcGlobal.ipdl | 41 + dom/media/webrtc/PeerIdentity.cpp | 80 + dom/media/webrtc/PeerIdentity.h | 70 + dom/media/webrtc/RTCCertificate.cpp | 438 ++ dom/media/webrtc/RTCCertificate.h | 98 + dom/media/webrtc/RTCIdentityProviderRegistrar.cpp | 70 + dom/media/webrtc/RTCIdentityProviderRegistrar.h | 59 + dom/media/webrtc/SineWaveGenerator.h | 58 + dom/media/webrtc/WebrtcGlobal.h | 506 +++ dom/media/webrtc/WebrtcIPCTraits.h | 89 + dom/media/webrtc/common/CandidateInfo.h | 27 + dom/media/webrtc/common/CommonTypes.h | 52 + dom/media/webrtc/common/EncodingConstraints.h | 58 + dom/media/webrtc/common/MediaEngineWrapper.h | 32 + dom/media/webrtc/common/NullDeleter.h | 14 + dom/media/webrtc/common/NullTransport.h | 56 + dom/media/webrtc/common/Wrapper.h | 157 + dom/media/webrtc/common/YuvStamper.cpp | 394 ++ dom/media/webrtc/common/YuvStamper.h | 77 + dom/media/webrtc/common/browser_logging/CSFLog.cpp | 85 + dom/media/webrtc/common/browser_logging/CSFLog.h | 58 + .../webrtc/common/browser_logging/WebRtcLog.cpp | 162 + .../webrtc/common/browser_logging/WebRtcLog.h | 17 + dom/media/webrtc/common/csf_common.h | 90 + dom/media/webrtc/common/moz.build | 23 + dom/media/webrtc/common/time_profiling/timecard.c | 112 + dom/media/webrtc/common/time_profiling/timecard.h | 74 + dom/media/webrtc/jsapi/MediaTransportHandler.cpp | 1727 ++++++++ dom/media/webrtc/jsapi/MediaTransportHandler.h | 167 + .../webrtc/jsapi/MediaTransportHandlerIPC.cpp | 414 ++ dom/media/webrtc/jsapi/MediaTransportHandlerIPC.h | 96 + dom/media/webrtc/jsapi/MediaTransportParent.cpp | 240 + dom/media/webrtc/jsapi/PacketDumper.cpp | 124 + dom/media/webrtc/jsapi/PacketDumper.h | 52 + dom/media/webrtc/jsapi/PeerConnectionCtx.cpp | 650 +++ dom/media/webrtc/jsapi/PeerConnectionCtx.h | 194 + dom/media/webrtc/jsapi/PeerConnectionImpl.cpp | 4640 ++++++++++++++++++++ dom/media/webrtc/jsapi/PeerConnectionImpl.h | 969 ++++ dom/media/webrtc/jsapi/RTCDTMFSender.cpp | 159 + dom/media/webrtc/jsapi/RTCDTMFSender.h | 78 + dom/media/webrtc/jsapi/RTCDtlsTransport.cpp | 69 + dom/media/webrtc/jsapi/RTCDtlsTransport.h | 43 + dom/media/webrtc/jsapi/RTCRtpReceiver.cpp | 942 ++++ dom/media/webrtc/jsapi/RTCRtpReceiver.h | 198 + dom/media/webrtc/jsapi/RTCRtpSender.cpp | 1654 +++++++ dom/media/webrtc/jsapi/RTCRtpSender.h | 260 ++ dom/media/webrtc/jsapi/RTCRtpTransceiver.cpp | 1080 +++++ dom/media/webrtc/jsapi/RTCRtpTransceiver.h | 243 + dom/media/webrtc/jsapi/RTCSctpTransport.cpp | 53 + dom/media/webrtc/jsapi/RTCSctpTransport.h | 65 + dom/media/webrtc/jsapi/RTCStatsIdGenerator.cpp | 90 + dom/media/webrtc/jsapi/RTCStatsIdGenerator.h | 42 + dom/media/webrtc/jsapi/RTCStatsReport.cpp | 213 + dom/media/webrtc/jsapi/RTCStatsReport.h | 205 + dom/media/webrtc/jsapi/RemoteTrackSource.cpp | 73 + dom/media/webrtc/jsapi/RemoteTrackSource.h | 64 + dom/media/webrtc/jsapi/WebrtcGlobalChild.h | 42 + dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp | 829 ++++ dom/media/webrtc/jsapi/WebrtcGlobalInformation.h | 102 + dom/media/webrtc/jsapi/WebrtcGlobalParent.h | 52 + .../webrtc/jsapi/WebrtcGlobalStatsHistory.cpp | 282 ++ dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.h | 88 + dom/media/webrtc/jsapi/moz.build | 51 + dom/media/webrtc/jsep/JsepCodecDescription.h | 1196 +++++ dom/media/webrtc/jsep/JsepSession.h | 287 ++ dom/media/webrtc/jsep/JsepSessionImpl.cpp | 2457 +++++++++++ dom/media/webrtc/jsep/JsepSessionImpl.h | 286 ++ dom/media/webrtc/jsep/JsepTrack.cpp | 670 +++ dom/media/webrtc/jsep/JsepTrack.h | 305 ++ dom/media/webrtc/jsep/JsepTrackEncoding.h | 62 + dom/media/webrtc/jsep/JsepTransceiver.h | 220 + dom/media/webrtc/jsep/JsepTransport.h | 102 + dom/media/webrtc/jsep/SsrcGenerator.cpp | 22 + dom/media/webrtc/jsep/SsrcGenerator.h | 20 + dom/media/webrtc/jsep/moz.build | 18 + dom/media/webrtc/libwebrtcglue/AudioConduit.cpp | 975 ++++ dom/media/webrtc/libwebrtcglue/AudioConduit.h | 303 ++ dom/media/webrtc/libwebrtcglue/CallWorkerThread.h | 116 + dom/media/webrtc/libwebrtcglue/CodecConfig.h | 237 + dom/media/webrtc/libwebrtcglue/GmpVideoCodec.cpp | 22 + dom/media/webrtc/libwebrtcglue/GmpVideoCodec.h | 27 + .../webrtc/libwebrtcglue/MediaConduitControl.h | 77 + .../webrtc/libwebrtcglue/MediaConduitErrors.h | 46 + .../webrtc/libwebrtcglue/MediaConduitInterface.cpp | 151 + .../webrtc/libwebrtcglue/MediaConduitInterface.h | 495 +++ dom/media/webrtc/libwebrtcglue/MediaDataCodec.cpp | 70 + dom/media/webrtc/libwebrtcglue/MediaDataCodec.h | 32 + dom/media/webrtc/libwebrtcglue/RtpRtcpConfig.h | 24 + dom/media/webrtc/libwebrtcglue/RunningStat.h | 48 + dom/media/webrtc/libwebrtcglue/SystemTime.cpp | 60 + dom/media/webrtc/libwebrtcglue/SystemTime.h | 44 + dom/media/webrtc/libwebrtcglue/TaskQueueWrapper.h | 182 + dom/media/webrtc/libwebrtcglue/VideoConduit.cpp | 1902 ++++++++ dom/media/webrtc/libwebrtcglue/VideoConduit.h | 494 +++ .../webrtc/libwebrtcglue/VideoStreamFactory.cpp | 387 ++ .../webrtc/libwebrtcglue/VideoStreamFactory.h | 132 + .../webrtc/libwebrtcglue/WebrtcCallWrapper.cpp | 105 + dom/media/webrtc/libwebrtcglue/WebrtcCallWrapper.h | 114 + .../webrtc/libwebrtcglue/WebrtcGmpVideoCodec.cpp | 1043 +++++ .../webrtc/libwebrtcglue/WebrtcGmpVideoCodec.h | 507 +++ dom/media/webrtc/libwebrtcglue/WebrtcImageBuffer.h | 53 + .../libwebrtcglue/WebrtcMediaDataDecoderCodec.cpp | 209 + .../libwebrtcglue/WebrtcMediaDataDecoderCodec.h | 70 + .../libwebrtcglue/WebrtcMediaDataEncoderCodec.cpp | 518 +++ .../libwebrtcglue/WebrtcMediaDataEncoderCodec.h | 78 + .../libwebrtcglue/WebrtcVideoCodecFactory.cpp | 139 + .../webrtc/libwebrtcglue/WebrtcVideoCodecFactory.h | 124 + dom/media/webrtc/libwebrtcglue/moz.build | 35 + dom/media/webrtc/metrics.yaml | 358 ++ dom/media/webrtc/moz.build | 132 + dom/media/webrtc/sdp/HybridSdpParser.cpp | 88 + dom/media/webrtc/sdp/HybridSdpParser.h | 38 + dom/media/webrtc/sdp/ParsingResultComparer.cpp | 331 ++ dom/media/webrtc/sdp/ParsingResultComparer.h | 57 + dom/media/webrtc/sdp/RsdparsaSdp.cpp | 126 + dom/media/webrtc/sdp/RsdparsaSdp.h | 72 + dom/media/webrtc/sdp/RsdparsaSdpAttributeList.cpp | 1301 ++++++ dom/media/webrtc/sdp/RsdparsaSdpAttributeList.h | 157 + dom/media/webrtc/sdp/RsdparsaSdpGlue.cpp | 106 + dom/media/webrtc/sdp/RsdparsaSdpGlue.h | 36 + dom/media/webrtc/sdp/RsdparsaSdpInc.h | 510 +++ dom/media/webrtc/sdp/RsdparsaSdpMediaSection.cpp | 253 ++ dom/media/webrtc/sdp/RsdparsaSdpMediaSection.h | 71 + dom/media/webrtc/sdp/RsdparsaSdpParser.cpp | 73 + dom/media/webrtc/sdp/RsdparsaSdpParser.h | 34 + dom/media/webrtc/sdp/Sdp.h | 166 + dom/media/webrtc/sdp/SdpAttribute.cpp | 1562 +++++++ dom/media/webrtc/sdp/SdpAttribute.h | 1907 ++++++++ dom/media/webrtc/sdp/SdpAttributeList.h | 90 + dom/media/webrtc/sdp/SdpEnum.h | 64 + dom/media/webrtc/sdp/SdpHelper.cpp | 801 ++++ dom/media/webrtc/sdp/SdpHelper.h | 109 + dom/media/webrtc/sdp/SdpLog.cpp | 68 + dom/media/webrtc/sdp/SdpLog.h | 17 + dom/media/webrtc/sdp/SdpMediaSection.cpp | 197 + dom/media/webrtc/sdp/SdpMediaSection.h | 317 ++ dom/media/webrtc/sdp/SdpParser.h | 81 + dom/media/webrtc/sdp/SdpPref.cpp | 107 + dom/media/webrtc/sdp/SdpPref.h | 82 + dom/media/webrtc/sdp/SdpTelemetry.cpp | 63 + dom/media/webrtc/sdp/SdpTelemetry.h | 43 + dom/media/webrtc/sdp/SipccSdp.cpp | 173 + dom/media/webrtc/sdp/SipccSdp.h | 82 + dom/media/webrtc/sdp/SipccSdpAttributeList.cpp | 1386 ++++++ dom/media/webrtc/sdp/SipccSdpAttributeList.h | 145 + dom/media/webrtc/sdp/SipccSdpMediaSection.cpp | 401 ++ dom/media/webrtc/sdp/SipccSdpMediaSection.h | 101 + dom/media/webrtc/sdp/SipccSdpParser.cpp | 88 + dom/media/webrtc/sdp/SipccSdpParser.h | 35 + dom/media/webrtc/sdp/moz.build | 48 + dom/media/webrtc/sdp/rsdparsa_capi/Cargo.toml | 12 + .../webrtc/sdp/rsdparsa_capi/src/attribute.rs | 1472 +++++++ dom/media/webrtc/sdp/rsdparsa_capi/src/lib.rs | 298 ++ .../webrtc/sdp/rsdparsa_capi/src/media_section.rs | 233 + dom/media/webrtc/sdp/rsdparsa_capi/src/network.rs | 266 ++ dom/media/webrtc/sdp/rsdparsa_capi/src/types.rs | 199 + dom/media/webrtc/tests/crashtests/1770075.html | 8 + dom/media/webrtc/tests/crashtests/1789908.html | 25 + dom/media/webrtc/tests/crashtests/1799168.html | 16 + dom/media/webrtc/tests/crashtests/1816708.html | 21 + dom/media/webrtc/tests/crashtests/1821477.html | 16 + dom/media/webrtc/tests/crashtests/crashtests.list | 7 + dom/media/webrtc/tests/fuzztests/moz.build | 22 + .../webrtc/tests/fuzztests/sdp_parser_libfuzz.cpp | 30 + .../mochitests/NetworkPreparationChromeScript.js | 43 + .../tests/mochitests/addTurnsSelfsignedCert.js | 32 + dom/media/webrtc/tests/mochitests/blacksilence.js | 134 + dom/media/webrtc/tests/mochitests/dataChannel.js | 352 ++ dom/media/webrtc/tests/mochitests/head.js | 1445 ++++++ .../tests/mochitests/helpers_from_wpt/sdp.js | 889 ++++ dom/media/webrtc/tests/mochitests/iceTestUtils.js | 302 ++ .../tests/mochitests/identity/identityPcTest.js | 79 + .../webrtc/tests/mochitests/identity/idp-bad.js | 1 + .../webrtc/tests/mochitests/identity/idp-min.js | 24 + .../mochitests/identity/idp-redirect-http-trick.js | 3 + .../identity/idp-redirect-http-trick.js^headers^ | 2 + .../tests/mochitests/identity/idp-redirect-http.js | 3 + .../identity/idp-redirect-http.js^headers^ | 2 + .../identity/idp-redirect-https-double.js | 3 + .../identity/idp-redirect-https-double.js^headers^ | 2 + .../identity/idp-redirect-https-odd-path.js | 3 + .../idp-redirect-https-odd-path.js^headers^ | 2 + .../mochitests/identity/idp-redirect-https.js | 3 + .../identity/idp-redirect-https.js^headers^ | 2 + dom/media/webrtc/tests/mochitests/identity/idp.js | 119 + dom/media/webrtc/tests/mochitests/identity/idp.sjs | 18 + .../webrtc/tests/mochitests/identity/login.html | 31 + .../webrtc/tests/mochitests/identity/mochitest.ini | 47 + .../mochitests/identity/test_fingerprints.html | 91 + .../identity/test_getIdentityAssertion.html | 101 + .../tests/mochitests/identity/test_idpproxy.html | 178 + .../mochitests/identity/test_loginNeeded.html | 72 + .../test_peerConnection_asymmetricIsolation.html | 31 + .../identity/test_peerConnection_peerIdentity.html | 21 + .../identity/test_setIdentityProvider.html | 67 + .../test_setIdentityProviderWithErrors.html | 57 + .../webrtc/tests/mochitests/mediaStreamPlayback.js | 241 + dom/media/webrtc/tests/mochitests/mochitest.ini | 65 + .../tests/mochitests/mochitest_datachannel.ini | 52 + .../tests/mochitests/mochitest_getusermedia.ini | 105 + .../tests/mochitests/mochitest_peerconnection.ini | 311 ++ dom/media/webrtc/tests/mochitests/network.js | 16 + dom/media/webrtc/tests/mochitests/nonTrickleIce.js | 97 + dom/media/webrtc/tests/mochitests/parser_rtp.js | 131 + dom/media/webrtc/tests/mochitests/pc.js | 2495 +++++++++++ .../peerconnection_audio_forced_sample_rate.js | 32 + dom/media/webrtc/tests/mochitests/sdpUtils.js | 398 ++ dom/media/webrtc/tests/mochitests/simulcast.js | 232 + dom/media/webrtc/tests/mochitests/stats.js | 1596 +++++++ dom/media/webrtc/tests/mochitests/templates.js | 615 +++ .../webrtc/tests/mochitests/test_1488832.html | 37 + .../webrtc/tests/mochitests/test_1717318.html | 26 + dom/media/webrtc/tests/mochitests/test_a_noOp.html | 32 + .../mochitests/test_dataChannel_basicAudio.html | 25 + .../test_dataChannel_basicAudioVideo.html | 26 + .../test_dataChannel_basicAudioVideoCombined.html | 26 + .../test_dataChannel_basicAudioVideoNoBundle.html | 27 + .../mochitests/test_dataChannel_basicDataOnly.html | 24 + .../mochitests/test_dataChannel_basicVideo.html | 25 + .../mochitests/test_dataChannel_bug1013809.html | 27 + ...test_dataChannel_dataOnlyBufferedAmountLow.html | 25 + .../mochitests/test_dataChannel_dtlsVersions.html | 38 + .../test_dataChannel_hostnameObfuscation.html | 59 + .../tests/mochitests/test_dataChannel_noOffer.html | 33 + .../tests/mochitests/test_dataChannel_stats.html | 50 + .../mochitests/test_defaultAudioConstraints.html | 80 + .../tests/mochitests/test_enumerateDevices.html | 141 + .../test_enumerateDevices_getUserMediaFake.html | 63 + .../mochitests/test_enumerateDevices_iframe.html | 28 + .../test_enumerateDevices_iframe_pre_gum.html | 22 + .../mochitests/test_enumerateDevices_legacy.html | 147 + .../test_enumerateDevices_navigation.html | 54 + .../mochitests/test_fingerprinting_resistance.html | 112 + .../tests/mochitests/test_forceSampleRate.html | 23 + .../test_getUserMedia_GC_MediaStream.html | 59 + .../test_getUserMedia_active_autoplay.html | 61 + .../test_getUserMedia_addTrackRemoveTrack.html | 169 + ...t_getUserMedia_addtrack_removetrack_events.html | 110 + .../mochitests/test_getUserMedia_audioCapture.html | 104 + .../test_getUserMedia_audioConstraints.html | 93 + ...erMedia_audioConstraints_concurrentIframes.html | 157 + ...erMedia_audioConstraints_concurrentStreams.html | 123 + .../mochitests/test_getUserMedia_basicAudio.html | 27 + .../test_getUserMedia_basicAudio_loopback.html | 99 + .../test_getUserMedia_basicScreenshare.html | 260 ++ .../test_getUserMedia_basicTabshare.html | 67 + .../mochitests/test_getUserMedia_basicVideo.html | 30 + .../test_getUserMedia_basicVideoAudio.html | 30 + ...erMedia_basicVideo_playAfterLoadedmetadata.html | 42 + .../test_getUserMedia_basicWindowshare.html | 39 + .../mochitests/test_getUserMedia_bug1223696.html | 54 + .../mochitests/test_getUserMedia_callbacks.html | 35 + .../mochitests/test_getUserMedia_constraints.html | 166 + .../test_getUserMedia_cubebDisabled.html | 42 + ...test_getUserMedia_cubebDisabledFakeStreams.html | 43 + .../mochitests/test_getUserMedia_getTrackById.html | 50 + .../mochitests/test_getUserMedia_gumWithinGum.html | 38 + .../test_getUserMedia_loadedmetadata.html | 39 + ...est_getUserMedia_mediaElementCapture_audio.html | 116 + ...st_getUserMedia_mediaElementCapture_tracks.html | 179 + ...est_getUserMedia_mediaElementCapture_video.html | 91 + .../test_getUserMedia_mediaStreamClone.html | 258 ++ .../test_getUserMedia_mediaStreamConstructors.html | 171 + .../test_getUserMedia_mediaStreamTrackClone.html | 170 + .../test_getUserMedia_nonDefaultRate.html | 37 + .../mochitests/test_getUserMedia_peerIdentity.html | 51 + .../mochitests/test_getUserMedia_permission.html | 104 + .../test_getUserMedia_permission_iframe.html | 30 + .../test_getUserMedia_playAudioTwice.html | 25 + .../test_getUserMedia_playVideoAudioTwice.html | 26 + .../test_getUserMedia_playVideoTwice.html | 26 + .../mochitests/test_getUserMedia_scarySources.html | 51 + .../test_getUserMedia_spinEventLoop.html | 28 + .../test_getUserMedia_trackCloneCleanup.html | 32 + .../mochitests/test_getUserMedia_trackEnded.html | 68 + .../webrtc/tests/mochitests/test_groupId.html | 53 + .../webrtc/tests/mochitests/test_multi_mics.html | 66 + .../tests/mochitests/test_ondevicechange.html | 180 + ...nection_addAudioTrackToExistingVideoStream.html | 55 + .../test_peerConnection_addDataChannel.html | 33 + ...test_peerConnection_addDataChannelNoBundle.html | 44 + .../test_peerConnection_addSecondAudioStream.html | 45 + ...eerConnection_addSecondAudioStreamNoBundle.html | 53 + .../test_peerConnection_addSecondVideoStream.html | 53 + ...eerConnection_addSecondVideoStreamNoBundle.html | 60 + ...peerConnection_addtrack_removetrack_events.html | 75 + ...eerConnection_answererAddSecondAudioStream.html | 32 + .../test_peerConnection_audioChannels.html | 102 + .../test_peerConnection_audioCodecs.html | 81 + ...st_peerConnection_audioContributingSources.html | 144 + ...onnection_audioRenegotiationInactiveAnswer.html | 69 + ...peerConnection_audioSynchronizationSources.html | 95 + ..._audioSynchronizationSourcesUnidirectional.html | 54 + .../mochitests/test_peerConnection_basicAudio.html | 25 + ...onnection_basicAudioDynamicPtMissingRtpmap.html | 36 + .../test_peerConnection_basicAudioNATRelay.html | 47 + .../test_peerConnection_basicAudioNATRelayTCP.html | 42 + ...onnection_basicAudioNATRelayTCPWithStun300.html | 54 + .../test_peerConnection_basicAudioNATRelayTLS.html | 41 + ...erConnection_basicAudioNATRelayWithStun300.html | 53 + .../test_peerConnection_basicAudioNATSrflx.html | 44 + ...est_peerConnection_basicAudioNoisyUDPBlock.html | 41 + ...test_peerConnection_basicAudioPcmaPcmuOnly.html | 39 + .../test_peerConnection_basicAudioRelayPolicy.html | 83 + .../test_peerConnection_basicAudioRequireEOC.html | 35 + ...ection_basicAudioVerifyRtpHeaderExtensions.html | 63 + .../test_peerConnection_basicAudioVideo.html | 24 + ...est_peerConnection_basicAudioVideoCombined.html | 24 + ...est_peerConnection_basicAudioVideoNoBundle.html | 25 + ...onnection_basicAudioVideoNoBundleNoRtcpMux.html | 39 + ...st_peerConnection_basicAudioVideoNoRtcpMux.html | 38 + ...peerConnection_basicAudioVideoTransceivers.html | 31 + ...peerConnection_basicAudioVideoVerifyExtmap.html | 97 + ...ection_basicAudioVideoVerifyExtmapSendonly.html | 97 + ...ction_basicAudioVideoVerifyTooLongMidFails.html | 47 + ...erConnection_basicAudio_forced_higher_rate.html | 19 + ...eerConnection_basicAudio_forced_lower_rate.html | 19 + .../test_peerConnection_basicH264Video.html | 26 + .../test_peerConnection_basicScreenshare.html | 56 + .../mochitests/test_peerConnection_basicVideo.html | 23 + ...ection_basicVideoVerifyRtpHeaderExtensions.html | 82 + .../test_peerConnection_basicWindowshare.html | 25 + .../mochitests/test_peerConnection_bug1013809.html | 25 + .../mochitests/test_peerConnection_bug1042791.html | 36 + .../mochitests/test_peerConnection_bug1227781.html | 37 + .../mochitests/test_peerConnection_bug1512281.html | 47 + .../mochitests/test_peerConnection_bug1773067.html | 32 + .../mochitests/test_peerConnection_bug822674.html | 26 + .../mochitests/test_peerConnection_bug825703.html | 140 + .../mochitests/test_peerConnection_bug827843.html | 50 + .../mochitests/test_peerConnection_bug834153.html | 36 + .../mochitests/test_peerConnection_callbacks.html | 86 + ...est_peerConnection_captureStream_canvas_2d.html | 81 + ...rConnection_captureStream_canvas_2d_noSSRC.html | 83 + ..._peerConnection_captureStream_canvas_webgl.html | 130 + .../test_peerConnection_capturedVideo.html | 81 + .../test_peerConnection_certificates.html | 185 + .../test_peerConnection_checkPacketDumpHook.html | 107 + .../mochitests/test_peerConnection_close.html | 134 + .../test_peerConnection_closeDuringIce.html | 79 + ...est_peerConnection_codecNegotiationFailure.html | 111 + .../test_peerConnection_constructedStream.html | 67 + ...peerConnection_disabledVideoPreNegotiation.html | 45 + .../test_peerConnection_encodingsNegotiation.html | 85 + .../test_peerConnection_errorCallbacks.html | 55 + .../test_peerConnection_extmapRenegotiation.html | 325 ++ ...nection_forwarding_basicAudioVideoCombined.html | 41 + ..._peerConnection_gatherWithSetConfiguration.html | 450 ++ .../test_peerConnection_gatherWithStun300.html | 269 ++ .../test_peerConnection_gatherWithStun300IPv6.html | 283 ++ .../mochitests/test_peerConnection_glean.html | 488 ++ .../mochitests/test_peerConnection_iceFailure.html | 84 + .../mochitests/test_peerConnection_insertDTMF.html | 76 + .../test_peerConnection_localReofferRollback.html | 44 + .../test_peerConnection_localRollback.html | 47 + .../test_peerConnection_maxFsConstraint.html | 112 + ...onnection_multiple_captureStream_canvas_2d.html | 115 + .../test_peerConnection_noTrickleAnswer.html | 25 + .../test_peerConnection_noTrickleOffer.html | 25 + .../test_peerConnection_noTrickleOfferAnswer.html | 26 + .../test_peerConnection_nonDefaultRate.html | 200 + ...t_peerConnection_offerRequiresReceiveAudio.html | 23 + ...t_peerConnection_offerRequiresReceiveVideo.html | 23 + ...rConnection_offerRequiresReceiveVideoAudio.html | 23 + .../test_peerConnection_portRestrictions.html | 63 + .../test_peerConnection_promiseSendOnly.html | 61 + .../test_peerConnection_recordReceiveTrack.html | 101 + .../mochitests/test_peerConnection_relayOnly.html | 60 + .../test_peerConnection_remoteReofferRollback.html | 50 + .../test_peerConnection_remoteRollback.html | 51 + .../test_peerConnection_removeAudioTrack.html | 57 + ...est_peerConnection_removeThenAddAudioTrack.html | 87 + ...Connection_removeThenAddAudioTrackNoBundle.html | 76 + ...est_peerConnection_removeThenAddVideoTrack.html | 98 + ...Connection_removeThenAddVideoTrackNoBundle.html | 89 + .../test_peerConnection_removeVideoTrack.html | 64 + ...st_peerConnection_renderAfterRenegotiation.html | 89 + ...ction_replaceNullTrackThenRenegotiateAudio.html | 53 + ...ction_replaceNullTrackThenRenegotiateVideo.html | 63 + .../test_peerConnection_replaceTrack.html | 187 + .../test_peerConnection_replaceTrack_camera.html | 48 + .../test_peerConnection_replaceTrack_disabled.html | 60 + ...est_peerConnection_replaceTrack_microphone.html | 46 + ...peerConnection_replaceVideoThenRenegotiate.html | 74 + .../mochitests/test_peerConnection_restartIce.html | 41 + .../test_peerConnection_restartIceBadAnswer.html | 58 + ...onnection_restartIceLocalAndRemoteRollback.html | 82 + ...eLocalAndRemoteRollbackNoSubsequentRestart.html | 77 + ...est_peerConnection_restartIceLocalRollback.html | 76 + ...restartIceLocalRollbackNoSubsequentRestart.html | 60 + .../test_peerConnection_restartIceNoBundle.html | 43 + ...peerConnection_restartIceNoBundleNoRtcpMux.html | 44 + .../test_peerConnection_restartIceNoRtcpMux.html | 43 + ...rConnection_restrictBandwidthTargetBitrate.html | 29 + ...t_peerConnection_restrictBandwidthWithTias.html | 30 + .../mochitests/test_peerConnection_rtcp_rsize.html | 81 + .../test_peerConnection_scaleResolution.html | 119 + ...onnection_scaleResolution_oldSetParameters.html | 122 + ...t_peerConnection_sender_and_receiver_stats.html | 73 + ...rConnection_setLocalAnswerInHaveLocalOffer.html | 34 + ...test_peerConnection_setLocalAnswerInStable.html | 34 + ...rConnection_setLocalOfferInHaveRemoteOffer.html | 31 + .../test_peerConnection_setParameters.html | 470 ++ ..._peerConnection_setParameters_maxFramerate.html | 63 + ...etParameters_maxFramerate_oldSetParameters.html | 60 + ...rConnection_setParameters_oldSetParameters.html | 86 + ...ection_setParameters_scaleResolutionDownBy.html | 98 + ...ers_scaleResolutionDownBy_oldSetParameters.html | 96 + ...onnection_setRemoteAnswerInHaveRemoteOffer.html | 34 + ...est_peerConnection_setRemoteAnswerInStable.html | 34 + ...rConnection_setRemoteOfferInHaveLocalOffer.html | 37 + .../test_peerConnection_simulcastAnswer.html | 121 + ...peerConnection_simulcastAnswer_lowResFirst.html | 113 + ...mulcastAnswer_lowResFirst_oldSetParameters.html | 115 + ...onnection_simulcastAnswer_oldSetParameters.html | 115 + ...test_peerConnection_simulcastOddResolution.html | 183 + ...on_simulcastOddResolution_oldSetParameters.html | 172 + .../test_peerConnection_simulcastOffer.html | 109 + ..._peerConnection_simulcastOffer_lowResFirst.html | 109 + ...imulcastOffer_lowResFirst_oldSetParameters.html | 112 + ...Connection_simulcastOffer_oldSetParameters.html | 112 + .../mochitests/test_peerConnection_stats.html | 42 + .../test_peerConnection_stats_jitter.html | 58 + .../test_peerConnection_stats_oneway.html | 65 + .../test_peerConnection_stats_relayProtocol.html | 58 + .../test_peerConnection_stereoFmtpPref.html | 61 + .../test_peerConnection_syncSetDescription.html | 53 + .../test_peerConnection_telephoneEventFirst.html | 56 + ...t_peerConnection_threeUnbundledConnections.html | 134 + .../test_peerConnection_throwInCallbacks.html | 83 + .../mochitests/test_peerConnection_toJSON.html | 39 + .../test_peerConnection_trackDisabling.html | 108 + .../test_peerConnection_trackDisabling_clones.html | 162 + ...test_peerConnection_trackless_sender_stats.html | 56 + .../test_peerConnection_twoAudioStreams.html | 23 + ...t_peerConnection_twoAudioTracksInOneStream.html | 37 + .../test_peerConnection_twoAudioVideoStreams.html | 26 + ...eerConnection_twoAudioVideoStreamsCombined.html | 70 + ...ction_twoAudioVideoStreamsCombinedNoBundle.html | 107 + .../test_peerConnection_twoVideoStreams.html | 23 + ...t_peerConnection_twoVideoTracksInOneStream.html | 37 + ...erConnection_verifyAudioAfterRenegotiation.html | 99 + .../test_peerConnection_verifyDescriptions.html | 58 + ...erConnection_verifyVideoAfterRenegotiation.html | 123 + .../test_peerConnection_videoCodecs.html | 142 + ...onnection_videoRenegotiationInactiveAnswer.html | 95 + .../mochitests/test_peerConnection_webAudio.html | 43 + .../webrtc/tests/mochitests/test_selftest.html | 37 + .../webrtc/tests/mochitests/test_setSinkId.html | 83 + .../test_setSinkId_default_addTrack.html | 52 + .../mochitests/test_setSinkId_preMutedElement.html | 100 + .../tests/mochitests/test_unfocused_pref.html | 49 + dom/media/webrtc/tests/mochitests/turnConfig.js | 16 + dom/media/webrtc/third_party_build/README.md | 17 + .../third_party_build/build_no_op_commits.sh | 126 + .../third_party_build/commit-build-file-changes.sh | 47 + .../webrtc/third_party_build/default_config_env | 42 + .../third_party_build/detect_upstream_revert.sh | 93 + .../webrtc/third_party_build/elm_arcconfig.patch | 10 + dom/media/webrtc/third_party_build/elm_rebase.sh | 247 ++ .../webrtc/third_party_build/extract-for-git.py | 145 + .../third_party_build/fast-forward-libwebrtc.sh | 256 ++ .../webrtc/third_party_build/fetch_github_repo.py | 122 + .../webrtc/third_party_build/filter_git_changes.py | 72 + .../webrtc/third_party_build/gn-configs/README.md | 16 + .../third_party_build/gn-configs/webrtc.json | 83 + .../webrtc/third_party_build/lookup_branch_head.py | 98 + dom/media/webrtc/third_party_build/loop-ff.sh | 236 + .../third_party_build/make_upstream_revert_noop.sh | 101 + .../third_party_build/pre-warmed-milestone.cache | 1 + dom/media/webrtc/third_party_build/prep_repo.sh | 93 + .../third_party_build/push_official_branch.sh | 50 + .../third_party_build/restore_elm_arcconfig.py | 27 + .../third_party_build/restore_patch_stack.py | 107 + .../webrtc/third_party_build/run_operations.py | 78 + .../webrtc/third_party_build/save_patch_stack.py | 142 + .../third_party_build/update_default_config.sh | 46 + .../webrtc/third_party_build/use_config_env.sh | 89 + .../webrtc/third_party_build/vendor-libwebrtc.py | 419 ++ .../webrtc/third_party_build/verify_vendoring.sh | 55 + dom/media/webrtc/third_party_build/webrtc.mozbuild | 40 + .../third_party_build/write_default_config.py | 104 + dom/media/webrtc/transport/README | 45 + dom/media/webrtc/transport/SrtpFlow.cpp | 259 ++ dom/media/webrtc/transport/SrtpFlow.h | 69 + .../webrtc/transport/WebrtcTCPSocketWrapper.cpp | 123 + .../webrtc/transport/WebrtcTCPSocketWrapper.h | 69 + dom/media/webrtc/transport/build/moz.build | 44 + dom/media/webrtc/transport/common.build | 94 + dom/media/webrtc/transport/dtlsidentity.cpp | 331 ++ dom/media/webrtc/transport/dtlsidentity.h | 101 + dom/media/webrtc/transport/fuzztest/moz.build | 31 + .../transport/fuzztest/stun_parser_libfuzz.cpp | 35 + .../transport/ipc/NrIceStunAddrMessageUtils.h | 54 + dom/media/webrtc/transport/ipc/PStunAddrsParams.h | 33 + .../webrtc/transport/ipc/PStunAddrsRequest.ipdl | 35 + .../webrtc/transport/ipc/PWebrtcTCPSocket.ipdl | 42 + .../webrtc/transport/ipc/StunAddrsRequestChild.cpp | 45 + .../webrtc/transport/ipc/StunAddrsRequestChild.h | 64 + .../transport/ipc/StunAddrsRequestParent.cpp | 262 ++ .../webrtc/transport/ipc/StunAddrsRequestParent.h | 82 + .../webrtc/transport/ipc/WebrtcProxyConfig.ipdlh | 23 + dom/media/webrtc/transport/ipc/WebrtcTCPSocket.cpp | 785 ++++ dom/media/webrtc/transport/ipc/WebrtcTCPSocket.h | 104 + .../webrtc/transport/ipc/WebrtcTCPSocketCallback.h | 28 + .../webrtc/transport/ipc/WebrtcTCPSocketChild.cpp | 96 + .../webrtc/transport/ipc/WebrtcTCPSocketChild.h | 47 + .../webrtc/transport/ipc/WebrtcTCPSocketLog.cpp | 11 + .../webrtc/transport/ipc/WebrtcTCPSocketLog.h | 20 + .../webrtc/transport/ipc/WebrtcTCPSocketParent.cpp | 122 + .../webrtc/transport/ipc/WebrtcTCPSocketParent.h | 59 + dom/media/webrtc/transport/ipc/moz.build | 54 + dom/media/webrtc/transport/logging.h | 65 + dom/media/webrtc/transport/m_cpp_utils.h | 25 + dom/media/webrtc/transport/mdns_service/Cargo.toml | 14 + .../webrtc/transport/mdns_service/mdns_service.h | 28 + dom/media/webrtc/transport/mdns_service/src/lib.rs | 843 ++++ dom/media/webrtc/transport/mediapacket.cpp | 145 + dom/media/webrtc/transport/mediapacket.h | 117 + dom/media/webrtc/transport/moz.build | 23 + .../webrtc/transport/nr_socket_proxy_config.cpp | 34 + .../webrtc/transport/nr_socket_proxy_config.h | 41 + dom/media/webrtc/transport/nr_socket_prsock.cpp | 1785 ++++++++ dom/media/webrtc/transport/nr_socket_prsock.h | 320 ++ dom/media/webrtc/transport/nr_socket_tcp.cpp | 310 ++ dom/media/webrtc/transport/nr_socket_tcp.h | 117 + dom/media/webrtc/transport/nr_timer.cpp | 256 ++ dom/media/webrtc/transport/nricectx.cpp | 1106 +++++ dom/media/webrtc/transport/nricectx.h | 421 ++ dom/media/webrtc/transport/nricemediastream.cpp | 709 +++ dom/media/webrtc/transport/nricemediastream.h | 225 + dom/media/webrtc/transport/nriceresolver.cpp | 234 + dom/media/webrtc/transport/nriceresolver.h | 119 + dom/media/webrtc/transport/nriceresolverfake.cpp | 174 + dom/media/webrtc/transport/nriceresolverfake.h | 137 + dom/media/webrtc/transport/nricestunaddr.cpp | 93 + dom/media/webrtc/transport/nricestunaddr.h | 36 + .../webrtc/transport/nrinterfaceprioritizer.cpp | 258 ++ .../webrtc/transport/nrinterfaceprioritizer.h | 17 + dom/media/webrtc/transport/rlogconnector.cpp | 186 + dom/media/webrtc/transport/rlogconnector.h | 127 + dom/media/webrtc/transport/runnable_utils.h | 222 + dom/media/webrtc/transport/sigslot.h | 619 +++ dom/media/webrtc/transport/simpletokenbucket.cpp | 60 + dom/media/webrtc/transport/simpletokenbucket.h | 54 + dom/media/webrtc/transport/srtp/README_MOZILLA | 7 + dom/media/webrtc/transport/srtp/moz.build | 8 + dom/media/webrtc/transport/stun_socket_filter.cpp | 432 ++ dom/media/webrtc/transport/stun_socket_filter.h | 41 + .../webrtc/transport/test/TestSyncRunnable.cpp | 56 + .../test/buffered_stun_socket_unittest.cpp | 245 ++ dom/media/webrtc/transport/test/dummysocket.h | 217 + .../transport/test/gtest_ringbuffer_dumper.h | 78 + dom/media/webrtc/transport/test/gtest_utils.h | 201 + dom/media/webrtc/transport/test/ice_unittest.cpp | 4400 +++++++++++++++++++ dom/media/webrtc/transport/test/moz.build | 104 + .../webrtc/transport/test/mtransport_test_utils.h | 57 + .../transport/test/multi_tcp_socket_unittest.cpp | 501 +++ .../webrtc/transport/test/nrappkit_unittest.cpp | 123 + .../test/proxy_tunnel_socket_unittest.cpp | 277 ++ .../transport/test/rlogconnector_unittest.cpp | 255 ++ .../transport/test/runnable_utils_unittest.cpp | 353 ++ dom/media/webrtc/transport/test/sctp_unittest.cpp | 381 ++ .../transport/test/simpletokenbucket_unittest.cpp | 114 + .../test/sockettransportservice_unittest.cpp | 181 + dom/media/webrtc/transport/test/stunserver.cpp | 652 +++ dom/media/webrtc/transport/test/stunserver.h | 123 + .../transport/test/test_nr_socket_ice_unittest.cpp | 409 ++ .../transport/test/test_nr_socket_unittest.cpp | 800 ++++ .../webrtc/transport/test/transport_unittests.cpp | 1400 ++++++ dom/media/webrtc/transport/test/turn_unittest.cpp | 432 ++ .../transport/test/webrtcproxychannel_unittest.cpp | 754 ++++ dom/media/webrtc/transport/test_nr_socket.cpp | 1135 +++++ dom/media/webrtc/transport/test_nr_socket.h | 370 ++ dom/media/webrtc/transport/third_party/moz.build | 39 + .../webrtc/transport/third_party/nICEr/COPYRIGHT | 36 + .../webrtc/transport/third_party/nICEr/README | 74 + .../webrtc/transport/third_party/nICEr/moz.yaml | 117 + .../webrtc/transport/third_party/nICEr/nicer.gyp | 276 ++ .../third_party/nICEr/non-unified-build.patch | 40 + .../third_party/nICEr/src/crypto/nr_crypto.c | 67 + .../third_party/nICEr/src/crypto/nr_crypto.h | 52 + .../third_party/nICEr/src/ice/ice_candidate.c | 1052 +++++ .../third_party/nICEr/src/ice/ice_candidate.h | 124 + .../third_party/nICEr/src/ice/ice_candidate_pair.c | 689 +++ .../third_party/nICEr/src/ice/ice_candidate_pair.h | 101 + .../third_party/nICEr/src/ice/ice_codeword.h | 41 + .../third_party/nICEr/src/ice/ice_component.c | 1786 ++++++++ .../third_party/nICEr/src/ice/ice_component.h | 111 + .../transport/third_party/nICEr/src/ice/ice_ctx.c | 1125 +++++ .../transport/third_party/nICEr/src/ice/ice_ctx.h | 188 + .../third_party/nICEr/src/ice/ice_handler.h | 84 + .../third_party/nICEr/src/ice/ice_media_stream.c | 1087 +++++ .../third_party/nICEr/src/ice/ice_media_stream.h | 146 + .../third_party/nICEr/src/ice/ice_parser.c | 564 +++ .../third_party/nICEr/src/ice/ice_peer_ctx.c | 875 ++++ .../third_party/nICEr/src/ice/ice_peer_ctx.h | 101 + .../transport/third_party/nICEr/src/ice/ice_reg.h | 81 + .../third_party/nICEr/src/ice/ice_socket.c | 404 ++ .../third_party/nICEr/src/ice/ice_socket.h | 98 + .../third_party/nICEr/src/net/local_addr.c | 70 + .../third_party/nICEr/src/net/local_addr.h | 62 + .../nICEr/src/net/nr_interface_prioritizer.c | 88 + .../nICEr/src/net/nr_interface_prioritizer.h | 66 + .../third_party/nICEr/src/net/nr_resolver.c | 85 + .../third_party/nICEr/src/net/nr_resolver.h | 96 + .../third_party/nICEr/src/net/nr_socket.c | 187 + .../third_party/nICEr/src/net/nr_socket.h | 123 + .../third_party/nICEr/src/net/nr_socket_local.h | 41 + .../nICEr/src/net/nr_socket_multi_tcp.c | 642 +++ .../nICEr/src/net/nr_socket_multi_tcp.h | 53 + .../third_party/nICEr/src/net/nr_socket_wrapper.c | 84 + .../third_party/nICEr/src/net/nr_socket_wrapper.h | 63 + .../third_party/nICEr/src/net/transport_addr.c | 559 +++ .../third_party/nICEr/src/net/transport_addr.h | 128 + .../third_party/nICEr/src/net/transport_addr_reg.c | 230 + .../third_party/nICEr/src/net/transport_addr_reg.h | 46 + .../third_party/nICEr/src/stun/addrs-bsd.c | 110 + .../third_party/nICEr/src/stun/addrs-bsd.h | 13 + .../third_party/nICEr/src/stun/addrs-netlink.c | 285 ++ .../third_party/nICEr/src/stun/addrs-netlink.h | 45 + .../third_party/nICEr/src/stun/addrs-win32.c | 210 + .../third_party/nICEr/src/stun/addrs-win32.h | 13 + .../transport/third_party/nICEr/src/stun/addrs.c | 176 + .../transport/third_party/nICEr/src/stun/addrs.h | 43 + .../nICEr/src/stun/nr_socket_buffered_stun.c | 656 +++ .../nICEr/src/stun/nr_socket_buffered_stun.h | 66 + .../third_party/nICEr/src/stun/nr_socket_turn.c | 195 + .../third_party/nICEr/src/stun/nr_socket_turn.h | 48 + .../transport/third_party/nICEr/src/stun/stun.h | 218 + .../third_party/nICEr/src/stun/stun_build.c | 611 +++ .../third_party/nICEr/src/stun/stun_build.h | 147 + .../third_party/nICEr/src/stun/stun_client_ctx.c | 888 ++++ .../third_party/nICEr/src/stun/stun_client_ctx.h | 200 + .../third_party/nICEr/src/stun/stun_codec.c | 1550 +++++++ .../third_party/nICEr/src/stun/stun_codec.h | 78 + .../third_party/nICEr/src/stun/stun_hint.c | 245 ++ .../third_party/nICEr/src/stun/stun_hint.h | 44 + .../third_party/nICEr/src/stun/stun_msg.c | 364 ++ .../third_party/nICEr/src/stun/stun_msg.h | 208 + .../third_party/nICEr/src/stun/stun_proc.c | 554 +++ .../third_party/nICEr/src/stun/stun_proc.h | 53 + .../third_party/nICEr/src/stun/stun_reg.h | 58 + .../third_party/nICEr/src/stun/stun_server_ctx.c | 468 ++ .../third_party/nICEr/src/stun/stun_server_ctx.h | 80 + .../third_party/nICEr/src/stun/stun_util.c | 352 ++ .../third_party/nICEr/src/stun/stun_util.h | 62 + .../third_party/nICEr/src/stun/turn_client_ctx.c | 1277 ++++++ .../third_party/nICEr/src/stun/turn_client_ctx.h | 161 + .../transport/third_party/nICEr/src/util/cb_args.c | 57 + .../transport/third_party/nICEr/src/util/cb_args.h | 41 + .../third_party/nICEr/src/util/ice_util.c | 71 + .../third_party/nICEr/src/util/ice_util.h | 41 + .../transport/third_party/nrappkit/COPYRIGHT | 159 + .../webrtc/transport/third_party/nrappkit/README | 133 + .../transport/third_party/nrappkit/README_MOZILLA | 21 + .../transport/third_party/nrappkit/nrappkit.gyp | 251 ++ .../third_party/nrappkit/src/event/async_timer.h | 54 + .../third_party/nrappkit/src/event/async_wait.h | 83 + .../nrappkit/src/event/async_wait_int.h | 62 + .../transport/third_party/nrappkit/src/log/r_log.c | 696 +++ .../transport/third_party/nrappkit/src/log/r_log.h | 85 + .../third_party/nrappkit/src/plugin/nr_plugin.h | 57 + .../src/port/android/include/android_funcs.h | 62 + .../src/port/android/include/csi_platform.h | 55 + .../nrappkit/src/port/android/include/sys/ttycom.h | 38 + .../nrappkit/src/port/android/port-impl.mk | 31 + .../src/port/darwin/include/csi_platform.h | 57 + .../nrappkit/src/port/generic/include/sys/queue.h | 562 +++ .../nrappkit/src/port/linux/include/csi_platform.h | 55 + .../nrappkit/src/port/linux/include/linux_funcs.h | 62 + .../nrappkit/src/port/linux/include/sys/ttycom.h | 38 + .../nrappkit/src/port/linux/port-impl.mk | 31 + .../nrappkit/src/port/win32/include/csi_platform.h | 107 + .../third_party/nrappkit/src/registry/c2ru.c | 320 ++ .../third_party/nrappkit/src/registry/c2ru.h | 96 + .../third_party/nrappkit/src/registry/registry.c | 604 +++ .../third_party/nrappkit/src/registry/registry.h | 154 + .../nrappkit/src/registry/registry_int.h | 97 + .../nrappkit/src/registry/registry_local.c | 1168 +++++ .../nrappkit/src/registry/registry_vtbl.h | 96 + .../third_party/nrappkit/src/registry/registrycb.c | 440 ++ .../third_party/nrappkit/src/share/nr_api.h | 51 + .../third_party/nrappkit/src/share/nr_common.h | 108 + .../third_party/nrappkit/src/share/nr_reg_keys.h | 167 + .../third_party/nrappkit/src/stats/nrstats.h | 118 + .../third_party/nrappkit/src/util/byteorder.c | 73 + .../third_party/nrappkit/src/util/byteorder.h | 47 + .../transport/third_party/nrappkit/src/util/hex.c | 109 + .../transport/third_party/nrappkit/src/util/hex.h | 47 + .../third_party/nrappkit/src/util/libekr/assoc.h | 90 + .../third_party/nrappkit/src/util/libekr/debug.c | 127 + .../third_party/nrappkit/src/util/libekr/debug.h | 94 + .../third_party/nrappkit/src/util/libekr/r_assoc.c | 539 +++ .../third_party/nrappkit/src/util/libekr/r_assoc.h | 126 + .../nrappkit/src/util/libekr/r_common.h | 100 + .../third_party/nrappkit/src/util/libekr/r_crc32.c | 175 + .../third_party/nrappkit/src/util/libekr/r_crc32.h | 14 + .../third_party/nrappkit/src/util/libekr/r_data.c | 248 ++ .../third_party/nrappkit/src/util/libekr/r_data.h | 108 + .../nrappkit/src/util/libekr/r_defaults.h | 91 + .../nrappkit/src/util/libekr/r_errors.c | 136 + .../nrappkit/src/util/libekr/r_errors.h | 127 + .../nrappkit/src/util/libekr/r_includes.h | 98 + .../third_party/nrappkit/src/util/libekr/r_list.c | 273 ++ .../third_party/nrappkit/src/util/libekr/r_list.h | 106 + .../nrappkit/src/util/libekr/r_macros.h | 137 + .../nrappkit/src/util/libekr/r_memory.c | 198 + .../nrappkit/src/util/libekr/r_memory.h | 101 + .../nrappkit/src/util/libekr/r_replace.c | 107 + .../nrappkit/src/util/libekr/r_thread.h | 68 + .../third_party/nrappkit/src/util/libekr/r_time.c | 235 + .../third_party/nrappkit/src/util/libekr/r_time.h | 109 + .../third_party/nrappkit/src/util/libekr/r_types.h | 213 + .../third_party/nrappkit/src/util/p_buf.c | 215 + .../third_party/nrappkit/src/util/p_buf.h | 72 + .../transport/third_party/nrappkit/src/util/util.c | 775 ++++ .../transport/third_party/nrappkit/src/util/util.h | 73 + dom/media/webrtc/transport/transportflow.cpp | 74 + dom/media/webrtc/transport/transportflow.h | 105 + dom/media/webrtc/transport/transportlayer.cpp | 49 + dom/media/webrtc/transport/transportlayer.h | 108 + dom/media/webrtc/transport/transportlayerdtls.cpp | 1558 +++++++ dom/media/webrtc/transport/transportlayerdtls.h | 187 + dom/media/webrtc/transport/transportlayerice.cpp | 168 + dom/media/webrtc/transport/transportlayerice.h | 60 + dom/media/webrtc/transport/transportlayerlog.cpp | 48 + dom/media/webrtc/transport/transportlayerlog.h | 38 + .../webrtc/transport/transportlayerloopback.cpp | 119 + .../webrtc/transport/transportlayerloopback.h | 109 + dom/media/webrtc/transport/transportlayersrtp.cpp | 222 + dom/media/webrtc/transport/transportlayersrtp.h | 43 + dom/media/webrtc/transportbridge/MediaPipeline.cpp | 1655 +++++++ dom/media/webrtc/transportbridge/MediaPipeline.h | 454 ++ .../webrtc/transportbridge/MediaPipelineFilter.cpp | 153 + .../webrtc/transportbridge/MediaPipelineFilter.h | 89 + dom/media/webrtc/transportbridge/RtpLogger.cpp | 67 + dom/media/webrtc/transportbridge/RtpLogger.h | 28 + dom/media/webrtc/transportbridge/moz.build | 27 + 758 files changed, 150508 insertions(+) create mode 100644 dom/media/webrtc/CubebDeviceEnumerator.cpp create mode 100644 dom/media/webrtc/CubebDeviceEnumerator.h create mode 100644 dom/media/webrtc/MediaEngine.h create mode 100644 dom/media/webrtc/MediaEngineFake.cpp create mode 100644 dom/media/webrtc/MediaEngineFake.h create mode 100644 dom/media/webrtc/MediaEnginePrefs.h create mode 100644 dom/media/webrtc/MediaEngineRemoteVideoSource.cpp create mode 100644 dom/media/webrtc/MediaEngineRemoteVideoSource.h create mode 100644 dom/media/webrtc/MediaEngineSource.cpp create mode 100644 dom/media/webrtc/MediaEngineSource.h create mode 100644 dom/media/webrtc/MediaEngineWebRTC.cpp create mode 100644 dom/media/webrtc/MediaEngineWebRTC.h create mode 100644 dom/media/webrtc/MediaEngineWebRTCAudio.cpp create mode 100644 dom/media/webrtc/MediaEngineWebRTCAudio.h create mode 100644 dom/media/webrtc/MediaTrackConstraints.cpp create mode 100644 dom/media/webrtc/MediaTrackConstraints.h create mode 100644 dom/media/webrtc/MediaTransportChild.h create mode 100644 dom/media/webrtc/MediaTransportParent.h create mode 100644 dom/media/webrtc/PMediaTransport.ipdl create mode 100644 dom/media/webrtc/PWebrtcGlobal.ipdl create mode 100644 dom/media/webrtc/PeerIdentity.cpp create mode 100644 dom/media/webrtc/PeerIdentity.h create mode 100644 dom/media/webrtc/RTCCertificate.cpp create mode 100644 dom/media/webrtc/RTCCertificate.h create mode 100644 dom/media/webrtc/RTCIdentityProviderRegistrar.cpp create mode 100644 dom/media/webrtc/RTCIdentityProviderRegistrar.h create mode 100644 dom/media/webrtc/SineWaveGenerator.h create mode 100644 dom/media/webrtc/WebrtcGlobal.h create mode 100644 dom/media/webrtc/WebrtcIPCTraits.h create mode 100644 dom/media/webrtc/common/CandidateInfo.h create mode 100644 dom/media/webrtc/common/CommonTypes.h create mode 100644 dom/media/webrtc/common/EncodingConstraints.h create mode 100644 dom/media/webrtc/common/MediaEngineWrapper.h create mode 100644 dom/media/webrtc/common/NullDeleter.h create mode 100644 dom/media/webrtc/common/NullTransport.h create mode 100644 dom/media/webrtc/common/Wrapper.h create mode 100644 dom/media/webrtc/common/YuvStamper.cpp create mode 100644 dom/media/webrtc/common/YuvStamper.h create mode 100644 dom/media/webrtc/common/browser_logging/CSFLog.cpp create mode 100644 dom/media/webrtc/common/browser_logging/CSFLog.h create mode 100644 dom/media/webrtc/common/browser_logging/WebRtcLog.cpp create mode 100644 dom/media/webrtc/common/browser_logging/WebRtcLog.h create mode 100644 dom/media/webrtc/common/csf_common.h create mode 100644 dom/media/webrtc/common/moz.build create mode 100644 dom/media/webrtc/common/time_profiling/timecard.c create mode 100644 dom/media/webrtc/common/time_profiling/timecard.h create mode 100644 dom/media/webrtc/jsapi/MediaTransportHandler.cpp create mode 100644 dom/media/webrtc/jsapi/MediaTransportHandler.h create mode 100644 dom/media/webrtc/jsapi/MediaTransportHandlerIPC.cpp create mode 100644 dom/media/webrtc/jsapi/MediaTransportHandlerIPC.h create mode 100644 dom/media/webrtc/jsapi/MediaTransportParent.cpp create mode 100644 dom/media/webrtc/jsapi/PacketDumper.cpp create mode 100644 dom/media/webrtc/jsapi/PacketDumper.h create mode 100644 dom/media/webrtc/jsapi/PeerConnectionCtx.cpp create mode 100644 dom/media/webrtc/jsapi/PeerConnectionCtx.h create mode 100644 dom/media/webrtc/jsapi/PeerConnectionImpl.cpp create mode 100644 dom/media/webrtc/jsapi/PeerConnectionImpl.h create mode 100644 dom/media/webrtc/jsapi/RTCDTMFSender.cpp create mode 100644 dom/media/webrtc/jsapi/RTCDTMFSender.h create mode 100644 dom/media/webrtc/jsapi/RTCDtlsTransport.cpp create mode 100644 dom/media/webrtc/jsapi/RTCDtlsTransport.h create mode 100644 dom/media/webrtc/jsapi/RTCRtpReceiver.cpp create mode 100644 dom/media/webrtc/jsapi/RTCRtpReceiver.h create mode 100644 dom/media/webrtc/jsapi/RTCRtpSender.cpp create mode 100644 dom/media/webrtc/jsapi/RTCRtpSender.h create mode 100644 dom/media/webrtc/jsapi/RTCRtpTransceiver.cpp create mode 100644 dom/media/webrtc/jsapi/RTCRtpTransceiver.h create mode 100644 dom/media/webrtc/jsapi/RTCSctpTransport.cpp create mode 100644 dom/media/webrtc/jsapi/RTCSctpTransport.h create mode 100644 dom/media/webrtc/jsapi/RTCStatsIdGenerator.cpp create mode 100644 dom/media/webrtc/jsapi/RTCStatsIdGenerator.h create mode 100644 dom/media/webrtc/jsapi/RTCStatsReport.cpp create mode 100644 dom/media/webrtc/jsapi/RTCStatsReport.h create mode 100644 dom/media/webrtc/jsapi/RemoteTrackSource.cpp create mode 100644 dom/media/webrtc/jsapi/RemoteTrackSource.h create mode 100644 dom/media/webrtc/jsapi/WebrtcGlobalChild.h create mode 100644 dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp create mode 100644 dom/media/webrtc/jsapi/WebrtcGlobalInformation.h create mode 100644 dom/media/webrtc/jsapi/WebrtcGlobalParent.h create mode 100644 dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.cpp create mode 100644 dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.h create mode 100644 dom/media/webrtc/jsapi/moz.build create mode 100644 dom/media/webrtc/jsep/JsepCodecDescription.h create mode 100644 dom/media/webrtc/jsep/JsepSession.h create mode 100644 dom/media/webrtc/jsep/JsepSessionImpl.cpp create mode 100644 dom/media/webrtc/jsep/JsepSessionImpl.h create mode 100644 dom/media/webrtc/jsep/JsepTrack.cpp create mode 100644 dom/media/webrtc/jsep/JsepTrack.h create mode 100644 dom/media/webrtc/jsep/JsepTrackEncoding.h create mode 100644 dom/media/webrtc/jsep/JsepTransceiver.h create mode 100644 dom/media/webrtc/jsep/JsepTransport.h create mode 100644 dom/media/webrtc/jsep/SsrcGenerator.cpp create mode 100644 dom/media/webrtc/jsep/SsrcGenerator.h create mode 100644 dom/media/webrtc/jsep/moz.build create mode 100644 dom/media/webrtc/libwebrtcglue/AudioConduit.cpp create mode 100644 dom/media/webrtc/libwebrtcglue/AudioConduit.h create mode 100644 dom/media/webrtc/libwebrtcglue/CallWorkerThread.h create mode 100644 dom/media/webrtc/libwebrtcglue/CodecConfig.h create mode 100644 dom/media/webrtc/libwebrtcglue/GmpVideoCodec.cpp create mode 100644 dom/media/webrtc/libwebrtcglue/GmpVideoCodec.h create mode 100644 dom/media/webrtc/libwebrtcglue/MediaConduitControl.h create mode 100644 dom/media/webrtc/libwebrtcglue/MediaConduitErrors.h create mode 100644 dom/media/webrtc/libwebrtcglue/MediaConduitInterface.cpp create mode 100644 dom/media/webrtc/libwebrtcglue/MediaConduitInterface.h create mode 100644 dom/media/webrtc/libwebrtcglue/MediaDataCodec.cpp create mode 100644 dom/media/webrtc/libwebrtcglue/MediaDataCodec.h create mode 100644 dom/media/webrtc/libwebrtcglue/RtpRtcpConfig.h create mode 100644 dom/media/webrtc/libwebrtcglue/RunningStat.h create mode 100644 dom/media/webrtc/libwebrtcglue/SystemTime.cpp create mode 100644 dom/media/webrtc/libwebrtcglue/SystemTime.h create mode 100644 dom/media/webrtc/libwebrtcglue/TaskQueueWrapper.h create mode 100644 dom/media/webrtc/libwebrtcglue/VideoConduit.cpp create mode 100644 dom/media/webrtc/libwebrtcglue/VideoConduit.h create mode 100644 dom/media/webrtc/libwebrtcglue/VideoStreamFactory.cpp create mode 100644 dom/media/webrtc/libwebrtcglue/VideoStreamFactory.h create mode 100644 dom/media/webrtc/libwebrtcglue/WebrtcCallWrapper.cpp create mode 100644 dom/media/webrtc/libwebrtcglue/WebrtcCallWrapper.h create mode 100644 dom/media/webrtc/libwebrtcglue/WebrtcGmpVideoCodec.cpp create mode 100644 dom/media/webrtc/libwebrtcglue/WebrtcGmpVideoCodec.h create mode 100644 dom/media/webrtc/libwebrtcglue/WebrtcImageBuffer.h create mode 100644 dom/media/webrtc/libwebrtcglue/WebrtcMediaDataDecoderCodec.cpp create mode 100644 dom/media/webrtc/libwebrtcglue/WebrtcMediaDataDecoderCodec.h create mode 100644 dom/media/webrtc/libwebrtcglue/WebrtcMediaDataEncoderCodec.cpp create mode 100644 dom/media/webrtc/libwebrtcglue/WebrtcMediaDataEncoderCodec.h create mode 100644 dom/media/webrtc/libwebrtcglue/WebrtcVideoCodecFactory.cpp create mode 100644 dom/media/webrtc/libwebrtcglue/WebrtcVideoCodecFactory.h create mode 100644 dom/media/webrtc/libwebrtcglue/moz.build create mode 100644 dom/media/webrtc/metrics.yaml create mode 100644 dom/media/webrtc/moz.build create mode 100644 dom/media/webrtc/sdp/HybridSdpParser.cpp create mode 100644 dom/media/webrtc/sdp/HybridSdpParser.h create mode 100644 dom/media/webrtc/sdp/ParsingResultComparer.cpp create mode 100644 dom/media/webrtc/sdp/ParsingResultComparer.h create mode 100644 dom/media/webrtc/sdp/RsdparsaSdp.cpp create mode 100644 dom/media/webrtc/sdp/RsdparsaSdp.h create mode 100644 dom/media/webrtc/sdp/RsdparsaSdpAttributeList.cpp create mode 100644 dom/media/webrtc/sdp/RsdparsaSdpAttributeList.h create mode 100644 dom/media/webrtc/sdp/RsdparsaSdpGlue.cpp create mode 100644 dom/media/webrtc/sdp/RsdparsaSdpGlue.h create mode 100644 dom/media/webrtc/sdp/RsdparsaSdpInc.h create mode 100644 dom/media/webrtc/sdp/RsdparsaSdpMediaSection.cpp create mode 100644 dom/media/webrtc/sdp/RsdparsaSdpMediaSection.h create mode 100644 dom/media/webrtc/sdp/RsdparsaSdpParser.cpp create mode 100644 dom/media/webrtc/sdp/RsdparsaSdpParser.h create mode 100644 dom/media/webrtc/sdp/Sdp.h create mode 100644 dom/media/webrtc/sdp/SdpAttribute.cpp create mode 100644 dom/media/webrtc/sdp/SdpAttribute.h create mode 100644 dom/media/webrtc/sdp/SdpAttributeList.h create mode 100644 dom/media/webrtc/sdp/SdpEnum.h create mode 100644 dom/media/webrtc/sdp/SdpHelper.cpp create mode 100644 dom/media/webrtc/sdp/SdpHelper.h create mode 100644 dom/media/webrtc/sdp/SdpLog.cpp create mode 100644 dom/media/webrtc/sdp/SdpLog.h create mode 100644 dom/media/webrtc/sdp/SdpMediaSection.cpp create mode 100644 dom/media/webrtc/sdp/SdpMediaSection.h create mode 100644 dom/media/webrtc/sdp/SdpParser.h create mode 100644 dom/media/webrtc/sdp/SdpPref.cpp create mode 100644 dom/media/webrtc/sdp/SdpPref.h create mode 100644 dom/media/webrtc/sdp/SdpTelemetry.cpp create mode 100644 dom/media/webrtc/sdp/SdpTelemetry.h create mode 100644 dom/media/webrtc/sdp/SipccSdp.cpp create mode 100644 dom/media/webrtc/sdp/SipccSdp.h create mode 100644 dom/media/webrtc/sdp/SipccSdpAttributeList.cpp create mode 100644 dom/media/webrtc/sdp/SipccSdpAttributeList.h create mode 100644 dom/media/webrtc/sdp/SipccSdpMediaSection.cpp create mode 100644 dom/media/webrtc/sdp/SipccSdpMediaSection.h create mode 100644 dom/media/webrtc/sdp/SipccSdpParser.cpp create mode 100644 dom/media/webrtc/sdp/SipccSdpParser.h create mode 100644 dom/media/webrtc/sdp/moz.build create mode 100644 dom/media/webrtc/sdp/rsdparsa_capi/Cargo.toml create mode 100644 dom/media/webrtc/sdp/rsdparsa_capi/src/attribute.rs create mode 100644 dom/media/webrtc/sdp/rsdparsa_capi/src/lib.rs create mode 100644 dom/media/webrtc/sdp/rsdparsa_capi/src/media_section.rs create mode 100644 dom/media/webrtc/sdp/rsdparsa_capi/src/network.rs create mode 100644 dom/media/webrtc/sdp/rsdparsa_capi/src/types.rs create mode 100644 dom/media/webrtc/tests/crashtests/1770075.html create mode 100644 dom/media/webrtc/tests/crashtests/1789908.html create mode 100644 dom/media/webrtc/tests/crashtests/1799168.html create mode 100644 dom/media/webrtc/tests/crashtests/1816708.html create mode 100644 dom/media/webrtc/tests/crashtests/1821477.html create mode 100644 dom/media/webrtc/tests/crashtests/crashtests.list create mode 100644 dom/media/webrtc/tests/fuzztests/moz.build create mode 100644 dom/media/webrtc/tests/fuzztests/sdp_parser_libfuzz.cpp create mode 100644 dom/media/webrtc/tests/mochitests/NetworkPreparationChromeScript.js create mode 100644 dom/media/webrtc/tests/mochitests/addTurnsSelfsignedCert.js create mode 100644 dom/media/webrtc/tests/mochitests/blacksilence.js create mode 100644 dom/media/webrtc/tests/mochitests/dataChannel.js create mode 100644 dom/media/webrtc/tests/mochitests/head.js create mode 100644 dom/media/webrtc/tests/mochitests/helpers_from_wpt/sdp.js create mode 100644 dom/media/webrtc/tests/mochitests/iceTestUtils.js create mode 100644 dom/media/webrtc/tests/mochitests/identity/identityPcTest.js create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-bad.js create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-min.js create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-redirect-http-trick.js create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-redirect-http-trick.js^headers^ create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-redirect-http.js create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-redirect-http.js^headers^ create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-double.js create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-double.js^headers^ create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-odd-path.js create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-odd-path.js^headers^ create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-redirect-https.js create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp-redirect-https.js^headers^ create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp.js create mode 100644 dom/media/webrtc/tests/mochitests/identity/idp.sjs create mode 100644 dom/media/webrtc/tests/mochitests/identity/login.html create mode 100644 dom/media/webrtc/tests/mochitests/identity/mochitest.ini create mode 100644 dom/media/webrtc/tests/mochitests/identity/test_fingerprints.html create mode 100644 dom/media/webrtc/tests/mochitests/identity/test_getIdentityAssertion.html create mode 100644 dom/media/webrtc/tests/mochitests/identity/test_idpproxy.html create mode 100644 dom/media/webrtc/tests/mochitests/identity/test_loginNeeded.html create mode 100644 dom/media/webrtc/tests/mochitests/identity/test_peerConnection_asymmetricIsolation.html create mode 100644 dom/media/webrtc/tests/mochitests/identity/test_peerConnection_peerIdentity.html create mode 100644 dom/media/webrtc/tests/mochitests/identity/test_setIdentityProvider.html create mode 100644 dom/media/webrtc/tests/mochitests/identity/test_setIdentityProviderWithErrors.html create mode 100644 dom/media/webrtc/tests/mochitests/mediaStreamPlayback.js create mode 100644 dom/media/webrtc/tests/mochitests/mochitest.ini create mode 100644 dom/media/webrtc/tests/mochitests/mochitest_datachannel.ini create mode 100644 dom/media/webrtc/tests/mochitests/mochitest_getusermedia.ini create mode 100644 dom/media/webrtc/tests/mochitests/mochitest_peerconnection.ini create mode 100644 dom/media/webrtc/tests/mochitests/network.js create mode 100644 dom/media/webrtc/tests/mochitests/nonTrickleIce.js create mode 100644 dom/media/webrtc/tests/mochitests/parser_rtp.js create mode 100644 dom/media/webrtc/tests/mochitests/pc.js create mode 100644 dom/media/webrtc/tests/mochitests/peerconnection_audio_forced_sample_rate.js create mode 100644 dom/media/webrtc/tests/mochitests/sdpUtils.js create mode 100644 dom/media/webrtc/tests/mochitests/simulcast.js create mode 100644 dom/media/webrtc/tests/mochitests/stats.js create mode 100644 dom/media/webrtc/tests/mochitests/templates.js create mode 100644 dom/media/webrtc/tests/mochitests/test_1488832.html create mode 100644 dom/media/webrtc/tests/mochitests/test_1717318.html create mode 100644 dom/media/webrtc/tests/mochitests/test_a_noOp.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudio.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideo.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideoCombined.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideoNoBundle.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_basicDataOnly.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_basicVideo.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_bug1013809.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_dataOnlyBufferedAmountLow.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_dtlsVersions.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_hostnameObfuscation.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_noOffer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_dataChannel_stats.html create mode 100644 dom/media/webrtc/tests/mochitests/test_defaultAudioConstraints.html create mode 100644 dom/media/webrtc/tests/mochitests/test_enumerateDevices.html create mode 100644 dom/media/webrtc/tests/mochitests/test_enumerateDevices_getUserMediaFake.html create mode 100644 dom/media/webrtc/tests/mochitests/test_enumerateDevices_iframe.html create mode 100644 dom/media/webrtc/tests/mochitests/test_enumerateDevices_iframe_pre_gum.html create mode 100644 dom/media/webrtc/tests/mochitests/test_enumerateDevices_legacy.html create mode 100644 dom/media/webrtc/tests/mochitests/test_enumerateDevices_navigation.html create mode 100644 dom/media/webrtc/tests/mochitests/test_fingerprinting_resistance.html create mode 100644 dom/media/webrtc/tests/mochitests/test_forceSampleRate.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_GC_MediaStream.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_active_autoplay.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_addTrackRemoveTrack.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_addtrack_removetrack_events.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_audioCapture.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints_concurrentIframes.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints_concurrentStreams.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_basicAudio.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_basicAudio_loopback.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_basicScreenshare.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_basicTabshare.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideo.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideoAudio.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideo_playAfterLoadedmetadata.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_basicWindowshare.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_bug1223696.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_callbacks.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_constraints.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_cubebDisabled.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_cubebDisabledFakeStreams.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_getTrackById.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_gumWithinGum.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_loadedmetadata.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_audio.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_tracks.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_video.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamClone.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamConstructors.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamTrackClone.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_nonDefaultRate.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_peerIdentity.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_permission.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_permission_iframe.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_playAudioTwice.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_playVideoAudioTwice.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_playVideoTwice.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_scarySources.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_spinEventLoop.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_trackCloneCleanup.html create mode 100644 dom/media/webrtc/tests/mochitests/test_getUserMedia_trackEnded.html create mode 100644 dom/media/webrtc/tests/mochitests/test_groupId.html create mode 100644 dom/media/webrtc/tests/mochitests/test_multi_mics.html create mode 100644 dom/media/webrtc/tests/mochitests/test_ondevicechange.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_addAudioTrackToExistingVideoStream.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_addDataChannel.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_addDataChannelNoBundle.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondAudioStream.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondAudioStreamNoBundle.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondVideoStream.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondVideoStreamNoBundle.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_addtrack_removetrack_events.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_answererAddSecondAudioStream.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_audioChannels.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_audioCodecs.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_audioContributingSources.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_audioRenegotiationInactiveAnswer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_audioSynchronizationSources.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_audioSynchronizationSourcesUnidirectional.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioDynamicPtMissingRtpmap.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelay.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCP.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCPWithStun300.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTLS.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayWithStun300.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATSrflx.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNoisyUDPBlock.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioPcmaPcmuOnly.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioRelayPolicy.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioRequireEOC.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVerifyRtpHeaderExtensions.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideo.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoCombined.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoBundle.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoBundleNoRtcpMux.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoRtcpMux.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoTransceivers.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyExtmap.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyExtmapSendonly.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyTooLongMidFails.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio_forced_higher_rate.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio_forced_lower_rate.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicH264Video.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicScreenshare.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicVideo.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicVideoVerifyRtpHeaderExtensions.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_basicWindowshare.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_bug1013809.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_bug1042791.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_bug1227781.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_bug1512281.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_bug1773067.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_bug822674.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_bug825703.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_bug827843.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_bug834153.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_callbacks.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_2d.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_2d_noSSRC.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_webgl.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_capturedVideo.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_certificates.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_checkPacketDumpHook.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_close.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_closeDuringIce.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_codecNegotiationFailure.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_constructedStream.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_disabledVideoPreNegotiation.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_encodingsNegotiation.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_errorCallbacks.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_extmapRenegotiation.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_forwarding_basicAudioVideoCombined.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithSetConfiguration.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithStun300.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithStun300IPv6.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_glean.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_iceFailure.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_insertDTMF.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_localReofferRollback.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_localRollback.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_maxFsConstraint.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_multiple_captureStream_canvas_2d.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleAnswer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleOffer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleOfferAnswer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_nonDefaultRate.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveAudio.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveVideo.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveVideoAudio.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_portRestrictions.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_promiseSendOnly.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_recordReceiveTrack.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_relayOnly.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_remoteReofferRollback.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_remoteRollback.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_removeAudioTrack.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddAudioTrack.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddAudioTrackNoBundle.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddVideoTrack.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddVideoTrackNoBundle.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_removeVideoTrack.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_renderAfterRenegotiation.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_replaceNullTrackThenRenegotiateAudio.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_replaceNullTrackThenRenegotiateVideo.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_camera.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_disabled.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_microphone.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_replaceVideoThenRenegotiate.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_restartIce.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceBadAnswer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalAndRemoteRollback.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalAndRemoteRollbackNoSubsequentRestart.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalRollback.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalRollbackNoSubsequentRestart.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundle.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundleNoRtcpMux.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoRtcpMux.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_restrictBandwidthTargetBitrate.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_restrictBandwidthWithTias.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_rtcp_rsize.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_scaleResolution.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_scaleResolution_oldSetParameters.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_sender_and_receiver_stats.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalAnswerInHaveLocalOffer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalAnswerInStable.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalOfferInHaveRemoteOffer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_maxFramerate.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_maxFramerate_oldSetParameters.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_oldSetParameters.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_scaleResolutionDownBy.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_scaleResolutionDownBy_oldSetParameters.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteAnswerInHaveRemoteOffer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteAnswerInStable.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteOfferInHaveLocalOffer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_lowResFirst.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_lowResFirst_oldSetParameters.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_oldSetParameters.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOddResolution.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOddResolution_oldSetParameters.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_lowResFirst.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_lowResFirst_oldSetParameters.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_oldSetParameters.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_stats.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_stats_jitter.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_stats_oneway.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_stats_relayProtocol.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_stereoFmtpPref.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_syncSetDescription.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_telephoneEventFirst.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_threeUnbundledConnections.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_throwInCallbacks.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_toJSON.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_trackDisabling.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_trackDisabling_clones.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_trackless_sender_stats.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioStreams.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioTracksInOneStream.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreams.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreamsCombined.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreamsCombinedNoBundle.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_twoVideoStreams.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_twoVideoTracksInOneStream.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_verifyAudioAfterRenegotiation.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_verifyDescriptions.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_verifyVideoAfterRenegotiation.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_videoCodecs.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_videoRenegotiationInactiveAnswer.html create mode 100644 dom/media/webrtc/tests/mochitests/test_peerConnection_webAudio.html create mode 100644 dom/media/webrtc/tests/mochitests/test_selftest.html create mode 100644 dom/media/webrtc/tests/mochitests/test_setSinkId.html create mode 100644 dom/media/webrtc/tests/mochitests/test_setSinkId_default_addTrack.html create mode 100644 dom/media/webrtc/tests/mochitests/test_setSinkId_preMutedElement.html create mode 100644 dom/media/webrtc/tests/mochitests/test_unfocused_pref.html create mode 100644 dom/media/webrtc/tests/mochitests/turnConfig.js create mode 100644 dom/media/webrtc/third_party_build/README.md create mode 100644 dom/media/webrtc/third_party_build/build_no_op_commits.sh create mode 100644 dom/media/webrtc/third_party_build/commit-build-file-changes.sh create mode 100644 dom/media/webrtc/third_party_build/default_config_env create mode 100644 dom/media/webrtc/third_party_build/detect_upstream_revert.sh create mode 100644 dom/media/webrtc/third_party_build/elm_arcconfig.patch create mode 100644 dom/media/webrtc/third_party_build/elm_rebase.sh create mode 100644 dom/media/webrtc/third_party_build/extract-for-git.py create mode 100644 dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh create mode 100644 dom/media/webrtc/third_party_build/fetch_github_repo.py create mode 100644 dom/media/webrtc/third_party_build/filter_git_changes.py create mode 100644 dom/media/webrtc/third_party_build/gn-configs/README.md create mode 100644 dom/media/webrtc/third_party_build/gn-configs/webrtc.json create mode 100644 dom/media/webrtc/third_party_build/lookup_branch_head.py create mode 100644 dom/media/webrtc/third_party_build/loop-ff.sh create mode 100755 dom/media/webrtc/third_party_build/make_upstream_revert_noop.sh create mode 100644 dom/media/webrtc/third_party_build/pre-warmed-milestone.cache create mode 100644 dom/media/webrtc/third_party_build/prep_repo.sh create mode 100644 dom/media/webrtc/third_party_build/push_official_branch.sh create mode 100644 dom/media/webrtc/third_party_build/restore_elm_arcconfig.py create mode 100644 dom/media/webrtc/third_party_build/restore_patch_stack.py create mode 100644 dom/media/webrtc/third_party_build/run_operations.py create mode 100644 dom/media/webrtc/third_party_build/save_patch_stack.py create mode 100644 dom/media/webrtc/third_party_build/update_default_config.sh create mode 100644 dom/media/webrtc/third_party_build/use_config_env.sh create mode 100644 dom/media/webrtc/third_party_build/vendor-libwebrtc.py create mode 100644 dom/media/webrtc/third_party_build/verify_vendoring.sh create mode 100644 dom/media/webrtc/third_party_build/webrtc.mozbuild create mode 100644 dom/media/webrtc/third_party_build/write_default_config.py create mode 100644 dom/media/webrtc/transport/README create mode 100644 dom/media/webrtc/transport/SrtpFlow.cpp create mode 100644 dom/media/webrtc/transport/SrtpFlow.h create mode 100644 dom/media/webrtc/transport/WebrtcTCPSocketWrapper.cpp create mode 100644 dom/media/webrtc/transport/WebrtcTCPSocketWrapper.h create mode 100644 dom/media/webrtc/transport/build/moz.build create mode 100644 dom/media/webrtc/transport/common.build create mode 100644 dom/media/webrtc/transport/dtlsidentity.cpp create mode 100644 dom/media/webrtc/transport/dtlsidentity.h create mode 100644 dom/media/webrtc/transport/fuzztest/moz.build create mode 100644 dom/media/webrtc/transport/fuzztest/stun_parser_libfuzz.cpp create mode 100644 dom/media/webrtc/transport/ipc/NrIceStunAddrMessageUtils.h create mode 100644 dom/media/webrtc/transport/ipc/PStunAddrsParams.h create mode 100644 dom/media/webrtc/transport/ipc/PStunAddrsRequest.ipdl create mode 100644 dom/media/webrtc/transport/ipc/PWebrtcTCPSocket.ipdl create mode 100644 dom/media/webrtc/transport/ipc/StunAddrsRequestChild.cpp create mode 100644 dom/media/webrtc/transport/ipc/StunAddrsRequestChild.h create mode 100644 dom/media/webrtc/transport/ipc/StunAddrsRequestParent.cpp create mode 100644 dom/media/webrtc/transport/ipc/StunAddrsRequestParent.h create mode 100644 dom/media/webrtc/transport/ipc/WebrtcProxyConfig.ipdlh create mode 100644 dom/media/webrtc/transport/ipc/WebrtcTCPSocket.cpp create mode 100644 dom/media/webrtc/transport/ipc/WebrtcTCPSocket.h create mode 100644 dom/media/webrtc/transport/ipc/WebrtcTCPSocketCallback.h create mode 100644 dom/media/webrtc/transport/ipc/WebrtcTCPSocketChild.cpp create mode 100644 dom/media/webrtc/transport/ipc/WebrtcTCPSocketChild.h create mode 100644 dom/media/webrtc/transport/ipc/WebrtcTCPSocketLog.cpp create mode 100644 dom/media/webrtc/transport/ipc/WebrtcTCPSocketLog.h create mode 100644 dom/media/webrtc/transport/ipc/WebrtcTCPSocketParent.cpp create mode 100644 dom/media/webrtc/transport/ipc/WebrtcTCPSocketParent.h create mode 100644 dom/media/webrtc/transport/ipc/moz.build create mode 100644 dom/media/webrtc/transport/logging.h create mode 100644 dom/media/webrtc/transport/m_cpp_utils.h create mode 100644 dom/media/webrtc/transport/mdns_service/Cargo.toml create mode 100644 dom/media/webrtc/transport/mdns_service/mdns_service.h create mode 100644 dom/media/webrtc/transport/mdns_service/src/lib.rs create mode 100644 dom/media/webrtc/transport/mediapacket.cpp create mode 100644 dom/media/webrtc/transport/mediapacket.h create mode 100644 dom/media/webrtc/transport/moz.build create mode 100644 dom/media/webrtc/transport/nr_socket_proxy_config.cpp create mode 100644 dom/media/webrtc/transport/nr_socket_proxy_config.h create mode 100644 dom/media/webrtc/transport/nr_socket_prsock.cpp create mode 100644 dom/media/webrtc/transport/nr_socket_prsock.h create mode 100644 dom/media/webrtc/transport/nr_socket_tcp.cpp create mode 100644 dom/media/webrtc/transport/nr_socket_tcp.h create mode 100644 dom/media/webrtc/transport/nr_timer.cpp create mode 100644 dom/media/webrtc/transport/nricectx.cpp create mode 100644 dom/media/webrtc/transport/nricectx.h create mode 100644 dom/media/webrtc/transport/nricemediastream.cpp create mode 100644 dom/media/webrtc/transport/nricemediastream.h create mode 100644 dom/media/webrtc/transport/nriceresolver.cpp create mode 100644 dom/media/webrtc/transport/nriceresolver.h create mode 100644 dom/media/webrtc/transport/nriceresolverfake.cpp create mode 100644 dom/media/webrtc/transport/nriceresolverfake.h create mode 100644 dom/media/webrtc/transport/nricestunaddr.cpp create mode 100644 dom/media/webrtc/transport/nricestunaddr.h create mode 100644 dom/media/webrtc/transport/nrinterfaceprioritizer.cpp create mode 100644 dom/media/webrtc/transport/nrinterfaceprioritizer.h create mode 100644 dom/media/webrtc/transport/rlogconnector.cpp create mode 100644 dom/media/webrtc/transport/rlogconnector.h create mode 100644 dom/media/webrtc/transport/runnable_utils.h create mode 100644 dom/media/webrtc/transport/sigslot.h create mode 100644 dom/media/webrtc/transport/simpletokenbucket.cpp create mode 100644 dom/media/webrtc/transport/simpletokenbucket.h create mode 100644 dom/media/webrtc/transport/srtp/README_MOZILLA create mode 100644 dom/media/webrtc/transport/srtp/moz.build create mode 100644 dom/media/webrtc/transport/stun_socket_filter.cpp create mode 100644 dom/media/webrtc/transport/stun_socket_filter.h create mode 100644 dom/media/webrtc/transport/test/TestSyncRunnable.cpp create mode 100644 dom/media/webrtc/transport/test/buffered_stun_socket_unittest.cpp create mode 100644 dom/media/webrtc/transport/test/dummysocket.h create mode 100644 dom/media/webrtc/transport/test/gtest_ringbuffer_dumper.h create mode 100644 dom/media/webrtc/transport/test/gtest_utils.h create mode 100644 dom/media/webrtc/transport/test/ice_unittest.cpp create mode 100644 dom/media/webrtc/transport/test/moz.build create mode 100644 dom/media/webrtc/transport/test/mtransport_test_utils.h create mode 100644 dom/media/webrtc/transport/test/multi_tcp_socket_unittest.cpp create mode 100644 dom/media/webrtc/transport/test/nrappkit_unittest.cpp create mode 100644 dom/media/webrtc/transport/test/proxy_tunnel_socket_unittest.cpp create mode 100644 dom/media/webrtc/transport/test/rlogconnector_unittest.cpp create mode 100644 dom/media/webrtc/transport/test/runnable_utils_unittest.cpp create mode 100644 dom/media/webrtc/transport/test/sctp_unittest.cpp create mode 100644 dom/media/webrtc/transport/test/simpletokenbucket_unittest.cpp create mode 100644 dom/media/webrtc/transport/test/sockettransportservice_unittest.cpp create mode 100644 dom/media/webrtc/transport/test/stunserver.cpp create mode 100644 dom/media/webrtc/transport/test/stunserver.h create mode 100644 dom/media/webrtc/transport/test/test_nr_socket_ice_unittest.cpp create mode 100644 dom/media/webrtc/transport/test/test_nr_socket_unittest.cpp create mode 100644 dom/media/webrtc/transport/test/transport_unittests.cpp create mode 100644 dom/media/webrtc/transport/test/turn_unittest.cpp create mode 100644 dom/media/webrtc/transport/test/webrtcproxychannel_unittest.cpp create mode 100644 dom/media/webrtc/transport/test_nr_socket.cpp create mode 100644 dom/media/webrtc/transport/test_nr_socket.h create mode 100644 dom/media/webrtc/transport/third_party/moz.build create mode 100644 dom/media/webrtc/transport/third_party/nICEr/COPYRIGHT create mode 100644 dom/media/webrtc/transport/third_party/nICEr/README create mode 100644 dom/media/webrtc/transport/third_party/nICEr/moz.yaml create mode 100644 dom/media/webrtc/transport/third_party/nICEr/nicer.gyp create mode 100644 dom/media/webrtc/transport/third_party/nICEr/non-unified-build.patch create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/crypto/nr_crypto.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/crypto/nr_crypto.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_candidate.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_candidate.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_candidate_pair.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_candidate_pair.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_codeword.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_component.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_component.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_ctx.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_ctx.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_handler.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_media_stream.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_media_stream.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_parser.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_peer_ctx.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_peer_ctx.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_reg.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_socket.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_socket.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/net/local_addr.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/net/local_addr.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/net/nr_interface_prioritizer.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/net/nr_interface_prioritizer.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/net/nr_resolver.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/net/nr_resolver.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_local.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_multi_tcp.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_multi_tcp.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_wrapper.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_wrapper.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/net/transport_addr.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/net/transport_addr.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/net/transport_addr_reg.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/net/transport_addr_reg.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-bsd.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-bsd.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-netlink.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-netlink.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-win32.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-win32.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/nr_socket_buffered_stun.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/nr_socket_buffered_stun.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/nr_socket_turn.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/nr_socket_turn.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/stun.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_build.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_build.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_client_ctx.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_client_ctx.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_codec.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_codec.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_hint.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_hint.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_msg.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_msg.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_proc.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_proc.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_reg.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_server_ctx.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_server_ctx.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_util.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_util.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/turn_client_ctx.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/stun/turn_client_ctx.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/util/cb_args.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/util/cb_args.h create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/util/ice_util.c create mode 100644 dom/media/webrtc/transport/third_party/nICEr/src/util/ice_util.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/COPYRIGHT create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/README create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/README_MOZILLA create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/nrappkit.gyp create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/event/async_timer.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/event/async_wait.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/event/async_wait_int.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/log/r_log.c create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/log/r_log.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/plugin/nr_plugin.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/port/android/include/android_funcs.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/port/android/include/csi_platform.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/port/android/include/sys/ttycom.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/port/android/port-impl.mk create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/port/darwin/include/csi_platform.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/port/generic/include/sys/queue.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/port/linux/include/csi_platform.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/port/linux/include/linux_funcs.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/port/linux/include/sys/ttycom.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/port/linux/port-impl.mk create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/port/win32/include/csi_platform.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/registry/c2ru.c create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/registry/c2ru.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry.c create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry_int.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry_local.c create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry_vtbl.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/registry/registrycb.c create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/share/nr_api.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/share/nr_common.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/share/nr_reg_keys.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/stats/nrstats.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/byteorder.c create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/byteorder.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/hex.c create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/hex.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/assoc.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/debug.c create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/debug.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_assoc.c create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_assoc.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_common.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_crc32.c create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_crc32.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_data.c create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_data.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_defaults.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_errors.c create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_errors.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_includes.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_list.c create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_list.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_macros.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_memory.c create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_memory.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_replace.c create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_thread.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_time.c create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_time.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_types.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/p_buf.c create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/p_buf.h create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/util.c create mode 100644 dom/media/webrtc/transport/third_party/nrappkit/src/util/util.h create mode 100644 dom/media/webrtc/transport/transportflow.cpp create mode 100644 dom/media/webrtc/transport/transportflow.h create mode 100644 dom/media/webrtc/transport/transportlayer.cpp create mode 100644 dom/media/webrtc/transport/transportlayer.h create mode 100644 dom/media/webrtc/transport/transportlayerdtls.cpp create mode 100644 dom/media/webrtc/transport/transportlayerdtls.h create mode 100644 dom/media/webrtc/transport/transportlayerice.cpp create mode 100644 dom/media/webrtc/transport/transportlayerice.h create mode 100644 dom/media/webrtc/transport/transportlayerlog.cpp create mode 100644 dom/media/webrtc/transport/transportlayerlog.h create mode 100644 dom/media/webrtc/transport/transportlayerloopback.cpp create mode 100644 dom/media/webrtc/transport/transportlayerloopback.h create mode 100644 dom/media/webrtc/transport/transportlayersrtp.cpp create mode 100644 dom/media/webrtc/transport/transportlayersrtp.h create mode 100644 dom/media/webrtc/transportbridge/MediaPipeline.cpp create mode 100644 dom/media/webrtc/transportbridge/MediaPipeline.h create mode 100644 dom/media/webrtc/transportbridge/MediaPipelineFilter.cpp create mode 100644 dom/media/webrtc/transportbridge/MediaPipelineFilter.h create mode 100644 dom/media/webrtc/transportbridge/RtpLogger.cpp create mode 100644 dom/media/webrtc/transportbridge/RtpLogger.h create mode 100644 dom/media/webrtc/transportbridge/moz.build (limited to 'dom/media/webrtc') diff --git a/dom/media/webrtc/CubebDeviceEnumerator.cpp b/dom/media/webrtc/CubebDeviceEnumerator.cpp new file mode 100644 index 0000000000..b5e705ea7d --- /dev/null +++ b/dom/media/webrtc/CubebDeviceEnumerator.cpp @@ -0,0 +1,334 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "CubebDeviceEnumerator.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/media/MediaUtils.h" +#include "nsThreadUtils.h" +#ifdef XP_WIN +# include "mozilla/mscom/EnsureMTA.h" +#endif + +namespace mozilla { + +using namespace CubebUtils; +using AudioDeviceSet = CubebDeviceEnumerator::AudioDeviceSet; + +/* static */ +static StaticRefPtr sInstance; +static StaticMutex sInstanceMutex MOZ_UNANNOTATED; + +/* static */ +CubebDeviceEnumerator* CubebDeviceEnumerator::GetInstance() { + StaticMutexAutoLock lock(sInstanceMutex); + if (!sInstance) { + sInstance = new CubebDeviceEnumerator(); + static bool clearOnShutdownSetup = []() -> bool { + auto setClearOnShutdown = []() -> void { + ClearOnShutdown(&sInstance, ShutdownPhase::XPCOMShutdownThreads); + }; + if (NS_IsMainThread()) { + setClearOnShutdown(); + } else { + SchedulerGroup::Dispatch( + TaskCategory::Other, + NS_NewRunnableFunction("CubebDeviceEnumerator::::GetInstance()", + std::move(setClearOnShutdown))); + } + return true; + }(); + Unused << clearOnShutdownSetup; + } + return sInstance.get(); +} + +CubebDeviceEnumerator::CubebDeviceEnumerator() + : mMutex("CubebDeviceListMutex"), + mManualInputInvalidation(false), + mManualOutputInvalidation(false) { +#ifdef XP_WIN + // Ensure the MTA thread exists and gets instantiated before the + // CubebDeviceEnumerator so that this instance will always gets destructed + // before the MTA thread gets shutdown. + mozilla::mscom::EnsureMTA([&]() -> void { +#endif + int rv = cubeb_register_device_collection_changed( + GetCubebContext(), CUBEB_DEVICE_TYPE_OUTPUT, + &OutputAudioDeviceListChanged_s, this); + if (rv != CUBEB_OK) { + NS_WARNING( + "Could not register the audio output" + " device collection changed callback."); + mManualOutputInvalidation = true; + } + rv = cubeb_register_device_collection_changed( + GetCubebContext(), CUBEB_DEVICE_TYPE_INPUT, + &InputAudioDeviceListChanged_s, this); + if (rv != CUBEB_OK) { + NS_WARNING( + "Could not register the audio input" + " device collection changed callback."); + mManualInputInvalidation = true; + } +#ifdef XP_WIN + }); +#endif +} + +/* static */ +void CubebDeviceEnumerator::Shutdown() { + StaticMutexAutoLock lock(sInstanceMutex); + if (sInstance) { + sInstance = nullptr; + } +} + +CubebDeviceEnumerator::~CubebDeviceEnumerator() { +#ifdef XP_WIN + mozilla::mscom::EnsureMTA([&]() -> void { +#endif + int rv = cubeb_register_device_collection_changed( + GetCubebContext(), CUBEB_DEVICE_TYPE_OUTPUT, nullptr, this); + if (rv != CUBEB_OK) { + NS_WARNING( + "Could not unregister the audio output" + " device collection changed callback."); + } + rv = cubeb_register_device_collection_changed( + GetCubebContext(), CUBEB_DEVICE_TYPE_INPUT, nullptr, this); + if (rv != CUBEB_OK) { + NS_WARNING( + "Could not unregister the audio input" + " device collection changed callback."); + } +#ifdef XP_WIN + }); +#endif +} + +RefPtr +CubebDeviceEnumerator::EnumerateAudioInputDevices() { + return EnumerateAudioDevices(Side::INPUT); +} + +RefPtr +CubebDeviceEnumerator::EnumerateAudioOutputDevices() { + return EnumerateAudioDevices(Side::OUTPUT); +} + +#ifndef ANDROID +static uint16_t ConvertCubebType(cubeb_device_type aType) { + uint16_t map[] = { + nsIAudioDeviceInfo::TYPE_UNKNOWN, // CUBEB_DEVICE_TYPE_UNKNOWN + nsIAudioDeviceInfo::TYPE_INPUT, // CUBEB_DEVICE_TYPE_INPUT, + nsIAudioDeviceInfo::TYPE_OUTPUT // CUBEB_DEVICE_TYPE_OUTPUT + }; + return map[aType]; +} + +static uint16_t ConvertCubebState(cubeb_device_state aState) { + uint16_t map[] = { + nsIAudioDeviceInfo::STATE_DISABLED, // CUBEB_DEVICE_STATE_DISABLED + nsIAudioDeviceInfo::STATE_UNPLUGGED, // CUBEB_DEVICE_STATE_UNPLUGGED + nsIAudioDeviceInfo::STATE_ENABLED // CUBEB_DEVICE_STATE_ENABLED + }; + return map[aState]; +} + +static uint16_t ConvertCubebPreferred(cubeb_device_pref aPreferred) { + if (aPreferred == CUBEB_DEVICE_PREF_NONE) { + return nsIAudioDeviceInfo::PREF_NONE; + } + if (aPreferred == CUBEB_DEVICE_PREF_ALL) { + return nsIAudioDeviceInfo::PREF_ALL; + } + + uint16_t preferred = 0; + if (aPreferred & CUBEB_DEVICE_PREF_MULTIMEDIA) { + preferred |= nsIAudioDeviceInfo::PREF_MULTIMEDIA; + } + if (aPreferred & CUBEB_DEVICE_PREF_VOICE) { + preferred |= nsIAudioDeviceInfo::PREF_VOICE; + } + if (aPreferred & CUBEB_DEVICE_PREF_NOTIFICATION) { + preferred |= nsIAudioDeviceInfo::PREF_NOTIFICATION; + } + return preferred; +} + +static uint16_t ConvertCubebFormat(cubeb_device_fmt aFormat) { + uint16_t format = 0; + if (aFormat & CUBEB_DEVICE_FMT_S16LE) { + format |= nsIAudioDeviceInfo::FMT_S16LE; + } + if (aFormat & CUBEB_DEVICE_FMT_S16BE) { + format |= nsIAudioDeviceInfo::FMT_S16BE; + } + if (aFormat & CUBEB_DEVICE_FMT_F32LE) { + format |= nsIAudioDeviceInfo::FMT_F32LE; + } + if (aFormat & CUBEB_DEVICE_FMT_F32BE) { + format |= nsIAudioDeviceInfo::FMT_F32BE; + } + return format; +} + +static RefPtr GetDeviceCollection(Side aSide) { + RefPtr set = new AudioDeviceSet(); + cubeb* context = GetCubebContext(); + if (context) { + cubeb_device_collection collection = {nullptr, 0}; +# ifdef XP_WIN + mozilla::mscom::EnsureMTA([&]() -> void { +# endif + if (cubeb_enumerate_devices(context, + aSide == Input ? CUBEB_DEVICE_TYPE_INPUT + : CUBEB_DEVICE_TYPE_OUTPUT, + &collection) == CUBEB_OK) { + for (unsigned int i = 0; i < collection.count; ++i) { + auto device = collection.device[i]; + if (device.max_channels == 0) { + continue; + } + RefPtr info = new AudioDeviceInfo( + device.devid, NS_ConvertUTF8toUTF16(device.friendly_name), + NS_ConvertUTF8toUTF16(device.group_id), + NS_ConvertUTF8toUTF16(device.vendor_name), + ConvertCubebType(device.type), ConvertCubebState(device.state), + ConvertCubebPreferred(device.preferred), + ConvertCubebFormat(device.format), + ConvertCubebFormat(device.default_format), device.max_channels, + device.default_rate, device.max_rate, device.min_rate, + device.latency_hi, device.latency_lo); + set->AppendElement(std::move(info)); + } + } + cubeb_device_collection_destroy(context, &collection); +# ifdef XP_WIN + }); +# endif + } + return set; +} +#endif // non ANDROID + +RefPtr CubebDeviceEnumerator::EnumerateAudioDevices( + CubebDeviceEnumerator::Side aSide) { + MOZ_ASSERT(aSide == Side::INPUT || aSide == Side::OUTPUT); + + RefPtr* devicesCache; + bool manualInvalidation = true; + + if (aSide == Side::INPUT) { + devicesCache = &mInputDevices; + manualInvalidation = mManualInputInvalidation; + } else { + MOZ_ASSERT(aSide == Side::OUTPUT); + devicesCache = &mOutputDevices; + manualInvalidation = mManualOutputInvalidation; + } + + cubeb* context = GetCubebContext(); + if (!context) { + return new AudioDeviceSet(); + } + if (!manualInvalidation) { + MutexAutoLock lock(mMutex); + if (*devicesCache) { + return *devicesCache; + } + } + +#ifdef ANDROID + cubeb_device_type type = CUBEB_DEVICE_TYPE_UNKNOWN; + uint32_t channels = 0; + nsAutoString name; + if (aSide == Side::INPUT) { + type = CUBEB_DEVICE_TYPE_INPUT; + channels = 1; + name = u"Default audio input device"_ns; + } else { + MOZ_ASSERT(aSide == Side::OUTPUT); + type = CUBEB_DEVICE_TYPE_OUTPUT; + channels = 2; + name = u"Default audio output device"_ns; + } + RefPtr devices = new AudioDeviceSet(); + // Bug 1473346: enumerating devices is not supported on Android in cubeb, + // simply state that there is a single sink, that it is the default, and has + // a single channel. All the other values are made up and are not to be used. + // Bug 1660391: we can't use fluent here yet to get localized strings, so + // those are hard-coded en_US strings for now. + RefPtr info = new AudioDeviceInfo( + nullptr, name, u""_ns, u""_ns, type, CUBEB_DEVICE_STATE_ENABLED, + CUBEB_DEVICE_PREF_ALL, CUBEB_DEVICE_FMT_ALL, CUBEB_DEVICE_FMT_S16NE, + channels, 44100, 44100, 44100, 441, 128); + devices->AppendElement(std::move(info)); +#else + RefPtr devices = GetDeviceCollection( + (aSide == Side::INPUT) ? CubebUtils::Input : CubebUtils::Output); +#endif + { + MutexAutoLock lock(mMutex); + *devicesCache = devices; + } + return devices; +} + +already_AddRefed CubebDeviceEnumerator::DeviceInfoFromName( + const nsString& aName, Side aSide) { + RefPtr devices = EnumerateAudioDevices(aSide); + for (const RefPtr& device : *devices) { + if (device->Name().Equals(aName)) { + RefPtr other = device; + return other.forget(); + } + } + + return nullptr; +} + +RefPtr CubebDeviceEnumerator::DefaultDevice(Side aSide) { + RefPtr devices = EnumerateAudioDevices(aSide); + for (const RefPtr& device : *devices) { + if (device->Preferred()) { + RefPtr other = device; + return other.forget(); + } + } + + return nullptr; +} + +void CubebDeviceEnumerator::InputAudioDeviceListChanged_s(cubeb* aContext, + void* aUser) { + CubebDeviceEnumerator* self = reinterpret_cast(aUser); + self->AudioDeviceListChanged(CubebDeviceEnumerator::Side::INPUT); +} + +void CubebDeviceEnumerator::OutputAudioDeviceListChanged_s(cubeb* aContext, + void* aUser) { + CubebDeviceEnumerator* self = reinterpret_cast(aUser); + self->AudioDeviceListChanged(CubebDeviceEnumerator::Side::OUTPUT); +} + +void CubebDeviceEnumerator::AudioDeviceListChanged(Side aSide) { + MutexAutoLock lock(mMutex); + if (aSide == Side::INPUT) { + mInputDevices = nullptr; + mOnInputDeviceListChange.Notify(); + } else { + MOZ_ASSERT(aSide == Side::OUTPUT); + mOutputDevices = nullptr; + mOnOutputDeviceListChange.Notify(); + } +} + +} // namespace mozilla diff --git a/dom/media/webrtc/CubebDeviceEnumerator.h b/dom/media/webrtc/CubebDeviceEnumerator.h new file mode 100644 index 0000000000..6b6499f728 --- /dev/null +++ b/dom/media/webrtc/CubebDeviceEnumerator.h @@ -0,0 +1,87 @@ +/* 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/. */ + +#ifndef CUBEBDEVICEENUMERATOR_H_ +#define CUBEBDEVICEENUMERATOR_H_ + +#include "AudioDeviceInfo.h" +#include "cubeb/cubeb.h" +#include "MediaEventSource.h" +#include "mozilla/Mutex.h" +#include "nsTArray.h" + +namespace mozilla { + +namespace media { +template +class Refcountable; +} + +// This class implements a cache for accessing the audio device list. +// It can be accessed on any thread. +class CubebDeviceEnumerator final { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CubebDeviceEnumerator) + + static CubebDeviceEnumerator* GetInstance(); + static void Shutdown(); + using AudioDeviceSet = media::Refcountable>>; + // This method returns a list of all the input audio devices + // (sources) available on this machine. + // This method is safe to call from all threads. + RefPtr EnumerateAudioInputDevices(); + // Similar for the audio audio devices (sinks). Also thread safe. + RefPtr EnumerateAudioOutputDevices(); + // From a device name, return the info for this device, if it's a valid name, + // or nullptr otherwise. + // This method is safe to call from any thread. + enum class Side { + INPUT, + OUTPUT, + }; + already_AddRefed DeviceInfoFromName(const nsString& aName, + Side aSide); + // Event source to listen for changes to the audio input device list on. + MediaEventSource& OnAudioInputDeviceListChange() { + return mOnInputDeviceListChange; + } + + // Event source to listen for changes to the audio output device list on. + MediaEventSource& OnAudioOutputDeviceListChange() { + return mOnOutputDeviceListChange; + } + + // Return the default device for a particular side. + RefPtr DefaultDevice(Side aSide); + + private: + CubebDeviceEnumerator(); + ~CubebDeviceEnumerator(); + // Static functions called by cubeb when the audio device list changes + // (i.e. when a new device is made available, or non-available). This + // simply calls `AudioDeviceListChanged` below. + static void InputAudioDeviceListChanged_s(cubeb* aContext, void* aUser); + static void OutputAudioDeviceListChanged_s(cubeb* aContext, void* aUser); + // Invalidates the cached audio input device list, can be called on any + // thread. + void AudioDeviceListChanged(Side aSide); + RefPtr EnumerateAudioDevices(Side aSide); + // Synchronize access to mInputDevices and mOutputDevices; + Mutex mMutex MOZ_UNANNOTATED; + RefPtr mInputDevices; + RefPtr mOutputDevices; + // If mManual*Invalidation is true, then it is necessary to query the device + // list each time instead of relying on automatic invalidation of the cache by + // cubeb itself. Set in the constructor and then can be access on any thread. + bool mManualInputInvalidation; + bool mManualOutputInvalidation; + MediaEventProducer mOnInputDeviceListChange; + MediaEventProducer mOnOutputDeviceListChange; +}; + +typedef CubebDeviceEnumerator Enumerator; +typedef CubebDeviceEnumerator::Side EnumeratorSide; +} // namespace mozilla + +#endif // CUBEBDEVICEENUMERATOR_H_ diff --git a/dom/media/webrtc/MediaEngine.h b/dom/media/webrtc/MediaEngine.h new file mode 100644 index 0000000000..d3e1ece452 --- /dev/null +++ b/dom/media/webrtc/MediaEngine.h @@ -0,0 +1,66 @@ +/* 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/. */ + +#ifndef MEDIAENGINE_H_ +#define MEDIAENGINE_H_ + +#include "DOMMediaStream.h" +#include "MediaEventSource.h" +#include "MediaTrackGraph.h" +#include "MediaTrackConstraints.h" +#include "mozilla/dom/MediaStreamTrackBinding.h" +#include "mozilla/dom/VideoStreamTrack.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ThreadSafeWeakPtr.h" + +namespace mozilla { + +namespace dom { +class Blob; +} // namespace dom + +class AllocationHandle; +class MediaDevice; +class MediaEngineSource; + +enum MediaSinkEnum { + Speaker, + Other, +}; + +enum { kVideoTrack = 1, kAudioTrack = 2, kTrackCount }; + +class MediaEngine { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaEngine) + NS_DECL_OWNINGEVENTTARGET + + void AssertIsOnOwningThread() const { NS_ASSERT_OWNINGTHREAD(MediaEngine); } + + /** + * Populate an array of sources of the requested type in the nsTArray. + * Also include devices that are currently unavailable. + */ + virtual void EnumerateDevices(dom::MediaSourceEnum, MediaSinkEnum, + nsTArray>*) = 0; + + virtual void Shutdown() = 0; + + virtual RefPtr CreateSource( + const MediaDevice* aDevice) = 0; + + virtual MediaEventSource& DeviceListChangeEvent() = 0; + /** + * Return true if devices returned from EnumerateDevices are emulated media + * devices. + */ + virtual bool IsFake() const = 0; + + protected: + virtual ~MediaEngine() = default; +}; + +} // namespace mozilla + +#endif /* MEDIAENGINE_H_ */ diff --git a/dom/media/webrtc/MediaEngineFake.cpp b/dom/media/webrtc/MediaEngineFake.cpp new file mode 100644 index 0000000000..bba6c18694 --- /dev/null +++ b/dom/media/webrtc/MediaEngineFake.cpp @@ -0,0 +1,653 @@ +/* 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 "MediaEngineFake.h" + +#include "AudioSegment.h" +#include "DOMMediaStream.h" +#include "ImageContainer.h" +#include "ImageTypes.h" +#include "MediaEnginePrefs.h" +#include "MediaEngineSource.h" +#include "MediaTrackGraph.h" +#include "MediaTrackListener.h" +#include "MediaTrackConstraints.h" +#include "mozilla/dom/File.h" +#include "mozilla/MediaManager.h" +#include "mozilla/Mutex.h" +#include "mozilla/SyncRunnable.h" +#include "mozilla/UniquePtr.h" +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsContentUtils.h" +#include "nsITimer.h" +#include "SineWaveGenerator.h" +#include "Tracing.h" +#include "VideoSegment.h" +#include "VideoUtils.h" + +#ifdef MOZ_WIDGET_ANDROID +# include "nsISupportsUtils.h" +#endif + +#ifdef MOZ_WEBRTC +# include "YuvStamper.h" +#endif + +#define VIDEO_WIDTH_MIN 160 +#define VIDEO_WIDTH_MAX 4096 +#define VIDEO_HEIGHT_MIN 90 +#define VIDEO_HEIGHT_MAX 2160 +#define DEFAULT_AUDIO_TIMER_MS 10 +namespace mozilla { + +using namespace mozilla::gfx; +using dom::MediaSourceEnum; +using dom::MediaTrackConstraints; +using dom::MediaTrackSettings; +using dom::VideoFacingModeEnum; + +static nsString FakeVideoName() { + // For the purpose of testing we allow to change the name of the fake device + // by pref. + nsAutoString cameraNameFromPref; + nsresult rv; + auto getPref = [&]() { + rv = Preferences::GetString("media.getusermedia.fake-camera-name", + cameraNameFromPref); + }; + if (NS_IsMainThread()) { + getPref(); + } else { + // Here it is preferred a "hard" block, instead of "soft" block provided + // by sync dispatch, which allows the waiting thread to spin its event + // loop. The latter would allow multiple enumeration requests being + // processed out-of-order. + RefPtr runnable = NS_NewRunnableFunction(__func__, getPref); + SyncRunnable::DispatchToThread(GetMainThreadSerialEventTarget(), runnable); + } + + if (NS_SUCCEEDED(rv)) { + return std::move(cameraNameFromPref); + } + return u"Default Video Device"_ns; +} + +/** + * Fake video source. + */ +class MediaEngineFakeVideoSource : public MediaEngineSource { + public: + MediaEngineFakeVideoSource(); + + static nsString GetGroupId(); + + nsresult Allocate(const dom::MediaTrackConstraints& aConstraints, + const MediaEnginePrefs& aPrefs, uint64_t aWindowID, + const char** aOutBadConstraint) override; + void SetTrack(const RefPtr& aTrack, + const PrincipalHandle& aPrincipal) override; + nsresult Start() override; + nsresult Reconfigure(const dom::MediaTrackConstraints& aConstraints, + const MediaEnginePrefs& aPrefs, + const char** aOutBadConstraint) override; + nsresult Stop() override; + nsresult Deallocate() override; + + uint32_t GetBestFitnessDistance( + const nsTArray& aConstraintSets) + const override; + void GetSettings(dom::MediaTrackSettings& aOutSettings) const override; + + bool IsFake() const override { return true; } + + protected: + ~MediaEngineFakeVideoSource() = default; + + /** + * Called by mTimer when it's time to generate a new frame. + */ + void GenerateFrame(); + + nsCOMPtr mTimer; + + RefPtr mImageContainer; + + // Current state of this source. + MediaEngineSourceState mState = kReleased; + RefPtr mImage; + RefPtr mTrack; + PrincipalHandle mPrincipalHandle = PRINCIPAL_HANDLE_NONE; + + MediaEnginePrefs mOpts; + int mCb = 16; + int mCr = 16; + + // Main thread only. + const RefPtr> mSettings; +}; + +MediaEngineFakeVideoSource::MediaEngineFakeVideoSource() + : mTimer(nullptr), + mSettings(MakeAndAddRef>()) { + mSettings->mWidth.Construct( + int32_t(MediaEnginePrefs::DEFAULT_43_VIDEO_WIDTH)); + mSettings->mHeight.Construct( + int32_t(MediaEnginePrefs::DEFAULT_43_VIDEO_HEIGHT)); + mSettings->mFrameRate.Construct(double(MediaEnginePrefs::DEFAULT_VIDEO_FPS)); + mSettings->mFacingMode.Construct( + NS_ConvertASCIItoUTF16(dom::VideoFacingModeEnumValues::strings + [uint8_t(VideoFacingModeEnum::Environment)] + .value)); +} + +nsString MediaEngineFakeVideoSource::GetGroupId() { + return u"Fake Video Group"_ns; +} + +uint32_t MediaEngineFakeVideoSource::GetBestFitnessDistance( + const nsTArray& aConstraintSets) const { + AssertIsOnOwningThread(); + + uint64_t distance = 0; + +#ifdef MOZ_WEBRTC + // distance is read from first entry only + if (aConstraintSets.Length() >= 1) { + const auto* cs = aConstraintSets.ElementAt(0); + Maybe facingMode = Nothing(); + distance += + MediaConstraintsHelper::FitnessDistance(facingMode, cs->mFacingMode); + + if (cs->mWidth.mMax < VIDEO_WIDTH_MIN || + cs->mWidth.mMin > VIDEO_WIDTH_MAX) { + distance += UINT32_MAX; + } + + if (cs->mHeight.mMax < VIDEO_HEIGHT_MIN || + cs->mHeight.mMin > VIDEO_HEIGHT_MAX) { + distance += UINT32_MAX; + } + } +#endif + + return uint32_t(std::min(distance, uint64_t(UINT32_MAX))); +} + +void MediaEngineFakeVideoSource::GetSettings( + MediaTrackSettings& aOutSettings) const { + MOZ_ASSERT(NS_IsMainThread()); + aOutSettings = *mSettings; +} + +nsresult MediaEngineFakeVideoSource::Allocate( + const MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs, + uint64_t aWindowID, const char** aOutBadConstraint) { + AssertIsOnOwningThread(); + + MOZ_ASSERT(mState == kReleased); + + FlattenedConstraints c(aConstraints); + + // emulator debug is very, very slow; reduce load on it with smaller/slower + // fake video + mOpts = aPrefs; + mOpts.mWidth = + c.mWidth.Get(aPrefs.mWidth ? aPrefs.mWidth : +#ifdef DEBUG + MediaEnginePrefs::DEFAULT_43_VIDEO_WIDTH / 2 +#else + MediaEnginePrefs::DEFAULT_43_VIDEO_WIDTH +#endif + ); + mOpts.mHeight = + c.mHeight.Get(aPrefs.mHeight ? aPrefs.mHeight : +#ifdef DEBUG + MediaEnginePrefs::DEFAULT_43_VIDEO_HEIGHT / 2 +#else + MediaEnginePrefs::DEFAULT_43_VIDEO_HEIGHT +#endif + ); + mOpts.mWidth = + std::max(VIDEO_WIDTH_MIN, std::min(mOpts.mWidth, VIDEO_WIDTH_MAX)) & ~1; + mOpts.mHeight = + std::max(VIDEO_HEIGHT_MIN, std::min(mOpts.mHeight, VIDEO_HEIGHT_MAX)) & + ~1; + + NS_DispatchToMainThread(NS_NewRunnableFunction( + __func__, [settings = mSettings, frameRate = mOpts.mFPS, + width = mOpts.mWidth, height = mOpts.mHeight]() { + settings->mFrameRate.Value() = frameRate; + settings->mWidth.Value() = width; + settings->mHeight.Value() = height; + })); + + mState = kAllocated; + return NS_OK; +} + +nsresult MediaEngineFakeVideoSource::Deallocate() { + AssertIsOnOwningThread(); + + MOZ_ASSERT(!mImage); + MOZ_ASSERT(mState == kStopped || mState == kAllocated); + + if (mTrack) { + mTrack->End(); + mTrack = nullptr; + mPrincipalHandle = PRINCIPAL_HANDLE_NONE; + } + mState = kReleased; + mImageContainer = nullptr; + + return NS_OK; +} + +static bool AllocateSolidColorFrame(layers::PlanarYCbCrData& aData, int aWidth, + int aHeight, int aY, int aCb, int aCr) { + MOZ_ASSERT(!(aWidth & 1)); + MOZ_ASSERT(!(aHeight & 1)); + // Allocate a single frame with a solid color + int yLen = aWidth * aHeight; + int cbLen = yLen >> 2; + int crLen = cbLen; + uint8_t* frame = (uint8_t*)malloc(yLen + cbLen + crLen); + if (!frame) { + return false; + } + memset(frame, aY, yLen); + memset(frame + yLen, aCb, cbLen); + memset(frame + yLen + cbLen, aCr, crLen); + + aData.mYChannel = frame; + aData.mYStride = aWidth; + aData.mCbCrStride = aWidth >> 1; + aData.mCbChannel = frame + yLen; + aData.mCrChannel = aData.mCbChannel + cbLen; + aData.mPictureRect = IntRect(0, 0, aWidth, aHeight); + aData.mStereoMode = StereoMode::MONO; + aData.mYUVColorSpace = gfx::YUVColorSpace::BT601; + aData.mChromaSubsampling = gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT; + return true; +} + +static void ReleaseFrame(layers::PlanarYCbCrData& aData) { + free(aData.mYChannel); +} + +void MediaEngineFakeVideoSource::SetTrack(const RefPtr& aTrack, + const PrincipalHandle& aPrincipal) { + AssertIsOnOwningThread(); + + MOZ_ASSERT(mState == kAllocated); + MOZ_ASSERT(!mTrack); + MOZ_ASSERT(aTrack->AsSourceTrack()); + + mTrack = aTrack->AsSourceTrack(); + mPrincipalHandle = aPrincipal; +} + +nsresult MediaEngineFakeVideoSource::Start() { + AssertIsOnOwningThread(); + + MOZ_ASSERT(mState == kAllocated || mState == kStopped); + MOZ_ASSERT(mTrack, "SetTrack() must happen before Start()"); + + mTimer = NS_NewTimer(GetCurrentSerialEventTarget()); + if (!mTimer) { + return NS_ERROR_FAILURE; + } + + if (!mImageContainer) { + mImageContainer = MakeAndAddRef( + layers::ImageContainer::ASYNCHRONOUS); + } + + // Start timer for subsequent frames + uint32_t interval; +#if defined(MOZ_WIDGET_ANDROID) && defined(DEBUG) + // emulator debug is very, very slow and has problems dealing with realtime + // audio inputs + interval = 10 * (1000 / mOpts.mFPS); +#else + interval = 1000 / mOpts.mFPS; +#endif + mTimer->InitWithNamedFuncCallback( + [](nsITimer* aTimer, void* aClosure) { + RefPtr source = + static_cast(aClosure); + source->GenerateFrame(); + }, + this, interval, nsITimer::TYPE_REPEATING_SLACK, + "MediaEngineFakeVideoSource::GenerateFrame"); + + mState = kStarted; + return NS_OK; +} + +nsresult MediaEngineFakeVideoSource::Stop() { + AssertIsOnOwningThread(); + + if (mState == kStopped || mState == kAllocated) { + return NS_OK; + } + + MOZ_ASSERT(mState == kStarted); + MOZ_ASSERT(mTimer); + MOZ_ASSERT(mTrack); + + mTimer->Cancel(); + mTimer = nullptr; + + mState = kStopped; + + return NS_OK; +} + +nsresult MediaEngineFakeVideoSource::Reconfigure( + const MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs, + const char** aOutBadConstraint) { + return NS_OK; +} + +void MediaEngineFakeVideoSource::GenerateFrame() { + AssertIsOnOwningThread(); + + // Update the target color + if (mCr <= 16) { + if (mCb < 240) { + mCb++; + } else { + mCr++; + } + } else if (mCb >= 240) { + if (mCr < 240) { + mCr++; + } else { + mCb--; + } + } else if (mCr >= 240) { + if (mCb > 16) { + mCb--; + } else { + mCr--; + } + } else { + mCr--; + } + + // Allocate a single solid color image + RefPtr ycbcr_image = + mImageContainer->CreatePlanarYCbCrImage(); + layers::PlanarYCbCrData data; + if (NS_WARN_IF(!AllocateSolidColorFrame(data, mOpts.mWidth, mOpts.mHeight, + 0x80, mCb, mCr))) { + return; + } + +#ifdef MOZ_WEBRTC + uint64_t timestamp = PR_Now(); + YuvStamper::Encode(mOpts.mWidth, mOpts.mHeight, mOpts.mWidth, data.mYChannel, + reinterpret_cast(×tamp), + sizeof(timestamp), 0, 0); +#endif + + bool setData = ycbcr_image->CopyData(data); + MOZ_ASSERT(setData); + + // SetData copies data, so we can free the frame + ReleaseFrame(data); + + if (!setData) { + return; + } + + VideoSegment segment; + segment.AppendFrame(ycbcr_image.forget(), + gfx::IntSize(mOpts.mWidth, mOpts.mHeight), + mPrincipalHandle); + mTrack->AppendData(&segment); +} + +// This class is created on the media thread, as part of Start(), then entirely +// self-sustained until destruction, just forwarding calls to Pull(). +class AudioSourcePullListener : public MediaTrackListener { + public: + AudioSourcePullListener(RefPtr aTrack, + const PrincipalHandle& aPrincipalHandle, + uint32_t aFrequency) + : mTrack(std::move(aTrack)), + mPrincipalHandle(aPrincipalHandle), + mSineGenerator(MakeUnique>( + mTrack->mSampleRate, aFrequency)) { + MOZ_COUNT_CTOR(AudioSourcePullListener); + } + + MOZ_COUNTED_DTOR(AudioSourcePullListener) + + void NotifyPull(MediaTrackGraph* aGraph, TrackTime aEndOfAppendedData, + TrackTime aDesiredTime) override; + + const RefPtr mTrack; + const PrincipalHandle mPrincipalHandle; + const UniquePtr> mSineGenerator; +}; + +/** + * Fake audio source. + */ +class MediaEngineFakeAudioSource : public MediaEngineSource { + public: + MediaEngineFakeAudioSource() = default; + + static nsString GetUUID(); + static nsString GetGroupId(); + + nsresult Allocate(const dom::MediaTrackConstraints& aConstraints, + const MediaEnginePrefs& aPrefs, uint64_t aWindowID, + const char** aOutBadConstraint) override; + void SetTrack(const RefPtr& aTrack, + const PrincipalHandle& aPrincipal) override; + nsresult Start() override; + nsresult Reconfigure(const dom::MediaTrackConstraints& aConstraints, + const MediaEnginePrefs& aPrefs, + const char** aOutBadConstraint) override; + nsresult Stop() override; + nsresult Deallocate() override; + + bool IsFake() const override { return true; } + + void GetSettings(dom::MediaTrackSettings& aOutSettings) const override; + + protected: + ~MediaEngineFakeAudioSource() = default; + + // Current state of this source. + MediaEngineSourceState mState = kReleased; + RefPtr mTrack; + PrincipalHandle mPrincipalHandle = PRINCIPAL_HANDLE_NONE; + uint32_t mFrequency = 1000; + RefPtr mPullListener; +}; + +nsString MediaEngineFakeAudioSource::GetUUID() { + return u"B7CBD7C1-53EF-42F9-8353-73F61C70C092"_ns; +} + +nsString MediaEngineFakeAudioSource::GetGroupId() { + return u"Fake Audio Group"_ns; +} + +void MediaEngineFakeAudioSource::GetSettings( + MediaTrackSettings& aOutSettings) const { + MOZ_ASSERT(NS_IsMainThread()); + aOutSettings.mAutoGainControl.Construct(false); + aOutSettings.mEchoCancellation.Construct(false); + aOutSettings.mNoiseSuppression.Construct(false); + aOutSettings.mChannelCount.Construct(1); +} + +nsresult MediaEngineFakeAudioSource::Allocate( + const MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs, + uint64_t aWindowID, const char** aOutBadConstraint) { + AssertIsOnOwningThread(); + + MOZ_ASSERT(mState == kReleased); + + mFrequency = aPrefs.mFreq ? aPrefs.mFreq : 1000; + + mState = kAllocated; + return NS_OK; +} + +nsresult MediaEngineFakeAudioSource::Deallocate() { + AssertIsOnOwningThread(); + + MOZ_ASSERT(mState == kStopped || mState == kAllocated); + + if (mTrack) { + mTrack->End(); + mTrack = nullptr; + mPrincipalHandle = PRINCIPAL_HANDLE_NONE; + } + mState = kReleased; + return NS_OK; +} + +void MediaEngineFakeAudioSource::SetTrack(const RefPtr& aTrack, + const PrincipalHandle& aPrincipal) { + AssertIsOnOwningThread(); + + MOZ_ASSERT(mState == kAllocated); + MOZ_ASSERT(!mTrack); + MOZ_ASSERT(aTrack->AsSourceTrack()); + + mTrack = aTrack->AsSourceTrack(); + mPrincipalHandle = aPrincipal; +} + +nsresult MediaEngineFakeAudioSource::Start() { + AssertIsOnOwningThread(); + + if (mState == kStarted) { + return NS_OK; + } + + MOZ_ASSERT(mState == kAllocated || mState == kStopped); + MOZ_ASSERT(mTrack, "SetTrack() must happen before Start()"); + + if (!mPullListener) { + mPullListener = MakeAndAddRef( + mTrack, mPrincipalHandle, mFrequency); + } + + mState = kStarted; + + NS_DispatchToMainThread(NS_NewRunnableFunction( + __func__, [track = mTrack, listener = mPullListener]() { + if (track->IsDestroyed()) { + return; + } + track->AddListener(listener); + track->SetPullingEnabled(true); + })); + + return NS_OK; +} + +nsresult MediaEngineFakeAudioSource::Stop() { + AssertIsOnOwningThread(); + + if (mState == kStopped || mState == kAllocated) { + return NS_OK; + } + MOZ_ASSERT(mState == kStarted); + mState = kStopped; + + NS_DispatchToMainThread(NS_NewRunnableFunction( + __func__, [track = mTrack, listener = std::move(mPullListener)]() { + if (track->IsDestroyed()) { + return; + } + track->RemoveListener(listener); + track->SetPullingEnabled(false); + })); + return NS_OK; +} + +nsresult MediaEngineFakeAudioSource::Reconfigure( + const MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs, + const char** aOutBadConstraint) { + return NS_OK; +} + +void AudioSourcePullListener::NotifyPull(MediaTrackGraph* aGraph, + TrackTime aEndOfAppendedData, + TrackTime aDesiredTime) { + TRACE_COMMENT("SourceMediaTrack::NotifyPull", "SourceMediaTrack %p", + mTrack.get()); + AudioSegment segment; + TrackTicks delta = aDesiredTime - aEndOfAppendedData; + CheckedInt bufferSize(sizeof(int16_t)); + bufferSize *= delta; + RefPtr buffer = SharedBuffer::Create(bufferSize); + int16_t* dest = static_cast(buffer->Data()); + mSineGenerator->generate(dest, delta); + AutoTArray channels; + channels.AppendElement(dest); + segment.AppendFrames(buffer.forget(), channels, delta, mPrincipalHandle); + mTrack->AppendData(&segment); +} + +MediaEngineFake::MediaEngineFake() = default; +MediaEngineFake::~MediaEngineFake() = default; + +void MediaEngineFake::EnumerateDevices( + MediaSourceEnum aMediaSource, MediaSinkEnum aMediaSink, + nsTArray>* aDevices) { + AssertIsOnOwningThread(); + using IsScary = MediaDevice::IsScary; + using OsPromptable = MediaDevice::OsPromptable; + + if (aMediaSink == MediaSinkEnum::Speaker) { + NS_WARNING("No default implementation for MediaSinkEnum::Speaker"); + } + + switch (aMediaSource) { + case MediaSourceEnum::Camera: { + nsString name = FakeVideoName(); + aDevices->EmplaceBack( + new MediaDevice(this, aMediaSource, name, /*aRawId=*/name, + MediaEngineFakeVideoSource::GetGroupId(), IsScary::No, + OsPromptable::No)); + return; + } + case MediaSourceEnum::Microphone: + aDevices->EmplaceBack( + new MediaDevice(this, aMediaSource, u"Default Audio Device"_ns, + MediaEngineFakeAudioSource::GetUUID(), + MediaEngineFakeAudioSource::GetGroupId(), IsScary::No, + OsPromptable::No)); + return; + default: + MOZ_ASSERT_UNREACHABLE("Unsupported source type"); + return; + } +} + +RefPtr MediaEngineFake::CreateSource( + const MediaDevice* aMediaDevice) { + MOZ_ASSERT(aMediaDevice->mEngine == this); + switch (aMediaDevice->mMediaSource) { + case MediaSourceEnum::Camera: + return new MediaEngineFakeVideoSource(); + case MediaSourceEnum::Microphone: + return new MediaEngineFakeAudioSource(); + default: + MOZ_ASSERT_UNREACHABLE("Unsupported source type"); + return nullptr; + } +} + +} // namespace mozilla diff --git a/dom/media/webrtc/MediaEngineFake.h b/dom/media/webrtc/MediaEngineFake.h new file mode 100644 index 0000000000..611529a067 --- /dev/null +++ b/dom/media/webrtc/MediaEngineFake.h @@ -0,0 +1,40 @@ +/* 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/. */ + +#ifndef MEDIAENGINEFAKE_H_ +#define MEDIAENGINEFAKE_H_ + +#include "nsTArrayForwardDeclare.h" +#include "MediaEngine.h" + +namespace mozilla { + +template +class MediaEventProducer; + +/** + * The fake implementation of the MediaEngine interface. + */ +class MediaEngineFake : public MediaEngine { + public: + MediaEngineFake(); + + void EnumerateDevices(dom::MediaSourceEnum, MediaSinkEnum, + nsTArray>*) override; + void Shutdown() override {} + RefPtr CreateSource(const MediaDevice* aDevice) override; + + MediaEventSource& DeviceListChangeEvent() override { + return mDeviceListChangeEvent; + } + bool IsFake() const override { return true; } + + private: + ~MediaEngineFake(); + MediaEventProducer mDeviceListChangeEvent; +}; + +} // namespace mozilla + +#endif /* NSMEDIAENGINEFAKE_H_ */ diff --git a/dom/media/webrtc/MediaEnginePrefs.h b/dom/media/webrtc/MediaEnginePrefs.h new file mode 100644 index 0000000000..6c9775a238 --- /dev/null +++ b/dom/media/webrtc/MediaEnginePrefs.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef MediaEnginePrefs_h +#define MediaEnginePrefs_h + +#include +#include + +namespace mozilla { + +/** + * Video source and friends. + */ +class MediaEnginePrefs { + public: + static const int DEFAULT_VIDEO_FPS = 30; + static const int DEFAULT_43_VIDEO_WIDTH = 640; + static const int DEFAULT_43_VIDEO_HEIGHT = 480; + static const int DEFAULT_169_VIDEO_WIDTH = 1280; + static const int DEFAULT_169_VIDEO_HEIGHT = 720; + + MediaEnginePrefs() + : mWidth(0), + mHeight(0), + mFPS(0), + mFreq(0), + mAecOn(false), + mUseAecMobile(false), + mAgcOn(false), + mHPFOn(false), + mNoiseOn(false), + mTransientOn(false), + mResidualEchoOn(false), + mAgc2Forced(false), + mAgc(0), + mNoise(0), + mChannels(0) {} + + int32_t mWidth; + int32_t mHeight; + int32_t mFPS; + int32_t mFreq; // for test tones (fake:true) + bool mAecOn; + bool mUseAecMobile; + bool mAgcOn; + bool mHPFOn; + bool mNoiseOn; + bool mTransientOn; + bool mResidualEchoOn; + bool mAgc2Forced; + int32_t mAgc; + int32_t mNoise; + int32_t mChannels; + + bool operator==(const MediaEnginePrefs& aRhs) { + return memcmp(this, &aRhs, sizeof(MediaEnginePrefs)) == 0; + }; + + // mWidth and/or mHeight may be zero (=adaptive default), so use functions. + + int32_t GetWidth(bool aHD = false) const { + return mWidth ? mWidth + : (mHeight ? (mHeight * GetDefWidth(aHD)) / GetDefHeight(aHD) + : GetDefWidth(aHD)); + } + + int32_t GetHeight(bool aHD = false) const { + return mHeight ? mHeight + : (mWidth ? (mWidth * GetDefHeight(aHD)) / GetDefWidth(aHD) + : GetDefHeight(aHD)); + } + + private: + static int32_t GetDefWidth(bool aHD = false) { + // It'd be nice if we could use the ternary operator here, but we can't + // because of bug 1002729. + if (aHD) { + return DEFAULT_169_VIDEO_WIDTH; + } + + return DEFAULT_43_VIDEO_WIDTH; + } + + static int32_t GetDefHeight(bool aHD = false) { + // It'd be nice if we could use the ternary operator here, but we can't + // because of bug 1002729. + if (aHD) { + return DEFAULT_169_VIDEO_HEIGHT; + } + + return DEFAULT_43_VIDEO_HEIGHT; + } +}; + +} // namespace mozilla + +#endif // MediaEnginePrefs_h diff --git a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp new file mode 100644 index 0000000000..ce9f4ad9a8 --- /dev/null +++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp @@ -0,0 +1,907 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 "MediaEngineRemoteVideoSource.h" + +#include "CamerasChild.h" +#include "MediaManager.h" +#include "MediaTrackConstraints.h" +#include "mozilla/dom/MediaTrackSettingsBinding.h" +#include "mozilla/ErrorNames.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/RefPtr.h" +#include "PerformanceRecorder.h" +#include "Tracing.h" +#include "VideoFrameUtils.h" +#include "VideoUtils.h" +#include "ImageContainer.h" +#include "common_video/include/video_frame_buffer.h" +#include "common_video/libyuv/include/webrtc_libyuv.h" + +namespace mozilla { + +extern LazyLogModule gMediaManagerLog; +#define LOG(...) MOZ_LOG(gMediaManagerLog, LogLevel::Debug, (__VA_ARGS__)) +#define LOG_FRAME(...) \ + MOZ_LOG(gMediaManagerLog, LogLevel::Verbose, (__VA_ARGS__)) + +using dom::ConstrainLongRange; +using dom::MediaSourceEnum; +using dom::MediaTrackConstraints; +using dom::MediaTrackConstraintSet; +using dom::MediaTrackSettings; +using dom::VideoFacingModeEnum; + +/* static */ +camera::CaptureEngine MediaEngineRemoteVideoSource::CaptureEngine( + MediaSourceEnum aMediaSource) { + switch (aMediaSource) { + case MediaSourceEnum::Browser: + return camera::BrowserEngine; + case MediaSourceEnum::Camera: + return camera::CameraEngine; + case MediaSourceEnum::Screen: + return camera::ScreenEngine; + case MediaSourceEnum::Window: + return camera::WinEngine; + default: + MOZ_CRASH(); + } +} + +static Maybe GetFacingMode(const nsString& aDeviceName) { + // Set facing mode based on device name. +#if defined(ANDROID) + // Names are generated. Example: "Camera 0, Facing back, Orientation 90" + // + // See media/webrtc/trunk/webrtc/modules/video_capture/android/java/src/org/ + // webrtc/videoengine/VideoCaptureDeviceInfoAndroid.java + + if (aDeviceName.Find(u"Facing back"_ns) != kNotFound) { + return Some(VideoFacingModeEnum::Environment); + } + if (aDeviceName.Find(u"Facing front"_ns) != kNotFound) { + return Some(VideoFacingModeEnum::User); + } +#endif // ANDROID +#ifdef XP_MACOSX + // Kludge to test user-facing cameras on OSX. + if (aDeviceName.Find(u"Face"_ns) != -1) { + return Some(VideoFacingModeEnum::User); + } +#endif +#ifdef XP_WIN + // The cameras' name of Surface book are "Microsoft Camera Front" and + // "Microsoft Camera Rear" respectively. + + if (aDeviceName.Find(u"Front"_ns) != kNotFound) { + return Some(VideoFacingModeEnum::User); + } + if (aDeviceName.Find(u"Rear"_ns) != kNotFound) { + return Some(VideoFacingModeEnum::Environment); + } +#endif // WINDOWS + + return Nothing(); +} + +MediaEngineRemoteVideoSource::MediaEngineRemoteVideoSource( + const MediaDevice* aMediaDevice) + : mCapEngine(CaptureEngine(aMediaDevice->mMediaSource)), + mTrackingId(CaptureEngineToTrackingSourceStr(mCapEngine), 0), + mMutex("MediaEngineRemoteVideoSource::mMutex"), + mRescalingBufferPool(/* zero_initialize */ false, + /* max_number_of_buffers */ 1), + mSettingsUpdatedByFrame(MakeAndAddRef>()), + mSettings(MakeAndAddRef>()), + mFirstFramePromise(mFirstFramePromiseHolder.Ensure(__func__)), + mMediaDevice(aMediaDevice), + mDeviceUUID(NS_ConvertUTF16toUTF8(aMediaDevice->mRawID)) { + LOG("%s", __PRETTY_FUNCTION__); + mSettings->mWidth.Construct(0); + mSettings->mHeight.Construct(0); + mSettings->mFrameRate.Construct(0); + if (mCapEngine == camera::CameraEngine) { + // Only cameras can have a facing mode. + Maybe facingMode = + GetFacingMode(mMediaDevice->mRawName); + if (facingMode.isSome()) { + NS_ConvertASCIItoUTF16 facingString( + dom::VideoFacingModeEnumValues::GetString(*facingMode)); + mSettings->mFacingMode.Construct(facingString); + mFacingMode.emplace(facingString); + } + } +} + +MediaEngineRemoteVideoSource::~MediaEngineRemoteVideoSource() { + mFirstFramePromiseHolder.RejectIfExists(NS_ERROR_ABORT, __func__); +} + +nsresult MediaEngineRemoteVideoSource::Allocate( + const MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs, + uint64_t aWindowID, const char** aOutBadConstraint) { + LOG("%s", __PRETTY_FUNCTION__); + AssertIsOnOwningThread(); + + MOZ_ASSERT(mState == kReleased); + + NormalizedConstraints constraints(aConstraints); + webrtc::CaptureCapability newCapability; + LOG("ChooseCapability(kFitness) for mCapability (Allocate) ++"); + if (!ChooseCapability(constraints, aPrefs, newCapability, kFitness)) { + *aOutBadConstraint = + MediaConstraintsHelper::FindBadConstraint(constraints, mMediaDevice); + return NS_ERROR_FAILURE; + } + LOG("ChooseCapability(kFitness) for mCapability (Allocate) --"); + + mCaptureId = + camera::GetChildAndCall(&camera::CamerasChild::AllocateCapture, + mCapEngine, mDeviceUUID.get(), aWindowID); + if (mCaptureId < 0) { + return NS_ERROR_FAILURE; + } + + { + MutexAutoLock lock(mMutex); + mState = kAllocated; + mCapability = newCapability; + mTrackingId = + TrackingId(CaptureEngineToTrackingSourceStr(mCapEngine), mCaptureId); + } + + LOG("Video device %d allocated", mCaptureId); + return NS_OK; +} + +nsresult MediaEngineRemoteVideoSource::Deallocate() { + LOG("%s", __PRETTY_FUNCTION__); + AssertIsOnOwningThread(); + + MOZ_ASSERT(mState == kStopped || mState == kAllocated); + + if (mTrack) { + mTrack->End(); + } + + { + MutexAutoLock lock(mMutex); + + mTrack = nullptr; + mPrincipal = PRINCIPAL_HANDLE_NONE; + mState = kReleased; + } + + // Stop() has stopped capture synchronously on the media thread before we get + // here, so there are no longer any callbacks on an IPC thread accessing + // mImageContainer or mRescalingBufferPool. + mImageContainer = nullptr; + mRescalingBufferPool.Release(); + + LOG("Video device %d deallocated", mCaptureId); + + if (camera::GetChildAndCall(&camera::CamerasChild::ReleaseCapture, mCapEngine, + mCaptureId)) { + // Failure can occur when the parent process is shutting down. + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +void MediaEngineRemoteVideoSource::SetTrack(const RefPtr& aTrack, + const PrincipalHandle& aPrincipal) { + LOG("%s", __PRETTY_FUNCTION__); + AssertIsOnOwningThread(); + + MOZ_ASSERT(mState == kAllocated); + MOZ_ASSERT(!mTrack); + MOZ_ASSERT(aTrack); + MOZ_ASSERT(aTrack->AsSourceTrack()); + + if (!mImageContainer) { + mImageContainer = MakeAndAddRef( + layers::ImageContainer::ASYNCHRONOUS); + } + + { + MutexAutoLock lock(mMutex); + mTrack = aTrack->AsSourceTrack(); + mPrincipal = aPrincipal; + } +} + +nsresult MediaEngineRemoteVideoSource::Start() { + LOG("%s", __PRETTY_FUNCTION__); + AssertIsOnOwningThread(); + + MOZ_ASSERT(mState == kAllocated || mState == kStopped); + MOZ_ASSERT(mTrack); + + { + MutexAutoLock lock(mMutex); + mState = kStarted; + } + + mSettingsUpdatedByFrame->mValue = false; + + if (camera::GetChildAndCall(&camera::CamerasChild::StartCapture, mCapEngine, + mCaptureId, mCapability, this)) { + LOG("StartCapture failed"); + MutexAutoLock lock(mMutex); + mState = kStopped; + return NS_ERROR_FAILURE; + } + + NS_DispatchToMainThread(NS_NewRunnableFunction( + "MediaEngineRemoteVideoSource::SetLastCapability", + [settings = mSettings, updated = mSettingsUpdatedByFrame, + capEngine = mCapEngine, cap = mCapability]() mutable { + switch (capEngine) { + case camera::ScreenEngine: + case camera::WinEngine: + // Undo the hack where ideal and max constraints are crammed + // together in mCapability for consumption by low-level code. We + // don't actually know the real resolution yet, so report min(ideal, + // max) for now. + // TODO: This can be removed in bug 1453269. + cap.width = std::min(cap.width >> 16, cap.width & 0xffff); + cap.height = std::min(cap.height >> 16, cap.height & 0xffff); + break; + default: + break; + } + + if (!updated->mValue) { + settings->mWidth.Value() = cap.width; + settings->mHeight.Value() = cap.height; + } + settings->mFrameRate.Value() = cap.maxFPS; + })); + + return NS_OK; +} + +nsresult MediaEngineRemoteVideoSource::FocusOnSelectedSource() { + LOG("%s", __PRETTY_FUNCTION__); + AssertIsOnOwningThread(); + + int result; + result = camera::GetChildAndCall(&camera::CamerasChild::FocusOnSelectedSource, + mCapEngine, mCaptureId); + return result == 0 ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult MediaEngineRemoteVideoSource::Stop() { + LOG("%s", __PRETTY_FUNCTION__); + AssertIsOnOwningThread(); + + if (mState == kStopped || mState == kAllocated) { + return NS_OK; + } + + MOZ_ASSERT(mState == kStarted); + + if (camera::GetChildAndCall(&camera::CamerasChild::StopCapture, mCapEngine, + mCaptureId)) { + // Failure can occur when the parent process is shutting down. + return NS_ERROR_FAILURE; + } + + { + MutexAutoLock lock(mMutex); + mState = kStopped; + } + + return NS_OK; +} + +nsresult MediaEngineRemoteVideoSource::Reconfigure( + const MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs, + const char** aOutBadConstraint) { + LOG("%s", __PRETTY_FUNCTION__); + AssertIsOnOwningThread(); + + NormalizedConstraints constraints(aConstraints); + webrtc::CaptureCapability newCapability; + LOG("ChooseCapability(kFitness) for mTargetCapability (Reconfigure) ++"); + if (!ChooseCapability(constraints, aPrefs, newCapability, kFitness)) { + *aOutBadConstraint = + MediaConstraintsHelper::FindBadConstraint(constraints, mMediaDevice); + return NS_ERROR_INVALID_ARG; + } + LOG("ChooseCapability(kFitness) for mTargetCapability (Reconfigure) --"); + + if (mCapability == newCapability) { + return NS_OK; + } + + bool started = mState == kStarted; + if (started) { + nsresult rv = Stop(); + if (NS_WARN_IF(NS_FAILED(rv))) { + nsAutoCString name; + GetErrorName(rv, name); + LOG("Video source %p for video device %d Reconfigure() failed " + "unexpectedly in Stop(). rv=%s", + this, mCaptureId, name.Data()); + return NS_ERROR_UNEXPECTED; + } + } + + { + MutexAutoLock lock(mMutex); + // Start() applies mCapability on the device. + mCapability = newCapability; + } + + if (started) { + nsresult rv = Start(); + if (NS_WARN_IF(NS_FAILED(rv))) { + nsAutoCString name; + GetErrorName(rv, name); + LOG("Video source %p for video device %d Reconfigure() failed " + "unexpectedly in Start(). rv=%s", + this, mCaptureId, name.Data()); + return NS_ERROR_UNEXPECTED; + } + } + + return NS_OK; +} + +size_t MediaEngineRemoteVideoSource::NumCapabilities() const { + AssertIsOnOwningThread(); + + if (!mCapabilities.IsEmpty()) { + return mCapabilities.Length(); + } + + int num = camera::GetChildAndCall(&camera::CamerasChild::NumberOfCapabilities, + mCapEngine, mDeviceUUID.get()); + if (num > 0) { + mCapabilities.SetLength(num); + } else { + // The default for devices that don't return discrete capabilities: treat + // them as supporting all capabilities orthogonally. E.g. screensharing. + // CaptureCapability defaults key values to 0, which means accept any value. + mCapabilities.AppendElement(MakeUnique()); + mCapabilitiesAreHardcoded = true; + } + + return mCapabilities.Length(); +} + +webrtc::CaptureCapability& MediaEngineRemoteVideoSource::GetCapability( + size_t aIndex) const { + AssertIsOnOwningThread(); + MOZ_RELEASE_ASSERT(aIndex < mCapabilities.Length()); + if (!mCapabilities[aIndex]) { + mCapabilities[aIndex] = MakeUnique(); + camera::GetChildAndCall(&camera::CamerasChild::GetCaptureCapability, + mCapEngine, mDeviceUUID.get(), aIndex, + mCapabilities[aIndex].get()); + } + return *mCapabilities[aIndex]; +} + +const TrackingId& MediaEngineRemoteVideoSource::GetTrackingId() const { + AssertIsOnOwningThread(); + MOZ_ASSERT(mState != kReleased); + return mTrackingId; +} + +int MediaEngineRemoteVideoSource::DeliverFrame( + uint8_t* aBuffer, const camera::VideoFrameProperties& aProps) { + // Cameras IPC thread - take great care with accessing members! + + Maybe req_max_width; + Maybe req_max_height; + Maybe req_ideal_width; + Maybe req_ideal_height; + { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mState == kStarted); + // TODO: These can be removed in bug 1453269. + const int32_t max_width = mCapability.width & 0xffff; + const int32_t max_height = mCapability.height & 0xffff; + const int32_t ideal_width = (mCapability.width >> 16) & 0xffff; + const int32_t ideal_height = (mCapability.height >> 16) & 0xffff; + + req_max_width = max_width ? Some(max_width) : Nothing(); + req_max_height = max_height ? Some(max_height) : Nothing(); + req_ideal_width = ideal_width ? Some(ideal_width) : Nothing(); + req_ideal_height = ideal_height ? Some(ideal_height) : Nothing(); + if (!mFrameDeliveringTrackingId) { + mFrameDeliveringTrackingId = Some(mTrackingId); + } + } + + // This is only used in the case of screen sharing, see bug 1453269. + + if (aProps.rotation() == 90 || aProps.rotation() == 270) { + // This frame is rotated, so what was negotiated as width is now height, + // and vice versa. + std::swap(req_max_width, req_max_height); + std::swap(req_ideal_width, req_ideal_height); + } + + int32_t dst_max_width = + std::min(aProps.width(), req_max_width.valueOr(aProps.width())); + int32_t dst_max_height = + std::min(aProps.height(), req_max_height.valueOr(aProps.height())); + // This logic works for both camera and screen sharing case. + // for camera case, req_ideal_width and req_ideal_height are absent. + int32_t dst_width = req_ideal_width.valueOr(aProps.width()); + int32_t dst_height = req_ideal_height.valueOr(aProps.height()); + + if (!req_ideal_width && req_ideal_height) { + dst_width = *req_ideal_height * aProps.width() / aProps.height(); + } else if (!req_ideal_height && req_ideal_width) { + dst_height = *req_ideal_width * aProps.height() / aProps.width(); + } + dst_width = std::min(dst_width, dst_max_width); + dst_height = std::min(dst_height, dst_max_height); + + // Apply scaling for screen sharing, see bug 1453269. + switch (mCapEngine) { + case camera::ScreenEngine: + case camera::WinEngine: { + // scale to average of portrait and landscape + float scale_width = (float)dst_width / (float)aProps.width(); + float scale_height = (float)dst_height / (float)aProps.height(); + float scale = (scale_width + scale_height) / 2; + // If both req_ideal_width & req_ideal_height are absent, scale is 1, but + // if one is present and the other not, scale precisely to the one present + if (!req_ideal_width) { + scale = scale_height; + } else if (!req_ideal_height) { + scale = scale_width; + } + dst_width = int32_t(scale * (float)aProps.width()); + dst_height = int32_t(scale * (float)aProps.height()); + + // if scaled rectangle exceeds max rectangle, scale to minimum of portrait + // and landscape + if (dst_width > dst_max_width || dst_height > dst_max_height) { + scale_width = (float)dst_max_width / (float)dst_width; + scale_height = (float)dst_max_height / (float)dst_height; + scale = std::min(scale_width, scale_height); + dst_width = int32_t(scale * dst_width); + dst_height = int32_t(scale * dst_height); + } + break; + } + default: { + break; + } + } + + // Ensure width and height are at least two. Smaller frames can lead to + // problems with scaling and video encoding. + dst_width = std::max(2, dst_width); + dst_height = std::max(2, dst_height); + + std::function callback_unused = []() {}; + rtc::scoped_refptr buffer = + webrtc::WrapI420Buffer( + aProps.width(), aProps.height(), aBuffer, aProps.yStride(), + aBuffer + aProps.yAllocatedSize(), aProps.uStride(), + aBuffer + aProps.yAllocatedSize() + aProps.uAllocatedSize(), + aProps.vStride(), callback_unused); + + if ((dst_width != aProps.width() || dst_height != aProps.height()) && + dst_width <= aProps.width() && dst_height <= aProps.height()) { + PerformanceRecorder rec("MERVS::CropAndScale"_ns, + *mFrameDeliveringTrackingId, + dst_width, dst_height); + // Destination resolution is smaller than source buffer. We'll rescale. + rtc::scoped_refptr scaledBuffer = + mRescalingBufferPool.CreateI420Buffer(dst_width, dst_height); + if (!scaledBuffer) { + MOZ_ASSERT_UNREACHABLE( + "We might fail to allocate a buffer, but with this " + "being a recycling pool that shouldn't happen"); + return 0; + } + scaledBuffer->CropAndScaleFrom(*buffer); + buffer = scaledBuffer; + rec.Record(); + } + + layers::PlanarYCbCrData data; + data.mYChannel = const_cast(buffer->DataY()); + data.mYStride = buffer->StrideY(); + MOZ_ASSERT(buffer->StrideU() == buffer->StrideV()); + data.mCbCrStride = buffer->StrideU(); + data.mCbChannel = const_cast(buffer->DataU()); + data.mCrChannel = const_cast(buffer->DataV()); + data.mPictureRect = gfx::IntRect(0, 0, buffer->width(), buffer->height()); + data.mYUVColorSpace = gfx::YUVColorSpace::BT601; + data.mChromaSubsampling = gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT; + + RefPtr image; + { + PerformanceRecorder rec( + "MERVS::Copy"_ns, *mFrameDeliveringTrackingId, dst_width, dst_height); + image = mImageContainer->CreatePlanarYCbCrImage(); + if (!image->CopyData(data)) { + MOZ_ASSERT_UNREACHABLE( + "We might fail to allocate a buffer, but with this " + "being a recycling container that shouldn't happen"); + return 0; + } + rec.Record(); + } + +#ifdef DEBUG + static uint32_t frame_num = 0; + LOG_FRAME( + "frame %d (%dx%d)->(%dx%d); rotation %d, timeStamp %u, ntpTimeMs %" PRIu64 + ", renderTimeMs %" PRIu64, + frame_num++, aProps.width(), aProps.height(), dst_width, dst_height, + aProps.rotation(), aProps.timeStamp(), aProps.ntpTimeMs(), + aProps.renderTimeMs()); +#endif + + if (mImageSize.width != dst_width || mImageSize.height != dst_height) { + NS_DispatchToMainThread(NS_NewRunnableFunction( + "MediaEngineRemoteVideoSource::FrameSizeChange", + [settings = mSettings, updated = mSettingsUpdatedByFrame, + holder = std::move(mFirstFramePromiseHolder), dst_width, + dst_height]() mutable { + settings->mWidth.Value() = dst_width; + settings->mHeight.Value() = dst_height; + updated->mValue = true; + // Since mImageSize was initialized to (0,0), we end up here on the + // arrival of the first frame. We resolve the promise representing + // arrival of first frame, after correct settings values have been + // made available (Resolve() is idempotent if already resolved). + holder.ResolveIfExists(true, __func__); + })); + } + + { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mState == kStarted); + VideoSegment segment; + mImageSize = image->GetSize(); + segment.AppendFrame(image.forget(), mImageSize, mPrincipal); + mTrack->AppendData(&segment); + } + + return 0; +} + +uint32_t MediaEngineRemoteVideoSource::GetDistance( + const webrtc::CaptureCapability& aCandidate, + const NormalizedConstraintSet& aConstraints, + const DistanceCalculation aCalculate) const { + if (aCalculate == kFeasibility) { + return GetFeasibilityDistance(aCandidate, aConstraints); + } + return GetFitnessDistance(aCandidate, aConstraints); +} + +uint32_t MediaEngineRemoteVideoSource::GetFitnessDistance( + const webrtc::CaptureCapability& aCandidate, + const NormalizedConstraintSet& aConstraints) const { + AssertIsOnOwningThread(); + + // Treat width|height|frameRate == 0 on capability as "can do any". + // This allows for orthogonal capabilities that are not in discrete steps. + + typedef MediaConstraintsHelper H; + uint64_t distance = + uint64_t(H::FitnessDistance(mFacingMode, aConstraints.mFacingMode)) + + uint64_t(aCandidate.width ? H::FitnessDistance(int32_t(aCandidate.width), + aConstraints.mWidth) + : 0) + + uint64_t(aCandidate.height + ? H::FitnessDistance(int32_t(aCandidate.height), + aConstraints.mHeight) + : 0) + + uint64_t(aCandidate.maxFPS ? H::FitnessDistance(double(aCandidate.maxFPS), + aConstraints.mFrameRate) + : 0); + return uint32_t(std::min(distance, uint64_t(UINT32_MAX))); +} + +uint32_t MediaEngineRemoteVideoSource::GetFeasibilityDistance( + const webrtc::CaptureCapability& aCandidate, + const NormalizedConstraintSet& aConstraints) const { + AssertIsOnOwningThread(); + + // Treat width|height|frameRate == 0 on capability as "can do any". + // This allows for orthogonal capabilities that are not in discrete steps. + + typedef MediaConstraintsHelper H; + uint64_t distance = + uint64_t(H::FitnessDistance(mFacingMode, aConstraints.mFacingMode)) + + uint64_t(aCandidate.width + ? H::FeasibilityDistance(int32_t(aCandidate.width), + aConstraints.mWidth) + : 0) + + uint64_t(aCandidate.height + ? H::FeasibilityDistance(int32_t(aCandidate.height), + aConstraints.mHeight) + : 0) + + uint64_t(aCandidate.maxFPS + ? H::FeasibilityDistance(double(aCandidate.maxFPS), + aConstraints.mFrameRate) + : 0); + return uint32_t(std::min(distance, uint64_t(UINT32_MAX))); +} + +// Find best capability by removing inferiors. May leave >1 of equal distance + +/* static */ +void MediaEngineRemoteVideoSource::TrimLessFitCandidates( + nsTArray& aSet) { + uint32_t best = UINT32_MAX; + for (auto& candidate : aSet) { + if (best > candidate.mDistance) { + best = candidate.mDistance; + } + } + aSet.RemoveElementsBy( + [best](const auto& set) { return set.mDistance > best; }); + MOZ_ASSERT(aSet.Length()); +} + +uint32_t MediaEngineRemoteVideoSource::GetBestFitnessDistance( + const nsTArray& aConstraintSets) const { + AssertIsOnOwningThread(); + + size_t num = NumCapabilities(); + nsTArray candidateSet; + for (size_t i = 0; i < num; i++) { + candidateSet.AppendElement(CapabilityCandidate(GetCapability(i))); + } + + bool first = true; + for (const NormalizedConstraintSet* ns : aConstraintSets) { + for (size_t i = 0; i < candidateSet.Length();) { + auto& candidate = candidateSet[i]; + uint32_t distance = GetFitnessDistance(candidate.mCapability, *ns); + if (distance == UINT32_MAX) { + candidateSet.RemoveElementAt(i); + } else { + ++i; + if (first) { + candidate.mDistance = distance; + } + } + } + first = false; + } + if (!candidateSet.Length()) { + return UINT32_MAX; + } + TrimLessFitCandidates(candidateSet); + return candidateSet[0].mDistance; +} + +static const char* ConvertVideoTypeToCStr(webrtc::VideoType aType) { + switch (aType) { + case webrtc::VideoType::kI420: + return "I420"; + case webrtc::VideoType::kIYUV: + case webrtc::VideoType::kYV12: + return "YV12"; + case webrtc::VideoType::kRGB24: + return "24BG"; + case webrtc::VideoType::kABGR: + return "ABGR"; + case webrtc::VideoType::kARGB: + return "ARGB"; + case webrtc::VideoType::kARGB4444: + return "R444"; + case webrtc::VideoType::kRGB565: + return "RGBP"; + case webrtc::VideoType::kARGB1555: + return "RGBO"; + case webrtc::VideoType::kYUY2: + return "YUY2"; + case webrtc::VideoType::kUYVY: + return "UYVY"; + case webrtc::VideoType::kMJPEG: + return "MJPG"; + case webrtc::VideoType::kNV21: + return "NV21"; + case webrtc::VideoType::kNV12: + return "NV12"; + case webrtc::VideoType::kBGRA: + return "BGRA"; + case webrtc::VideoType::kUnknown: + default: + return "unknown"; + } +} + +static void LogCapability(const char* aHeader, + const webrtc::CaptureCapability& aCapability, + uint32_t aDistance) { + LOG("%s: %4u x %4u x %2u maxFps, %s. Distance = %" PRIu32, aHeader, + aCapability.width, aCapability.height, aCapability.maxFPS, + ConvertVideoTypeToCStr(aCapability.videoType), aDistance); +} + +bool MediaEngineRemoteVideoSource::ChooseCapability( + const NormalizedConstraints& aConstraints, const MediaEnginePrefs& aPrefs, + webrtc::CaptureCapability& aCapability, + const DistanceCalculation aCalculate) { + LOG("%s", __PRETTY_FUNCTION__); + AssertIsOnOwningThread(); + + if (MOZ_LOG_TEST(gMediaManagerLog, LogLevel::Debug)) { + LOG("ChooseCapability: prefs: %dx%d @%dfps", aPrefs.GetWidth(), + aPrefs.GetHeight(), aPrefs.mFPS); + MediaConstraintsHelper::LogConstraints(aConstraints); + if (!aConstraints.mAdvanced.empty()) { + LOG("Advanced array[%zu]:", aConstraints.mAdvanced.size()); + for (auto& advanced : aConstraints.mAdvanced) { + MediaConstraintsHelper::LogConstraints(advanced); + } + } + } + + switch (mCapEngine) { + case camera::ScreenEngine: + case camera::WinEngine: { + FlattenedConstraints c(aConstraints); + // The actual resolution to constrain around is not easy to find ahead of + // time (and may in fact change over time), so as a hack, we push ideal + // and max constraints down to desktop_capture_impl.cc and finish the + // algorithm there. + // TODO: This can be removed in bug 1453269. + aCapability.width = + (std::min(0xffff, c.mWidth.mIdeal.valueOr(0)) & 0xffff) << 16 | + (std::min(0xffff, c.mWidth.mMax) & 0xffff); + aCapability.height = + (std::min(0xffff, c.mHeight.mIdeal.valueOr(0)) & 0xffff) << 16 | + (std::min(0xffff, c.mHeight.mMax) & 0xffff); + aCapability.maxFPS = + c.mFrameRate.Clamp(c.mFrameRate.mIdeal.valueOr(aPrefs.mFPS)); + return true; + } + case camera::BrowserEngine: { + FlattenedConstraints c(aConstraints); + aCapability.maxFPS = + c.mFrameRate.Clamp(c.mFrameRate.mIdeal.valueOr(aPrefs.mFPS)); + return true; + } + default: + break; + } + + nsTArray candidateSet; + size_t num = NumCapabilities(); + for (size_t i = 0; i < num; i++) { + candidateSet.AppendElement(CapabilityCandidate(GetCapability(i))); + } + + if (mCapabilitiesAreHardcoded && mCapEngine == camera::CameraEngine) { + // We have a hardcoded capability, which means this camera didn't report + // discrete capabilities. It might still allow a ranged capability, so we + // add a couple of default candidates based on prefs and constraints. + // The chosen candidate will be propagated to StartCapture() which will fail + // for an invalid candidate. + MOZ_DIAGNOSTIC_ASSERT(mCapabilities.Length() == 1); + MOZ_DIAGNOSTIC_ASSERT(candidateSet.Length() == 1); + candidateSet.Clear(); + + FlattenedConstraints c(aConstraints); + // Reuse the code across both the low-definition (`false`) pref and + // the high-definition (`true`) pref. + // If there are constraints we try to satisfy them but we default to prefs. + // Note that since constraints are from content and can literally be + // anything we put (rather generous) caps on them. + for (bool isHd : {false, true}) { + webrtc::CaptureCapability cap; + int32_t prefWidth = aPrefs.GetWidth(isHd); + int32_t prefHeight = aPrefs.GetHeight(isHd); + + cap.width = c.mWidth.Get(prefWidth); + cap.width = std::max(0, std::min(cap.width, 7680)); + + cap.height = c.mHeight.Get(prefHeight); + cap.height = std::max(0, std::min(cap.height, 4320)); + + cap.maxFPS = c.mFrameRate.Get(aPrefs.mFPS); + cap.maxFPS = std::max(0, std::min(cap.maxFPS, 480)); + + if (cap.width != prefWidth) { + // Width was affected by constraints. + // We'll adjust the height too so the aspect ratio is retained. + cap.height = cap.width * prefHeight / prefWidth; + } else if (cap.height != prefHeight) { + // Height was affected by constraints but not width. + // We'll adjust the width too so the aspect ratio is retained. + cap.width = cap.height * prefWidth / prefHeight; + } + + if (candidateSet.Contains(cap, CapabilityComparator())) { + continue; + } + LogCapability("Hardcoded capability", cap, 0); + candidateSet.AppendElement(cap); + } + } + + // First, filter capabilities by required constraints (min, max, exact). + + for (size_t i = 0; i < candidateSet.Length();) { + auto& candidate = candidateSet[i]; + candidate.mDistance = + GetDistance(candidate.mCapability, aConstraints, aCalculate); + LogCapability("Capability", candidate.mCapability, candidate.mDistance); + if (candidate.mDistance == UINT32_MAX) { + candidateSet.RemoveElementAt(i); + } else { + ++i; + } + } + + if (candidateSet.IsEmpty()) { + LOG("failed to find capability match from %zu choices", + candidateSet.Length()); + return false; + } + + // Filter further with all advanced constraints (that don't overconstrain). + + for (const auto& cs : aConstraints.mAdvanced) { + nsTArray rejects; + for (size_t i = 0; i < candidateSet.Length();) { + if (GetDistance(candidateSet[i].mCapability, cs, aCalculate) == + UINT32_MAX) { + rejects.AppendElement(candidateSet[i]); + candidateSet.RemoveElementAt(i); + } else { + ++i; + } + } + if (!candidateSet.Length()) { + candidateSet.AppendElements(std::move(rejects)); + } + } + MOZ_ASSERT( + candidateSet.Length(), + "advanced constraints filtering step can't reduce candidates to zero"); + + // Remaining algorithm is up to the UA. + + TrimLessFitCandidates(candidateSet); + + // Any remaining multiples all have the same distance. A common case of this + // occurs when no ideal is specified. Lean toward defaults. + uint32_t sameDistance = candidateSet[0].mDistance; + { + MediaTrackConstraintSet prefs; + prefs.mWidth.Construct().SetAsLong() = aPrefs.GetWidth(); + prefs.mHeight.Construct().SetAsLong() = aPrefs.GetHeight(); + prefs.mFrameRate.Construct().SetAsDouble() = aPrefs.mFPS; + NormalizedConstraintSet normPrefs(prefs, false); + + for (auto& candidate : candidateSet) { + candidate.mDistance = + GetDistance(candidate.mCapability, normPrefs, aCalculate); + } + TrimLessFitCandidates(candidateSet); + } + + aCapability = candidateSet[0].mCapability; + + LogCapability("Chosen capability", aCapability, sameDistance); + return true; +} + +void MediaEngineRemoteVideoSource::GetSettings( + MediaTrackSettings& aOutSettings) const { + aOutSettings = *mSettings; +} + +} // namespace mozilla diff --git a/dom/media/webrtc/MediaEngineRemoteVideoSource.h b/dom/media/webrtc/MediaEngineRemoteVideoSource.h new file mode 100644 index 0000000000..7e4f852701 --- /dev/null +++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.h @@ -0,0 +1,242 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* 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/. */ + +#ifndef MEDIAENGINE_REMOTE_VIDEO_SOURCE_H_ +#define MEDIAENGINE_REMOTE_VIDEO_SOURCE_H_ + +#include "prcvar.h" +#include "prthread.h" + +#include "mozilla/Mutex.h" +#include "nsCOMPtr.h" +#include "nsThreadUtils.h" +#include "DOMMediaStream.h" +#include "nsDirectoryServiceDefs.h" +#include "nsComponentManagerUtils.h" + +// Avoid warnings about redefinition of WARN_UNUSED_RESULT +#include "ipc/IPCMessageUtils.h" +#include "VideoUtils.h" +#include "MediaEngineSource.h" +#include "VideoSegment.h" +#include "AudioSegment.h" +#include "MediaTrackGraph.h" + +#include "MediaEngineWrapper.h" +#include "mozilla/dom/MediaStreamTrackBinding.h" + +// Camera Access via IPC +#include "CamerasChild.h" + +#include "NullTransport.h" + +// WebRTC includes +#include "common_video/include/video_frame_buffer_pool.h" +#include "modules/video_capture/video_capture_defines.h" + +namespace webrtc { +using CaptureCapability = VideoCaptureCapability; +} + +namespace mozilla { + +// Fitness distance is defined in +// https://w3c.github.io/mediacapture-main/getusermedia.html#dfn-selectsettings + +// The main difference of feasibility and fitness distance is that if the +// constraint is required ('max', or 'exact'), and the settings dictionary's +// value for the constraint does not satisfy the constraint, the fitness +// distance is positive infinity. Given a continuous space of settings +// dictionaries comprising all discrete combinations of dimension and frame-rate +// related properties, the feasibility distance is still in keeping with the +// constraints algorithm. +enum DistanceCalculation { kFitness, kFeasibility }; + +/** + * The WebRTC implementation of the MediaEngine interface. + */ +class MediaEngineRemoteVideoSource : public MediaEngineSource, + public camera::FrameRelay { + ~MediaEngineRemoteVideoSource(); + + struct CapabilityCandidate { + explicit CapabilityCandidate(webrtc::CaptureCapability aCapability, + uint32_t aDistance = 0) + : mCapability(aCapability), mDistance(aDistance) {} + + const webrtc::CaptureCapability mCapability; + uint32_t mDistance; + }; + + class CapabilityComparator { + public: + bool Equals(const CapabilityCandidate& aCandidate, + const webrtc::CaptureCapability& aCapability) const { + return aCandidate.mCapability == aCapability; + } + }; + + bool ChooseCapability(const NormalizedConstraints& aConstraints, + const MediaEnginePrefs& aPrefs, + webrtc::CaptureCapability& aCapability, + const DistanceCalculation aCalculate); + + uint32_t GetDistance(const webrtc::CaptureCapability& aCandidate, + const NormalizedConstraintSet& aConstraints, + const DistanceCalculation aCalculate) const; + + uint32_t GetFitnessDistance( + const webrtc::CaptureCapability& aCandidate, + const NormalizedConstraintSet& aConstraints) const; + + uint32_t GetFeasibilityDistance( + const webrtc::CaptureCapability& aCandidate, + const NormalizedConstraintSet& aConstraints) const; + + static void TrimLessFitCandidates(nsTArray& aSet); + + public: + explicit MediaEngineRemoteVideoSource(const MediaDevice* aMediaDevice); + + // ExternalRenderer + int DeliverFrame(uint8_t* aBuffer, + const camera::VideoFrameProperties& aProps) override; + + // MediaEngineSource + nsresult Allocate(const dom::MediaTrackConstraints& aConstraints, + const MediaEnginePrefs& aPrefs, uint64_t aWindowID, + const char** aOutBadConstraint) override; + nsresult Deallocate() override; + void SetTrack(const RefPtr& aTrack, + const PrincipalHandle& aPrincipal) override; + nsresult Start() override; + nsresult Reconfigure(const dom::MediaTrackConstraints& aConstraints, + const MediaEnginePrefs& aPrefs, + const char** aOutBadConstraint) override; + nsresult FocusOnSelectedSource() override; + nsresult Stop() override; + + uint32_t GetBestFitnessDistance( + const nsTArray& aConstraintSets) + const override; + void GetSettings(dom::MediaTrackSettings& aOutSettings) const override; + + RefPtr GetFirstFramePromise() const override { + return mFirstFramePromise; + } + + const TrackingId& GetTrackingId() const override; + + static camera::CaptureEngine CaptureEngine(dom::MediaSourceEnum aMediaSource); + + private: + /** + * Returns the number of capabilities for the underlying device. + * + * Guaranteed to return at least one capability. + */ + size_t NumCapabilities() const; + + /** + * Returns the capability with index `aIndex` for our assigned device. + * + * It is an error to call this with `aIndex >= NumCapabilities()`. + * + * The lifetime of the returned capability is the same as for this source. + */ + webrtc::CaptureCapability& GetCapability(size_t aIndex) const; + + int mCaptureId = -1; + const camera::CaptureEngine mCapEngine; // source of media (cam, screen etc) + + // A tracking id used to uniquely identify the source of video frames. + // Set under mMutex on the owning thread. Accessed under one of the two. + TrackingId mTrackingId; + + // Mirror of mTrackingId on the frame-delivering thread (Cameras IPC). + Maybe mFrameDeliveringTrackingId; + + // mMutex protects certain members on 3 threads: + // MediaManager, Cameras IPC and MediaTrackGraph. + Mutex mMutex MOZ_UNANNOTATED; + + // Current state of this source. + // Set under mMutex on the owning thread. Accessed under one of the two. + MediaEngineSourceState mState = kReleased; + + // The source track that we feed video data to. + // Set under mMutex on the owning thread. Accessed under one of the two. + RefPtr mTrack; + + // The PrincipalHandle that gets attached to the frames we feed to mTrack. + // Set under mMutex on the owning thread. Accessed under one of the two. + PrincipalHandle mPrincipal = PRINCIPAL_HANDLE_NONE; + + // Set in Start() and Deallocate() on the owning thread. + // Accessed in DeliverFrame() on the camera IPC thread, guaranteed to happen + // after Start() and before the end of Stop(). + RefPtr mImageContainer; + + // A buffer pool used to manage the temporary buffer used when rescaling + // incoming images. Cameras IPC thread only. + webrtc::VideoFrameBufferPool mRescalingBufferPool; + + // The intrinsic size of the latest captured image, so we can feed black + // images of the same size while stopped. + // Set under mMutex on the Cameras IPC thread. Accessed under one of the two. + gfx::IntSize mImageSize = gfx::IntSize(0, 0); + + struct AtomicBool { + Atomic mValue; + }; + + // True when resolution settings have been updated from a real frame's + // resolution. Threadsafe. + // TODO: This can be removed in bug 1453269. + const RefPtr> mSettingsUpdatedByFrame; + + // The current settings of this source. + // Note that these may be different from the settings of the underlying device + // since we scale frames to avoid fingerprinting. + // Members are main thread only. + const RefPtr> mSettings; + MozPromiseHolder mFirstFramePromiseHolder; + RefPtr mFirstFramePromise; + + // The capability currently chosen by constraints of the user of this source. + // Set under mMutex on the owning thread. Accessed under one of the two. + webrtc::CaptureCapability mCapability; + + /** + * Capabilities that we choose between when applying constraints. + * + * This allows for memoization of capabilities as they're requested from the + * parent process. + * + * This is mutable so that the const methods NumCapabilities() and + * GetCapability() can reset it. Owning thread only. + */ + mutable nsTArray> mCapabilities; + + /** + * True if mCapabilities only contains hardcoded capabilities. This can happen + * if the underlying device is not reporting any capabilities. These can be + * affected by constraints, so they're evaluated in ChooseCapability() rather + * than GetCapability(). + * + * This is mutable so that the const methods NumCapabilities() and + * GetCapability() can reset it. Owning thread only. + */ + mutable bool mCapabilitiesAreHardcoded = false; + + const RefPtr mMediaDevice; + const nsCString mDeviceUUID; + Maybe mFacingMode; +}; + +} // namespace mozilla + +#endif /* MEDIAENGINE_REMOTE_VIDEO_SOURCE_H_ */ diff --git a/dom/media/webrtc/MediaEngineSource.cpp b/dom/media/webrtc/MediaEngineSource.cpp new file mode 100644 index 0000000000..d0e5e23ae7 --- /dev/null +++ b/dom/media/webrtc/MediaEngineSource.cpp @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ +/* 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 "MediaEngineSource.h" + +#include "mozilla/dom/MediaTrackSettingsBinding.h" + +namespace mozilla { + +using dom::MediaSourceEnum; +using dom::MediaTrackSettings; + +// These need a definition somewhere because template +// code is allowed to take their address, and they aren't +// guaranteed to have one without this. +const unsigned int MediaEngineSource::kMaxDeviceNameLength; +const unsigned int MediaEngineSource::kMaxUniqueIdLength; + +/* static */ +bool MediaEngineSource::IsVideo(MediaSourceEnum aSource) { + switch (aSource) { + case MediaSourceEnum::Camera: + case MediaSourceEnum::Screen: + case MediaSourceEnum::Window: + case MediaSourceEnum::Browser: + return true; + case MediaSourceEnum::Microphone: + case MediaSourceEnum::AudioCapture: + case MediaSourceEnum::Other: + return false; + default: + MOZ_ASSERT_UNREACHABLE("Unknown type"); + return false; + } +} + +/* static */ +bool MediaEngineSource::IsAudio(MediaSourceEnum aSource) { + switch (aSource) { + case MediaSourceEnum::Microphone: + case MediaSourceEnum::AudioCapture: + return true; + case MediaSourceEnum::Camera: + case MediaSourceEnum::Screen: + case MediaSourceEnum::Window: + case MediaSourceEnum::Browser: + case MediaSourceEnum::Other: + return false; + default: + MOZ_ASSERT_UNREACHABLE("Unknown type"); + return false; + } +} + +bool MediaEngineSource::IsFake() const { return false; } + +nsresult MediaEngineSource::FocusOnSelectedSource() { + return NS_ERROR_NOT_AVAILABLE; +} + +nsresult MediaEngineSource::TakePhoto(MediaEnginePhotoCallback* aCallback) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +MediaEngineSource::~MediaEngineSource() = default; + +} // namespace mozilla diff --git a/dom/media/webrtc/MediaEngineSource.h b/dom/media/webrtc/MediaEngineSource.h new file mode 100644 index 0000000000..d32796b094 --- /dev/null +++ b/dom/media/webrtc/MediaEngineSource.h @@ -0,0 +1,255 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef MediaEngineSource_h +#define MediaEngineSource_h + +#include "MediaSegment.h" +#include "MediaTrackConstraints.h" +#include "mozilla/dom/MediaStreamTrackBinding.h" +#include "mozilla/media/MediaUtils.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ThreadSafeWeakPtr.h" +#include "nsStringFwd.h" +#include "PerformanceRecorder.h" + +namespace mozilla { + +namespace dom { +class Blob; +struct MediaTrackSettings; +} // namespace dom + +namespace ipc { +class PrincipalInfo; +} // namespace ipc + +class MediaEnginePhotoCallback; +class MediaEnginePrefs; +class MediaTrack; + +/** + * Callback interface for TakePhoto(). Either PhotoComplete() or PhotoError() + * should be called. + */ +class MediaEnginePhotoCallback { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaEnginePhotoCallback) + + // aBlob is the image captured by MediaEngineSource. It is + // called on main thread. + virtual nsresult PhotoComplete(already_AddRefed aBlob) = 0; + + // It is called on main thread. aRv is the error code. + virtual nsresult PhotoError(nsresult aRv) = 0; + + protected: + virtual ~MediaEnginePhotoCallback() = default; +}; + +/** + * Lifecycle state of MediaEngineSource. + */ +enum MediaEngineSourceState { + kAllocated, // Allocated, not yet started. + kStarted, // Previously allocated or stopped, then started. + kStopped, // Previously started, then stopped. + kReleased // Not allocated. +}; + +/** + * The pure interface of a MediaEngineSource. + * + * Most sources are helped by the defaults implemented in MediaEngineSource. + */ +class MediaEngineSourceInterface { + public: + /** + * Return true if this is a fake source. I.e., if it is generating media + * itself rather than being an interface to underlying hardware. + */ + virtual bool IsFake() const = 0; + + /** + * Override w/a promise if source has frames, in order to potentially allow + * deferring success of source acquisition until first frame has arrived. + */ + virtual RefPtr GetFirstFramePromise() const { + return nullptr; + } + + /** + * Get an id uniquely identifying the source of video frames that this + * MediaEngineSource represents. This can be used in profiler markers to + * separate markers from different sources into different lanes. + */ + virtual const TrackingId& GetTrackingId() const = 0; + + /** + * Called by MediaEngine to allocate an instance of this source. + */ + virtual nsresult Allocate(const dom::MediaTrackConstraints& aConstraints, + const MediaEnginePrefs& aPrefs, uint64_t aWindowID, + const char** aOutBadConstraint) = 0; + + /** + * Called by MediaEngine when a MediaTrack has been provided for the source to + * feed data to. + * + * This must be called before Start. + */ + virtual void SetTrack(const RefPtr& aTrack, + const PrincipalHandle& aPrincipal) = 0; + + /** + * Called by MediaEngine to start feeding data to the track. + * + * NB: Audio sources handle the enabling of pulling themselves. + */ + virtual nsresult Start() = 0; + + /** + * This brings focus to the selected source, e.g. to bring a captured window + * to the front. + * + * We return one of the following: + * NS_OK - Success. + * NS_ERROR_NOT_AVAILABLE - For backends where focusing does not make sense. + * NS_ERROR_NOT_IMPLEMENTED - For backends where focusing makes sense, but + * is not yet implemented. + * NS_ERROR_FAILURE - Failures reported from underlying code. + */ + virtual nsresult FocusOnSelectedSource() = 0; + + /** + * Applies new constraints to the capability selection for the underlying + * device. + * + * Should the constraints lead to choosing a new capability while the device + * is actively being captured, the device will restart using the new + * capability. + * + * We return one of the following: + * NS_OK - Successful reconfigure. + * NS_ERROR_INVALID_ARG - Couldn't find a capability fitting aConstraints. + * See aBadConstraint for details. + * NS_ERROR_UNEXPECTED - Reconfiguring the underlying device failed + * unexpectedly. This leaves the device in a stopped + * state. + */ + virtual nsresult Reconfigure(const dom::MediaTrackConstraints& aConstraints, + const MediaEnginePrefs& aPrefs, + const char** aOutBadConstraint) = 0; + + /** + * Called by MediaEngine to stop feeding data to the track. + * + * Double-stopping is allowed and will return NS_OK. This is necessary + * sometimes during shutdown. + * + * NB: Audio sources handle the disabling of pulling themselves. + */ + virtual nsresult Stop() = 0; + + /** + * Called by MediaEngine to deallocate an underlying device. + */ + virtual nsresult Deallocate() = 0; + + /** + * If implementation of MediaEngineSource supports TakePhoto(), the picture + * should be returned via aCallback object. Otherwise, it returns + * NS_ERROR_NOT_IMPLEMENTED. + */ + virtual nsresult TakePhoto(MediaEnginePhotoCallback* aCallback) = 0; + + /** + * GetBestFitnessDistance returns the best distance the capture device can + * offer as a whole, given an accumulated number of ConstraintSets. Ideal + * values are considered in the first ConstraintSet only. Plain values are + * treated as Ideal in the first ConstraintSet. Plain values are treated as + * Exact in subsequent ConstraintSets. Infinity = UINT32_MAX e.g. device + * cannot satisfy accumulated ConstraintSets. A finite result may be used to + * calculate this device's ranking as a choice. + */ + virtual uint32_t GetBestFitnessDistance( + const nsTArray& aConstraintSets) + const = 0; + + /** + * Returns the current settings of the underlying device. + * + * Note that this might not be the settings of the underlying hardware. + * In case of a camera where we intervene and scale frames to avoid + * leaking information from other documents than the current one, + * GetSettings() will return the scaled resolution. I.e., the + * device settings as seen by js. + */ + virtual void GetSettings(dom::MediaTrackSettings& aOutSettings) const = 0; +}; + +/** + * Abstract base class for MediaEngineSources. + * + * Implements defaults for some common MediaEngineSourceInterface methods below. + * Also implements RefPtr support and an owning-thread model for thread safety + * checks in subclasses. + */ +class MediaEngineSource : public MediaEngineSourceInterface { + public: + // code inside webrtc.org assumes these sizes; don't use anything smaller + // without verifying it's ok + static const unsigned int kMaxDeviceNameLength = 128; + static const unsigned int kMaxUniqueIdLength = 256; + + /** + * Returns true if the given source type is for video, false otherwise. + * Only call with real types. + */ + static bool IsVideo(dom::MediaSourceEnum aSource); + + /** + * Returns true if the given source type is for audio, false otherwise. + * Only call with real types. + */ + static bool IsAudio(dom::MediaSourceEnum aSource); + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaEngineSource) + NS_DECL_OWNINGEVENTTARGET + + void AssertIsOnOwningThread() const { + NS_ASSERT_OWNINGTHREAD(MediaEngineSource); + } + + const TrackingId& GetTrackingId() const override { + static auto notImplementedId = TrackingId(); + return notImplementedId; + } + + // Not fake by default. + bool IsFake() const override; + + // Returns NS_ERROR_NOT_AVAILABLE by default. + nsresult FocusOnSelectedSource() override; + + // TakePhoto returns NS_ERROR_NOT_IMPLEMENTED by default, + // to tell the caller to fallback to other methods. + nsresult TakePhoto(MediaEnginePhotoCallback* aCallback) override; + + // Returns a default distance of 0 for devices that don't have capabilities. + uint32_t GetBestFitnessDistance( + const nsTArray& aConstraintSets) + const override { + return 0; + } + + protected: + virtual ~MediaEngineSource(); +}; + +} // namespace mozilla + +#endif /* MediaEngineSource_h */ diff --git a/dom/media/webrtc/MediaEngineWebRTC.cpp b/dom/media/webrtc/MediaEngineWebRTC.cpp new file mode 100644 index 0000000000..cf638497a9 --- /dev/null +++ b/dom/media/webrtc/MediaEngineWebRTC.cpp @@ -0,0 +1,299 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* 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 "MediaEngineWebRTC.h" + +#include "CamerasChild.h" +#include "MediaEngineRemoteVideoSource.h" +#include "MediaEngineWebRTCAudio.h" +#include "MediaManager.h" +#include "mozilla/Logging.h" + +// Pipewire detection support +#if defined(WEBRTC_USE_PIPEWIRE) +# include "mozilla/StaticPrefs_media.h" +# include "modules/desktop_capture/desktop_capturer.h" +#endif + +#define FAKE_ONDEVICECHANGE_EVENT_PERIOD_IN_MS 500 + +static mozilla::LazyLogModule sGetUserMediaLog("GetUserMedia"); +#undef LOG +#define LOG(args) MOZ_LOG(sGetUserMediaLog, mozilla::LogLevel::Debug, args) + +namespace mozilla { + +using AudioDeviceSet = CubebDeviceEnumerator::AudioDeviceSet; +using camera::CamerasChild; +using camera::GetChildAndCall; +using dom::MediaSourceEnum; + +CubebDeviceEnumerator* GetEnumerator() { + return CubebDeviceEnumerator::GetInstance(); +} + +MediaEngineWebRTC::MediaEngineWebRTC() { + AssertIsOnOwningThread(); + + GetChildAndCall( + &CamerasChild::ConnectDeviceListChangeListener, + &mCameraListChangeListener, AbstractThread::MainThread(), this, + &MediaEngineWebRTC::DeviceListChanged); + mMicrophoneListChangeListener = + GetEnumerator()->OnAudioInputDeviceListChange().Connect( + AbstractThread::MainThread(), this, + &MediaEngineWebRTC::DeviceListChanged); + mSpeakerListChangeListener = + GetEnumerator()->OnAudioOutputDeviceListChange().Connect( + AbstractThread::MainThread(), this, + &MediaEngineWebRTC::DeviceListChanged); +} + +void MediaEngineWebRTC::EnumerateVideoDevices( + MediaSourceEnum aMediaSource, nsTArray>* aDevices) { + AssertIsOnOwningThread(); + // flag sources with cross-origin exploit potential + bool scaryKind = (aMediaSource == MediaSourceEnum::Screen || + aMediaSource == MediaSourceEnum::Browser); +#if defined(WEBRTC_USE_PIPEWIRE) + bool canRequestOsLevelPrompt = + mozilla::StaticPrefs::media_webrtc_capture_allow_pipewire() && + webrtc::DesktopCapturer::IsRunningUnderWayland() && + (aMediaSource == MediaSourceEnum::Application || + aMediaSource == MediaSourceEnum::Screen || + aMediaSource == MediaSourceEnum::Window); +#else + bool canRequestOsLevelPrompt = false; +#endif + /* + * We still enumerate every time, in case a new device was plugged in since + * the last call. TODO: Verify that WebRTC actually does deal with hotplugging + * new devices (with or without new engine creation) and accordingly adjust. + * Enumeration is not neccessary if GIPS reports the same set of devices + * for a given instance of the engine. + */ + int num; +#if defined(_ARM64_) && defined(XP_WIN) + // There are problems with using DirectShow on versions of Windows before + // 19H1 on arm64. This disables the camera on older versions of Windows. + if (aMediaSource == MediaSourceEnum::Camera) { + typedef ULONG (*RtlGetVersionFn)(LPOSVERSIONINFOEXW); + RtlGetVersionFn RtlGetVersion; + RtlGetVersion = (RtlGetVersionFn)GetProcAddress(GetModuleHandleA("ntdll"), + "RtlGetVersion"); + if (RtlGetVersion) { + OSVERSIONINFOEXW info; + info.dwOSVersionInfoSize = sizeof(info); + RtlGetVersion(&info); + // 19H1 is 18346 + if (info.dwBuildNumber < 18346) { + return; + } + } + } +#endif + camera::CaptureEngine capEngine = + MediaEngineRemoteVideoSource::CaptureEngine(aMediaSource); + num = GetChildAndCall(&CamerasChild::NumberOfCaptureDevices, capEngine); + + for (int i = 0; i < num; i++) { + char deviceName[MediaEngineSource::kMaxDeviceNameLength]; + char uniqueId[MediaEngineSource::kMaxUniqueIdLength]; + bool scarySource = false; + + // paranoia + deviceName[0] = '\0'; + uniqueId[0] = '\0'; + int error; + + error = GetChildAndCall(&CamerasChild::GetCaptureDevice, capEngine, i, + deviceName, sizeof(deviceName), uniqueId, + sizeof(uniqueId), &scarySource); + if (error) { + LOG(("camera:GetCaptureDevice: Failed %d", error)); + continue; + } +#ifdef DEBUG + LOG((" Capture Device Index %d, Name %s", i, deviceName)); + + webrtc::CaptureCapability cap; + int numCaps = GetChildAndCall(&CamerasChild::NumberOfCapabilities, + capEngine, uniqueId); + LOG(("Number of Capabilities %d", numCaps)); + for (int j = 0; j < numCaps; j++) { + if (GetChildAndCall(&CamerasChild::GetCaptureCapability, capEngine, + uniqueId, j, &cap) != 0) { + break; + } + LOG(("type=%d width=%d height=%d maxFPS=%d", + static_cast(cap.videoType), cap.width, cap.height, cap.maxFPS)); + } +#endif + + NS_ConvertUTF8toUTF16 name(deviceName); + NS_ConvertUTF8toUTF16 uuid(uniqueId); + // The remote video backend doesn't implement group id. We return the + // device name and higher layers will correlate this with the name of + // audio devices. + aDevices->EmplaceBack(new MediaDevice( + this, aMediaSource, name, uuid, uuid, + MediaDevice::IsScary(scaryKind || scarySource), + canRequestOsLevelPrompt ? MediaDevice::OsPromptable::Yes + : MediaDevice::OsPromptable::No)); + } +} + +void MediaEngineWebRTC::EnumerateMicrophoneDevices( + nsTArray>* aDevices) { + AssertIsOnOwningThread(); + + RefPtr devices = + GetEnumerator()->EnumerateAudioInputDevices(); + + DebugOnly foundPreferredDevice = false; + + for (const auto& deviceInfo : *devices) { +#ifndef ANDROID + MOZ_ASSERT(deviceInfo->DeviceID()); +#endif + LOG(("Cubeb device: type 0x%x, state 0x%x, name %s, id %p", + deviceInfo->Type(), deviceInfo->State(), + NS_ConvertUTF16toUTF8(deviceInfo->Name()).get(), + deviceInfo->DeviceID())); + + if (deviceInfo->State() == CUBEB_DEVICE_STATE_ENABLED) { + MOZ_ASSERT(deviceInfo->Type() == CUBEB_DEVICE_TYPE_INPUT); + // Lie and provide the name as UUID + RefPtr device = new MediaDevice(this, deviceInfo, deviceInfo->Name()); + if (deviceInfo->Preferred()) { +#ifdef DEBUG + if (!foundPreferredDevice) { + foundPreferredDevice = true; + } else { + // This is possible on windows, there is a default communication + // device, and a default device: + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1542739 +# ifndef XP_WIN + MOZ_ASSERT(!foundPreferredDevice, + "Found more than one preferred audio input device" + "while enumerating"); +# endif + } +#endif + aDevices->InsertElementAt(0, std::move(device)); + } else { + aDevices->AppendElement(std::move(device)); + } + } + } +} + +void MediaEngineWebRTC::EnumerateSpeakerDevices( + nsTArray>* aDevices) { + AssertIsOnOwningThread(); + + RefPtr devices = + GetEnumerator()->EnumerateAudioOutputDevices(); + +#ifndef XP_WIN + DebugOnly preferredDeviceFound = false; +#endif + for (const auto& deviceInfo : *devices) { + LOG(("Cubeb device: type 0x%x, state 0x%x, name %s, id %p", + deviceInfo->Type(), deviceInfo->State(), + NS_ConvertUTF16toUTF8(deviceInfo->Name()).get(), + deviceInfo->DeviceID())); + if (deviceInfo->State() == CUBEB_DEVICE_STATE_ENABLED) { + MOZ_ASSERT(deviceInfo->Type() == CUBEB_DEVICE_TYPE_OUTPUT); + nsString uuid(deviceInfo->Name()); + // If, for example, input and output are in the same device, uuid + // would be the same for both which ends up to create the same + // deviceIDs (in JS). + uuid.Append(u"_Speaker"_ns); + RefPtr device = new MediaDevice(this, deviceInfo, uuid); + if (deviceInfo->Preferred()) { + // In windows is possible to have more than one preferred device +#if defined(DEBUG) && !defined(XP_WIN) + MOZ_ASSERT(!preferredDeviceFound, "More than one preferred device"); + preferredDeviceFound = true; +#endif + aDevices->InsertElementAt(0, std::move(device)); + } else { + aDevices->AppendElement(std::move(device)); + } + } + } +} + +void MediaEngineWebRTC::EnumerateDevices( + MediaSourceEnum aMediaSource, MediaSinkEnum aMediaSink, + nsTArray>* aDevices) { + AssertIsOnOwningThread(); + MOZ_ASSERT(aMediaSource != MediaSourceEnum::Other || + aMediaSink != MediaSinkEnum::Other); + if (MediaEngineSource::IsVideo(aMediaSource)) { + switch (aMediaSource) { + case MediaSourceEnum::Window: + // Since the mediaSource constraint is deprecated, treat the Window + // value as a request for getDisplayMedia-equivalent sharing: Combine + // window and fullscreen into a single list of choices. The other values + // are still useful for testing. + EnumerateVideoDevices(MediaSourceEnum::Window, aDevices); + EnumerateVideoDevices(MediaSourceEnum::Browser, aDevices); + EnumerateVideoDevices(MediaSourceEnum::Screen, aDevices); + break; + case MediaSourceEnum::Screen: + case MediaSourceEnum::Browser: + case MediaSourceEnum::Camera: + EnumerateVideoDevices(aMediaSource, aDevices); + break; + default: + MOZ_CRASH("No valid video source"); + break; + } + } else if (aMediaSource == MediaSourceEnum::AudioCapture) { + aDevices->EmplaceBack(new MediaDevice( + this, aMediaSource, u"AudioCapture"_ns, + MediaEngineWebRTCAudioCaptureSource::GetUUID(), + MediaEngineWebRTCAudioCaptureSource::GetGroupId(), + MediaDevice::IsScary::No, MediaDevice::OsPromptable::No)); + } else if (aMediaSource == MediaSourceEnum::Microphone) { + EnumerateMicrophoneDevices(aDevices); + } + + if (aMediaSink == MediaSinkEnum::Speaker) { + EnumerateSpeakerDevices(aDevices); + } +} + +RefPtr MediaEngineWebRTC::CreateSource( + const MediaDevice* aMediaDevice) { + MOZ_ASSERT(aMediaDevice->mEngine == this); + if (MediaEngineSource::IsVideo(aMediaDevice->mMediaSource)) { + return new MediaEngineRemoteVideoSource(aMediaDevice); + } + switch (aMediaDevice->mMediaSource) { + case MediaSourceEnum::AudioCapture: + return new MediaEngineWebRTCAudioCaptureSource(aMediaDevice); + case MediaSourceEnum::Microphone: + return new MediaEngineWebRTCMicrophoneSource(aMediaDevice); + default: + MOZ_CRASH("Unsupported source type"); + return nullptr; + } +} + +void MediaEngineWebRTC::Shutdown() { + AssertIsOnOwningThread(); + mCameraListChangeListener.DisconnectIfExists(); + mMicrophoneListChangeListener.DisconnectIfExists(); + mSpeakerListChangeListener.DisconnectIfExists(); + + LOG(("%s", __FUNCTION__)); + mozilla::camera::Shutdown(); +} + +} // namespace mozilla diff --git a/dom/media/webrtc/MediaEngineWebRTC.h b/dom/media/webrtc/MediaEngineWebRTC.h new file mode 100644 index 0000000000..918558dfab --- /dev/null +++ b/dom/media/webrtc/MediaEngineWebRTC.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef MEDIAENGINEWEBRTC_H_ +#define MEDIAENGINEWEBRTC_H_ + +#include "MediaEngine.h" +#include "MediaEventSource.h" +#include "MediaEngineSource.h" +#include "nsTArray.h" +#include "CubebDeviceEnumerator.h" +#include "mozilla/RefPtr.h" +#include "mozilla/dom/MediaStreamTrackBinding.h" + +namespace mozilla { + +class MediaEngineWebRTC : public MediaEngine { + public: + MediaEngineWebRTC(); + + // Clients should ensure to clean-up sources video/audio sources + // before invoking Shutdown on this class. + void Shutdown() override; + + void EnumerateDevices(dom::MediaSourceEnum, MediaSinkEnum, + nsTArray>*) override; + RefPtr CreateSource(const MediaDevice* aDevice) override; + + MediaEventSource& DeviceListChangeEvent() override { + return mDeviceListChangeEvent; + } + bool IsFake() const override { return false; } + + private: + ~MediaEngineWebRTC() = default; + void EnumerateVideoDevices(dom::MediaSourceEnum, + nsTArray>*); + void EnumerateMicrophoneDevices(nsTArray>*); + void EnumerateSpeakerDevices(nsTArray>*); + + void DeviceListChanged() { mDeviceListChangeEvent.Notify(); } + + MediaEventListener mCameraListChangeListener; + MediaEventListener mMicrophoneListChangeListener; + MediaEventListener mSpeakerListChangeListener; + MediaEventProducer mDeviceListChangeEvent; +}; + +} // namespace mozilla + +#endif /* NSMEDIAENGINEWEBRTC_H_ */ diff --git a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp new file mode 100644 index 0000000000..64ed88c625 --- /dev/null +++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp @@ -0,0 +1,1329 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ +/* 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 "MediaEngineWebRTCAudio.h" + +#include +#include + +#include "AudioConverter.h" +#include "MediaManager.h" +#include "MediaTrackGraphImpl.h" +#include "MediaTrackConstraints.h" +#include "mozilla/Assertions.h" +#include "mozilla/ErrorNames.h" +#include "nsIDUtils.h" +#include "transport/runnable_utils.h" +#include "Tracing.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Logging.h" + +#include "common_audio/include/audio_util.h" +#include "modules/audio_processing/include/audio_processing.h" + +using namespace webrtc; + +// These are restrictions from the webrtc.org code +#define MAX_CHANNELS 2 +#define MONO 1 +#define MAX_SAMPLING_FREQ 48000 // Hz - multiple of 100 + +namespace mozilla { + +using dom::MediaSourceEnum; + +extern LazyLogModule gMediaManagerLog; +#define LOG(...) MOZ_LOG(gMediaManagerLog, LogLevel::Debug, (__VA_ARGS__)) +#define LOG_FRAME(...) \ + MOZ_LOG(gMediaManagerLog, LogLevel::Verbose, (__VA_ARGS__)) +#define LOG_ERROR(...) MOZ_LOG(gMediaManagerLog, LogLevel::Error, (__VA_ARGS__)) + +/** + * WebRTC Microphone MediaEngineSource. + */ + +MediaEngineWebRTCMicrophoneSource::MediaEngineWebRTCMicrophoneSource( + const MediaDevice* aMediaDevice) + : mPrincipal(PRINCIPAL_HANDLE_NONE), + mDeviceInfo(aMediaDevice->mAudioDeviceInfo), + mDeviceMaxChannelCount(mDeviceInfo->MaxChannels()), + mSettings(new nsMainThreadPtrHolder< + media::Refcountable>( + "MediaEngineWebRTCMicrophoneSource::mSettings", + new media::Refcountable(), + // Non-strict means it won't assert main thread for us. + // It would be great if it did but we're already on the media thread. + /* aStrict = */ false)) { + MOZ_ASSERT(aMediaDevice->mMediaSource == MediaSourceEnum::Microphone); +#ifndef ANDROID + MOZ_ASSERT(mDeviceInfo->DeviceID()); +#endif + + // We'll init lazily as needed + mSettings->mEchoCancellation.Construct(0); + mSettings->mAutoGainControl.Construct(0); + mSettings->mNoiseSuppression.Construct(0); + mSettings->mChannelCount.Construct(0); + + mState = kReleased; +} + +nsresult MediaEngineWebRTCMicrophoneSource::EvaluateSettings( + const NormalizedConstraints& aConstraintsUpdate, + const MediaEnginePrefs& aInPrefs, MediaEnginePrefs* aOutPrefs, + const char** aOutBadConstraint) { + AssertIsOnOwningThread(); + + FlattenedConstraints c(aConstraintsUpdate); + MediaEnginePrefs prefs = aInPrefs; + + prefs.mAecOn = c.mEchoCancellation.Get(aInPrefs.mAecOn); + prefs.mAgcOn = c.mAutoGainControl.Get(aInPrefs.mAgcOn && prefs.mAecOn); + prefs.mNoiseOn = c.mNoiseSuppression.Get(aInPrefs.mNoiseOn && prefs.mAecOn); + + // Determine an actual channel count to use for this source. Three factors at + // play here: the device capabilities, the constraints passed in by content, + // and a pref that can force things (for testing) + int32_t maxChannels = static_cast(mDeviceInfo->MaxChannels()); + + // First, check channelCount violation wrt constraints. This fails in case of + // error. + if (c.mChannelCount.mMin > maxChannels) { + *aOutBadConstraint = "channelCount"; + return NS_ERROR_FAILURE; + } + // A pref can force the channel count to use. If the pref has a value of zero + // or lower, it has no effect. + if (aInPrefs.mChannels <= 0) { + prefs.mChannels = maxChannels; + } + + // Get the number of channels asked for by content, and clamp it between the + // pref and the maximum number of channels that the device supports. + prefs.mChannels = c.mChannelCount.Get(std::min(prefs.mChannels, maxChannels)); + prefs.mChannels = std::max(1, std::min(prefs.mChannels, maxChannels)); + + LOG("Audio config: agc: %d, noise: %d, channels: %d", + prefs.mAgcOn ? prefs.mAgc : -1, prefs.mNoiseOn ? prefs.mNoise : -1, + prefs.mChannels); + + *aOutPrefs = prefs; + + return NS_OK; +} + +nsresult MediaEngineWebRTCMicrophoneSource::Reconfigure( + const dom::MediaTrackConstraints& aConstraints, + const MediaEnginePrefs& aPrefs, const char** aOutBadConstraint) { + AssertIsOnOwningThread(); + MOZ_ASSERT(mTrack); + + LOG("Mic source %p Reconfigure ", this); + + NormalizedConstraints constraints(aConstraints); + MediaEnginePrefs outputPrefs; + nsresult rv = + EvaluateSettings(constraints, aPrefs, &outputPrefs, aOutBadConstraint); + if (NS_FAILED(rv)) { + if (aOutBadConstraint) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoCString name; + GetErrorName(rv, name); + LOG("Mic source %p Reconfigure() failed unexpectedly. rv=%s", this, + name.Data()); + Stop(); + return NS_ERROR_UNEXPECTED; + } + + ApplySettings(outputPrefs); + + mCurrentPrefs = outputPrefs; + + return NS_OK; +} + +void MediaEngineWebRTCMicrophoneSource::ApplySettings( + const MediaEnginePrefs& aPrefs) { + AssertIsOnOwningThread(); + + TRACE("ApplySettings"); + MOZ_ASSERT( + mTrack, + "ApplySetting is to be called only after SetTrack has been called"); + + mAudioProcessingConfig.pipeline.multi_channel_render = true; + mAudioProcessingConfig.pipeline.multi_channel_capture = true; + + mAudioProcessingConfig.echo_canceller.enabled = aPrefs.mAecOn; + mAudioProcessingConfig.echo_canceller.mobile_mode = aPrefs.mUseAecMobile; + + if ((mAudioProcessingConfig.gain_controller1.enabled = + aPrefs.mAgcOn && !aPrefs.mAgc2Forced)) { + auto mode = static_cast( + aPrefs.mAgc); + if (mode != AudioProcessing::Config::GainController1::kAdaptiveAnalog && + mode != AudioProcessing::Config::GainController1::kAdaptiveDigital && + mode != AudioProcessing::Config::GainController1::kFixedDigital) { + LOG_ERROR("AudioInputProcessing %p Attempt to set invalid AGC mode %d", + mInputProcessing.get(), static_cast(mode)); + mode = AudioProcessing::Config::GainController1::kAdaptiveDigital; + } +#if defined(WEBRTC_IOS) || defined(ATA) || defined(WEBRTC_ANDROID) + if (mode == AudioProcessing::Config::GainController1::kAdaptiveAnalog) { + LOG_ERROR( + "AudioInputProcessing %p Invalid AGC mode kAdaptiveAnalog on " + "mobile", + mInputProcessing.get()); + MOZ_ASSERT_UNREACHABLE( + "Bad pref set in all.js or in about:config" + " for the auto gain, on mobile."); + mode = AudioProcessing::Config::GainController1::kFixedDigital; + } +#endif + mAudioProcessingConfig.gain_controller1.mode = mode; + } + mAudioProcessingConfig.gain_controller2.enabled = + mAudioProcessingConfig.gain_controller2.adaptive_digital.enabled = + aPrefs.mAgcOn && aPrefs.mAgc2Forced; + + if ((mAudioProcessingConfig.noise_suppression.enabled = aPrefs.mNoiseOn)) { + auto level = static_cast( + aPrefs.mNoise); + if (level != AudioProcessing::Config::NoiseSuppression::kLow && + level != AudioProcessing::Config::NoiseSuppression::kModerate && + level != AudioProcessing::Config::NoiseSuppression::kHigh && + level != AudioProcessing::Config::NoiseSuppression::kVeryHigh) { + LOG_ERROR( + "AudioInputProcessing %p Attempt to set invalid noise suppression " + "level %d", + mInputProcessing.get(), static_cast(level)); + + level = AudioProcessing::Config::NoiseSuppression::kModerate; + } + mAudioProcessingConfig.noise_suppression.level = level; + } + + mAudioProcessingConfig.transient_suppression.enabled = aPrefs.mTransientOn; + + mAudioProcessingConfig.high_pass_filter.enabled = aPrefs.mHPFOn; + + // See https://bugs.chromium.org/p/webrtc/issues/detail?id=11539 for more + // info. Our pref defaults to false, and if this is truly as unhelpful + // as the upstream bug claim, we could delete the pref that drive this: + // media.getusermedia.residual_echo_enabled. See Bug 1779498. + // mAudioProcessingConfig.residual_echo_detector.enabled = + // aPrefs.mResidualEchoOn; + + RefPtr that = this; + CubebUtils::AudioDeviceID deviceID = mDeviceInfo->DeviceID(); + NS_DispatchToMainThread(NS_NewRunnableFunction( + __func__, [this, that, deviceID, track = mTrack, prefs = aPrefs, + audioProcessingConfig = mAudioProcessingConfig] { + mSettings->mEchoCancellation.Value() = prefs.mAecOn; + mSettings->mAutoGainControl.Value() = prefs.mAgcOn; + mSettings->mNoiseSuppression.Value() = prefs.mNoiseOn; + mSettings->mChannelCount.Value() = prefs.mChannels; + + class Message : public ControlMessage { + CubebUtils::AudioDeviceID mDeviceID; + const RefPtr mInputProcessing; + const AudioProcessing::Config mAudioProcessingConfig; + const bool mPassThrough; + const uint32_t mRequestedInputChannelCount; + + public: + Message(MediaTrack* aTrack, CubebUtils::AudioDeviceID aDeviceID, + AudioInputProcessing* aInputProcessing, + const AudioProcessing::Config& aAudioProcessingConfig, + bool aPassThrough, uint32_t aRequestedInputChannelCount) + : ControlMessage(aTrack), + mDeviceID(aDeviceID), + mInputProcessing(aInputProcessing), + mAudioProcessingConfig(aAudioProcessingConfig), + mPassThrough(aPassThrough), + mRequestedInputChannelCount(aRequestedInputChannelCount) {} + + void Run() override { + mInputProcessing->ApplyConfig(mTrack->GraphImpl(), + mAudioProcessingConfig); + { + TRACE("SetRequestedInputChannelCount"); + mInputProcessing->SetRequestedInputChannelCount( + mTrack->GraphImpl(), mDeviceID, mRequestedInputChannelCount); + } + { + TRACE("SetPassThrough") + mInputProcessing->SetPassThrough(mTrack->GraphImpl(), + mPassThrough); + } + } + }; + + // The high-pass filter is not taken into account when activating the + // pass through, since it's not controllable from content. + bool passThrough = !(prefs.mAecOn || prefs.mAgcOn || prefs.mNoiseOn); + + if (track->IsDestroyed()) { + return; + } + track->GraphImpl()->AppendMessage(MakeUnique( + track, deviceID, mInputProcessing, audioProcessingConfig, + passThrough, prefs.mChannels)); + })); +} + +nsresult MediaEngineWebRTCMicrophoneSource::Allocate( + const dom::MediaTrackConstraints& aConstraints, + const MediaEnginePrefs& aPrefs, uint64_t aWindowID, + const char** aOutBadConstraint) { + AssertIsOnOwningThread(); + + mState = kAllocated; + + NormalizedConstraints normalized(aConstraints); + MediaEnginePrefs outputPrefs; + nsresult rv = + EvaluateSettings(normalized, aPrefs, &outputPrefs, aOutBadConstraint); + if (NS_FAILED(rv)) { + return rv; + } + + NS_DispatchToMainThread(NS_NewRunnableFunction( + __func__, [settings = mSettings, prefs = outputPrefs] { + settings->mEchoCancellation.Value() = prefs.mAecOn; + settings->mAutoGainControl.Value() = prefs.mAgcOn; + settings->mNoiseSuppression.Value() = prefs.mNoiseOn; + settings->mChannelCount.Value() = prefs.mChannels; + })); + + mCurrentPrefs = outputPrefs; + + return rv; +} + +nsresult MediaEngineWebRTCMicrophoneSource::Deallocate() { + AssertIsOnOwningThread(); + + MOZ_ASSERT(mState == kStopped || mState == kAllocated); + + class EndTrackMessage : public ControlMessage { + const RefPtr mInputProcessing; + + public: + explicit EndTrackMessage(AudioInputProcessing* aAudioInputProcessing) + : ControlMessage(nullptr), mInputProcessing(aAudioInputProcessing) {} + + void Run() override { + TRACE("mInputProcessing::End"); + mInputProcessing->End(); + } + }; + + if (mTrack) { + NS_DispatchToMainThread(NS_NewRunnableFunction( + __func__, + [track = std::move(mTrack), inputProcessing = mInputProcessing] { + if (track->IsDestroyed()) { + // This track has already been destroyed on main thread by its + // DOMMediaStream. No cleanup left to do. + return; + } + track->GraphImpl()->AppendMessage( + MakeUnique(inputProcessing)); + })); + } + + // Reset all state. This is not strictly necessary, this instance will get + // destroyed soon. + mTrack = nullptr; + mPrincipal = PRINCIPAL_HANDLE_NONE; + + // If empty, no callbacks to deliver data should be occuring + MOZ_ASSERT(mState != kReleased, "Source not allocated"); + MOZ_ASSERT(mState != kStarted, "Source not stopped"); + + mState = kReleased; + LOG("Mic source %p Audio device %s deallocated", this, + NS_ConvertUTF16toUTF8(mDeviceInfo->Name()).get()); + return NS_OK; +} + +void MediaEngineWebRTCMicrophoneSource::SetTrack( + const RefPtr& aTrack, const PrincipalHandle& aPrincipal) { + AssertIsOnOwningThread(); + MOZ_ASSERT(aTrack); + MOZ_ASSERT(aTrack->AsAudioProcessingTrack()); + + MOZ_ASSERT(!mTrack); + MOZ_ASSERT(mPrincipal == PRINCIPAL_HANDLE_NONE); + mTrack = aTrack->AsAudioProcessingTrack(); + mPrincipal = aPrincipal; + + mInputProcessing = + MakeAndAddRef(mDeviceMaxChannelCount); + + NS_DispatchToMainThread(NS_NewRunnableFunction( + __func__, [track = mTrack, processing = mInputProcessing]() mutable { + track->SetInputProcessing(std::move(processing)); + track->Resume(); // Suspended by MediaManager + })); + + LOG("Mic source %p Track %p registered for microphone capture", this, + aTrack.get()); +} + +class StartStopMessage : public ControlMessage { + public: + enum StartStop { Start, Stop }; + + StartStopMessage(MediaTrack* aTrack, AudioInputProcessing* aInputProcessing, + StartStop aAction) + : ControlMessage(aTrack), + mInputProcessing(aInputProcessing), + mAction(aAction) {} + + void Run() override { + if (mAction == StartStopMessage::Start) { + TRACE("InputProcessing::Start") + mInputProcessing->Start(mTrack->GraphImpl()); + } else if (mAction == StartStopMessage::Stop) { + TRACE("InputProcessing::Stop") + mInputProcessing->Stop(mTrack->GraphImpl()); + } else { + MOZ_CRASH("Invalid enum value"); + } + } + + protected: + const RefPtr mInputProcessing; + const StartStop mAction; +}; + +nsresult MediaEngineWebRTCMicrophoneSource::Start() { + AssertIsOnOwningThread(); + + // This spans setting both the enabled state and mState. + if (mState == kStarted) { + return NS_OK; + } + + MOZ_ASSERT(mState == kAllocated || mState == kStopped); + + ApplySettings(mCurrentPrefs); + + CubebUtils::AudioDeviceID deviceID = mDeviceInfo->DeviceID(); + NS_DispatchToMainThread(NS_NewRunnableFunction( + __func__, [inputProcessing = mInputProcessing, deviceID, track = mTrack, + principal = mPrincipal] { + if (track->IsDestroyed()) { + return; + } + + track->GraphImpl()->AppendMessage(MakeUnique( + track, inputProcessing, StartStopMessage::Start)); + track->ConnectDeviceInput(deviceID, inputProcessing.get(), principal); + })); + + MOZ_ASSERT(mState != kReleased); + mState = kStarted; + + return NS_OK; +} + +nsresult MediaEngineWebRTCMicrophoneSource::Stop() { + AssertIsOnOwningThread(); + + LOG("Mic source %p Stop()", this); + MOZ_ASSERT(mTrack, "SetTrack must have been called before ::Stop"); + + if (mState == kStopped) { + // Already stopped - this is allowed + return NS_OK; + } + + NS_DispatchToMainThread(NS_NewRunnableFunction( + __func__, [inputProcessing = mInputProcessing, deviceInfo = mDeviceInfo, + track = mTrack] { + if (track->IsDestroyed()) { + return; + } + + MOZ_ASSERT(track->DeviceId().value() == deviceInfo->DeviceID()); + track->DisconnectDeviceInput(); + track->GraphImpl()->AppendMessage(MakeUnique( + track, inputProcessing, StartStopMessage::Stop)); + })); + + MOZ_ASSERT(mState == kStarted, "Should be started when stopping"); + mState = kStopped; + + return NS_OK; +} + +void MediaEngineWebRTCMicrophoneSource::GetSettings( + dom::MediaTrackSettings& aOutSettings) const { + MOZ_ASSERT(NS_IsMainThread()); + aOutSettings = *mSettings; +} + +AudioInputProcessing::AudioInputProcessing(uint32_t aMaxChannelCount) + : mAudioProcessing(AudioProcessingBuilder().Create().release()), + mRequestedInputChannelCount(aMaxChannelCount), + mSkipProcessing(false), + mInputDownmixBuffer(MAX_SAMPLING_FREQ * MAX_CHANNELS / 100), + mEnabled(false), + mEnded(false), + mPacketCount(0) {} + +void AudioInputProcessing::Disconnect(MediaTrackGraphImpl* aGraph) { + // This method is just for asserts. + MOZ_ASSERT(aGraph->OnGraphThread()); +} + +bool AudioInputProcessing::PassThrough(MediaTrackGraphImpl* aGraph) const { + MOZ_ASSERT(aGraph->OnGraphThread()); + return mSkipProcessing; +} + +void AudioInputProcessing::SetPassThrough(MediaTrackGraphImpl* aGraph, + bool aPassThrough) { + MOZ_ASSERT(aGraph->OnGraphThread()); + + if (aPassThrough == mSkipProcessing) { + return; + } + mSkipProcessing = aPassThrough; + + if (!mEnabled) { + MOZ_ASSERT(!mPacketizerInput); + return; + } + + if (aPassThrough) { + // Turn on pass-through + ResetAudioProcessing(aGraph); + } else { + // Turn off pass-through + MOZ_ASSERT(!mPacketizerInput); + EnsureAudioProcessing(aGraph, mRequestedInputChannelCount); + } +} + +uint32_t AudioInputProcessing::GetRequestedInputChannelCount() { + return mRequestedInputChannelCount; +} + +void AudioInputProcessing::SetRequestedInputChannelCount( + MediaTrackGraphImpl* aGraph, CubebUtils::AudioDeviceID aDeviceId, + uint32_t aRequestedInputChannelCount) { + mRequestedInputChannelCount = aRequestedInputChannelCount; + + aGraph->ReevaluateInputDevice(aDeviceId); +} + +void AudioInputProcessing::Start(MediaTrackGraphImpl* aGraph) { + MOZ_ASSERT(aGraph->OnGraphThread()); + + if (mEnabled) { + return; + } + mEnabled = true; + + if (mSkipProcessing) { + return; + } + + MOZ_ASSERT(!mPacketizerInput); + EnsureAudioProcessing(aGraph, mRequestedInputChannelCount); +} + +void AudioInputProcessing::Stop(MediaTrackGraphImpl* aGraph) { + MOZ_ASSERT(aGraph->OnGraphThread()); + + if (!mEnabled) { + return; + } + + mEnabled = false; + + if (mSkipProcessing) { + return; + } + + // Packetizer is active and we were just stopped. Stop the packetizer and + // processing. + ResetAudioProcessing(aGraph); +} + +// The following is how how Process() works in pass-through and non-pass-through +// mode. In both mode, Process() outputs the same amount of the frames as its +// input data. +// +// I. In non-pass-through mode: +// +// We will use webrtc::AudioProcessing to process the input audio data in this +// mode. The data input in webrtc::AudioProcessing needs to be a 10ms chunk, +// while the input data passed to Process() is not necessary to have times of +// 10ms-chunk length. To divide the input data into 10ms chunks, +// mPacketizerInput is introduced. +// +// We will add one 10ms-chunk silence into the internal buffer before Process() +// works. Those extra frames is called pre-buffering. It aims to avoid glitches +// we may have when producing data in mPacketizerInput. Without pre-buffering, +// when the input data length is not 10ms-times, we could end up having no +// enough output needs since mPacketizerInput would keep some input data, which +// is the remainder of the 10ms-chunk length. To force processing those data +// left in mPacketizerInput, we would need to add some extra frames to make +// mPacketizerInput produce a 10ms-chunk. For example, if the sample rate is +// 44100 Hz, then the packet-size is 441 frames. When we only have 384 input +// frames, we would need to put additional 57 frames to mPacketizerInput to +// produce a packet. However, those extra 57 frames result in a glitch sound. +// +// By adding one 10ms-chunk silence in advance to the internal buffer, we won't +// need to add extra frames between the input data no matter what data length it +// is. The only drawback is the input data won't be processed and send to output +// immediately. Process() will consume pre-buffering data for its output first. +// The below describes how it works: +// +// +// Process() +// +-----------------------------+ +// input D(N) | +--------+ +--------+ | output D(N) +// --------------|-->| P(N) |-->| S(N) |---|--------------> +// | +--------+ +--------+ | +// | packetizer mSegment | +// +-----------------------------+ +// <------ internal buffer ------> +// +// +// D(N): number of frames from the input and the output needs in the N round +// Z: number of frames of a 10ms chunk(packet) in mPacketizerInput, Z >= 1 +// (if Z = 1, packetizer has no effect) +// P(N): number of frames left in mPacketizerInput after the N round. Once the +// frames in packetizer >= Z, packetizer will produce a packet to +// mSegment, so P(N) = (P(N-1) + D(N)) % Z, 0 <= P(N) <= Z-1 +// S(N): number of frames left in mSegment after the N round. The input D(N) +// frames will be passed to mPacketizerInput first, and then +// mPacketizerInput may append some packets to mSegment, so +// S(N) = S(N-1) + Z * floor((P(N-1) + D(N)) / Z) - D(N) +// +// At the first, we set P(0) = 0, S(0) = X, where X >= Z-1. X is the +// pre-buffering put in the internal buffer. With this settings, P(K) + S(K) = X +// always holds. +// +// Intuitively, this seems true: We put X frames in the internal buffer at +// first. If the data won't be blocked in packetizer, after the Process(), the +// internal buffer should still hold X frames since the number of frames coming +// from input is the same as the output needs. The key of having enough data for +// output needs, while the input data is piled up in packetizer, is by putting +// at least Z-1 frames as pre-buffering, since the maximum number of frames +// stuck in the packetizer before it can emit a packet is packet-size - 1. +// Otherwise, we don't have enough data for output if the new input data plus +// the data left in packetizer produces a smaller-than-10ms chunk, which will be +// left in packetizer. Thus we must have some pre-buffering frames in the +// mSegment to make up the length of the left chunk we need for output. This can +// also be told by by induction: +// (1) This holds when K = 0 +// (2) Assume this holds when K = N: so P(N) + S(N) = X +// => P(N) + S(N) = X >= Z-1 => S(N) >= Z-1-P(N) +// (3) When K = N+1, we have D(N+1) input frames comes +// a. if P(N) + D(N+1) < Z, then packetizer has no enough data for one +// packet. No data produced by packertizer, so the mSegment now has +// S(N) >= Z-1-P(N) frames. Output needs D(N+1) < Z-P(N) frames. So it +// needs at most Z-P(N)-1 frames, and mSegment has enough frames for +// output, Then, P(N+1) = P(N) + D(N+1) and S(N+1) = S(N) - D(N+1) +// => P(N+1) + S(N+1) = P(N) + S(N) = X +// b. if P(N) + D(N+1) = Z, then packetizer will produce one packet for +// mSegment, so mSegment now has S(N) + Z frames. Output needs D(N+1) +// = Z-P(N) frames. S(N) has at least Z-1-P(N)+Z >= Z-P(N) frames, since +// Z >= 1. So mSegment has enough frames for output. Then, P(N+1) = 0 and +// S(N+1) = S(N) + Z - D(N+1) = S(N) + P(N) +// => P(N+1) + S(N+1) = P(N) + S(N) = X +// c. if P(N) + D(N+1) > Z, and let P(N) + D(N+1) = q * Z + r, where q >= 1 +// and 0 <= r <= Z-1, then packetizer will produce can produce q packets +// for mSegment. Output needs D(N+1) = q * Z - P(N) + r frames and +// mSegment has S(N) + q * z >= q * z - P(N) + Z-1 >= q*z -P(N) + r, +// since r <= Z-1. So mSegment has enough frames for output. Then, +// P(N+1) = r and S(N+1) = S(N) + q * Z - D(N+1) +// => P(N+1) + S(N+1) = S(N) + (q * Z + r - D(N+1)) = S(N) + P(N) = X +// => P(K) + S(K) = X always holds +// +// Since P(K) + S(K) = X and P(K) is in [0, Z-1], the S(K) is in [X-Z+1, X] +// range. In our implementation, X is set to Z so S(K) is in [1, Z]. +// By the above workflow, we always have enough data for output and no extra +// frames put into packetizer. It means we don't have any glitch! +// +// II. In pass-through mode: +// +// Process() +// +--------+ +// input D(N) | | output D(N) +// -------------->-------->---------------> +// | | +// +--------+ +// +// The D(N) frames of data are just forwarded from input to output without any +// processing +void AudioInputProcessing::Process(MediaTrackGraphImpl* aGraph, GraphTime aFrom, + GraphTime aTo, AudioSegment* aInput, + AudioSegment* aOutput) { + MOZ_ASSERT(aGraph->OnGraphThread()); + MOZ_ASSERT(aFrom <= aTo); + MOZ_ASSERT(!mEnded); + + TrackTime need = aTo - aFrom; + if (need == 0) { + return; + } + + if (!mEnabled) { + LOG_FRAME("(Graph %p, Driver %p) AudioInputProcessing %p Filling %" PRId64 + " frames of silence to output (disabled)", + aGraph, aGraph->CurrentDriver(), this, need); + aOutput->AppendNullData(need); + return; + } + + MOZ_ASSERT(aInput->GetDuration() == need, + "Wrong data length from input port source"); + + if (PassThrough(aGraph)) { + LOG_FRAME( + "(Graph %p, Driver %p) AudioInputProcessing %p Forwarding %" PRId64 + " frames of input data to output directly (PassThrough)", + aGraph, aGraph->CurrentDriver(), this, aInput->GetDuration()); + aOutput->AppendSegment(aInput); + return; + } + + // SetPassThrough(false) must be called before reaching here. + MOZ_ASSERT(mPacketizerInput); + // If mRequestedInputChannelCount is updated, create a new packetizer. No + // need to change the pre-buffering since the rate is always the same. The + // frames left in the packetizer would be replaced by null data and then + // transferred to mSegment. + EnsureAudioProcessing(aGraph, mRequestedInputChannelCount); + + // Preconditions of the audio-processing logic. + MOZ_ASSERT(static_cast(mSegment.GetDuration()) + + mPacketizerInput->FramesAvailable() == + mPacketizerInput->mPacketSize); + // We pre-buffer mPacketSize frames, but the maximum number of frames stuck in + // the packetizer before it can emit a packet is mPacketSize-1. Thus that + // remaining 1 frame will always be present in mSegment. + MOZ_ASSERT(mSegment.GetDuration() >= 1); + MOZ_ASSERT(mSegment.GetDuration() <= mPacketizerInput->mPacketSize); + + PacketizeAndProcess(aGraph, *aInput); + LOG_FRAME("(Graph %p, Driver %p) AudioInputProcessing %p Buffer has %" PRId64 + " frames of data now, after packetizing and processing", + aGraph, aGraph->CurrentDriver(), this, mSegment.GetDuration()); + + // By setting pre-buffering to the number of frames of one packet, and + // because the maximum number of frames stuck in the packetizer before + // it can emit a packet is the mPacketSize-1, we always have at least + // one more frame than output needs. + MOZ_ASSERT(mSegment.GetDuration() > need); + aOutput->AppendSlice(mSegment, 0, need); + mSegment.RemoveLeading(need); + LOG_FRAME("(Graph %p, Driver %p) AudioInputProcessing %p moving %" PRId64 + " frames of data to output, leaving %" PRId64 " frames in buffer", + aGraph, aGraph->CurrentDriver(), this, need, + mSegment.GetDuration()); + + // Postconditions of the audio-processing logic. + MOZ_ASSERT(static_cast(mSegment.GetDuration()) + + mPacketizerInput->FramesAvailable() == + mPacketizerInput->mPacketSize); + MOZ_ASSERT(mSegment.GetDuration() >= 1); + MOZ_ASSERT(mSegment.GetDuration() <= mPacketizerInput->mPacketSize); +} + +void AudioInputProcessing::ProcessOutputData(MediaTrackGraphImpl* aGraph, + AudioDataValue* aBuffer, + size_t aFrames, TrackRate aRate, + uint32_t aChannels) { + MOZ_ASSERT(aGraph->OnGraphThread()); + + if (!mEnabled || PassThrough(aGraph)) { + return; + } + + if (!mPacketizerOutput || + mPacketizerOutput->mPacketSize != GetPacketSize(aRate) || + mPacketizerOutput->mChannels != aChannels) { + // It's ok to drop the audio still in the packetizer here: if this changes, + // we changed devices or something. + mPacketizerOutput = Nothing(); + mPacketizerOutput.emplace(GetPacketSize(aRate), aChannels); + } + + mPacketizerOutput->Input(aBuffer, aFrames); + + while (mPacketizerOutput->PacketsAvailable()) { + uint32_t samplesPerPacket = + mPacketizerOutput->mPacketSize * mPacketizerOutput->mChannels; + if (mOutputBuffer.Length() < samplesPerPacket) { + mOutputBuffer.SetLength(samplesPerPacket); + } + if (mDeinterleavedBuffer.Length() < samplesPerPacket) { + mDeinterleavedBuffer.SetLength(samplesPerPacket); + } + float* packet = mOutputBuffer.Data(); + mPacketizerOutput->Output(packet); + + AutoTArray deinterleavedPacketDataChannelPointers; + float* interleavedFarend = nullptr; + uint32_t channelCountFarend = 0; + uint32_t framesPerPacketFarend = 0; + + // Downmix from aChannels to MAX_CHANNELS if needed. We always have + // floats here, the packetized performed the conversion. + if (aChannels > MAX_CHANNELS) { + AudioConverter converter( + AudioConfig(aChannels, 0, AudioConfig::FORMAT_FLT), + AudioConfig(MAX_CHANNELS, 0, AudioConfig::FORMAT_FLT)); + framesPerPacketFarend = mPacketizerOutput->mPacketSize; + framesPerPacketFarend = + converter.Process(mInputDownmixBuffer, packet, framesPerPacketFarend); + interleavedFarend = mInputDownmixBuffer.Data(); + channelCountFarend = MAX_CHANNELS; + deinterleavedPacketDataChannelPointers.SetLength(MAX_CHANNELS); + } else { + interleavedFarend = packet; + channelCountFarend = aChannels; + framesPerPacketFarend = mPacketizerOutput->mPacketSize; + deinterleavedPacketDataChannelPointers.SetLength(aChannels); + } + + MOZ_ASSERT(interleavedFarend && + (channelCountFarend == 1 || channelCountFarend == 2) && + framesPerPacketFarend); + + if (mInputBuffer.Length() < framesPerPacketFarend * channelCountFarend) { + mInputBuffer.SetLength(framesPerPacketFarend * channelCountFarend); + } + + size_t offset = 0; + for (size_t i = 0; i < deinterleavedPacketDataChannelPointers.Length(); + ++i) { + deinterleavedPacketDataChannelPointers[i] = mInputBuffer.Data() + offset; + offset += framesPerPacketFarend; + } + + // Deinterleave, prepare a channel pointers array, with enough storage for + // the frames. + DeinterleaveAndConvertBuffer( + interleavedFarend, framesPerPacketFarend, channelCountFarend, + deinterleavedPacketDataChannelPointers.Elements()); + + // Having the same config for input and output means we potentially save + // some CPU. + StreamConfig inputConfig(aRate, channelCountFarend); + StreamConfig outputConfig = inputConfig; + + // Passing the same pointers here saves a copy inside this function. + DebugOnly err = mAudioProcessing->ProcessReverseStream( + deinterleavedPacketDataChannelPointers.Elements(), inputConfig, + outputConfig, deinterleavedPacketDataChannelPointers.Elements()); + + MOZ_ASSERT(!err, "Could not process the reverse stream."); + } +} + +// Only called if we're not in passthrough mode +void AudioInputProcessing::PacketizeAndProcess(MediaTrackGraphImpl* aGraph, + const AudioSegment& aSegment) { + MOZ_ASSERT(!PassThrough(aGraph), + "This should be bypassed when in PassThrough mode."); + MOZ_ASSERT(mEnabled); + MOZ_ASSERT(mPacketizerInput); + MOZ_ASSERT(mPacketizerInput->mPacketSize == + GetPacketSize(aGraph->GraphRate())); + + // Calculate number of the pending frames in mChunksInPacketizer. + auto pendingFrames = [&]() { + TrackTime frames = 0; + for (const auto& p : mChunksInPacketizer) { + frames += p.first; + } + return frames; + }; + + // Precondition of the Principal-labelling logic below. + MOZ_ASSERT(mPacketizerInput->FramesAvailable() == + static_cast(pendingFrames())); + + // The WriteToInterleavedBuffer will do upmix or downmix if the channel-count + // in aSegment's chunks is different from mPacketizerInput->mChannels + // WriteToInterleavedBuffer could be avoided once Bug 1729041 is done. + size_t sampleCount = aSegment.WriteToInterleavedBuffer( + mInterleavedBuffer, mPacketizerInput->mChannels); + size_t frameCount = + sampleCount / static_cast(mPacketizerInput->mChannels); + + // Packetize our input data into 10ms chunks, deinterleave into planar channel + // buffers, process, and append to the right MediaStreamTrack. + mPacketizerInput->Input(mInterleavedBuffer.Elements(), + static_cast(frameCount)); + + // Update mChunksInPacketizer and make sure the precondition for the + // Principal-labelling logic still holds. + for (AudioSegment::ConstChunkIterator iter(aSegment); !iter.IsEnded(); + iter.Next()) { + MOZ_ASSERT(iter->mDuration > 0); + mChunksInPacketizer.emplace_back( + std::make_pair(iter->mDuration, iter->mPrincipalHandle)); + } + MOZ_ASSERT(mPacketizerInput->FramesAvailable() == + static_cast(pendingFrames())); + + LOG_FRAME( + "(Graph %p, Driver %p) AudioInputProcessing %p Packetizing %zu frames. " + "Packetizer has %u frames (enough for %u packets) now", + aGraph, aGraph->CurrentDriver(), this, frameCount, + mPacketizerInput->FramesAvailable(), + mPacketizerInput->PacketsAvailable()); + + size_t offset = 0; + + while (mPacketizerInput->PacketsAvailable()) { + mPacketCount++; + uint32_t samplesPerPacket = + mPacketizerInput->mPacketSize * mPacketizerInput->mChannels; + if (mInputBuffer.Length() < samplesPerPacket) { + mInputBuffer.SetLength(samplesPerPacket); + } + if (mDeinterleavedBuffer.Length() < samplesPerPacket) { + mDeinterleavedBuffer.SetLength(samplesPerPacket); + } + float* packet = mInputBuffer.Data(); + mPacketizerInput->Output(packet); + + // Downmix from mPacketizerInput->mChannels to mono if needed. We always + // have floats here, the packetizer performed the conversion. + AutoTArray deinterleavedPacketizedInputDataChannelPointers; + uint32_t channelCountInput = 0; + if (mPacketizerInput->mChannels > MAX_CHANNELS) { + channelCountInput = MONO; + deinterleavedPacketizedInputDataChannelPointers.SetLength( + channelCountInput); + deinterleavedPacketizedInputDataChannelPointers[0] = + mDeinterleavedBuffer.Data(); + // Downmix to mono (and effectively have a planar buffer) by summing all + // channels in the first channel, and scaling by the number of channels to + // avoid clipping. + float gain = 1.f / mPacketizerInput->mChannels; + size_t readIndex = 0; + for (size_t i = 0; i < mPacketizerInput->mPacketSize; i++) { + mDeinterleavedBuffer.Data()[i] = 0.; + for (size_t j = 0; j < mPacketizerInput->mChannels; j++) { + mDeinterleavedBuffer.Data()[i] += gain * packet[readIndex++]; + } + } + } else { + channelCountInput = mPacketizerInput->mChannels; + // Deinterleave the input data + // Prepare an array pointing to deinterleaved channels. + deinterleavedPacketizedInputDataChannelPointers.SetLength( + channelCountInput); + offset = 0; + for (size_t i = 0; + i < deinterleavedPacketizedInputDataChannelPointers.Length(); ++i) { + deinterleavedPacketizedInputDataChannelPointers[i] = + mDeinterleavedBuffer.Data() + offset; + offset += mPacketizerInput->mPacketSize; + } + // Deinterleave to mInputBuffer, pointed to by inputBufferChannelPointers. + Deinterleave(packet, mPacketizerInput->mPacketSize, channelCountInput, + deinterleavedPacketizedInputDataChannelPointers.Elements()); + } + + StreamConfig inputConfig(aGraph->GraphRate(), channelCountInput); + StreamConfig outputConfig = inputConfig; + + // Bug 1404965: Get the right delay here, it saves some work down the line. + mAudioProcessing->set_stream_delay_ms(0); + + // Bug 1414837: find a way to not allocate here. + CheckedInt bufferSize(sizeof(float)); + bufferSize *= mPacketizerInput->mPacketSize; + bufferSize *= channelCountInput; + RefPtr buffer = SharedBuffer::Create(bufferSize); + + // Prepare channel pointers to the SharedBuffer created above. + AutoTArray processedOutputChannelPointers; + AutoTArray processedOutputChannelPointersConst; + processedOutputChannelPointers.SetLength(channelCountInput); + processedOutputChannelPointersConst.SetLength(channelCountInput); + + offset = 0; + for (size_t i = 0; i < processedOutputChannelPointers.Length(); ++i) { + processedOutputChannelPointers[i] = + static_cast(buffer->Data()) + offset; + processedOutputChannelPointersConst[i] = + static_cast(buffer->Data()) + offset; + offset += mPacketizerInput->mPacketSize; + } + + mAudioProcessing->ProcessStream( + deinterleavedPacketizedInputDataChannelPointers.Elements(), inputConfig, + outputConfig, processedOutputChannelPointers.Elements()); + + // If logging is enabled, dump the audio processing stats twice a second + if (MOZ_LOG_TEST(gMediaManagerLog, LogLevel::Debug) && + !(mPacketCount % 50)) { + AudioProcessingStats stats = mAudioProcessing->GetStatistics(); + char msg[1024]; + size_t offset = 0; +#define AddIfValue(format, member) \ + if (stats.member.has_value()) { \ + offset += SprintfBuf(msg + offset, sizeof(msg) - offset, \ + #member ":" format ", ", stats.member.value()); \ + } + AddIfValue("%d", voice_detected); + AddIfValue("%lf", echo_return_loss); + AddIfValue("%lf", echo_return_loss_enhancement); + AddIfValue("%lf", divergent_filter_fraction); + AddIfValue("%d", delay_median_ms); + AddIfValue("%d", delay_standard_deviation_ms); + AddIfValue("%lf", residual_echo_likelihood); + AddIfValue("%lf", residual_echo_likelihood_recent_max); + AddIfValue("%d", delay_ms); +#undef AddIfValue + LOG("AudioProcessing statistics: %s", msg); + } + + if (mEnded) { + continue; + } + + // We already have planar audio data of the right format. Insert into the + // MTG. + MOZ_ASSERT(processedOutputChannelPointers.Length() == channelCountInput); + + // Insert the processed data chunk by chunk to mSegment with the paired + // PrincipalHandle value. The chunks are tracked in mChunksInPacketizer. + + auto getAudioChunk = [&](TrackTime aStart, TrackTime aEnd, + const PrincipalHandle& aPrincipalHandle) { + if (aStart == aEnd) { + return AudioChunk(); + } + RefPtr other = buffer; + AudioChunk c = + AudioChunk(other.forget(), processedOutputChannelPointersConst, + static_cast(mPacketizerInput->mPacketSize), + aPrincipalHandle); + c.SliceTo(aStart, aEnd); + return c; + }; + + // The number of frames of data that needs to be labelled with Principal + // values. + TrackTime len = static_cast(mPacketizerInput->mPacketSize); + // The start offset of the unlabelled chunk. + TrackTime start = 0; + // By mChunksInPacketizer's information, we can keep labelling the + // unlabelled frames chunk by chunk. + while (!mChunksInPacketizer.empty()) { + auto& [frames, principal] = mChunksInPacketizer.front(); + const TrackTime end = start + frames; + if (end > len) { + // If the left unlabelled frames are part of this chunk, then we need to + // adjust the number of frames in the chunk. + if (len > start) { + mSegment.AppendAndConsumeChunk(getAudioChunk(start, len, principal)); + frames -= len - start; + } + break; + } + // Otherwise, the number of unlabelled frames is larger than or equal to + // this chunk. We can label the whole chunk directly. + mSegment.AppendAndConsumeChunk(getAudioChunk(start, end, principal)); + start = end; + mChunksInPacketizer.pop_front(); + } + + LOG_FRAME( + "(Graph %p, Driver %p) AudioInputProcessing %p Appending %u frames of " + "packetized audio, leaving %u frames in packetizer (%" PRId64 + " frames in mChunksInPacketizer)", + aGraph, aGraph->CurrentDriver(), this, mPacketizerInput->mPacketSize, + mPacketizerInput->FramesAvailable(), pendingFrames()); + + // Postcondition of the Principal-labelling logic. + MOZ_ASSERT(mPacketizerInput->FramesAvailable() == + static_cast(pendingFrames())); + } +} + +void AudioInputProcessing::DeviceChanged(MediaTrackGraphImpl* aGraph) { + MOZ_ASSERT(aGraph->OnGraphThread()); + + // Reset some processing + mAudioProcessing->Initialize(); + LOG_FRAME( + "(Graph %p, Driver %p) AudioInputProcessing %p Reinitializing audio " + "processing", + aGraph, aGraph->CurrentDriver(), this); +} + +void AudioInputProcessing::ApplyConfig(MediaTrackGraphImpl* aGraph, + const AudioProcessing::Config& aConfig) { + MOZ_ASSERT(aGraph->OnGraphThread()); + mAudioProcessing->ApplyConfig(aConfig); +} + +void AudioInputProcessing::End() { + mEnded = true; + mSegment.Clear(); +} + +TrackTime AudioInputProcessing::NumBufferedFrames( + MediaTrackGraphImpl* aGraph) const { + MOZ_ASSERT(aGraph->OnGraphThread()); + return mSegment.GetDuration(); +} + +void AudioInputProcessing::EnsureAudioProcessing(MediaTrackGraphImpl* aGraph, + uint32_t aChannels) { + MOZ_ASSERT(aGraph->OnGraphThread()); + MOZ_ASSERT(aChannels > 0); + MOZ_ASSERT(mEnabled); + MOZ_ASSERT(!mSkipProcessing); + + if (mPacketizerInput && mPacketizerInput->mChannels == aChannels) { + return; + } + + // If mPacketizerInput exists but with different channel-count, there is no + // need to change pre-buffering since the packet size is the same as the old + // one, since the rate is a constant. + MOZ_ASSERT_IF(mPacketizerInput, mPacketizerInput->mPacketSize == + GetPacketSize(aGraph->GraphRate())); + bool needPreBuffering = !mPacketizerInput; + if (mPacketizerInput) { + const TrackTime numBufferedFrames = + static_cast(mPacketizerInput->FramesAvailable()); + mSegment.AppendNullData(numBufferedFrames); + mPacketizerInput = Nothing(); + mChunksInPacketizer.clear(); + } + + mPacketizerInput.emplace(GetPacketSize(aGraph->GraphRate()), aChannels); + + if (needPreBuffering) { + LOG_FRAME( + "(Graph %p, Driver %p) AudioInputProcessing %p: Adding %u frames of " + "silence as pre-buffering", + aGraph, aGraph->CurrentDriver(), this, mPacketizerInput->mPacketSize); + + AudioSegment buffering; + buffering.AppendNullData( + static_cast(mPacketizerInput->mPacketSize)); + PacketizeAndProcess(aGraph, buffering); + } +} + +void AudioInputProcessing::ResetAudioProcessing(MediaTrackGraphImpl* aGraph) { + MOZ_ASSERT(aGraph->OnGraphThread()); + MOZ_ASSERT(mSkipProcessing || !mEnabled); + MOZ_ASSERT(mPacketizerInput); + + LOG_FRAME( + "(Graph %p, Driver %p) AudioInputProcessing %p Resetting audio " + "processing", + aGraph, aGraph->CurrentDriver(), this); + + // Reset AudioProcessing so that if we resume processing in the future it + // doesn't depend on old state. + mAudioProcessing->Initialize(); + + MOZ_ASSERT(static_cast(mSegment.GetDuration()) + + mPacketizerInput->FramesAvailable() == + mPacketizerInput->mPacketSize); + + // It's ok to clear all the internal buffer here since we won't use mSegment + // in pass-through mode or when audio processing is disabled. + LOG_FRAME( + "(Graph %p, Driver %p) AudioInputProcessing %p Emptying out %" PRId64 + " frames of data", + aGraph, aGraph->CurrentDriver(), this, mSegment.GetDuration()); + mSegment.Clear(); + + mPacketizerInput = Nothing(); + mChunksInPacketizer.clear(); +} + +void AudioProcessingTrack::Destroy() { + MOZ_ASSERT(NS_IsMainThread()); + DisconnectDeviceInput(); + + MediaTrack::Destroy(); +} + +void AudioProcessingTrack::SetInputProcessing( + RefPtr aInputProcessing) { + class Message : public ControlMessage { + const RefPtr mTrack; + const RefPtr mProcessing; + + public: + Message(RefPtr aTrack, + RefPtr aProcessing) + : ControlMessage(aTrack), + mTrack(std::move(aTrack)), + mProcessing(std::move(aProcessing)) {} + void Run() override { + TRACE("AudioProcessingTrack::SetInputProcessingImpl"); + mTrack->SetInputProcessingImpl(mProcessing); + } + }; + + if (IsDestroyed()) { + return; + } + GraphImpl()->AppendMessage( + MakeUnique(std::move(this), std::move(aInputProcessing))); +} + +AudioProcessingTrack* AudioProcessingTrack::Create(MediaTrackGraph* aGraph) { + MOZ_ASSERT(NS_IsMainThread()); + AudioProcessingTrack* track = new AudioProcessingTrack(aGraph->GraphRate()); + aGraph->AddTrack(track); + return track; +} + +void AudioProcessingTrack::DestroyImpl() { + ProcessedMediaTrack::DestroyImpl(); + if (mInputProcessing) { + mInputProcessing->End(); + } +} + +void AudioProcessingTrack::ProcessInput(GraphTime aFrom, GraphTime aTo, + uint32_t aFlags) { + TRACE_COMMENT("AudioProcessingTrack::ProcessInput", "AudioProcessingTrack %p", + this); + MOZ_ASSERT(mInputProcessing); + + LOG_FRAME( + "(Graph %p, Driver %p) AudioProcessingTrack %p ProcessInput from %" PRId64 + " to %" PRId64 ", needs %" PRId64 " frames", + mGraph, mGraph->CurrentDriver(), this, aFrom, aTo, aTo - aFrom); + + if (aFrom >= aTo) { + return; + } + + if (!mInputProcessing->IsEnded()) { + MOZ_ASSERT(TrackTimeToGraphTime(GetEnd()) == aFrom); + if (mInputs.IsEmpty()) { + GetData()->AppendNullData(aTo - aFrom); + LOG_FRAME("(Graph %p, Driver %p) AudioProcessingTrack %p Filling %" PRId64 + " frames of null data (no input source)", + mGraph, mGraph->CurrentDriver(), this, aTo - aFrom); + } else { + MOZ_ASSERT(mInputs.Length() == 1); + AudioSegment data; + DeviceInputConsumerTrack::GetInputSourceData(data, mInputs[0], aFrom, + aTo); + mInputProcessing->Process(GraphImpl(), aFrom, aTo, &data, + GetData()); + } + MOZ_ASSERT(TrackTimeToGraphTime(GetEnd()) == aTo); + + ApplyTrackDisabling(mSegment.get()); + } else if (aFlags & ALLOW_END) { + mEnded = true; + } +} + +void AudioProcessingTrack::NotifyOutputData(MediaTrackGraphImpl* aGraph, + AudioDataValue* aBuffer, + size_t aFrames, TrackRate aRate, + uint32_t aChannels) { + MOZ_ASSERT(mGraph == aGraph, "Cannot feed audio output to another graph"); + MOZ_ASSERT(mGraph->OnGraphThread()); + if (mInputProcessing) { + mInputProcessing->ProcessOutputData(aGraph, aBuffer, aFrames, aRate, + aChannels); + } +} + +void AudioProcessingTrack::SetInputProcessingImpl( + RefPtr aInputProcessing) { + MOZ_ASSERT(GraphImpl()->OnGraphThread()); + mInputProcessing = std::move(aInputProcessing); +} + +MediaEngineWebRTCAudioCaptureSource::MediaEngineWebRTCAudioCaptureSource( + const MediaDevice* aMediaDevice) { + MOZ_ASSERT(aMediaDevice->mMediaSource == MediaSourceEnum::AudioCapture); +} + +/* static */ +nsString MediaEngineWebRTCAudioCaptureSource::GetUUID() { + nsID uuid{}; + char uuidBuffer[NSID_LENGTH]; + nsCString asciiString; + ErrorResult rv; + + rv = nsID::GenerateUUIDInPlace(uuid); + if (rv.Failed()) { + return u""_ns; + } + + uuid.ToProvidedString(uuidBuffer); + asciiString.AssignASCII(uuidBuffer); + + // Remove {} and the null terminator + return NS_ConvertASCIItoUTF16(Substring(asciiString, 1, NSID_LENGTH - 3)); +} + +/* static */ +nsString MediaEngineWebRTCAudioCaptureSource::GetGroupId() { + return u"AudioCaptureGroup"_ns; +} + +void MediaEngineWebRTCAudioCaptureSource::SetTrack( + const RefPtr& aTrack, const PrincipalHandle& aPrincipalHandle) { + AssertIsOnOwningThread(); + // Nothing to do here. aTrack is a placeholder dummy and not exposed. +} + +nsresult MediaEngineWebRTCAudioCaptureSource::Start() { + AssertIsOnOwningThread(); + return NS_OK; +} + +nsresult MediaEngineWebRTCAudioCaptureSource::Stop() { + AssertIsOnOwningThread(); + return NS_OK; +} + +nsresult MediaEngineWebRTCAudioCaptureSource::Reconfigure( + const dom::MediaTrackConstraints& aConstraints, + const MediaEnginePrefs& aPrefs, const char** aOutBadConstraint) { + return NS_OK; +} + +void MediaEngineWebRTCAudioCaptureSource::GetSettings( + dom::MediaTrackSettings& aOutSettings) const { + aOutSettings.mAutoGainControl.Construct(false); + aOutSettings.mEchoCancellation.Construct(false); + aOutSettings.mNoiseSuppression.Construct(false); + aOutSettings.mChannelCount.Construct(1); +} + +} // namespace mozilla + +// Don't allow our macros to leak into other cpps in our unified build unit. +#undef MAX_CHANNELS +#undef MONO +#undef MAX_SAMPLING_FREQ diff --git a/dom/media/webrtc/MediaEngineWebRTCAudio.h b/dom/media/webrtc/MediaEngineWebRTCAudio.h new file mode 100644 index 0000000000..d9a602962d --- /dev/null +++ b/dom/media/webrtc/MediaEngineWebRTCAudio.h @@ -0,0 +1,295 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef MediaEngineWebRTCAudio_h +#define MediaEngineWebRTCAudio_h + +#include "AudioPacketizer.h" +#include "AudioSegment.h" +#include "AudioDeviceInfo.h" +#include "DeviceInputTrack.h" +#include "MediaEngineWebRTC.h" +#include "MediaEnginePrefs.h" +#include "MediaTrackListener.h" +#include "modules/audio_processing/include/audio_processing.h" + +namespace mozilla { + +class AudioInputProcessing; +class AudioProcessingTrack; + +// This class is created and used exclusively on the Media Manager thread, with +// exactly two exceptions: +// - Pull is always called on the MTG thread. It only ever uses +// mInputProcessing. mInputProcessing is set, then a message is sent first to +// the main thread and then the MTG thread so that it can be used as part of +// the graph processing. On destruction, similarly, a message is sent to the +// graph so that it stops using it, and then it is deleted. +// - mSettings is created on the MediaManager thread is always ever accessed on +// the Main Thread. It is const. +class MediaEngineWebRTCMicrophoneSource : public MediaEngineSource { + public: + explicit MediaEngineWebRTCMicrophoneSource(const MediaDevice* aMediaDevice); + + nsresult Allocate(const dom::MediaTrackConstraints& aConstraints, + const MediaEnginePrefs& aPrefs, uint64_t aWindowID, + const char** aOutBadConstraint) override; + nsresult Deallocate() override; + void SetTrack(const RefPtr& aTrack, + const PrincipalHandle& aPrincipal) override; + nsresult Start() override; + nsresult Stop() override; + nsresult Reconfigure(const dom::MediaTrackConstraints& aConstraints, + const MediaEnginePrefs& aPrefs, + const char** aOutBadConstraint) override; + + /** + * Assigns the current settings of the capture to aOutSettings. + * Main thread only. + */ + void GetSettings(dom::MediaTrackSettings& aOutSettings) const override; + + nsresult TakePhoto(MediaEnginePhotoCallback* aCallback) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + protected: + ~MediaEngineWebRTCMicrophoneSource() = default; + + private: + /** + * From a set of constraints and about:config preferences, output the correct + * set of preferences that can be sent to AudioInputProcessing. + * + * This can fail if the number of channels requested is zero, negative, or + * more than the device supports. + */ + nsresult EvaluateSettings(const NormalizedConstraints& aConstraintsUpdate, + const MediaEnginePrefs& aInPrefs, + MediaEnginePrefs* aOutPrefs, + const char** aOutBadConstraint); + /** + * From settings output by EvaluateSettings, send those settings to the + * AudioInputProcessing instance and the main thread (for use in GetSettings). + */ + void ApplySettings(const MediaEnginePrefs& aPrefs); + + PrincipalHandle mPrincipal = PRINCIPAL_HANDLE_NONE; + + const RefPtr mDeviceInfo; + + // The maximum number of channels that this device supports. + const uint32_t mDeviceMaxChannelCount; + // The current settings for the underlying device. + // Constructed on the MediaManager thread, and then only ever accessed on the + // main thread. + const nsMainThreadPtrHandle> + mSettings; + + // Current state of the resource for this source. + MediaEngineSourceState mState; + + // The current preferences that will be forwarded to mAudioProcessingConfig + // below. + MediaEnginePrefs mCurrentPrefs; + + // The AudioProcessingTrack used to inteface with the MediaTrackGraph. Set in + // SetTrack as part of the initialization, and nulled in ::Deallocate. + RefPtr mTrack; + + // See note at the top of this class. + RefPtr mInputProcessing; + + // Copy of the config currently applied to AudioProcessing through + // mInputProcessing. + webrtc::AudioProcessing::Config mAudioProcessingConfig; +}; + +// This class is created on the MediaManager thread, and then exclusively used +// on the MTG thread. +// All communication is done via message passing using MTG ControlMessages +class AudioInputProcessing : public AudioDataListener { + public: + explicit AudioInputProcessing(uint32_t aMaxChannelCount); + void Process(MediaTrackGraphImpl* aGraph, GraphTime aFrom, GraphTime aTo, + AudioSegment* aInput, AudioSegment* aOutput); + + void ProcessOutputData(MediaTrackGraphImpl* aGraph, AudioDataValue* aBuffer, + size_t aFrames, TrackRate aRate, uint32_t aChannels); + bool IsVoiceInput(MediaTrackGraphImpl* aGraph) const override { + // If we're passing data directly without AEC or any other process, this + // means that all voice-processing has been disabled intentionaly. In this + // case, consider that the device is not used for voice input. + return !PassThrough(aGraph); + } + + void Start(MediaTrackGraphImpl* aGraph); + void Stop(MediaTrackGraphImpl* aGraph); + + void DeviceChanged(MediaTrackGraphImpl* aGraph) override; + + uint32_t RequestedInputChannelCount(MediaTrackGraphImpl*) override { + return GetRequestedInputChannelCount(); + } + + void Disconnect(MediaTrackGraphImpl* aGraph) override; + + void PacketizeAndProcess(MediaTrackGraphImpl* aGraph, + const AudioSegment& aSegment); + + void SetPassThrough(MediaTrackGraphImpl* aGraph, bool aPassThrough); + uint32_t GetRequestedInputChannelCount(); + void SetRequestedInputChannelCount(MediaTrackGraphImpl* aGraph, + CubebUtils::AudioDeviceID aDeviceId, + uint32_t aRequestedInputChannelCount); + // This is true when all processing is disabled, we can skip + // packetization, resampling and other processing passes. + bool PassThrough(MediaTrackGraphImpl* aGraphImpl) const; + + // This allow changing the APM options, enabling or disabling processing + // steps. The config gets applied the next time we're about to process input + // data. + void ApplyConfig(MediaTrackGraphImpl* aGraph, + const webrtc::AudioProcessing::Config& aConfig); + + void End(); + + TrackTime NumBufferedFrames(MediaTrackGraphImpl* aGraph) const; + + // The packet size contains samples in 10ms. The unit of aRate is hz. + constexpr static uint32_t GetPacketSize(TrackRate aRate) { + return static_cast(aRate) / 100u; + } + + bool IsEnded() const { return mEnded; } + + private: + ~AudioInputProcessing() = default; + void EnsureAudioProcessing(MediaTrackGraphImpl* aGraph, uint32_t aChannels); + void ResetAudioProcessing(MediaTrackGraphImpl* aGraph); + PrincipalHandle GetCheckedPrincipal(const AudioSegment& aSegment); + // This implements the processing algoritm to apply to the input (e.g. a + // microphone). If all algorithms are disabled, this class in not used. This + // class only accepts audio chunks of 10ms. It has two inputs and one output: + // it is fed the speaker data and the microphone data. It outputs processed + // input data. + const UniquePtr mAudioProcessing; + // Packetizer to be able to feed 10ms packets to the input side of + // mAudioProcessing. Not used if the processing is bypassed. + Maybe> mPacketizerInput; + // Packetizer to be able to feed 10ms packets to the output side of + // mAudioProcessing. Not used if the processing is bypassed. + Maybe> mPacketizerOutput; + // The number of channels asked for by content, after clamping to the range of + // legal channel count for this particular device. + uint32_t mRequestedInputChannelCount; + // mSkipProcessing is true if none of the processing passes are enabled, + // because of prefs or constraints. This allows simply copying the audio into + // the MTG, skipping resampling and the whole webrtc.org code. + bool mSkipProcessing; + // Stores the mixed audio output for the reverse-stream of the AEC (the + // speaker data). + AlignedFloatBuffer mOutputBuffer; + // Stores the input audio, to be processed by the APM. + AlignedFloatBuffer mInputBuffer; + // Stores the deinterleaved microphone audio + AlignedFloatBuffer mDeinterleavedBuffer; + // Stores the mixed down input audio + AlignedFloatBuffer mInputDownmixBuffer; + // Stores data waiting to be pulled. + AudioSegment mSegment; + // Whether or not this MediaEngine is enabled. If it's not enabled, it + // operates in "pull" mode, and we append silence only, releasing the audio + // input track. + bool mEnabled; + // Whether or not we've ended and removed the AudioProcessingTrack. + bool mEnded; + // When processing is enabled, the number of packets received by this + // instance, to implement periodic logging. + uint64_t mPacketCount; + // A storage holding the interleaved audio data converted the AudioSegment. + // This will be used as an input parameter for PacketizeAndProcess. This + // should be removed once bug 1729041 is done. + AutoTArray + mInterleavedBuffer; + // Tracks the pending frames with paired principals piled up in packetizer. + std::deque> mChunksInPacketizer; +}; + +// MediaTrack subclass tailored for MediaEngineWebRTCMicrophoneSource. +class AudioProcessingTrack : public DeviceInputConsumerTrack { + // Only accessed on the graph thread. + RefPtr mInputProcessing; + + explicit AudioProcessingTrack(TrackRate aSampleRate) + : DeviceInputConsumerTrack(aSampleRate) {} + + ~AudioProcessingTrack() = default; + + public: + // Main Thread API + void Destroy() override; + void SetInputProcessing(RefPtr aInputProcessing); + static AudioProcessingTrack* Create(MediaTrackGraph* aGraph); + + // Graph Thread API + void DestroyImpl() override; + void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override; + uint32_t NumberOfChannels() const override { + MOZ_DIAGNOSTIC_ASSERT( + mInputProcessing, + "Must set mInputProcessing before exposing to content"); + return mInputProcessing->GetRequestedInputChannelCount(); + } + // Pass the graph's mixed audio output to mInputProcessing for processing as + // the reverse stream. + void NotifyOutputData(MediaTrackGraphImpl* aGraph, AudioDataValue* aBuffer, + size_t aFrames, TrackRate aRate, uint32_t aChannels); + + // Any thread + AudioProcessingTrack* AsAudioProcessingTrack() override { return this; } + + private: + // Graph thread API + void SetInputProcessingImpl(RefPtr aInputProcessing); +}; + +class MediaEngineWebRTCAudioCaptureSource : public MediaEngineSource { + public: + explicit MediaEngineWebRTCAudioCaptureSource(const MediaDevice* aMediaDevice); + static nsString GetUUID(); + static nsString GetGroupId(); + nsresult Allocate(const dom::MediaTrackConstraints& aConstraints, + const MediaEnginePrefs& aPrefs, uint64_t aWindowID, + const char** aOutBadConstraint) override { + // Nothing to do here, everything is managed in MediaManager.cpp + return NS_OK; + } + nsresult Deallocate() override { + // Nothing to do here, everything is managed in MediaManager.cpp + return NS_OK; + } + void SetTrack(const RefPtr& aTrack, + const PrincipalHandle& aPrincipal) override; + nsresult Start() override; + nsresult Stop() override; + nsresult Reconfigure(const dom::MediaTrackConstraints& aConstraints, + const MediaEnginePrefs& aPrefs, + const char** aOutBadConstraint) override; + + nsresult TakePhoto(MediaEnginePhotoCallback* aCallback) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + void GetSettings(dom::MediaTrackSettings& aOutSettings) const override; + + protected: + virtual ~MediaEngineWebRTCAudioCaptureSource() = default; +}; + +} // end namespace mozilla + +#endif // MediaEngineWebRTCAudio_h diff --git a/dom/media/webrtc/MediaTrackConstraints.cpp b/dom/media/webrtc/MediaTrackConstraints.cpp new file mode 100644 index 0000000000..101cd0240a --- /dev/null +++ b/dom/media/webrtc/MediaTrackConstraints.cpp @@ -0,0 +1,560 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 "MediaTrackConstraints.h" + +#include +#include +#include + +#include "MediaEngineSource.h" +#include "mozilla/dom/MediaStreamTrackBinding.h" +#include "mozilla/MediaManager.h" + +#ifdef MOZ_WEBRTC +namespace mozilla { +extern LazyLogModule gMediaManagerLog; +} +#else +static mozilla::LazyLogModule gMediaManagerLog("MediaManager"); +#endif +#define LOG(...) MOZ_LOG(gMediaManagerLog, LogLevel::Debug, (__VA_ARGS__)) + +namespace mozilla { + +using dom::CallerType; +using dom::ConstrainBooleanParameters; + +template +template +void NormalizedConstraintSet::Range::SetFrom( + const ConstrainRange& aOther) { + if (aOther.mIdeal.WasPassed()) { + mIdeal.emplace(aOther.mIdeal.Value()); + } + if (aOther.mExact.WasPassed()) { + mMin = aOther.mExact.Value(); + mMax = aOther.mExact.Value(); + } else { + if (aOther.mMin.WasPassed()) { + mMin = aOther.mMin.Value(); + } + if (aOther.mMax.WasPassed()) { + mMax = aOther.mMax.Value(); + } + } +} + +// The Range code works surprisingly well for bool, except when averaging +// ideals. +template <> +bool NormalizedConstraintSet::Range::Merge(const Range& aOther) { + if (!Intersects(aOther)) { + return false; + } + Intersect(aOther); + + // To avoid "unsafe use of type 'bool'", we keep counter in mMergeDenominator + uint32_t counter = mMergeDenominator >> 16; + uint32_t denominator = mMergeDenominator & 0xffff; + + if (aOther.mIdeal.isSome()) { + if (mIdeal.isNothing()) { + mIdeal.emplace(aOther.Get(false)); + counter = aOther.Get(false); + denominator = 1; + } else { + if (!denominator) { + counter = Get(false); + denominator = 1; + } + counter += aOther.Get(false); + denominator++; + } + } + mMergeDenominator = ((counter & 0xffff) << 16) + (denominator & 0xffff); + return true; +} + +template <> +void NormalizedConstraintSet::Range::FinalizeMerge() { + if (mMergeDenominator) { + uint32_t counter = mMergeDenominator >> 16; + uint32_t denominator = mMergeDenominator & 0xffff; + + *mIdeal = !!(counter / denominator); + mMergeDenominator = 0; + } +} + +NormalizedConstraintSet::LongRange::LongRange( + LongPtrType aMemberPtr, const char* aName, + const dom::Optional& aOther, + bool advanced, nsTArray* aList) + : Range((MemberPtrType)aMemberPtr, aName, 1 + INT32_MIN, + INT32_MAX, // +1 avoids Windows compiler bug + aList) { + if (!aOther.WasPassed()) { + return; + } + auto& other = aOther.Value(); + if (other.IsLong()) { + if (advanced) { + mMin = mMax = other.GetAsLong(); + } else { + mIdeal.emplace(other.GetAsLong()); + } + } else { + SetFrom(other.GetAsConstrainLongRange()); + } +} + +NormalizedConstraintSet::LongLongRange::LongLongRange( + LongLongPtrType aMemberPtr, const char* aName, const long long& aOther, + nsTArray* aList) + : Range((MemberPtrType)aMemberPtr, aName, 1 + INT64_MIN, + INT64_MAX, // +1 avoids Windows compiler bug + aList) { + mIdeal.emplace(aOther); +} + +NormalizedConstraintSet::DoubleRange::DoubleRange( + DoublePtrType aMemberPtr, const char* aName, + const dom::Optional& aOther, + bool advanced, nsTArray* aList) + : Range((MemberPtrType)aMemberPtr, aName, + -std::numeric_limits::infinity(), + std::numeric_limits::infinity(), aList) { + if (!aOther.WasPassed()) { + return; + } + auto& other = aOther.Value(); + if (other.IsDouble()) { + if (advanced) { + mMin = mMax = other.GetAsDouble(); + } else { + mIdeal.emplace(other.GetAsDouble()); + } + } else { + SetFrom(other.GetAsConstrainDoubleRange()); + } +} + +NormalizedConstraintSet::BooleanRange::BooleanRange( + BooleanPtrType aMemberPtr, const char* aName, + const dom::Optional& aOther, + bool advanced, nsTArray* aList) + : Range((MemberPtrType)aMemberPtr, aName, false, true, aList) { + if (!aOther.WasPassed()) { + return; + } + auto& other = aOther.Value(); + if (other.IsBoolean()) { + if (advanced) { + mMin = mMax = other.GetAsBoolean(); + } else { + mIdeal.emplace(other.GetAsBoolean()); + } + } else { + auto& r = other.GetAsConstrainBooleanParameters(); + if (r.mIdeal.WasPassed()) { + mIdeal.emplace(r.mIdeal.Value()); + } + if (r.mExact.WasPassed()) { + mMin = r.mExact.Value(); + mMax = r.mExact.Value(); + } + } +} + +NormalizedConstraintSet::StringRange::StringRange( + StringPtrType aMemberPtr, const char* aName, + const dom::Optional< + dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters>& + aOther, + bool advanced, nsTArray* aList) + : BaseRange((MemberPtrType)aMemberPtr, aName, aList) { + if (!aOther.WasPassed()) { + return; + } + auto& other = aOther.Value(); + if (other.IsString()) { + if (advanced) { + mExact.insert(other.GetAsString()); + } else { + mIdeal.insert(other.GetAsString()); + } + } else if (other.IsStringSequence()) { + if (advanced) { + mExact.clear(); + for (auto& str : other.GetAsStringSequence()) { + mExact.insert(str); + } + } else { + mIdeal.clear(); + for (auto& str : other.GetAsStringSequence()) { + mIdeal.insert(str); + } + } + } else { + SetFrom(other.GetAsConstrainDOMStringParameters()); + } +} + +void NormalizedConstraintSet::StringRange::SetFrom( + const dom::ConstrainDOMStringParameters& aOther) { + if (aOther.mIdeal.WasPassed()) { + mIdeal.clear(); + if (aOther.mIdeal.Value().IsString()) { + mIdeal.insert(aOther.mIdeal.Value().GetAsString()); + } else { + for (auto& str : aOther.mIdeal.Value().GetAsStringSequence()) { + mIdeal.insert(str); + } + } + } + if (aOther.mExact.WasPassed()) { + mExact.clear(); + if (aOther.mExact.Value().IsString()) { + mExact.insert(aOther.mExact.Value().GetAsString()); + } else { + for (auto& str : aOther.mExact.Value().GetAsStringSequence()) { + mExact.insert(str); + } + } + } +} + +auto NormalizedConstraintSet::StringRange::Clamp(const ValueType& n) const + -> ValueType { + if (mExact.empty()) { + return n; + } + ValueType result; + for (auto& entry : n) { + if (mExact.find(entry) != mExact.end()) { + result.insert(entry); + } + } + return result; +} + +bool NormalizedConstraintSet::StringRange::Intersects( + const StringRange& aOther) const { + if (mExact.empty() || aOther.mExact.empty()) { + return true; + } + + ValueType intersection; + set_intersection(mExact.begin(), mExact.end(), aOther.mExact.begin(), + aOther.mExact.end(), + std::inserter(intersection, intersection.begin())); + return !intersection.empty(); +} + +void NormalizedConstraintSet::StringRange::Intersect( + const StringRange& aOther) { + if (aOther.mExact.empty()) { + return; + } + + ValueType intersection; + set_intersection(mExact.begin(), mExact.end(), aOther.mExact.begin(), + aOther.mExact.end(), + std::inserter(intersection, intersection.begin())); + mExact = intersection; +} + +bool NormalizedConstraintSet::StringRange::Merge(const StringRange& aOther) { + if (!Intersects(aOther)) { + return false; + } + Intersect(aOther); + + ValueType unioned; + set_union(mIdeal.begin(), mIdeal.end(), aOther.mIdeal.begin(), + aOther.mIdeal.end(), std::inserter(unioned, unioned.begin())); + mIdeal = unioned; + return true; +} + +NormalizedConstraints::NormalizedConstraints( + const dom::MediaTrackConstraints& aOther, nsTArray* aList) + : NormalizedConstraintSet(aOther, false, aList), mBadConstraint(nullptr) { + if (aOther.mAdvanced.WasPassed()) { + for (auto& entry : aOther.mAdvanced.Value()) { + mAdvanced.push_back(NormalizedConstraintSet(entry, true)); + } + } +} + +FlattenedConstraints::FlattenedConstraints(const NormalizedConstraints& aOther) + : NormalizedConstraintSet(aOther) { + for (auto& set : aOther.mAdvanced) { + // Must only apply compatible i.e. inherently non-overconstraining sets + // This rule is pretty much why this code is centralized here. + if (mWidth.Intersects(set.mWidth) && mHeight.Intersects(set.mHeight) && + mFrameRate.Intersects(set.mFrameRate)) { + mWidth.Intersect(set.mWidth); + mHeight.Intersect(set.mHeight); + mFrameRate.Intersect(set.mFrameRate); + } + if (mEchoCancellation.Intersects(set.mEchoCancellation)) { + mEchoCancellation.Intersect(set.mEchoCancellation); + } + if (mNoiseSuppression.Intersects(set.mNoiseSuppression)) { + mNoiseSuppression.Intersect(set.mNoiseSuppression); + } + if (mAutoGainControl.Intersects(set.mAutoGainControl)) { + mAutoGainControl.Intersect(set.mAutoGainControl); + } + if (mChannelCount.Intersects(set.mChannelCount)) { + mChannelCount.Intersect(set.mChannelCount); + } + } +} + +// MediaEngine helper +// +// The full algorithm for all devices. +// +// Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX + +// First, all devices have a minimum distance based on their deviceId. +// If you have no other constraints, use this one. Reused by all device types. + +/* static */ +bool MediaConstraintsHelper::SomeSettingsFit( + const NormalizedConstraints& aConstraints, + const nsTArray>& aDevices) { + nsTArray sets; + sets.AppendElement(&aConstraints); + + MOZ_ASSERT(!aDevices.IsEmpty()); + for (auto& device : aDevices) { + auto distance = device->GetBestFitnessDistance(sets, CallerType::NonSystem); + if (distance != UINT32_MAX) { + return true; + } + } + return false; +} + +// Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX + +/* static */ +uint32_t MediaConstraintsHelper::FitnessDistance( + const Maybe& aN, + const NormalizedConstraintSet::StringRange& aParams) { + if (!aParams.mExact.empty() && + (aN.isNothing() || aParams.mExact.find(*aN) == aParams.mExact.end())) { + return UINT32_MAX; + } + if (!aParams.mIdeal.empty() && + (aN.isNothing() || aParams.mIdeal.find(*aN) == aParams.mIdeal.end())) { + return 1000; + } + return 0; +} + +/* static */ const char* MediaConstraintsHelper::SelectSettings( + const NormalizedConstraints& aConstraints, + nsTArray>& aDevices, CallerType aCallerType) { + auto& c = aConstraints; + LogConstraints(c); + + // First apply top-level constraints. + + // Stack constraintSets that pass, starting with the required one, because the + // whole stack must be re-satisfied each time a capability-set is ruled out + // (this avoids storing state or pushing algorithm into the lower-level code). + nsTArray> unsatisfactory; + nsTArray aggregateConstraints; + aggregateConstraints.AppendElement(&c); + + std::multimap> ordered; + + for (uint32_t i = 0; i < aDevices.Length();) { + uint32_t distance = + aDevices[i]->GetBestFitnessDistance(aggregateConstraints, aCallerType); + if (distance == UINT32_MAX) { + unsatisfactory.AppendElement(std::move(aDevices[i])); + aDevices.RemoveElementAt(i); + } else { + ordered.insert(std::make_pair(distance, aDevices[i])); + ++i; + } + } + if (aDevices.IsEmpty()) { + return FindBadConstraint(c, unsatisfactory); + } + + // Order devices by shortest distance + for (auto& ordinal : ordered) { + aDevices.RemoveElement(ordinal.second); + aDevices.AppendElement(ordinal.second); + } + + // Then apply advanced constraints. + + for (const auto& advanced : c.mAdvanced) { + aggregateConstraints.AppendElement(&advanced); + nsTArray> rejects; + for (uint32_t j = 0; j < aDevices.Length();) { + uint32_t distance = aDevices[j]->GetBestFitnessDistance( + aggregateConstraints, aCallerType); + if (distance == UINT32_MAX) { + rejects.AppendElement(std::move(aDevices[j])); + aDevices.RemoveElementAt(j); + } else { + ++j; + } + } + if (aDevices.IsEmpty()) { + aDevices.AppendElements(std::move(rejects)); + aggregateConstraints.RemoveLastElement(); + } + } + return nullptr; +} + +/* static */ const char* MediaConstraintsHelper::FindBadConstraint( + const NormalizedConstraints& aConstraints, + const nsTArray>& aDevices) { + // The spec says to report a constraint that satisfies NONE + // of the sources. Unfortunately, this is a bit laborious to find out, and + // requires updating as new constraints are added! + auto& c = aConstraints; + dom::MediaTrackConstraints empty; + + if (aDevices.IsEmpty() || + !SomeSettingsFit(NormalizedConstraints(empty), aDevices)) { + return ""; + } + { + NormalizedConstraints fresh(empty); + fresh.mDeviceId = c.mDeviceId; + if (!SomeSettingsFit(fresh, aDevices)) { + return "deviceId"; + } + } + { + NormalizedConstraints fresh(empty); + fresh.mGroupId = c.mGroupId; + if (!SomeSettingsFit(fresh, aDevices)) { + return "groupId"; + } + } + { + NormalizedConstraints fresh(empty); + fresh.mWidth = c.mWidth; + if (!SomeSettingsFit(fresh, aDevices)) { + return "width"; + } + } + { + NormalizedConstraints fresh(empty); + fresh.mHeight = c.mHeight; + if (!SomeSettingsFit(fresh, aDevices)) { + return "height"; + } + } + { + NormalizedConstraints fresh(empty); + fresh.mFrameRate = c.mFrameRate; + if (!SomeSettingsFit(fresh, aDevices)) { + return "frameRate"; + } + } + { + NormalizedConstraints fresh(empty); + fresh.mFacingMode = c.mFacingMode; + if (!SomeSettingsFit(fresh, aDevices)) { + return "facingMode"; + } + } + return ""; +} + +/* static */ +const char* MediaConstraintsHelper::FindBadConstraint( + const NormalizedConstraints& aConstraints, + const MediaDevice* aMediaDevice) { + NormalizedConstraints c(aConstraints); + NormalizedConstraints empty((dom::MediaTrackConstraints())); + c.mDeviceId = empty.mDeviceId; + c.mGroupId = empty.mGroupId; + AutoTArray, 1> devices; + devices.EmplaceBack( + new LocalMediaDevice(aMediaDevice, u""_ns, u""_ns, u""_ns)); + return FindBadConstraint(c, devices); +} + +static void LogConstraintStringRange( + const NormalizedConstraintSet::StringRange& aRange) { + if (aRange.mExact.size() <= 1 && aRange.mIdeal.size() <= 1) { + LOG(" %s: { exact: [%s], ideal: [%s] }", aRange.mName, + (aRange.mExact.empty() + ? "" + : NS_ConvertUTF16toUTF8(*aRange.mExact.begin()).get()), + (aRange.mIdeal.empty() + ? "" + : NS_ConvertUTF16toUTF8(*aRange.mIdeal.begin()).get())); + } else { + LOG(" %s: { exact: [", aRange.mName); + for (auto& entry : aRange.mExact) { + LOG(" %s,", NS_ConvertUTF16toUTF8(entry).get()); + } + LOG(" ], ideal: ["); + for (auto& entry : aRange.mIdeal) { + LOG(" %s,", NS_ConvertUTF16toUTF8(entry).get()); + } + LOG(" ]}"); + } +} + +template +static void LogConstraintRange( + const NormalizedConstraintSet::Range& aRange) { + if (aRange.mIdeal.isSome()) { + LOG(" %s: { min: %d, max: %d, ideal: %d }", aRange.mName, aRange.mMin, + aRange.mMax, aRange.mIdeal.valueOr(0)); + } else { + LOG(" %s: { min: %d, max: %d }", aRange.mName, aRange.mMin, aRange.mMax); + } +} + +template <> +void LogConstraintRange(const NormalizedConstraintSet::Range& aRange) { + if (aRange.mIdeal.isSome()) { + LOG(" %s: { min: %f, max: %f, ideal: %f }", aRange.mName, aRange.mMin, + aRange.mMax, aRange.mIdeal.valueOr(0)); + } else { + LOG(" %s: { min: %f, max: %f }", aRange.mName, aRange.mMin, aRange.mMax); + } +} + +/* static */ +void MediaConstraintsHelper::LogConstraints( + const NormalizedConstraintSet& aConstraints) { + auto& c = aConstraints; + LOG("Constraints: {"); + LOG("%s", [&]() { + LogConstraintRange(c.mWidth); + LogConstraintRange(c.mHeight); + LogConstraintRange(c.mFrameRate); + LogConstraintStringRange(c.mMediaSource); + LogConstraintStringRange(c.mFacingMode); + LogConstraintStringRange(c.mDeviceId); + LogConstraintStringRange(c.mGroupId); + LogConstraintRange(c.mEchoCancellation); + LogConstraintRange(c.mAutoGainControl); + LogConstraintRange(c.mNoiseSuppression); + LogConstraintRange(c.mChannelCount); + return "}"; + }()); +} + +} // namespace mozilla diff --git a/dom/media/webrtc/MediaTrackConstraints.h b/dom/media/webrtc/MediaTrackConstraints.h new file mode 100644 index 0000000000..61e0ed85ea --- /dev/null +++ b/dom/media/webrtc/MediaTrackConstraints.h @@ -0,0 +1,371 @@ +/* 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/. */ + +// This file should not be included by other includes, as it contains code + +#ifndef MEDIATRACKCONSTRAINTS_H_ +#define MEDIATRACKCONSTRAINTS_H_ + +#include +#include +#include + +#include "mozilla/Attributes.h" +#include "mozilla/dom/MediaStreamTrackBinding.h" +#include "mozilla/dom/MediaTrackSupportedConstraintsBinding.h" + +namespace mozilla { + +class LocalMediaDevice; +class MediaDevice; + +template +static Enum StringToEnum(const EnumValuesStrings& aStrings, + const nsAString& aValue, Enum aDefaultValue) { + for (size_t i = 0; aStrings[i].value; i++) { + if (aValue.EqualsASCII(aStrings[i].value)) { + return Enum(i); + } + } + return aDefaultValue; +} + +// Helper classes for orthogonal constraints without interdependencies. +// Instead of constraining values, constrain the constraints themselves. +class NormalizedConstraintSet { + protected: + class BaseRange { + protected: + typedef BaseRange NormalizedConstraintSet::*MemberPtrType; + + BaseRange(MemberPtrType aMemberPtr, const char* aName, + nsTArray* aList) + : mName(aName) { + if (aList) { + aList->AppendElement(aMemberPtr); + } + } + virtual ~BaseRange() = default; + + public: + virtual bool Merge(const BaseRange& aOther) = 0; + virtual void FinalizeMerge() = 0; + + const char* mName; + }; + + typedef BaseRange NormalizedConstraintSet::*MemberPtrType; + + public: + template + class Range : public BaseRange { + public: + ValueType mMin, mMax; + Maybe mIdeal; + + Range(MemberPtrType aMemberPtr, const char* aName, ValueType aMin, + ValueType aMax, nsTArray* aList) + : BaseRange(aMemberPtr, aName, aList), + mMin(aMin), + mMax(aMax), + mMergeDenominator(0) {} + virtual ~Range() = default; + + template + void SetFrom(const ConstrainRange& aOther); + ValueType Clamp(ValueType n) const { + return std::max(mMin, std::min(n, mMax)); + } + ValueType Get(ValueType defaultValue) const { + return Clamp(mIdeal.valueOr(defaultValue)); + } + bool Intersects(const Range& aOther) const { + return mMax >= aOther.mMin && mMin <= aOther.mMax; + } + void Intersect(const Range& aOther) { + mMin = std::max(mMin, aOther.mMin); + if (Intersects(aOther)) { + mMax = std::min(mMax, aOther.mMax); + } else { + // If there is no intersection, we will down-scale or drop frame + mMax = std::max(mMax, aOther.mMax); + } + } + bool Merge(const Range& aOther) { + if (strcmp(mName, "width") != 0 && strcmp(mName, "height") != 0 && + strcmp(mName, "frameRate") != 0 && !Intersects(aOther)) { + return false; + } + Intersect(aOther); + + if (aOther.mIdeal.isSome()) { + // Ideal values, as stored, may be outside their min max range, so use + // clamped values in averaging, to avoid extreme outliers skewing + // results. + if (mIdeal.isNothing()) { + mIdeal.emplace(aOther.Get(0)); + mMergeDenominator = 1; + } else { + if (!mMergeDenominator) { + *mIdeal = Get(0); + mMergeDenominator = 1; + } + *mIdeal += aOther.Get(0); + mMergeDenominator++; + } + } + return true; + } + void FinalizeMerge() override { + if (mMergeDenominator) { + *mIdeal /= mMergeDenominator; + mMergeDenominator = 0; + } + } + void TakeHighestIdeal(const Range& aOther) { + if (aOther.mIdeal.isSome()) { + if (mIdeal.isNothing()) { + mIdeal.emplace(aOther.Get(0)); + } else { + *mIdeal = std::max(Get(0), aOther.Get(0)); + } + } + } + + private: + bool Merge(const BaseRange& aOther) override { + return Merge(static_cast(aOther)); + } + + uint32_t mMergeDenominator; + }; + + struct LongRange : public Range { + typedef LongRange NormalizedConstraintSet::*LongPtrType; + + LongRange(LongPtrType aMemberPtr, const char* aName, + const dom::Optional& aOther, + bool advanced, nsTArray* aList); + }; + + struct LongLongRange : public Range { + typedef LongLongRange NormalizedConstraintSet::*LongLongPtrType; + + LongLongRange(LongLongPtrType aMemberPtr, const char* aName, + const long long& aOther, nsTArray* aList); + }; + + struct DoubleRange : public Range { + typedef DoubleRange NormalizedConstraintSet::*DoublePtrType; + + DoubleRange( + DoublePtrType aMemberPtr, const char* aName, + const dom::Optional& aOther, + bool advanced, nsTArray* aList); + }; + + struct BooleanRange : public Range { + typedef BooleanRange NormalizedConstraintSet::*BooleanPtrType; + + BooleanRange( + BooleanPtrType aMemberPtr, const char* aName, + const dom::Optional& + aOther, + bool advanced, nsTArray* aList); + + BooleanRange(BooleanPtrType aMemberPtr, const char* aName, + const bool& aOther, nsTArray* aList) + : Range((MemberPtrType)aMemberPtr, aName, false, true, aList) { + mIdeal.emplace(aOther); + } + }; + + struct StringRange : public BaseRange { + typedef std::set ValueType; + ValueType mExact, mIdeal; + + typedef StringRange NormalizedConstraintSet::*StringPtrType; + + StringRange( + StringPtrType aMemberPtr, const char* aName, + const dom::Optional< + dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters>& + aOther, + bool advanced, nsTArray* aList); + + StringRange(StringPtrType aMemberPtr, const char* aName, + const dom::Optional& aOther, + nsTArray* aList) + : BaseRange((MemberPtrType)aMemberPtr, aName, aList) { + if (aOther.WasPassed()) { + mIdeal.insert(aOther.Value()); + } + } + + ~StringRange() = default; + + void SetFrom(const dom::ConstrainDOMStringParameters& aOther); + ValueType Clamp(const ValueType& n) const; + ValueType Get(const ValueType& defaultValue) const { + return Clamp(mIdeal.empty() ? defaultValue : mIdeal); + } + bool Intersects(const StringRange& aOther) const; + void Intersect(const StringRange& aOther); + bool Merge(const StringRange& aOther); + void FinalizeMerge() override {} + + private: + bool Merge(const BaseRange& aOther) override { + return Merge(static_cast(aOther)); + } + }; + + // All new constraints should be added here whether they use flattening or not + LongRange mWidth, mHeight; + DoubleRange mFrameRate; + StringRange mFacingMode; + StringRange mMediaSource; + LongLongRange mBrowserWindow; + StringRange mDeviceId; + StringRange mGroupId; + LongRange mViewportOffsetX, mViewportOffsetY, mViewportWidth, mViewportHeight; + BooleanRange mEchoCancellation, mNoiseSuppression, mAutoGainControl; + LongRange mChannelCount; + + private: + typedef NormalizedConstraintSet T; + + public: + NormalizedConstraintSet(const dom::MediaTrackConstraintSet& aOther, + bool advanced, + nsTArray* aList = nullptr) + : mWidth(&T::mWidth, "width", aOther.mWidth, advanced, aList), + mHeight(&T::mHeight, "height", aOther.mHeight, advanced, aList), + mFrameRate(&T::mFrameRate, "frameRate", aOther.mFrameRate, advanced, + aList), + mFacingMode(&T::mFacingMode, "facingMode", aOther.mFacingMode, advanced, + aList), + mMediaSource(&T::mMediaSource, "mediaSource", aOther.mMediaSource, + aList), + mBrowserWindow(&T::mBrowserWindow, "browserWindow", + aOther.mBrowserWindow.WasPassed() + ? aOther.mBrowserWindow.Value() + : 0, + aList), + mDeviceId(&T::mDeviceId, "deviceId", aOther.mDeviceId, advanced, aList), + mGroupId(&T::mGroupId, "groupId", aOther.mGroupId, advanced, aList), + mViewportOffsetX(&T::mViewportOffsetX, "viewportOffsetX", + aOther.mViewportOffsetX, advanced, aList), + mViewportOffsetY(&T::mViewportOffsetY, "viewportOffsetY", + aOther.mViewportOffsetY, advanced, aList), + mViewportWidth(&T::mViewportWidth, "viewportWidth", + aOther.mViewportWidth, advanced, aList), + mViewportHeight(&T::mViewportHeight, "viewportHeight", + aOther.mViewportHeight, advanced, aList), + mEchoCancellation(&T::mEchoCancellation, "echoCancellation", + aOther.mEchoCancellation, advanced, aList), + mNoiseSuppression(&T::mNoiseSuppression, "noiseSuppression", + aOther.mNoiseSuppression, advanced, aList), + mAutoGainControl(&T::mAutoGainControl, "autoGainControl", + aOther.mAutoGainControl, advanced, aList), + mChannelCount(&T::mChannelCount, "channelCount", aOther.mChannelCount, + advanced, aList) {} +}; + +template <> +bool NormalizedConstraintSet::Range::Merge(const Range& aOther); +template <> +void NormalizedConstraintSet::Range::FinalizeMerge(); + +// Used instead of MediaTrackConstraints in lower-level code. +struct NormalizedConstraints : public NormalizedConstraintSet { + explicit NormalizedConstraints(const dom::MediaTrackConstraints& aOther, + nsTArray* aList = nullptr); + + std::vector mAdvanced; + const char* mBadConstraint; +}; + +// Flattened version is used in low-level code with orthogonal constraints only. +struct FlattenedConstraints : public NormalizedConstraintSet { + explicit FlattenedConstraints(const NormalizedConstraints& aOther); + + explicit FlattenedConstraints(const dom::MediaTrackConstraints& aOther) + : FlattenedConstraints(NormalizedConstraints(aOther)) {} +}; + +// A helper class for MediaEngineSources +class MediaConstraintsHelper { + public: + template + static uint32_t FitnessDistance(ValueType aN, const NormalizedRange& aRange) { + if (aRange.mMin > aN || aRange.mMax < aN) { + return UINT32_MAX; + } + if (aN == aRange.mIdeal.valueOr(aN)) { + return 0; + } + return uint32_t( + ValueType((std::abs(aN - aRange.mIdeal.value()) * 1000) / + std::max(std::abs(aN), std::abs(aRange.mIdeal.value())))); + } + + template + static uint32_t FeasibilityDistance(ValueType aN, + const NormalizedRange& aRange) { + if (aRange.mMin > aN) { + return UINT32_MAX; + } + // We prefer larger resolution because now we support downscaling + if (aN == aRange.mIdeal.valueOr(aN)) { + return 0; + } + + if (aN > aRange.mIdeal.value()) { + return uint32_t( + ValueType((std::abs(aN - aRange.mIdeal.value()) * 1000) / + std::max(std::abs(aN), std::abs(aRange.mIdeal.value())))); + } + + return 10000 + + uint32_t(ValueType( + (std::abs(aN - aRange.mIdeal.value()) * 1000) / + std::max(std::abs(aN), std::abs(aRange.mIdeal.value())))); + } + + static uint32_t FitnessDistance( + const Maybe& aN, + const NormalizedConstraintSet::StringRange& aParams); + + protected: + static bool SomeSettingsFit( + const NormalizedConstraints& aConstraints, + const nsTArray>& aDevices); + + public: + static uint32_t GetMinimumFitnessDistance( + const NormalizedConstraintSet& aConstraints, const nsString& aDeviceId, + const nsString& aGroupId); + + // Apply constrains to a supplied list of devices (removes items from the + // list) + static const char* SelectSettings( + const NormalizedConstraints& aConstraints, + nsTArray>& aDevices, + dom::CallerType aCallerType); + + static const char* FindBadConstraint( + const NormalizedConstraints& aConstraints, + const nsTArray>& aDevices); + + static const char* FindBadConstraint( + const NormalizedConstraints& aConstraints, + const MediaDevice* aMediaDevice); + + static void LogConstraints(const NormalizedConstraintSet& aConstraints); +}; + +} // namespace mozilla + +#endif /* MEDIATRACKCONSTRAINTS_H_ */ diff --git a/dom/media/webrtc/MediaTransportChild.h b/dom/media/webrtc/MediaTransportChild.h new file mode 100644 index 0000000000..b84dce70b2 --- /dev/null +++ b/dom/media/webrtc/MediaTransportChild.h @@ -0,0 +1,38 @@ +/* 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/. */ + +#ifndef _MTRANSPORTCHILD_H__ +#define _MTRANSPORTCHILD_H__ + +#include "mozilla/dom/PMediaTransportChild.h" + +namespace mozilla { +class MediaTransportHandlerIPC; + +class MediaTransportChild : public dom::PMediaTransportChild { + public: +#ifdef MOZ_WEBRTC + explicit MediaTransportChild(MediaTransportHandlerIPC* aUser); + virtual ~MediaTransportChild(); + mozilla::ipc::IPCResult RecvOnCandidate(const string& transportId, + const CandidateInfo& candidateInfo); + mozilla::ipc::IPCResult RecvOnAlpnNegotiated(const string& alpn); + mozilla::ipc::IPCResult RecvOnGatheringStateChange(const int& state); + mozilla::ipc::IPCResult RecvOnConnectionStateChange(const int& state); + mozilla::ipc::IPCResult RecvOnPacketReceived(const string& transportId, + const MediaPacket& packet); + mozilla::ipc::IPCResult RecvOnEncryptedSending(const string& transportId, + const MediaPacket& packet); + mozilla::ipc::IPCResult RecvOnStateChange(const string& transportId, + const int& state); + mozilla::ipc::IPCResult RecvOnRtcpStateChange(const string& transportId, + const int& state); + + private: + RefPtr mUser; +#endif // MOZ_WEBRTC +}; + +} // namespace mozilla +#endif diff --git a/dom/media/webrtc/MediaTransportParent.h b/dom/media/webrtc/MediaTransportParent.h new file mode 100644 index 0000000000..8c698d0f54 --- /dev/null +++ b/dom/media/webrtc/MediaTransportParent.h @@ -0,0 +1,69 @@ +/* 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/. */ + +#ifndef _MTRANSPORTHANDLER_PARENT_H__ +#define _MTRANSPORTHANDLER_PARENT_H__ + +#include "mozilla/dom/PMediaTransportParent.h" +#include + +namespace mozilla { + +class MediaTransportParent : public dom::PMediaTransportParent { + public: +#ifdef MOZ_WEBRTC + MediaTransportParent(); + virtual ~MediaTransportParent(); + + mozilla::ipc::IPCResult RecvGetIceLog(const nsCString& pattern, + GetIceLogResolver&& aResolve); + mozilla::ipc::IPCResult RecvClearIceLog(); + mozilla::ipc::IPCResult RecvEnterPrivateMode(); + mozilla::ipc::IPCResult RecvExitPrivateMode(); + mozilla::ipc::IPCResult RecvCreateIceCtx(const string& name); + mozilla::ipc::IPCResult RecvSetIceConfig( + nsTArray&& iceServers, + const RTCIceTransportPolicy& icePolicy); + mozilla::ipc::IPCResult RecvSetProxyConfig( + const net::WebrtcProxyConfig& aProxyConfig); + mozilla::ipc::IPCResult RecvEnsureProvisionalTransport( + const string& transportId, const string& localUfrag, + const string& localPwd, const int& componentCount); + mozilla::ipc::IPCResult RecvSetTargetForDefaultLocalAddressLookup( + const string& targetIp, uint16_t targetPort); + mozilla::ipc::IPCResult RecvStartIceGathering( + const bool& defaultRouteOnly, const bool& obfuscateAddresses, + const net::NrIceStunAddrArray& stunAddrs); + mozilla::ipc::IPCResult RecvActivateTransport( + const string& transportId, const string& localUfrag, + const string& localPwd, const int& componentCount, + const string& remoteUfrag, const string& remotePwd, + nsTArray&& keyDer, nsTArray&& certDer, + const int& authType, const bool& dtlsClient, + const DtlsDigestList& digests, const bool& privacyRequested); + mozilla::ipc::IPCResult RecvRemoveTransportsExcept( + const StringVector& transportIds); + mozilla::ipc::IPCResult RecvStartIceChecks(const bool& isControlling, + const StringVector& iceOptions); + mozilla::ipc::IPCResult RecvSendPacket(const string& transportId, + MediaPacket&& packet); + mozilla::ipc::IPCResult RecvAddIceCandidate(const string& transportId, + const string& candidate, + const string& ufrag, + const string& obfuscatedAddress); + mozilla::ipc::IPCResult RecvUpdateNetworkState(const bool& online); + mozilla::ipc::IPCResult RecvGetIceStats(const string& transportId, + const double& now, + GetIceStatsResolver&& aResolve); + + void ActorDestroy(ActorDestroyReason aWhy); + + private: + // Hide the sigslot/MediaTransportHandler stuff from IPC. + class Impl; + std::unique_ptr mImpl; +#endif // MOZ_WEBRTC +}; +} // namespace mozilla +#endif //_MTRANSPORTHANDLER_PARENT_H__ diff --git a/dom/media/webrtc/PMediaTransport.ipdl b/dom/media/webrtc/PMediaTransport.ipdl new file mode 100644 index 0000000000..b754d34ad2 --- /dev/null +++ b/dom/media/webrtc/PMediaTransport.ipdl @@ -0,0 +1,105 @@ +/* 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 protocol PBackground; + +#ifdef MOZ_WEBRTC +include WebrtcProxyConfig; + +// ParamTraits stuff for generated code and other classes we don't want to change +include "mozilla/net/NrIceStunAddrMessageUtils.h"; +include "mozilla/media/webrtc/WebrtcIPCTraits.h"; +using mozilla::StringVector from "mozilla/media/webrtc/WebrtcIPCTraits.h"; +using mozilla::CandidateInfo from "mozilla/media/webrtc/WebrtcIPCTraits.h"; +using mozilla::DtlsDigestList from "mozilla/media/webrtc/WebrtcIPCTraits.h"; +using std::string from "string"; +using struct mozilla::dom::RTCStatsCollection from "mozilla/dom/RTCStatsReportBinding.h"; +using WebrtcGlobalLog from "mozilla/media/webrtc/WebrtcGlobal.h"; +using mozilla::dom::RTCIceServer from "mozilla/dom/RTCConfigurationBinding.h"; +using mozilla::dom::RTCIceTransportPolicy from "mozilla/dom/RTCConfigurationBinding.h"; + +// ParamTraits stuff for our own classes +using mozilla::MediaPacket from "transport/mediapacket.h"; +include "mozilla/net/NrIceStunAddrMessageUtils.h"; +using mozilla::net::NrIceStunAddrArray from "mozilla/net/PStunAddrsParams.h"; +#endif // MOZ_WEBRTC + +namespace mozilla { +namespace dom { + +[ManualDealloc] +async protocol PMediaTransport { + manager PBackground; + +parent: + async __delete__(); + +#ifdef MOZ_WEBRTC + async GetIceLog(nsCString pattern) returns (WebrtcGlobalLog loglines); + async ClearIceLog(); + async EnterPrivateMode(); + async ExitPrivateMode(); + + async CreateIceCtx(string name); + + async SetIceConfig(RTCIceServer[] iceServers, + RTCIceTransportPolicy icePolicy); + + async SetProxyConfig(WebrtcProxyConfig proxyConfig); + + async EnsureProvisionalTransport(string transportId, + string localUfrag, + string localPwd, + int componentCount); + + async SetTargetForDefaultLocalAddressLookup(string targetIp, + uint16_t targetPort); + + async StartIceGathering(bool defaultRouteOnly, + bool obfuscateHostAddresses, + NrIceStunAddrArray stunAddrs); + + async ActivateTransport(string transportId, + string localUfrag, + string localPwd, + int componentCount, + string remoteUfrag, + string remotePwd, + uint8_t[] keyDer, + uint8_t[] certDer, + int authType, + bool dtlsClient, + DtlsDigestList digests, + bool privacyRequested); + + async RemoveTransportsExcept(StringVector transportIds); + + async StartIceChecks(bool isControlling, + StringVector iceOptions); + + async SendPacket(string transportId, MediaPacket packet); + + async AddIceCandidate(string transportId, + string candidate, + string ufrag, + string obfuscatedAddr); + + async UpdateNetworkState(bool online); + + async GetIceStats(string transportId, double now) returns (UniquePtr stats); + +child: + async OnCandidate(string transportId, CandidateInfo candidateInfo); + async OnAlpnNegotiated(string alpn); + async OnGatheringStateChange(int state); + async OnConnectionStateChange(int state); + async OnPacketReceived(string transportId, MediaPacket packet); + async OnEncryptedSending(string transportId, MediaPacket packet); + async OnStateChange(string transportId, int state); + async OnRtcpStateChange(string transportId, int state); + +#endif // MOZ_WEBRTC +}; +} // namespace dom +} // namespace mozilla diff --git a/dom/media/webrtc/PWebrtcGlobal.ipdl b/dom/media/webrtc/PWebrtcGlobal.ipdl new file mode 100644 index 0000000000..e274b23636 --- /dev/null +++ b/dom/media/webrtc/PWebrtcGlobal.ipdl @@ -0,0 +1,41 @@ +/* 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 protocol PContent; + +#ifdef MOZ_WEBRTC +include "mozilla/media/webrtc/WebrtcGlobal.h"; + +using struct mozilla::dom::RTCStatsReportInternal from "mozilla/dom/RTCStatsReportBinding.h"; +using WebrtcGlobalLog from "mozilla/media/webrtc/WebrtcGlobal.h"; +#endif + +namespace mozilla { +namespace dom { + +[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual] +async protocol PWebrtcGlobal { + manager PContent; + +parent: // child -> parent messages + async __delete__(); +#ifdef MOZ_WEBRTC + async PeerConnectionCreated(nsString aPcId, bool aIsLongTermStatsDisabled); + async PeerConnectionDestroyed(nsString aPcId); + async PeerConnectionFinalStats(RTCStatsReportInternal aFinalStats); + +child: // parent -> child messages + async GetStats(nsString aPcIdFilter) returns (RTCStatsReportInternal[] stats); + async ClearStats(); + async GetLog() returns (WebrtcGlobalLog logs); + async ClearLog(); + async SetAecLogging(bool aEnable); + async SetDebugMode(int aLevel); +#endif +}; + +} // end namespace net +} // end namespace mozilla + + diff --git a/dom/media/webrtc/PeerIdentity.cpp b/dom/media/webrtc/PeerIdentity.cpp new file mode 100644 index 0000000000..d36fea5ea3 --- /dev/null +++ b/dom/media/webrtc/PeerIdentity.cpp @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 sts=2 expandtab + * 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 "PeerIdentity.h" + +#include "mozilla/DebugOnly.h" +#include "nsCOMPtr.h" +#include "nsIIDNService.h" +#include "nsNetCID.h" +#include "nsServiceManagerUtils.h" + +namespace mozilla { + +bool PeerIdentity::Equals(const PeerIdentity& aOther) const { + return Equals(aOther.mPeerIdentity); +} + +bool PeerIdentity::Equals(const nsAString& aOtherString) const { + nsString user; + GetUser(mPeerIdentity, user); + nsString otherUser; + GetUser(aOtherString, otherUser); + if (user != otherUser) { + return false; + } + + nsString host; + GetHost(mPeerIdentity, host); + nsString otherHost; + GetHost(aOtherString, otherHost); + + nsresult rv; + nsCOMPtr idnService = + do_GetService("@mozilla.org/network/idn-service;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return host == otherHost; + } + + nsCString normHost; + GetNormalizedHost(idnService, host, normHost); + nsCString normOtherHost; + GetNormalizedHost(idnService, otherHost, normOtherHost); + return normHost == normOtherHost; +} + +/* static */ +void PeerIdentity::GetUser(const nsAString& aPeerIdentity, nsAString& aUser) { + int32_t at = aPeerIdentity.FindChar('@'); + if (at >= 0) { + aUser = Substring(aPeerIdentity, 0, at); + } else { + aUser.Truncate(); + } +} + +/* static */ +void PeerIdentity::GetHost(const nsAString& aPeerIdentity, nsAString& aHost) { + int32_t at = aPeerIdentity.FindChar('@'); + if (at >= 0) { + aHost = Substring(aPeerIdentity, at + 1); + } else { + aHost = aPeerIdentity; + } +} + +/* static */ +void PeerIdentity::GetNormalizedHost(const nsCOMPtr& aIdnService, + const nsAString& aHost, + nsACString& aNormalizedHost) { + const nsCString chost = NS_ConvertUTF16toUTF8(aHost); + DebugOnly rv = + aIdnService->ConvertUTF8toACE(chost, aNormalizedHost); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "Failed to convert UTF-8 host to ASCII"); +} + +} /* namespace mozilla */ diff --git a/dom/media/webrtc/PeerIdentity.h b/dom/media/webrtc/PeerIdentity.h new file mode 100644 index 0000000000..64364d3359 --- /dev/null +++ b/dom/media/webrtc/PeerIdentity.h @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 sts=2 expandtab + * 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/. */ + +#ifndef PeerIdentity_h +#define PeerIdentity_h + +#include "nsISupportsImpl.h" +#include "nsString.h" + +template +class nsCOMPtr; +class nsIIDNService; + +namespace mozilla { + +/** + * This class implements the identifier used in WebRTC identity. Peers are + * identified using a string in the form [@], for instance, + * "user@example.com'. The (optional) user portion is a site-controlled string + * containing any character other than '@'. The domain portion is a valid IDN + * domain name and is compared accordingly. + * + * See: + * http://tools.ietf.org/html/draft-ietf-rtcweb-security-arch-09#section-5.6.5.3.3.1 + */ +class PeerIdentity final { + public: + NS_INLINE_DECL_REFCOUNTING(PeerIdentity) + + explicit PeerIdentity(const nsAString& aPeerIdentity) + : mPeerIdentity(aPeerIdentity) {} + + bool Equals(const PeerIdentity& aOther) const; + bool Equals(const nsAString& aOtherString) const; + const nsString& ToString() const { return mPeerIdentity; } + + private: + ~PeerIdentity() = default; + static void GetUser(const nsAString& aPeerIdentity, nsAString& aUser); + static void GetHost(const nsAString& aPeerIdentity, nsAString& aHost); + + static void GetNormalizedHost(const nsCOMPtr& aIdnService, + const nsAString& aHost, + nsACString& aNormalizedHost); + + nsString mPeerIdentity; +}; + +inline bool operator==(const PeerIdentity& aOne, const PeerIdentity& aTwo) { + return aOne.Equals(aTwo); +} + +inline bool operator==(const PeerIdentity& aOne, const nsAString& aString) { + return aOne.Equals(aString); +} + +inline bool operator!=(const PeerIdentity& aOne, const PeerIdentity& aTwo) { + return !aOne.Equals(aTwo); +} + +inline bool operator!=(const PeerIdentity& aOne, const nsAString& aString) { + return !aOne.Equals(aString); +} + +} /* namespace mozilla */ + +#endif /* PeerIdentity_h */ diff --git a/dom/media/webrtc/RTCCertificate.cpp b/dom/media/webrtc/RTCCertificate.cpp new file mode 100644 index 0000000000..69b6b0f563 --- /dev/null +++ b/dom/media/webrtc/RTCCertificate.cpp @@ -0,0 +1,438 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/RTCCertificate.h" + +#include +#include +#include +#include +#include +#include "ErrorList.h" +#include "MainThreadUtils.h" +#include "cert.h" +#include "cryptohi.h" +#include "js/StructuredClone.h" +#include "js/TypeDecls.h" +#include "js/Value.h" +#include "keyhi.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/MacroForEach.h" +#include "mozilla/OwningNonNull.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/CryptoBuffer.h" +#include "mozilla/dom/CryptoKey.h" +#include "mozilla/dom/KeyAlgorithmBinding.h" +#include "mozilla/dom/KeyAlgorithmProxy.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/RTCCertificateBinding.h" +#include "mozilla/dom/StructuredCloneHolder.h" +#include "mozilla/dom/SubtleCryptoBinding.h" +#include "mozilla/dom/UnionTypes.h" +#include "mozilla/dom/WebCryptoCommon.h" +#include "mozilla/dom/WebCryptoTask.h" +#include "mozilla/fallible.h" +#include "nsDebug.h" +#include "nsError.h" +#include "nsLiteralString.h" +#include "nsStringFlags.h" +#include "nsStringFwd.h" +#include "nsTLiteralString.h" +#include "pk11pub.h" +#include "plarena.h" +#include "secasn1.h" +#include "secasn1t.h" +#include "seccomon.h" +#include "secmodt.h" +#include "secoid.h" +#include "secoidt.h" +#include "transport/dtlsidentity.h" +#include "xpcpublic.h" + +namespace mozilla::dom { + +#define RTCCERTIFICATE_SC_VERSION 0x00000001 + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(RTCCertificate, mGlobal) +NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCCertificate) +NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCCertificate) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCCertificate) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +// Note: explicit casts necessary to avoid +// warning C4307: '*' : integral constant overflow +#define ONE_DAY \ + PRTime(PR_USEC_PER_SEC) * PRTime(60) /*sec*/ \ + * PRTime(60) /*min*/ * PRTime(24) /*hours*/ +#define EXPIRATION_DEFAULT ONE_DAY* PRTime(30) +#define EXPIRATION_SLACK ONE_DAY +#define EXPIRATION_MAX ONE_DAY* PRTime(365) /*year*/ + +const size_t RTCCertificateCommonNameLength = 16; +const size_t RTCCertificateMinRsaSize = 1024; + +class GenerateRTCCertificateTask : public GenerateAsymmetricKeyTask { + public: + GenerateRTCCertificateTask(nsIGlobalObject* aGlobal, JSContext* aCx, + const ObjectOrString& aAlgorithm, + const Sequence& aKeyUsages, + PRTime aExpires) + : GenerateAsymmetricKeyTask(aGlobal, aCx, aAlgorithm, true, aKeyUsages), + mExpires(aExpires), + mAuthType(ssl_kea_null), + mCertificate(nullptr), + mSignatureAlg(SEC_OID_UNKNOWN) { + if (NS_FAILED(mEarlyRv)) { + // webrtc-pc says to throw NotSupportedError if we have passed "an + // algorithm that the user agent cannot or will not use to generate a + // certificate". This catches these cases. + mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR; + } + } + + private: + PRTime mExpires; + SSLKEAType mAuthType; + UniqueCERTCertificate mCertificate; + SECOidTag mSignatureAlg; + + static CERTName* GenerateRandomName(PK11SlotInfo* aSlot) { + uint8_t randomName[RTCCertificateCommonNameLength]; + SECStatus rv = + PK11_GenerateRandomOnSlot(aSlot, randomName, sizeof(randomName)); + if (rv != SECSuccess) { + return nullptr; + } + + char buf[sizeof(randomName) * 2 + 4]; + strncpy(buf, "CN=", 4); + for (size_t i = 0; i < sizeof(randomName); ++i) { + snprintf(&buf[i * 2 + 3], 3, "%.2x", randomName[i]); + } + buf[sizeof(buf) - 1] = '\0'; + + return CERT_AsciiToName(buf); + } + + nsresult GenerateCertificate() { + UniquePK11SlotInfo slot(PK11_GetInternalSlot()); + MOZ_ASSERT(slot.get()); + + UniqueCERTName subjectName(GenerateRandomName(slot.get())); + if (!subjectName) { + return NS_ERROR_DOM_UNKNOWN_ERR; + } + + UniqueSECKEYPublicKey publicKey(mKeyPair->mPublicKey->GetPublicKey()); + UniqueCERTSubjectPublicKeyInfo spki( + SECKEY_CreateSubjectPublicKeyInfo(publicKey.get())); + if (!spki) { + return NS_ERROR_DOM_UNKNOWN_ERR; + } + + UniqueCERTCertificateRequest certreq( + CERT_CreateCertificateRequest(subjectName.get(), spki.get(), nullptr)); + if (!certreq) { + return NS_ERROR_DOM_UNKNOWN_ERR; + } + + PRTime now = PR_Now(); + PRTime notBefore = now - EXPIRATION_SLACK; + mExpires += now; + + UniqueCERTValidity validity(CERT_CreateValidity(notBefore, mExpires)); + if (!validity) { + return NS_ERROR_DOM_UNKNOWN_ERR; + } + + unsigned long serial; + // Note: This serial in principle could collide, but it's unlikely, and we + // don't expect anyone to be validating certificates anyway. + SECStatus rv = PK11_GenerateRandomOnSlot( + slot.get(), reinterpret_cast(&serial), sizeof(serial)); + if (rv != SECSuccess) { + return NS_ERROR_DOM_UNKNOWN_ERR; + } + + // NB: CERTCertificates created with CERT_CreateCertificate are not safe to + // use with other NSS functions like CERT_DupCertificate. The strategy + // here is to create a tbsCertificate ("to-be-signed certificate"), encode + // it, and sign it, resulting in a signed DER certificate that can be + // decoded into a CERTCertificate. + UniqueCERTCertificate tbsCertificate(CERT_CreateCertificate( + serial, subjectName.get(), validity.get(), certreq.get())); + if (!tbsCertificate) { + return NS_ERROR_DOM_UNKNOWN_ERR; + } + + MOZ_ASSERT(mSignatureAlg != SEC_OID_UNKNOWN); + PLArenaPool* arena = tbsCertificate->arena; + + rv = SECOID_SetAlgorithmID(arena, &tbsCertificate->signature, mSignatureAlg, + nullptr); + if (rv != SECSuccess) { + return NS_ERROR_DOM_UNKNOWN_ERR; + } + + // Set version to X509v3. + *(tbsCertificate->version.data) = SEC_CERTIFICATE_VERSION_3; + tbsCertificate->version.len = 1; + + SECItem innerDER = {siBuffer, nullptr, 0}; + if (!SEC_ASN1EncodeItem(arena, &innerDER, tbsCertificate.get(), + SEC_ASN1_GET(CERT_CertificateTemplate))) { + return NS_ERROR_DOM_UNKNOWN_ERR; + } + + SECItem* certDer = PORT_ArenaZNew(arena, SECItem); + if (!certDer) { + return NS_ERROR_DOM_UNKNOWN_ERR; + } + + UniqueSECKEYPrivateKey privateKey(mKeyPair->mPrivateKey->GetPrivateKey()); + rv = SEC_DerSignData(arena, certDer, innerDER.data, innerDER.len, + privateKey.get(), mSignatureAlg); + if (rv != SECSuccess) { + return NS_ERROR_DOM_UNKNOWN_ERR; + } + + mCertificate.reset(CERT_NewTempCertificate(CERT_GetDefaultCertDB(), certDer, + nullptr, false, true)); + if (!mCertificate) { + return NS_ERROR_DOM_UNKNOWN_ERR; + } + return NS_OK; + } + + nsresult BeforeCrypto() override { + if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) { + // Double check that size is OK. + auto sz = static_cast(mRsaParams.keySizeInBits); + if (sz < RTCCertificateMinRsaSize) { + return NS_ERROR_DOM_NOT_SUPPORTED_ERR; + } + + KeyAlgorithmProxy& alg = mKeyPair->mPublicKey->Algorithm(); + if (alg.mType != KeyAlgorithmProxy::RSA || + !alg.mRsa.mHash.mName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) { + return NS_ERROR_DOM_NOT_SUPPORTED_ERR; + } + + mSignatureAlg = SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION; + mAuthType = ssl_kea_rsa; + + } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) { + // We only support good curves in WebCrypto. + // If that ever changes, check that a good one was chosen. + + mSignatureAlg = SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE; + mAuthType = ssl_kea_ecdh; + } else { + return NS_ERROR_DOM_NOT_SUPPORTED_ERR; + } + return NS_OK; + } + + nsresult DoCrypto() override { + nsresult rv = GenerateAsymmetricKeyTask::DoCrypto(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GenerateCertificate(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; + } + + virtual void Resolve() override { + // Make copies of the private key and certificate, otherwise, when this + // object is deleted, the structures they reference will be deleted too. + UniqueSECKEYPrivateKey key = mKeyPair->mPrivateKey->GetPrivateKey(); + CERTCertificate* cert = CERT_DupCertificate(mCertificate.get()); + RefPtr result = + new RTCCertificate(mResultPromise->GetParentObject(), key.release(), + cert, mAuthType, mExpires); + mResultPromise->MaybeResolve(result); + } +}; + +static PRTime ReadExpires(JSContext* aCx, const ObjectOrString& aOptions, + ErrorResult& aRv) { + // This conversion might fail, but we don't really care; use the default. + // If this isn't an object, or it doesn't coerce into the right type, + // then we won't get the |expires| value. Either will be caught later. + RTCCertificateExpiration expiration; + if (!aOptions.IsObject()) { + return EXPIRATION_DEFAULT; + } + JS::Rooted value(aCx, JS::ObjectValue(*aOptions.GetAsObject())); + if (!expiration.Init(aCx, value)) { + aRv.NoteJSContextException(aCx); + return 0; + } + + if (!expiration.mExpires.WasPassed()) { + return EXPIRATION_DEFAULT; + } + static const uint64_t max = + static_cast(EXPIRATION_MAX / PR_USEC_PER_MSEC); + if (expiration.mExpires.Value() > max) { + return EXPIRATION_MAX; + } + return static_cast(expiration.mExpires.Value() * PR_USEC_PER_MSEC); +} + +already_AddRefed RTCCertificate::GenerateCertificate( + const GlobalObject& aGlobal, const ObjectOrString& aOptions, + ErrorResult& aRv, JS::Compartment* aCompartment) { + nsIGlobalObject* global = xpc::NativeGlobal(aGlobal.Get()); + RefPtr p = Promise::Create(global, aRv); + if (aRv.Failed()) { + return nullptr; + } + Sequence usages; + if (!usages.AppendElement(u"sign"_ns, fallible)) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + + PRTime expires = ReadExpires(aGlobal.Context(), aOptions, aRv); + if (aRv.Failed()) { + return nullptr; + } + RefPtr task = new GenerateRTCCertificateTask( + global, aGlobal.Context(), aOptions, usages, expires); + task->DispatchWithPromise(p); + return p.forget(); +} + +RTCCertificate::RTCCertificate(nsIGlobalObject* aGlobal) + : mGlobal(aGlobal), + mPrivateKey(nullptr), + mCertificate(nullptr), + mAuthType(ssl_kea_null), + mExpires(0) {} + +RTCCertificate::RTCCertificate(nsIGlobalObject* aGlobal, + SECKEYPrivateKey* aPrivateKey, + CERTCertificate* aCertificate, + SSLKEAType aAuthType, PRTime aExpires) + : mGlobal(aGlobal), + mPrivateKey(aPrivateKey), + mCertificate(aCertificate), + mAuthType(aAuthType), + mExpires(aExpires) {} + +RefPtr RTCCertificate::CreateDtlsIdentity() const { + if (!mPrivateKey || !mCertificate) { + return nullptr; + } + UniqueSECKEYPrivateKey key(SECKEY_CopyPrivateKey(mPrivateKey.get())); + UniqueCERTCertificate cert(CERT_DupCertificate(mCertificate.get())); + RefPtr id = + new DtlsIdentity(std::move(key), std::move(cert), mAuthType); + return id; +} + +JSObject* RTCCertificate::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + return RTCCertificate_Binding::Wrap(aCx, this, aGivenProto); +} + +bool RTCCertificate::WritePrivateKey(JSStructuredCloneWriter* aWriter) const { + JsonWebKey jwk; + nsresult rv = CryptoKey::PrivateKeyToJwk(mPrivateKey.get(), jwk); + if (NS_FAILED(rv)) { + return false; + } + nsString json; + if (!jwk.ToJSON(json)) { + return false; + } + return StructuredCloneHolder::WriteString(aWriter, json); +} + +bool RTCCertificate::WriteCertificate(JSStructuredCloneWriter* aWriter) const { + UniqueCERTCertificateList certs(CERT_CertListFromCert(mCertificate.get())); + if (!certs || certs->len <= 0) { + return false; + } + if (!JS_WriteUint32Pair(aWriter, certs->certs[0].len, 0)) { + return false; + } + return JS_WriteBytes(aWriter, certs->certs[0].data, certs->certs[0].len); +} + +bool RTCCertificate::WriteStructuredClone( + JSContext* aCx, JSStructuredCloneWriter* aWriter) const { + if (!mPrivateKey || !mCertificate) { + return false; + } + + return JS_WriteUint32Pair(aWriter, RTCCERTIFICATE_SC_VERSION, mAuthType) && + JS_WriteUint32Pair(aWriter, (mExpires >> 32) & 0xffffffff, + mExpires & 0xffffffff) && + WritePrivateKey(aWriter) && WriteCertificate(aWriter); +} + +bool RTCCertificate::ReadPrivateKey(JSStructuredCloneReader* aReader) { + nsString json; + if (!StructuredCloneHolder::ReadString(aReader, json)) { + return false; + } + JsonWebKey jwk; + if (!jwk.Init(json)) { + return false; + } + mPrivateKey = CryptoKey::PrivateKeyFromJwk(jwk); + return !!mPrivateKey; +} + +bool RTCCertificate::ReadCertificate(JSStructuredCloneReader* aReader) { + CryptoBuffer cert; + if (!ReadBuffer(aReader, cert) || cert.Length() == 0) { + return false; + } + + SECItem der = {siBuffer, cert.Elements(), + static_cast(cert.Length())}; + mCertificate.reset(CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &der, + nullptr, true, true)); + return !!mCertificate; +} + +// static +already_AddRefed RTCCertificate::ReadStructuredClone( + JSContext* aCx, nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader) { + if (!NS_IsMainThread()) { + // These objects are mainthread-only. + return nullptr; + } + uint32_t version, authType; + if (!JS_ReadUint32Pair(aReader, &version, &authType) || + version != RTCCERTIFICATE_SC_VERSION) { + return nullptr; + } + RefPtr cert = new RTCCertificate(aGlobal); + cert->mAuthType = static_cast(authType); + + uint32_t high, low; + if (!JS_ReadUint32Pair(aReader, &high, &low)) { + return nullptr; + } + cert->mExpires = static_cast(high) << 32 | low; + + if (!cert->ReadPrivateKey(aReader) || !cert->ReadCertificate(aReader)) { + return nullptr; + } + + return cert.forget(); +} + +} // namespace mozilla::dom diff --git a/dom/media/webrtc/RTCCertificate.h b/dom/media/webrtc/RTCCertificate.h new file mode 100644 index 0000000000..a5cc6bde32 --- /dev/null +++ b/dom/media/webrtc/RTCCertificate.h @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_dom_RTCCertificate_h +#define mozilla_dom_RTCCertificate_h + +#include +#include "ScopedNSSTypes.h" +#include "certt.h" +#include "js/RootingAPI.h" +#include "keythi.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/RefPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIGlobalObject.h" +#include "nsISupports.h" +#include "nsWrapperCache.h" +#include "prtime.h" +#include "sslt.h" + +class JSObject; +struct JSContext; +struct JSStructuredCloneReader; +struct JSStructuredCloneWriter; + +namespace JS { +class Compartment; +} + +namespace mozilla { +class DtlsIdentity; +class ErrorResult; + +namespace dom { + +class GlobalObject; +class ObjectOrString; +class Promise; + +class RTCCertificate final : public nsISupports, public nsWrapperCache { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCCertificate) + + // WebIDL method that implements RTCPeerConnection.generateCertificate. + static already_AddRefed GenerateCertificate( + const GlobalObject& aGlobal, const ObjectOrString& aOptions, + ErrorResult& aRv, JS::Compartment* aCompartment = nullptr); + + explicit RTCCertificate(nsIGlobalObject* aGlobal); + RTCCertificate(nsIGlobalObject* aGlobal, SECKEYPrivateKey* aPrivateKey, + CERTCertificate* aCertificate, SSLKEAType aAuthType, + PRTime aExpires); + + nsIGlobalObject* GetParentObject() const { return mGlobal; } + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + // WebIDL expires attribute. Note: JS dates are milliseconds since epoch; + // NSPR PRTime is in microseconds since the same epoch. + uint64_t Expires() const { return mExpires / PR_USEC_PER_MSEC; } + + // Accessors for use by PeerConnectionImpl. + RefPtr CreateDtlsIdentity() const; + const UniqueCERTCertificate& Certificate() const { return mCertificate; } + + // Structured clone methods + bool WriteStructuredClone(JSContext* aCx, + JSStructuredCloneWriter* aWriter) const; + static already_AddRefed ReadStructuredClone( + JSContext* aCx, nsIGlobalObject* aGlobal, + JSStructuredCloneReader* aReader); + + private: + ~RTCCertificate() = default; + void operator=(const RTCCertificate&) = delete; + RTCCertificate(const RTCCertificate&) = delete; + + bool ReadCertificate(JSStructuredCloneReader* aReader); + bool ReadPrivateKey(JSStructuredCloneReader* aReader); + bool WriteCertificate(JSStructuredCloneWriter* aWriter) const; + bool WritePrivateKey(JSStructuredCloneWriter* aWriter) const; + + RefPtr mGlobal; + UniqueSECKEYPrivateKey mPrivateKey; + UniqueCERTCertificate mCertificate; + SSLKEAType mAuthType; + PRTime mExpires; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_RTCCertificate_h diff --git a/dom/media/webrtc/RTCIdentityProviderRegistrar.cpp b/dom/media/webrtc/RTCIdentityProviderRegistrar.cpp new file mode 100644 index 0000000000..44e4c0d775 --- /dev/null +++ b/dom/media/webrtc/RTCIdentityProviderRegistrar.cpp @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ +/* 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 "RTCIdentityProviderRegistrar.h" +#include "mozilla/Attributes.h" +#include "nsCycleCollectionParticipant.h" + +namespace mozilla::dom { + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCIdentityProviderRegistrar) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCIdentityProviderRegistrar) +NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCIdentityProviderRegistrar) + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(RTCIdentityProviderRegistrar, mGlobal, + mGenerateAssertionCallback, + mValidateAssertionCallback) + +RTCIdentityProviderRegistrar::RTCIdentityProviderRegistrar( + nsIGlobalObject* aGlobal) + : mGlobal(aGlobal), + mGenerateAssertionCallback(nullptr), + mValidateAssertionCallback(nullptr) {} + +RTCIdentityProviderRegistrar::~RTCIdentityProviderRegistrar() = default; + +nsIGlobalObject* RTCIdentityProviderRegistrar::GetParentObject() const { + return mGlobal; +} + +JSObject* RTCIdentityProviderRegistrar::WrapObject( + JSContext* aCx, JS::Handle aGivenProto) { + return RTCIdentityProviderRegistrar_Binding::Wrap(aCx, this, aGivenProto); +} + +void RTCIdentityProviderRegistrar::Register(const RTCIdentityProvider& aIdp) { + mGenerateAssertionCallback = aIdp.mGenerateAssertion; + mValidateAssertionCallback = aIdp.mValidateAssertion; +} + +bool RTCIdentityProviderRegistrar::HasIdp() const { + return mGenerateAssertionCallback && mValidateAssertionCallback; +} + +already_AddRefed RTCIdentityProviderRegistrar::GenerateAssertion( + const nsAString& aContents, const nsAString& aOrigin, + const RTCIdentityProviderOptions& aOptions, ErrorResult& aRv) { + if (!mGenerateAssertionCallback) { + aRv.Throw(NS_ERROR_NOT_INITIALIZED); + return nullptr; + } + RefPtr callback(mGenerateAssertionCallback); + return callback->Call(aContents, aOrigin, aOptions, aRv); +} +already_AddRefed RTCIdentityProviderRegistrar::ValidateAssertion( + const nsAString& aAssertion, const nsAString& aOrigin, ErrorResult& aRv) { + if (!mValidateAssertionCallback) { + aRv.Throw(NS_ERROR_NOT_INITIALIZED); + return nullptr; + } + RefPtr callback(mValidateAssertionCallback); + return callback->Call(aAssertion, aOrigin, aRv); +} + +} // namespace mozilla::dom diff --git a/dom/media/webrtc/RTCIdentityProviderRegistrar.h b/dom/media/webrtc/RTCIdentityProviderRegistrar.h new file mode 100644 index 0000000000..0510471f17 --- /dev/null +++ b/dom/media/webrtc/RTCIdentityProviderRegistrar.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ +/* 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/. */ + +#ifndef RTCIDENTITYPROVIDER_H_ +#define RTCIDENTITYPROVIDER_H_ + +#include "mozilla/RefPtr.h" +#include "nsCOMPtr.h" +#include "nsISupportsImpl.h" +#include "nsIGlobalObject.h" +#include "nsWrapperCache.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/RTCIdentityProviderBinding.h" + +namespace mozilla::dom { + +struct RTCIdentityProvider; + +class RTCIdentityProviderRegistrar final : public nsISupports, + public nsWrapperCache { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCIdentityProviderRegistrar) + + explicit RTCIdentityProviderRegistrar(nsIGlobalObject* aGlobal); + + // As required + nsIGlobalObject* GetParentObject() const; + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + // setter and checker + void Register(const RTCIdentityProvider& aIdp); + bool HasIdp() const; + + MOZ_CAN_RUN_SCRIPT + already_AddRefed GenerateAssertion( + const nsAString& aContents, const nsAString& aOrigin, + const RTCIdentityProviderOptions& aOptions, ErrorResult& aRv); + MOZ_CAN_RUN_SCRIPT + already_AddRefed ValidateAssertion(const nsAString& assertion, + const nsAString& origin, + ErrorResult& aRv); + + private: + ~RTCIdentityProviderRegistrar(); + + nsCOMPtr mGlobal; + RefPtr mGenerateAssertionCallback; + RefPtr mValidateAssertionCallback; +}; + +} // namespace mozilla::dom + +#endif /* RTCIDENTITYPROVIDER_H_ */ diff --git a/dom/media/webrtc/SineWaveGenerator.h b/dom/media/webrtc/SineWaveGenerator.h new file mode 100644 index 0000000000..73bab53f19 --- /dev/null +++ b/dom/media/webrtc/SineWaveGenerator.h @@ -0,0 +1,58 @@ +/* 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/. */ + +#ifndef SINEWAVEGENERATOR_H_ +#define SINEWAVEGENERATOR_H_ + +#include "MediaSegment.h" +#include "prtime.h" + +namespace mozilla { + +// generate 1k sine wave per second +template +class SineWaveGenerator { + static_assert(std::is_same::value || + std::is_same::value); + + public: + static const int bytesPerSample = sizeof(Sample); + static const int millisecondsPerSecond = PR_MSEC_PER_SEC; + static constexpr float twopi = 2 * M_PI; + + /* If more than 1 channel, generated samples are interleaved. */ + SineWaveGenerator(uint32_t aSampleRate, uint32_t aFrequency) + : mPhase(0.), mPhaseIncrement(twopi * aFrequency / aSampleRate) {} + + // NOTE: only safely called from a single thread (MTG callback) + void generate(Sample* aBuffer, TrackTicks aFrameCount, + uint32_t aChannelCount = 1) { + while (aFrameCount--) { + Sample value = sin(mPhase) * Amplitude(); + for (uint32_t channel = 0; channel < aChannelCount; channel++) { + *aBuffer++ = value; + } + mPhase += mPhaseIncrement; + if (mPhase > twopi) { + mPhase -= twopi; + } + } + } + + static float Amplitude() { + // Set volume to -20db. + if (std::is_same::value) { + return 3276.8; // 32768.0 * 10^(-20/20) = 3276.8 + } + return 0.1f; // 1.0 * 10^(-20/20) = 0.1 + } + + private: + double mPhase; + const double mPhaseIncrement; +}; + +} // namespace mozilla + +#endif /* SINEWAVEGENERATOR_H_ */ diff --git a/dom/media/webrtc/WebrtcGlobal.h b/dom/media/webrtc/WebrtcGlobal.h new file mode 100644 index 0000000000..dac51dcf6b --- /dev/null +++ b/dom/media/webrtc/WebrtcGlobal.h @@ -0,0 +1,506 @@ +/* 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/. */ + +#ifndef _WEBRTC_GLOBAL_H_ +#define _WEBRTC_GLOBAL_H_ + +#include "WebrtcIPCTraits.h" +#include "ipc/EnumSerializer.h" +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/RTCDataChannelBinding.h" +#include "mozilla/dom/RTCStatsReportBinding.h" +#include "mozilla/UniquePtr.h" + +typedef mozilla::dom::RTCStatsReportInternal StatsReport; +typedef nsTArray> RTCReports; +typedef mozilla::dom::Sequence WebrtcGlobalLog; + +namespace mozilla { +namespace dom { +// Calls aFunction with all public members of aStats. +// Typical usage would have aFunction take a parameter pack. +// To avoid inconsistencies, this should be the only explicit list of the +// public RTCStatscollection members in C++. +template +static auto ForAllPublicRTCStatsCollectionMembers(Collection& aStats, + Function aFunction) { + static_assert(std::is_same_v::type, + RTCStatsCollection>, + "aStats must be a const or non-const RTCStatsCollection"); + return aFunction( + aStats.mInboundRtpStreamStats, aStats.mOutboundRtpStreamStats, + aStats.mRemoteInboundRtpStreamStats, aStats.mRemoteOutboundRtpStreamStats, + aStats.mMediaSourceStats, aStats.mPeerConnectionStats, + aStats.mRtpContributingSourceStats, aStats.mIceCandidatePairStats, + aStats.mIceCandidateStats, aStats.mTrickledIceCandidateStats, + aStats.mDataChannelStats, aStats.mCodecStats); +} + +// Calls aFunction with all members of aStats, including internal ones. +// Typical usage would have aFunction take a parameter pack. +// To avoid inconsistencies, this should be the only explicit list of the +// internal RTCStatscollection members in C++. +template +static auto ForAllRTCStatsCollectionMembers(Collection& aStats, + Function aFunction) { + static_assert(std::is_same_v::type, + RTCStatsCollection>, + "aStats must be a const or non-const RTCStatsCollection"); + return ForAllPublicRTCStatsCollectionMembers(aStats, [&](auto&... aMember) { + return aFunction(aMember..., aStats.mRawLocalCandidates, + aStats.mRawRemoteCandidates, aStats.mVideoFrameHistories, + aStats.mBandwidthEstimations); + }); +} +} // namespace dom +} // namespace mozilla + +namespace IPC { + +template <> +struct ParamTraits + : public ContiguousEnumSerializer {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer< + mozilla::dom::RTCStatsIceCandidatePairState, + mozilla::dom::RTCStatsIceCandidatePairState::Frozen, + mozilla::dom::RTCStatsIceCandidatePairState::EndGuard_> {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer< + mozilla::dom::RTCIceCandidateType, + mozilla::dom::RTCIceCandidateType::Host, + mozilla::dom::RTCIceCandidateType::EndGuard_> {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer< + mozilla::dom::RTCBundlePolicy, + mozilla::dom::RTCBundlePolicy::Balanced, + mozilla::dom::RTCBundlePolicy::EndGuard_> {}; + +DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCIceServerInternal, mUrls, + mCredentialProvided, mUserNameProvided); + +DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCConfigurationInternal, + mBundlePolicy, mCertificatesProvided, + mIceServers, mIceTransportPolicy, + mPeerIdentityProvided, mSdpSemantics); + +DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCSdpParsingErrorInternal, + mLineNumber, mError); + +DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCSdpHistoryEntryInternal, + mTimestamp, mIsLocal, mSdp, mErrors); + +template <> +struct ParamTraits { + static void Write(MessageWriter* aWriter, + const mozilla::dom::RTCStatsCollection& aParam) { + mozilla::dom::ForAllRTCStatsCollectionMembers( + aParam, + [&](const auto&... aMember) { WriteParams(aWriter, aMember...); }); + } + + static bool Read(MessageReader* aReader, + mozilla::dom::RTCStatsCollection* aResult) { + return mozilla::dom::ForAllRTCStatsCollectionMembers( + *aResult, + [&](auto&... aMember) { return ReadParams(aReader, aMember...); }); + } +}; + +DEFINE_IPC_SERIALIZER_WITH_SUPER_CLASS_AND_FIELDS( + mozilla::dom::RTCStatsReportInternal, mozilla::dom::RTCStatsCollection, + mClosed, mSdpHistory, mPcid, mBrowserId, mTimestamp, mCallDurationMs, + mIceRestarts, mIceRollbacks, mOfferer, mConfiguration); + +typedef mozilla::dom::RTCStats RTCStats; + +static void WriteRTCStats(MessageWriter* aWriter, const RTCStats& aParam) { + // RTCStats base class + WriteParam(aWriter, aParam.mId); + WriteParam(aWriter, aParam.mTimestamp); + WriteParam(aWriter, aParam.mType); +} + +static bool ReadRTCStats(MessageReader* aReader, RTCStats* aResult) { + // RTCStats base class + if (!ReadParam(aReader, &(aResult->mId)) || + !ReadParam(aReader, &(aResult->mTimestamp)) || + !ReadParam(aReader, &(aResult->mType))) { + return false; + } + + return true; +} + +template <> +struct ParamTraits { + typedef mozilla::dom::RTCIceCandidatePairStats paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mTransportId); + WriteParam(aWriter, aParam.mLocalCandidateId); + WriteParam(aWriter, aParam.mPriority); + WriteParam(aWriter, aParam.mNominated); + WriteParam(aWriter, aParam.mWritable); + WriteParam(aWriter, aParam.mReadable); + WriteParam(aWriter, aParam.mRemoteCandidateId); + WriteParam(aWriter, aParam.mSelected); + WriteParam(aWriter, aParam.mComponentId); + WriteParam(aWriter, aParam.mState); + WriteParam(aWriter, aParam.mBytesSent); + WriteParam(aWriter, aParam.mBytesReceived); + WriteParam(aWriter, aParam.mLastPacketSentTimestamp); + WriteParam(aWriter, aParam.mLastPacketReceivedTimestamp); + WriteRTCStats(aWriter, aParam); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + if (!ReadParam(aReader, &(aResult->mTransportId)) || + !ReadParam(aReader, &(aResult->mLocalCandidateId)) || + !ReadParam(aReader, &(aResult->mPriority)) || + !ReadParam(aReader, &(aResult->mNominated)) || + !ReadParam(aReader, &(aResult->mWritable)) || + !ReadParam(aReader, &(aResult->mReadable)) || + !ReadParam(aReader, &(aResult->mRemoteCandidateId)) || + !ReadParam(aReader, &(aResult->mSelected)) || + !ReadParam(aReader, &(aResult->mComponentId)) || + !ReadParam(aReader, &(aResult->mState)) || + !ReadParam(aReader, &(aResult->mBytesSent)) || + !ReadParam(aReader, &(aResult->mBytesReceived)) || + !ReadParam(aReader, &(aResult->mLastPacketSentTimestamp)) || + !ReadParam(aReader, &(aResult->mLastPacketReceivedTimestamp)) || + !ReadRTCStats(aReader, aResult)) { + return false; + } + + return true; + } +}; + +template <> +struct ParamTraits { + typedef mozilla::dom::RTCIceCandidateStats paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mCandidateType); + WriteParam(aWriter, aParam.mPriority); + WriteParam(aWriter, aParam.mTransportId); + WriteParam(aWriter, aParam.mAddress); + WriteParam(aWriter, aParam.mRelayProtocol); + WriteParam(aWriter, aParam.mPort); + WriteParam(aWriter, aParam.mProtocol); + WriteParam(aWriter, aParam.mProxied); + WriteRTCStats(aWriter, aParam); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + if (!ReadParam(aReader, &(aResult->mCandidateType)) || + !ReadParam(aReader, &(aResult->mPriority)) || + !ReadParam(aReader, &(aResult->mTransportId)) || + !ReadParam(aReader, &(aResult->mAddress)) || + !ReadParam(aReader, &(aResult->mRelayProtocol)) || + !ReadParam(aReader, &(aResult->mPort)) || + !ReadParam(aReader, &(aResult->mProtocol)) || + !ReadParam(aReader, &(aResult->mProxied)) || + !ReadRTCStats(aReader, aResult)) { + return false; + } + + return true; + } +}; + +static void WriteRTCRtpStreamStats( + MessageWriter* aWriter, const mozilla::dom::RTCRtpStreamStats& aParam) { + WriteParam(aWriter, aParam.mSsrc); + WriteParam(aWriter, aParam.mMediaType); + WriteParam(aWriter, aParam.mKind); + WriteParam(aWriter, aParam.mTransportId); + WriteParam(aWriter, aParam.mCodecId); + WriteRTCStats(aWriter, aParam); +} + +static bool ReadRTCRtpStreamStats(MessageReader* aReader, + mozilla::dom::RTCRtpStreamStats* aResult) { + return ReadParam(aReader, &(aResult->mSsrc)) && + ReadParam(aReader, &(aResult->mMediaType)) && + ReadParam(aReader, &(aResult->mKind)) && + ReadParam(aReader, &(aResult->mTransportId)) && + ReadParam(aReader, &(aResult->mCodecId)) && + ReadRTCStats(aReader, aResult); +} + +static void WriteRTCReceivedRtpStreamStats( + MessageWriter* aWriter, + const mozilla::dom::RTCReceivedRtpStreamStats& aParam) { + WriteParam(aWriter, aParam.mPacketsReceived); + WriteParam(aWriter, aParam.mPacketsLost); + WriteParam(aWriter, aParam.mJitter); + WriteParam(aWriter, aParam.mDiscardedPackets); + WriteParam(aWriter, aParam.mPacketsDiscarded); + WriteRTCRtpStreamStats(aWriter, aParam); +} + +static bool ReadRTCReceivedRtpStreamStats( + MessageReader* aReader, mozilla::dom::RTCReceivedRtpStreamStats* aResult) { + return ReadParam(aReader, &(aResult->mPacketsReceived)) && + ReadParam(aReader, &(aResult->mPacketsLost)) && + ReadParam(aReader, &(aResult->mJitter)) && + ReadParam(aReader, &(aResult->mDiscardedPackets)) && + ReadParam(aReader, &(aResult->mPacketsDiscarded)) && + ReadRTCRtpStreamStats(aReader, aResult); +} + +template <> +struct ParamTraits { + typedef mozilla::dom::RTCInboundRtpStreamStats paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mTrackIdentifier); + WriteParam(aWriter, aParam.mRemoteId); + WriteParam(aWriter, aParam.mFramesDecoded); + WriteParam(aWriter, aParam.mFramesDropped); + WriteParam(aWriter, aParam.mFrameWidth); + WriteParam(aWriter, aParam.mFrameHeight); + WriteParam(aWriter, aParam.mFramesPerSecond); + WriteParam(aWriter, aParam.mQpSum); + WriteParam(aWriter, aParam.mTotalDecodeTime); + WriteParam(aWriter, aParam.mTotalInterFrameDelay); + WriteParam(aWriter, aParam.mTotalSquaredInterFrameDelay); + WriteParam(aWriter, aParam.mLastPacketReceivedTimestamp); + WriteParam(aWriter, aParam.mHeaderBytesReceived); + WriteParam(aWriter, aParam.mFecPacketsReceived); + WriteParam(aWriter, aParam.mFecPacketsDiscarded); + WriteParam(aWriter, aParam.mBytesReceived); + WriteParam(aWriter, aParam.mNackCount); + WriteParam(aWriter, aParam.mFirCount); + WriteParam(aWriter, aParam.mPliCount); + WriteParam(aWriter, aParam.mTotalProcessingDelay); + // Always missing from libwebrtc stats + // WriteParam(aWriter, aParam.mEstimatedPlayoutTimestamp); + WriteParam(aWriter, aParam.mFramesReceived); + WriteParam(aWriter, aParam.mJitterBufferDelay); + WriteParam(aWriter, aParam.mJitterBufferEmittedCount); + WriteParam(aWriter, aParam.mTotalSamplesReceived); + WriteParam(aWriter, aParam.mConcealedSamples); + WriteParam(aWriter, aParam.mSilentConcealedSamples); + WriteParam(aWriter, aParam.mConcealmentEvents); + WriteParam(aWriter, aParam.mInsertedSamplesForDeceleration); + WriteParam(aWriter, aParam.mRemovedSamplesForAcceleration); + WriteParam(aWriter, aParam.mAudioLevel); + WriteParam(aWriter, aParam.mTotalAudioEnergy); + WriteParam(aWriter, aParam.mTotalSamplesDuration); + WriteRTCReceivedRtpStreamStats(aWriter, aParam); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &(aResult->mTrackIdentifier)) && + ReadParam(aReader, &(aResult->mRemoteId)) && + ReadParam(aReader, &(aResult->mFramesDecoded)) && + ReadParam(aReader, &(aResult->mFramesDropped)) && + ReadParam(aReader, &(aResult->mFrameWidth)) && + ReadParam(aReader, &(aResult->mFrameHeight)) && + ReadParam(aReader, &(aResult->mFramesPerSecond)) && + ReadParam(aReader, &(aResult->mQpSum)) && + ReadParam(aReader, &(aResult->mTotalDecodeTime)) && + ReadParam(aReader, &(aResult->mTotalInterFrameDelay)) && + ReadParam(aReader, &(aResult->mTotalSquaredInterFrameDelay)) && + ReadParam(aReader, &(aResult->mLastPacketReceivedTimestamp)) && + ReadParam(aReader, &(aResult->mHeaderBytesReceived)) && + ReadParam(aReader, &(aResult->mFecPacketsReceived)) && + ReadParam(aReader, &(aResult->mFecPacketsDiscarded)) && + ReadParam(aReader, &(aResult->mBytesReceived)) && + ReadParam(aReader, &(aResult->mNackCount)) && + ReadParam(aReader, &(aResult->mFirCount)) && + ReadParam(aReader, &(aResult->mPliCount)) && + ReadParam(aReader, &(aResult->mTotalProcessingDelay)) && + // Always missing from libwebrtc + // ReadParam(aReader, &(aResult->mEstimatedPlayoutTimestamp)) && + ReadParam(aReader, &(aResult->mFramesReceived)) && + ReadParam(aReader, &(aResult->mJitterBufferDelay)) && + ReadParam(aReader, &(aResult->mJitterBufferEmittedCount)) && + ReadParam(aReader, &(aResult->mTotalSamplesReceived)) && + ReadParam(aReader, &(aResult->mConcealedSamples)) && + ReadParam(aReader, &(aResult->mSilentConcealedSamples)) && + ReadParam(aReader, &(aResult->mConcealmentEvents)) && + ReadParam(aReader, &(aResult->mInsertedSamplesForDeceleration)) && + ReadParam(aReader, &(aResult->mRemovedSamplesForAcceleration)) && + ReadParam(aReader, &(aResult->mAudioLevel)) && + ReadParam(aReader, &(aResult->mTotalAudioEnergy)) && + ReadParam(aReader, &(aResult->mTotalSamplesDuration)) && + ReadRTCReceivedRtpStreamStats(aReader, aResult); + } +}; + +static void WriteRTCSentRtpStreamStats( + MessageWriter* aWriter, const mozilla::dom::RTCSentRtpStreamStats& aParam) { + WriteParam(aWriter, aParam.mPacketsSent); + WriteParam(aWriter, aParam.mBytesSent); + WriteRTCRtpStreamStats(aWriter, aParam); +} + +static bool ReadRTCSentRtpStreamStats( + MessageReader* aReader, mozilla::dom::RTCSentRtpStreamStats* aResult) { + return ReadParam(aReader, &(aResult->mPacketsSent)) && + ReadParam(aReader, &(aResult->mBytesSent)) && + ReadRTCRtpStreamStats(aReader, aResult); +} + +template <> +struct ParamTraits { + typedef mozilla::dom::RTCOutboundRtpStreamStats paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mRemoteId); + WriteParam(aWriter, aParam.mFramesEncoded); + WriteParam(aWriter, aParam.mQpSum); + WriteParam(aWriter, aParam.mNackCount); + WriteParam(aWriter, aParam.mFirCount); + WriteParam(aWriter, aParam.mPliCount); + WriteParam(aWriter, aParam.mHeaderBytesSent); + WriteParam(aWriter, aParam.mRetransmittedPacketsSent); + WriteParam(aWriter, aParam.mRetransmittedBytesSent); + WriteParam(aWriter, aParam.mTotalEncodedBytesTarget); + WriteParam(aWriter, aParam.mFrameWidth); + WriteParam(aWriter, aParam.mFrameHeight); + WriteParam(aWriter, aParam.mFramesSent); + WriteParam(aWriter, aParam.mHugeFramesSent); + WriteParam(aWriter, aParam.mTotalEncodeTime); + WriteRTCSentRtpStreamStats(aWriter, aParam); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &(aResult->mRemoteId)) && + ReadParam(aReader, &(aResult->mFramesEncoded)) && + ReadParam(aReader, &(aResult->mQpSum)) && + ReadParam(aReader, &(aResult->mNackCount)) && + ReadParam(aReader, &(aResult->mFirCount)) && + ReadParam(aReader, &(aResult->mPliCount)) && + ReadParam(aReader, &(aResult->mHeaderBytesSent)) && + ReadParam(aReader, &(aResult->mRetransmittedPacketsSent)) && + ReadParam(aReader, &(aResult->mRetransmittedBytesSent)) && + ReadParam(aReader, &(aResult->mTotalEncodedBytesTarget)) && + ReadParam(aReader, &(aResult->mFrameWidth)) && + ReadParam(aReader, &(aResult->mFrameHeight)) && + ReadParam(aReader, &(aResult->mFramesSent)) && + ReadParam(aReader, &(aResult->mHugeFramesSent)) && + ReadParam(aReader, &(aResult->mTotalEncodeTime)) && + ReadRTCSentRtpStreamStats(aReader, aResult); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::dom::RTCRemoteInboundRtpStreamStats paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mLocalId); + WriteParam(aWriter, aParam.mRoundTripTime); + WriteParam(aWriter, aParam.mTotalRoundTripTime); + WriteParam(aWriter, aParam.mFractionLost); + WriteParam(aWriter, aParam.mRoundTripTimeMeasurements); + WriteRTCReceivedRtpStreamStats(aWriter, aParam); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &(aResult->mLocalId)) && + ReadParam(aReader, &(aResult->mRoundTripTime)) && + ReadParam(aReader, &(aResult->mTotalRoundTripTime)) && + ReadParam(aReader, &(aResult->mFractionLost)) && + ReadParam(aReader, &(aResult->mRoundTripTimeMeasurements)) && + ReadRTCReceivedRtpStreamStats(aReader, aResult); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::dom::RTCRemoteOutboundRtpStreamStats paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mLocalId); + WriteParam(aWriter, aParam.mRemoteTimestamp); + WriteRTCSentRtpStreamStats(aWriter, aParam); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &(aResult->mLocalId)) && + ReadParam(aReader, &(aResult->mRemoteTimestamp)) && + ReadRTCSentRtpStreamStats(aReader, aResult); + } +}; + +DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCMediaSourceStats, mId, + mTimestamp, mType, mTrackIdentifier, mKind); + +template <> +struct ParamTraits { + typedef mozilla::dom::RTCRTPContributingSourceStats paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mContributorSsrc); + WriteParam(aWriter, aParam.mInboundRtpStreamId); + WriteRTCStats(aWriter, aParam); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + if (!ReadParam(aReader, &(aResult->mContributorSsrc)) || + !ReadParam(aReader, &(aResult->mInboundRtpStreamId)) || + !ReadRTCStats(aReader, aResult)) { + return false; + } + return true; + } +}; + +DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCPeerConnectionStats, mId, + mTimestamp, mType, mDataChannelsOpened, + mDataChannelsClosed); + +DEFINE_IPC_SERIALIZER_WITH_FIELDS( + mozilla::dom::RTCVideoFrameHistoryEntryInternal, mWidth, mHeight, + mRotationAngle, mFirstFrameTimestamp, mLastFrameTimestamp, + mConsecutiveFrames, mLocalSsrc, mRemoteSsrc); + +DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCVideoFrameHistoryInternal, + mTrackIdentifier, mEntries); + +DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCBandwidthEstimationInternal, + mTrackIdentifier, mSendBandwidthBps, + mMaxPaddingBps, mReceiveBandwidthBps, + mPacerDelayMs, mRttMs); + +DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCDataChannelStats, mId, + mTimestamp, mType, mLabel, mProtocol, + mDataChannelIdentifier, mState, mMessagesSent, + mBytesSent, mMessagesReceived, mBytesReceived) + +template <> +struct ParamTraits + : public ContiguousEnumSerializer< + mozilla::dom::RTCDataChannelState, + mozilla::dom::RTCDataChannelState::Connecting, + mozilla::dom::RTCDataChannelState::EndGuard_> {}; + +DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCCodecStats, mTimestamp, + mType, mId, mPayloadType, mCodecType, + mTransportId, mMimeType, mClockRate, + mChannels, mSdpFmtpLine) + +template <> +struct ParamTraits + : public ContiguousEnumSerializer {}; +} // namespace IPC + +#endif // _WEBRTC_GLOBAL_H_ diff --git a/dom/media/webrtc/WebrtcIPCTraits.h b/dom/media/webrtc/WebrtcIPCTraits.h new file mode 100644 index 0000000000..b076745608 --- /dev/null +++ b/dom/media/webrtc/WebrtcIPCTraits.h @@ -0,0 +1,89 @@ +/* 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/. */ + +#ifndef _WEBRTC_IPC_TRAITS_H_ +#define _WEBRTC_IPC_TRAITS_H_ + +#include "ipc/EnumSerializer.h" +#include "ipc/IPCMessageUtils.h" +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/RTCConfigurationBinding.h" +#include "mozilla/media/webrtc/WebrtcGlobal.h" +#include "mozilla/dom/CandidateInfo.h" +#include "mozilla/MacroForEach.h" +#include "transport/dtlsidentity.h" +#include + +namespace mozilla { +typedef std::vector StringVector; +} + +namespace IPC { + +template <> +struct ParamTraits { + typedef mozilla::dom::OwningStringOrStringSequence paramType; + + // Ugh. OwningStringOrStringSequence already has this enum, but it is + // private generated code. So we have to re-create it. + enum Type { kUninitialized, kString, kStringSequence }; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + if (aParam.IsString()) { + aWriter->WriteInt16(kString); + WriteParam(aWriter, aParam.GetAsString()); + } else if (aParam.IsStringSequence()) { + aWriter->WriteInt16(kStringSequence); + WriteParam(aWriter, aParam.GetAsStringSequence()); + } else { + aWriter->WriteInt16(kUninitialized); + } + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + int16_t type; + if (!aReader->ReadInt16(&type)) { + return false; + } + + switch (type) { + case kUninitialized: + aResult->Uninit(); + return true; + case kString: + return ReadParam(aReader, &aResult->SetAsString()); + case kStringSequence: + return ReadParam(aReader, &aResult->SetAsStringSequence()); + } + + return false; + } +}; + +template +struct WebidlEnumSerializer + : public ContiguousEnumSerializer {}; + +template <> +struct ParamTraits + : public WebidlEnumSerializer {}; + +template <> +struct ParamTraits + : public WebidlEnumSerializer {}; + +DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCIceServer, mCredential, + mCredentialType, mUrl, mUrls, mUsername) + +DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::CandidateInfo, mCandidate, mUfrag, + mDefaultHostRtp, mDefaultPortRtp, + mDefaultHostRtcp, mDefaultPortRtcp, + mMDNSAddress, mActualAddress) + +DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::DtlsDigest, algorithm_, value_) + +} // namespace IPC + +#endif // _WEBRTC_IPC_TRAITS_H_ diff --git a/dom/media/webrtc/common/CandidateInfo.h b/dom/media/webrtc/common/CandidateInfo.h new file mode 100644 index 0000000000..eb5b2ea299 --- /dev/null +++ b/dom/media/webrtc/common/CandidateInfo.h @@ -0,0 +1,27 @@ +/* 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/. */ + +#ifndef _CANDIDATE_INFO_H__ +#define _CANDIDATE_INFO_H__ + +#include +#include + +namespace mozilla { + +// This is used both by IPDL code, and by signaling code. +struct CandidateInfo { + std::string mCandidate; + std::string mMDNSAddress; + std::string mActualAddress; + std::string mUfrag; + std::string mDefaultHostRtp; + uint16_t mDefaultPortRtp = 0; + std::string mDefaultHostRtcp; + uint16_t mDefaultPortRtcp = 0; +}; + +} // namespace mozilla + +#endif //_CANDIDATE_INFO_H__ diff --git a/dom/media/webrtc/common/CommonTypes.h b/dom/media/webrtc/common/CommonTypes.h new file mode 100644 index 0000000000..e9c9ffdefc --- /dev/null +++ b/dom/media/webrtc/common/CommonTypes.h @@ -0,0 +1,52 @@ +/* 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/. */ + +#pragma once + +#include + +namespace csf { + +namespace ProviderStateEnum { +enum ProviderState { + Ready, + Registering, + AwaitingIpAddress, + FetchingDeviceConfig, + Idle, + RecoveryPending, + Connected +}; +const std::string toString(ProviderState); +} // namespace ProviderStateEnum +namespace LoginErrorStatusEnum { +enum LoginErrorStatus { + Ok, // No Error + Unknown, // Unknown Error + NoCallManagerConfigured, // No Primary or Backup Call Manager + NoDevicesFound, // No devices + NoCsfDevicesFound, // Devices but none of type CSF + PhoneConfigGenError, // Could not generate phone config + SipProfileGenError, // Could not build SIP profile + ConfigNotSet, // Config not set before calling login() + CreateConfigProviderFailed, // Could not create ConfigProvider + CreateSoftPhoneProviderFailed, // Could not create SoftPhoneProvider + MissingUsername, // Username argument missing, + ManualLogout, // logout() has been called + LoggedInElseWhere, // Another process has the mutex indicating it is logged + // in + AuthenticationFailure, // Authentication failure (probably bad password, but + // best not to say for sure) + CtiCouldNotConnect, // Could not connect to CTI service + InvalidServerSearchList +}; +const std::string toString(LoginErrorStatus); +} // namespace LoginErrorStatusEnum + +namespace ErrorCodeEnum { +enum ErrorCode { Ok, Unknown, InvalidState, InvalidArgument }; +const std::string toString(ErrorCode); +} // namespace ErrorCodeEnum + +} // namespace csf diff --git a/dom/media/webrtc/common/EncodingConstraints.h b/dom/media/webrtc/common/EncodingConstraints.h new file mode 100644 index 0000000000..0bfa7f9f75 --- /dev/null +++ b/dom/media/webrtc/common/EncodingConstraints.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef _ENCODING_CONSTRAINTS_H_ +#define _ENCODING_CONSTRAINTS_H_ + +#include +#include "mozilla/Maybe.h" + +namespace mozilla { +class EncodingConstraints { + public: + EncodingConstraints() + : maxWidth(0), + maxHeight(0), + maxFs(0), + maxBr(0), + maxPps(0), + maxMbps(0), + maxCpb(0), + maxDpb(0), + scaleDownBy(1.0) {} + + bool operator==(const EncodingConstraints& constraints) const { + return maxWidth == constraints.maxWidth && + maxHeight == constraints.maxHeight && maxFps == constraints.maxFps && + maxFs == constraints.maxFs && maxBr == constraints.maxBr && + maxPps == constraints.maxPps && maxMbps == constraints.maxMbps && + maxCpb == constraints.maxCpb && maxDpb == constraints.maxDpb && + scaleDownBy == constraints.scaleDownBy; + } + + /** + * This returns true if the constraints affecting resolution are equal. + */ + bool ResolutionEquals(const EncodingConstraints& constraints) const { + return maxWidth == constraints.maxWidth && + maxHeight == constraints.maxHeight && maxFs == constraints.maxFs && + scaleDownBy == constraints.scaleDownBy; + } + + uint32_t maxWidth; + uint32_t maxHeight; + Maybe maxFps; + uint32_t maxFs; + uint32_t maxBr; + uint32_t maxPps; + uint32_t maxMbps; // macroblocks per second + uint32_t maxCpb; // coded picture buffer size + uint32_t maxDpb; // decoded picture buffer size + double scaleDownBy; // To preserve resolution +}; +} // namespace mozilla + +#endif // _ENCODING_CONSTRAINTS_H_ diff --git a/dom/media/webrtc/common/MediaEngineWrapper.h b/dom/media/webrtc/common/MediaEngineWrapper.h new file mode 100644 index 0000000000..ba48ad57d8 --- /dev/null +++ b/dom/media/webrtc/common/MediaEngineWrapper.h @@ -0,0 +1,32 @@ +/* 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/. */ + +#ifndef MEDIA_ENGINE_WRAPPER_H_ +#define MEDIA_ENGINE_WRAPPER_H_ + +#include + +namespace mozilla { +/** + * A Custom scoped template to release a resoure of Type T + * with a function of Type F + * ScopedCustomReleasePtr ptr = + * webrtc::VoENetwork->GetInterface(voiceEngine); + * + */ +template +struct ScopedCustomReleaseTraits0 { + typedef T* type; + static T* empty() { return nullptr; } + static void release(T* ptr) { + if (ptr) { + (ptr)->Release(); + } + } +}; + +SCOPED_TEMPLATE(ScopedCustomReleasePtr, ScopedCustomReleaseTraits0) +} // namespace mozilla + +#endif diff --git a/dom/media/webrtc/common/NullDeleter.h b/dom/media/webrtc/common/NullDeleter.h new file mode 100644 index 0000000000..76326c197e --- /dev/null +++ b/dom/media/webrtc/common/NullDeleter.h @@ -0,0 +1,14 @@ +/* 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/. */ + +#pragma once + +/* + * Helper class to allow smart pointers to stack objects to be constructed for + * ease of unit testing. Recycled here to help expose a shared_ptr interface to + * objects which are really raw pointers. + */ +struct null_deleter { + void operator()(void const*) const {} +}; diff --git a/dom/media/webrtc/common/NullTransport.h b/dom/media/webrtc/common/NullTransport.h new file mode 100644 index 0000000000..a9b270c6d6 --- /dev/null +++ b/dom/media/webrtc/common/NullTransport.h @@ -0,0 +1,56 @@ +/* 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/. */ + +#ifndef NULL_TRANSPORT_H_ +#define NULL_TRANSPORT_H_ + +#include "mozilla/Attributes.h" + +#include "api/call/transport.h" + +namespace mozilla { + +/** + * NullTransport is registered as ExternalTransport to throw away data + */ +class NullTransport : public webrtc::Transport { + public: + virtual bool SendRtp(const uint8_t* packet, size_t length, + const webrtc::PacketOptions& options) override { + (void)packet; + (void)length; + (void)options; + return true; + } + + virtual bool SendRtcp(const uint8_t* packet, size_t length) override { + (void)packet; + (void)length; + return true; + } +#if 0 + virtual int SendPacket(int channel, const void *data, size_t len) + { + (void) channel; (void) data; + return len; + } + + virtual int SendRTCPPacket(int channel, const void *data, size_t len) + { + (void) channel; (void) data; + return len; + } +#endif + NullTransport() {} + + virtual ~NullTransport() {} + + private: + NullTransport(const NullTransport& other) = delete; + void operator=(const NullTransport& other) = delete; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/webrtc/common/Wrapper.h b/dom/media/webrtc/common/Wrapper.h new file mode 100644 index 0000000000..69c7406b07 --- /dev/null +++ b/dom/media/webrtc/common/Wrapper.h @@ -0,0 +1,157 @@ +/* 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/. */ + +#pragma once + +/* + * Wrapper - Helper class for wrapper objects. + * + * This helps to construct a shared_ptr object which wraps access to an + * underlying handle. (The handle could be a pointer to some low-level type, a + * conventional C handle, an int ID, a GUID, etc.) + * + * Usage: + * To obtain a FooPtr from a foo_handle_t, call + * FooPtr Foo::wrap(foo_handle_t); + * + * To implement Foo using Wrapper, Foo needs to include this macro in its class + * definition: + * CSF_DECLARE_WRAP(Foo, foo_handle_t); + * It also needs to include this in the cpp file, to provide the wrap() + * implementation and define the static Wrapper. + * CSF_IMPLEMENT_WRAP(Foo, foo_handle_t); + * These are all declared in common/Wrapper.h - Foo.h needs to include this + * too. + * The client needs to declare Foo(foo_handle_t) as private, and provide a + * suitable implementation, as well as implementing wrappers for any other + * functions to be exposed. + * The client needs to implement ~Foo() to perform any cleanup as usual. + * + * wrap() will always return the same FooPtr for a given foo_handle_t, it will + * not construct additional objects if a suitable one already exists. + * changeHandle() is used in rare cases where the underlying handle is changed, + * but the wrapper object is intended to remain. This is the + * case for the "fake" CC_DPCall generated on + * CC_DPLine::CreateCall(), where the correct IDPCall* is + * provided later. + * reset() is a cleanup step to wipe the handle map and allow memory to be + * reclaimed. + * + * Future enhancements: + * - For now, objects remain in the map forever. Better would be to add a + * releaseHandle() function which would allow the map to be emptied as + * underlying handles expired. While we can't force the client to give up + * its shared_ptr objects, we can remove our own copy, for instance on a + * call ended event. + */ + +#include +#include "prlock.h" +#include "mozilla/Assertions.h" + +/* + * Wrapper has its own autolock class because the instances are declared + * statically and mozilla::Mutex will not work properly when instantiated + * in a static constructor. + */ + +class LockNSPR { + public: + LockNSPR() : lock_(nullptr) { + lock_ = PR_NewLock(); + MOZ_ASSERT(lock_); + } + ~LockNSPR() { PR_DestroyLock(lock_); } + + void Acquire() { PR_Lock(lock_); } + + void Release() { PR_Unlock(lock_); } + + private: + PRLock* lock_; +}; + +class AutoLockNSPR { + public: + explicit AutoLockNSPR(LockNSPR& lock) : lock_(lock) { lock_.Acquire(); } + ~AutoLockNSPR() { lock_.Release(); } + + private: + LockNSPR& lock_; +}; + +template +class Wrapper { + private: + typedef std::map HandleMapType; + HandleMapType handleMap; + LockNSPR handleMapMutex; + + public: + Wrapper() {} + + typename T::Ptr wrap(typename T::Handle handle) { + AutoLockNSPR lock(handleMapMutex); + typename HandleMapType::iterator it = handleMap.find(handle); + if (it != handleMap.end()) { + return it->second; + } else { + typename T::Ptr p(new T(handle)); + handleMap[handle] = p; + return p; + } + } + + bool changeHandle(typename T::Handle oldHandle, + typename T::Handle newHandle) { + AutoLockNSPR lock(handleMapMutex); + typename HandleMapType::iterator it = handleMap.find(oldHandle); + if (it != handleMap.end()) { + typename T::Ptr p = it->second; + handleMap.erase(it); + handleMap[newHandle] = p; + return true; + } else { + return false; + } + } + + bool release(typename T::Handle handle) { + AutoLockNSPR lock(handleMapMutex); + typename HandleMapType::iterator it = handleMap.find(handle); + if (it != handleMap.end()) { + handleMap.erase(it); + return true; + } else { + return false; + } + } + + void reset() { + AutoLockNSPR lock(handleMapMutex); + handleMap.clear(); + } +}; + +#define CSF_DECLARE_WRAP(classname, handletype) \ + public: \ + static classname##Ptr wrap(handletype handle); \ + static void reset(); \ + static void release(handletype handle); \ + \ + private: \ + friend class Wrapper; \ + typedef classname##Ptr Ptr; \ + typedef handletype Handle; \ + static Wrapper& getWrapper() { \ + static Wrapper wrapper; \ + return wrapper; \ + } + +#define CSF_IMPLEMENT_WRAP(classname, handletype) \ + classname##Ptr classname::wrap(handletype handle) { \ + return getWrapper().wrap(handle); \ + } \ + void classname::reset() { getWrapper().reset(); } \ + void classname::release(handletype handle) { getWrapper().release(handle); } diff --git a/dom/media/webrtc/common/YuvStamper.cpp b/dom/media/webrtc/common/YuvStamper.cpp new file mode 100644 index 0000000000..b0c8426983 --- /dev/null +++ b/dom/media/webrtc/common/YuvStamper.cpp @@ -0,0 +1,394 @@ +/* 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/. */ + +#ifdef HAVE_NETINET_IN_H +# include +#elif defined XP_WIN +# include +#endif +#include + +#include "nspr.h" +#include "YuvStamper.h" +#include "mozilla/Sprintf.h" + +typedef uint32_t UINT4; // Needed for r_crc32() call +extern "C" { +#include "r_crc32.h" +} + +namespace mozilla { + +#define ON_5 0x20 +#define ON_4 0x10 +#define ON_3 0x08 +#define ON_2 0x04 +#define ON_1 0x02 +#define ON_0 0x01 + +/* + 0, 0, 1, 1, 0, 0, + 0, 1, 0, 0, 1, 0, + 1, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 1, + 0, 1, 0, 0, 1, 0, + 0, 0, 1, 1, 0, 0 +*/ +static unsigned char DIGIT_0[] = {ON_3 | ON_2, ON_4 | ON_1, ON_5 | ON_0, + ON_5 | ON_0, ON_5 | ON_0, ON_4 | ON_1, + ON_3 | ON_2}; + +/* + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, 0, +*/ +static unsigned char DIGIT_1[] = {ON_2, ON_2, ON_2, ON_2, ON_2, ON_2, ON_2}; + +/* + 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, 0, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, +*/ +static unsigned char DIGIT_2[] = { + ON_5 | ON_4 | ON_3 | ON_2 | ON_1, ON_0, ON_0, + ON_4 | ON_3 | ON_2 | ON_1, ON_5, ON_5, + ON_4 | ON_3 | ON_2 | ON_1 | ON_0, +}; + +/* + 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 0, +*/ +static unsigned char DIGIT_3[] = { + ON_5 | ON_4 | ON_3 | ON_2 | ON_1, ON_0, ON_0, + ON_4 | ON_3 | ON_2 | ON_1 | ON_0, ON_0, ON_0, + ON_5 | ON_4 | ON_3 | ON_2 | ON_1, +}; + +/* + 0, 1, 0, 0, 0, 1, + 0, 1, 0, 0, 0, 1, + 0, 1, 0, 0, 0, 1, + 0, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1 +*/ +static unsigned char DIGIT_4[] = { + ON_4 | ON_0, ON_4 | ON_0, ON_4 | ON_0, ON_4 | ON_3 | ON_2 | ON_1 | ON_0, + ON_0, ON_0, ON_0, +}; + +/* + 0, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 0, +*/ +static unsigned char DIGIT_5[] = { + ON_4 | ON_3 | ON_2 | ON_1 | ON_0, ON_5, ON_5, + ON_4 | ON_3 | ON_2 | ON_1, ON_0, ON_0, + ON_5 | ON_4 | ON_3 | ON_2 | ON_1, +}; + +/* + 0, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 0, + 1, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, 0, +*/ +static unsigned char DIGIT_6[] = { + ON_4 | ON_3 | ON_2 | ON_1 | ON_0, ON_5, ON_5, + ON_4 | ON_3 | ON_2 | ON_1, ON_5 | ON_0, ON_5 | ON_0, + ON_4 | ON_3 | ON_2 | ON_1, +}; + +/* + 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0 +*/ +static unsigned char DIGIT_7[] = {ON_5 | ON_4 | ON_3 | ON_2 | ON_1 | ON_0, + ON_0, + ON_1, + ON_2, + ON_3, + ON_4, + ON_5}; + +/* + 0, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, 0, + 1, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, 0 +*/ +static unsigned char DIGIT_8[] = { + ON_4 | ON_3 | ON_2 | ON_1, ON_5 | ON_0, ON_5 | ON_0, + ON_4 | ON_3 | ON_2 | ON_1, ON_5 | ON_0, ON_5 | ON_0, + ON_4 | ON_3 | ON_2 | ON_1, +}; + +/* + 0, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, 0 +*/ +static unsigned char DIGIT_9[] = { + ON_4 | ON_3 | ON_2 | ON_1 | ON_0, ON_5 | ON_0, ON_5 | ON_0, + ON_4 | ON_3 | ON_2 | ON_1 | ON_0, ON_0, ON_0, + ON_4 | ON_3 | ON_2 | ON_1, +}; + +static unsigned char* DIGITS[] = {DIGIT_0, DIGIT_1, DIGIT_2, DIGIT_3, DIGIT_4, + DIGIT_5, DIGIT_6, DIGIT_7, DIGIT_8, DIGIT_9}; + +YuvStamper::YuvStamper(unsigned char* pYData, uint32_t width, uint32_t height, + uint32_t stride, uint32_t x, uint32_t y, + unsigned char symbol_width, unsigned char symbol_height) + : pYData(pYData), + mStride(stride), + mWidth(width), + mHeight(height), + mSymbolWidth(symbol_width), + mSymbolHeight(symbol_height), + mCursor(x, y) {} + +bool YuvStamper::Encode(uint32_t width, uint32_t height, uint32_t stride, + unsigned char* pYData, unsigned char* pMsg, + size_t msg_len, uint32_t x, uint32_t y) { + YuvStamper stamper(pYData, width, height, stride, x, y, sBitSize, sBitSize); + + // Reserve space for a checksum. + if (stamper.Capacity() < 8 * (msg_len + sizeof(uint32_t))) { + return false; + } + + bool ok = false; + uint32_t crc; + unsigned char* pCrc = reinterpret_cast(&crc); + r_crc32(reinterpret_cast(pMsg), (int)msg_len, &crc); + crc = htonl(crc); + + while (msg_len-- > 0) { + if (!stamper.Write8(*pMsg++)) { + return false; + } + } + + // Add checksum after the message. + ok = stamper.Write8(*pCrc++) && stamper.Write8(*pCrc++) && + stamper.Write8(*pCrc++) && stamper.Write8(*pCrc++); + + return ok; +} + +bool YuvStamper::Decode(uint32_t width, uint32_t height, uint32_t stride, + unsigned char* pYData, unsigned char* pMsg, + size_t msg_len, uint32_t x, uint32_t y) { + YuvStamper stamper(pYData, width, height, stride, x, y, sBitSize, sBitSize); + + unsigned char* ptr = pMsg; + size_t len = msg_len; + uint32_t crc, msg_crc; + unsigned char* pCrc = reinterpret_cast(&crc); + + // Account for space reserved for the checksum + if (stamper.Capacity() < 8 * (len + sizeof(uint32_t))) { + return false; + } + + while (len-- > 0) { + if (!stamper.Read8(*ptr++)) { + return false; + } + } + + if (!(stamper.Read8(*pCrc++) && stamper.Read8(*pCrc++) && + stamper.Read8(*pCrc++) && stamper.Read8(*pCrc++))) { + return false; + } + + r_crc32(reinterpret_cast(pMsg), (int)msg_len, &msg_crc); + return crc == htonl(msg_crc); +} + +inline uint32_t YuvStamper::Capacity() { + // Enforce at least a symbol width and height offset from outer edges. + if (mCursor.y + mSymbolHeight > mHeight) { + return 0; + } + + if (mCursor.x + mSymbolWidth > mWidth && !AdvanceCursor()) { + return 0; + } + + // Normalize frame integral to mSymbolWidth x mSymbolHeight + uint32_t width = mWidth / mSymbolWidth; + uint32_t height = mHeight / mSymbolHeight; + uint32_t x = mCursor.x / mSymbolWidth; + uint32_t y = mCursor.y / mSymbolHeight; + + return (width * height - width * y) - x; +} + +bool YuvStamper::Write8(unsigned char value) { + // Encode MSB to LSB. + unsigned char mask = 0x80; + while (mask) { + if (!WriteBit(!!(value & mask))) { + return false; + } + mask >>= 1; + } + return true; +} + +bool YuvStamper::WriteBit(bool one) { + // A bit is mapped to a mSymbolWidth x mSymbolHeight square of luma data + // points. Don't use ternary op.: + // https://bugzilla.mozilla.org/show_bug.cgi?id=1001708 + unsigned char value; + if (one) + value = sYOn; + else + value = sYOff; + + for (uint32_t y = 0; y < mSymbolHeight; y++) { + for (uint32_t x = 0; x < mSymbolWidth; x++) { + *(pYData + (mCursor.x + x) + ((mCursor.y + y) * mStride)) = value; + } + } + + return AdvanceCursor(); +} + +bool YuvStamper::AdvanceCursor() { + mCursor.x += mSymbolWidth; + if (mCursor.x + mSymbolWidth > mWidth) { + // move to the start of the next row if possible. + mCursor.y += mSymbolHeight; + if (mCursor.y + mSymbolHeight > mHeight) { + // end of frame, do not advance + mCursor.y -= mSymbolHeight; + mCursor.x -= mSymbolWidth; + return false; + } else { + mCursor.x = 0; + } + } + + return true; +} + +bool YuvStamper::Read8(unsigned char& value) { + unsigned char octet = 0; + unsigned char bit = 0; + + for (int i = 8; i > 0; --i) { + if (!ReadBit(bit)) { + return false; + } + octet <<= 1; + octet |= bit; + } + + value = octet; + return true; +} + +bool YuvStamper::ReadBit(unsigned char& bit) { + uint32_t sum = 0; + for (uint32_t y = 0; y < mSymbolHeight; y++) { + for (uint32_t x = 0; x < mSymbolWidth; x++) { + sum += *(pYData + mStride * (mCursor.y + y) + mCursor.x + x); + } + } + + // apply threshold to collected bit square + bit = (sum > (sBitThreshold * mSymbolWidth * mSymbolHeight)) ? 1 : 0; + return AdvanceCursor(); +} + +bool YuvStamper::WriteDigits(uint32_t value) { + char buf[20]; + SprintfLiteral(buf, "%.5u", value); + size_t size = strlen(buf); + + if (Capacity() < size) { + return false; + } + + for (size_t i = 0; i < size; ++i) { + if (!WriteDigit(buf[i] - '0')) return false; + if (!AdvanceCursor()) { + return false; + } + } + + return true; +} + +bool YuvStamper::WriteDigit(unsigned char digit) { + if (digit > sizeof(DIGITS) / sizeof(DIGITS[0])) return false; + + unsigned char* dig = DIGITS[digit]; + for (uint32_t row = 0; row < sDigitHeight; ++row) { + unsigned char mask = 0x01 << (sDigitWidth - 1); + for (uint32_t col = 0; col < sDigitWidth; ++col, mask >>= 1) { + if (dig[row] & mask) { + for (uint32_t xx = 0; xx < sPixelSize; ++xx) { + for (uint32_t yy = 0; yy < sPixelSize; ++yy) { + WritePixel(pYData, mCursor.x + (col * sPixelSize) + xx, + mCursor.y + (row * sPixelSize) + yy); + } + } + } + } + } + + return true; +} + +void YuvStamper::WritePixel(unsigned char* data, uint32_t x, uint32_t y) { + unsigned char* ptr = &data[y * mStride + x]; + // Don't use ternary op.: https://bugzilla.mozilla.org/show_bug.cgi?id=1001708 + if (*ptr > sLumaThreshold) + *ptr = sLumaMin; + else + *ptr = sLumaMax; +} + +} // namespace mozilla. diff --git a/dom/media/webrtc/common/YuvStamper.h b/dom/media/webrtc/common/YuvStamper.h new file mode 100644 index 0000000000..f355055cd9 --- /dev/null +++ b/dom/media/webrtc/common/YuvStamper.h @@ -0,0 +1,77 @@ +/* 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/. */ + +#ifndef YUV_STAMPER_H_ +#define YUV_STAMPER_H_ + +#include + +namespace mozilla { + +class YuvStamper { + public: + bool WriteDigits(uint32_t value); + + template + static bool Write(uint32_t width, uint32_t height, uint32_t stride, + unsigned char* pYData, const T& value, uint32_t x = 0, + uint32_t y = 0) { + YuvStamper stamper(pYData, width, height, stride, x, y, + (sDigitWidth + sInterDigit) * sPixelSize, + (sDigitHeight + sInterLine) * sPixelSize); + return stamper.WriteDigits(value); + } + + static bool Encode(uint32_t width, uint32_t height, uint32_t stride, + unsigned char* pYData, unsigned char* pMsg, size_t msg_len, + uint32_t x = 0, uint32_t y = 0); + + static bool Decode(uint32_t width, uint32_t height, uint32_t stride, + unsigned char* pYData, unsigned char* pMsg, size_t msg_len, + uint32_t x = 0, uint32_t y = 0); + + private: + YuvStamper(unsigned char* pYData, uint32_t width, uint32_t height, + uint32_t stride, uint32_t x, uint32_t y, + unsigned char symbol_width, unsigned char symbol_height); + + bool WriteDigit(unsigned char digit); + void WritePixel(unsigned char* data, uint32_t x, uint32_t y); + uint32_t Capacity(); + bool AdvanceCursor(); + bool WriteBit(bool one); + bool Write8(unsigned char value); + bool ReadBit(unsigned char& value); + bool Read8(unsigned char& bit); + + const static unsigned char sPixelSize = 3; + const static unsigned char sDigitWidth = 6; + const static unsigned char sDigitHeight = 7; + const static unsigned char sInterDigit = 1; + const static unsigned char sInterLine = 1; + const static uint32_t sBitSize = 4; + const static uint32_t sBitThreshold = 60; + const static unsigned char sYOn = 0x80; + const static unsigned char sYOff = 0; + const static unsigned char sLumaThreshold = 96; + const static unsigned char sLumaMin = 16; + const static unsigned char sLumaMax = 235; + + unsigned char* pYData; + uint32_t mStride; + uint32_t mWidth; + uint32_t mHeight; + unsigned char mSymbolWidth; + unsigned char mSymbolHeight; + + struct Cursor { + Cursor(uint32_t x, uint32_t y) : x(x), y(y) {} + uint32_t x; + uint32_t y; + } mCursor; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/webrtc/common/browser_logging/CSFLog.cpp b/dom/media/webrtc/common/browser_logging/CSFLog.cpp new file mode 100644 index 0000000000..d58f5726a5 --- /dev/null +++ b/dom/media/webrtc/common/browser_logging/CSFLog.cpp @@ -0,0 +1,85 @@ +/* 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 +#include +#include + +#include "CSFLog.h" + +#include +#include "prrwlock.h" +#include "prthread.h" +#include "nsThreadUtils.h" + +#include "mozilla/Logging.h" +#include "mozilla/Sprintf.h" + +mozilla::LazyLogModule gSignalingLog("signaling"); + +void CSFLogV(CSFLogLevel priority, const char* sourceFile, int sourceLine, + const char* tag, const char* format, va_list args) { +#ifdef STDOUT_LOGGING + printf("%s\n:", tag); + vprintf(format, args); +#else + + mozilla::LogLevel level = + static_cast(static_cast(priority)); + + // Skip doing any of this work if we're not logging the indicated level... + if (!MOZ_LOG_TEST(gSignalingLog, level)) { + return; + } + + // Trim the path component from the filename + const char* lastSlash = sourceFile; + while (*sourceFile) { + if (*sourceFile == '/' || *sourceFile == '\\') { + lastSlash = sourceFile; + } + sourceFile++; + } + sourceFile = lastSlash; + if (*sourceFile == '/' || *sourceFile == '\\') { + sourceFile++; + } + +# define MAX_MESSAGE_LENGTH 1024 + char message[MAX_MESSAGE_LENGTH]; + + const char* threadName = NULL; + + // Check if we're the main thread... + if (NS_IsMainThread()) { + threadName = "main"; + } else { + threadName = PR_GetThreadName(PR_GetCurrentThread()); + } + + // If we can't find it anywhere, use a blank string + if (!threadName) { + threadName = ""; + } + + VsprintfLiteral(message, format, args); + MOZ_LOG( + gSignalingLog, level, + ("[%s|%s] %s:%d: %s", threadName, tag, sourceFile, sourceLine, message)); +#endif +} + +void CSFLog(CSFLogLevel priority, const char* sourceFile, int sourceLine, + const char* tag, const char* format, ...) { + va_list ap; + va_start(ap, format); + + CSFLogV(priority, sourceFile, sourceLine, tag, format, ap); + va_end(ap); +} + +int CSFLogTestLevel(CSFLogLevel priority) { + return MOZ_LOG_TEST(gSignalingLog, static_cast( + static_cast(priority))); +} diff --git a/dom/media/webrtc/common/browser_logging/CSFLog.h b/dom/media/webrtc/common/browser_logging/CSFLog.h new file mode 100644 index 0000000000..eb46b37cc3 --- /dev/null +++ b/dom/media/webrtc/common/browser_logging/CSFLog.h @@ -0,0 +1,58 @@ +/* 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/. */ + +#ifndef CSFLOG_H +#define CSFLOG_H + +#include + +typedef enum { + CSF_LOG_ERROR = 1, + CSF_LOG_WARNING, + CSF_LOG_INFO, + CSF_LOG_DEBUG, + CSF_LOG_VERBOSE, +} CSFLogLevel; + +#define CSFLogError(tag, format, ...) \ + CSFLog(CSF_LOG_ERROR, __FILE__, __LINE__, tag, format, ##__VA_ARGS__) +#define CSFLogErrorV(tag, format, va_list_arg) \ + CSFLogV(CSF_LOG_ERROR, __FILE__, __LINE__, tag, format, va_list_arg) +#define CSFLogWarn(tag, format, ...) \ + CSFLog(CSF_LOG_WARNING, __FILE__, __LINE__, tag, format, ##__VA_ARGS__) +#define CSFLogWarnV(tag, format, va_list_arg) \ + CSFLogV(CSF_LOG_WARNING, __FILE__, __LINE__, tag, format, va_list_arg) +#define CSFLogInfo(tag, format, ...) \ + CSFLog(CSF_LOG_INFO, __FILE__, __LINE__, tag, format, ##__VA_ARGS__) +#define CSFLogInfoV(tag, format, va_list_arg) \ + CSFLogV(CSF_LOG_INFO, __FILE__, __LINE__, tag, format, va_list_arg) +#define CSFLogDebug(tag, format, ...) \ + CSFLog(CSF_LOG_DEBUG, __FILE__, __LINE__, tag, format, ##__VA_ARGS__) +#define CSFLogDebugV(tag, format, va_list_arg) \ + CSFLogV(CSF_LOG_DEBUG, __FILE__, __LINE__, tag, format, va_list_arg) +#define CSFLogVerbose(tag, format, ...) \ + CSFLog(CSF_LOG_VERBOSE, __FILE__, __LINE__, tag, format, ##__VA_ARGS__) +#define CSFLogVerboseV(tag, format, va_list_arg) \ + CSFLogV(CSF_LOG_VERBOSE, __FILE__, __LINE__, tag, format, va_list_arg) + +#ifdef __cplusplus +extern "C" { +#endif +void CSFLog(CSFLogLevel priority, const char* sourceFile, int sourceLine, + const char* tag, const char* format, ...) +#ifdef __GNUC__ + __attribute__((format(printf, 5, 6))) +#endif + ; + +void CSFLogV(CSFLogLevel priority, const char* sourceFile, int sourceLine, + const char* tag, const char* format, va_list args); + +int CSFLogTestLevel(CSFLogLevel priority); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dom/media/webrtc/common/browser_logging/WebRtcLog.cpp b/dom/media/webrtc/common/browser_logging/WebRtcLog.cpp new file mode 100644 index 0000000000..0f3769604e --- /dev/null +++ b/dom/media/webrtc/common/browser_logging/WebRtcLog.cpp @@ -0,0 +1,162 @@ +/* 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 "WebRtcLog.h" + +#include "mozilla/Logging.h" +#include "mozilla/StaticPtr.h" +#include "prenv.h" +#include "rtc_base/logging.h" + +#include "nscore.h" +#include "nsString.h" +#include "nsXULAppAPI.h" +#include "mozilla/Preferences.h" + +#include "nsIFile.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsNativeCharsetUtils.h" + +using mozilla::LogLevel; + +static mozilla::LazyLogModule sWebRtcLog("webrtc_trace"); +static mozilla::LazyLogModule sLogAEC("AEC"); + +class LogSinkImpl : public rtc::LogSink { + public: + LogSinkImpl() {} + + private: + void OnLogMessage(const std::string& message) override { + MOZ_LOG(sWebRtcLog, LogLevel::Debug, ("%s", message.data())); + } +}; + +// For RTC_LOG() +static mozilla::StaticAutoPtr sSink; + +void GetWebRtcLogPrefs() { + rtc::LogMessage::set_aec_debug_size( + mozilla::Preferences::GetUint("media.webrtc.debug.aec_dump_max_size")); +} + +mozilla::LogLevel CheckOverrides() { + mozilla::LogModule* log_info = sWebRtcLog; + mozilla::LogLevel log_level = log_info->Level(); + + log_info = sLogAEC; + if (sLogAEC && (log_info->Level() != mozilla::LogLevel::Disabled)) { + rtc::LogMessage::set_aec_debug(true); + } + + return log_level; +} + +void ConfigWebRtcLog(mozilla::LogLevel level) { + rtc::LoggingSeverity log_level; + switch (level) { + case mozilla::LogLevel::Verbose: + log_level = rtc::LoggingSeverity::LS_VERBOSE; + break; + case mozilla::LogLevel::Debug: + case mozilla::LogLevel::Info: + log_level = rtc::LoggingSeverity::LS_INFO; + break; + case mozilla::LogLevel::Warning: + log_level = rtc::LoggingSeverity::LS_WARNING; + break; + case mozilla::LogLevel::Error: + log_level = rtc::LoggingSeverity::LS_ERROR; + break; + case mozilla::LogLevel::Disabled: + log_level = rtc::LoggingSeverity::LS_NONE; + break; + default: + MOZ_ASSERT(false); + break; + } + rtc::LogMessage::LogToDebug(log_level); + if (level != mozilla::LogLevel::Disabled) { + // always capture LOG(...) << ... logging in webrtc.org code to nspr logs + if (!sSink) { + sSink = new LogSinkImpl(); + rtc::LogMessage::AddLogToStream(sSink, log_level); + // it's ok if this leaks to program end + } + } else if (sSink) { + rtc::LogMessage::RemoveLogToStream(sSink); + sSink = nullptr; + } +} + +void StartWebRtcLog(mozilla::LogLevel log_level) { + if (log_level == mozilla::LogLevel::Disabled) { + return; + } + + GetWebRtcLogPrefs(); + mozilla::LogLevel level = CheckOverrides(); + + ConfigWebRtcLog(level); +} + +void EnableWebRtcLog() { + GetWebRtcLogPrefs(); + mozilla::LogLevel level = CheckOverrides(); + ConfigWebRtcLog(level); +} + +// Called when we destroy the singletons from PeerConnectionCtx or if the +// user changes logging in about:webrtc +void StopWebRtcLog() { + if (sSink) { + rtc::LogMessage::RemoveLogToStream(sSink); + sSink = nullptr; + } +} + +nsCString ConfigAecLog() { + nsCString aecLogDir; + if (rtc::LogMessage::aec_debug()) { + return ""_ns; + } +#if defined(ANDROID) + const char* default_tmp_dir = "/dev/null"; + aecLogDir.Assign(default_tmp_dir); +#else + nsCOMPtr tempDir; + nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tempDir)); + if (NS_SUCCEEDED(rv)) { +# ifdef XP_WIN + // WebRTC wants a path encoded in the native charset, not UTF-8. + nsAutoString temp; + tempDir->GetPath(temp); + NS_CopyUnicodeToNative(temp, aecLogDir); +# else + tempDir->GetNativePath(aecLogDir); +# endif + } +#endif + rtc::LogMessage::set_aec_debug_filename(aecLogDir.get()); + + return aecLogDir; +} + +nsCString StartAecLog() { + nsCString aecLogDir; + if (rtc::LogMessage::aec_debug()) { + return ""_ns; + } + + GetWebRtcLogPrefs(); + CheckOverrides(); + aecLogDir = ConfigAecLog(); + + rtc::LogMessage::set_aec_debug(true); + + return aecLogDir; +} + +void StopAecLog() { rtc::LogMessage::set_aec_debug(false); } diff --git a/dom/media/webrtc/common/browser_logging/WebRtcLog.h b/dom/media/webrtc/common/browser_logging/WebRtcLog.h new file mode 100644 index 0000000000..b933ff43a5 --- /dev/null +++ b/dom/media/webrtc/common/browser_logging/WebRtcLog.h @@ -0,0 +1,17 @@ +/* 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/. */ + +#ifndef WEBRTCLOG_H_ +#define WEBRTCLOG_H_ + +#include "mozilla/Logging.h" +#include "nsStringFwd.h" + +nsCString StartAecLog(); +void StopAecLog(); +void EnableWebRtcLog(); +void StartWebRtcLog(mozilla::LogLevel level); +void StopWebRtcLog(); + +#endif diff --git a/dom/media/webrtc/common/csf_common.h b/dom/media/webrtc/common/csf_common.h new file mode 100644 index 0000000000..8b01047553 --- /dev/null +++ b/dom/media/webrtc/common/csf_common.h @@ -0,0 +1,90 @@ +/* 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/. */ + +#ifndef _CSF_COMMON_E58E5677_950A_424c_B6C2_CA180092E6A2_H +#define _CSF_COMMON_E58E5677_950A_424c_B6C2_CA180092E6A2_H + +#include +#include +#include +#include + +/* + +This header file defines: + +csf_countof +csf_sprintf +csf_vsprintf + +*/ + +/* + General security tip: Ensure that "format" is never a user-defined string. + Format should ALWAYS be something that's built into your code, not user + supplied. For example: never write: + + csf_sprintf(buffer, csf_countof(buffer), pUserSuppliedString); + + Instead write: + + csf_sprintf(buffer, csf_countof(buffer), "%s", pUserSuppliedString); + +*/ + +#ifdef WIN32 +# if !defined(_countof) +# if !defined(__cplusplus) +# define _countof(_Array) (sizeof(_Array) / sizeof(_Array[0])) +# else +extern "C++" { +template +char (*_csf_countof_helper(_CountofType (&_Array)[_SizeOfArray]))[_SizeOfArray]; +# define _countof(_Array) sizeof(*_csf_countof_helper(_Array)) +} +# endif +# endif +#else +# define _countof(_Array) (sizeof(_Array) / sizeof(_Array[0])) +#endif +// csf_countof + +#define csf_countof(anArray) _countof(anArray) + +// csf_sprintf + +#ifdef _WIN32 +// Unlike snprintf, sprintf_s guarantees that the buffer will be null-terminated +// (unless the buffer size is zero). +# define csf_sprintf(/* char* */ buffer, \ + /* size_t */ sizeOfBufferInCharsInclNullTerm, \ + /* const char * */ format, ...) \ + _snprintf_s(buffer, sizeOfBufferInCharsInclNullTerm, _TRUNCATE, format, \ + __VA_ARGS__) +#else +# define csf_sprintf(/* char */ buffer, \ + /* size_t */ sizeOfBufferInCharsInclNullTerm, \ + /* const char * */ format, ...) \ + snprintf(buffer, sizeOfBufferInCharsInclNullTerm, format, __VA_ARGS__); \ + buffer[sizeOfBufferInCharsInclNullTerm - 1] = '\0' +#endif + +// csf_vsprintf + +#ifdef _WIN32 +# define csf_vsprintf(/* char* */ buffer, \ + /* size_t */ sizeOfBufferInCharsInclNullTerm, \ + /* const char * */ format, /* va_list */ vaList) \ + vsnprintf_s(buffer, sizeOfBufferInCharsInclNullTerm, _TRUNCATE, format, \ + vaList); \ + buffer[sizeOfBufferInCharsInclNullTerm - 1] = '\0' +#else +# define csf_vsprintf(/* char */ buffer, \ + /* size_t */ sizeOfBufferInCharsInclNullTerm, \ + /* const char * */ format, /* va_list */ vaList) \ + vsprintf(buffer, format, vaList); \ + buffer[sizeOfBufferInCharsInclNullTerm - 1] = '\0' +#endif + +#endif diff --git a/dom/media/webrtc/common/moz.build b/dom/media/webrtc/common/moz.build new file mode 100644 index 0000000000..7605d27835 --- /dev/null +++ b/dom/media/webrtc/common/moz.build @@ -0,0 +1,23 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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("/dom/media/webrtc/third_party_build/webrtc.mozbuild") + +EXPORTS.mozilla.dom += ["CandidateInfo.h"] + +LOCAL_INCLUDES += [ + "/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr", + "/third_party/libwebrtc", + "/third_party/libwebrtc/third_party/abseil-cpp", +] + +UNIFIED_SOURCES += [ + "browser_logging/CSFLog.cpp", + "browser_logging/WebRtcLog.cpp", + "time_profiling/timecard.c", + "YuvStamper.cpp", +] + +FINAL_LIBRARY = "xul" diff --git a/dom/media/webrtc/common/time_profiling/timecard.c b/dom/media/webrtc/common/time_profiling/timecard.c new file mode 100644 index 0000000000..c0218cb92d --- /dev/null +++ b/dom/media/webrtc/common/time_profiling/timecard.c @@ -0,0 +1,112 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 +#include +#include "timecard.h" +#include "mozilla/mozalloc.h" + +Timecard* create_timecard() { + Timecard* tc = moz_xcalloc(1, sizeof(Timecard)); + tc->entries_allocated = TIMECARD_INITIAL_TABLE_SIZE; + tc->entries = moz_xcalloc(tc->entries_allocated, sizeof(TimecardEntry)); + tc->start_time = PR_Now(); + return tc; +} + +void destroy_timecard(Timecard* tc) { + free(tc->entries); + free(tc); +} + +void stamp_timecard(Timecard* tc, const char* event, const char* file, + unsigned int line, const char* function) { + TimecardEntry* entry = NULL; + + /* Trim the path component from the filename */ + const char* last_slash = file; + while (*file) { + if (*file == '/' || *file == '\\') { + last_slash = file; + } + file++; + } + file = last_slash; + if (*file == '/' || *file == '\\') { + file++; + } + + /* Ensure there is enough space left in the entries list */ + if (tc->curr_entry == tc->entries_allocated) { + tc->entries_allocated *= 2; + tc->entries = moz_xrealloc(tc->entries, + tc->entries_allocated * sizeof(TimecardEntry)); + } + + /* Record the data into the timecard entry */ + entry = &tc->entries[tc->curr_entry]; + entry->timestamp = PR_Now(); + entry->event = event; + entry->file = file; + entry->line = line; + entry->function = function; + tc->curr_entry++; +} + +void print_timecard(Timecard* tc) { + size_t i; + TimecardEntry* entry; + size_t event_width = 5; + size_t file_width = 4; + size_t function_width = 8; + size_t line_width; + PRTime offset, delta; + + for (i = 0; i < tc->curr_entry; i++) { + entry = &tc->entries[i]; + if (strlen(entry->event) > event_width) { + event_width = strlen(entry->event); + } + if (strlen(entry->file) > file_width) { + file_width = strlen(entry->file); + } + if (strlen(entry->function) > function_width) { + function_width = strlen(entry->function); + } + } + + printf("\nTimecard created %4ld.%6.6ld\n\n", + (long)(tc->start_time / PR_USEC_PER_SEC), + (long)(tc->start_time % PR_USEC_PER_SEC)); + + line_width = + 1 + 11 + 11 + event_width + file_width + 6 + function_width + (4 * 3); + + printf(" %-11s | %-11s | %-*s | %-*s | %-*s\n", "Timestamp", "Delta", + (int)event_width, "Event", (int)file_width + 6, "File", + (int)function_width, "Function"); + + for (i = 0; i <= line_width; i++) { + printf("="); + } + printf("\n"); + + for (i = 0; i < tc->curr_entry; i++) { + entry = &tc->entries[i]; + offset = entry->timestamp - tc->start_time; + if (i > 0) { + delta = entry->timestamp - tc->entries[i - 1].timestamp; + } else { + delta = entry->timestamp - tc->start_time; + } + printf(" %4ld.%6.6ld | %4ld.%6.6ld | %-*s | %*s:%-5d | %-*s\n", + (long)(offset / PR_USEC_PER_SEC), (long)(offset % PR_USEC_PER_SEC), + (long)(delta / PR_USEC_PER_SEC), (long)(delta % PR_USEC_PER_SEC), + (int)event_width, entry->event, (int)file_width, entry->file, + entry->line, (int)function_width, entry->function); + } + printf("\n"); +} diff --git a/dom/media/webrtc/common/time_profiling/timecard.h b/dom/media/webrtc/common/time_profiling/timecard.h new file mode 100644 index 0000000000..38d4a8d1ec --- /dev/null +++ b/dom/media/webrtc/common/time_profiling/timecard.h @@ -0,0 +1,74 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef timecard_h__ +#define timecard_h__ + +#include +#include "prtime.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define STAMP_TIMECARD(card, event) \ + do { \ + if (card) { \ + stamp_timecard((card), (event), __FILE__, __LINE__, __FUNCTION__); \ + } \ + } while (0) + +#define TIMECARD_INITIAL_TABLE_SIZE 16 + +/* + * The "const char *" members of this structure point to static strings. + * We do not own them, and should not attempt to deallocate them. + */ + +typedef struct { + PRTime timestamp; + const char* event; + const char* file; + unsigned int line; + const char* function; +} TimecardEntry; + +typedef struct Timecard { + size_t curr_entry; + size_t entries_allocated; + TimecardEntry* entries; + PRTime start_time; +} Timecard; + +/** + * Creates a new Timecard structure for tracking events. + */ +Timecard* create_timecard(); + +/** + * Frees the memory associated with a timecard. After returning, the + * timecard pointed to by tc is no longer valid. + */ +void destroy_timecard(Timecard* tc); + +/** + * Records a new event in the indicated timecard. This should not be + * called directly; code should instead use the STAMP_TIMECARD macro, + * above. + */ +void stamp_timecard(Timecard* tc, const char* event, const char* file, + unsigned int line, const char* function); + +/** + * Formats and outputs the contents of a timecard onto stdout. + */ +void print_timecard(Timecard* tc); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/dom/media/webrtc/jsapi/MediaTransportHandler.cpp b/dom/media/webrtc/jsapi/MediaTransportHandler.cpp new file mode 100644 index 0000000000..3ee95f36f6 --- /dev/null +++ b/dom/media/webrtc/jsapi/MediaTransportHandler.cpp @@ -0,0 +1,1727 @@ +/* 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 "MediaTransportHandler.h" +#include "MediaTransportHandlerIPC.h" +#include "transport/nricemediastream.h" +#include "transport/nriceresolver.h" +#include "transport/transportflow.h" +#include "transport/transportlayerice.h" +#include "transport/transportlayerdtls.h" +#include "transport/transportlayersrtp.h" + +// Config stuff +#include "mozilla/dom/RTCConfigurationBinding.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_network.h" + +// Parsing STUN/TURN URIs +#include "nsIURI.h" +#include "nsNetUtil.h" +#include "nsURLHelper.h" +#include "nsIURLParser.h" + +// Logging stuff +#include "common/browser_logging/CSFLog.h" + +// For fetching ICE logging +#include "transport/rlogconnector.h" + +// DTLS +#include "sdp/SdpAttribute.h" + +#include "transport/runnable_utils.h" + +#include "mozilla/Algorithm.h" +#include "mozilla/Telemetry.h" + +#include "mozilla/dom/RTCStatsReportBinding.h" + +#include "nss.h" // For NSS_NoDB_Init +#include "mozilla/PublicSSL.h" // For psm::InitializeCipherSuite + +#include "nsISocketTransportService.h" +#include "nsDNSService2.h" + +#include +#include +#include + +#ifdef MOZ_GECKO_PROFILER +# include "mozilla/ProfilerMarkers.h" + +# define MEDIA_TRANSPORT_HANDLER_PACKET_RECEIVED(aPacket) \ + PROFILER_MARKER_TEXT("WebRTC Packet Received", MEDIA_RT, {}, \ + ProfilerString8View::WrapNullTerminatedString( \ + PacketTypeToString((aPacket).type()))); +#else +# define MEDIA_TRANSPORT_HANDLER_PACKET_RECEIVED(aPacket) +#endif + +namespace mozilla { + +static const char* mthLogTag = "MediaTransportHandler"; +#ifdef LOGTAG +# undef LOGTAG +#endif +#define LOGTAG mthLogTag + +class MediaTransportHandlerSTS : public MediaTransportHandler, + public sigslot::has_slots<> { + public: + explicit MediaTransportHandlerSTS(nsISerialEventTarget* aCallbackThread); + + RefPtr GetIceLog(const nsCString& aPattern) override; + void ClearIceLog() override; + void EnterPrivateMode() override; + void ExitPrivateMode() override; + + void CreateIceCtx(const std::string& aName) override; + + nsresult SetIceConfig(const nsTArray& aIceServers, + dom::RTCIceTransportPolicy aIcePolicy) override; + + // We will probably be able to move the proxy lookup stuff into + // this class once we move mtransport to its own process. + void SetProxyConfig(NrSocketProxyConfig&& aProxyConfig) override; + + void EnsureProvisionalTransport(const std::string& aTransportId, + const std::string& aUfrag, + const std::string& aPwd, + int aComponentCount) override; + + void SetTargetForDefaultLocalAddressLookup(const std::string& aTargetIp, + uint16_t aTargetPort) override; + + // We set default-route-only as late as possible because it depends on what + // capture permissions have been granted on the window, which could easily + // change between Init (ie; when the PC is created) and StartIceGathering + // (ie; when we set the local description). + void StartIceGathering(bool aDefaultRouteOnly, bool aObfuscateHostAddresses, + // This will go away once mtransport moves to its + // own process, because we won't need to get this + // via IPC anymore + const nsTArray& aStunAddrs) override; + + void ActivateTransport( + const std::string& aTransportId, const std::string& aLocalUfrag, + const std::string& aLocalPwd, size_t aComponentCount, + const std::string& aUfrag, const std::string& aPassword, + const nsTArray& aKeyDer, const nsTArray& aCertDer, + SSLKEAType aAuthType, bool aDtlsClient, const DtlsDigestList& aDigests, + bool aPrivacyRequested) override; + + void RemoveTransportsExcept( + const std::set& aTransportIds) override; + + void StartIceChecks(bool aIsControlling, + const std::vector& aIceOptions) override; + + void AddIceCandidate(const std::string& aTransportId, + const std::string& aCandidate, const std::string& aUfrag, + const std::string& aObfuscatedAddress) override; + + void UpdateNetworkState(bool aOnline) override; + + void SendPacket(const std::string& aTransportId, + MediaPacket&& aPacket) override; + + RefPtr GetIceStats(const std::string& aTransportId, + DOMHighResTimeStamp aNow) override; + + void Shutdown(); + + private: + void Destroy() override; + void Destroy_s(); + void DestroyFinal(); + void Shutdown_s(); + RefPtr CreateTransportFlow( + const std::string& aTransportId, bool aIsRtcp, + const RefPtr& aDtlsIdentity, bool aDtlsClient, + const DtlsDigestList& aDigests, bool aPrivacyRequested); + + struct Transport { + RefPtr mFlow; + RefPtr mRtcpFlow; + }; + + using MediaTransportHandler::OnAlpnNegotiated; + using MediaTransportHandler::OnCandidate; + using MediaTransportHandler::OnConnectionStateChange; + using MediaTransportHandler::OnEncryptedSending; + using MediaTransportHandler::OnGatheringStateChange; + using MediaTransportHandler::OnPacketReceived; + using MediaTransportHandler::OnRtcpStateChange; + using MediaTransportHandler::OnStateChange; + + void OnGatheringStateChange(NrIceCtx* aIceCtx, + NrIceCtx::GatheringState aState); + void OnConnectionStateChange(NrIceCtx* aIceCtx, + NrIceCtx::ConnectionState aState); + void OnCandidateFound(NrIceMediaStream* aStream, + const std::string& aCandidate, + const std::string& aUfrag, const std::string& aMDNSAddr, + const std::string& aActualAddr); + void OnStateChange(TransportLayer* aLayer, TransportLayer::State); + void OnRtcpStateChange(TransportLayer* aLayer, TransportLayer::State); + void PacketReceived(TransportLayer* aLayer, MediaPacket& aPacket); + void EncryptedPacketSending(TransportLayer* aLayer, MediaPacket& aPacket); + RefPtr GetTransportFlow(const std::string& aTransportId, + bool aIsRtcp) const; + void GetIceStats(const NrIceMediaStream& aStream, DOMHighResTimeStamp aNow, + dom::RTCStatsCollection* aStats) const; + + virtual ~MediaTransportHandlerSTS() = default; + nsCOMPtr mStsThread; + RefPtr mIceCtx; + RefPtr mDNSResolver; + std::map mTransports; + bool mObfuscateHostAddresses = false; + bool mTurnDisabled = false; + uint32_t mMinDtlsVersion = 0; + uint32_t mMaxDtlsVersion = 0; + bool mForceNoHost = false; + Maybe mNatConfig; + + std::set mSignaledAddresses; + + // Init can only be done on main, but we want this to be usable on any thread + using InitPromise = MozPromise; + RefPtr mInitPromise; +}; + +/* static */ +already_AddRefed MediaTransportHandler::Create( + nsISerialEventTarget* aCallbackThread) { + RefPtr result; + if (XRE_IsContentProcess() && + Preferences::GetBool("media.peerconnection.mtransport_process") && + StaticPrefs::network_process_enabled()) { + result = new MediaTransportHandlerIPC(aCallbackThread); + } else { + result = new MediaTransportHandlerSTS(aCallbackThread); + } + result->Initialize(); + return result.forget(); +} + +class STSShutdownHandler : public nsISTSShutdownObserver { + public: + NS_DECL_ISUPPORTS + + // Lazy singleton + static RefPtr& Instance() { + MOZ_ASSERT(NS_IsMainThread()); + static RefPtr sHandler(new STSShutdownHandler); + return sHandler; + } + + void Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + for (const auto& handler : mHandlers) { + handler->Shutdown(); + } + mHandlers.clear(); + } + + STSShutdownHandler() { + CSFLogDebug(LOGTAG, "%s", __func__); + nsresult res; + nsCOMPtr sts = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &res); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(res)); + MOZ_RELEASE_ASSERT(sts); + sts->AddShutdownObserver(this); + } + + NS_IMETHOD Observe() override { + CSFLogDebug(LOGTAG, "%s", __func__); + Shutdown(); + nsresult res; + nsCOMPtr sts = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &res); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(res)); + MOZ_RELEASE_ASSERT(sts); + sts->RemoveShutdownObserver(this); + Instance() = nullptr; + return NS_OK; + } + + void Register(MediaTransportHandlerSTS* aHandler) { + MOZ_ASSERT(NS_IsMainThread()); + mHandlers.insert(aHandler); + } + + void Deregister(MediaTransportHandlerSTS* aHandler) { + MOZ_ASSERT(NS_IsMainThread()); + mHandlers.erase(aHandler); + } + + private: + virtual ~STSShutdownHandler() = default; + + // Raw ptrs, registered on init, deregistered on destruction, all on main + std::set mHandlers; +}; + +NS_IMPL_ISUPPORTS(STSShutdownHandler, nsISTSShutdownObserver); + +MediaTransportHandlerSTS::MediaTransportHandlerSTS( + nsISerialEventTarget* aCallbackThread) + : MediaTransportHandler(aCallbackThread) { + nsresult rv; + mStsThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + if (!mStsThread) { + MOZ_CRASH(); + } + + RLogConnector::CreateInstance(); + + CSFLogDebug(LOGTAG, "%s done %p", __func__, this); + + // We do not set up mDNSService here, because we are not running on main (we + // use PBackground), and the DNS service asserts. +} + +static NrIceCtx::Policy toNrIcePolicy(dom::RTCIceTransportPolicy aPolicy) { + switch (aPolicy) { + case dom::RTCIceTransportPolicy::Relay: + return NrIceCtx::ICE_POLICY_RELAY; + case dom::RTCIceTransportPolicy::All: + return NrIceCtx::ICE_POLICY_ALL; + default: + MOZ_CRASH(); + } + return NrIceCtx::ICE_POLICY_ALL; +} + +// list of known acceptable ports for webrtc +int16_t gGoodWebrtcPortList[] = { + 53, // Some deplyoments use DNS port to punch through overzealous NATs + 3478, // stun or turn + 5349, // stuns or turns + 0, // Sentinel value: This MUST be zero +}; + +static nsresult addNrIceServer(const nsString& aIceUrl, + const dom::RTCIceServer& aIceServer, + std::vector* aStunServersOut, + std::vector* aTurnServersOut) { + // Without STUN/TURN handlers, NS_NewURI returns nsSimpleURI rather than + // nsStandardURL. To parse STUN/TURN URI's to spec + // http://tools.ietf.org/html/draft-nandakumar-rtcweb-stun-uri-02#section-3 + // http://tools.ietf.org/html/draft-petithuguenin-behave-turn-uri-03#section-3 + // we parse out the query-string, and use ParseAuthority() on the rest + RefPtr url; + nsresult rv = NS_NewURI(getter_AddRefs(url), aIceUrl); + NS_ENSURE_SUCCESS(rv, rv); + bool isStun = url->SchemeIs("stun"); + bool isStuns = url->SchemeIs("stuns"); + bool isTurn = url->SchemeIs("turn"); + bool isTurns = url->SchemeIs("turns"); + if (!(isStun || isStuns || isTurn || isTurns)) { + return NS_ERROR_FAILURE; + } + if (isStuns) { + return NS_OK; // TODO: Support STUNS (Bug 1056934) + } + + nsAutoCString spec; + rv = url->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + // TODO(jib@mozilla.com): Revisit once nsURI supports STUN/TURN (Bug 833509) + int32_t port; + nsAutoCString host; + nsAutoCString transport; + { + uint32_t hostPos; + int32_t hostLen; + nsAutoCString path; + rv = url->GetPathQueryRef(path); + NS_ENSURE_SUCCESS(rv, rv); + + // Tolerate query-string + parse 'transport=[udp|tcp]' by hand. + int32_t questionmark = path.FindChar('?'); + if (questionmark >= 0) { + const nsCString match = "transport="_ns; + + for (int32_t i = questionmark, endPos; i >= 0; i = endPos) { + endPos = path.FindCharInSet("&", i + 1); + const nsDependentCSubstring fieldvaluepair = + Substring(path, i + 1, endPos); + if (StringBeginsWith(fieldvaluepair, match)) { + transport = Substring(fieldvaluepair, match.Length()); + ToLowerCase(transport); + } + } + path.SetLength(questionmark); + } + + rv = net_GetAuthURLParser()->ParseAuthority( + path.get(), static_cast(path.Length()), nullptr, nullptr, nullptr, + nullptr, &hostPos, &hostLen, &port); + NS_ENSURE_SUCCESS(rv, rv); + if (!hostLen) { + return NS_ERROR_FAILURE; + } + if (hostPos > 1) { + /* The username was removed */ + return NS_ERROR_FAILURE; + } + path.Mid(host, hostPos, hostLen); + // Strip off brackets around IPv6 literals + host.Trim("[]"); + } + if (port == -1) port = (isStuns || isTurns) ? 5349 : 3478; + + // First check the known good ports for webrtc + bool goodPort = false; + for (int i = 0; !goodPort && gGoodWebrtcPortList[i]; i++) { + if (port == gGoodWebrtcPortList[i]) { + goodPort = true; + } + } + + // if not in the list of known good ports for webrtc, check + // the generic block list using NS_CheckPortSafety. + if (!goodPort) { + rv = NS_CheckPortSafety(port, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (isStuns || isTurns) { + // Should we barf if transport is set to udp or something? + transport = kNrIceTransportTls; + } + + if (transport.IsEmpty()) { + transport = kNrIceTransportUdp; + } + + if (isTurn || isTurns) { + std::string pwd( + NS_ConvertUTF16toUTF8(aIceServer.mCredential.Value()).get()); + std::string username( + NS_ConvertUTF16toUTF8(aIceServer.mUsername.Value()).get()); + + std::vector password(pwd.begin(), pwd.end()); + + UniquePtr server(NrIceTurnServer::Create( + host.get(), port, username, password, transport.get())); + if (!server) { + return NS_ERROR_FAILURE; + } + if (server->HasFqdn()) { + // Add an IPv4 entry, then an IPv6 entry + aTurnServersOut->push_back(*server); + server->SetUseIPv6IfFqdn(); + } + aTurnServersOut->emplace_back(std::move(*server)); + } else { + UniquePtr server( + NrIceStunServer::Create(host.get(), port, transport.get())); + if (!server) { + return NS_ERROR_FAILURE; + } + if (server->HasFqdn()) { + // Add an IPv4 entry, then an IPv6 entry + aStunServersOut->push_back(*server); + server->SetUseIPv6IfFqdn(); + } + aStunServersOut->emplace_back(std::move(*server)); + } + return NS_OK; +} + +/* static */ +nsresult MediaTransportHandler::ConvertIceServers( + const nsTArray& aIceServers, + std::vector* aStunServers, + std::vector* aTurnServers) { + for (const auto& iceServer : aIceServers) { + NS_ENSURE_STATE(iceServer.mUrls.WasPassed()); + NS_ENSURE_STATE(iceServer.mUrls.Value().IsStringSequence()); + for (const auto& iceUrl : iceServer.mUrls.Value().GetAsStringSequence()) { + nsresult rv = + addNrIceServer(iceUrl, iceServer, aStunServers, aTurnServers); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "%s: invalid STUN/TURN server: %s", __FUNCTION__, + NS_ConvertUTF16toUTF8(iceUrl).get()); + return rv; + } + } + } + + return NS_OK; +} + +static NrIceCtx::GlobalConfig GetGlobalConfig() { + NrIceCtx::GlobalConfig config; + config.mAllowLinkLocal = + Preferences::GetBool("media.peerconnection.ice.link_local", false); + config.mAllowLoopback = + Preferences::GetBool("media.peerconnection.ice.loopback", false); + config.mTcpEnabled = + Preferences::GetBool("media.peerconnection.ice.tcp", false); + config.mStunClientMaxTransmits = Preferences::GetInt( + "media.peerconnection.ice.stun_client_maximum_transmits", + config.mStunClientMaxTransmits); + config.mTrickleIceGracePeriod = + Preferences::GetInt("media.peerconnection.ice.trickle_grace_period", + config.mTrickleIceGracePeriod); + config.mIceTcpSoSockCount = Preferences::GetInt( + "media.peerconnection.ice.tcp_so_sock_count", config.mIceTcpSoSockCount); + config.mIceTcpListenBacklog = + Preferences::GetInt("media.peerconnection.ice.tcp_listen_backlog", + config.mIceTcpListenBacklog); + (void)Preferences::GetCString("media.peerconnection.ice.force_interface", + config.mForceNetInterface); + return config; +} + +static Maybe GetNatConfig() { + bool block_tcp = Preferences::GetBool( + "media.peerconnection.nat_simulator.block_tcp", false); + bool block_udp = Preferences::GetBool( + "media.peerconnection.nat_simulator.block_udp", false); + bool block_tls = Preferences::GetBool( + "media.peerconnection.nat_simulator.block_tls", false); + int error_code_for_drop = Preferences::GetInt( + "media.peerconnection.nat_simulator.error_code_for_drop", 0); + nsAutoCString mapping_type; + (void)Preferences::GetCString( + "media.peerconnection.nat_simulator.mapping_type", mapping_type); + nsAutoCString filtering_type; + (void)Preferences::GetCString( + "media.peerconnection.nat_simulator.filtering_type", filtering_type); + nsAutoCString redirect_address; + (void)Preferences::GetCString( + "media.peerconnection.nat_simulator.redirect_address", redirect_address); + nsAutoCString redirect_targets; + (void)Preferences::GetCString( + "media.peerconnection.nat_simulator.redirect_targets", redirect_targets); + + if (block_udp || block_tcp || block_tls || !mapping_type.IsEmpty() || + !filtering_type.IsEmpty() || !redirect_address.IsEmpty()) { + CSFLogDebug(LOGTAG, "NAT filtering type: %s", filtering_type.get()); + CSFLogDebug(LOGTAG, "NAT mapping type: %s", mapping_type.get()); + NrIceCtx::NatSimulatorConfig natConfig; + natConfig.mBlockUdp = block_udp; + natConfig.mBlockTcp = block_tcp; + natConfig.mBlockTls = block_tls; + natConfig.mErrorCodeForDrop = error_code_for_drop; + natConfig.mFilteringType = filtering_type; + natConfig.mMappingType = mapping_type; + if (redirect_address.Length()) { + CSFLogDebug(LOGTAG, "Redirect address: %s", redirect_address.get()); + CSFLogDebug(LOGTAG, "Redirect targets: %s", redirect_targets.get()); + natConfig.mRedirectAddress = redirect_address; + std::stringstream str(redirect_targets.Data()); + std::string target; + while (getline(str, target, ',')) { + CSFLogDebug(LOGTAG, "Adding target: %s", target.c_str()); + natConfig.mRedirectTargets.AppendElement(target); + } + } + return Some(natConfig); + } + return Nothing(); +} + +void MediaTransportHandlerSTS::CreateIceCtx(const std::string& aName) { + mInitPromise = InvokeAsync( + GetMainThreadSerialEventTarget(), __func__, + [=, self = RefPtr(this)]() { + CSFLogDebug(LOGTAG, "%s starting", __func__); + if (!NSS_IsInitialized()) { + if (NSS_NoDB_Init(nullptr) != SECSuccess) { + MOZ_CRASH(); + return InitPromise::CreateAndReject("NSS_NoDB_Init failed", + __func__); + } + + if (NS_FAILED(mozilla::psm::InitializeCipherSuite())) { + MOZ_CRASH(); + return InitPromise::CreateAndReject("InitializeCipherSuite failed", + __func__); + } + + mozilla::psm::DisableMD5(); + } + + static bool globalInitDone = false; + if (!globalInitDone) { + // Ensure the DNS service is initted for the first time on main + DebugOnly> dnsService = + RefPtr(nsDNSService::GetXPCOMSingleton()); + MOZ_ASSERT(dnsService.value); + mStsThread->Dispatch( + WrapRunnableNM(&NrIceCtx::InitializeGlobals, GetGlobalConfig()), + NS_DISPATCH_NORMAL); + globalInitDone = true; + } + + // Give us a way to globally turn off TURN support + mTurnDisabled = + Preferences::GetBool("media.peerconnection.turn.disable", false); + // We are reading these here, because when we setup the DTLS transport + // we are on the wrong thread to read prefs + mMinDtlsVersion = + Preferences::GetUint("media.peerconnection.dtls.version.min"); + mMaxDtlsVersion = + Preferences::GetUint("media.peerconnection.dtls.version.max"); + mForceNoHost = + Preferences::GetBool("media.peerconnection.ice.no_host", false); + mNatConfig = GetNatConfig(); + + MOZ_RELEASE_ASSERT(STSShutdownHandler::Instance()); + STSShutdownHandler::Instance()->Register(this); + + return InvokeAsync( + mStsThread, __func__, + [=, self = RefPtr(this)]() { + mIceCtx = NrIceCtx::Create(aName); + if (!mIceCtx) { + return InitPromise::CreateAndReject("NrIceCtx::Create failed", + __func__); + } + + mIceCtx->SignalGatheringStateChange.connect( + this, &MediaTransportHandlerSTS::OnGatheringStateChange); + mIceCtx->SignalConnectionStateChange.connect( + this, &MediaTransportHandlerSTS::OnConnectionStateChange); + + mDNSResolver = new NrIceResolver; + nsresult rv; + if (NS_FAILED(rv = mDNSResolver->Init())) { + CSFLogError(LOGTAG, "%s: Failed to initialize dns resolver", + __FUNCTION__); + return InitPromise::CreateAndReject( + "Failed to initialize dns resolver", __func__); + } + if (NS_FAILED(rv = mIceCtx->SetResolver( + mDNSResolver->AllocateResolver()))) { + CSFLogError(LOGTAG, "%s: Failed to get dns resolver", + __FUNCTION__); + return InitPromise::CreateAndReject( + "Failed to get dns resolver", __func__); + } + + CSFLogDebug(LOGTAG, "%s done", __func__); + return InitPromise::CreateAndResolve(true, __func__); + }); + }); +} + +nsresult MediaTransportHandlerSTS::SetIceConfig( + const nsTArray& aIceServers, + dom::RTCIceTransportPolicy aIcePolicy) { + // We rely on getting an error when this happens, so do it up front. + std::vector stunServers; + std::vector turnServers; + nsresult rv = ConvertIceServers(aIceServers, &stunServers, &turnServers); + if (NS_FAILED(rv)) { + return rv; + } + + MOZ_RELEASE_ASSERT(mInitPromise); + + mInitPromise->Then( + mStsThread, __func__, + [=, self = RefPtr(this)]() { + if (!mIceCtx) { + CSFLogError(LOGTAG, "%s: mIceCtx is null", __FUNCTION__); + return; + } + NrIceCtx::Config config; + config.mPolicy = toNrIcePolicy(aIcePolicy); + if (config.mPolicy == NrIceCtx::ICE_POLICY_ALL && mForceNoHost) { + config.mPolicy = NrIceCtx::ICE_POLICY_NO_HOST; + } + config.mNatSimulatorConfig = mNatConfig; + + nsresult rv; + + if (NS_FAILED(rv = mIceCtx->SetStunServers(stunServers))) { + CSFLogError(LOGTAG, "%s: Failed to set stun servers", __FUNCTION__); + return; + } + if (!mTurnDisabled) { + if (NS_FAILED(rv = mIceCtx->SetTurnServers(turnServers))) { + CSFLogError(LOGTAG, "%s: Failed to set turn servers", __FUNCTION__); + return; + } + } else if (!turnServers.empty()) { + CSFLogError(LOGTAG, "%s: Setting turn servers disabled", + __FUNCTION__); + } + if (NS_FAILED(rv = mIceCtx->SetIceConfig(config))) { + CSFLogError(LOGTAG, "%s: Failed to set config", __FUNCTION__); + } + }); + + return NS_OK; +} + +void MediaTransportHandlerSTS::Shutdown() { + CSFLogDebug(LOGTAG, "%s", __func__); + MOZ_ASSERT(NS_IsMainThread()); + mStsThread->Dispatch(NewNonOwningRunnableMethod( + __func__, this, &MediaTransportHandlerSTS::Shutdown_s)); +} + +void MediaTransportHandlerSTS::Shutdown_s() { + CSFLogDebug(LOGTAG, "%s", __func__); + disconnect_all(); + // Clear the transports before destroying the ice ctx so that + // the close_notify alerts have a chance to be sent as the + // TransportFlow destructors execute. + mTransports.clear(); + if (mIceCtx) { + NrIceStats stats = mIceCtx->Destroy(); + CSFLogDebug(LOGTAG, + "Ice Telemetry: stun (retransmits: %d)" + " turn (401s: %d 403s: %d 438s: %d)", + stats.stun_retransmits, stats.turn_401s, stats.turn_403s, + stats.turn_438s); + } + mIceCtx = nullptr; + mDNSResolver = nullptr; +} + +void MediaTransportHandlerSTS::Destroy() { + CSFLogDebug(LOGTAG, "%s %p", __func__, this); + // Our "destruction tour" starts on main, because we need to deregister. + if (!NS_IsMainThread()) { + GetMainThreadSerialEventTarget()->Dispatch( + NewNonOwningRunnableMethod("MediaTransportHandlerSTS::Destroy", this, + &MediaTransportHandlerSTS::Destroy)); + return; + } + + MOZ_ASSERT(NS_IsMainThread()); + if (STSShutdownHandler::Instance()) { + STSShutdownHandler::Instance()->Deregister(this); + Shutdown(); + } + + // mIceCtx still has a reference to us via sigslot! We must dispach to STS, + // and clean up there. However, by the time _that_ happens, we may have + // dispatched a signal callback to mCallbackThread, so we have to dispatch + // the final destruction to mCallbackThread. + nsresult rv = mStsThread->Dispatch( + NewNonOwningRunnableMethod("MediaTransportHandlerSTS::Destroy_s", this, + &MediaTransportHandlerSTS::Destroy_s)); + if (NS_WARN_IF(NS_FAILED(rv))) { + CSFLogError(LOGTAG, + "Unable to dispatch to STS: why has the XPCOM shutdown handler " + "not been invoked?"); + delete this; + } +} + +void MediaTransportHandlerSTS::Destroy_s() { + if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) { + nsresult rv = mCallbackThread->Dispatch(NewNonOwningRunnableMethod( + __func__, this, &MediaTransportHandlerSTS::DestroyFinal)); + if (NS_SUCCEEDED(rv)) { + return; + } + } + + DestroyFinal(); +} + +void MediaTransportHandlerSTS::DestroyFinal() { delete this; } + +void MediaTransportHandlerSTS::SetProxyConfig( + NrSocketProxyConfig&& aProxyConfig) { + MOZ_RELEASE_ASSERT(mInitPromise); + + mInitPromise->Then( + mStsThread, __func__, + [this, self = RefPtr(this), + aProxyConfig = std::move(aProxyConfig)]() mutable { + if (!mIceCtx) { + return; // Probably due to XPCOM shutdown + } + + mIceCtx->SetProxyConfig(std::move(aProxyConfig)); + }, + [](const std::string& aError) {}); +} + +void MediaTransportHandlerSTS::EnsureProvisionalTransport( + const std::string& aTransportId, const std::string& aUfrag, + const std::string& aPwd, int aComponentCount) { + MOZ_RELEASE_ASSERT(mInitPromise); + + mInitPromise->Then( + mStsThread, __func__, + [=, self = RefPtr(this)]() { + if (!mIceCtx) { + return; // Probably due to XPCOM shutdown + } + + RefPtr stream(mIceCtx->GetStream(aTransportId)); + if (!stream) { + CSFLogDebug(LOGTAG, "%s: Creating ICE media stream=%s components=%d", + mIceCtx->name().c_str(), aTransportId.c_str(), + aComponentCount); + + std::ostringstream os; + os << mIceCtx->name() << " transport-id=" << aTransportId; + stream = + mIceCtx->CreateStream(aTransportId, os.str(), aComponentCount); + + if (!stream) { + CSFLogError(LOGTAG, "Failed to create ICE stream."); + return; + } + + stream->SignalCandidate.connect( + this, &MediaTransportHandlerSTS::OnCandidateFound); + } + + // Begins an ICE restart if this stream has a different ufrag/pwd + stream->SetIceCredentials(aUfrag, aPwd); + + // Make sure there's an entry in mTransports + mTransports[aTransportId]; + }, + [](const std::string& aError) {}); +} + +void MediaTransportHandlerSTS::ActivateTransport( + const std::string& aTransportId, const std::string& aLocalUfrag, + const std::string& aLocalPwd, size_t aComponentCount, + const std::string& aUfrag, const std::string& aPassword, + const nsTArray& aKeyDer, const nsTArray& aCertDer, + SSLKEAType aAuthType, bool aDtlsClient, const DtlsDigestList& aDigests, + bool aPrivacyRequested) { + MOZ_RELEASE_ASSERT(mInitPromise); + + mInitPromise->Then( + mStsThread, __func__, + [=, keyDer = aKeyDer.Clone(), certDer = aCertDer.Clone(), + self = RefPtr(this)]() { + if (!mIceCtx) { + return; // Probably due to XPCOM shutdown + } + + MOZ_ASSERT(aComponentCount); + RefPtr dtlsIdentity( + DtlsIdentity::Deserialize(keyDer, certDer, aAuthType)); + if (!dtlsIdentity) { + MOZ_ASSERT(false); + return; + } + + RefPtr stream(mIceCtx->GetStream(aTransportId)); + if (!stream) { + MOZ_ASSERT(false); + return; + } + + CSFLogDebug(LOGTAG, "%s: Activating ICE media stream=%s components=%u", + mIceCtx->name().c_str(), aTransportId.c_str(), + static_cast(aComponentCount)); + + std::vector attrs; + attrs.reserve(2 /* ufrag + pwd */); + attrs.push_back("ice-ufrag:" + aUfrag); + attrs.push_back("ice-pwd:" + aPassword); + + // If we started an ICE restart in EnsureProvisionalTransport, this is + // where we decide whether to commit or rollback. + nsresult rv = stream->ConnectToPeer(aLocalUfrag, aLocalPwd, attrs); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "Couldn't parse ICE attributes, rv=%u", + static_cast(rv)); + MOZ_ASSERT(false); + return; + } + + Transport transport = mTransports[aTransportId]; + if (!transport.mFlow) { + transport.mFlow = + CreateTransportFlow(aTransportId, false, dtlsIdentity, + aDtlsClient, aDigests, aPrivacyRequested); + if (!transport.mFlow) { + return; + } + TransportLayer* dtls = + transport.mFlow->GetLayer(TransportLayerDtls::ID()); + dtls->SignalStateChange.connect( + this, &MediaTransportHandlerSTS::OnStateChange); + if (aComponentCount < 2) { + dtls->SignalStateChange.connect( + this, &MediaTransportHandlerSTS::OnRtcpStateChange); + } + } + + if (aComponentCount == 2) { + if (!transport.mRtcpFlow) { + transport.mRtcpFlow = + CreateTransportFlow(aTransportId, true, dtlsIdentity, + aDtlsClient, aDigests, aPrivacyRequested); + if (!transport.mRtcpFlow) { + return; + } + TransportLayer* dtls = + transport.mRtcpFlow->GetLayer(TransportLayerDtls::ID()); + dtls->SignalStateChange.connect( + this, &MediaTransportHandlerSTS::OnRtcpStateChange); + } + } else { + transport.mRtcpFlow = nullptr; + // components are 1-indexed + stream->DisableComponent(2); + } + + mTransports[aTransportId] = transport; + }, + [](const std::string& aError) {}); +} + +void MediaTransportHandlerSTS::SetTargetForDefaultLocalAddressLookup( + const std::string& aTargetIp, uint16_t aTargetPort) { + MOZ_RELEASE_ASSERT(mInitPromise); + + mInitPromise->Then( + mStsThread, __func__, + [=, self = RefPtr(this)]() { + if (!mIceCtx) { + return; // Probably due to XPCOM shutdown + } + + mIceCtx->SetTargetForDefaultLocalAddressLookup(aTargetIp, aTargetPort); + }, + [](const std::string& aError) {}); +} + +void MediaTransportHandlerSTS::StartIceGathering( + bool aDefaultRouteOnly, bool aObfuscateHostAddresses, + const nsTArray& aStunAddrs) { + MOZ_RELEASE_ASSERT(mInitPromise); + + mInitPromise->Then( + mStsThread, __func__, + [=, stunAddrs = aStunAddrs.Clone(), + self = RefPtr(this)]() { + if (!mIceCtx) { + return; // Probably due to XPCOM shutdown + } + + mObfuscateHostAddresses = aObfuscateHostAddresses; + + // Belt and suspenders - in e10s mode, the call below to SetStunAddrs + // needs to have the proper flags set on ice ctx. For non-e10s, + // setting those flags happens in StartGathering. We could probably + // just set them here, and only do it here. + mIceCtx->SetCtxFlags(aDefaultRouteOnly); + + if (stunAddrs.Length()) { + mIceCtx->SetStunAddrs(stunAddrs); + } + + // Start gathering, but only if there are streams + if (!mIceCtx->GetStreams().empty()) { + mIceCtx->StartGathering(aDefaultRouteOnly, aObfuscateHostAddresses); + } + }, + [](const std::string& aError) {}); +} + +void MediaTransportHandlerSTS::StartIceChecks( + bool aIsControlling, const std::vector& aIceOptions) { + MOZ_RELEASE_ASSERT(mInitPromise); + + mInitPromise->Then( + mStsThread, __func__, + [=, self = RefPtr(this)]() { + if (!mIceCtx) { + return; // Probably due to XPCOM shutdown + } + + nsresult rv = mIceCtx->ParseGlobalAttributes(aIceOptions); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "%s: couldn't parse global parameters", + __FUNCTION__); + return; + } + + rv = mIceCtx->SetControlling(aIsControlling ? NrIceCtx::ICE_CONTROLLING + : NrIceCtx::ICE_CONTROLLED); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "%s: couldn't set controlling to %d", + __FUNCTION__, aIsControlling); + return; + } + + rv = mIceCtx->StartChecks(); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "%s: couldn't start checks", __FUNCTION__); + return; + } + }, + [](const std::string& aError) {}); +} + +void TokenizeCandidate(const std::string& aCandidate, + std::vector& aTokens) { + aTokens.clear(); + + std::istringstream iss(aCandidate); + std::string token; + while (std::getline(iss, token, ' ')) { + aTokens.push_back(token); + } +} + +void MediaTransportHandlerSTS::AddIceCandidate( + const std::string& aTransportId, const std::string& aCandidate, + const std::string& aUfrag, const std::string& aObfuscatedAddress) { + MOZ_RELEASE_ASSERT(mInitPromise); + + mInitPromise->Then( + mStsThread, __func__, + [=, self = RefPtr(this)]() { + if (!mIceCtx) { + return; // Probably due to XPCOM shutdown + } + + std::vector tokens; + TokenizeCandidate(aCandidate, tokens); + + RefPtr stream(mIceCtx->GetStream(aTransportId)); + if (!stream) { + CSFLogError(LOGTAG, + "No ICE stream for candidate with transport id %s: %s", + aTransportId.c_str(), aCandidate.c_str()); + return; + } + + nsresult rv = stream->ParseTrickleCandidate(aCandidate, aUfrag, + aObfuscatedAddress); + if (NS_SUCCEEDED(rv)) { + // If the address is not obfuscated, we want to track it as + // explicitly signaled so that we know it is fine to reveal + // the address later on. + if (mObfuscateHostAddresses && tokens.size() > 4 && + aObfuscatedAddress.empty()) { + mSignaledAddresses.insert(tokens[4]); + } + } else { + CSFLogError(LOGTAG, + "Couldn't process ICE candidate with transport id %s: " + "%s", + aTransportId.c_str(), aCandidate.c_str()); + } + }, + [](const std::string& aError) {}); +} + +void MediaTransportHandlerSTS::UpdateNetworkState(bool aOnline) { + MOZ_RELEASE_ASSERT(mInitPromise); + + mInitPromise->Then( + mStsThread, __func__, + [=, self = RefPtr(this)]() { + if (!mIceCtx) { + return; // Probably due to XPCOM shutdown + } + + mIceCtx->UpdateNetworkState(aOnline); + }, + [](const std::string& aError) {}); +} + +void MediaTransportHandlerSTS::RemoveTransportsExcept( + const std::set& aTransportIds) { + MOZ_RELEASE_ASSERT(mInitPromise); + + mInitPromise->Then( + mStsThread, __func__, + [=, self = RefPtr(this)]() { + if (!mIceCtx) { + return; // Probably due to XPCOM shutdown + } + + for (auto it = mTransports.begin(); it != mTransports.end();) { + const std::string transportId(it->first); + if (!aTransportIds.count(transportId)) { + if (it->second.mFlow) { + OnStateChange(transportId, TransportLayer::TS_NONE); + OnRtcpStateChange(transportId, TransportLayer::TS_NONE); + } + // Erase the transport before destroying the ice stream so that + // the close_notify alerts have a chance to be sent as the + // TransportFlow destructors execute. + it = mTransports.erase(it); + // We're already on the STS thread, but the TransportFlow + // destructor executed when mTransports.erase(it) is called + // above dispatches the call to DestroyFinal to the STS thread. If + // we don't also dispatch the call to destroy the NrIceMediaStream + // to the STS thread, it will tear down the NrIceMediaStream + // before the TransportFlow is destroyed. Without a valid + // NrIceMediaStream the close_notify alert cannot be sent. + mStsThread->Dispatch(NS_NewRunnableFunction( + __func__, [iceCtx = RefPtr(mIceCtx), transportId] { + iceCtx->DestroyStream(transportId); + })); + } else { + MOZ_ASSERT(it->second.mFlow); + ++it; + } + } + }, + [](const std::string& aError) {}); +} + +void MediaTransportHandlerSTS::SendPacket(const std::string& aTransportId, + MediaPacket&& aPacket) { + MOZ_RELEASE_ASSERT(mInitPromise); + + mInitPromise->Then( + mStsThread, __func__, + [this, self = RefPtr(this), aTransportId, + aPacket = std::move(aPacket)]() mutable { + if (!mIceCtx) { + return; // Probably due to XPCOM shutdown + } + + MOZ_ASSERT(aPacket.type() != MediaPacket::UNCLASSIFIED); + RefPtr flow = + GetTransportFlow(aTransportId, aPacket.type() == MediaPacket::RTCP); + + if (!flow) { + CSFLogError(LOGTAG, + "%s: No such transport flow (%s) for outgoing packet", + mIceCtx->name().c_str(), aTransportId.c_str()); + return; + } + + TransportLayer* layer = nullptr; + switch (aPacket.type()) { + case MediaPacket::SCTP: + layer = flow->GetLayer(TransportLayerDtls::ID()); + break; + case MediaPacket::RTP: + case MediaPacket::RTCP: + layer = flow->GetLayer(TransportLayerSrtp::ID()); + break; + default: + // Maybe it would be useful to allow the injection of other packet + // types for testing? + MOZ_ASSERT(false); + return; + } + + MOZ_ASSERT(layer); + + if (layer->SendPacket(aPacket) < 0) { + CSFLogError(LOGTAG, "%s: Transport flow (%s) failed to send packet", + mIceCtx->name().c_str(), aTransportId.c_str()); + } + }, + [](const std::string& aError) {}); +} + +TransportLayer::State MediaTransportHandler::GetState( + const std::string& aTransportId, bool aRtcp) const { + // TODO Bug 1520692: we should allow Datachannel to connect without + // DTLS SRTP keys + if (mCallbackThread) { + MOZ_ASSERT(mCallbackThread->IsOnCurrentThread()); + } + + const std::map* cache = nullptr; + if (aRtcp) { + cache = &mRtcpStateCache; + } else { + cache = &mStateCache; + } + + auto it = cache->find(aTransportId); + if (it != cache->end()) { + return it->second; + } + return TransportLayer::TS_NONE; +} + +void MediaTransportHandler::OnCandidate(const std::string& aTransportId, + const CandidateInfo& aCandidateInfo) { + if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) { + mCallbackThread->Dispatch( + // This is being called from sigslot, which does not hold a strong ref. + WrapRunnable(this, &MediaTransportHandler::OnCandidate, aTransportId, + aCandidateInfo), + NS_DISPATCH_NORMAL); + return; + } + + SignalCandidate(aTransportId, aCandidateInfo); +} + +void MediaTransportHandler::OnAlpnNegotiated(const std::string& aAlpn) { + if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) { + mCallbackThread->Dispatch( + // This is being called from sigslot, which does not hold a strong ref. + WrapRunnable(this, &MediaTransportHandler::OnAlpnNegotiated, aAlpn), + NS_DISPATCH_NORMAL); + return; + } + + const bool privacyRequested = aAlpn == "c-webrtc"; + SignalAlpnNegotiated(aAlpn, privacyRequested); +} + +void MediaTransportHandler::OnGatheringStateChange( + dom::RTCIceGatheringState aState) { + if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) { + mCallbackThread->Dispatch( + // This is being called from sigslot, which does not hold a strong ref. + WrapRunnable(this, &MediaTransportHandler::OnGatheringStateChange, + aState), + NS_DISPATCH_NORMAL); + return; + } + + SignalGatheringStateChange(aState); +} + +void MediaTransportHandler::OnConnectionStateChange( + dom::RTCIceConnectionState aState) { + if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) { + mCallbackThread->Dispatch( + // This is being called from sigslot, which does not hold a strong ref. + WrapRunnable(this, &MediaTransportHandler::OnConnectionStateChange, + aState), + NS_DISPATCH_NORMAL); + return; + } + + SignalConnectionStateChange(aState); +} + +void MediaTransportHandler::OnPacketReceived(const std::string& aTransportId, + const MediaPacket& aPacket) { + if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) { + mCallbackThread->Dispatch( + // This is being called from sigslot, which does not hold a strong ref. + WrapRunnable(this, &MediaTransportHandler::OnPacketReceived, + aTransportId, aPacket.Clone()), + NS_DISPATCH_NORMAL); + return; + } + + SignalPacketReceived(aTransportId, aPacket); +} + +void MediaTransportHandler::OnEncryptedSending(const std::string& aTransportId, + const MediaPacket& aPacket) { + if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) { + mCallbackThread->Dispatch( + // This is being called from sigslot, which does not hold a strong ref. + WrapRunnable(this, &MediaTransportHandler::OnEncryptedSending, + aTransportId, aPacket.Clone()), + NS_DISPATCH_NORMAL); + return; + } + + SignalEncryptedSending(aTransportId, aPacket); +} + +void MediaTransportHandler::OnStateChange(const std::string& aTransportId, + TransportLayer::State aState) { + if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) { + mCallbackThread->Dispatch( + // This is being called from sigslot, which does not hold a strong ref. + WrapRunnable(this, &MediaTransportHandler::OnStateChange, aTransportId, + aState), + NS_DISPATCH_NORMAL); + return; + } + + if (aState == TransportLayer::TS_NONE) { + mStateCache.erase(aTransportId); + } else { + mStateCache[aTransportId] = aState; + } + SignalStateChange(aTransportId, aState); +} + +void MediaTransportHandler::OnRtcpStateChange(const std::string& aTransportId, + TransportLayer::State aState) { + if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) { + mCallbackThread->Dispatch( + // This is being called from sigslot, which does not hold a strong ref. + WrapRunnable(this, &MediaTransportHandler::OnRtcpStateChange, + aTransportId, aState), + NS_DISPATCH_NORMAL); + return; + } + + if (aState == TransportLayer::TS_NONE) { + mRtcpStateCache.erase(aTransportId); + } else { + mRtcpStateCache[aTransportId] = aState; + } + SignalRtcpStateChange(aTransportId, aState); +} + +RefPtr MediaTransportHandlerSTS::GetIceStats( + const std::string& aTransportId, DOMHighResTimeStamp aNow) { + MOZ_RELEASE_ASSERT(mInitPromise); + + return mInitPromise->Then(mStsThread, __func__, [=, self = RefPtr(this)]() { + UniquePtr stats(new dom::RTCStatsCollection); + if (mIceCtx) { + for (const auto& stream : mIceCtx->GetStreams()) { + if (aTransportId.empty() || aTransportId == stream->GetId()) { + GetIceStats(*stream, aNow, stats.get()); + } + } + } + return dom::RTCStatsPromise::CreateAndResolve(std::move(stats), __func__); + }); +} + +RefPtr +MediaTransportHandlerSTS::GetIceLog(const nsCString& aPattern) { + return InvokeAsync( + mStsThread, __func__, [=, self = RefPtr(this)] { + dom::Sequence converted; + RLogConnector* logs = RLogConnector::GetInstance(); + std::deque result; + // Might not exist yet. + if (logs) { + logs->Filter(aPattern.get(), 0, &result); + } + /// XXX(Bug 1631386) Check if we should reject the promise instead of + /// crashing in an OOM situation. + if (!converted.SetCapacity(result.size(), fallible)) { + mozalloc_handle_oom(sizeof(nsString) * result.size()); + } + for (auto& line : result) { + // Cannot fail, SetCapacity was called before. + (void)converted.AppendElement(NS_ConvertUTF8toUTF16(line.c_str()), + fallible); + } + return IceLogPromise::CreateAndResolve(std::move(converted), __func__); + }); +} + +void MediaTransportHandlerSTS::ClearIceLog() { + if (!mStsThread->IsOnCurrentThread()) { + mStsThread->Dispatch(WrapRunnable(RefPtr(this), + &MediaTransportHandlerSTS::ClearIceLog), + NS_DISPATCH_NORMAL); + return; + } + + RLogConnector* logs = RLogConnector::GetInstance(); + if (logs) { + logs->Clear(); + } +} + +void MediaTransportHandlerSTS::EnterPrivateMode() { + if (!mStsThread->IsOnCurrentThread()) { + mStsThread->Dispatch( + WrapRunnable(RefPtr(this), + &MediaTransportHandlerSTS::EnterPrivateMode), + NS_DISPATCH_NORMAL); + return; + } + + RLogConnector::GetInstance()->EnterPrivateMode(); +} + +void MediaTransportHandlerSTS::ExitPrivateMode() { + if (!mStsThread->IsOnCurrentThread()) { + mStsThread->Dispatch( + WrapRunnable(RefPtr(this), + &MediaTransportHandlerSTS::ExitPrivateMode), + NS_DISPATCH_NORMAL); + return; + } + + auto* log = RLogConnector::GetInstance(); + MOZ_ASSERT(log); + if (log) { + log->ExitPrivateMode(); + } +} + +static void ToRTCIceCandidateStats( + const std::vector& candidates, + dom::RTCStatsType candidateType, const nsString& transportId, + DOMHighResTimeStamp now, dom::RTCStatsCollection* stats, + bool obfuscateHostAddresses, + const std::set& signaledAddresses) { + MOZ_ASSERT(stats); + for (const auto& candidate : candidates) { + dom::RTCIceCandidateStats cand; + cand.mType.Construct(candidateType); + NS_ConvertASCIItoUTF16 codeword(candidate.codeword.c_str()); + cand.mTransportId.Construct(transportId); + cand.mId.Construct(codeword); + cand.mTimestamp.Construct(now); + cand.mCandidateType.Construct(dom::RTCIceCandidateType(candidate.type)); + cand.mPriority.Construct(candidate.priority); + // https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-03#section-3.3.1 + // This obfuscates the address with the mDNS address if one exists + if (!candidate.mdns_addr.empty()) { + cand.mAddress.Construct( + NS_ConvertASCIItoUTF16(candidate.mdns_addr.c_str())); + } else if (obfuscateHostAddresses && + candidate.type == NrIceCandidate::ICE_PEER_REFLEXIVE && + signaledAddresses.find(candidate.cand_addr.host) == + signaledAddresses.end()) { + cand.mAddress.Construct(NS_ConvertASCIItoUTF16("(redacted)")); + } else { + cand.mAddress.Construct( + NS_ConvertASCIItoUTF16(candidate.cand_addr.host.c_str())); + } + cand.mPort.Construct(candidate.cand_addr.port); + cand.mProtocol.Construct( + NS_ConvertASCIItoUTF16(candidate.cand_addr.transport.c_str())); + if (candidateType == dom::RTCStatsType::Local_candidate && + dom::RTCIceCandidateType(candidate.type) == + dom::RTCIceCandidateType::Relay) { + cand.mRelayProtocol.Construct( + NS_ConvertASCIItoUTF16(candidate.local_addr.transport.c_str())); + } + cand.mProxied.Construct(NS_ConvertASCIItoUTF16( + candidate.is_proxied ? "proxied" : "non-proxied")); + if (!stats->mIceCandidateStats.AppendElement(cand, fallible)) { + // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might + // involve multiple reallocations) and potentially crashing here, + // SetCapacity could be called outside the loop once. + mozalloc_handle_oom(0); + } + if (candidate.trickled) { + if (!stats->mTrickledIceCandidateStats.AppendElement(cand, fallible)) { + mozalloc_handle_oom(0); + } + } + } +} + +void MediaTransportHandlerSTS::GetIceStats( + const NrIceMediaStream& aStream, DOMHighResTimeStamp aNow, + dom::RTCStatsCollection* aStats) const { + MOZ_ASSERT(mStsThread->IsOnCurrentThread()); + + NS_ConvertASCIItoUTF16 transportId(aStream.GetId().c_str()); + + std::vector candPairs; + nsresult res = aStream.GetCandidatePairs(&candPairs); + if (NS_FAILED(res)) { + CSFLogError(LOGTAG, + "%s: Error getting candidate pairs for transport id \"%s\"", + __FUNCTION__, aStream.GetId().c_str()); + return; + } + + for (auto& candPair : candPairs) { + NS_ConvertASCIItoUTF16 codeword(candPair.codeword.c_str()); + NS_ConvertASCIItoUTF16 localCodeword(candPair.local.codeword.c_str()); + NS_ConvertASCIItoUTF16 remoteCodeword(candPair.remote.codeword.c_str()); + // Only expose candidate-pair statistics to chrome, until we've thought + // through the implications of exposing it to content. + + dom::RTCIceCandidatePairStats s; + s.mId.Construct(codeword); + s.mTransportId.Construct(transportId); + s.mTimestamp.Construct(aNow); + s.mType.Construct(dom::RTCStatsType::Candidate_pair); + s.mLocalCandidateId.Construct(localCodeword); + s.mRemoteCandidateId.Construct(remoteCodeword); + s.mNominated.Construct(candPair.nominated); + s.mWritable.Construct(candPair.writable); + s.mReadable.Construct(candPair.readable); + s.mPriority.Construct(candPair.priority); + s.mSelected.Construct(candPair.selected); + s.mBytesSent.Construct(candPair.bytes_sent); + s.mBytesReceived.Construct(candPair.bytes_recvd); + s.mLastPacketSentTimestamp.Construct(candPair.ms_since_last_send); + s.mLastPacketReceivedTimestamp.Construct(candPair.ms_since_last_recv); + s.mState.Construct(dom::RTCStatsIceCandidatePairState(candPair.state)); + s.mComponentId.Construct(candPair.component_id); + if (!aStats->mIceCandidatePairStats.AppendElement(s, fallible)) { + // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might + // involve multiple reallocations) and potentially crashing here, + // SetCapacity could be called outside the loop once. + mozalloc_handle_oom(0); + } + } + + std::vector candidates; + if (NS_SUCCEEDED(aStream.GetLocalCandidates(&candidates))) { + ToRTCIceCandidateStats(candidates, dom::RTCStatsType::Local_candidate, + transportId, aNow, aStats, mObfuscateHostAddresses, + mSignaledAddresses); + // add the local candidates unparsed string to a sequence + for (const auto& candidate : candidates) { + if (!aStats->mRawLocalCandidates.AppendElement( + NS_ConvertASCIItoUTF16(candidate.label.c_str()), fallible)) { + // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might + // involve multiple reallocations) and potentially crashing here, + // SetCapacity could be called outside the loop once. + mozalloc_handle_oom(0); + } + } + } + candidates.clear(); + + if (NS_SUCCEEDED(aStream.GetRemoteCandidates(&candidates))) { + ToRTCIceCandidateStats(candidates, dom::RTCStatsType::Remote_candidate, + transportId, aNow, aStats, mObfuscateHostAddresses, + mSignaledAddresses); + // add the remote candidates unparsed string to a sequence + for (const auto& candidate : candidates) { + if (!aStats->mRawRemoteCandidates.AppendElement( + NS_ConvertASCIItoUTF16(candidate.label.c_str()), fallible)) { + // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might + // involve multiple reallocations) and potentially crashing here, + // SetCapacity could be called outside the loop once. + mozalloc_handle_oom(0); + } + } + } +} + +RefPtr MediaTransportHandlerSTS::GetTransportFlow( + const std::string& aTransportId, bool aIsRtcp) const { + auto it = mTransports.find(aTransportId); + if (it == mTransports.end()) { + return nullptr; + } + + if (aIsRtcp) { + return it->second.mRtcpFlow ? it->second.mRtcpFlow : it->second.mFlow; + ; + } + + return it->second.mFlow; +} + +RefPtr MediaTransportHandlerSTS::CreateTransportFlow( + const std::string& aTransportId, bool aIsRtcp, + const RefPtr& aDtlsIdentity, bool aDtlsClient, + const DtlsDigestList& aDigests, bool aPrivacyRequested) { + nsresult rv; + RefPtr flow = new TransportFlow(aTransportId); + + // The media streams are made on STS so we need to defer setup. + auto ice = MakeUnique(); + auto dtls = MakeUnique(); + auto srtp = MakeUnique(*dtls); + dtls->SetRole(aDtlsClient ? TransportLayerDtls::CLIENT + : TransportLayerDtls::SERVER); + + dtls->SetIdentity(aDtlsIdentity); + + dtls->SetMinMaxVersion( + static_cast(mMinDtlsVersion), + static_cast(mMaxDtlsVersion)); + + for (const auto& digest : aDigests) { + rv = dtls->SetVerificationDigest(digest); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "Could not set fingerprint"); + return nullptr; + } + } + + std::vector srtpCiphers = + TransportLayerDtls::GetDefaultSrtpCiphers(); + + rv = dtls->SetSrtpCiphers(srtpCiphers); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "Couldn't set SRTP ciphers"); + return nullptr; + } + + // Always permits negotiation of the confidential mode. + // Only allow non-confidential (which is an allowed default), + // if we aren't confidential. + std::set alpn = {"c-webrtc"}; + std::string alpnDefault; + if (!aPrivacyRequested) { + alpnDefault = "webrtc"; + alpn.insert(alpnDefault); + } + rv = dtls->SetAlpn(alpn, alpnDefault); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "Couldn't set ALPN"); + return nullptr; + } + + ice->SetParameters(mIceCtx->GetStream(aTransportId), aIsRtcp ? 2 : 1); + NS_ENSURE_SUCCESS(ice->Init(), nullptr); + NS_ENSURE_SUCCESS(dtls->Init(), nullptr); + NS_ENSURE_SUCCESS(srtp->Init(), nullptr); + dtls->Chain(ice.get()); + srtp->Chain(ice.get()); + + dtls->SignalPacketReceived.connect(this, + &MediaTransportHandlerSTS::PacketReceived); + srtp->SignalPacketReceived.connect(this, + &MediaTransportHandlerSTS::PacketReceived); + ice->SignalPacketSending.connect( + this, &MediaTransportHandlerSTS::EncryptedPacketSending); + flow->PushLayer(ice.release()); + flow->PushLayer(dtls.release()); + flow->PushLayer(srtp.release()); + return flow; +} + +static mozilla::dom::RTCIceGatheringState toDomIceGatheringState( + NrIceCtx::GatheringState aState) { + switch (aState) { + case NrIceCtx::ICE_CTX_GATHER_INIT: + return dom::RTCIceGatheringState::New; + case NrIceCtx::ICE_CTX_GATHER_STARTED: + return dom::RTCIceGatheringState::Gathering; + case NrIceCtx::ICE_CTX_GATHER_COMPLETE: + return dom::RTCIceGatheringState::Complete; + } + MOZ_CRASH(); +} + +void MediaTransportHandlerSTS::OnGatheringStateChange( + NrIceCtx* aIceCtx, NrIceCtx::GatheringState aState) { + OnGatheringStateChange(toDomIceGatheringState(aState)); +} + +static mozilla::dom::RTCIceConnectionState toDomIceConnectionState( + NrIceCtx::ConnectionState aState) { + switch (aState) { + case NrIceCtx::ICE_CTX_INIT: + return dom::RTCIceConnectionState::New; + case NrIceCtx::ICE_CTX_CHECKING: + return dom::RTCIceConnectionState::Checking; + case NrIceCtx::ICE_CTX_CONNECTED: + return dom::RTCIceConnectionState::Connected; + case NrIceCtx::ICE_CTX_COMPLETED: + return dom::RTCIceConnectionState::Completed; + case NrIceCtx::ICE_CTX_FAILED: + return dom::RTCIceConnectionState::Failed; + case NrIceCtx::ICE_CTX_DISCONNECTED: + return dom::RTCIceConnectionState::Disconnected; + case NrIceCtx::ICE_CTX_CLOSED: + return dom::RTCIceConnectionState::Closed; + } + MOZ_CRASH(); +} + +void MediaTransportHandlerSTS::OnConnectionStateChange( + NrIceCtx* aIceCtx, NrIceCtx::ConnectionState aState) { + OnConnectionStateChange(toDomIceConnectionState(aState)); +} + +// The stuff below here will eventually go into the MediaTransportChild class +void MediaTransportHandlerSTS::OnCandidateFound( + NrIceMediaStream* aStream, const std::string& aCandidate, + const std::string& aUfrag, const std::string& aMDNSAddr, + const std::string& aActualAddr) { + CandidateInfo info; + info.mCandidate = aCandidate; + MOZ_ASSERT(!aUfrag.empty()); + info.mUfrag = aUfrag; + NrIceCandidate defaultRtpCandidate; + NrIceCandidate defaultRtcpCandidate; + nsresult rv = aStream->GetDefaultCandidate(1, &defaultRtpCandidate); + if (NS_SUCCEEDED(rv)) { + if (!defaultRtpCandidate.mdns_addr.empty()) { + info.mDefaultHostRtp = "0.0.0.0"; + info.mDefaultPortRtp = 9; + } else { + info.mDefaultHostRtp = defaultRtpCandidate.cand_addr.host; + info.mDefaultPortRtp = defaultRtpCandidate.cand_addr.port; + } + } else { + CSFLogError(LOGTAG, + "%s: GetDefaultCandidates failed for transport id %s, " + "res=%u", + __FUNCTION__, aStream->GetId().c_str(), + static_cast(rv)); + } + + // Optional; component won't exist if doing rtcp-mux + if (NS_SUCCEEDED(aStream->GetDefaultCandidate(2, &defaultRtcpCandidate))) { + if (!defaultRtcpCandidate.mdns_addr.empty()) { + info.mDefaultHostRtcp = defaultRtcpCandidate.mdns_addr; + } else { + info.mDefaultHostRtcp = defaultRtcpCandidate.cand_addr.host; + } + info.mDefaultPortRtcp = defaultRtcpCandidate.cand_addr.port; + } + + info.mMDNSAddress = aMDNSAddr; + info.mActualAddress = aActualAddr; + + OnCandidate(aStream->GetId(), info); +} + +void MediaTransportHandlerSTS::OnStateChange(TransportLayer* aLayer, + TransportLayer::State aState) { + if (aState == TransportLayer::TS_OPEN) { + MOZ_ASSERT(aLayer->id() == TransportLayerDtls::ID()); + TransportLayerDtls* dtlsLayer = static_cast(aLayer); + OnAlpnNegotiated(dtlsLayer->GetNegotiatedAlpn()); + } + + // DTLS state indicates the readiness of the transport as a whole, because + // SRTP uses the keys from the DTLS handshake. + MediaTransportHandler::OnStateChange(aLayer->flow_id(), aState); +} + +void MediaTransportHandlerSTS::OnRtcpStateChange(TransportLayer* aLayer, + TransportLayer::State aState) { + MediaTransportHandler::OnRtcpStateChange(aLayer->flow_id(), aState); +} + +constexpr static const char* PacketTypeToString(MediaPacket::Type type) { + switch (type) { + case MediaPacket::Type::UNCLASSIFIED: + return "UNCLASSIFIED"; + case MediaPacket::Type::SRTP: + return "SRTP"; + case MediaPacket::Type::SRTCP: + return "SRTCP"; + case MediaPacket::Type::DTLS: + return "DTLS"; + case MediaPacket::Type::RTP: + return "RTP"; + case MediaPacket::Type::RTCP: + return "RTCP"; + case MediaPacket::Type::SCTP: + return "SCTP"; + default: + MOZ_ASSERT(false, "unreached"); + return ""; + } +} + +void MediaTransportHandlerSTS::PacketReceived(TransportLayer* aLayer, + MediaPacket& aPacket) { + MEDIA_TRANSPORT_HANDLER_PACKET_RECEIVED(aPacket); + OnPacketReceived(aLayer->flow_id(), aPacket); +} + +void MediaTransportHandlerSTS::EncryptedPacketSending(TransportLayer* aLayer, + MediaPacket& aPacket) { + OnEncryptedSending(aLayer->flow_id(), aPacket); +} + +} // namespace mozilla + +#undef MEDIA_TRANSPORT_HANDLER_PACKET_RECEIVED diff --git a/dom/media/webrtc/jsapi/MediaTransportHandler.h b/dom/media/webrtc/jsapi/MediaTransportHandler.h new file mode 100644 index 0000000000..a776cb6fd7 --- /dev/null +++ b/dom/media/webrtc/jsapi/MediaTransportHandler.h @@ -0,0 +1,167 @@ +/* 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/. */ + +#ifndef _MTRANSPORTHANDLER_H__ +#define _MTRANSPORTHANDLER_H__ + +#include "mozilla/RefPtr.h" +#include "nsISupportsImpl.h" +#include "transport/sigslot.h" +#include "transport/transportlayer.h" // Need the State enum +#include "transport/dtlsidentity.h" // For DtlsDigest +#include "mozilla/dom/RTCPeerConnectionBinding.h" +#include "mozilla/dom/RTCConfigurationBinding.h" +#include "transport/nricectx.h" // Need some enums +#include "common/CandidateInfo.h" +#include "transport/nr_socket_proxy_config.h" +#include "RTCStatsReport.h" + +#include "nsString.h" + +#include +#include +#include + +namespace mozilla { +class DtlsIdentity; +class NrIceCtx; +class NrIceMediaStream; +class NrIceResolver; +class TransportFlow; +class RTCStatsQuery; + +namespace dom { +struct RTCStatsReportInternal; +} + +class MediaTransportHandler { + public: + // Creates either a MediaTransportHandlerSTS or a MediaTransportHandlerIPC, + // as appropriate. If you want signals to fire on a specific thread, pass + // the event target here, otherwise they will fire on whatever is convenient. + // Note: This also determines what thread the state cache is updated on! + // Don't call GetState on any other thread! + static already_AddRefed Create( + nsISerialEventTarget* aCallbackThread); + + explicit MediaTransportHandler(nsISerialEventTarget* aCallbackThread) + : mCallbackThread(aCallbackThread) {} + + // Exposed so we can synchronously validate ICE servers from PeerConnection + static nsresult ConvertIceServers( + const nsTArray& aIceServers, + std::vector* aStunServers, + std::vector* aTurnServers); + + typedef MozPromise, nsresult, true> IceLogPromise; + + virtual void Initialize() {} + + // There's a wrinkle here; the ICE logging is not separated out by + // MediaTransportHandler. These are a little more like static methods, but + // to avoid needing yet another IPC interface, we bolt them on here. + virtual RefPtr GetIceLog(const nsCString& aPattern) = 0; + virtual void ClearIceLog() = 0; + virtual void EnterPrivateMode() = 0; + virtual void ExitPrivateMode() = 0; + + virtual void CreateIceCtx(const std::string& aName) = 0; + + virtual nsresult SetIceConfig(const nsTArray& aIceServers, + dom::RTCIceTransportPolicy aIcePolicy) = 0; + + // We will probably be able to move the proxy lookup stuff into + // this class once we move mtransport to its own process. + virtual void SetProxyConfig(NrSocketProxyConfig&& aProxyConfig) = 0; + + virtual void EnsureProvisionalTransport(const std::string& aTransportId, + const std::string& aLocalUfrag, + const std::string& aLocalPwd, + int aComponentCount) = 0; + + virtual void SetTargetForDefaultLocalAddressLookup( + const std::string& aTargetIp, uint16_t aTargetPort) = 0; + + // We set default-route-only as late as possible because it depends on what + // capture permissions have been granted on the window, which could easily + // change between Init (ie; when the PC is created) and StartIceGathering + // (ie; when we set the local description). + virtual void StartIceGathering(bool aDefaultRouteOnly, + bool aObfuscateHostAddresses, + // TODO: It probably makes sense to look + // this up internally + const nsTArray& aStunAddrs) = 0; + + virtual void ActivateTransport( + const std::string& aTransportId, const std::string& aLocalUfrag, + const std::string& aLocalPwd, size_t aComponentCount, + const std::string& aUfrag, const std::string& aPassword, + const nsTArray& aKeyDer, const nsTArray& aCertDer, + SSLKEAType aAuthType, bool aDtlsClient, const DtlsDigestList& aDigests, + bool aPrivacyRequested) = 0; + + virtual void RemoveTransportsExcept( + const std::set& aTransportIds) = 0; + + virtual void StartIceChecks(bool aIsControlling, + const std::vector& aIceOptions) = 0; + + virtual void SendPacket(const std::string& aTransportId, + MediaPacket&& aPacket) = 0; + + virtual void AddIceCandidate(const std::string& aTransportId, + const std::string& aCandidate, + const std::string& aUFrag, + const std::string& aObfuscatedAddress) = 0; + + virtual void UpdateNetworkState(bool aOnline) = 0; + + virtual RefPtr GetIceStats( + const std::string& aTransportId, DOMHighResTimeStamp aNow) = 0; + + sigslot::signal2 SignalCandidate; + sigslot::signal2 SignalAlpnNegotiated; + sigslot::signal1 SignalGatheringStateChange; + sigslot::signal1 SignalConnectionStateChange; + + sigslot::signal2 SignalPacketReceived; + sigslot::signal2 + SignalEncryptedSending; + sigslot::signal2 SignalStateChange; + sigslot::signal2 + SignalRtcpStateChange; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(MediaTransportHandler, + Destroy()) + + TransportLayer::State GetState(const std::string& aTransportId, + bool aRtcp) const; + + protected: + void OnCandidate(const std::string& aTransportId, + const CandidateInfo& aCandidateInfo); + void OnAlpnNegotiated(const std::string& aAlpn); + void OnGatheringStateChange(dom::RTCIceGatheringState aState); + void OnConnectionStateChange(dom::RTCIceConnectionState aState); + void OnPacketReceived(const std::string& aTransportId, + const MediaPacket& aPacket); + void OnEncryptedSending(const std::string& aTransportId, + const MediaPacket& aPacket); + void OnStateChange(const std::string& aTransportId, + TransportLayer::State aState); + void OnRtcpStateChange(const std::string& aTransportId, + TransportLayer::State aState); + virtual void Destroy() = 0; + virtual ~MediaTransportHandler() = default; + std::map mStateCache; + std::map mRtcpStateCache; + RefPtr mCallbackThread; +}; + +void TokenizeCandidate(const std::string& aCandidate, + std::vector& aTokens); + +} // namespace mozilla + +#endif //_MTRANSPORTHANDLER_H__ diff --git a/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.cpp b/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.cpp new file mode 100644 index 0000000000..6369d229ee --- /dev/null +++ b/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.cpp @@ -0,0 +1,414 @@ +/* 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 "MediaTransportHandlerIPC.h" +#include "mozilla/dom/MediaTransportChild.h" +#include "nsThreadUtils.h" +#include "mozilla/net/SocketProcessBridgeChild.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "common/browser_logging/CSFLog.h" + +namespace mozilla { + +static const char* mthipcLogTag = "MediaTransportHandler"; +#ifdef LOGTAG +# undef LOGTAG +#endif +#define LOGTAG mthipcLogTag + +MediaTransportHandlerIPC::MediaTransportHandlerIPC( + nsISerialEventTarget* aCallbackThread) + : MediaTransportHandler(aCallbackThread) {} + +void MediaTransportHandlerIPC::Initialize() { + mInitPromise = net::SocketProcessBridgeChild::GetSocketProcessBridge()->Then( + mCallbackThread, __func__, + [this, self = RefPtr(this)]( + const RefPtr& aBridge) { + ipc::PBackgroundChild* actor = + ipc::BackgroundChild::GetOrCreateSocketActorForCurrentThread(); + // An actor that can't send is possible if the socket process has + // crashed but hasn't been reconnected properly. See + // SocketProcessBridgeChild::ActorDestroy for more info. + if (!actor || !actor->CanSend()) { + NS_WARNING( + "MediaTransportHandlerIPC async init failed! Webrtc networking " + "will not work!"); + return InitPromise::CreateAndReject( + nsCString("GetOrCreateSocketActorForCurrentThread failed!"), + __func__); + } + MediaTransportChild* child = new MediaTransportChild(this); + // PBackgroungChild owns mChild! When it is done with it, + // mChild will let us know it it going away. + mChild = actor->SendPMediaTransportConstructor(child); + CSFLogDebug(LOGTAG, "%s Init done", __func__); + return InitPromise::CreateAndResolve(true, __func__); + }, + [=](const nsCString& aError) { + CSFLogError(LOGTAG, + "MediaTransportHandlerIPC async init failed! Webrtc " + "networking will not work! Error was %s", + aError.get()); + NS_WARNING( + "MediaTransportHandlerIPC async init failed! Webrtc networking " + "will not work!"); + return InitPromise::CreateAndReject(aError, __func__); + }); +} + +RefPtr +MediaTransportHandlerIPC::GetIceLog(const nsCString& aPattern) { + return mInitPromise->Then( + mCallbackThread, __func__, + [=, self = RefPtr(this)](bool /* dummy */) { + if (!mChild) { + return IceLogPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + // Compiler has trouble deducing the return type here for some reason, + // so we use a temp variable as a hint. + // SendGetIceLog _almost_ returns an IceLogPromise; the reject value + // differs (ipc::ResponseRejectReason vs nsresult) so we need to + // convert. + RefPtr promise = mChild->SendGetIceLog(aPattern)->Then( + mCallbackThread, __func__, + [](WebrtcGlobalLog&& aLogLines) { + return IceLogPromise::CreateAndResolve(std::move(aLogLines), + __func__); + }, + [](ipc::ResponseRejectReason aReason) { + return IceLogPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + }); + return promise; + }, + [](const nsCString& aError) { + return IceLogPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + }); +} + +void MediaTransportHandlerIPC::ClearIceLog() { + mInitPromise->Then( + mCallbackThread, __func__, + [=, self = RefPtr(this)](bool /*dummy*/) { + if (mChild) { + mChild->SendClearIceLog(); + } + }, + [](const nsCString& aError) {}); +} + +void MediaTransportHandlerIPC::EnterPrivateMode() { + mInitPromise->Then( + mCallbackThread, __func__, + [=, self = RefPtr(this)](bool /*dummy*/) { + if (mChild) { + mChild->SendEnterPrivateMode(); + } + }, + [](const nsCString& aError) {}); +} + +void MediaTransportHandlerIPC::ExitPrivateMode() { + mInitPromise->Then( + mCallbackThread, __func__, + [=, self = RefPtr(this)](bool /*dummy*/) { + if (mChild) { + mChild->SendExitPrivateMode(); + } + }, + [](const nsCString& aError) {}); +} + +void MediaTransportHandlerIPC::CreateIceCtx(const std::string& aName) { + CSFLogDebug(LOGTAG, "MediaTransportHandlerIPC::CreateIceCtx start"); + + mInitPromise->Then( + mCallbackThread, __func__, + [=, self = RefPtr(this)](bool /*dummy*/) { + if (mChild) { + CSFLogDebug(LOGTAG, "%s starting", __func__); + if (NS_WARN_IF(!mChild->SendCreateIceCtx(aName))) { + CSFLogError(LOGTAG, "%s failed!", __func__); + } + } + }, + [](const nsCString& aError) {}); +} + +nsresult MediaTransportHandlerIPC::SetIceConfig( + const nsTArray& aIceServers, + dom::RTCIceTransportPolicy aIcePolicy) { + // Run some validation on this side of the IPC boundary so we can return + // errors synchronously. We don't actually use the results. It might make + // sense to move this check to PeerConnection and have this API take the + // converted form, but we would need to write IPC serialization code for + // the NrIce*Server types. + std::vector stunServers; + std::vector turnServers; + nsresult rv = ConvertIceServers(aIceServers, &stunServers, &turnServers); + if (NS_FAILED(rv)) { + return rv; + } + + mInitPromise->Then( + mCallbackThread, __func__, + [=, iceServers = aIceServers.Clone(), + self = RefPtr(this)](bool /*dummy*/) { + if (mChild) { + if (NS_WARN_IF(!mChild->SendSetIceConfig(std::move(iceServers), + aIcePolicy))) { + CSFLogError(LOGTAG, "%s failed!", __func__); + } + } + }, + [](const nsCString& aError) {}); + + return NS_OK; +} + +void MediaTransportHandlerIPC::Destroy() { + if (mChild) { + MediaTransportChild::Send__delete__(mChild); + mChild = nullptr; + } + delete this; +} + +// We will probably be able to move the proxy lookup stuff into +// this class once we move mtransport to its own process. +void MediaTransportHandlerIPC::SetProxyConfig( + NrSocketProxyConfig&& aProxyConfig) { + mInitPromise->Then( + mCallbackThread, __func__, + [aProxyConfig = std::move(aProxyConfig), this, + self = RefPtr(this)](bool /*dummy*/) mutable { + if (mChild) { + mChild->SendSetProxyConfig(aProxyConfig.GetConfig()); + } + }, + [](const nsCString& aError) {}); +} + +void MediaTransportHandlerIPC::EnsureProvisionalTransport( + const std::string& aTransportId, const std::string& aLocalUfrag, + const std::string& aLocalPwd, int aComponentCount) { + mInitPromise->Then( + mCallbackThread, __func__, + [=, self = RefPtr(this)](bool /*dummy*/) { + if (mChild) { + mChild->SendEnsureProvisionalTransport(aTransportId, aLocalUfrag, + aLocalPwd, aComponentCount); + } + }, + [](const nsCString& aError) {}); +} + +void MediaTransportHandlerIPC::SetTargetForDefaultLocalAddressLookup( + const std::string& aTargetIp, uint16_t aTargetPort) { + mInitPromise->Then( + mCallbackThread, __func__, + [=, self = RefPtr(this)](bool /*dummy*/) { + if (mChild) { + mChild->SendSetTargetForDefaultLocalAddressLookup(aTargetIp, + aTargetPort); + } + }, + [](const nsCString& aError) {}); +} + +// We set default-route-only as late as possible because it depends on what +// capture permissions have been granted on the window, which could easily +// change between Init (ie; when the PC is created) and StartIceGathering +// (ie; when we set the local description). +void MediaTransportHandlerIPC::StartIceGathering( + bool aDefaultRouteOnly, bool aObfuscateHostAddresses, + // TODO(bug 1522205): It probably makes sense to look this up internally + const nsTArray& aStunAddrs) { + mInitPromise->Then( + mCallbackThread, __func__, + [=, stunAddrs = aStunAddrs.Clone(), + self = RefPtr(this)](bool /*dummy*/) { + if (mChild) { + mChild->SendStartIceGathering(aDefaultRouteOnly, + aObfuscateHostAddresses, stunAddrs); + } + }, + [](const nsCString& aError) {}); +} + +void MediaTransportHandlerIPC::ActivateTransport( + const std::string& aTransportId, const std::string& aLocalUfrag, + const std::string& aLocalPwd, size_t aComponentCount, + const std::string& aUfrag, const std::string& aPassword, + const nsTArray& aKeyDer, const nsTArray& aCertDer, + SSLKEAType aAuthType, bool aDtlsClient, const DtlsDigestList& aDigests, + bool aPrivacyRequested) { + mInitPromise->Then( + mCallbackThread, __func__, + [=, keyDer = aKeyDer.Clone(), certDer = aCertDer.Clone(), + self = RefPtr(this)](bool /*dummy*/) { + if (mChild) { + mChild->SendActivateTransport(aTransportId, aLocalUfrag, aLocalPwd, + aComponentCount, aUfrag, aPassword, + keyDer, certDer, aAuthType, aDtlsClient, + aDigests, aPrivacyRequested); + } + }, + [](const nsCString& aError) {}); +} + +void MediaTransportHandlerIPC::RemoveTransportsExcept( + const std::set& aTransportIds) { + std::vector transportIds(aTransportIds.begin(), + aTransportIds.end()); + mInitPromise->Then( + mCallbackThread, __func__, + [=, self = RefPtr(this)](bool /*dummy*/) { + if (mChild) { + mChild->SendRemoveTransportsExcept(transportIds); + } + }, + [](const nsCString& aError) {}); +} + +void MediaTransportHandlerIPC::StartIceChecks( + bool aIsControlling, const std::vector& aIceOptions) { + mInitPromise->Then( + mCallbackThread, __func__, + [=, self = RefPtr(this)](bool /*dummy*/) { + if (mChild) { + mChild->SendStartIceChecks(aIsControlling, aIceOptions); + } + }, + [](const nsCString& aError) {}); +} + +void MediaTransportHandlerIPC::SendPacket(const std::string& aTransportId, + MediaPacket&& aPacket) { + mInitPromise->Then( + mCallbackThread, __func__, + [this, self = RefPtr(this), aTransportId, + aPacket = std::move(aPacket)](bool /*dummy*/) mutable { + if (mChild) { + mChild->SendSendPacket(aTransportId, aPacket); + } + }, + [](const nsCString& aError) {}); +} + +void MediaTransportHandlerIPC::AddIceCandidate( + const std::string& aTransportId, const std::string& aCandidate, + const std::string& aUfrag, const std::string& aObfuscatedAddress) { + mInitPromise->Then( + mCallbackThread, __func__, + [=, self = RefPtr(this)](bool /*dummy*/) { + if (mChild) { + mChild->SendAddIceCandidate(aTransportId, aCandidate, aUfrag, + aObfuscatedAddress); + } + }, + [](const nsCString& aError) {}); +} + +void MediaTransportHandlerIPC::UpdateNetworkState(bool aOnline) { + mInitPromise->Then( + mCallbackThread, __func__, + [=, self = RefPtr(this)](bool /*dummy*/) { + if (mChild) { + mChild->SendUpdateNetworkState(aOnline); + } + }, + [](const nsCString& aError) {}); +} + +RefPtr MediaTransportHandlerIPC::GetIceStats( + const std::string& aTransportId, DOMHighResTimeStamp aNow) { + using IPCPromise = dom::PMediaTransportChild::GetIceStatsPromise; + return mInitPromise + ->Then(mCallbackThread, __func__, + [aTransportId, aNow, this, self = RefPtr(this)]( + const InitPromise::ResolveOrRejectValue& aValue) { + if (aValue.IsReject()) { + return IPCPromise::CreateAndResolve( + MakeUnique(), + "MediaTransportHandlerIPC::GetIceStats_1"); + } + if (!mChild) { + return IPCPromise::CreateAndResolve( + MakeUnique(), + "MediaTransportHandlerIPC::GetIceStats_1"); + } + return mChild->SendGetIceStats(aTransportId, aNow); + }) + ->Then(mCallbackThread, __func__, + [](IPCPromise::ResolveOrRejectValue&& aValue) { + if (aValue.IsReject()) { + return dom::RTCStatsPromise::CreateAndResolve( + MakeUnique(), + "MediaTransportHandlerIPC::GetIceStats_2"); + } + return dom::RTCStatsPromise::CreateAndResolve( + std::move(aValue.ResolveValue()), + "MediaTransportHandlerIPC::GetIceStats_2"); + }); +} + +MediaTransportChild::MediaTransportChild(MediaTransportHandlerIPC* aUser) + : mUser(aUser) {} + +MediaTransportChild::~MediaTransportChild() { mUser->mChild = nullptr; } + +mozilla::ipc::IPCResult MediaTransportChild::RecvOnCandidate( + const string& transportId, const CandidateInfo& candidateInfo) { + mUser->OnCandidate(transportId, candidateInfo); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportChild::RecvOnAlpnNegotiated( + const string& alpn) { + mUser->OnAlpnNegotiated(alpn); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportChild::RecvOnGatheringStateChange( + const int& state) { + mUser->OnGatheringStateChange(static_cast(state)); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportChild::RecvOnConnectionStateChange( + const int& state) { + mUser->OnConnectionStateChange( + static_cast(state)); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportChild::RecvOnPacketReceived( + const string& transportId, const MediaPacket& packet) { + mUser->OnPacketReceived(transportId, packet); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportChild::RecvOnEncryptedSending( + const string& transportId, const MediaPacket& packet) { + mUser->OnEncryptedSending(transportId, packet); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportChild::RecvOnStateChange( + const string& transportId, const int& state) { + mUser->OnStateChange(transportId, static_cast(state)); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportChild::RecvOnRtcpStateChange( + const string& transportId, const int& state) { + mUser->OnRtcpStateChange(transportId, + static_cast(state)); + return ipc::IPCResult::Ok(); +} + +} // namespace mozilla diff --git a/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.h b/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.h new file mode 100644 index 0000000000..6b285dee2a --- /dev/null +++ b/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.h @@ -0,0 +1,96 @@ +/* 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/. */ + +#ifndef _MTRANSPORTHANDLER_IPC_H__ +#define _MTRANSPORTHANDLER_IPC_H__ + +#include "jsapi/MediaTransportHandler.h" +#include "mozilla/dom/PMediaTransportChild.h" + +namespace mozilla { + +class MediaTransportChild; + +// Implementation of MediaTransportHandler that uses IPC (PMediaTransport) to +// talk to mtransport on another process. +class MediaTransportHandlerIPC final : public MediaTransportHandler { + public: + explicit MediaTransportHandlerIPC(nsISerialEventTarget* aCallbackThread); + void Initialize() override; + RefPtr GetIceLog(const nsCString& aPattern) override; + void ClearIceLog() override; + void EnterPrivateMode() override; + void ExitPrivateMode() override; + + void CreateIceCtx(const std::string& aName) override; + + nsresult SetIceConfig(const nsTArray& aIceServers, + dom::RTCIceTransportPolicy aIcePolicy) override; + + // We will probably be able to move the proxy lookup stuff into + // this class once we move mtransport to its own process. + void SetProxyConfig(NrSocketProxyConfig&& aProxyConfig) override; + + void EnsureProvisionalTransport(const std::string& aTransportId, + const std::string& aLocalUfrag, + const std::string& aLocalPwd, + int aComponentCount) override; + + void SetTargetForDefaultLocalAddressLookup(const std::string& aTargetIp, + uint16_t aTargetPort) override; + + // We set default-route-only as late as possible because it depends on what + // capture permissions have been granted on the window, which could easily + // change between Init (ie; when the PC is created) and StartIceGathering + // (ie; when we set the local description). + void StartIceGathering(bool aDefaultRouteOnly, bool aObfuscateHostAddresses, + // TODO: It probably makes sense to look + // this up internally + const nsTArray& aStunAddrs) override; + + void ActivateTransport( + const std::string& aTransportId, const std::string& aLocalUfrag, + const std::string& aLocalPwd, size_t aComponentCount, + const std::string& aUfrag, const std::string& aPassword, + const nsTArray& aKeyDer, const nsTArray& aCertDer, + SSLKEAType aAuthType, bool aDtlsClient, const DtlsDigestList& aDigests, + bool aPrivacyRequested) override; + + void RemoveTransportsExcept( + const std::set& aTransportIds) override; + + void StartIceChecks(bool aIsControlling, + const std::vector& aIceOptions) override; + + void SendPacket(const std::string& aTransportId, + MediaPacket&& aPacket) override; + + void AddIceCandidate(const std::string& aTransportId, + const std::string& aCandidate, const std::string& aUfrag, + const std::string& aObfuscatedAddress) override; + + void UpdateNetworkState(bool aOnline) override; + + RefPtr GetIceStats(const std::string& aTransportId, + DOMHighResTimeStamp aNow) override; + + private: + friend class MediaTransportChild; + void Destroy() override; + + // We do not own this; it will tell us when it is going away. + dom::PMediaTransportChild* mChild = nullptr; + + // |mChild| can only be initted asynchronously, |mInitPromise| resolves + // when that happens. The |Then| calls make it convenient to dispatch API + // calls to main, which is a bonus. + // Init promise is not exclusive; this lets us call |Then| on it for every + // API call we get, instead of creating another promise each time. + typedef MozPromise InitPromise; + RefPtr mInitPromise; +}; + +} // namespace mozilla + +#endif //_MTRANSPORTHANDLER_IPC_H__ diff --git a/dom/media/webrtc/jsapi/MediaTransportParent.cpp b/dom/media/webrtc/jsapi/MediaTransportParent.cpp new file mode 100644 index 0000000000..d0f90df8c5 --- /dev/null +++ b/dom/media/webrtc/jsapi/MediaTransportParent.cpp @@ -0,0 +1,240 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/MediaTransportParent.h" +#include "jsapi/MediaTransportHandler.h" + +#include "transport/sigslot.h" +#include "common/browser_logging/CSFLog.h" + +namespace mozilla { + +// Deals with the MediaTransportHandler interface, so MediaTransportParent +// doesn't have to.. +class MediaTransportParent::Impl : public sigslot::has_slots<> { + public: + explicit Impl(MediaTransportParent* aParent) + : mHandler(MediaTransportHandler::Create(GetCurrentSerialEventTarget())), + mParent(aParent) { + mHandler->SignalCandidate.connect(this, + &MediaTransportParent::Impl::OnCandidate); + mHandler->SignalAlpnNegotiated.connect( + this, &MediaTransportParent::Impl::OnAlpnNegotiated); + mHandler->SignalGatheringStateChange.connect( + this, &MediaTransportParent::Impl::OnGatheringStateChange); + mHandler->SignalConnectionStateChange.connect( + this, &MediaTransportParent::Impl::OnConnectionStateChange); + mHandler->SignalPacketReceived.connect( + this, &MediaTransportParent::Impl::OnPacketReceived); + mHandler->SignalEncryptedSending.connect( + this, &MediaTransportParent::Impl::OnEncryptedSending); + mHandler->SignalStateChange.connect( + this, &MediaTransportParent::Impl::OnStateChange); + mHandler->SignalRtcpStateChange.connect( + this, &MediaTransportParent::Impl::OnRtcpStateChange); + } + + virtual ~Impl() { + disconnect_all(); + mHandler = nullptr; + } + + void OnCandidate(const std::string& aTransportId, + const CandidateInfo& aCandidateInfo) { + NS_ENSURE_TRUE_VOID(mParent->SendOnCandidate(aTransportId, aCandidateInfo)); + } + + void OnAlpnNegotiated(const std::string& aAlpn, bool aPrivacyRequested) { + NS_ENSURE_TRUE_VOID(mParent->SendOnAlpnNegotiated(aAlpn)); + } + + void OnGatheringStateChange(dom::RTCIceGatheringState aState) { + NS_ENSURE_TRUE_VOID( + mParent->SendOnGatheringStateChange(static_cast(aState))); + } + + void OnConnectionStateChange(dom::RTCIceConnectionState aState) { + NS_ENSURE_TRUE_VOID( + mParent->SendOnConnectionStateChange(static_cast(aState))); + } + + void OnPacketReceived(const std::string& aTransportId, + const MediaPacket& aPacket) { + NS_ENSURE_TRUE_VOID(mParent->SendOnPacketReceived(aTransportId, aPacket)); + } + + void OnEncryptedSending(const std::string& aTransportId, + const MediaPacket& aPacket) { + NS_ENSURE_TRUE_VOID(mParent->SendOnEncryptedSending(aTransportId, aPacket)); + } + + void OnStateChange(const std::string& aTransportId, + TransportLayer::State aState) { + NS_ENSURE_TRUE_VOID(mParent->SendOnStateChange(aTransportId, aState)); + } + + void OnRtcpStateChange(const std::string& aTransportId, + TransportLayer::State aState) { + NS_ENSURE_TRUE_VOID(mParent->SendOnRtcpStateChange(aTransportId, aState)); + } + + RefPtr mHandler; + + private: + MediaTransportParent* mParent; +}; + +MediaTransportParent::MediaTransportParent() : mImpl(new Impl(this)) {} + +MediaTransportParent::~MediaTransportParent() {} + +mozilla::ipc::IPCResult MediaTransportParent::RecvGetIceLog( + const nsCString& pattern, GetIceLogResolver&& aResolve) { + mImpl->mHandler->GetIceLog(pattern)->Then( + GetCurrentSerialEventTarget(), __func__, + // IPDL doesn't give us a reject function, so we cannot reject async, so + // we are forced to resolve with an empty result. Laaaaaaame. + [aResolve = std::move(aResolve)]( + MediaTransportHandler::IceLogPromise::ResolveOrRejectValue&& + aResult) mutable { + WebrtcGlobalLog logLines; + if (aResult.IsResolve()) { + logLines = std::move(aResult.ResolveValue()); + } + aResolve(logLines); + }); + + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvClearIceLog() { + mImpl->mHandler->ClearIceLog(); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvEnterPrivateMode() { + mImpl->mHandler->EnterPrivateMode(); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvExitPrivateMode() { + mImpl->mHandler->ExitPrivateMode(); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvCreateIceCtx( + const string& name) { + mImpl->mHandler->CreateIceCtx(name); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvSetIceConfig( + nsTArray&& iceServers, + const RTCIceTransportPolicy& icePolicy) { + nsresult rv = mImpl->mHandler->SetIceConfig(iceServers, icePolicy); + if (NS_FAILED(rv)) { + return ipc::IPCResult::Fail(WrapNotNull(this), __func__, + "MediaTransportHandler::SetIceConfig failed"); + } + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvSetProxyConfig( + const net::WebrtcProxyConfig& aProxyConfig) { + mImpl->mHandler->SetProxyConfig(NrSocketProxyConfig(aProxyConfig)); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvEnsureProvisionalTransport( + const string& transportId, const string& localUfrag, const string& localPwd, + const int& componentCount) { + mImpl->mHandler->EnsureProvisionalTransport(transportId, localUfrag, localPwd, + componentCount); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult +MediaTransportParent::RecvSetTargetForDefaultLocalAddressLookup( + const std::string& targetIp, uint16_t targetPort) { + mImpl->mHandler->SetTargetForDefaultLocalAddressLookup(targetIp, targetPort); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvStartIceGathering( + const bool& defaultRouteOnly, const bool& obfuscateHostAddresses, + const net::NrIceStunAddrArray& stunAddrs) { + mImpl->mHandler->StartIceGathering(defaultRouteOnly, obfuscateHostAddresses, + stunAddrs); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvActivateTransport( + const string& transportId, const string& localUfrag, const string& localPwd, + const int& componentCount, const string& remoteUfrag, + const string& remotePwd, nsTArray&& keyDer, + nsTArray&& certDer, const int& authType, const bool& dtlsClient, + const DtlsDigestList& digests, const bool& privacyRequested) { + mImpl->mHandler->ActivateTransport( + transportId, localUfrag, localPwd, componentCount, remoteUfrag, remotePwd, + keyDer, certDer, static_cast(authType), dtlsClient, digests, + privacyRequested); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvRemoveTransportsExcept( + const StringVector& transportIds) { + std::set ids(transportIds.begin(), transportIds.end()); + mImpl->mHandler->RemoveTransportsExcept(ids); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvStartIceChecks( + const bool& isControlling, const StringVector& iceOptions) { + mImpl->mHandler->StartIceChecks(isControlling, iceOptions); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvSendPacket( + const string& transportId, MediaPacket&& packet) { + mImpl->mHandler->SendPacket(transportId, std::move(packet)); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvAddIceCandidate( + const string& transportId, const string& candidate, const string& ufrag, + const string& obfuscatedAddr) { + mImpl->mHandler->AddIceCandidate(transportId, candidate, ufrag, + obfuscatedAddr); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvUpdateNetworkState( + const bool& online) { + mImpl->mHandler->UpdateNetworkState(online); + return ipc::IPCResult::Ok(); +} + +mozilla::ipc::IPCResult MediaTransportParent::RecvGetIceStats( + const string& transportId, const double& now, + GetIceStatsResolver&& aResolve) { + mImpl->mHandler->GetIceStats(transportId, now) + ->Then( + GetCurrentSerialEventTarget(), __func__, + // IPDL doesn't give us a reject function, so we cannot reject async, + // so we are forced to resolve with an unmodified result. Laaaaaaame. + [aResolve = std::move(aResolve)]( + dom::RTCStatsPromise::ResolveOrRejectValue&& aResult) { + if (aResult.IsResolve()) { + aResolve(aResult.ResolveValue()); + } else { + aResolve(MakeUnique()); + } + }); + + return ipc::IPCResult::Ok(); +} + +void MediaTransportParent::ActorDestroy(ActorDestroyReason aWhy) {} + +} // namespace mozilla diff --git a/dom/media/webrtc/jsapi/PacketDumper.cpp b/dom/media/webrtc/jsapi/PacketDumper.cpp new file mode 100644 index 0000000000..2b62e0806e --- /dev/null +++ b/dom/media/webrtc/jsapi/PacketDumper.cpp @@ -0,0 +1,124 @@ +/* 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 "jsapi/PacketDumper.h" +#include "jsapi/PeerConnectionImpl.h" +#include "mozilla/media/MediaUtils.h" // NewRunnableFrom +#include "nsThreadUtils.h" // NS_DispatchToMainThread + +namespace mozilla { + +/* static */ +RefPtr PacketDumper::GetPacketDumper( + const std::string& aPcHandle) { + MOZ_ASSERT(NS_IsMainThread()); + PeerConnectionWrapper pcw(aPcHandle); + if (pcw.impl()) { + return pcw.impl()->GetPacketDumper(); + } + + return new PacketDumper(""); +} + +PacketDumper::PacketDumper(const std::string& aPcHandle) + : mPcHandle(aPcHandle), + mPacketDumpEnabled(false), + mPacketDumpFlagsMutex("Packet dump flags mutex") {} + +void PacketDumper::Dump(size_t aLevel, dom::mozPacketDumpType aType, + bool aSending, const void* aData, size_t aSize) { + // Optimization; avoids making a copy of the buffer, but we need to lock a + // mutex and check the flags. Could be optimized further, if we really want to + if (!ShouldDumpPacket(aLevel, aType, aSending)) { + return; + } + + UniquePtr ownedPacket = MakeUnique(aSize); + memcpy(ownedPacket.get(), aData, aSize); + + RefPtr dumpRunnable = media::NewRunnableFrom(std::bind( + [this, self = RefPtr(this), aLevel, aType, aSending, + aSize](UniquePtr& aPacket) -> nsresult { + // Check again; packet dump might have been disabled since the dispatch + if (ShouldDumpPacket(aLevel, aType, aSending)) { + PeerConnectionWrapper pcw(mPcHandle); + RefPtr pc = pcw.impl(); + if (pc) { + pc->DumpPacket_m(aLevel, aType, aSending, aPacket, aSize); + } + } + return NS_OK; + }, + std::move(ownedPacket))); + + NS_DispatchToMainThread(dumpRunnable); +} + +nsresult PacketDumper::EnablePacketDump(unsigned long aLevel, + dom::mozPacketDumpType aType, + bool aSending) { + mPacketDumpEnabled = true; + std::vector* packetDumpFlags; + if (aSending) { + packetDumpFlags = &mSendPacketDumpFlags; + } else { + packetDumpFlags = &mRecvPacketDumpFlags; + } + + unsigned flag = 1 << (unsigned)aType; + + MutexAutoLock lock(mPacketDumpFlagsMutex); + if (aLevel >= packetDumpFlags->size()) { + packetDumpFlags->resize(aLevel + 1); + } + + (*packetDumpFlags)[aLevel] |= flag; + return NS_OK; +} + +nsresult PacketDumper::DisablePacketDump(unsigned long aLevel, + dom::mozPacketDumpType aType, + bool aSending) { + std::vector* packetDumpFlags; + if (aSending) { + packetDumpFlags = &mSendPacketDumpFlags; + } else { + packetDumpFlags = &mRecvPacketDumpFlags; + } + + unsigned flag = 1 << (unsigned)aType; + + MutexAutoLock lock(mPacketDumpFlagsMutex); + if (aLevel < packetDumpFlags->size()) { + (*packetDumpFlags)[aLevel] &= ~flag; + } + + return NS_OK; +} + +bool PacketDumper::ShouldDumpPacket(size_t aLevel, dom::mozPacketDumpType aType, + bool aSending) const { + if (!mPacketDumpEnabled) { + return false; + } + + MutexAutoLock lock(mPacketDumpFlagsMutex); + + const std::vector* packetDumpFlags; + + if (aSending) { + packetDumpFlags = &mSendPacketDumpFlags; + } else { + packetDumpFlags = &mRecvPacketDumpFlags; + } + + if (aLevel < packetDumpFlags->size()) { + unsigned flag = 1 << (unsigned)aType; + return flag & packetDumpFlags->at(aLevel); + } + + return false; +} + +} // namespace mozilla diff --git a/dom/media/webrtc/jsapi/PacketDumper.h b/dom/media/webrtc/jsapi/PacketDumper.h new file mode 100644 index 0000000000..e998b3871f --- /dev/null +++ b/dom/media/webrtc/jsapi/PacketDumper.h @@ -0,0 +1,52 @@ +/* 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/. */ + +#ifndef _PACKET_DUMPER_H_ +#define _PACKET_DUMPER_H_ + +#include "nsISupportsImpl.h" +#include "mozilla/RefPtr.h" +#include "mozilla/dom/RTCPeerConnectionBinding.h" + +#include + +namespace mozilla { +class PeerConnectionImpl; + +class PacketDumper { + public: + static RefPtr GetPacketDumper(const std::string& aPcHandle); + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PacketDumper) + + PacketDumper(const PacketDumper&) = delete; + PacketDumper& operator=(const PacketDumper&) = delete; + + void Dump(size_t aLevel, dom::mozPacketDumpType aType, bool aSending, + const void* aData, size_t aSize); + + nsresult EnablePacketDump(unsigned long aLevel, dom::mozPacketDumpType aType, + bool aSending); + + nsresult DisablePacketDump(unsigned long aLevel, dom::mozPacketDumpType aType, + bool aSending); + + private: + friend class PeerConnectionImpl; + explicit PacketDumper(const std::string& aPcHandle); + ~PacketDumper() = default; + bool ShouldDumpPacket(size_t aLevel, dom::mozPacketDumpType aType, + bool aSending) const; + + // This class is not cycle-collected, so it cannot hold onto a strong ref + const std::string mPcHandle; + std::vector mSendPacketDumpFlags; + std::vector mRecvPacketDumpFlags; + Atomic mPacketDumpEnabled; + mutable Mutex mPacketDumpFlagsMutex; +}; + +} // namespace mozilla + +#endif // _PACKET_DUMPER_H_ diff --git a/dom/media/webrtc/jsapi/PeerConnectionCtx.cpp b/dom/media/webrtc/jsapi/PeerConnectionCtx.cpp new file mode 100644 index 0000000000..9a8f27fb59 --- /dev/null +++ b/dom/media/webrtc/jsapi/PeerConnectionCtx.cpp @@ -0,0 +1,650 @@ +/* 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 "PeerConnectionCtx.h" + +#include "WebrtcGlobalStatsHistory.h" +#include "api/audio/audio_mixer.h" +#include "api/audio_codecs/builtin_audio_decoder_factory.h" +#include "modules/rtp_rtcp/source/rtp_header_extensions.h" +#include "call/audio_state.h" +#include "common/browser_logging/CSFLog.h" +#include "common/browser_logging/WebRtcLog.h" +#include "gmp-video-decode.h" // GMP_API_VIDEO_DECODER +#include "gmp-video-encode.h" // GMP_API_VIDEO_ENCODER +#include "libwebrtcglue/CallWorkerThread.h" +#include "modules/audio_device/include/fake_audio_device.h" +#include "modules/audio_processing/include/audio_processing.h" +#include "modules/audio_processing/include/aec_dump.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/RTCPeerConnectionBinding.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Types.h" +#include "mozilla/dom/RTCStatsReportBinding.h" +#include "nsCRTGlue.h" +#include "nsIIOService.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsNetCID.h" // NS_SOCKETTRANSPORTSERVICE_CONTRACTID +#include "nsServiceManagerUtils.h" // do_GetService +#include "PeerConnectionImpl.h" +#include "prcvar.h" +#include "transport/runnable_utils.h" +#include "WebrtcGlobalChild.h" +#include "WebrtcGlobalInformation.h" + +static const char* pccLogTag = "PeerConnectionCtx"; +#ifdef LOGTAG +# undef LOGTAG +#endif +#define LOGTAG pccLogTag + +using namespace webrtc; + +namespace { +class DummyAudioMixer : public AudioMixer { + public: + bool AddSource(Source*) override { return true; } + void RemoveSource(Source*) override {} + void Mix(size_t, AudioFrame*) override { MOZ_CRASH("Unexpected call"); } +}; + +class DummyAudioProcessing : public AudioProcessing { + public: + int Initialize() override { + MOZ_CRASH("Unexpected call"); + return kNoError; + } + int Initialize(const ProcessingConfig&) override { return Initialize(); } + void ApplyConfig(const Config&) override { MOZ_CRASH("Unexpected call"); } + int proc_sample_rate_hz() const override { + MOZ_CRASH("Unexpected call"); + return 0; + } + int proc_split_sample_rate_hz() const override { + MOZ_CRASH("Unexpected call"); + return 0; + } + size_t num_input_channels() const override { + MOZ_CRASH("Unexpected call"); + return 0; + } + size_t num_proc_channels() const override { + MOZ_CRASH("Unexpected call"); + return 0; + } + size_t num_output_channels() const override { + MOZ_CRASH("Unexpected call"); + return 0; + } + size_t num_reverse_channels() const override { + MOZ_CRASH("Unexpected call"); + return 0; + } + void set_output_will_be_muted(bool) override { MOZ_CRASH("Unexpected call"); } + void SetRuntimeSetting(RuntimeSetting) override { + MOZ_CRASH("Unexpected call"); + } + bool PostRuntimeSetting(RuntimeSetting setting) override { return false; } + int ProcessStream(const int16_t* const, const StreamConfig&, + const StreamConfig&, int16_t* const) override { + MOZ_CRASH("Unexpected call"); + return kNoError; + } + int ProcessStream(const float* const*, const StreamConfig&, + const StreamConfig&, float* const*) override { + MOZ_CRASH("Unexpected call"); + return kNoError; + } + int ProcessReverseStream(const int16_t* const, const StreamConfig&, + const StreamConfig&, int16_t* const) override { + MOZ_CRASH("Unexpected call"); + return kNoError; + } + int ProcessReverseStream(const float* const*, const StreamConfig&, + const StreamConfig&, float* const*) override { + MOZ_CRASH("Unexpected call"); + return kNoError; + } + int AnalyzeReverseStream(const float* const*, const StreamConfig&) override { + MOZ_CRASH("Unexpected call"); + return kNoError; + } + bool GetLinearAecOutput( + rtc::ArrayView>) const override { + MOZ_CRASH("Unexpected call"); + return false; + } + void set_stream_analog_level(int) override { MOZ_CRASH("Unexpected call"); } + int recommended_stream_analog_level() const override { + MOZ_CRASH("Unexpected call"); + return -1; + } + int set_stream_delay_ms(int) override { + MOZ_CRASH("Unexpected call"); + return kNoError; + } + int stream_delay_ms() const override { + MOZ_CRASH("Unexpected call"); + return 0; + } + void set_stream_key_pressed(bool) override { MOZ_CRASH("Unexpected call"); } + bool CreateAndAttachAecDump(absl::string_view, int64_t, + rtc::TaskQueue*) override { + MOZ_CRASH("Unexpected call"); + return false; + } + bool CreateAndAttachAecDump(FILE*, int64_t, rtc::TaskQueue*) override { + MOZ_CRASH("Unexpected call"); + return false; + } + void AttachAecDump(std::unique_ptr) override { + MOZ_CRASH("Unexpected call"); + } + void DetachAecDump() override { MOZ_CRASH("Unexpected call"); } + AudioProcessingStats GetStatistics() override { + return AudioProcessingStats(); + } + AudioProcessingStats GetStatistics(bool) override { return GetStatistics(); } + AudioProcessing::Config GetConfig() const override { + MOZ_CRASH("Unexpected call"); + return Config(); + } +}; +} // namespace + +namespace mozilla { + +using namespace dom; + +SharedWebrtcState::SharedWebrtcState( + RefPtr aCallWorkerThread, + webrtc::AudioState::Config&& aAudioStateConfig, + RefPtr aAudioDecoderFactory, + UniquePtr aTrials) + : mCallWorkerThread(std::move(aCallWorkerThread)), + mAudioStateConfig(std::move(aAudioStateConfig)), + mAudioDecoderFactory(std::move(aAudioDecoderFactory)), + mTrials(std::move(aTrials)) {} + +SharedWebrtcState::~SharedWebrtcState() = default; + +class PeerConnectionCtxObserver : public nsIObserver { + public: + NS_DECL_ISUPPORTS + + PeerConnectionCtxObserver() {} + + void Init() { + nsCOMPtr observerService = + services::GetObserverService(); + if (!observerService) return; + + nsresult rv = NS_OK; + + rv = observerService->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, + false); + MOZ_ALWAYS_SUCCEEDS(rv); + rv = observerService->AddObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, + false); + MOZ_ALWAYS_SUCCEEDS(rv); + (void)rv; + } + + NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) override { + if (strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID) == 0) { + CSFLogDebug(LOGTAG, "Shutting down PeerConnectionCtx"); + PeerConnectionCtx::Destroy(); + + nsCOMPtr observerService = + services::GetObserverService(); + if (!observerService) return NS_ERROR_FAILURE; + + nsresult rv = observerService->RemoveObserver( + this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC); + MOZ_ALWAYS_SUCCEEDS(rv); + rv = observerService->RemoveObserver(this, + NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID); + MOZ_ALWAYS_SUCCEEDS(rv); + + // Make sure we're not deleted while still inside ::Observe() + RefPtr kungFuDeathGrip(this); + PeerConnectionCtx::gPeerConnectionCtxObserver = nullptr; + } + if (strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC) == 0) { + if (NS_strcmp(aData, u"" NS_IOSERVICE_OFFLINE) == 0) { + CSFLogDebug(LOGTAG, "Updating network state to offline"); + PeerConnectionCtx::UpdateNetworkState(false); + } else if (NS_strcmp(aData, u"" NS_IOSERVICE_ONLINE) == 0) { + CSFLogDebug(LOGTAG, "Updating network state to online"); + PeerConnectionCtx::UpdateNetworkState(true); + } else { + CSFLogDebug(LOGTAG, "Received unsupported network state event"); + MOZ_CRASH(); + } + } + return NS_OK; + } + + private: + virtual ~PeerConnectionCtxObserver() { + nsCOMPtr observerService = + services::GetObserverService(); + if (observerService) { + observerService->RemoveObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC); + observerService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID); + } + } +}; + +NS_IMPL_ISUPPORTS(PeerConnectionCtxObserver, nsIObserver); + +PeerConnectionCtx* PeerConnectionCtx::gInstance; +StaticRefPtr + PeerConnectionCtx::gPeerConnectionCtxObserver; + +nsresult PeerConnectionCtx::InitializeGlobal() { + MOZ_ASSERT(NS_IsMainThread()); + + nsresult res; + + if (!gInstance) { + CSFLogDebug(LOGTAG, "Creating PeerConnectionCtx"); + PeerConnectionCtx* ctx = new PeerConnectionCtx(); + + res = ctx->Initialize(); + PR_ASSERT(NS_SUCCEEDED(res)); + if (!NS_SUCCEEDED(res)) return res; + + gInstance = ctx; + + if (!PeerConnectionCtx::gPeerConnectionCtxObserver) { + PeerConnectionCtx::gPeerConnectionCtxObserver = + new PeerConnectionCtxObserver(); + PeerConnectionCtx::gPeerConnectionCtxObserver->Init(); + } + } + + EnableWebRtcLog(); + return NS_OK; +} + +PeerConnectionCtx* PeerConnectionCtx::GetInstance() { + MOZ_ASSERT(gInstance); + return gInstance; +} + +bool PeerConnectionCtx::isActive() { return gInstance; } + +void PeerConnectionCtx::Destroy() { + CSFLogDebug(LOGTAG, "%s", __FUNCTION__); + + if (gInstance) { + // Null out gInstance first, so PeerConnectionImpl doesn't try to use it + // in Cleanup. + auto* instance = gInstance; + gInstance = nullptr; + instance->Cleanup(); + delete instance; + } + + StopWebRtcLog(); +} + +template +static void RecordCommonRtpTelemetry(const T& list, const T& lastList, + const bool isRemote) { + using namespace Telemetry; + for (const auto& s : list) { + const bool isAudio = s.mKind.Find(u"audio") != -1; + if (s.mPacketsLost.WasPassed() && s.mPacketsReceived.WasPassed()) { + if (const uint64_t total = + s.mPacketsLost.Value() + s.mPacketsReceived.Value()) { + HistogramID id = + isRemote ? (isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_PACKETLOSS_RATE + : WEBRTC_VIDEO_QUALITY_OUTBOUND_PACKETLOSS_RATE) + : (isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_PACKETLOSS_RATE + : WEBRTC_VIDEO_QUALITY_INBOUND_PACKETLOSS_RATE); + Accumulate(id, (s.mPacketsLost.Value() * 1000) / total); + } + } + if (s.mJitter.WasPassed()) { + HistogramID id = isRemote + ? (isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_JITTER + : WEBRTC_VIDEO_QUALITY_OUTBOUND_JITTER) + : (isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_JITTER + : WEBRTC_VIDEO_QUALITY_INBOUND_JITTER); + Accumulate(id, s.mJitter.Value() * 1000); + } + } +} + +// Telemetry reporting every second after start of first call. +// The threading model around the media pipelines is weird: +// - The pipelines are containers, +// - containers that are only safe on main thread, with members only safe on +// STS, +// - hence the there and back again approach. + +void PeerConnectionCtx::DeliverStats( + UniquePtr&& aReport) { + using namespace Telemetry; + + // First, get reports from a second ago, if any, for calculations below + UniquePtr lastReport; + { + auto i = mLastReports.find(aReport->mPcid); + if (i != mLastReports.end()) { + lastReport = std::move(i->second); + } else { + lastReport = MakeUnique(); + } + } + // Record Telemetery + RecordCommonRtpTelemetry(aReport->mInboundRtpStreamStats, + lastReport->mInboundRtpStreamStats, false); + // Record bandwidth telemetry + for (const auto& s : aReport->mInboundRtpStreamStats) { + if (s.mBytesReceived.WasPassed()) { + const bool isAudio = s.mKind.Find(u"audio") != -1; + for (const auto& lastS : lastReport->mInboundRtpStreamStats) { + if (lastS.mId == s.mId) { + int32_t deltaMs = s.mTimestamp.Value() - lastS.mTimestamp.Value(); + // In theory we're called every second, so delta *should* be in that + // range. Small deltas could cause errors due to division + if (deltaMs < 500 || deltaMs > 60000 || + !lastS.mBytesReceived.WasPassed()) { + break; + } + HistogramID id = isAudio + ? WEBRTC_AUDIO_QUALITY_INBOUND_BANDWIDTH_KBITS + : WEBRTC_VIDEO_QUALITY_INBOUND_BANDWIDTH_KBITS; + // We could accumulate values until enough time has passed + // and then Accumulate() but this isn't that important + Accumulate( + id, + ((s.mBytesReceived.Value() - lastS.mBytesReceived.Value()) * 8) / + deltaMs); + break; + } + } + } + } + RecordCommonRtpTelemetry(aReport->mRemoteInboundRtpStreamStats, + lastReport->mRemoteInboundRtpStreamStats, true); + for (const auto& s : aReport->mRemoteInboundRtpStreamStats) { + if (s.mRoundTripTime.WasPassed()) { + const bool isAudio = s.mKind.Find(u"audio") != -1; + HistogramID id = isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_RTT + : WEBRTC_VIDEO_QUALITY_OUTBOUND_RTT; + Accumulate(id, s.mRoundTripTime.Value() * 1000); + } + } + + mLastReports[aReport->mPcid] = std::move(aReport); +} + +void PeerConnectionCtx::EverySecondTelemetryCallback_m(nsITimer* timer, + void* closure) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(PeerConnectionCtx::isActive()); + + for (auto& idAndPc : GetInstance()->mPeerConnections) { + if (!idAndPc.second->IsClosed()) { + idAndPc.second->GetStats(nullptr, true) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [=](UniquePtr&& aReport) { + if (PeerConnectionCtx::isActive()) { + PeerConnectionCtx::GetInstance()->DeliverStats( + std::move(aReport)); + } + }, + [=](nsresult aError) {}); + idAndPc.second->CollectConduitTelemetryData(); + } + } +} + +void PeerConnectionCtx::UpdateNetworkState(bool online) { + auto ctx = GetInstance(); + if (ctx->mPeerConnections.empty()) { + return; + } + for (auto pc : ctx->mPeerConnections) { + pc.second->UpdateNetworkState(online); + } +} + +SharedWebrtcState* PeerConnectionCtx::GetSharedWebrtcState() const { + MOZ_ASSERT(NS_IsMainThread()); + return mSharedWebrtcState; +} + +void PeerConnectionCtx::RemovePeerConnection(const std::string& aKey) { + MOZ_ASSERT(NS_IsMainThread()); + auto it = mPeerConnections.find(aKey); + if (it != mPeerConnections.end()) { + if (it->second->GetFinalStats() && !it->second->LongTermStatsIsDisabled()) { + WebrtcGlobalInformation::StashStats(*(it->second->GetFinalStats())); + } + nsAutoString pcId = NS_ConvertASCIItoUTF16(it->second->GetName().c_str()); + if (XRE_IsContentProcess()) { + if (auto* child = WebrtcGlobalChild::Get(); child) { + auto pcId = NS_ConvertASCIItoUTF16(it->second->GetName().c_str()); + child->SendPeerConnectionFinalStats(*(it->second->GetFinalStats())); + child->SendPeerConnectionDestroyed(pcId); + } + } else { + using Update = WebrtcGlobalInformation::PcTrackingUpdate; + auto update = Update::Remove(pcId); + auto finalStats = + MakeUnique(*(it->second->GetFinalStats())); + WebrtcGlobalStatsHistory::Record(std::move(finalStats)); + WebrtcGlobalInformation::PeerConnectionTracking(update); + } + + mPeerConnections.erase(it); + if (mPeerConnections.empty()) { + mSharedWebrtcState = nullptr; + StopTelemetryTimer(); + } + } +} + +void PeerConnectionCtx::AddPeerConnection(const std::string& aKey, + PeerConnectionImpl* aPeerConnection) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mPeerConnections.count(aKey) == 0, + "PeerConnection with this key should not already exist"); + if (mPeerConnections.empty()) { + AudioState::Config audioStateConfig; + audioStateConfig.audio_mixer = new rtc::RefCountedObject(); + AudioProcessingBuilder audio_processing_builder; + audioStateConfig.audio_processing = + new rtc::RefCountedObject(); + audioStateConfig.audio_device_module = + new rtc::RefCountedObject(); + + SharedThreadPoolWebRtcTaskQueueFactory taskQueueFactory; + constexpr bool supportTailDispatch = true; + // Note the NonBlocking DeletionPolicy! + // This task queue is passed into libwebrtc as a raw pointer. + // WebrtcCallWrapper guarantees that it outlives its webrtc::Call instance. + // Outside of libwebrtc we must use ref-counting to either the + // WebrtcCallWrapper or to the CallWorkerThread to keep it alive. + auto callWorkerThread = + WrapUnique(taskQueueFactory + .CreateTaskQueueWrapper( + "CallWorker", supportTailDispatch, + webrtc::TaskQueueFactory::Priority::NORMAL, + MediaThreadType::WEBRTC_CALL_THREAD) + .release()); + + UniquePtr trials = + WrapUnique(new NoTrialsConfig()); + + mSharedWebrtcState = MakeAndAddRef( + new CallWorkerThread(std::move(callWorkerThread)), + std::move(audioStateConfig), + already_AddRefed(CreateBuiltinAudioDecoderFactory().release()), + std::move(trials)); + StartTelemetryTimer(); + } + auto pcId = NS_ConvertASCIItoUTF16(aPeerConnection->GetName().c_str()); + if (XRE_IsContentProcess()) { + if (auto* child = WebrtcGlobalChild::Get(); child) { + child->SendPeerConnectionCreated( + pcId, aPeerConnection->LongTermStatsIsDisabled()); + } + } else { + using Update = WebrtcGlobalInformation::PcTrackingUpdate; + auto update = Update::Add(pcId, aPeerConnection->LongTermStatsIsDisabled()); + WebrtcGlobalInformation::PeerConnectionTracking(update); + } + mPeerConnections[aKey] = aPeerConnection; +} + +PeerConnectionImpl* PeerConnectionCtx::GetPeerConnection( + const std::string& aKey) const { + MOZ_ASSERT(NS_IsMainThread()); + auto iterator = mPeerConnections.find(aKey); + if (iterator == mPeerConnections.end()) { + return nullptr; + } + return iterator->second; +} + +void PeerConnectionCtx::ClearClosedStats() { + for (auto& [id, pc] : mPeerConnections) { + Unused << id; + if (pc->IsClosed()) { + // Rare case + pc->DisableLongTermStats(); + } + } +} + +nsresult PeerConnectionCtx::Initialize() { + MOZ_ASSERT(NS_IsMainThread()); + initGMP(); + SdpRidAttributeList::kMaxRidLength = + webrtc::BaseRtpStringExtension::kMaxValueSizeBytes; + + if (XRE_IsContentProcess()) { + WebrtcGlobalChild::Get(); + } + + return NS_OK; +} + +nsresult PeerConnectionCtx::StartTelemetryTimer() { + return NS_NewTimerWithFuncCallback(getter_AddRefs(mTelemetryTimer), + EverySecondTelemetryCallback_m, this, 1000, + nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP, + "EverySecondTelemetryCallback_m"); +} + +void PeerConnectionCtx::StopTelemetryTimer() { + if (mTelemetryTimer) { + mTelemetryTimer->Cancel(); + mTelemetryTimer = nullptr; + } +} + +static void GMPReady_m() { + if (PeerConnectionCtx::isActive()) { + PeerConnectionCtx::GetInstance()->onGMPReady(); + } +}; + +static void GMPReady() { + GetMainThreadSerialEventTarget()->Dispatch(WrapRunnableNM(&GMPReady_m), + NS_DISPATCH_NORMAL); +}; + +void PeerConnectionCtx::initGMP() { + mGMPService = do_GetService("@mozilla.org/gecko-media-plugin-service;1"); + + if (!mGMPService) { + CSFLogError(LOGTAG, "%s failed to get the gecko-media-plugin-service", + __FUNCTION__); + return; + } + + nsCOMPtr thread; + nsresult rv = mGMPService->GetThread(getter_AddRefs(thread)); + + if (NS_FAILED(rv)) { + mGMPService = nullptr; + CSFLogError(LOGTAG, + "%s failed to get the gecko-media-plugin thread, err=%u", + __FUNCTION__, static_cast(rv)); + return; + } + + // presumes that all GMP dir scans have been queued for the GMPThread + thread->Dispatch(WrapRunnableNM(&GMPReady), NS_DISPATCH_NORMAL); +} + +nsresult PeerConnectionCtx::Cleanup() { + CSFLogDebug(LOGTAG, "%s", __FUNCTION__); + MOZ_ASSERT(NS_IsMainThread()); + + mQueuedJSEPOperations.Clear(); + mGMPService = nullptr; + mTransportHandler = nullptr; + for (auto& [id, pc] : mPeerConnections) { + (void)id; + pc->Close(); + } + mPeerConnections.clear(); + mSharedWebrtcState = nullptr; + return NS_OK; +} + +void PeerConnectionCtx::queueJSEPOperation(nsIRunnable* aOperation) { + mQueuedJSEPOperations.AppendElement(aOperation); +} + +void PeerConnectionCtx::onGMPReady() { + mGMPReady = true; + for (size_t i = 0; i < mQueuedJSEPOperations.Length(); ++i) { + mQueuedJSEPOperations[i]->Run(); + } + mQueuedJSEPOperations.Clear(); +} + +bool PeerConnectionCtx::gmpHasH264() { + if (!mGMPService) { + return false; + } + + // XXX I'd prefer if this was all known ahead of time... + + AutoTArray tags; + tags.AppendElement("h264"_ns); + + bool has_gmp; + nsresult rv; + rv = mGMPService->HasPluginForAPI(nsLiteralCString(GMP_API_VIDEO_ENCODER), + tags, &has_gmp); + if (NS_FAILED(rv) || !has_gmp) { + return false; + } + + rv = mGMPService->HasPluginForAPI(nsLiteralCString(GMP_API_VIDEO_DECODER), + tags, &has_gmp); + if (NS_FAILED(rv) || !has_gmp) { + return false; + } + + return true; +} + +} // namespace mozilla diff --git a/dom/media/webrtc/jsapi/PeerConnectionCtx.h b/dom/media/webrtc/jsapi/PeerConnectionCtx.h new file mode 100644 index 0000000000..fdd81f6406 --- /dev/null +++ b/dom/media/webrtc/jsapi/PeerConnectionCtx.h @@ -0,0 +1,194 @@ +/* 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/. */ + +#ifndef peerconnectionctx_h___h__ +#define peerconnectionctx_h___h__ + +#include +#include + +#include "WebrtcGlobalChild.h" +#include "api/field_trials_view.h" +#include "api/scoped_refptr.h" +#include "call/audio_state.h" +#include "MediaTransportHandler.h" // Mostly for IceLogPromise +#include "mozIGeckoMediaPluginService.h" +#include "mozilla/Attributes.h" +#include "mozilla/StaticPtr.h" +#include "nsIRunnable.h" +#include "PeerConnectionImpl.h" + +namespace webrtc { +class AudioDecoderFactory; + +// Used for testing in mediapipeline_unittest.cpp, MockCall.h +class NoTrialsConfig : public FieldTrialsView { + public: + NoTrialsConfig() = default; + std::string Lookup(absl::string_view key) const override { + // Upstream added a new default field trial string for + // CongestionWindow, that we don't want. In + // third_party/libwebrtc/rtc_base/experiments/rate_control_settings.cc + // they set kCongestionWindowDefaultFieldTrialString to + // "QueueSize:350,MinBitrate:30000,DropFrame:true". With QueueSize + // set, GoogCcNetworkController::UpdateCongestionWindowSize is + // called. Because negative values are calculated in + // feedback_rtt, an assert fires when calculating data_window in + // GoogCcNetworkController::UpdateCongestionWindowSize. We probably + // need to figure out why we're calculating negative feedback_rtt. + // See Bug 1780620. + if ("WebRTC-CongestionWindow" == key) { + return std::string("MinBitrate:30000,DropFrame:true"); + } + return std::string(); + } +}; +} // namespace webrtc + +namespace mozilla { +class PeerConnectionCtxObserver; + +namespace dom { +class WebrtcGlobalInformation; +} + +/** + * Refcounted class containing state shared across all PeerConnections and all + * Call instances. Managed by PeerConnectionCtx, and kept around while there are + * registered peer connections. + */ +class SharedWebrtcState { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedWebrtcState) + + SharedWebrtcState(RefPtr aCallWorkerThread, + webrtc::AudioState::Config&& aAudioStateConfig, + RefPtr aAudioDecoderFactory, + UniquePtr aTrials); + + // A global Call worker thread shared between all Call instances. Implements + // AbstractThread for running tasks that call into a Call instance through its + // webrtc::TaskQueue member, and for using AbstractThread-specific higher + // order constructs like StateMirroring. + const RefPtr mCallWorkerThread; + + // AudioState config containing dummy implementations of the audio stack, + // since we use our own audio stack instead. Shared across all Call instances. + const webrtc::AudioState::Config mAudioStateConfig; + + // AudioDecoderFactory instance shared between calls, to limit the number of + // instances in large calls. + const RefPtr mAudioDecoderFactory; + + // Trials instance shared between calls, to limit the number of instances in + // large calls. + const UniquePtr mTrials; + + private: + virtual ~SharedWebrtcState(); +}; + +// A class to hold some of the singleton objects we need: +// * The global PeerConnectionImpl table and its associated lock. +// * Stats report objects for PCs that are gone +// * GMP related state +// * Upstream webrtc state shared across all Calls (processing thread) +class PeerConnectionCtx { + public: + static nsresult InitializeGlobal(); + static PeerConnectionCtx* GetInstance(); + static bool isActive(); + static void Destroy(); + + bool isReady() { + // If mGMPService is not set, we aren't using GMP. + if (mGMPService) { + return mGMPReady; + } + return true; + } + + void queueJSEPOperation(nsIRunnable* aJSEPOperation); + void onGMPReady(); + + bool gmpHasH264(); + + static void UpdateNetworkState(bool online); + + RefPtr GetTransportHandler() const { + return mTransportHandler; + } + + SharedWebrtcState* GetSharedWebrtcState() const; + + void RemovePeerConnection(const std::string& aKey); + void AddPeerConnection(const std::string& aKey, + PeerConnectionImpl* aPeerConnection); + PeerConnectionImpl* GetPeerConnection(const std::string& aKey) const; + template + void ForEachPeerConnection(Function&& aFunction) const { + MOZ_ASSERT(NS_IsMainThread()); + for (const auto& pair : mPeerConnections) { + aFunction(pair.second); + } + } + + void ClearClosedStats(); + + private: + std::map mPeerConnections; + + PeerConnectionCtx() + : mGMPReady(false), + mTransportHandler( + MediaTransportHandler::Create(GetMainThreadSerialEventTarget())) {} + + // This is a singleton, so don't copy construct it, etc. + PeerConnectionCtx(const PeerConnectionCtx& other) = delete; + void operator=(const PeerConnectionCtx& other) = delete; + virtual ~PeerConnectionCtx() = default; + + nsresult Initialize(); + nsresult StartTelemetryTimer(); + void StopTelemetryTimer(); + nsresult Cleanup(); + + void initGMP(); + + static void EverySecondTelemetryCallback_m(nsITimer* timer, void*); + + nsCOMPtr mTelemetryTimer; + + private: + void DeliverStats(UniquePtr&& aReport); + + std::map> mLastReports; + // We cannot form offers/answers properly until the Gecko Media Plugin stuff + // has been initted, which is a complicated mess of thread dispatches, + // including sync dispatches to main. So, we need to be able to queue up + // offer creation (or SetRemote, when we're the answerer) until all of this is + // ready to go, since blocking on this init is just begging for deadlock. + nsCOMPtr mGMPService; + bool mGMPReady; + nsTArray> mQueuedJSEPOperations; + + // Not initted, just for ICE logging stuff + RefPtr mTransportHandler; + + // State used by libwebrtc that needs to be shared across all PeerConnections + // and all Call instances. Set while there is at least one peer connection + // registered. CallWrappers can hold a ref to this object to be sure members + // are alive long enough. + RefPtr mSharedWebrtcState; + + static PeerConnectionCtx* gInstance; + + public: + static mozilla::StaticRefPtr + gPeerConnectionCtxObserver; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp b/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp new file mode 100644 index 0000000000..567b682b2a --- /dev/null +++ b/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp @@ -0,0 +1,4640 @@ +/* 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 +#include +#include +#include +#include +#include + +#include "common/browser_logging/CSFLog.h" +#include "base/histogram.h" +#include "common/time_profiling/timecard.h" + +#include "jsapi.h" +#include "nspr.h" +#include "nss.h" +#include "pk11pub.h" + +#include "nsNetCID.h" +#include "nsIIDNService.h" +#include "nsILoadContext.h" +#include "nsEffectiveTLDService.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsProxyRelease.h" +#include "prtime.h" + +#include "libwebrtcglue/AudioConduit.h" +#include "libwebrtcglue/VideoConduit.h" +#include "libwebrtcglue/WebrtcCallWrapper.h" +#include "MediaTrackGraph.h" +#include "transport/runnable_utils.h" +#include "IPeerConnection.h" +#include "PeerConnectionCtx.h" +#include "PeerConnectionImpl.h" +#include "RemoteTrackSource.h" +#include "nsDOMDataChannelDeclarations.h" +#include "transport/dtlsidentity.h" +#include "sdp/SdpAttribute.h" + +#include "jsep/JsepTrack.h" +#include "jsep/JsepSession.h" +#include "jsep/JsepSessionImpl.h" + +#include "transportbridge/MediaPipeline.h" +#include "jsapi/RTCRtpReceiver.h" +#include "jsapi/RTCRtpSender.h" + +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Sprintf.h" + +#ifdef XP_WIN +// We need to undef the MS macro for Document::CreateEvent +# ifdef CreateEvent +# undef CreateEvent +# endif +#endif // XP_WIN + +#include "mozilla/dom/Document.h" +#include "nsGlobalWindow.h" +#include "nsDOMDataChannel.h" +#include "mozilla/dom/Location.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Preferences.h" +#include "mozilla/PublicSSL.h" +#include "nsXULAppAPI.h" +#include "nsContentUtils.h" +#include "nsDOMJSUtils.h" +#include "nsPrintfCString.h" +#include "nsURLHelper.h" +#include "nsNetUtil.h" +#include "js/ArrayBuffer.h" // JS::NewArrayBufferWithContents +#include "js/GCAnnotations.h" // JS_HAZ_ROOTED +#include "js/RootingAPI.h" // JS::{{,Mutable}Handle,Rooted} +#include "mozilla/PeerIdentity.h" +#include "mozilla/dom/RTCCertificate.h" +#include "mozilla/dom/RTCSctpTransportBinding.h" // RTCSctpTransportState +#include "mozilla/dom/RTCDtlsTransportBinding.h" // RTCDtlsTransportState +#include "mozilla/dom/RTCRtpReceiverBinding.h" +#include "mozilla/dom/RTCRtpSenderBinding.h" +#include "mozilla/dom/RTCStatsReportBinding.h" +#include "mozilla/dom/RTCPeerConnectionBinding.h" +#include "mozilla/dom/PeerConnectionImplBinding.h" +#include "mozilla/dom/RTCDataChannelBinding.h" +#include "mozilla/dom/PluginCrashedEvent.h" +#include "MediaStreamTrack.h" +#include "AudioStreamTrack.h" +#include "VideoStreamTrack.h" +#include "nsIScriptGlobalObject.h" +#include "DOMMediaStream.h" +#include "WebrtcGlobalInformation.h" +#include "mozilla/dom/Event.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/net/DataChannelProtocol.h" +#include "MediaManager.h" + +#include "transport/nr_socket_proxy_config.h" +#include "RTCSctpTransport.h" +#include "RTCDtlsTransport.h" +#include "jsep/JsepTransport.h" + +#include "nsILoadInfo.h" +#include "nsIPrincipal.h" +#include "mozilla/LoadInfo.h" +#include "nsIProxiedChannel.h" + +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/net/WebrtcProxyConfig.h" + +#ifdef XP_WIN +// We need to undef the MS macro again in case the windows include file +// got imported after we included mozilla/dom/Document.h +# ifdef CreateEvent +# undef CreateEvent +# endif +#endif // XP_WIN + +#include "MediaSegment.h" + +#ifdef USE_FAKE_PCOBSERVER +# include "FakePCObserver.h" +#else +# include "mozilla/dom/PeerConnectionObserverBinding.h" +#endif +#include "mozilla/dom/PeerConnectionObserverEnumsBinding.h" + +#define ICE_PARSING \ + "In RTCConfiguration passed to RTCPeerConnection constructor" + +using namespace mozilla; +using namespace mozilla::dom; + +typedef PCObserverString ObString; + +static const char* pciLogTag = "PeerConnectionImpl"; +#ifdef LOGTAG +# undef LOGTAG +#endif +#define LOGTAG pciLogTag + +static mozilla::LazyLogModule logModuleInfo("signaling"); + +// Getting exceptions back down from PCObserver is generally not harmful. +namespace { +// This is a terrible hack. The problem is that SuppressException is not +// inline, and we link this file without libxul in some cases (e.g. for our test +// setup). So we can't use ErrorResult or IgnoredErrorResult because those call +// SuppressException... And we can't use FastErrorResult because we can't +// include BindingUtils.h, because our linking is completely broken. Use +// BaseErrorResult directly. Please do not let me see _anyone_ doing this +// without really careful review from someone who knows what they are doing. +class JSErrorResult : public binding_danger::TErrorResult< + binding_danger::JustAssertCleanupPolicy> { + public: + ~JSErrorResult() { SuppressException(); } +} JS_HAZ_ROOTED; + +// The WrapRunnable() macros copy passed-in args and passes them to the function +// later on the other thread. ErrorResult cannot be passed like this because it +// disallows copy-semantics. +// +// This WrappableJSErrorResult hack solves this by not actually copying the +// ErrorResult, but creating a new one instead, which works because we don't +// care about the result. +// +// Since this is for JS-calls, these can only be dispatched to the main thread. + +class WrappableJSErrorResult { + public: + WrappableJSErrorResult() : mRv(MakeUnique()), isCopy(false) {} + WrappableJSErrorResult(const WrappableJSErrorResult& other) + : mRv(MakeUnique()), isCopy(true) {} + ~WrappableJSErrorResult() { + if (isCopy) { + MOZ_ASSERT(NS_IsMainThread()); + } + } + operator ErrorResult&() { return *mRv; } + + private: + mozilla::UniquePtr mRv; + bool isCopy; +} JS_HAZ_ROOTED; + +} // namespace + +static nsresult InitNSSInContent() { + NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD); + + if (!XRE_IsContentProcess()) { + MOZ_ASSERT_UNREACHABLE("Must be called in content process"); + return NS_ERROR_FAILURE; + } + + static bool nssStarted = false; + if (nssStarted) { + return NS_OK; + } + + if (NSS_NoDB_Init(nullptr) != SECSuccess) { + CSFLogError(LOGTAG, "NSS_NoDB_Init failed."); + return NS_ERROR_FAILURE; + } + + if (NS_FAILED(mozilla::psm::InitializeCipherSuite())) { + CSFLogError(LOGTAG, "Fail to set up nss cipher suite."); + return NS_ERROR_FAILURE; + } + + mozilla::psm::DisableMD5(); + + nssStarted = true; + + return NS_OK; +} + +namespace mozilla { +class DataChannel; +} + +namespace mozilla { + +void PeerConnectionAutoTimer::RegisterConnection() { mRefCnt++; } + +void PeerConnectionAutoTimer::UnregisterConnection(bool aContainedAV) { + MOZ_ASSERT(mRefCnt); + mRefCnt--; + mUsedAV |= aContainedAV; + if (mRefCnt == 0) { + if (mUsedAV) { + Telemetry::Accumulate( + Telemetry::WEBRTC_AV_CALL_DURATION, + static_cast((TimeStamp::Now() - mStart).ToSeconds())); + } + Telemetry::Accumulate( + Telemetry::WEBRTC_CALL_DURATION, + static_cast((TimeStamp::Now() - mStart).ToSeconds())); + } +} + +bool PeerConnectionAutoTimer::IsStopped() { return mRefCnt == 0; } + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(PeerConnectionImpl) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PeerConnectionImpl) + tmp->Close(); + tmp->BreakCycles(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPCObserver, mWindow, mCertificate, + mSTSThread, mReceiveStreams, mOperations, + mSctpTransport, mKungFuDeathGrip) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PeerConnectionImpl) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE( + mPCObserver, mWindow, mCertificate, mSTSThread, mReceiveStreams, + mOperations, mTransceivers, mSctpTransport, mKungFuDeathGrip) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(PeerConnectionImpl) +NS_IMPL_CYCLE_COLLECTING_RELEASE(PeerConnectionImpl) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PeerConnectionImpl) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +already_AddRefed PeerConnectionImpl::Constructor( + const dom::GlobalObject& aGlobal) { + RefPtr pc = new PeerConnectionImpl(&aGlobal); + + CSFLogDebug(LOGTAG, "Created PeerConnection: %p", pc.get()); + + return pc.forget(); +} + +JSObject* PeerConnectionImpl::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + return PeerConnectionImpl_Binding::Wrap(aCx, this, aGivenProto); +} + +nsPIDOMWindowInner* PeerConnectionImpl::GetParentObject() const { + return mWindow; +} + +bool PCUuidGenerator::Generate(std::string* idp) { + nsresult rv; + + if (!mGenerator) { + mGenerator = do_GetService("@mozilla.org/uuid-generator;1", &rv); + if (NS_FAILED(rv)) { + return false; + } + if (!mGenerator) { + return false; + } + } + + nsID id; + rv = mGenerator->GenerateUUIDInPlace(&id); + if (NS_FAILED(rv)) { + return false; + } + char buffer[NSID_LENGTH]; + id.ToProvidedString(buffer); + idp->assign(buffer); + + return true; +} + +bool IsPrivateBrowsing(nsPIDOMWindowInner* aWindow) { + if (!aWindow) { + return false; + } + + Document* doc = aWindow->GetExtantDoc(); + if (!doc) { + return false; + } + + nsILoadContext* loadContext = doc->GetLoadContext(); + return loadContext && loadContext->UsePrivateBrowsing(); +} + +PeerConnectionImpl::PeerConnectionImpl(const GlobalObject* aGlobal) + : mTimeCard(MOZ_LOG_TEST(logModuleInfo, LogLevel::Error) ? create_timecard() + : nullptr), + mJsConfiguration(), + mSignalingState(RTCSignalingState::Stable), + mIceConnectionState(RTCIceConnectionState::New), + mIceGatheringState(RTCIceGatheringState::New), + mConnectionState(RTCPeerConnectionState::New), + mWindow(do_QueryInterface(aGlobal ? aGlobal->GetAsSupports() : nullptr)), + mCertificate(nullptr), + mSTSThread(nullptr), + mForceIceTcp(false), + mTransportHandler(nullptr), + mUuidGen(MakeUnique()), + mIceRestartCount(0), + mIceRollbackCount(0), + mHaveConfiguredCodecs(false), + mTrickle(true) // TODO(ekr@rtfm.com): Use pref + , + mPrivateWindow(false), + mActiveOnWindow(false), + mTimestampMaker(dom::RTCStatsTimestampMaker::Create(mWindow)), + mIdGenerator(new RTCStatsIdGenerator()), + listenPort(0), + connectPort(0), + connectStr(nullptr) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT_IF(aGlobal, mWindow); + mKungFuDeathGrip = this; + if (aGlobal) { + if (IsPrivateBrowsing(mWindow)) { + mPrivateWindow = true; + mDisableLongTermStats = true; + } + mWindow->AddPeerConnection(); + mActiveOnWindow = true; + + if (mWindow->GetDocumentURI()) { + mWindow->GetDocumentURI()->GetAsciiHost(mHostname); + nsresult rv; + nsCOMPtr eTLDService( + do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv)); + if (eTLDService) { + Unused << eTLDService->GetBaseDomain(mWindow->GetDocumentURI(), 0, + mEffectiveTLDPlus1); + } + + mRtxIsAllowed = !HostnameInPref( + "media.peerconnection.video.use_rtx.blocklist", mHostname); + } + } + + if (!mUuidGen->Generate(&mHandle)) { + MOZ_CRASH(); + } + + CSFLogInfo(LOGTAG, "%s: PeerConnectionImpl constructor for %s", __FUNCTION__, + mHandle.c_str()); + STAMP_TIMECARD(mTimeCard, "Constructor Completed"); + mForceIceTcp = + Preferences::GetBool("media.peerconnection.ice.force_ice_tcp", false); + memset(mMaxReceiving, 0, sizeof(mMaxReceiving)); + memset(mMaxSending, 0, sizeof(mMaxSending)); + mJsConfiguration.mCertificatesProvided = false; + mJsConfiguration.mPeerIdentityProvided = false; +} + +PeerConnectionImpl::~PeerConnectionImpl() { + MOZ_ASSERT(NS_IsMainThread()); + + MOZ_ASSERT(!mTransportHandler, + "PeerConnection should either be closed, or not initted in the " + "first place."); + + if (mTimeCard) { + STAMP_TIMECARD(mTimeCard, "Destructor Invoked"); + STAMP_TIMECARD(mTimeCard, mHandle.c_str()); + print_timecard(mTimeCard); + destroy_timecard(mTimeCard); + mTimeCard = nullptr; + } + + CSFLogInfo(LOGTAG, "%s: PeerConnectionImpl destructor invoked for %s", + __FUNCTION__, mHandle.c_str()); +} + +nsresult PeerConnectionImpl::Initialize(PeerConnectionObserver& aObserver, + nsGlobalWindowInner* aWindow) { + nsresult res; + + MOZ_ASSERT(NS_IsMainThread()); + + mPCObserver = &aObserver; + + // Find the STS thread + + mSTSThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &res); + MOZ_ASSERT(mSTSThread); + + // We do callback handling on STS instead of main to avoid media jank. + // Someday, we may have a dedicated thread for this. + mTransportHandler = MediaTransportHandler::Create(mSTSThread); + if (mPrivateWindow) { + mTransportHandler->EnterPrivateMode(); + } + + // Initialize NSS if we are in content process. For chrome process, NSS should + // already been initialized. + if (XRE_IsParentProcess()) { + // This code interferes with the C++ unit test startup code. + nsCOMPtr nssDummy = do_GetService("@mozilla.org/psm;1", &res); + NS_ENSURE_SUCCESS(res, res); + } else { + NS_ENSURE_SUCCESS(res = InitNSSInContent(), res); + } + + // Currently no standalone unit tests for DataChannel, + // which is the user of mWindow + MOZ_ASSERT(aWindow); + mWindow = aWindow; + NS_ENSURE_STATE(mWindow); + + PRTime timestamp = PR_Now(); + // Ok if we truncate this, but we want it to be large enough to reliably + // contain the location on the tests we run in CI. + char temp[256]; + + nsAutoCString locationCStr; + + RefPtr location = mWindow->Location(); + nsAutoString locationAStr; + res = location->ToString(locationAStr); + NS_ENSURE_SUCCESS(res, res); + + CopyUTF16toUTF8(locationAStr, locationCStr); + + SprintfLiteral(temp, "%s %" PRIu64 " (id=%" PRIu64 " url=%s)", + mHandle.c_str(), static_cast(timestamp), + static_cast(mWindow ? mWindow->WindowID() : 0), + locationCStr.get() ? locationCStr.get() : "NULL"); + + mName = temp; + + STAMP_TIMECARD(mTimeCard, "Initializing PC Ctx"); + res = PeerConnectionCtx::InitializeGlobal(); + NS_ENSURE_SUCCESS(res, res); + + mTransportHandler->CreateIceCtx("PC:" + GetName()); + + mJsepSession = + MakeUnique(mName, MakeUnique()); + mJsepSession->SetRtxIsAllowed(mRtxIsAllowed); + + res = mJsepSession->Init(); + if (NS_FAILED(res)) { + CSFLogError(LOGTAG, "%s: Couldn't init JSEP Session, res=%u", __FUNCTION__, + static_cast(res)); + return res; + } + + std::vector> preferredCodecs; + SetupPreferredCodecs(preferredCodecs); + mJsepSession->SetDefaultCodecs(preferredCodecs); + + std::vector preferredHeaders; + SetupPreferredRtpExtensions(preferredHeaders); + + for (const auto& header : preferredHeaders) { + mJsepSession->AddRtpExtension(header.mMediaType, header.extensionname, + header.direction); + } + + if (XRE_IsContentProcess()) { + mStunAddrsRequest = + new net::StunAddrsRequestChild(new StunAddrsHandler(this)); + } + + // Initialize the media object. + mForceProxy = ShouldForceProxy(); + + // We put this here, in case we later want to set this based on a non-standard + // param in RTCConfiguration. + mAllowOldSetParameters = Preferences::GetBool( + "media.peerconnection.allow_old_setParameters", false); + + // setup the stun local addresses IPC async call + InitLocalAddrs(); + + mSignalHandler = MakeUnique(this, mTransportHandler.get()); + + PeerConnectionCtx::GetInstance()->AddPeerConnection(mHandle, this); + + return NS_OK; +} + +void PeerConnectionImpl::Initialize(PeerConnectionObserver& aObserver, + nsGlobalWindowInner& aWindow, + ErrorResult& rv) { + MOZ_ASSERT(NS_IsMainThread()); + + nsresult res = Initialize(aObserver, &aWindow); + if (NS_FAILED(res)) { + rv.Throw(res); + return; + } +} + +void PeerConnectionImpl::SetCertificate( + mozilla::dom::RTCCertificate& aCertificate) { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + MOZ_ASSERT(!mCertificate, "This can only be called once"); + mCertificate = &aCertificate; + + std::vector fingerprint; + nsresult rv = + CalculateFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM, &fingerprint); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "%s: Couldn't calculate fingerprint, rv=%u", + __FUNCTION__, static_cast(rv)); + mCertificate = nullptr; + return; + } + rv = mJsepSession->AddDtlsFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM, + fingerprint); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "%s: Couldn't set DTLS credentials, rv=%u", + __FUNCTION__, static_cast(rv)); + mCertificate = nullptr; + } + + if (mUncommittedJsepSession) { + Unused << mUncommittedJsepSession->AddDtlsFingerprint( + DtlsIdentity::DEFAULT_HASH_ALGORITHM, fingerprint); + } +} + +const RefPtr& PeerConnectionImpl::Certificate() + const { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mCertificate; +} + +RefPtr PeerConnectionImpl::Identity() const { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + MOZ_ASSERT(mCertificate); + return mCertificate->CreateDtlsIdentity(); +} + +class CompareCodecPriority { + public: + void SetPreferredCodec(int32_t preferredCodec) { + // This pref really ought to be a string, preferably something like + // "H264" or "VP8" instead of a payload type. + // Bug 1101259. + std::ostringstream os; + os << preferredCodec; + mPreferredCodec = os.str(); + } + + bool operator()(const UniquePtr& lhs, + const UniquePtr& rhs) const { + if (!mPreferredCodec.empty() && lhs->mDefaultPt == mPreferredCodec && + rhs->mDefaultPt != mPreferredCodec) { + return true; + } + + if (lhs->mStronglyPreferred && !rhs->mStronglyPreferred) { + return true; + } + + return false; + } + + private: + std::string mPreferredCodec; +}; + +class ConfigureCodec { + public: + explicit ConfigureCodec(nsCOMPtr& branch) + : mHardwareH264Enabled(false), + mSoftwareH264Enabled(false), + mH264Enabled(false), + mVP9Enabled(true), + mVP9Preferred(false), + mH264Level(13), // minimum suggested for WebRTC spec + mH264MaxBr(0), // Unlimited + mH264MaxMbps(0), // Unlimited + mVP8MaxFs(0), + mVP8MaxFr(0), + mUseTmmbr(false), + mUseRemb(false), + mUseTransportCC(false), + mUseAudioFec(false), + mRedUlpfecEnabled(false), + mDtmfEnabled(false) { + mSoftwareH264Enabled = PeerConnectionCtx::GetInstance()->gmpHasH264(); + + if (WebrtcVideoConduit::HasH264Hardware()) { + Telemetry::Accumulate(Telemetry::WEBRTC_HAS_H264_HARDWARE, true); + branch->GetBoolPref("media.webrtc.hw.h264.enabled", + &mHardwareH264Enabled); + } + + mH264Enabled = mHardwareH264Enabled || mSoftwareH264Enabled; + Telemetry::Accumulate(Telemetry::WEBRTC_SOFTWARE_H264_ENABLED, + mSoftwareH264Enabled); + Telemetry::Accumulate(Telemetry::WEBRTC_HARDWARE_H264_ENABLED, + mHardwareH264Enabled); + Telemetry::Accumulate(Telemetry::WEBRTC_H264_ENABLED, mH264Enabled); + + branch->GetIntPref("media.navigator.video.h264.level", &mH264Level); + mH264Level &= 0xFF; + + branch->GetIntPref("media.navigator.video.h264.max_br", &mH264MaxBr); + + branch->GetIntPref("media.navigator.video.h264.max_mbps", &mH264MaxMbps); + + branch->GetBoolPref("media.peerconnection.video.vp9_enabled", &mVP9Enabled); + + branch->GetBoolPref("media.peerconnection.video.vp9_preferred", + &mVP9Preferred); + + branch->GetIntPref("media.navigator.video.max_fs", &mVP8MaxFs); + if (mVP8MaxFs <= 0) { + mVP8MaxFs = 12288; // We must specify something other than 0 + } + + branch->GetIntPref("media.navigator.video.max_fr", &mVP8MaxFr); + if (mVP8MaxFr <= 0) { + mVP8MaxFr = 60; // We must specify something other than 0 + } + + // TMMBR is enabled from a pref in about:config + branch->GetBoolPref("media.navigator.video.use_tmmbr", &mUseTmmbr); + + // REMB is enabled by default, but can be disabled from about:config + branch->GetBoolPref("media.navigator.video.use_remb", &mUseRemb); + + branch->GetBoolPref("media.navigator.video.use_transport_cc", + &mUseTransportCC); + + branch->GetBoolPref("media.navigator.audio.use_fec", &mUseAudioFec); + + branch->GetBoolPref("media.navigator.video.red_ulpfec_enabled", + &mRedUlpfecEnabled); + + // media.peerconnection.dtmf.enabled controls both sdp generation for + // DTMF support as well as DTMF exposure to DOM + branch->GetBoolPref("media.peerconnection.dtmf.enabled", &mDtmfEnabled); + } + + void operator()(UniquePtr& codec) const { + switch (codec->Type()) { + case SdpMediaSection::kAudio: { + JsepAudioCodecDescription& audioCodec = + static_cast(*codec); + if (audioCodec.mName == "opus") { + audioCodec.mFECEnabled = mUseAudioFec; + } else if (audioCodec.mName == "telephone-event") { + audioCodec.mEnabled = mDtmfEnabled; + } + } break; + case SdpMediaSection::kVideo: { + JsepVideoCodecDescription& videoCodec = + static_cast(*codec); + + if (videoCodec.mName == "H264") { + // Override level + videoCodec.mProfileLevelId &= 0xFFFF00; + videoCodec.mProfileLevelId |= mH264Level; + + videoCodec.mConstraints.maxBr = mH264MaxBr; + + videoCodec.mConstraints.maxMbps = mH264MaxMbps; + + // Might disable it, but we set up other params anyway + videoCodec.mEnabled = mH264Enabled; + + if (videoCodec.mPacketizationMode == 0 && !mSoftwareH264Enabled) { + // We're assuming packetization mode 0 is unsupported by + // hardware. + videoCodec.mEnabled = false; + } + + if (mHardwareH264Enabled) { + videoCodec.mStronglyPreferred = true; + } + } else if (videoCodec.mName == "red") { + videoCodec.mEnabled = mRedUlpfecEnabled; + } else if (videoCodec.mName == "ulpfec") { + videoCodec.mEnabled = mRedUlpfecEnabled; + } else if (videoCodec.mName == "VP8" || videoCodec.mName == "VP9") { + if (videoCodec.mName == "VP9") { + if (!mVP9Enabled) { + videoCodec.mEnabled = false; + break; + } + if (mVP9Preferred) { + videoCodec.mStronglyPreferred = true; + } + } + videoCodec.mConstraints.maxFs = mVP8MaxFs; + videoCodec.mConstraints.maxFps = Some(mVP8MaxFr); + } + + if (mUseTmmbr) { + videoCodec.EnableTmmbr(); + } + if (mUseRemb) { + videoCodec.EnableRemb(); + } + if (mUseTransportCC) { + videoCodec.EnableTransportCC(); + } + } break; + case SdpMediaSection::kText: + case SdpMediaSection::kApplication: + case SdpMediaSection::kMessage: { + } // Nothing to configure for these. + } + } + + private: + bool mHardwareH264Enabled; + bool mSoftwareH264Enabled; + bool mH264Enabled; + bool mVP9Enabled; + bool mVP9Preferred; + int32_t mH264Level; + int32_t mH264MaxBr; + int32_t mH264MaxMbps; + int32_t mVP8MaxFs; + int32_t mVP8MaxFr; + bool mUseTmmbr; + bool mUseRemb; + bool mUseTransportCC; + bool mUseAudioFec; + bool mRedUlpfecEnabled; + bool mDtmfEnabled; +}; + +class ConfigureRedCodec { + public: + explicit ConfigureRedCodec(nsCOMPtr& branch, + std::vector* redundantEncodings) + : mRedundantEncodings(redundantEncodings) { + // if we wanted to override or modify which encodings are considered + // for redundant encodings, we'd probably want to handle it here by + // checking prefs modifying the operator() code below + } + + void operator()(UniquePtr& codec) const { + if (codec->Type() == SdpMediaSection::kVideo && !codec->mEnabled) { + uint8_t pt = (uint8_t)strtoul(codec->mDefaultPt.c_str(), nullptr, 10); + // don't search for the codec payload type unless we have a valid + // conversion (non-zero) + if (pt != 0) { + std::vector::iterator it = std::find( + mRedundantEncodings->begin(), mRedundantEncodings->end(), pt); + if (it != mRedundantEncodings->end()) { + mRedundantEncodings->erase(it); + } + } + } + } + + private: + std::vector* mRedundantEncodings; +}; + +nsresult PeerConnectionImpl::ConfigureJsepSessionCodecs() { + nsresult res; + nsCOMPtr prefs = + do_GetService("@mozilla.org/preferences-service;1", &res); + + if (NS_FAILED(res)) { + CSFLogError(LOGTAG, "%s: Couldn't get prefs service, res=%u", __FUNCTION__, + static_cast(res)); + return res; + } + + nsCOMPtr branch = do_QueryInterface(prefs); + if (!branch) { + CSFLogError(LOGTAG, "%s: Couldn't get prefs branch", __FUNCTION__); + return NS_ERROR_FAILURE; + } + + ConfigureCodec configurer(branch); + mJsepSession->ForEachCodec(configurer); + + // if red codec is enabled, configure it for the other enabled codecs + for (auto& codec : mJsepSession->Codecs()) { + if (codec->mName == "red" && codec->mEnabled) { + JsepVideoCodecDescription* redCodec = + static_cast(codec.get()); + ConfigureRedCodec configureRed(branch, &(redCodec->mRedundantEncodings)); + mJsepSession->ForEachCodec(configureRed); + break; + } + } + + // We use this to sort the list of codecs once everything is configured + CompareCodecPriority comparator; + + // Sort by priority + int32_t preferredCodec = 0; + branch->GetIntPref("media.navigator.video.preferred_codec", &preferredCodec); + + if (preferredCodec) { + comparator.SetPreferredCodec(preferredCodec); + } + + mJsepSession->SortCodecs(comparator); + return NS_OK; +} + +// Data channels won't work without a window, so in order for the C++ unit +// tests to work (it doesn't have a window available) we ifdef the following +// two implementations. +// +// Note: 'media.peerconnection.sctp.force_maximum_message_size' changes +// behaviour triggered by these parameters. +NS_IMETHODIMP +PeerConnectionImpl::EnsureDataConnection(uint16_t aLocalPort, + uint16_t aNumstreams, + uint32_t aMaxMessageSize, + bool aMMSSet) { + PC_AUTO_ENTER_API_CALL(false); + + if (mDataConnection) { + CSFLogDebug(LOGTAG, "%s DataConnection already connected", __FUNCTION__); + mDataConnection->SetMaxMessageSize(aMMSSet, aMaxMessageSize); + return NS_OK; + } + + nsCOMPtr target = + mWindow ? mWindow->EventTargetFor(TaskCategory::Other) : nullptr; + Maybe mms = aMMSSet ? Some(aMaxMessageSize) : Nothing(); + if (auto res = DataChannelConnection::Create(this, target, mTransportHandler, + aLocalPort, aNumstreams, mms)) { + mDataConnection = res.value(); + CSFLogDebug(LOGTAG, "%s DataChannelConnection %p attached to %s", + __FUNCTION__, (void*)mDataConnection.get(), mHandle.c_str()); + return NS_OK; + } + CSFLogError(LOGTAG, "%s DataConnection Create Failed", __FUNCTION__); + return NS_ERROR_FAILURE; +} + +nsresult PeerConnectionImpl::GetDatachannelParameters( + uint32_t* channels, uint16_t* localport, uint16_t* remoteport, + uint32_t* remotemaxmessagesize, bool* mmsset, std::string* transportId, + bool* client) const { + // Clear, just in case we fail. + *channels = 0; + *localport = 0; + *remoteport = 0; + *remotemaxmessagesize = 0; + *mmsset = false; + transportId->clear(); + + Maybe datachannelTransceiver = + mJsepSession->FindTransceiver([](const JsepTransceiver& aTransceiver) { + return aTransceiver.GetMediaType() == SdpMediaSection::kApplication; + }); + + if (!datachannelTransceiver || + !datachannelTransceiver->mTransport.mComponents || + !datachannelTransceiver->mSendTrack.GetNegotiatedDetails()) { + return NS_ERROR_FAILURE; + } + + // This will release assert if there is no such index, and that's ok + const JsepTrackEncoding& encoding = + datachannelTransceiver->mSendTrack.GetNegotiatedDetails()->GetEncoding(0); + + if (NS_WARN_IF(encoding.GetCodecs().empty())) { + CSFLogError(LOGTAG, + "%s: Negotiated m=application with no codec. " + "This is likely to be broken.", + __FUNCTION__); + return NS_ERROR_FAILURE; + } + + for (const auto& codec : encoding.GetCodecs()) { + if (codec->Type() != SdpMediaSection::kApplication) { + CSFLogError(LOGTAG, + "%s: Codec type for m=application was %u, this " + "is a bug.", + __FUNCTION__, static_cast(codec->Type())); + MOZ_ASSERT(false, "Codec for m=application was not \"application\""); + return NS_ERROR_FAILURE; + } + + if (codec->mName != "webrtc-datachannel") { + CSFLogWarn(LOGTAG, + "%s: Codec for m=application was not " + "webrtc-datachannel (was instead %s). ", + __FUNCTION__, codec->mName.c_str()); + continue; + } + + if (codec->mChannels) { + *channels = codec->mChannels; + } else { + *channels = WEBRTC_DATACHANNEL_STREAMS_DEFAULT; + } + const JsepApplicationCodecDescription* appCodec = + static_cast(codec.get()); + *localport = appCodec->mLocalPort; + *remoteport = appCodec->mRemotePort; + *remotemaxmessagesize = appCodec->mRemoteMaxMessageSize; + *mmsset = appCodec->mRemoteMMSSet; + MOZ_ASSERT(!datachannelTransceiver->mTransport.mTransportId.empty()); + *transportId = datachannelTransceiver->mTransport.mTransportId; + *client = datachannelTransceiver->mTransport.mDtls->GetRole() == + JsepDtlsTransport::kJsepDtlsClient; + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +nsresult PeerConnectionImpl::AddRtpTransceiverToJsepSession( + JsepTransceiver& transceiver) { + nsresult res = ConfigureJsepSessionCodecs(); + if (NS_FAILED(res)) { + CSFLogError(LOGTAG, "Failed to configure codecs"); + return res; + } + + mJsepSession->AddTransceiver(transceiver); + return NS_OK; +} + +static Maybe ToSdpMediaType( + const nsAString& aKind) { + if (aKind.EqualsASCII("audio")) { + return Some(SdpMediaSection::MediaType::kAudio); + } else if (aKind.EqualsASCII("video")) { + return Some(SdpMediaSection::MediaType::kVideo); + } + return Nothing(); +} + +already_AddRefed PeerConnectionImpl::AddTransceiver( + const dom::RTCRtpTransceiverInit& aInit, const nsAString& aKind, + dom::MediaStreamTrack* aSendTrack, bool aAddTrackMagic, ErrorResult& aRv) { + // Copy, because we might need to modify + RTCRtpTransceiverInit init(aInit); + + Maybe type = ToSdpMediaType(aKind); + if (NS_WARN_IF(!type.isSome())) { + MOZ_ASSERT(false, "Invalid media kind"); + aRv = NS_ERROR_INVALID_ARG; + return nullptr; + } + + JsepTransceiver jsepTransceiver(*type, *mUuidGen); + jsepTransceiver.SetRtxIsAllowed(mRtxIsAllowed); + + // Do this last, since it is not possible to roll back. + nsresult rv = AddRtpTransceiverToJsepSession(jsepTransceiver); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "%s: AddRtpTransceiverToJsepSession failed, res=%u", + __FUNCTION__, static_cast(rv)); + aRv = rv; + return nullptr; + } + + auto& sendEncodings = init.mSendEncodings; + + // CheckAndRectifyEncodings covers these six: + // If any encoding contains a rid member whose value does not conform to the + // grammar requirements specified in Section 10 of [RFC8851], throw a + // TypeError. + + // If some but not all encodings contain a rid member, throw a TypeError. + + // If any encoding contains a rid member whose value is the same as that of a + // rid contained in another encoding in sendEncodings, throw a TypeError. + + // If kind is "audio", remove the scaleResolutionDownBy member from all + // encodings that contain one. + + // If any encoding contains a scaleResolutionDownBy member whose value is + // less than 1.0, throw a RangeError. + + // Verify that the value of each maxFramerate member in sendEncodings that is + // defined is greater than 0.0. If one of the maxFramerate values does not + // meet this requirement, throw a RangeError. + RTCRtpSender::CheckAndRectifyEncodings(sendEncodings, + *type == SdpMediaSection::kVideo, aRv); + if (aRv.Failed()) { + return nullptr; + } + + // If any encoding contains a read-only parameter other than rid, throw an + // InvalidAccessError. + // NOTE: We don't support any additional read-only params right now. Also, + // spec shoehorns this in between checks that setParameters also performs + // (between the rid checks and the scaleResolutionDownBy checks). + + // If any encoding contains a scaleResolutionDownBy member, then for each + // encoding without one, add a scaleResolutionDownBy member with the value + // 1.0. + for (const auto& constEncoding : sendEncodings) { + if (constEncoding.mScaleResolutionDownBy.WasPassed()) { + for (auto& encoding : sendEncodings) { + if (!encoding.mScaleResolutionDownBy.WasPassed()) { + encoding.mScaleResolutionDownBy.Construct(1.0f); + } + } + break; + } + } + + // Let maxN be the maximum number of total simultaneous encodings the user + // agent may support for this kind, at minimum 1.This should be an optimistic + // number since the codec to be used is not known yet. + size_t maxN = + (*type == SdpMediaSection::kVideo) ? webrtc::kMaxSimulcastStreams : 1; + + // If the number of encodings stored in sendEncodings exceeds maxN, then trim + // sendEncodings from the tail until its length is maxN. + // NOTE: Spec has this after all validation steps; even if there are elements + // that we will trim off, we still validate them. + if (sendEncodings.Length() > maxN) { + sendEncodings.TruncateLength(maxN); + } + + // If kind is "video" and none of the encodings contain a + // scaleResolutionDownBy member, then for each encoding, add a + // scaleResolutionDownBy member with the value 2^(length of sendEncodings - + // encoding index - 1). This results in smaller-to-larger resolutions where + // the last encoding has no scaling applied to it, e.g. 4:2:1 if the length + // is 3. + // NOTE: The code above ensures that these are all set, or all unset, so we + // can just check the first one. + if (sendEncodings.Length() && *type == SdpMediaSection::kVideo && + !sendEncodings[0].mScaleResolutionDownBy.WasPassed()) { + double scale = 1.0f; + for (auto it = sendEncodings.rbegin(); it != sendEncodings.rend(); ++it) { + it->mScaleResolutionDownBy.Construct(scale); + scale *= 2; + } + } + + // If the number of encodings now stored in sendEncodings is 1, then remove + // any rid member from the lone entry. + if (sendEncodings.Length() == 1) { + sendEncodings[0].mRid.Reset(); + } + + RefPtr transceiver = CreateTransceiver( + jsepTransceiver.GetUuid(), + jsepTransceiver.GetMediaType() == SdpMediaSection::kVideo, init, + aSendTrack, aAddTrackMagic, aRv); + + if (aRv.Failed()) { + // Would be nice if we could peek at the rv without stealing it, so we + // could log... + CSFLogError(LOGTAG, "%s: failed", __FUNCTION__); + return nullptr; + } + + mTransceivers.AppendElement(transceiver); + return transceiver.forget(); +} + +bool PeerConnectionImpl::CheckNegotiationNeeded() { + MOZ_ASSERT(mSignalingState == RTCSignalingState::Stable); + SyncToJsep(); + return !mLocalIceCredentialsToReplace.empty() || + mJsepSession->CheckNegotiationNeeded(); +} + +bool PeerConnectionImpl::CreatedSender(const dom::RTCRtpSender& aSender) const { + return aSender.IsMyPc(this); +} + +nsresult PeerConnectionImpl::InitializeDataChannel() { + PC_AUTO_ENTER_API_CALL(false); + CSFLogDebug(LOGTAG, "%s", __FUNCTION__); + + uint32_t channels = 0; + uint16_t localport = 0; + uint16_t remoteport = 0; + uint32_t remotemaxmessagesize = 0; + bool mmsset = false; + std::string transportId; + bool client = false; + nsresult rv = GetDatachannelParameters(&channels, &localport, &remoteport, + &remotemaxmessagesize, &mmsset, + &transportId, &client); + + if (NS_FAILED(rv)) { + CSFLogDebug(LOGTAG, "%s: We did not negotiate datachannel", __FUNCTION__); + return NS_OK; + } + + if (channels > MAX_NUM_STREAMS) { + channels = MAX_NUM_STREAMS; + } + + rv = EnsureDataConnection(localport, channels, remotemaxmessagesize, mmsset); + if (NS_SUCCEEDED(rv)) { + if (mDataConnection->ConnectToTransport(transportId, client, localport, + remoteport)) { + return NS_OK; + } + // If we inited the DataConnection, call Destroy() before releasing it + mDataConnection->Destroy(); + } + mDataConnection = nullptr; + return NS_ERROR_FAILURE; +} + +already_AddRefed PeerConnectionImpl::CreateDataChannel( + const nsAString& aLabel, const nsAString& aProtocol, uint16_t aType, + bool ordered, uint16_t aMaxTime, uint16_t aMaxNum, bool aExternalNegotiated, + uint16_t aStream, ErrorResult& rv) { + RefPtr result; + rv = CreateDataChannel(aLabel, aProtocol, aType, ordered, aMaxTime, aMaxNum, + aExternalNegotiated, aStream, getter_AddRefs(result)); + return result.forget(); +} + +NS_IMETHODIMP +PeerConnectionImpl::CreateDataChannel( + const nsAString& aLabel, const nsAString& aProtocol, uint16_t aType, + bool ordered, uint16_t aMaxTime, uint16_t aMaxNum, bool aExternalNegotiated, + uint16_t aStream, nsDOMDataChannel** aRetval) { + PC_AUTO_ENTER_API_CALL(false); + MOZ_ASSERT(aRetval); + + RefPtr dataChannel; + DataChannelConnection::Type theType = + static_cast(aType); + + nsresult rv = EnsureDataConnection( + WEBRTC_DATACHANNEL_PORT_DEFAULT, WEBRTC_DATACHANNEL_STREAMS_DEFAULT, + WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_REMOTE_DEFAULT, false); + if (NS_FAILED(rv)) { + return rv; + } + dataChannel = mDataConnection->Open( + NS_ConvertUTF16toUTF8(aLabel), NS_ConvertUTF16toUTF8(aProtocol), theType, + ordered, + aType == DataChannelConnection::PARTIAL_RELIABLE_REXMIT + ? aMaxNum + : (aType == DataChannelConnection::PARTIAL_RELIABLE_TIMED ? aMaxTime + : 0), + nullptr, nullptr, aExternalNegotiated, aStream); + NS_ENSURE_TRUE(dataChannel, NS_ERROR_NOT_AVAILABLE); + + CSFLogDebug(LOGTAG, "%s: making DOMDataChannel", __FUNCTION__); + + Maybe dcTransceiver = + mJsepSession->FindTransceiver([](const JsepTransceiver& aTransceiver) { + return aTransceiver.GetMediaType() == SdpMediaSection::kApplication; + }); + + if (dcTransceiver) { + dcTransceiver->RestartDatachannelTransceiver(); + mJsepSession->SetTransceiver(*dcTransceiver); + } else { + mJsepSession->AddTransceiver( + JsepTransceiver(SdpMediaSection::MediaType::kApplication, *mUuidGen)); + } + + RefPtr retval; + rv = NS_NewDOMDataChannel(dataChannel.forget(), mWindow, + getter_AddRefs(retval)); + if (NS_FAILED(rv)) { + return rv; + } + retval.forget(aRetval); + return NS_OK; +} + +NS_IMPL_CYCLE_COLLECTION(PeerConnectionImpl::Operation, mPromise, mPc) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PeerConnectionImpl::Operation) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END +NS_IMPL_CYCLE_COLLECTING_ADDREF(PeerConnectionImpl::Operation) +NS_IMPL_CYCLE_COLLECTING_RELEASE(PeerConnectionImpl::Operation) + +PeerConnectionImpl::Operation::Operation(PeerConnectionImpl* aPc, + ErrorResult& aError) + : mPromise(aPc->MakePromise(aError)), mPc(aPc) {} + +PeerConnectionImpl::Operation::~Operation() = default; + +void PeerConnectionImpl::Operation::Call(ErrorResult& aError) { + RefPtr opPromise = CallImpl(aError); + if (aError.Failed()) { + return; + } + // Upon fulfillment or rejection of the promise returned by the operation, + // run the following steps: + // (NOTE: mPromise is p from https://w3c.github.io/webrtc-pc/#dfn-chain, + // and CallImpl() is what returns the promise for the operation itself) + opPromise->AppendNativeHandler(this); +} + +void PeerConnectionImpl::Operation::ResolvedCallback( + JSContext* aCx, JS::Handle aValue, ErrorResult& aRv) { + // If connection.[[IsClosed]] is true, abort these steps. + // (the spec wants p to never settle in this event) + if (!mPc->IsClosed()) { + // If the promise returned by operation was fulfilled with a + // value, fulfill p with that value. + mPromise->MaybeResolveWithClone(aCx, aValue); + // Upon fulfillment or rejection of p, execute the following + // steps: + // (Static analysis forces us to use a temporary) + RefPtr pc = mPc; + pc->RunNextOperation(aRv); + } +} + +void PeerConnectionImpl::Operation::RejectedCallback( + JSContext* aCx, JS::Handle aValue, ErrorResult& aRv) { + // If connection.[[IsClosed]] is true, abort these steps. + // (the spec wants p to never settle in this event) + if (!mPc->IsClosed()) { + // If the promise returned by operation was rejected with a + // value, reject p with that value. + mPromise->MaybeRejectWithClone(aCx, aValue); + // Upon fulfillment or rejection of p, execute the following + // steps: + // (Static analysis forces us to use a temporary) + RefPtr pc = mPc; + pc->RunNextOperation(aRv); + } +} + +NS_IMPL_CYCLE_COLLECTION_INHERITED(PeerConnectionImpl::JSOperation, + PeerConnectionImpl::Operation, mOperation) + +NS_IMPL_ADDREF_INHERITED(PeerConnectionImpl::JSOperation, + PeerConnectionImpl::Operation) +NS_IMPL_RELEASE_INHERITED(PeerConnectionImpl::JSOperation, + PeerConnectionImpl::Operation) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PeerConnectionImpl::JSOperation) +NS_INTERFACE_MAP_END_INHERITING(PeerConnectionImpl::Operation) + +PeerConnectionImpl::JSOperation::JSOperation(PeerConnectionImpl* aPc, + dom::ChainedOperation& aOp, + ErrorResult& aError) + : Operation(aPc, aError), mOperation(&aOp) {} + +RefPtr PeerConnectionImpl::JSOperation::CallImpl( + ErrorResult& aError) { + // Static analysis will not let us call this without a temporary :( + RefPtr op = mOperation; + return op->Call(aError); +} + +already_AddRefed PeerConnectionImpl::Chain( + dom::ChainedOperation& aOperation, ErrorResult& aError) { + MOZ_RELEASE_ASSERT(!mChainingOperation); + mChainingOperation = true; + RefPtr operation = new JSOperation(this, aOperation, aError); + if (aError.Failed()) { + return nullptr; + } + RefPtr promise = Chain(operation, aError); + if (aError.Failed()) { + return nullptr; + } + mChainingOperation = false; + return promise.forget(); +} + +// This is kinda complicated, but it is what the spec requires us to do. The +// core of what makes this complicated is the requirement that |aOperation| be +// run _immediately_ (without any Promise.Then!) if the operations chain is +// empty. +already_AddRefed PeerConnectionImpl::Chain( + const RefPtr& aOperation, ErrorResult& aError) { + // If connection.[[IsClosed]] is true, return a promise rejected with a newly + // created InvalidStateError. + if (IsClosed()) { + CSFLogDebug(LOGTAG, "%s:%d: Peer connection is closed", __FILE__, __LINE__); + RefPtr error = MakePromise(aError); + if (aError.Failed()) { + return nullptr; + } + error->MaybeRejectWithInvalidStateError("Peer connection is closed"); + return error.forget(); + } + + // Append operation to [[Operations]]. + mOperations.AppendElement(aOperation); + + // If the length of [[Operations]] is exactly 1, execute operation. + if (mOperations.Length() == 1) { + aOperation->Call(aError); + if (aError.Failed()) { + return nullptr; + } + } + + // This is the promise p from https://w3c.github.io/webrtc-pc/#dfn-chain + return do_AddRef(aOperation->GetPromise()); +} + +void PeerConnectionImpl::RunNextOperation(ErrorResult& aError) { + // If connection.[[IsClosed]] is true, abort these steps. + if (IsClosed()) { + return; + } + + // Remove the first element of [[Operations]]. + mOperations.RemoveElementAt(0); + + // If [[Operations]] is non-empty, execute the operation represented by the + // first element of [[Operations]], and abort these steps. + if (mOperations.Length()) { + // Cannot call without a temporary :( + RefPtr op = mOperations[0]; + op->Call(aError); + return; + } + + // If connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] is false, abort + // these steps. + if (!mUpdateNegotiationNeededFlagOnEmptyChain) { + return; + } + + // Set connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] to false. + mUpdateNegotiationNeededFlagOnEmptyChain = false; + // Update the negotiation-needed flag for connection. + UpdateNegotiationNeeded(); +} + +void PeerConnectionImpl::SyncToJsep() { + for (const auto& transceiver : mTransceivers) { + transceiver->SyncToJsep(*mJsepSession); + } +} + +void PeerConnectionImpl::SyncFromJsep() { + CSFLogDebug(LOGTAG, "%s", __FUNCTION__); + mJsepSession->ForEachTransceiver( + [this, self = RefPtr(this)]( + const JsepTransceiver& jsepTransceiver) { + if (jsepTransceiver.GetMediaType() == + SdpMediaSection::MediaType::kApplication) { + return; + } + + CSFLogDebug(LOGTAG, "%s: Looking for match", __FUNCTION__); + RefPtr transceiver; + for (auto& temp : mTransceivers) { + if (temp->GetJsepTransceiverId() == jsepTransceiver.GetUuid()) { + CSFLogDebug(LOGTAG, "%s: Found match", __FUNCTION__); + transceiver = temp; + break; + } + } + + if (!transceiver) { + CSFLogDebug(LOGTAG, "%s: No match, making new", __FUNCTION__); + dom::RTCRtpTransceiverInit init; + init.mDirection = RTCRtpTransceiverDirection::Recvonly; + IgnoredErrorResult rv; + transceiver = CreateTransceiver( + jsepTransceiver.GetUuid(), + jsepTransceiver.GetMediaType() == SdpMediaSection::kVideo, init, + nullptr, false, rv); + if (NS_WARN_IF(rv.Failed())) { + MOZ_ASSERT(false); + return; + } + mTransceivers.AppendElement(transceiver); + } + + CSFLogDebug(LOGTAG, "%s: Syncing transceiver", __FUNCTION__); + transceiver->SyncFromJsep(*mJsepSession); + }); +} + +already_AddRefed PeerConnectionImpl::MakePromise( + ErrorResult& aError) const { + nsCOMPtr global = do_QueryInterface(mWindow); + return dom::Promise::Create(global, aError); +} + +void PeerConnectionImpl::UpdateNegotiationNeeded() { + // If the length of connection.[[Operations]] is not 0, then set + // connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] to true, and abort + // these steps. + if (mOperations.Length() != 0) { + mUpdateNegotiationNeededFlagOnEmptyChain = true; + return; + } + + // Queue a task to run the following steps: + GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + __func__, [this, self = RefPtr(this)] { + // If connection.[[IsClosed]] is true, abort these steps. + if (IsClosed()) { + return; + } + // If the length of connection.[[Operations]] is not 0, then set + // connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] to true, and + // abort these steps. + if (mOperations.Length()) { + mUpdateNegotiationNeededFlagOnEmptyChain = true; + return; + } + // If connection's signaling state is not "stable", abort these steps. + if (mSignalingState != RTCSignalingState::Stable) { + return; + } + // If the result of checking if negotiation is needed is false, clear + // the negotiation-needed flag by setting + // connection.[[NegotiationNeeded]] to false, and abort these steps. + if (!CheckNegotiationNeeded()) { + mNegotiationNeeded = false; + return; + } + + // If connection.[[NegotiationNeeded]] is already true, abort these + // steps. + if (mNegotiationNeeded) { + return; + } + + // Set connection.[[NegotiationNeeded]] to true. + mNegotiationNeeded = true; + + // Fire an event named negotiationneeded at connection. + ErrorResult rv; + mPCObserver->FireNegotiationNeededEvent(rv); + })); +} + +void PeerConnectionImpl::NotifyDataChannel( + already_AddRefed aChannel) { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + + RefPtr channel(aChannel); + MOZ_ASSERT(channel); + CSFLogDebug(LOGTAG, "%s: channel: %p", __FUNCTION__, channel.get()); + + RefPtr domchannel; + nsresult rv = NS_NewDOMDataChannel(channel.forget(), mWindow, + getter_AddRefs(domchannel)); + NS_ENSURE_SUCCESS_VOID(rv); + + JSErrorResult jrv; + mPCObserver->NotifyDataChannel(*domchannel, jrv); +} + +void PeerConnectionImpl::NotifyDataChannelOpen(DataChannel*) { + mDataChannelsOpened++; +} + +void PeerConnectionImpl::NotifyDataChannelClosed(DataChannel*) { + mDataChannelsClosed++; +} + +void PeerConnectionImpl::NotifySctpConnected() { + if (!mSctpTransport) { + MOZ_ASSERT(false); + return; + } + + mSctpTransport->UpdateState(RTCSctpTransportState::Connected); +} + +void PeerConnectionImpl::NotifySctpClosed() { + if (!mSctpTransport) { + MOZ_ASSERT(false); + return; + } + + mSctpTransport->UpdateState(RTCSctpTransportState::Closed); +} + +NS_IMETHODIMP +PeerConnectionImpl::CreateOffer(const RTCOfferOptions& aOptions) { + JsepOfferOptions options; + // convert the RTCOfferOptions to JsepOfferOptions + if (aOptions.mOfferToReceiveAudio.WasPassed()) { + options.mOfferToReceiveAudio = + mozilla::Some(size_t(aOptions.mOfferToReceiveAudio.Value())); + } + + if (aOptions.mOfferToReceiveVideo.WasPassed()) { + options.mOfferToReceiveVideo = + mozilla::Some(size_t(aOptions.mOfferToReceiveVideo.Value())); + } + + options.mIceRestart = mozilla::Some(aOptions.mIceRestart || + !mLocalIceCredentialsToReplace.empty()); + + return CreateOffer(options); +} + +static void DeferredCreateOffer(const std::string& aPcHandle, + const JsepOfferOptions& aOptions) { + PeerConnectionWrapper wrapper(aPcHandle); + + if (wrapper.impl()) { + if (!PeerConnectionCtx::GetInstance()->isReady()) { + MOZ_CRASH( + "Why is DeferredCreateOffer being executed when the " + "PeerConnectionCtx isn't ready?"); + } + wrapper.impl()->CreateOffer(aOptions); + } +} + +// Have to use unique_ptr because webidl enums are generated without a +// copy c'tor. +static std::unique_ptr buildJSErrorData( + const JsepSession::Result& aResult, const std::string& aMessage) { + std::unique_ptr result(new dom::PCErrorData); + result->mName = *aResult.mError; + result->mMessage = NS_ConvertASCIItoUTF16(aMessage.c_str()); + return result; +} + +// Used by unit tests and the IDL CreateOffer. +NS_IMETHODIMP +PeerConnectionImpl::CreateOffer(const JsepOfferOptions& aOptions) { + PC_AUTO_ENTER_API_CALL(true); + + if (!PeerConnectionCtx::GetInstance()->isReady()) { + // Uh oh. We're not ready yet. Enqueue this operation. + PeerConnectionCtx::GetInstance()->queueJSEPOperation( + WrapRunnableNM(DeferredCreateOffer, mHandle, aOptions)); + STAMP_TIMECARD(mTimeCard, "Deferring CreateOffer (not ready)"); + return NS_OK; + } + + CSFLogDebug(LOGTAG, "CreateOffer()"); + + nsresult nrv = ConfigureJsepSessionCodecs(); + if (NS_FAILED(nrv)) { + CSFLogError(LOGTAG, "Failed to configure codecs"); + return nrv; + } + + STAMP_TIMECARD(mTimeCard, "Create Offer"); + + GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + __func__, [this, self = RefPtr(this), aOptions] { + std::string offer; + + SyncToJsep(); + UniquePtr uncommittedJsepSession(mJsepSession->Clone()); + JsepSession::Result result = + uncommittedJsepSession->CreateOffer(aOptions, &offer); + JSErrorResult rv; + if (result.mError.isSome()) { + std::string errorString = uncommittedJsepSession->GetLastError(); + + CSFLogError(LOGTAG, "%s: pc = %s, error = %s", __FUNCTION__, + mHandle.c_str(), errorString.c_str()); + + mPCObserver->OnCreateOfferError( + *buildJSErrorData(result, errorString), rv); + } else { + mJsepSession = std::move(uncommittedJsepSession); + mPCObserver->OnCreateOfferSuccess(ObString(offer.c_str()), rv); + } + })); + + return NS_OK; +} + +NS_IMETHODIMP +PeerConnectionImpl::CreateAnswer() { + PC_AUTO_ENTER_API_CALL(true); + + CSFLogDebug(LOGTAG, "CreateAnswer()"); + + STAMP_TIMECARD(mTimeCard, "Create Answer"); + // TODO(bug 1098015): Once RTCAnswerOptions is standardized, we'll need to + // add it as a param to CreateAnswer, and convert it here. + JsepAnswerOptions options; + + GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + __func__, [this, self = RefPtr(this), options] { + std::string answer; + SyncToJsep(); + UniquePtr uncommittedJsepSession(mJsepSession->Clone()); + JsepSession::Result result = + uncommittedJsepSession->CreateAnswer(options, &answer); + JSErrorResult rv; + if (result.mError.isSome()) { + std::string errorString = uncommittedJsepSession->GetLastError(); + + CSFLogError(LOGTAG, "%s: pc = %s, error = %s", __FUNCTION__, + mHandle.c_str(), errorString.c_str()); + + mPCObserver->OnCreateAnswerError( + *buildJSErrorData(result, errorString), rv); + } else { + mJsepSession = std::move(uncommittedJsepSession); + mPCObserver->OnCreateAnswerSuccess(ObString(answer.c_str()), rv); + } + })); + + return NS_OK; +} + +dom::RTCSdpType ToDomSdpType(JsepSdpType aType) { + switch (aType) { + case kJsepSdpOffer: + return dom::RTCSdpType::Offer; + case kJsepSdpAnswer: + return dom::RTCSdpType::Answer; + case kJsepSdpPranswer: + return dom::RTCSdpType::Pranswer; + case kJsepSdpRollback: + return dom::RTCSdpType::Rollback; + } + + MOZ_CRASH("Nonexistent JsepSdpType"); +} + +JsepSdpType ToJsepSdpType(dom::RTCSdpType aType) { + switch (aType) { + case dom::RTCSdpType::Offer: + return kJsepSdpOffer; + case dom::RTCSdpType::Pranswer: + return kJsepSdpPranswer; + case dom::RTCSdpType::Answer: + return kJsepSdpAnswer; + case dom::RTCSdpType::Rollback: + return kJsepSdpRollback; + case dom::RTCSdpType::EndGuard_:; + } + + MOZ_CRASH("Nonexistent dom::RTCSdpType"); +} + +NS_IMETHODIMP +PeerConnectionImpl::SetLocalDescription(int32_t aAction, const char* aSDP) { + PC_AUTO_ENTER_API_CALL(true); + + if (!aSDP) { + CSFLogError(LOGTAG, "%s - aSDP is NULL", __FUNCTION__); + return NS_ERROR_FAILURE; + } + + STAMP_TIMECARD(mTimeCard, "Set Local Description"); + + if (AnyLocalTrackHasPeerIdentity()) { + mRequestedPrivacy = Some(PrincipalPrivacy::Private); + } + + mozilla::dom::RTCSdpHistoryEntryInternal sdpEntry; + sdpEntry.mIsLocal = true; + sdpEntry.mTimestamp = mTimestampMaker.GetNow().ToDom(); + sdpEntry.mSdp = NS_ConvertASCIItoUTF16(aSDP); + auto appendHistory = [&]() { + if (!mSdpHistory.AppendElement(sdpEntry, fallible)) { + mozalloc_handle_oom(0); + } + }; + + mLocalRequestedSDP = aSDP; + + SyncToJsep(); + + bool wasRestartingIce = mJsepSession->IsIceRestarting(); + JsepSdpType sdpType; + switch (aAction) { + case IPeerConnection::kActionOffer: + sdpType = mozilla::kJsepSdpOffer; + break; + case IPeerConnection::kActionAnswer: + sdpType = mozilla::kJsepSdpAnswer; + break; + case IPeerConnection::kActionPRAnswer: + sdpType = mozilla::kJsepSdpPranswer; + break; + case IPeerConnection::kActionRollback: + sdpType = mozilla::kJsepSdpRollback; + break; + default: + MOZ_ASSERT(false); + appendHistory(); + return NS_ERROR_FAILURE; + } + MOZ_ASSERT(!mUncommittedJsepSession); + mUncommittedJsepSession.reset(mJsepSession->Clone()); + JsepSession::Result result = + mUncommittedJsepSession->SetLocalDescription(sdpType, mLocalRequestedSDP); + JSErrorResult rv; + if (result.mError.isSome()) { + std::string errorString = mUncommittedJsepSession->GetLastError(); + mUncommittedJsepSession = nullptr; + CSFLogError(LOGTAG, "%s: pc = %s, error = %s", __FUNCTION__, + mHandle.c_str(), errorString.c_str()); + mPCObserver->OnSetDescriptionError(*buildJSErrorData(result, errorString), + rv); + sdpEntry.mErrors = GetLastSdpParsingErrors(); + } else { + if (wasRestartingIce) { + RecordIceRestartStatistics(sdpType); + } + + mPCObserver->OnSetDescriptionSuccess(rv); + } + + appendHistory(); + + if (rv.Failed()) { + return rv.StealNSResult(); + } + + return NS_OK; +} + +static void DeferredSetRemote(const std::string& aPcHandle, int32_t aAction, + const std::string& aSdp) { + PeerConnectionWrapper wrapper(aPcHandle); + + if (wrapper.impl()) { + if (!PeerConnectionCtx::GetInstance()->isReady()) { + MOZ_CRASH( + "Why is DeferredSetRemote being executed when the " + "PeerConnectionCtx isn't ready?"); + } + wrapper.impl()->SetRemoteDescription(aAction, aSdp.c_str()); + } +} + +NS_IMETHODIMP +PeerConnectionImpl::SetRemoteDescription(int32_t action, const char* aSDP) { + PC_AUTO_ENTER_API_CALL(true); + + if (!aSDP) { + CSFLogError(LOGTAG, "%s - aSDP is NULL", __FUNCTION__); + return NS_ERROR_FAILURE; + } + + if (action == IPeerConnection::kActionOffer) { + if (!PeerConnectionCtx::GetInstance()->isReady()) { + // Uh oh. We're not ready yet. Enqueue this operation. (This must be a + // remote offer, or else we would not have gotten this far) + PeerConnectionCtx::GetInstance()->queueJSEPOperation(WrapRunnableNM( + DeferredSetRemote, mHandle, action, std::string(aSDP))); + STAMP_TIMECARD(mTimeCard, "Deferring SetRemote (not ready)"); + return NS_OK; + } + + nsresult nrv = ConfigureJsepSessionCodecs(); + if (NS_FAILED(nrv)) { + CSFLogError(LOGTAG, "Failed to configure codecs"); + return nrv; + } + } + + STAMP_TIMECARD(mTimeCard, "Set Remote Description"); + + mozilla::dom::RTCSdpHistoryEntryInternal sdpEntry; + sdpEntry.mIsLocal = false; + sdpEntry.mTimestamp = mTimestampMaker.GetNow().ToDom(); + sdpEntry.mSdp = NS_ConvertASCIItoUTF16(aSDP); + auto appendHistory = [&]() { + if (!mSdpHistory.AppendElement(sdpEntry, fallible)) { + mozalloc_handle_oom(0); + } + }; + + SyncToJsep(); + + mRemoteRequestedSDP = aSDP; + bool wasRestartingIce = mJsepSession->IsIceRestarting(); + JsepSdpType sdpType; + switch (action) { + case IPeerConnection::kActionOffer: + sdpType = mozilla::kJsepSdpOffer; + break; + case IPeerConnection::kActionAnswer: + sdpType = mozilla::kJsepSdpAnswer; + break; + case IPeerConnection::kActionPRAnswer: + sdpType = mozilla::kJsepSdpPranswer; + break; + case IPeerConnection::kActionRollback: + sdpType = mozilla::kJsepSdpRollback; + break; + default: + MOZ_ASSERT(false); + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(!mUncommittedJsepSession); + mUncommittedJsepSession.reset(mJsepSession->Clone()); + JsepSession::Result result = mUncommittedJsepSession->SetRemoteDescription( + sdpType, mRemoteRequestedSDP); + JSErrorResult jrv; + if (result.mError.isSome()) { + std::string errorString = mUncommittedJsepSession->GetLastError(); + mUncommittedJsepSession = nullptr; + sdpEntry.mErrors = GetLastSdpParsingErrors(); + CSFLogError(LOGTAG, "%s: pc = %s, error = %s", __FUNCTION__, + mHandle.c_str(), errorString.c_str()); + mPCObserver->OnSetDescriptionError(*buildJSErrorData(result, errorString), + jrv); + } else { + if (wasRestartingIce) { + RecordIceRestartStatistics(sdpType); + } + + mPCObserver->OnSetDescriptionSuccess(jrv); + } + + appendHistory(); + + if (jrv.Failed()) { + return jrv.StealNSResult(); + } + + return NS_OK; +} + +already_AddRefed PeerConnectionImpl::GetStats( + MediaStreamTrack* aSelector) { + if (!mWindow) { + MOZ_CRASH("Cannot create a promise without a window!"); + } + + nsCOMPtr global = do_QueryInterface(mWindow); + ErrorResult rv; + RefPtr promise = Promise::Create(global, rv); + if (NS_WARN_IF(rv.Failed())) { + MOZ_CRASH("Failed to create a promise!"); + } + + if (!IsClosed()) { + GetStats(aSelector, false) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [promise, window = mWindow]( + UniquePtr&& aReport) { + RefPtr report(new RTCStatsReport(window)); + report->Incorporate(*aReport); + promise->MaybeResolve(std::move(report)); + }, + [promise, window = mWindow](nsresult aError) { + RefPtr report(new RTCStatsReport(window)); + promise->MaybeResolve(std::move(report)); + }); + } else { + promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); + } + + return promise.forget(); +} + +void PeerConnectionImpl::GetRemoteStreams( + nsTArray>& aStreamsOut) const { + aStreamsOut = mReceiveStreams.Clone(); +} + +NS_IMETHODIMP +PeerConnectionImpl::AddIceCandidate( + const char* aCandidate, const char* aMid, const char* aUfrag, + const dom::Nullable& aLevel) { + PC_AUTO_ENTER_API_CALL(true); + + if (mForceIceTcp && + std::string::npos != std::string(aCandidate).find(" UDP ")) { + CSFLogError(LOGTAG, "Blocking remote UDP candidate: %s", aCandidate); + return NS_OK; + } + + STAMP_TIMECARD(mTimeCard, "Add Ice Candidate"); + + CSFLogDebug(LOGTAG, "AddIceCandidate: %s %s", aCandidate, aUfrag); + + std::string transportId; + Maybe level; + if (!aLevel.IsNull()) { + level = Some(aLevel.Value()); + } + MOZ_DIAGNOSTIC_ASSERT( + !mUncommittedJsepSession, + "AddIceCandidate is chained, which means it should never " + "run while an sRD/sLD is in progress"); + JsepSession::Result result = mJsepSession->AddRemoteIceCandidate( + aCandidate, aMid, level, aUfrag, &transportId); + + if (!result.mError.isSome()) { + // We do not bother the MediaTransportHandler about this before + // offer/answer concludes. Once offer/answer concludes, we will extract + // these candidates from the remote SDP. + if (mSignalingState == RTCSignalingState::Stable && !transportId.empty()) { + AddIceCandidate(aCandidate, transportId, aUfrag); + mRawTrickledCandidates.push_back(aCandidate); + } + // Spec says we queue a task for these updates + GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + __func__, [this, self = RefPtr(this)] { + if (IsClosed()) { + return; + } + mPendingRemoteDescription = + mJsepSession->GetRemoteDescription(kJsepDescriptionPending); + mCurrentRemoteDescription = + mJsepSession->GetRemoteDescription(kJsepDescriptionCurrent); + JSErrorResult rv; + mPCObserver->OnAddIceCandidateSuccess(rv); + })); + } else { + std::string errorString = mJsepSession->GetLastError(); + + CSFLogError(LOGTAG, + "Failed to incorporate remote candidate into SDP:" + " res = %u, candidate = %s, level = %i, error = %s", + static_cast(*result.mError), aCandidate, + level.valueOr(-1), errorString.c_str()); + + GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + __func__, + [this, self = RefPtr(this), errorString, result] { + if (IsClosed()) { + return; + } + JSErrorResult rv; + mPCObserver->OnAddIceCandidateError( + *buildJSErrorData(result, errorString), rv); + })); + } + + return NS_OK; +} + +NS_IMETHODIMP +PeerConnectionImpl::CloseStreams() { + PC_AUTO_ENTER_API_CALL(false); + + return NS_OK; +} + +NS_IMETHODIMP +PeerConnectionImpl::SetPeerIdentity(const nsAString& aPeerIdentity) { + PC_AUTO_ENTER_API_CALL(true); + MOZ_ASSERT(!aPeerIdentity.IsEmpty()); + + // once set, this can't be changed + if (mPeerIdentity) { + if (!mPeerIdentity->Equals(aPeerIdentity)) { + return NS_ERROR_FAILURE; + } + } else { + mPeerIdentity = new PeerIdentity(aPeerIdentity); + Document* doc = mWindow->GetExtantDoc(); + if (!doc) { + CSFLogInfo(LOGTAG, "Can't update principal on streams; document gone"); + return NS_ERROR_FAILURE; + } + for (const auto& transceiver : mTransceivers) { + transceiver->Sender()->GetPipeline()->UpdateSinkIdentity( + doc->NodePrincipal(), mPeerIdentity); + } + } + return NS_OK; +} + +nsresult PeerConnectionImpl::OnAlpnNegotiated(bool aPrivacyRequested) { + PC_AUTO_ENTER_API_CALL(false); + MOZ_DIAGNOSTIC_ASSERT(!mRequestedPrivacy || + (*mRequestedPrivacy == PrincipalPrivacy::Private) == + aPrivacyRequested); + + mRequestedPrivacy = Some(aPrivacyRequested ? PrincipalPrivacy::Private + : PrincipalPrivacy::NonPrivate); + // This updates the MediaPipelines with a private PrincipalHandle. Note that + // MediaPipelineReceive has its own AlpnNegotiated handler so it can get + // signaled off-main to drop data until it receives the new PrincipalHandle + // from us. + UpdateMediaPipelines(); + return NS_OK; +} + +void PeerConnectionImpl::OnDtlsStateChange(const std::string& aTransportId, + TransportLayer::State aState) { + auto it = mTransportIdToRTCDtlsTransport.find(aTransportId); + if (it != mTransportIdToRTCDtlsTransport.end()) { + it->second->UpdateState(aState); + } + UpdateConnectionState(); +} + +RTCPeerConnectionState PeerConnectionImpl::GetNewConnectionState() const { + // closed The RTCPeerConnection object's [[IsClosed]] slot is true. + if (IsClosed()) { + return RTCPeerConnectionState::Closed; + } + + // Would use a bitset, but that requires lots of static_cast + // Oh well. + std::set statesFound; + for (const auto& [id, dtlsTransport] : mTransportIdToRTCDtlsTransport) { + Unused << id; + statesFound.insert(dtlsTransport->State()); + } + + // failed The previous state doesn't apply and any RTCIceTransports are + // in the "failed" state or any RTCDtlsTransports are in the "failed" state. + // NOTE: "any RTCIceTransports are in the failed state" is equivalent to + // mIceConnectionState == Failed + if (mIceConnectionState == RTCIceConnectionState::Failed || + statesFound.count(RTCDtlsTransportState::Failed)) { + return RTCPeerConnectionState::Failed; + } + + // disconnected None of the previous states apply and any + // RTCIceTransports are in the "disconnected" state. + // NOTE: "any RTCIceTransports are in the disconnected state" is equivalent to + // mIceConnectionState == Disconnected. + if (mIceConnectionState == RTCIceConnectionState::Disconnected) { + return RTCPeerConnectionState::Disconnected; + } + + // new None of the previous states apply and all RTCIceTransports are + // in the "new" or "closed" state, and all RTCDtlsTransports are in the "new" + // or "closed" state, or there are no transports. + // NOTE: "all RTCIceTransports are in the new or closed state" is equivalent + // to mIceConnectionState == New. + if (mIceConnectionState == RTCIceConnectionState::New && + !statesFound.count(RTCDtlsTransportState::Connecting) && + !statesFound.count(RTCDtlsTransportState::Connected) && + !statesFound.count(RTCDtlsTransportState::Failed)) { + return RTCPeerConnectionState::New; + } + + // No transports + if (statesFound.empty()) { + return RTCPeerConnectionState::New; + } + + // connecting None of the previous states apply and any + // RTCIceTransport is in the "new" or "checking" state or any + // RTCDtlsTransport is in the "new" or "connecting" state. + // NOTE: "None of the previous states apply and any RTCIceTransport is in the + // "new" or "checking" state" is equivalent to mIceConnectionState == + // Checking. + if (mIceConnectionState == RTCIceConnectionState::Checking || + statesFound.count(RTCDtlsTransportState::New) || + statesFound.count(RTCDtlsTransportState::Connecting)) { + return RTCPeerConnectionState::Connecting; + } + + // connected None of the previous states apply and all RTCIceTransports are + // in the "connected", "completed" or "closed" state, and all + // RTCDtlsTransports are in the "connected" or "closed" state. + // NOTE: "None of the previous states apply and all RTCIceTransports are in + // the "connected", "completed" or "closed" state" is equivalent to + // mIceConnectionState == Connected. + if (mIceConnectionState == RTCIceConnectionState::Connected && + !statesFound.count(RTCDtlsTransportState::New) && + !statesFound.count(RTCDtlsTransportState::Failed) && + !statesFound.count(RTCDtlsTransportState::Connecting)) { + return RTCPeerConnectionState::Connected; + } + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // THERE IS NO CATCH-ALL NONE-OF-THE-ABOVE IN THE SPEC! THIS IS REALLY BAD! !! + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + // Let's try to figure out how bad, precisely. + // Any one of these will cause us to bail above. + MOZ_ASSERT(mIceConnectionState != RTCIceConnectionState::Failed && + mIceConnectionState != RTCIceConnectionState::Disconnected && + mIceConnectionState != RTCIceConnectionState::Checking); + MOZ_ASSERT(!statesFound.count(RTCDtlsTransportState::New) && + !statesFound.count(RTCDtlsTransportState::Connecting) && + !statesFound.count(RTCDtlsTransportState::Failed)); + + // One of these must be set, or the empty() check would have failed above. + MOZ_ASSERT(statesFound.count(RTCDtlsTransportState::Connected) || + statesFound.count(RTCDtlsTransportState::Closed)); + + // Here are our remaining possibilities: + // ICE connected, !statesFound.count(Connected), statesFound.count(Closed) + // ICE connected, statesFound.count(Connected), !statesFound.count(Closed) + // ICE connected, statesFound.count(Connected), statesFound.count(Closed) + // All three of these would result in returning Connected above. + + // ICE new, !statesFound.count(Connected), statesFound.count(Closed) + // This results in returning New above. Whew. + + // ICE new, statesFound.count(Connected), !statesFound.count(Closed) + // ICE new, statesFound.count(Connected), statesFound.count(Closed) + // These would make it all the way here! Very weird state though, for all + // ICE transports to be new/closed, but having a connected DTLS transport. + // Handle this as a non-transition, just in case. + return mConnectionState; +} + +void PeerConnectionImpl::UpdateConnectionState() { + auto newState = GetNewConnectionState(); + if (newState != mConnectionState) { + CSFLogDebug(LOGTAG, "%s: %d -> %d (%p)", __FUNCTION__, + static_cast(mConnectionState), static_cast(newState), + this); + mConnectionState = newState; + if (mConnectionState != RTCPeerConnectionState::Closed) { + JSErrorResult jrv; + mPCObserver->OnStateChange(PCObserverStateType::ConnectionState, jrv); + } + } +} + +void PeerConnectionImpl::OnMediaError(const std::string& aError) { + CSFLogError(LOGTAG, "Encountered media error! %s", aError.c_str()); + // TODO: Let content know about this somehow. +} + +void PeerConnectionImpl::DumpPacket_m(size_t level, dom::mozPacketDumpType type, + bool sending, + UniquePtr& packet, + size_t size) { + if (IsClosed()) { + return; + } + + // TODO: Is this efficient? Should we try grabbing our JS ctx from somewhere + // else? + AutoJSAPI jsapi; + if (!jsapi.Init(mWindow)) { + return; + } + + JS::Rooted jsobj( + jsapi.cx(), + JS::NewArrayBufferWithContents(jsapi.cx(), size, packet.release())); + + RootedSpiderMonkeyInterface arrayBuffer(jsapi.cx()); + if (!arrayBuffer.Init(jsobj)) { + return; + } + + JSErrorResult jrv; + mPCObserver->OnPacket(level, type, sending, arrayBuffer, jrv); +} + +bool PeerConnectionImpl::HostnameInPref(const char* aPref, + const nsCString& aHostName) { + auto HostInDomain = [](const nsCString& aHost, const nsCString& aPattern) { + int32_t patternOffset = 0; + int32_t hostOffset = 0; + + // Act on '*.' wildcard in the left-most position in a domain pattern. + if (StringBeginsWith(aPattern, nsCString("*."))) { + patternOffset = 2; + + // Ignore the lowest level sub-domain for the hostname. + hostOffset = aHost.FindChar('.') + 1; + + if (hostOffset <= 1) { + // Reject a match between a wildcard and a TLD or '.foo' form. + return false; + } + } + + nsDependentCString hostRoot(aHost, hostOffset); + return hostRoot.EqualsIgnoreCase(aPattern.BeginReading() + patternOffset); + }; + + nsCString domainList; + nsresult nr = Preferences::GetCString(aPref, domainList); + + if (NS_FAILED(nr)) { + return false; + } + + domainList.StripWhitespace(); + + if (domainList.IsEmpty() || aHostName.IsEmpty()) { + return false; + } + + // Get UTF8 to ASCII domain name normalization service + nsresult rv; + nsCOMPtr idnService = + do_GetService("@mozilla.org/network/idn-service;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + // Test each domain name in the comma separated list + // after converting from UTF8 to ASCII. Each domain + // must match exactly or have a single leading '*.' wildcard. + for (const nsACString& each : domainList.Split(',')) { + nsCString domainPattern; + rv = idnService->ConvertUTF8toACE(each, domainPattern); + if (NS_SUCCEEDED(rv)) { + if (HostInDomain(aHostName, domainPattern)) { + return true; + } + } else { + NS_WARNING("Failed to convert UTF-8 host to ASCII"); + } + } + + return false; +} + +nsresult PeerConnectionImpl::EnablePacketDump(unsigned long level, + dom::mozPacketDumpType type, + bool sending) { + return GetPacketDumper()->EnablePacketDump(level, type, sending); +} + +nsresult PeerConnectionImpl::DisablePacketDump(unsigned long level, + dom::mozPacketDumpType type, + bool sending) { + return GetPacketDumper()->DisablePacketDump(level, type, sending); +} + +void PeerConnectionImpl::StampTimecard(const char* aEvent) { + MOZ_ASSERT(NS_IsMainThread()); + STAMP_TIMECARD(mTimeCard, aEvent); +} + +void PeerConnectionImpl::SendWarningToConsole(const nsCString& aWarning) { + nsAutoString msg = NS_ConvertASCIItoUTF16(aWarning); + nsContentUtils::ReportToConsoleByWindowID(msg, nsIScriptError::warningFlag, + "WebRTC"_ns, mWindow->WindowID()); +} + +void PeerConnectionImpl::GetDefaultVideoCodecs( + std::vector>& aSupportedCodecs, + bool aUseRtx) { + // Supported video codecs. + // Note: order here implies priority for building offers! + aSupportedCodecs.emplace_back( + JsepVideoCodecDescription::CreateDefaultVP8(aUseRtx)); + aSupportedCodecs.emplace_back( + JsepVideoCodecDescription::CreateDefaultVP9(aUseRtx)); + aSupportedCodecs.emplace_back( + JsepVideoCodecDescription::CreateDefaultH264_1(aUseRtx)); + aSupportedCodecs.emplace_back( + JsepVideoCodecDescription::CreateDefaultH264_0(aUseRtx)); + aSupportedCodecs.emplace_back( + JsepVideoCodecDescription::CreateDefaultUlpFec()); + aSupportedCodecs.emplace_back( + JsepApplicationCodecDescription::CreateDefault()); + aSupportedCodecs.emplace_back(JsepVideoCodecDescription::CreateDefaultRed()); +} + +void PeerConnectionImpl::GetDefaultAudioCodecs( + std::vector>& aSupportedCodecs) { + aSupportedCodecs.emplace_back(JsepAudioCodecDescription::CreateDefaultOpus()); + aSupportedCodecs.emplace_back(JsepAudioCodecDescription::CreateDefaultG722()); + aSupportedCodecs.emplace_back(JsepAudioCodecDescription::CreateDefaultPCMU()); + aSupportedCodecs.emplace_back(JsepAudioCodecDescription::CreateDefaultPCMA()); + aSupportedCodecs.emplace_back( + JsepAudioCodecDescription::CreateDefaultTelephoneEvent()); +} + +void PeerConnectionImpl::GetDefaultRtpExtensions( + std::vector& aRtpExtensions) { + RtpExtensionHeader audioLevel = {JsepMediaType::kAudio, + SdpDirectionAttribute::Direction::kSendrecv, + webrtc::RtpExtension::kAudioLevelUri}; + aRtpExtensions.push_back(audioLevel); + + RtpExtensionHeader csrcAudioLevels = { + JsepMediaType::kAudio, SdpDirectionAttribute::Direction::kRecvonly, + webrtc::RtpExtension::kCsrcAudioLevelsUri}; + aRtpExtensions.push_back(csrcAudioLevels); + + RtpExtensionHeader mid = {JsepMediaType::kAudioVideo, + SdpDirectionAttribute::Direction::kSendrecv, + webrtc::RtpExtension::kMidUri}; + aRtpExtensions.push_back(mid); + + RtpExtensionHeader absSendTime = {JsepMediaType::kVideo, + SdpDirectionAttribute::Direction::kSendrecv, + webrtc::RtpExtension::kAbsSendTimeUri}; + aRtpExtensions.push_back(absSendTime); + + RtpExtensionHeader timestampOffset = { + JsepMediaType::kVideo, SdpDirectionAttribute::Direction::kSendrecv, + webrtc::RtpExtension::kTimestampOffsetUri}; + aRtpExtensions.push_back(timestampOffset); + + RtpExtensionHeader playoutDelay = { + JsepMediaType::kVideo, SdpDirectionAttribute::Direction::kRecvonly, + webrtc::RtpExtension::kPlayoutDelayUri}; + aRtpExtensions.push_back(playoutDelay); + + RtpExtensionHeader transportSequenceNumber = { + JsepMediaType::kVideo, SdpDirectionAttribute::Direction::kSendrecv, + webrtc::RtpExtension::kTransportSequenceNumberUri}; + aRtpExtensions.push_back(transportSequenceNumber); +} + +void PeerConnectionImpl::GetCapabilities( + const nsAString& aKind, dom::Nullable& aResult, + sdp::Direction aDirection) { + std::vector> codecs; + std::vector headers; + auto mediaType = JsepMediaType::kNone; + + if (aKind.EqualsASCII("video")) { + GetDefaultVideoCodecs(codecs, true); + mediaType = JsepMediaType::kVideo; + } else if (aKind.EqualsASCII("audio")) { + GetDefaultAudioCodecs(codecs); + mediaType = JsepMediaType::kAudio; + } else { + return; + } + + GetDefaultRtpExtensions(headers); + + // Use the codecs for kind to fill out the RTCRtpCodecCapability + for (const auto& codec : codecs) { + // To avoid misleading information on codec capabilities skip those + // not signaled for audio/video (webrtc-datachannel) + // and any disabled by default (ulpfec and red). + if (codec->mName == "webrtc-datachannel" || codec->mName == "ulpfec" || + codec->mName == "red") { + continue; + } + + dom::RTCRtpCodecCapability capability; + capability.mMimeType = aKind + NS_ConvertASCIItoUTF16("/" + codec->mName); + capability.mClockRate = codec->mClock; + + if (codec->mChannels) { + capability.mChannels.Construct(codec->mChannels); + } + + UniquePtr params; + codec->ApplyConfigToFmtp(params); + + if (params != nullptr) { + std::ostringstream paramsString; + params->Serialize(paramsString); + nsTString fmtp; + fmtp.AssignASCII(paramsString.str()); + capability.mSdpFmtpLine.Construct(fmtp); + } + + if (!aResult.SetValue().mCodecs.AppendElement(capability, fallible)) { + mozalloc_handle_oom(0); + } + } + + // We need to manually add rtx for video. + if (mediaType == JsepMediaType::kVideo) { + dom::RTCRtpCodecCapability capability; + capability.mMimeType = aKind + NS_ConvertASCIItoUTF16("/rtx"); + capability.mClockRate = 90000; + if (!aResult.SetValue().mCodecs.AppendElement(capability, fallible)) { + mozalloc_handle_oom(0); + } + } + + // Add headers that match the direction and media type requested. + for (const auto& header : headers) { + if ((header.direction & aDirection) && (header.mMediaType & mediaType)) { + dom::RTCRtpHeaderExtensionCapability rtpHeader; + rtpHeader.mUri.AssignASCII(header.extensionname); + if (!aResult.SetValue().mHeaderExtensions.AppendElement(rtpHeader, + fallible)) { + mozalloc_handle_oom(0); + } + } + } +} + +void PeerConnectionImpl::SetupPreferredCodecs( + std::vector>& aPreferredCodecs) { + bool useRtx = + Preferences::GetBool("media.peerconnection.video.use_rtx", false); + + GetDefaultVideoCodecs(aPreferredCodecs, useRtx); + GetDefaultAudioCodecs(aPreferredCodecs); + + // With red update the redundant encodings list + for (auto& videoCodec : aPreferredCodecs) { + if (videoCodec->mName == "red") { + JsepVideoCodecDescription& red = + static_cast(*videoCodec); + red.UpdateRedundantEncodings(aPreferredCodecs); + } + } +} + +void PeerConnectionImpl::SetupPreferredRtpExtensions( + std::vector& aPreferredheaders) { + GetDefaultRtpExtensions(aPreferredheaders); + + if (!Preferences::GetBool("media.navigator.video.use_transport_cc", false)) { + aPreferredheaders.erase( + std::remove_if( + aPreferredheaders.begin(), aPreferredheaders.end(), + [&](const RtpExtensionHeader& header) { + return header.extensionname == + webrtc::RtpExtension::kTransportSequenceNumberUri; + }), + aPreferredheaders.end()); + } +} + +nsresult PeerConnectionImpl::CalculateFingerprint( + const std::string& algorithm, std::vector* fingerprint) const { + DtlsDigest digest(algorithm); + + MOZ_ASSERT(fingerprint); + const UniqueCERTCertificate& cert = mCertificate->Certificate(); + nsresult rv = DtlsIdentity::ComputeFingerprint(cert, &digest); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "Unable to calculate certificate fingerprint, rv=%u", + static_cast(rv)); + return rv; + } + *fingerprint = digest.value_; + return NS_OK; +} + +NS_IMETHODIMP +PeerConnectionImpl::GetFingerprint(char** fingerprint) { + MOZ_ASSERT(fingerprint); + MOZ_ASSERT(mCertificate); + std::vector fp; + nsresult rv = CalculateFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM, &fp); + NS_ENSURE_SUCCESS(rv, rv); + std::ostringstream os; + os << DtlsIdentity::DEFAULT_HASH_ALGORITHM << ' ' + << SdpFingerprintAttributeList::FormatFingerprint(fp); + std::string fpStr = os.str(); + + char* tmp = new char[fpStr.size() + 1]; + std::copy(fpStr.begin(), fpStr.end(), tmp); + tmp[fpStr.size()] = '\0'; + + *fingerprint = tmp; + return NS_OK; +} + +void PeerConnectionImpl::GetCurrentLocalDescription(nsAString& aSDP) const { + aSDP = NS_ConvertASCIItoUTF16(mCurrentLocalDescription.c_str()); +} + +void PeerConnectionImpl::GetPendingLocalDescription(nsAString& aSDP) const { + aSDP = NS_ConvertASCIItoUTF16(mPendingLocalDescription.c_str()); +} + +void PeerConnectionImpl::GetCurrentRemoteDescription(nsAString& aSDP) const { + aSDP = NS_ConvertASCIItoUTF16(mCurrentRemoteDescription.c_str()); +} + +void PeerConnectionImpl::GetPendingRemoteDescription(nsAString& aSDP) const { + aSDP = NS_ConvertASCIItoUTF16(mPendingRemoteDescription.c_str()); +} + +dom::Nullable PeerConnectionImpl::GetCurrentOfferer() const { + dom::Nullable result; + if (mCurrentOfferer.isSome()) { + result.SetValue(*mCurrentOfferer); + } + return result; +} + +dom::Nullable PeerConnectionImpl::GetPendingOfferer() const { + dom::Nullable result; + if (mPendingOfferer.isSome()) { + result.SetValue(*mPendingOfferer); + } + return result; +} + +NS_IMETHODIMP +PeerConnectionImpl::SignalingState(RTCSignalingState* aState) { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + MOZ_ASSERT(aState); + + *aState = mSignalingState; + return NS_OK; +} + +NS_IMETHODIMP +PeerConnectionImpl::IceConnectionState(RTCIceConnectionState* aState) { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + MOZ_ASSERT(aState); + + *aState = mIceConnectionState; + return NS_OK; +} + +NS_IMETHODIMP +PeerConnectionImpl::IceGatheringState(RTCIceGatheringState* aState) { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + MOZ_ASSERT(aState); + + *aState = mIceGatheringState; + return NS_OK; +} + +NS_IMETHODIMP +PeerConnectionImpl::ConnectionState(RTCPeerConnectionState* aState) { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + MOZ_ASSERT(aState); + + *aState = mConnectionState; + return NS_OK; +} + +nsresult PeerConnectionImpl::CheckApiState(bool assert_ice_ready) const { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + MOZ_ASSERT(mTrickle || !assert_ice_ready || + (mIceGatheringState == RTCIceGatheringState::Complete)); + + if (IsClosed()) { + CSFLogError(LOGTAG, "%s: called API while closed", __FUNCTION__); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +void PeerConnectionImpl::StoreFinalStats( + UniquePtr&& report) { + using namespace Telemetry; + + report->mClosed = true; + + for (const auto& inboundRtpStats : report->mInboundRtpStreamStats) { + bool isVideo = (inboundRtpStats.mId.Value().Find(u"video") != -1); + if (!isVideo) { + continue; + } + if (inboundRtpStats.mDiscardedPackets.WasPassed() && + report->mCallDurationMs.WasPassed()) { + double mins = report->mCallDurationMs.Value() / (1000 * 60); + if (mins > 0) { + Accumulate( + WEBRTC_VIDEO_DECODER_DISCARDED_PACKETS_PER_CALL_PPM, + uint32_t(double(inboundRtpStats.mDiscardedPackets.Value()) / mins)); + } + } + } + + // Finally, store the stats + mFinalStats = std::move(report); +} + +NS_IMETHODIMP +PeerConnectionImpl::Close() { + CSFLogDebug(LOGTAG, "%s: for %s", __FUNCTION__, mHandle.c_str()); + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + + if (IsClosed()) { + return NS_OK; + } + + STAMP_TIMECARD(mTimeCard, "Close"); + + // When ICE completes, we record some telemetry. We do this at the end of the + // call because we want to make sure we've waited for all trickle ICE + // candidates to come in; this can happen well after we've transitioned to + // connected. As a bonus, this allows us to detect race conditions where a + // stats dispatch happens right as the PC closes. + RecordEndOfCallTelemetry(); + + CSFLogInfo(LOGTAG, + "%s: Closing PeerConnectionImpl %s; " + "ending call", + __FUNCTION__, mHandle.c_str()); + if (mJsepSession) { + mJsepSession->Close(); + } + if (mDataConnection) { + CSFLogInfo(LOGTAG, "%s: Destroying DataChannelConnection %p for %s", + __FUNCTION__, (void*)mDataConnection.get(), mHandle.c_str()); + mDataConnection->Destroy(); + mDataConnection = + nullptr; // it may not go away until the runnables are dead + } + + if (mStunAddrsRequest) { + for (const auto& hostname : mRegisteredMDNSHostnames) { + mStunAddrsRequest->SendUnregisterMDNSHostname( + nsCString(hostname.c_str())); + } + mRegisteredMDNSHostnames.clear(); + mStunAddrsRequest->Cancel(); + mStunAddrsRequest = nullptr; + } + + for (auto& transceiver : mTransceivers) { + transceiver->Close(); + } + + mTransportIdToRTCDtlsTransport.clear(); + + mQueuedIceCtxOperations.clear(); + + mOperations.Clear(); + + // Uncount this connection as active on the inner window upon close. + if (mWindow && mActiveOnWindow) { + mWindow->RemovePeerConnection(); + mActiveOnWindow = false; + } + + mSignalingState = RTCSignalingState::Closed; + mConnectionState = RTCPeerConnectionState::Closed; + + if (!mTransportHandler) { + // We were never initialized, apparently. + return NS_OK; + } + + // Clear any resources held by libwebrtc through our Call instance. + RefPtr callDestroyPromise; + if (mCall) { + // Make sure the compiler does not get confused and try to acquire a + // reference to this thread _after_ we null out mCall. + auto callThread = mCall->mCallThread; + callDestroyPromise = + InvokeAsync(callThread, __func__, [call = std::move(mCall)]() { + call->Destroy(); + return GenericPromise::CreateAndResolve( + true, "PCImpl->WebRtcCallWrapper::Destroy"); + }); + } else { + callDestroyPromise = GenericPromise::CreateAndResolve(true, __func__); + } + + mFinalStatsQuery = + GetStats(nullptr, true) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [this, self = RefPtr(this)]( + UniquePtr&& aReport) mutable { + StoreFinalStats(std::move(aReport)); + return GenericNonExclusivePromise::CreateAndResolve(true, + __func__); + }, + [](nsresult aError) { + return GenericNonExclusivePromise::CreateAndResolve(true, + __func__); + }); + + // 1. Allow final stats query to complete. + // 2. Tear down call, if necessary. We do this before we shut down the + // transport handler, so RTCP BYE can be sent. + // 3. Unhook from the signal handler (sigslot) for transport stuff. This must + // be done before we tear down the transport handler. + // 4. Tear down the transport handler, and deregister from PeerConnectionCtx. + // When we deregister from PeerConnectionCtx, our final stats (if any) + // will be stored. + MOZ_RELEASE_ASSERT(mSTSThread); + mFinalStatsQuery + ->Then(GetMainThreadSerialEventTarget(), __func__, + [callDestroyPromise]() mutable { return callDestroyPromise; }) + ->Then( + mSTSThread, __func__, + [signalHandler = std::move(mSignalHandler)]() mutable { + CSFLogDebug( + LOGTAG, + "Destroying PeerConnectionImpl::SignalHandler on STS thread"); + return GenericPromise::CreateAndResolve( + true, "PeerConnectionImpl::~SignalHandler"); + }) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [this, self = RefPtr(this)]() mutable { + CSFLogDebug(LOGTAG, "PCImpl->mTransportHandler::RemoveTransports"); + mTransportHandler->RemoveTransportsExcept(std::set()); + if (mPrivateWindow) { + mTransportHandler->ExitPrivateMode(); + } + mTransportHandler = nullptr; + if (PeerConnectionCtx::isActive()) { + // If we're shutting down xpcom, this Instance will be unset + // before calling Close() on all remaining PCs, to avoid + // reentrancy. + PeerConnectionCtx::GetInstance()->RemovePeerConnection(mHandle); + } + }); + + return NS_OK; +} + +void PeerConnectionImpl::BreakCycles() { + for (auto& transceiver : mTransceivers) { + transceiver->BreakCycles(); + } + mTransceivers.Clear(); +} + +bool PeerConnectionImpl::HasPendingSetParameters() const { + for (const auto& transceiver : mTransceivers) { + if (transceiver->Sender()->HasPendingSetParameters()) { + return true; + } + } + return false; +} + +void PeerConnectionImpl::InvalidateLastReturnedParameters() { + for (const auto& transceiver : mTransceivers) { + transceiver->Sender()->InvalidateLastReturnedParameters(); + } +} + +nsresult PeerConnectionImpl::SetConfiguration( + const RTCConfiguration& aConfiguration) { + nsresult rv = mTransportHandler->SetIceConfig( + aConfiguration.mIceServers, aConfiguration.mIceTransportPolicy); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + JsepBundlePolicy bundlePolicy; + switch (aConfiguration.mBundlePolicy) { + case dom::RTCBundlePolicy::Balanced: + bundlePolicy = kBundleBalanced; + break; + case dom::RTCBundlePolicy::Max_compat: + bundlePolicy = kBundleMaxCompat; + break; + case dom::RTCBundlePolicy::Max_bundle: + bundlePolicy = kBundleMaxBundle; + break; + default: + MOZ_CRASH(); + } + + // Ignore errors, since those ought to be handled earlier. + Unused << mJsepSession->SetBundlePolicy(bundlePolicy); + + if (!aConfiguration.mPeerIdentity.IsEmpty()) { + mPeerIdentity = new PeerIdentity(aConfiguration.mPeerIdentity); + mRequestedPrivacy = Some(PrincipalPrivacy::Private); + } + + auto proxyConfig = GetProxyConfig(); + if (proxyConfig) { + // Note that this could check if PrivacyRequested() is set on the PC and + // remove "webrtc" from the ALPN list. But that would only work if the PC + // was constructed with a peerIdentity constraint, not when isolated + // streams are added. If we ever need to signal to the proxy that the + // media is isolated, then we would need to restructure this code. + mTransportHandler->SetProxyConfig(std::move(*proxyConfig)); + } + + // Store the configuration for about:webrtc + StoreConfigurationForAboutWebrtc(aConfiguration); + + return NS_OK; +} + +RTCSctpTransport* PeerConnectionImpl::GetSctp() const { + return mSctpTransport.get(); +} + +void PeerConnectionImpl::RestartIce() { + RestartIceNoRenegotiationNeeded(); + // Update the negotiation-needed flag for connection. + UpdateNegotiationNeeded(); +} + +// webrtc-pc does not specify any situations where this is done, but the JSEP +// spec does, in some situations due to setConfiguration. +void PeerConnectionImpl::RestartIceNoRenegotiationNeeded() { + // Empty connection.[[LocalIceCredentialsToReplace]], and populate it with + // all ICE credentials (ice-ufrag and ice-pwd as defined in section 15.4 of + // [RFC5245]) found in connection.[[CurrentLocalDescription]], as well as all + // ICE credentials found in connection.[[PendingLocalDescription]]. + mLocalIceCredentialsToReplace = mJsepSession->GetLocalIceCredentials(); +} + +bool PeerConnectionImpl::PluginCrash(uint32_t aPluginID, + const nsAString& aPluginName) { + // fire an event to the DOM window if this is "ours" + if (!AnyCodecHasPluginID(aPluginID)) { + return false; + } + + CSFLogError(LOGTAG, "%s: Our plugin %llu crashed", __FUNCTION__, + static_cast(aPluginID)); + + RefPtr doc = mWindow->GetExtantDoc(); + if (!doc) { + NS_WARNING("Couldn't get document for PluginCrashed event!"); + return true; + } + + PluginCrashedEventInit init; + init.mPluginID = aPluginID; + init.mPluginName = aPluginName; + init.mSubmittedCrashReport = false; + init.mGmpPlugin = true; + init.mBubbles = true; + init.mCancelable = true; + + RefPtr event = + PluginCrashedEvent::Constructor(doc, u"PluginCrashed"_ns, init); + + event->SetTrusted(true); + event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true; + + nsCOMPtr window = mWindow; + EventDispatcher::DispatchDOMEvent(window, nullptr, event, nullptr, nullptr); + + return true; +} + +void PeerConnectionImpl::RecordEndOfCallTelemetry() { + if (!mCallTelemStarted) { + return; + } + MOZ_RELEASE_ASSERT(!mCallTelemEnded, "Don't end telemetry twice"); + MOZ_RELEASE_ASSERT(mJsepSession, + "Call telemetry only starts after jsep session start"); + MOZ_RELEASE_ASSERT(mJsepSession->GetNegotiations() > 0, + "Call telemetry only starts after first connection"); + + // Bitmask used for WEBRTC/LOOP_CALL_TYPE telemetry reporting + static const uint32_t kAudioTypeMask = 1; + static const uint32_t kVideoTypeMask = 2; + static const uint32_t kDataChannelTypeMask = 4; + + // Report end-of-call Telemetry + Telemetry::Accumulate(Telemetry::WEBRTC_RENEGOTIATIONS, + mJsepSession->GetNegotiations() - 1); + Telemetry::Accumulate(Telemetry::WEBRTC_MAX_VIDEO_SEND_TRACK, + mMaxSending[SdpMediaSection::MediaType::kVideo]); + Telemetry::Accumulate(Telemetry::WEBRTC_MAX_VIDEO_RECEIVE_TRACK, + mMaxReceiving[SdpMediaSection::MediaType::kVideo]); + Telemetry::Accumulate(Telemetry::WEBRTC_MAX_AUDIO_SEND_TRACK, + mMaxSending[SdpMediaSection::MediaType::kAudio]); + Telemetry::Accumulate(Telemetry::WEBRTC_MAX_AUDIO_RECEIVE_TRACK, + mMaxReceiving[SdpMediaSection::MediaType::kAudio]); + // DataChannels appear in both Sending and Receiving + Telemetry::Accumulate(Telemetry::WEBRTC_DATACHANNEL_NEGOTIATED, + mMaxSending[SdpMediaSection::MediaType::kApplication]); + // Enumerated/bitmask: 1 = Audio, 2 = Video, 4 = DataChannel + // A/V = 3, A/V/D = 7, etc + uint32_t type = 0; + if (mMaxSending[SdpMediaSection::MediaType::kAudio] || + mMaxReceiving[SdpMediaSection::MediaType::kAudio]) { + type = kAudioTypeMask; + } + if (mMaxSending[SdpMediaSection::MediaType::kVideo] || + mMaxReceiving[SdpMediaSection::MediaType::kVideo]) { + type |= kVideoTypeMask; + } + if (mMaxSending[SdpMediaSection::MediaType::kApplication]) { + type |= kDataChannelTypeMask; + } + Telemetry::Accumulate(Telemetry::WEBRTC_CALL_TYPE, type); + + MOZ_RELEASE_ASSERT(mWindow); + auto found = sCallDurationTimers.find(mWindow->WindowID()); + if (found != sCallDurationTimers.end()) { + found->second.UnregisterConnection((type & kAudioTypeMask) || + (type & kVideoTypeMask)); + if (found->second.IsStopped()) { + sCallDurationTimers.erase(found); + } + } + mCallTelemEnded = true; +} + +DOMMediaStream* PeerConnectionImpl::GetReceiveStream( + const std::string& aId) const { + nsString wanted = NS_ConvertASCIItoUTF16(aId.c_str()); + for (auto& stream : mReceiveStreams) { + nsString id; + stream->GetId(id); + if (id == wanted) { + return stream; + } + } + return nullptr; +} + +DOMMediaStream* PeerConnectionImpl::CreateReceiveStream( + const std::string& aId) { + mReceiveStreams.AppendElement(new DOMMediaStream(mWindow)); + mReceiveStreams.LastElement()->AssignId(NS_ConvertASCIItoUTF16(aId.c_str())); + return mReceiveStreams.LastElement(); +} + +already_AddRefed PeerConnectionImpl::OnSetDescriptionSuccess( + dom::RTCSdpType aSdpType, bool aRemote, ErrorResult& aError) { + CSFLogDebug(LOGTAG, __FUNCTION__); + + RefPtr p = MakePromise(aError); + if (aError.Failed()) { + return nullptr; + } + + DoSetDescriptionSuccessPostProcessing(aSdpType, aRemote, p); + + return p.forget(); +} + +void PeerConnectionImpl::DoSetDescriptionSuccessPostProcessing( + dom::RTCSdpType aSdpType, bool aRemote, const RefPtr& aP) { + // Spec says we queue a task for all the stuff that ends up back in JS + GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + __func__, + [this, self = RefPtr(this), aSdpType, aRemote, aP] { + if (IsClosed()) { + // Yes, we do not settle the promise here. Yes, this is what the spec + // wants. + return; + } + + MOZ_ASSERT(mUncommittedJsepSession); + + // sRD/sLD needs to be redone in certain circumstances + bool needsRedo = HasPendingSetParameters(); + if (!needsRedo && aRemote && (aSdpType == dom::RTCSdpType::Offer)) { + for (auto& transceiver : mTransceivers) { + if (!mUncommittedJsepSession->GetTransceiver( + transceiver->GetJsepTransceiverId())) { + needsRedo = true; + break; + } + } + } + + if (needsRedo) { + // Spec says to abort, and re-do the sRD! + // This happens either when there is a SetParameters call in + // flight (that will race against the [[SendEncodings]] + // modification caused by sRD(offer)), or when addTrack has been + // called while sRD(offer) was in progress. + mUncommittedJsepSession.reset(mJsepSession->Clone()); + JsepSession::Result result; + if (aRemote) { + mUncommittedJsepSession->SetRemoteDescription( + ToJsepSdpType(aSdpType), mRemoteRequestedSDP); + } else { + mUncommittedJsepSession->SetLocalDescription( + ToJsepSdpType(aSdpType), mLocalRequestedSDP); + } + if (result.mError.isSome()) { + // wat + nsCString error( + "When redoing sRD/sLD because it raced against " + "addTrack or setParameters, we encountered a failure that " + "did not happen " + "the first time. This should never happen. The error was: "); + error += mUncommittedJsepSession->GetLastError().c_str(); + aP->MaybeRejectWithOperationError(error); + MOZ_ASSERT(false); + } else { + DoSetDescriptionSuccessPostProcessing(aSdpType, aRemote, aP); + } + return; + } + + for (auto& transceiver : mTransceivers) { + if (!mUncommittedJsepSession->GetTransceiver( + transceiver->GetJsepTransceiverId())) { + // sLD, or sRD(answer), just make sure the new transceiver is + // added, no need to re-do anything. + mUncommittedJsepSession->AddTransceiver( + transceiver->GetJsepTransceiver()); + } + } + + mJsepSession = std::move(mUncommittedJsepSession); + + auto newSignalingState = GetSignalingState(); + SyncFromJsep(); + if (aRemote || aSdpType == dom::RTCSdpType::Pranswer || + aSdpType == dom::RTCSdpType::Answer) { + InvalidateLastReturnedParameters(); + } + + // Section 4.4.1.5 Set the RTCSessionDescription: + if (aSdpType == dom::RTCSdpType::Rollback) { + // - step 4.5.10, type is rollback + RollbackRTCDtlsTransports(); + } else if (!(aRemote && aSdpType == dom::RTCSdpType::Offer)) { + // - step 4.5.9 type is not rollback + // - step 4.5.9.1 when remote is false + // - step 4.5.9.2.13 when remote is true, type answer or pranswer + // More simply: not rollback, and not for remote offers. + bool markAsStable = aSdpType == dom::RTCSdpType::Offer && + mSignalingState == RTCSignalingState::Stable; + UpdateRTCDtlsTransports(markAsStable); + } + + // Did we just apply a local description? + if (!aRemote) { + // We'd like to handle this in PeerConnectionImpl::UpdateNetworkState. + // Unfortunately, if the WiFi switch happens quickly, we never see + // that state change. We need to detect the ice restart here and + // reset the PeerConnectionImpl's stun addresses so they are + // regathered when PeerConnectionImpl::GatherIfReady is called. + if (mJsepSession->IsIceRestarting()) { + ResetStunAddrsForIceRestart(); + } + EnsureTransports(*mJsepSession); + } + + if (mJsepSession->GetState() == kJsepStateStable) { + if (aSdpType != dom::RTCSdpType::Rollback) { + // We need this initted for UpdateTransports + InitializeDataChannel(); + } + + // If we're rolling back a local offer, we might need to remove some + // transports, and stomp some MediaPipeline setup, but nothing further + // needs to be done. + UpdateTransports(*mJsepSession, mForceIceTcp); + if (NS_FAILED(UpdateMediaPipelines())) { + CSFLogError(LOGTAG, "Error Updating MediaPipelines"); + NS_ASSERTION( + false, + "Error Updating MediaPipelines in OnSetDescriptionSuccess()"); + aP->MaybeRejectWithOperationError("Error Updating MediaPipelines"); + } + + if (aSdpType != dom::RTCSdpType::Rollback) { + StartIceChecks(*mJsepSession); + } + + // Telemetry: record info on the current state of + // streams/renegotiations/etc Note: this code gets run on rollbacks as + // well! + + // Update the max channels used with each direction for each type + uint16_t receiving[SdpMediaSection::kMediaTypes]; + uint16_t sending[SdpMediaSection::kMediaTypes]; + mJsepSession->CountTracksAndDatachannels(receiving, sending); + for (size_t i = 0; i < SdpMediaSection::kMediaTypes; i++) { + if (mMaxReceiving[i] < receiving[i]) { + mMaxReceiving[i] = receiving[i]; + } + if (mMaxSending[i] < sending[i]) { + mMaxSending[i] = sending[i]; + } + } + } + + mPendingRemoteDescription = + mJsepSession->GetRemoteDescription(kJsepDescriptionPending); + mCurrentRemoteDescription = + mJsepSession->GetRemoteDescription(kJsepDescriptionCurrent); + mPendingLocalDescription = + mJsepSession->GetLocalDescription(kJsepDescriptionPending); + mCurrentLocalDescription = + mJsepSession->GetLocalDescription(kJsepDescriptionCurrent); + mPendingOfferer = mJsepSession->IsPendingOfferer(); + mCurrentOfferer = mJsepSession->IsCurrentOfferer(); + + if (aSdpType == dom::RTCSdpType::Answer) { + std::set> iceCredentials = + mJsepSession->GetLocalIceCredentials(); + std::vector> + iceCredentialsNotReplaced; + std::set_intersection(mLocalIceCredentialsToReplace.begin(), + mLocalIceCredentialsToReplace.end(), + iceCredentials.begin(), iceCredentials.end(), + std::back_inserter(iceCredentialsNotReplaced)); + + if (iceCredentialsNotReplaced.empty()) { + mLocalIceCredentialsToReplace.clear(); + } + } + + if (newSignalingState == RTCSignalingState::Stable) { + mNegotiationNeeded = false; + UpdateNegotiationNeeded(); + } + + // Spec does not actually tell us to do this, but that is probably a + // spec bug. + UpdateConnectionState(); + + JSErrorResult jrv; + if (newSignalingState != mSignalingState) { + mSignalingState = newSignalingState; + mPCObserver->OnStateChange(PCObserverStateType::SignalingState, jrv); + } + + if (aRemote) { + dom::RTCRtpReceiver::StreamAssociationChanges changes; + for (const auto& transceiver : mTransceivers) { + transceiver->Receiver()->UpdateStreams(&changes); + } + + for (const auto& receiver : changes.mReceiversToMute) { + // This sets the muted state for the recv track and all its clones. + receiver->SetTrackMuteFromRemoteSdp(); + } + + for (const auto& association : changes.mStreamAssociationsRemoved) { + RefPtr stream = + GetReceiveStream(association.mStreamId); + if (stream && stream->HasTrack(*association.mTrack)) { + stream->RemoveTrackInternal(association.mTrack); + } + } + + // TODO(Bug 1241291): For legacy event, remove eventually + std::vector> newStreams; + + for (const auto& association : changes.mStreamAssociationsAdded) { + RefPtr stream = + GetReceiveStream(association.mStreamId); + if (!stream) { + stream = CreateReceiveStream(association.mStreamId); + newStreams.push_back(stream); + } + + if (!stream->HasTrack(*association.mTrack)) { + stream->AddTrackInternal(association.mTrack); + } + } + + // Make sure to wait until after we've calculated track changes before + // doing this. + for (size_t i = 0; i < mTransceivers.Length();) { + auto& transceiver = mTransceivers[i]; + if (transceiver->ShouldRemove()) { + mTransceivers[i]->Close(); + mTransceivers[i]->SetRemovedFromPc(); + mTransceivers.RemoveElementAt(i); + } else { + ++i; + } + } + + for (const auto& trackEvent : changes.mTrackEvents) { + dom::Sequence> streams; + for (const auto& id : trackEvent.mStreamIds) { + RefPtr stream = GetReceiveStream(id); + if (!stream) { + MOZ_ASSERT(false); + continue; + } + if (!streams.AppendElement(*stream, fallible)) { + // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which + // might involve multiple reallocations) and potentially + // crashing here, SetCapacity could be called outside the loop + // once. + mozalloc_handle_oom(0); + } + } + mPCObserver->FireTrackEvent(*trackEvent.mReceiver, streams, jrv); + } + + // TODO(Bug 1241291): Legacy event, remove eventually + for (const auto& stream : newStreams) { + mPCObserver->FireStreamEvent(*stream, jrv); + } + } + aP->MaybeResolveWithUndefined(); + })); +} + +void PeerConnectionImpl::OnSetDescriptionError() { + mUncommittedJsepSession = nullptr; +} + +RTCSignalingState PeerConnectionImpl::GetSignalingState() const { + switch (mJsepSession->GetState()) { + case kJsepStateStable: + return RTCSignalingState::Stable; + break; + case kJsepStateHaveLocalOffer: + return RTCSignalingState::Have_local_offer; + break; + case kJsepStateHaveRemoteOffer: + return RTCSignalingState::Have_remote_offer; + break; + case kJsepStateHaveLocalPranswer: + return RTCSignalingState::Have_local_pranswer; + break; + case kJsepStateHaveRemotePranswer: + return RTCSignalingState::Have_remote_pranswer; + break; + case kJsepStateClosed: + return RTCSignalingState::Closed; + break; + } + MOZ_CRASH("Invalid JSEP state"); +} + +bool PeerConnectionImpl::IsClosed() const { + return mSignalingState == RTCSignalingState::Closed; +} + +PeerConnectionWrapper::PeerConnectionWrapper(const std::string& handle) + : impl_(nullptr) { + if (PeerConnectionCtx::isActive()) { + impl_ = PeerConnectionCtx::GetInstance()->GetPeerConnection(handle); + } +} + +const RefPtr PeerConnectionImpl::GetTransportHandler() + const { + return mTransportHandler; +} + +const std::string& PeerConnectionImpl::GetHandle() { return mHandle; } + +const std::string& PeerConnectionImpl::GetName() { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mName; +} + +void PeerConnectionImpl::CandidateReady(const std::string& candidate, + const std::string& transportId, + const std::string& ufrag) { + STAMP_TIMECARD(mTimeCard, "Ice Candidate gathered"); + PC_AUTO_ENTER_API_CALL_VOID_RETURN(false); + + if (mForceIceTcp && std::string::npos != candidate.find(" UDP ")) { + CSFLogWarn(LOGTAG, "Blocking local UDP candidate: %s", candidate.c_str()); + STAMP_TIMECARD(mTimeCard, "UDP Ice Candidate blocked"); + return; + } + + // One of the very few places we still use level; required by the JSEP API + uint16_t level = 0; + std::string mid; + bool skipped = false; + + if (mUncommittedJsepSession) { + // An sLD or sRD is in progress, and while that is the case, we need to add + // the candidate to both the current JSEP engine, and the uncommitted JSEP + // engine. We ignore errors because the spec says to only take into account + // the current/pending local descriptions when determining whether to + // surface the candidate to content, which does not take into account any + // in-progress sRD/sLD. + Unused << mUncommittedJsepSession->AddLocalIceCandidate( + candidate, transportId, ufrag, &level, &mid, &skipped); + } + + nsresult res = mJsepSession->AddLocalIceCandidate( + candidate, transportId, ufrag, &level, &mid, &skipped); + + if (NS_FAILED(res)) { + std::string errorString = mJsepSession->GetLastError(); + + STAMP_TIMECARD(mTimeCard, "Local Ice Candidate invalid"); + CSFLogError(LOGTAG, + "Failed to incorporate local candidate into SDP:" + " res = %u, candidate = %s, transport-id = %s," + " error = %s", + static_cast(res), candidate.c_str(), + transportId.c_str(), errorString.c_str()); + return; + } + + if (skipped) { + STAMP_TIMECARD(mTimeCard, "Local Ice Candidate skipped"); + CSFLogInfo(LOGTAG, + "Skipped adding local candidate %s (transport-id %s) " + "to SDP, this typically happens because the m-section " + "is bundled, which means it doesn't make sense for it " + "to have its own transport-related attributes.", + candidate.c_str(), transportId.c_str()); + return; + } + + mPendingLocalDescription = + mJsepSession->GetLocalDescription(kJsepDescriptionPending); + mCurrentLocalDescription = + mJsepSession->GetLocalDescription(kJsepDescriptionCurrent); + CSFLogInfo(LOGTAG, "Passing local candidate to content: %s", + candidate.c_str()); + SendLocalIceCandidateToContent(level, mid, candidate, ufrag); +} + +void PeerConnectionImpl::SendLocalIceCandidateToContent( + uint16_t level, const std::string& mid, const std::string& candidate, + const std::string& ufrag) { + STAMP_TIMECARD(mTimeCard, "Send Ice Candidate to content"); + JSErrorResult rv; + mPCObserver->OnIceCandidate(level, ObString(mid.c_str()), + ObString(candidate.c_str()), + ObString(ufrag.c_str()), rv); +} + +void PeerConnectionImpl::IceConnectionStateChange( + dom::RTCIceConnectionState domState) { + PC_AUTO_ENTER_API_CALL_VOID_RETURN(false); + + CSFLogDebug(LOGTAG, "%s: %d -> %d", __FUNCTION__, + static_cast(mIceConnectionState), + static_cast(domState)); + + if (domState == mIceConnectionState) { + // no work to be done since the states are the same. + // this can happen during ICE rollback situations. + return; + } + + mIceConnectionState = domState; + + // Would be nice if we had a means of converting one of these dom enums + // to a string that wasn't almost as much text as this switch statement... + switch (mIceConnectionState) { + case RTCIceConnectionState::New: + STAMP_TIMECARD(mTimeCard, "Ice state: new"); + break; + case RTCIceConnectionState::Checking: + // For telemetry + mIceStartTime = TimeStamp::Now(); + STAMP_TIMECARD(mTimeCard, "Ice state: checking"); + break; + case RTCIceConnectionState::Connected: + STAMP_TIMECARD(mTimeCard, "Ice state: connected"); + StartCallTelem(); + break; + case RTCIceConnectionState::Completed: + STAMP_TIMECARD(mTimeCard, "Ice state: completed"); + break; + case RTCIceConnectionState::Failed: + STAMP_TIMECARD(mTimeCard, "Ice state: failed"); + break; + case RTCIceConnectionState::Disconnected: + STAMP_TIMECARD(mTimeCard, "Ice state: disconnected"); + break; + case RTCIceConnectionState::Closed: + STAMP_TIMECARD(mTimeCard, "Ice state: closed"); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unexpected mIceConnectionState!"); + } + + WrappableJSErrorResult rv; + mPCObserver->OnStateChange(PCObserverStateType::IceConnectionState, rv); + UpdateConnectionState(); +} + +void PeerConnectionImpl::OnCandidateFound(const std::string& aTransportId, + const CandidateInfo& aCandidateInfo) { + if (mStunAddrsRequest && !aCandidateInfo.mMDNSAddress.empty()) { + MOZ_ASSERT(!aCandidateInfo.mActualAddress.empty()); + + if (mCanRegisterMDNSHostnamesDirectly) { + auto itor = mRegisteredMDNSHostnames.find(aCandidateInfo.mMDNSAddress); + + // We'll see the address twice if we're generating both UDP and TCP + // candidates. + if (itor == mRegisteredMDNSHostnames.end()) { + mRegisteredMDNSHostnames.insert(aCandidateInfo.mMDNSAddress); + mStunAddrsRequest->SendRegisterMDNSHostname( + nsCString(aCandidateInfo.mMDNSAddress.c_str()), + nsCString(aCandidateInfo.mActualAddress.c_str())); + } + } else { + mMDNSHostnamesToRegister.emplace(aCandidateInfo.mMDNSAddress, + aCandidateInfo.mActualAddress); + } + } + + if (!aCandidateInfo.mDefaultHostRtp.empty()) { + UpdateDefaultCandidate(aCandidateInfo.mDefaultHostRtp, + aCandidateInfo.mDefaultPortRtp, + aCandidateInfo.mDefaultHostRtcp, + aCandidateInfo.mDefaultPortRtcp, aTransportId); + } + CandidateReady(aCandidateInfo.mCandidate, aTransportId, + aCandidateInfo.mUfrag); +} + +void PeerConnectionImpl::IceGatheringStateChange( + dom::RTCIceGatheringState state) { + PC_AUTO_ENTER_API_CALL_VOID_RETURN(false); + + CSFLogDebug(LOGTAG, "%s %d", __FUNCTION__, static_cast(state)); + if (mIceGatheringState == state) { + return; + } + + mIceGatheringState = state; + + // Would be nice if we had a means of converting one of these dom enums + // to a string that wasn't almost as much text as this switch statement... + switch (mIceGatheringState) { + case RTCIceGatheringState::New: + STAMP_TIMECARD(mTimeCard, "Ice gathering state: new"); + break; + case RTCIceGatheringState::Gathering: + STAMP_TIMECARD(mTimeCard, "Ice gathering state: gathering"); + break; + case RTCIceGatheringState::Complete: + STAMP_TIMECARD(mTimeCard, "Ice gathering state: complete"); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unexpected mIceGatheringState!"); + } + + JSErrorResult rv; + mPCObserver->OnStateChange(PCObserverStateType::IceGatheringState, rv); +} + +void PeerConnectionImpl::UpdateDefaultCandidate( + const std::string& defaultAddr, uint16_t defaultPort, + const std::string& defaultRtcpAddr, uint16_t defaultRtcpPort, + const std::string& transportId) { + CSFLogDebug(LOGTAG, "%s", __FUNCTION__); + mJsepSession->UpdateDefaultCandidate( + defaultAddr, defaultPort, defaultRtcpAddr, defaultRtcpPort, transportId); + if (mUncommittedJsepSession) { + mUncommittedJsepSession->UpdateDefaultCandidate( + defaultAddr, defaultPort, defaultRtcpAddr, defaultRtcpPort, + transportId); + } +} + +static UniquePtr GetDataChannelStats_s( + const RefPtr& aDataConnection, + const DOMHighResTimeStamp aTimestamp) { + UniquePtr report(new dom::RTCStatsCollection); + if (aDataConnection) { + aDataConnection->AppendStatsToReport(report, aTimestamp); + } + return report; +} + +RefPtr PeerConnectionImpl::GetDataChannelStats( + const RefPtr& aDataChannelConnection, + const DOMHighResTimeStamp aTimestamp) { + // Gather stats from DataChannels + return InvokeAsync( + GetMainThreadSerialEventTarget(), __func__, + [aDataChannelConnection, aTimestamp]() { + return dom::RTCStatsPromise::CreateAndResolve( + GetDataChannelStats_s(aDataChannelConnection, aTimestamp), + __func__); + }); +} + +void PeerConnectionImpl::CollectConduitTelemetryData() { + MOZ_ASSERT(NS_IsMainThread()); + + nsTArray> conduits; + for (const auto& transceiver : mTransceivers) { + if (RefPtr conduit = transceiver->GetConduit()) { + conduit->AsVideoSessionConduit().apply( + [&](const auto& aVideo) { conduits.AppendElement(aVideo); }); + } + } + + if (!conduits.IsEmpty() && mCall) { + mCall->mCallThread->Dispatch( + NS_NewRunnableFunction(__func__, [conduits = std::move(conduits)] { + for (const auto& conduit : conduits) { + conduit->CollectTelemetryData(); + } + })); + } +} + +nsTArray PeerConnectionImpl::GetCodecStats( + DOMHighResTimeStamp aNow) { + MOZ_ASSERT(NS_IsMainThread()); + nsTArray result; + + struct CodecComparator { + bool operator()(const JsepCodecDescription* aA, + const JsepCodecDescription* aB) const { + return aA->StatsId() < aB->StatsId(); + } + }; + + // transportId -> codec; per direction (whether the codecType + // shall be "encode", "decode" or absent (if a codec exists in both maps for a + // transport)). These do the bookkeeping to ensure codec stats get coalesced + // to transport level. + std::map> + sendCodecMap; + std::map> + recvCodecMap; + + // Find all JsepCodecDescription instances we want to turn into codec stats. + for (const auto& transceiver : mTransceivers) { + // TODO: Grab these from the JSEP transceivers instead + auto sendCodecs = transceiver->GetNegotiatedSendCodecs(); + auto recvCodecs = transceiver->GetNegotiatedRecvCodecs(); + + const std::string transportId = transceiver->GetTransportId(); + // This ensures both codec maps have the same size. + auto& sendMap = sendCodecMap[transportId]; + auto& recvMap = recvCodecMap[transportId]; + + sendCodecs.apply([&](const auto& aCodecs) { + for (const auto& codec : aCodecs) { + sendMap.insert(codec.get()); + } + }); + recvCodecs.apply([&](const auto& aCodecs) { + for (const auto& codec : aCodecs) { + recvMap.insert(codec.get()); + } + }); + } + + auto createCodecStat = [&](const JsepCodecDescription* aCodec, + const nsString& aTransportId, + Maybe aCodecType) { + uint16_t pt; + { + DebugOnly rv = aCodec->GetPtAsInt(&pt); + MOZ_ASSERT(rv); + } + nsString mimeType; + mimeType.AppendPrintf( + "%s/%s", aCodec->Type() == SdpMediaSection::kVideo ? "video" : "audio", + aCodec->mName.c_str()); + nsString id = aTransportId; + id.Append(u"_"); + id.Append(aCodec->StatsId()); + + dom::RTCCodecStats codec; + codec.mId.Construct(std::move(id)); + codec.mTimestamp.Construct(aNow); + codec.mType.Construct(RTCStatsType::Codec); + codec.mPayloadType = pt; + if (aCodecType) { + codec.mCodecType.Construct(*aCodecType); + } + codec.mTransportId = aTransportId; + codec.mMimeType = std::move(mimeType); + codec.mClockRate.Construct(aCodec->mClock); + if (aCodec->Type() == SdpMediaSection::MediaType::kAudio) { + codec.mChannels.Construct(aCodec->mChannels); + } + if (aCodec->mSdpFmtpLine) { + codec.mSdpFmtpLine.Construct( + NS_ConvertUTF8toUTF16(aCodec->mSdpFmtpLine->c_str())); + } + + result.AppendElement(std::move(codec)); + }; + + // Create codec stats for the gathered codec descriptions, sorted primarily + // by transportId, secondarily by payload type (from StatsId()). + for (const auto& [transportId, sendCodecs] : sendCodecMap) { + const auto& recvCodecs = recvCodecMap[transportId]; + const nsString tid = NS_ConvertASCIItoUTF16(transportId); + AutoTArray bidirectionalCodecs; + AutoTArray unidirectionalCodecs; + std::set_intersection(sendCodecs.cbegin(), sendCodecs.cend(), + recvCodecs.cbegin(), recvCodecs.cend(), + MakeBackInserter(bidirectionalCodecs), + CodecComparator()); + std::set_symmetric_difference(sendCodecs.cbegin(), sendCodecs.cend(), + recvCodecs.cbegin(), recvCodecs.cend(), + MakeBackInserter(unidirectionalCodecs), + CodecComparator()); + for (const auto* codec : bidirectionalCodecs) { + createCodecStat(codec, tid, Nothing()); + } + for (const auto* codec : unidirectionalCodecs) { + createCodecStat( + codec, tid, + Some(codec->mDirection == sdp::kSend ? RTCCodecType::Encode + : RTCCodecType::Decode)); + } + } + + return result; +} + +RefPtr PeerConnectionImpl::GetStats( + dom::MediaStreamTrack* aSelector, bool aInternalStats) { + MOZ_ASSERT(NS_IsMainThread()); + + if (mFinalStatsQuery) { + // This case should be _extremely_ rare; this will basically only happen + // when WebrtcGlobalInformation tries to get our stats while we are tearing + // down. + return mFinalStatsQuery->Then( + GetMainThreadSerialEventTarget(), __func__, + [this, self = RefPtr(this)]() { + UniquePtr finalStats = + MakeUnique(); + // Might not be set if this encountered some error. + if (mFinalStats) { + *finalStats = *mFinalStats; + } + return RTCStatsReportPromise::CreateAndResolve(std::move(finalStats), + __func__); + }); + } + + nsTArray> promises; + DOMHighResTimeStamp now = mTimestampMaker.GetNow().ToDom(); + + nsTArray codecStats = GetCodecStats(now); + std::set transportIds; + + if (!aSelector) { + // There might not be any senders/receivers if we're DataChannel only, so we + // don't handle the null selector case in the loop below. + transportIds.insert(""); + } + + nsTArray< + std::tuple>> + transceiverStatsPromises; + for (const auto& transceiver : mTransceivers) { + const bool sendSelected = transceiver->Sender()->HasTrack(aSelector); + const bool recvSelected = transceiver->Receiver()->HasTrack(aSelector); + if (!sendSelected && !recvSelected) { + continue; + } + + if (aSelector) { + transportIds.insert(transceiver->GetTransportId()); + } + + nsTArray> rtpStreamPromises; + // Get all rtp stream stats for the given selector. Then filter away any + // codec stat not related to the selector, and assign codec ids to the + // stream stats. + // Skips the ICE stats; we do our own queries based on |transportIds| to + // avoid duplicates + if (sendSelected) { + rtpStreamPromises.AppendElements( + transceiver->Sender()->GetStatsInternal(true)); + } + if (recvSelected) { + rtpStreamPromises.AppendElements( + transceiver->Receiver()->GetStatsInternal(true)); + } + transceiverStatsPromises.AppendElement( + std::make_tuple(transceiver.get(), + RTCStatsPromise::All(GetMainThreadSerialEventTarget(), + rtpStreamPromises))); + } + + promises.AppendElement(RTCRtpTransceiver::ApplyCodecStats( + std::move(codecStats), std::move(transceiverStatsPromises))); + + for (const auto& transportId : transportIds) { + promises.AppendElement(mTransportHandler->GetIceStats(transportId, now)); + } + + promises.AppendElement(GetDataChannelStats(mDataConnection, now)); + + auto pcStatsCollection = MakeUnique(); + RTCPeerConnectionStats pcStats; + pcStats.mTimestamp.Construct(now); + pcStats.mType.Construct(RTCStatsType::Peer_connection); + pcStats.mId.Construct(NS_ConvertUTF8toUTF16(mHandle.c_str())); + pcStats.mDataChannelsOpened.Construct(mDataChannelsOpened); + pcStats.mDataChannelsClosed.Construct(mDataChannelsClosed); + if (!pcStatsCollection->mPeerConnectionStats.AppendElement(std::move(pcStats), + fallible)) { + mozalloc_handle_oom(0); + } + promises.AppendElement(RTCStatsPromise::CreateAndResolve( + std::move(pcStatsCollection), __func__)); + + // This is what we're going to return; all the stuff in |promises| will be + // accumulated here. + UniquePtr report( + new dom::RTCStatsReportInternal); + report->mPcid = NS_ConvertASCIItoUTF16(mName.c_str()); + if (mWindow && mWindow->GetBrowsingContext()) { + report->mBrowserId = mWindow->GetBrowsingContext()->BrowserId(); + } + report->mConfiguration.Construct(mJsConfiguration); + // TODO(bug 1589416): We need to do better here. + if (!mIceStartTime.IsNull()) { + report->mCallDurationMs.Construct( + (TimeStamp::Now() - mIceStartTime).ToMilliseconds()); + } + report->mIceRestarts = mIceRestartCount; + report->mIceRollbacks = mIceRollbackCount; + report->mClosed = false; + report->mTimestamp = now; + + if (aInternalStats && mJsepSession) { + for (const auto& candidate : mRawTrickledCandidates) { + if (!report->mRawRemoteCandidates.AppendElement( + NS_ConvertASCIItoUTF16(candidate.c_str()), fallible)) { + // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might + // involve multiple reallocations) and potentially crashing here, + // SetCapacity could be called outside the loop once. + mozalloc_handle_oom(0); + } + } + + if (mJsepSession) { + // TODO we probably should report Current and Pending SDPs here + // separately. Plus the raw SDP we got from JS (mLocalRequestedSDP). + // And if it's the offer or answer would also be nice. + std::string localDescription = + mJsepSession->GetLocalDescription(kJsepDescriptionPendingOrCurrent); + std::string remoteDescription = + mJsepSession->GetRemoteDescription(kJsepDescriptionPendingOrCurrent); + if (!report->mSdpHistory.AppendElements(mSdpHistory, fallible)) { + mozalloc_handle_oom(0); + } + if (mJsepSession->IsPendingOfferer().isSome()) { + report->mOfferer.Construct(*mJsepSession->IsPendingOfferer()); + } else if (mJsepSession->IsCurrentOfferer().isSome()) { + report->mOfferer.Construct(*mJsepSession->IsCurrentOfferer()); + } else { + // Silly. + report->mOfferer.Construct(false); + } + } + } + + return dom::RTCStatsPromise::All(GetMainThreadSerialEventTarget(), promises) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [report = std::move(report), idGen = mIdGenerator]( + nsTArray> aStats) mutable { + idGen->RewriteIds(std::move(aStats), report.get()); + return dom::RTCStatsReportPromise::CreateAndResolve( + std::move(report), __func__); + }, + [](nsresult rv) { + return dom::RTCStatsReportPromise::CreateAndReject(rv, __func__); + }); +} + +void PeerConnectionImpl::RecordIceRestartStatistics(JsepSdpType type) { + switch (type) { + case mozilla::kJsepSdpOffer: + case mozilla::kJsepSdpPranswer: + break; + case mozilla::kJsepSdpAnswer: + ++mIceRestartCount; + break; + case mozilla::kJsepSdpRollback: + ++mIceRollbackCount; + break; + } +} + +void PeerConnectionImpl::StoreConfigurationForAboutWebrtc( + const dom::RTCConfiguration& aConfig) { + // This will only be called once, when the PeerConnection is initially + // configured, at least until setConfiguration is implemented + // see https://bugzilla.mozilla.org/show_bug.cgi?id=1253706 + // @TODO bug 1739451 call this from setConfiguration + mJsConfiguration.mIceServers.Clear(); + for (const auto& server : aConfig.mIceServers) { + RTCIceServerInternal internal; + internal.mCredentialProvided = server.mCredential.WasPassed(); + internal.mUserNameProvided = server.mUsername.WasPassed(); + if (server.mUrl.WasPassed()) { + if (!internal.mUrls.AppendElement(server.mUrl.Value(), fallible)) { + mozalloc_handle_oom(0); + } + } + if (server.mUrls.WasPassed()) { + for (const auto& url : server.mUrls.Value().GetAsStringSequence()) { + if (!internal.mUrls.AppendElement(url, fallible)) { + mozalloc_handle_oom(0); + } + } + } + if (!mJsConfiguration.mIceServers.AppendElement(internal, fallible)) { + mozalloc_handle_oom(0); + } + } + mJsConfiguration.mSdpSemantics.Reset(); + if (aConfig.mSdpSemantics.WasPassed()) { + mJsConfiguration.mSdpSemantics.Construct(aConfig.mSdpSemantics.Value()); + } + + mJsConfiguration.mIceTransportPolicy.Reset(); + mJsConfiguration.mIceTransportPolicy.Construct(aConfig.mIceTransportPolicy); + mJsConfiguration.mBundlePolicy.Reset(); + mJsConfiguration.mBundlePolicy.Construct(aConfig.mBundlePolicy); + mJsConfiguration.mPeerIdentityProvided = !aConfig.mPeerIdentity.IsEmpty(); + mJsConfiguration.mCertificatesProvided = !aConfig.mCertificates.Length(); +} + +dom::Sequence +PeerConnectionImpl::GetLastSdpParsingErrors() const { + const auto& sdpErrors = mJsepSession->GetLastSdpParsingErrors(); + dom::Sequence domErrors; + if (!domErrors.SetCapacity(domErrors.Length(), fallible)) { + mozalloc_handle_oom(0); + } + for (const auto& error : sdpErrors) { + mozilla::dom::RTCSdpParsingErrorInternal internal; + internal.mLineNumber = error.first; + if (!AppendASCIItoUTF16(MakeStringSpan(error.second.c_str()), + internal.mError, fallible)) { + mozalloc_handle_oom(0); + } + if (!domErrors.AppendElement(std::move(internal), fallible)) { + mozalloc_handle_oom(0); + } + } + return domErrors; +} + +// Telemetry for when calls start +void PeerConnectionImpl::StartCallTelem() { + if (mCallTelemStarted) { + return; + } + MOZ_RELEASE_ASSERT(mWindow); + uint64_t windowId = mWindow->WindowID(); + auto found = sCallDurationTimers.find(windowId); + if (found == sCallDurationTimers.end()) { + found = + sCallDurationTimers.emplace(windowId, PeerConnectionAutoTimer()).first; + } + found->second.RegisterConnection(); + mCallTelemStarted = true; + + // Increment session call counter + // If we want to track Loop calls independently here, we need two + // histograms. + // + // NOTE: As of bug 1654248 landing we are no longer counting renegotiations + // as separate calls. Expect numbers to drop compared to + // WEBRTC_CALL_COUNT_2. + Telemetry::Accumulate(Telemetry::WEBRTC_CALL_COUNT_3, 1); +} + +void PeerConnectionImpl::StunAddrsHandler::OnMDNSQueryComplete( + const nsCString& hostname, const Maybe& address) { + MOZ_ASSERT(NS_IsMainThread()); + PeerConnectionWrapper pcw(mPcHandle); + if (!pcw.impl()) { + return; + } + auto itor = pcw.impl()->mQueriedMDNSHostnames.find(hostname.BeginReading()); + if (itor != pcw.impl()->mQueriedMDNSHostnames.end()) { + if (address) { + for (auto& cand : itor->second) { + // Replace obfuscated address with actual address + std::string obfuscatedAddr = cand.mTokenizedCandidate[4]; + cand.mTokenizedCandidate[4] = address->BeginReading(); + std::ostringstream o; + for (size_t i = 0; i < cand.mTokenizedCandidate.size(); ++i) { + o << cand.mTokenizedCandidate[i]; + if (i + 1 != cand.mTokenizedCandidate.size()) { + o << " "; + } + } + std::string mungedCandidate = o.str(); + pcw.impl()->StampTimecard("Done looking up mDNS name"); + pcw.impl()->mTransportHandler->AddIceCandidate( + cand.mTransportId, mungedCandidate, cand.mUfrag, obfuscatedAddr); + } + } else { + pcw.impl()->StampTimecard("Failed looking up mDNS name"); + } + pcw.impl()->mQueriedMDNSHostnames.erase(itor); + } +} + +void PeerConnectionImpl::StunAddrsHandler::OnStunAddrsAvailable( + const mozilla::net::NrIceStunAddrArray& addrs) { + CSFLogInfo(LOGTAG, "%s: receiving (%d) stun addrs", __FUNCTION__, + (int)addrs.Length()); + PeerConnectionWrapper pcw(mPcHandle); + if (!pcw.impl()) { + return; + } + pcw.impl()->mStunAddrs = addrs.Clone(); + pcw.impl()->mLocalAddrsRequestState = STUN_ADDR_REQUEST_COMPLETE; + pcw.impl()->FlushIceCtxOperationQueueIfReady(); + // If parent process returns 0 STUN addresses, change ICE connection + // state to failed. + if (!pcw.impl()->mStunAddrs.Length()) { + pcw.impl()->IceConnectionStateChange(dom::RTCIceConnectionState::Failed); + } +} + +void PeerConnectionImpl::InitLocalAddrs() { + if (mLocalAddrsRequestState == STUN_ADDR_REQUEST_PENDING) { + return; + } + if (mStunAddrsRequest) { + mLocalAddrsRequestState = STUN_ADDR_REQUEST_PENDING; + mStunAddrsRequest->SendGetStunAddrs(); + } else { + mLocalAddrsRequestState = STUN_ADDR_REQUEST_COMPLETE; + } +} + +bool PeerConnectionImpl::ShouldForceProxy() const { + if (Preferences::GetBool("media.peerconnection.ice.proxy_only", false)) { + return true; + } + + bool isPBM = false; + // This complicated null check is being extra conservative to avoid + // introducing crashes. It may not be needed. + if (mWindow && mWindow->GetExtantDoc() && + mWindow->GetExtantDoc()->GetPrincipal() && + mWindow->GetExtantDoc() + ->GetPrincipal() + ->OriginAttributesRef() + .mPrivateBrowsingId > 0) { + isPBM = true; + } + + if (isPBM && Preferences::GetBool( + "media.peerconnection.ice.proxy_only_if_pbmode", false)) { + return true; + } + + if (!Preferences::GetBool( + "media.peerconnection.ice.proxy_only_if_behind_proxy", false)) { + return false; + } + + // Ok, we're supposed to be proxy_only, but only if a proxy is configured. + // Let's just see if the document was loaded via a proxy. + + nsCOMPtr httpChannelInternal = GetChannel(); + if (!httpChannelInternal) { + return false; + } + + bool proxyUsed = false; + Unused << httpChannelInternal->GetIsProxyUsed(&proxyUsed); + return proxyUsed; +} + +void PeerConnectionImpl::EnsureTransports(const JsepSession& aSession) { + mJsepSession->ForEachTransceiver([this, + self = RefPtr(this)]( + const JsepTransceiver& transceiver) { + if (transceiver.HasOwnTransport()) { + mTransportHandler->EnsureProvisionalTransport( + transceiver.mTransport.mTransportId, + transceiver.mTransport.mLocalUfrag, transceiver.mTransport.mLocalPwd, + transceiver.mTransport.mComponents); + } + }); + + GatherIfReady(); +} + +void PeerConnectionImpl::UpdateRTCDtlsTransports(bool aMarkAsStable) { + mJsepSession->ForEachTransceiver( + [this, self = RefPtr(this)]( + const JsepTransceiver& jsepTransceiver) { + std::string transportId = jsepTransceiver.mTransport.mTransportId; + if (transportId.empty()) { + return; + } + if (!mTransportIdToRTCDtlsTransport.count(transportId)) { + mTransportIdToRTCDtlsTransport.emplace( + transportId, new RTCDtlsTransport(GetParentObject())); + } + }); + + for (auto& transceiver : mTransceivers) { + std::string transportId = transceiver->GetTransportId(); + if (transportId.empty()) { + continue; + } + if (mTransportIdToRTCDtlsTransport.count(transportId)) { + transceiver->SetDtlsTransport(mTransportIdToRTCDtlsTransport[transportId], + aMarkAsStable); + } + } + + // Spec says we only update the RTCSctpTransport when negotiation completes +} + +void PeerConnectionImpl::RollbackRTCDtlsTransports() { + for (auto& transceiver : mTransceivers) { + transceiver->RollbackToStableDtlsTransport(); + } +} + +void PeerConnectionImpl::RemoveRTCDtlsTransportsExcept( + const std::set& aTransportIds) { + for (auto iter = mTransportIdToRTCDtlsTransport.begin(); + iter != mTransportIdToRTCDtlsTransport.end();) { + if (!aTransportIds.count(iter->first)) { + iter = mTransportIdToRTCDtlsTransport.erase(iter); + } else { + ++iter; + } + } +} + +nsresult PeerConnectionImpl::UpdateTransports(const JsepSession& aSession, + const bool forceIceTcp) { + std::set finalTransports; + Maybe sctpTransport; + mJsepSession->ForEachTransceiver( + [&, this, self = RefPtr(this)]( + const JsepTransceiver& transceiver) { + if (transceiver.GetMediaType() == SdpMediaSection::kApplication && + transceiver.HasTransport()) { + sctpTransport = Some(transceiver.mTransport.mTransportId); + } + + if (transceiver.HasOwnTransport()) { + finalTransports.insert(transceiver.mTransport.mTransportId); + UpdateTransport(transceiver, forceIceTcp); + } + }); + + // clean up the unused RTCDtlsTransports + RemoveRTCDtlsTransportsExcept(finalTransports); + + mTransportHandler->RemoveTransportsExcept(finalTransports); + + for (const auto& transceiverImpl : mTransceivers) { + transceiverImpl->UpdateTransport(); + } + + if (sctpTransport.isSome()) { + auto it = mTransportIdToRTCDtlsTransport.find(*sctpTransport); + if (it == mTransportIdToRTCDtlsTransport.end()) { + // What? + MOZ_ASSERT(false); + return NS_ERROR_FAILURE; + } + if (!mDataConnection) { + // What? + MOZ_ASSERT(false); + return NS_ERROR_FAILURE; + } + RefPtr dtlsTransport = it->second; + // Why on earth does the spec use a floating point for this? + double maxMessageSize = + static_cast(mDataConnection->GetMaxMessageSize()); + Nullable maxChannels; + + if (!mSctpTransport) { + mSctpTransport = new RTCSctpTransport(GetParentObject(), *dtlsTransport, + maxMessageSize, maxChannels); + } else { + mSctpTransport->SetTransport(*dtlsTransport); + mSctpTransport->SetMaxMessageSize(maxMessageSize); + mSctpTransport->SetMaxChannels(maxChannels); + } + } else { + mSctpTransport = nullptr; + } + + return NS_OK; +} + +void PeerConnectionImpl::UpdateTransport(const JsepTransceiver& aTransceiver, + bool aForceIceTcp) { + std::string ufrag; + std::string pwd; + std::vector candidates; + size_t components = 0; + + const JsepTransport& transport = aTransceiver.mTransport; + unsigned level = aTransceiver.GetLevel(); + + CSFLogDebug(LOGTAG, "ACTIVATING TRANSPORT! - PC %s: level=%u components=%u", + mHandle.c_str(), (unsigned)level, + (unsigned)transport.mComponents); + + ufrag = transport.mIce->GetUfrag(); + pwd = transport.mIce->GetPassword(); + candidates = transport.mIce->GetCandidates(); + components = transport.mComponents; + if (aForceIceTcp) { + candidates.erase( + std::remove_if(candidates.begin(), candidates.end(), + [](const std::string& s) { + return s.find(" UDP ") != std::string::npos || + s.find(" udp ") != std::string::npos; + }), + candidates.end()); + } + + nsTArray keyDer; + nsTArray certDer; + nsresult rv = Identity()->Serialize(&keyDer, &certDer); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "%s: Failed to serialize DTLS identity: %d", + __FUNCTION__, (int)rv); + return; + } + + DtlsDigestList digests; + for (const auto& fingerprint : + transport.mDtls->GetFingerprints().mFingerprints) { + std::ostringstream ss; + ss << fingerprint.hashFunc; + digests.emplace_back(ss.str(), fingerprint.fingerprint); + } + + mTransportHandler->ActivateTransport( + transport.mTransportId, transport.mLocalUfrag, transport.mLocalPwd, + components, ufrag, pwd, keyDer, certDer, Identity()->auth_type(), + transport.mDtls->GetRole() == JsepDtlsTransport::kJsepDtlsClient, digests, + PrivacyRequested()); + + for (auto& candidate : candidates) { + AddIceCandidate("candidate:" + candidate, transport.mTransportId, ufrag); + } +} + +nsresult PeerConnectionImpl::UpdateMediaPipelines() { + for (RefPtr& transceiver : mTransceivers) { + transceiver->ResetSync(); + } + + for (RefPtr& transceiver : mTransceivers) { + if (!transceiver->IsVideo()) { + nsresult rv = transceiver->SyncWithMatchingVideoConduits(mTransceivers); + if (NS_FAILED(rv)) { + return rv; + } + } + + transceiver->UpdatePrincipalPrivacy(PrivacyRequested() + ? PrincipalPrivacy::Private + : PrincipalPrivacy::NonPrivate); + + nsresult rv = transceiver->UpdateConduit(); + if (NS_FAILED(rv)) { + return rv; + } + } + + return NS_OK; +} + +void PeerConnectionImpl::StartIceChecks(const JsepSession& aSession) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mCanRegisterMDNSHostnamesDirectly) { + for (auto& pair : mMDNSHostnamesToRegister) { + mRegisteredMDNSHostnames.insert(pair.first); + mStunAddrsRequest->SendRegisterMDNSHostname( + nsCString(pair.first.c_str()), nsCString(pair.second.c_str())); + } + mMDNSHostnamesToRegister.clear(); + mCanRegisterMDNSHostnamesDirectly = true; + } + + std::vector attributes; + if (aSession.RemoteIsIceLite()) { + attributes.push_back("ice-lite"); + } + + if (!aSession.GetIceOptions().empty()) { + attributes.push_back("ice-options:"); + for (const auto& option : aSession.GetIceOptions()) { + attributes.back() += option + ' '; + } + } + + nsCOMPtr runnable( + WrapRunnable(mTransportHandler, &MediaTransportHandler::StartIceChecks, + aSession.IsIceControlling(), attributes)); + + PerformOrEnqueueIceCtxOperation(runnable); +} + +bool PeerConnectionImpl::GetPrefDefaultAddressOnly() const { + MOZ_ASSERT(NS_IsMainThread()); + + uint64_t winId = mWindow->WindowID(); + + bool default_address_only = Preferences::GetBool( + "media.peerconnection.ice.default_address_only", false); + default_address_only |= + !MediaManager::Get()->IsActivelyCapturingOrHasAPermission(winId); + return default_address_only; +} + +bool PeerConnectionImpl::GetPrefObfuscateHostAddresses() const { + MOZ_ASSERT(NS_IsMainThread()); + + uint64_t winId = mWindow->WindowID(); + + bool obfuscate_host_addresses = Preferences::GetBool( + "media.peerconnection.ice.obfuscate_host_addresses", false); + obfuscate_host_addresses &= + !MediaManager::Get()->IsActivelyCapturingOrHasAPermission(winId); + obfuscate_host_addresses &= !PeerConnectionImpl::HostnameInPref( + "media.peerconnection.ice.obfuscate_host_addresses.blocklist", mHostname); + obfuscate_host_addresses &= XRE_IsContentProcess(); + + return obfuscate_host_addresses; +} + +PeerConnectionImpl::SignalHandler::SignalHandler(PeerConnectionImpl* aPc, + MediaTransportHandler* aSource) + : mHandle(aPc->GetHandle()), + mSource(aSource), + mSTSThread(aPc->GetSTSThread()) { + ConnectSignals(); +} + +PeerConnectionImpl::SignalHandler::~SignalHandler() { + ASSERT_ON_THREAD(mSTSThread); +} + +void PeerConnectionImpl::SignalHandler::ConnectSignals() { + mSource->SignalGatheringStateChange.connect( + this, &PeerConnectionImpl::SignalHandler::IceGatheringStateChange_s); + mSource->SignalConnectionStateChange.connect( + this, &PeerConnectionImpl::SignalHandler::IceConnectionStateChange_s); + mSource->SignalCandidate.connect( + this, &PeerConnectionImpl::SignalHandler::OnCandidateFound_s); + mSource->SignalAlpnNegotiated.connect( + this, &PeerConnectionImpl::SignalHandler::AlpnNegotiated_s); + mSource->SignalStateChange.connect( + this, &PeerConnectionImpl::SignalHandler::ConnectionStateChange_s); + mSource->SignalRtcpStateChange.connect( + this, &PeerConnectionImpl::SignalHandler::ConnectionStateChange_s); +} + +void PeerConnectionImpl::AddIceCandidate(const std::string& aCandidate, + const std::string& aTransportId, + const std::string& aUfrag) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!aTransportId.empty()); + + bool obfuscate_host_addresses = Preferences::GetBool( + "media.peerconnection.ice.obfuscate_host_addresses", false); + + if (obfuscate_host_addresses && !RelayOnly()) { + std::vector tokens; + TokenizeCandidate(aCandidate, tokens); + + if (tokens.size() > 4) { + std::string addr = tokens[4]; + + // Check for address ending with .local + size_t nPeriods = std::count(addr.begin(), addr.end(), '.'); + size_t dotLocalLength = 6; // length of ".local" + + if (nPeriods == 1 && + addr.rfind(".local") + dotLocalLength == addr.length()) { + if (mStunAddrsRequest) { + PendingIceCandidate cand; + cand.mTokenizedCandidate = std::move(tokens); + cand.mTransportId = aTransportId; + cand.mUfrag = aUfrag; + mQueriedMDNSHostnames[addr].push_back(cand); + + GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + "PeerConnectionImpl::SendQueryMDNSHostname", + [self = RefPtr(this), addr]() mutable { + if (self->mStunAddrsRequest) { + self->StampTimecard("Look up mDNS name"); + self->mStunAddrsRequest->SendQueryMDNSHostname( + nsCString(nsAutoCString(addr.c_str()))); + } + NS_ReleaseOnMainThread( + "PeerConnectionImpl::SendQueryMDNSHostname", self.forget()); + })); + } + // TODO: Bug 1535690, we don't want to tell the ICE context that remote + // trickle is done if we are waiting to resolve a mDNS candidate. + return; + } + } + } + + mTransportHandler->AddIceCandidate(aTransportId, aCandidate, aUfrag, ""); +} + +void PeerConnectionImpl::UpdateNetworkState(bool online) { + if (mTransportHandler) { + mTransportHandler->UpdateNetworkState(online); + } +} + +void PeerConnectionImpl::FlushIceCtxOperationQueueIfReady() { + MOZ_ASSERT(NS_IsMainThread()); + + if (IsIceCtxReady()) { + for (auto& queuedIceCtxOperation : mQueuedIceCtxOperations) { + queuedIceCtxOperation->Run(); + } + mQueuedIceCtxOperations.clear(); + } +} + +void PeerConnectionImpl::PerformOrEnqueueIceCtxOperation( + nsIRunnable* runnable) { + MOZ_ASSERT(NS_IsMainThread()); + + if (IsIceCtxReady()) { + runnable->Run(); + } else { + mQueuedIceCtxOperations.push_back(runnable); + } +} + +void PeerConnectionImpl::GatherIfReady() { + MOZ_ASSERT(NS_IsMainThread()); + + // Init local addrs here so that if we re-gather after an ICE restart + // resulting from changing WiFi networks, we get new local addrs. + // Otherwise, we would reuse the addrs from the original WiFi network + // and the ICE restart will fail. + if (!mStunAddrs.Length()) { + InitLocalAddrs(); + } + + // If we had previously queued gathering or ICE start, unqueue them + mQueuedIceCtxOperations.clear(); + nsCOMPtr runnable(WrapRunnable( + RefPtr(this), &PeerConnectionImpl::EnsureIceGathering, + GetPrefDefaultAddressOnly(), GetPrefObfuscateHostAddresses())); + + PerformOrEnqueueIceCtxOperation(runnable); +} + +already_AddRefed PeerConnectionImpl::GetChannel() + const { + Document* doc = mWindow->GetExtantDoc(); + if (NS_WARN_IF(!doc)) { + NS_WARNING("Unable to get document from window"); + return nullptr; + } + + if (!doc->GetDocumentURI()->SchemeIs("file")) { + nsIChannel* channel = doc->GetChannel(); + if (!channel) { + NS_WARNING("Unable to get channel from document"); + return nullptr; + } + + nsCOMPtr httpChannelInternal = + do_QueryInterface(channel); + if (NS_WARN_IF(!httpChannelInternal)) { + CSFLogInfo(LOGTAG, "%s: Document does not have an HTTP channel", + __FUNCTION__); + return nullptr; + } + return httpChannelInternal.forget(); + } + return nullptr; +} + +nsresult PeerConnectionImpl::SetTargetForDefaultLocalAddressLookup() { + nsCOMPtr httpChannelInternal = GetChannel(); + if (!httpChannelInternal) { + return NS_OK; + } + + nsCString remoteIp; + nsresult rv = httpChannelInternal->GetRemoteAddress(remoteIp); + if (NS_FAILED(rv) || remoteIp.IsEmpty()) { + CSFLogError(LOGTAG, "%s: Failed to get remote IP address: %d", __FUNCTION__, + (int)rv); + return rv; + } + + int32_t remotePort; + rv = httpChannelInternal->GetRemotePort(&remotePort); + if (NS_FAILED(rv)) { + CSFLogError(LOGTAG, "%s: Failed to get remote port number: %d", + __FUNCTION__, (int)rv); + return rv; + } + + mTransportHandler->SetTargetForDefaultLocalAddressLookup(remoteIp.get(), + remotePort); + + return NS_OK; +} + +void PeerConnectionImpl::EnsureIceGathering(bool aDefaultRouteOnly, + bool aObfuscateHostAddresses) { + if (!mTargetForDefaultLocalAddressLookupIsSet) { + nsresult rv = SetTargetForDefaultLocalAddressLookup(); + if (NS_FAILED(rv)) { + NS_WARNING("Unable to set target for default local address lookup"); + } + mTargetForDefaultLocalAddressLookupIsSet = true; + } + + // Make sure we don't call StartIceGathering if we're in e10s mode + // and we received no STUN addresses from the parent process. In the + // absence of previously provided STUN addresses, StartIceGathering will + // attempt to gather them (as in non-e10s mode), and this will cause a + // sandboxing exception in e10s mode. + if (!mStunAddrs.Length() && XRE_IsContentProcess()) { + CSFLogInfo(LOGTAG, "%s: No STUN addresses returned from parent process", + __FUNCTION__); + return; + } + + mTransportHandler->StartIceGathering(aDefaultRouteOnly, + aObfuscateHostAddresses, mStunAddrs); +} + +already_AddRefed PeerConnectionImpl::CreateTransceiver( + const std::string& aId, bool aIsVideo, const RTCRtpTransceiverInit& aInit, + dom::MediaStreamTrack* aSendTrack, bool aAddTrackMagic, ErrorResult& aRv) { + PeerConnectionCtx* ctx = PeerConnectionCtx::GetInstance(); + if (!mCall) { + mCall = WebrtcCallWrapper::Create( + GetTimestampMaker(), + media::ShutdownBlockingTicket::Create( + u"WebrtcCallWrapper shutdown blocker"_ns, + NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__), + ctx->GetSharedWebrtcState()); + } + + if (aAddTrackMagic) { + mJsepSession->ApplyToTransceiver(aId, [](JsepTransceiver& aTransceiver) { + aTransceiver.SetAddTrackMagic(); + }); + } + + RefPtr transceiver = new RTCRtpTransceiver( + mWindow, PrivacyRequested(), this, mTransportHandler, mJsepSession.get(), + aId, aIsVideo, mSTSThread.get(), aSendTrack, mCall.get(), mIdGenerator); + + transceiver->Init(aInit, aRv); + if (aRv.Failed()) { + return nullptr; + } + + if (aSendTrack) { + // implement checking for peerIdentity (where failure == black/silence) + Document* doc = mWindow->GetExtantDoc(); + if (doc) { + transceiver->Sender()->GetPipeline()->UpdateSinkIdentity( + doc->NodePrincipal(), GetPeerIdentity()); + } else { + MOZ_CRASH(); + aRv = NS_ERROR_FAILURE; + return nullptr; // Don't remove this till we know it's safe. + } + } + + return transceiver.forget(); +} + +std::string PeerConnectionImpl::GetTransportIdMatchingSendTrack( + const dom::MediaStreamTrack& aTrack) const { + for (const RefPtr& transceiver : mTransceivers) { + if (transceiver->Sender()->HasTrack(&aTrack)) { + return transceiver->GetTransportId(); + } + } + return std::string(); +} + +void PeerConnectionImpl::SignalHandler::IceGatheringStateChange_s( + dom::RTCIceGatheringState aState) { + ASSERT_ON_THREAD(mSTSThread); + + GetMainThreadSerialEventTarget()->Dispatch( + NS_NewRunnableFunction(__func__, + [handle = mHandle, aState] { + PeerConnectionWrapper wrapper(handle); + if (wrapper.impl()) { + wrapper.impl()->IceGatheringStateChange( + aState); + } + }), + NS_DISPATCH_NORMAL); +} + +void PeerConnectionImpl::SignalHandler::IceConnectionStateChange_s( + dom::RTCIceConnectionState aState) { + ASSERT_ON_THREAD(mSTSThread); + + GetMainThreadSerialEventTarget()->Dispatch( + NS_NewRunnableFunction(__func__, + [handle = mHandle, aState] { + PeerConnectionWrapper wrapper(handle); + if (wrapper.impl()) { + wrapper.impl()->IceConnectionStateChange( + aState); + } + }), + NS_DISPATCH_NORMAL); +} + +void PeerConnectionImpl::SignalHandler::OnCandidateFound_s( + const std::string& aTransportId, const CandidateInfo& aCandidateInfo) { + ASSERT_ON_THREAD(mSTSThread); + CSFLogDebug(LOGTAG, "%s: %s", __FUNCTION__, aTransportId.c_str()); + + MOZ_ASSERT(!aCandidateInfo.mUfrag.empty()); + + GetMainThreadSerialEventTarget()->Dispatch( + NS_NewRunnableFunction(__func__, + [handle = mHandle, aTransportId, aCandidateInfo] { + PeerConnectionWrapper wrapper(handle); + if (wrapper.impl()) { + wrapper.impl()->OnCandidateFound( + aTransportId, aCandidateInfo); + } + }), + NS_DISPATCH_NORMAL); +} + +void PeerConnectionImpl::SignalHandler::AlpnNegotiated_s( + const std::string& aAlpn, bool aPrivacyRequested) { + MOZ_DIAGNOSTIC_ASSERT((aAlpn == "c-webrtc") == aPrivacyRequested); + GetMainThreadSerialEventTarget()->Dispatch( + NS_NewRunnableFunction(__func__, + [handle = mHandle, aPrivacyRequested] { + PeerConnectionWrapper wrapper(handle); + if (wrapper.impl()) { + wrapper.impl()->OnAlpnNegotiated( + aPrivacyRequested); + } + }), + NS_DISPATCH_NORMAL); +} + +void PeerConnectionImpl::SignalHandler::ConnectionStateChange_s( + const std::string& aTransportId, TransportLayer::State aState) { + GetMainThreadSerialEventTarget()->Dispatch( + NS_NewRunnableFunction(__func__, + [handle = mHandle, aTransportId, aState] { + PeerConnectionWrapper wrapper(handle); + if (wrapper.impl()) { + wrapper.impl()->OnDtlsStateChange(aTransportId, + aState); + } + }), + NS_DISPATCH_NORMAL); +} + +/** + * Tells you if any local track is isolated to a specific peer identity. + * Obviously, we want all the tracks to be isolated equally so that they can + * all be sent or not. We check once when we are setting a local description + * and that determines if we flip the "privacy requested" bit on. Once the bit + * is on, all media originating from this peer connection is isolated. + * + * @returns true if any track has a peerIdentity set on it + */ +bool PeerConnectionImpl::AnyLocalTrackHasPeerIdentity() const { + MOZ_ASSERT(NS_IsMainThread()); + + for (const RefPtr& transceiver : mTransceivers) { + if (transceiver->Sender()->GetTrack() && + transceiver->Sender()->GetTrack()->GetPeerIdentity()) { + return true; + } + } + return false; +} + +bool PeerConnectionImpl::AnyCodecHasPluginID(uint64_t aPluginID) { + for (RefPtr& transceiver : mTransceivers) { + if (transceiver->ConduitHasPluginID(aPluginID)) { + return true; + } + } + return false; +} + +std::unique_ptr PeerConnectionImpl::GetProxyConfig() + const { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mForceProxy && + Preferences::GetBool("media.peerconnection.disable_http_proxy", false)) { + return nullptr; + } + + nsCString alpn = "webrtc,c-webrtc"_ns; + auto* browserChild = BrowserChild::GetFrom(mWindow); + if (!browserChild) { + // Android doesn't have browser child apparently... + return nullptr; + } + + Document* doc = mWindow->GetExtantDoc(); + if (NS_WARN_IF(!doc)) { + NS_WARNING("Unable to get document from window"); + return nullptr; + } + + TabId id = browserChild->GetTabId(); + nsCOMPtr loadInfo = + new net::LoadInfo(doc->NodePrincipal(), doc->NodePrincipal(), doc, 0, + nsIContentPolicy::TYPE_INVALID); + + Maybe loadInfoArgs; + MOZ_ALWAYS_SUCCEEDS( + mozilla::ipc::LoadInfoToLoadInfoArgs(loadInfo, &loadInfoArgs)); + return std::unique_ptr(new NrSocketProxyConfig( + net::WebrtcProxyConfig(id, alpn, *loadInfoArgs, mForceProxy))); +} + +std::map + PeerConnectionImpl::sCallDurationTimers; +} // namespace mozilla diff --git a/dom/media/webrtc/jsapi/PeerConnectionImpl.h b/dom/media/webrtc/jsapi/PeerConnectionImpl.h new file mode 100644 index 0000000000..66af1aa0e9 --- /dev/null +++ b/dom/media/webrtc/jsapi/PeerConnectionImpl.h @@ -0,0 +1,969 @@ +/* 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/. */ + +#ifndef _PEER_CONNECTION_IMPL_H_ +#define _PEER_CONNECTION_IMPL_H_ + +#include +#include +#include +#include + +#include "prlock.h" +#include "mozilla/RefPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsPIDOMWindow.h" +#include "nsIUUIDGenerator.h" +#include "nsIThread.h" +#include "mozilla/Mutex.h" +#include "mozilla/Attributes.h" + +// Work around nasty macro in webrtc/voice_engine/voice_engine_defines.h +#ifdef GetLastError +# undef GetLastError +#endif + +#include "jsep/JsepSession.h" +#include "jsep/JsepSessionImpl.h" +#include "sdp/SdpMediaSection.h" + +#include "mozilla/ErrorResult.h" +#include "jsapi/PacketDumper.h" +#include "mozilla/dom/RTCPeerConnectionBinding.h" // mozPacketDumpType, maybe move? +#include "mozilla/dom/PeerConnectionImplBinding.h" // ChainedOperation +#include "mozilla/dom/RTCRtpCapabilitiesBinding.h" +#include "mozilla/dom/RTCRtpTransceiverBinding.h" +#include "mozilla/dom/RTCConfigurationBinding.h" +#include "PrincipalChangeObserver.h" +#include "mozilla/dom/PromiseNativeHandler.h" + +#include "mozilla/TimeStamp.h" +#include "mozilla/net/DataChannel.h" +#include "VideoUtils.h" +#include "VideoSegment.h" +#include "mozilla/dom/RTCStatsReportBinding.h" +#include "mozilla/PeerIdentity.h" +#include "RTCStatsIdGenerator.h" +#include "RTCStatsReport.h" + +#include "mozilla/net/StunAddrsRequestChild.h" +#include "MediaTransportHandler.h" +#include "nsIHttpChannelInternal.h" +#include "RTCDtlsTransport.h" +#include "RTCRtpTransceiver.h" + +namespace test { +#ifdef USE_FAKE_PCOBSERVER +class AFakePCObserver; +#endif +} // namespace test + +class nsDOMDataChannel; +class nsIPrincipal; + +namespace mozilla { +struct CandidateInfo; +class DataChannel; +class DtlsIdentity; +class MediaPipeline; +class MediaPipelineReceive; +class MediaPipelineTransmit; +enum class PrincipalPrivacy : uint8_t; +class SharedWebrtcState; + +namespace dom { +class RTCCertificate; +struct RTCConfiguration; +struct RTCRtpSourceEntry; +struct RTCIceServer; +struct RTCOfferOptions; +struct RTCRtpParameters; +class RTCRtpSender; +class MediaStreamTrack; + +#ifdef USE_FAKE_PCOBSERVER +typedef test::AFakePCObserver PeerConnectionObserver; +typedef const char* PCObserverString; +#else +class PeerConnectionObserver; +typedef NS_ConvertUTF8toUTF16 PCObserverString; +#endif +} // namespace dom +} // namespace mozilla + +#if defined(__cplusplus) && __cplusplus >= 201103L +typedef struct Timecard Timecard; +#else +# include "common/time_profiling/timecard.h" +#endif + +// To preserve blame, convert nsresult to ErrorResult with wrappers. These +// macros help declare wrappers w/function being wrapped when there are no +// differences. + +#define NS_IMETHODIMP_TO_ERRORRESULT(func, rv, ...) \ + NS_IMETHODIMP func(__VA_ARGS__); \ + void func(__VA_ARGS__, rv) + +#define NS_IMETHODIMP_TO_ERRORRESULT_RETREF(resulttype, func, rv, ...) \ + NS_IMETHODIMP func(__VA_ARGS__, resulttype** result); \ + already_AddRefed func(__VA_ARGS__, rv) + +namespace mozilla { + +using mozilla::DtlsIdentity; +using mozilla::ErrorResult; +using mozilla::PeerIdentity; +using mozilla::dom::PeerConnectionObserver; +using mozilla::dom::RTCConfiguration; +using mozilla::dom::RTCIceServer; +using mozilla::dom::RTCOfferOptions; + +class PeerConnectionWrapper; +class RemoteSourceStreamInfo; + +// Uuid Generator +class PCUuidGenerator : public mozilla::JsepUuidGenerator { + public: + virtual bool Generate(std::string* idp) override; + virtual mozilla::JsepUuidGenerator* Clone() const override { + return new PCUuidGenerator(*this); + } + + private: + nsCOMPtr mGenerator; +}; + +// This is a variation of Telemetry::AutoTimer that keeps a reference +// count and records the elapsed time when the count falls to zero. The +// elapsed time is recorded in seconds. +struct PeerConnectionAutoTimer { + PeerConnectionAutoTimer() + : mRefCnt(0), mStart(TimeStamp::Now()), mUsedAV(false){}; + void RegisterConnection(); + void UnregisterConnection(bool aContainedAV); + bool IsStopped(); + + private: + int64_t mRefCnt; + TimeStamp mStart; + bool mUsedAV; +}; + +// Enter an API call and check that the state is OK, +// the PC isn't closed, etc. +#define PC_AUTO_ENTER_API_CALL(assert_ice_ready) \ + do { \ + /* do/while prevents res from conflicting with locals */ \ + nsresult res = CheckApiState(assert_ice_ready); \ + if (NS_FAILED(res)) return res; \ + } while (0) +#define PC_AUTO_ENTER_API_CALL_VOID_RETURN(assert_ice_ready) \ + do { \ + /* do/while prevents res from conflicting with locals */ \ + nsresult res = CheckApiState(assert_ice_ready); \ + if (NS_FAILED(res)) return; \ + } while (0) +#define PC_AUTO_ENTER_API_CALL_NO_CHECK() CheckThread() + +class PeerConnectionImpl final + : public nsISupports, + public nsWrapperCache, + public mozilla::DataChannelConnection::DataConnectionListener { + struct Internal; // Avoid exposing c includes to bindings + + public: + explicit PeerConnectionImpl( + const mozilla::dom::GlobalObject* aGlobal = nullptr); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(PeerConnectionImpl) + + struct RtpExtensionHeader { + JsepMediaType mMediaType; + SdpDirectionAttribute::Direction direction; + std::string extensionname; + }; + + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + nsPIDOMWindowInner* GetParentObject() const; + + static already_AddRefed Constructor( + const mozilla::dom::GlobalObject& aGlobal); + + // DataConnection observers + void NotifyDataChannel(already_AddRefed aChannel) + // PeerConnectionImpl only inherits from mozilla::DataChannelConnection + // inside libxul. + override; + + void NotifyDataChannelOpen(DataChannel*) override; + + void NotifyDataChannelClosed(DataChannel*) override; + + void NotifySctpConnected() override; + + void NotifySctpClosed() override; + + const RefPtr GetTransportHandler() const; + + // Handle system to allow weak references to be passed through C code + virtual const std::string& GetHandle(); + + // Name suitable for exposing to content + virtual const std::string& GetName(); + + // ICE events + void IceConnectionStateChange(dom::RTCIceConnectionState state); + void IceGatheringStateChange(dom::RTCIceGatheringState state); + void OnCandidateFound(const std::string& aTransportId, + const CandidateInfo& aCandidateInfo); + void UpdateDefaultCandidate(const std::string& defaultAddr, + uint16_t defaultPort, + const std::string& defaultRtcpAddr, + uint16_t defaultRtcpPort, + const std::string& transportId); + + static void ListenThread(void* aData); + static void ConnectThread(void* aData); + + // Get the STS thread + nsISerialEventTarget* GetSTSThread() { + PC_AUTO_ENTER_API_CALL_NO_CHECK(); + return mSTSThread; + } + + nsresult Initialize(PeerConnectionObserver& aObserver, + nsGlobalWindowInner* aWindow); + + // Initialize PeerConnection from an RTCConfiguration object (JS entrypoint) + void Initialize(PeerConnectionObserver& aObserver, + nsGlobalWindowInner& aWindow, ErrorResult& rv); + + void SetCertificate(mozilla::dom::RTCCertificate& aCertificate); + const RefPtr& Certificate() const; + // This is a hack to support external linkage. + RefPtr Identity() const; + + NS_IMETHODIMP_TO_ERRORRESULT(CreateOffer, ErrorResult& rv, + const RTCOfferOptions& aOptions) { + rv = CreateOffer(aOptions); + } + + NS_IMETHODIMP CreateAnswer(); + void CreateAnswer(ErrorResult& rv) { rv = CreateAnswer(); } + + NS_IMETHODIMP CreateOffer(const mozilla::JsepOfferOptions& aConstraints); + + NS_IMETHODIMP SetLocalDescription(int32_t aAction, const char* aSDP); + + void SetLocalDescription(int32_t aAction, const nsAString& aSDP, + ErrorResult& rv) { + rv = SetLocalDescription(aAction, NS_ConvertUTF16toUTF8(aSDP).get()); + } + + NS_IMETHODIMP SetRemoteDescription(int32_t aAction, const char* aSDP); + + void SetRemoteDescription(int32_t aAction, const nsAString& aSDP, + ErrorResult& rv) { + rv = SetRemoteDescription(aAction, NS_ConvertUTF16toUTF8(aSDP).get()); + } + + already_AddRefed GetStats(dom::MediaStreamTrack* aSelector); + + void GetRemoteStreams(nsTArray>& aStreamsOut) const; + + NS_IMETHODIMP AddIceCandidate(const char* aCandidate, const char* aMid, + const char* aUfrag, + const dom::Nullable& aLevel); + + void AddIceCandidate(const nsAString& aCandidate, const nsAString& aMid, + const nsAString& aUfrag, + const dom::Nullable& aLevel, + ErrorResult& rv) { + rv = AddIceCandidate(NS_ConvertUTF16toUTF8(aCandidate).get(), + NS_ConvertUTF16toUTF8(aMid).get(), + NS_ConvertUTF16toUTF8(aUfrag).get(), aLevel); + } + + void UpdateNetworkState(bool online); + + NS_IMETHODIMP CloseStreams(); + + void CloseStreams(ErrorResult& rv) { rv = CloseStreams(); } + + already_AddRefed AddTransceiver( + const dom::RTCRtpTransceiverInit& aInit, const nsAString& aKind, + dom::MediaStreamTrack* aSendTrack, bool aAddTrackMagic, ErrorResult& aRv); + + bool CheckNegotiationNeeded(); + bool CreatedSender(const dom::RTCRtpSender& aSender) const; + + // test-only + NS_IMETHODIMP_TO_ERRORRESULT(EnablePacketDump, ErrorResult& rv, + unsigned long level, dom::mozPacketDumpType type, + bool sending) { + rv = EnablePacketDump(level, type, sending); + } + + // test-only + NS_IMETHODIMP_TO_ERRORRESULT(DisablePacketDump, ErrorResult& rv, + unsigned long level, dom::mozPacketDumpType type, + bool sending) { + rv = DisablePacketDump(level, type, sending); + } + + void GetPeerIdentity(nsAString& peerIdentity) { + if (mPeerIdentity) { + peerIdentity = mPeerIdentity->ToString(); + return; + } + + peerIdentity.SetIsVoid(true); + } + + const PeerIdentity* GetPeerIdentity() const { return mPeerIdentity; } + NS_IMETHODIMP_TO_ERRORRESULT(SetPeerIdentity, ErrorResult& rv, + const nsAString& peerIdentity) { + rv = SetPeerIdentity(peerIdentity); + } + + const std::string& GetIdAsAscii() const { return mName; } + + void GetId(nsAString& id) { id = NS_ConvertASCIItoUTF16(mName.c_str()); } + + void SetId(const nsAString& id) { mName = NS_ConvertUTF16toUTF8(id).get(); } + + // this method checks to see if we've made a promise to protect media. + bool PrivacyRequested() const { + return mRequestedPrivacy.valueOr(PrincipalPrivacy::NonPrivate) == + PrincipalPrivacy::Private; + } + + NS_IMETHODIMP GetFingerprint(char** fingerprint); + void GetFingerprint(nsAString& fingerprint) { + char* tmp; + nsresult rv = GetFingerprint(&tmp); + NS_ENSURE_SUCCESS_VOID(rv); + fingerprint.AssignASCII(tmp); + delete[] tmp; + } + + void GetCurrentLocalDescription(nsAString& aSDP) const; + void GetPendingLocalDescription(nsAString& aSDP) const; + + void GetCurrentRemoteDescription(nsAString& aSDP) const; + void GetPendingRemoteDescription(nsAString& aSDP) const; + + dom::Nullable GetCurrentOfferer() const; + dom::Nullable GetPendingOfferer() const; + + NS_IMETHODIMP SignalingState(mozilla::dom::RTCSignalingState* aState); + + mozilla::dom::RTCSignalingState SignalingState() { + mozilla::dom::RTCSignalingState state; + SignalingState(&state); + return state; + } + + NS_IMETHODIMP IceConnectionState(mozilla::dom::RTCIceConnectionState* aState); + + mozilla::dom::RTCIceConnectionState IceConnectionState() { + mozilla::dom::RTCIceConnectionState state; + IceConnectionState(&state); + return state; + } + + NS_IMETHODIMP IceGatheringState(mozilla::dom::RTCIceGatheringState* aState); + + mozilla::dom::RTCIceGatheringState IceGatheringState() { + return mIceGatheringState; + } + + NS_IMETHODIMP ConnectionState(mozilla::dom::RTCPeerConnectionState* aState); + + mozilla::dom::RTCPeerConnectionState ConnectionState() { + mozilla::dom::RTCPeerConnectionState state; + ConnectionState(&state); + return state; + } + + NS_IMETHODIMP Close(); + + void Close(ErrorResult& rv) { rv = Close(); } + + // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230) + MOZ_CAN_RUN_SCRIPT_BOUNDARY bool PluginCrash(uint32_t aPluginID, + const nsAString& aPluginName); + + NS_IMETHODIMP_TO_ERRORRESULT(SetConfiguration, ErrorResult& rv, + const RTCConfiguration& aConfiguration) { + rv = SetConfiguration(aConfiguration); + } + + dom::RTCSctpTransport* GetSctp() const; + + void RestartIce(); + void RestartIceNoRenegotiationNeeded(); + + void RecordEndOfCallTelemetry(); + + nsresult InitializeDataChannel(); + + NS_IMETHODIMP_TO_ERRORRESULT_RETREF(nsDOMDataChannel, CreateDataChannel, + ErrorResult& rv, const nsAString& aLabel, + const nsAString& aProtocol, + uint16_t aType, bool outOfOrderAllowed, + uint16_t aMaxTime, uint16_t aMaxNum, + bool aExternalNegotiated, + uint16_t aStream); + + // Base class for chained operations. Necessary right now because some + // operations come from JS (in the form of dom::ChainedOperation), and others + // come from c++ (dom::ChainedOperation is very unwieldy and arcane to build + // in c++). Once we stop using JSImpl, we should be able to simplify this. + class Operation : public dom::PromiseNativeHandler { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(Operation) + Operation(PeerConnectionImpl* aPc, ErrorResult& aError); + MOZ_CAN_RUN_SCRIPT + void Call(ErrorResult& aError); + dom::Promise* GetPromise() { return mPromise; } + MOZ_CAN_RUN_SCRIPT + void ResolvedCallback(JSContext* aCx, JS::Handle aValue, + ErrorResult& aRv) override; + + MOZ_CAN_RUN_SCRIPT + void RejectedCallback(JSContext* aCx, JS::Handle aValue, + ErrorResult& aRv) override; + + protected: + MOZ_CAN_RUN_SCRIPT + virtual RefPtr CallImpl(ErrorResult& aError) = 0; + virtual ~Operation(); + // This is the promise p from https://w3c.github.io/webrtc-pc/#dfn-chain + // This will be a content promise, since we return this to the caller of + // Chain. + RefPtr mPromise; + RefPtr mPc; + }; + + class JSOperation final : public Operation { + public: + JSOperation(PeerConnectionImpl* aPc, dom::ChainedOperation& aOp, + ErrorResult& aError); + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(JSOperation, Operation) + + private: + MOZ_CAN_RUN_SCRIPT + RefPtr CallImpl(ErrorResult& aError) override; + ~JSOperation() = default; + RefPtr mOperation; + }; + + MOZ_CAN_RUN_SCRIPT + already_AddRefed Chain(dom::ChainedOperation& aOperation, + ErrorResult& aError); + MOZ_CAN_RUN_SCRIPT + already_AddRefed Chain(const RefPtr& aOperation, + ErrorResult& aError); + already_AddRefed MakePromise(ErrorResult& aError) const; + + void UpdateNegotiationNeeded(); + + void GetTransceivers( + nsTArray>& aTransceiversOut) { + aTransceiversOut = mTransceivers.Clone(); + } + + // Gets the RTC Signaling State of the JSEP session + dom::RTCSignalingState GetSignalingState() const; + + already_AddRefed OnSetDescriptionSuccess( + dom::RTCSdpType aSdpType, bool aRemote, ErrorResult& aError); + + void OnSetDescriptionError(); + + bool IsClosed() const; + + // called when DTLS connects; we only need this once + nsresult OnAlpnNegotiated(bool aPrivacyRequested); + + void OnDtlsStateChange(const std::string& aTransportId, + TransportLayer::State aState); + void UpdateConnectionState(); + dom::RTCPeerConnectionState GetNewConnectionState() const; + + // initialize telemetry for when calls start + void StartCallTelem(); + + // Gets all codec stats for all transports, coalesced to transport level. + nsTArray GetCodecStats(DOMHighResTimeStamp aNow); + + RefPtr GetStats(dom::MediaStreamTrack* aSelector, + bool aInternalStats); + + void CollectConduitTelemetryData(); + + void OnMediaError(const std::string& aError); + + void DumpPacket_m(size_t level, dom::mozPacketDumpType type, bool sending, + UniquePtr& packet, size_t size); + + const dom::RTCStatsTimestampMaker& GetTimestampMaker() const { + return mTimestampMaker; + } + + // Utility function, given a string pref and an URI, returns whether or not + // the URI occurs in the pref. Wildcards are supported (e.g. *.example.com) + // and multiple hostnames can be present, separated by commas. + static bool HostnameInPref(const char* aPrefList, const nsCString& aHostName); + + void StampTimecard(const char* aEvent); + + bool RelayOnly() const { + return mJsConfiguration.mIceTransportPolicy.WasPassed() && + mJsConfiguration.mIceTransportPolicy.Value() == + dom::RTCIceTransportPolicy::Relay; + } + + RefPtr GetPacketDumper() { + if (!mPacketDumper) { + mPacketDumper = new PacketDumper(mHandle); + } + + return mPacketDumper; + } + + nsString GenerateUUID() const { + std::string result; + if (!mUuidGen->Generate(&result)) { + MOZ_CRASH(); + } + return NS_ConvertUTF8toUTF16(result.c_str()); + } + + bool ShouldAllowOldSetParameters() const { return mAllowOldSetParameters; } + + nsCString GetHostname() const { return mHostname; } + nsCString GetEffectiveTLDPlus1() const { return mEffectiveTLDPlus1; } + + void SendWarningToConsole(const nsCString& aWarning); + + const UniquePtr& GetFinalStats() const { + return mFinalStats; + } + + void DisableLongTermStats() { mDisableLongTermStats = true; } + + bool LongTermStatsIsDisabled() const { return mDisableLongTermStats; } + + static void GetDefaultVideoCodecs( + std::vector>& aSupportedCodecs, + bool aUseRtx); + + static void GetDefaultAudioCodecs( + std::vector>& aSupportedCodecs); + + static void GetDefaultRtpExtensions( + std::vector& aRtpExtensions); + + static void GetCapabilities(const nsAString& aKind, + dom::Nullable& aResult, + sdp::Direction aDirection); + static void SetupPreferredCodecs( + std::vector>& aPreferredCodecs); + + static void SetupPreferredRtpExtensions( + std::vector& aPreferredheaders); + + private: + virtual ~PeerConnectionImpl(); + PeerConnectionImpl(const PeerConnectionImpl& rhs); + PeerConnectionImpl& operator=(PeerConnectionImpl); + + RefPtr GetDataChannelStats( + const RefPtr& aDataChannelConnection, + const DOMHighResTimeStamp aTimestamp); + nsresult CalculateFingerprint(const std::string& algorithm, + std::vector* fingerprint) const; + nsresult ConfigureJsepSessionCodecs(); + + NS_IMETHODIMP EnsureDataConnection(uint16_t aLocalPort, uint16_t aNumstreams, + uint32_t aMaxMessageSize, bool aMMSSet); + + nsresult CheckApiState(bool assert_ice_ready) const; + void StoreFinalStats(UniquePtr&& report); + void CheckThread() const { MOZ_ASSERT(NS_IsMainThread(), "Wrong thread"); } + + // test-only: called from AddRIDExtension and AddRIDFilter + // for simulcast mochitests. + RefPtr GetMediaPipelineForTrack( + dom::MediaStreamTrack& aRecvTrack); + + void CandidateReady(const std::string& candidate, + const std::string& transportId, const std::string& ufrag); + void SendLocalIceCandidateToContent(uint16_t level, const std::string& mid, + const std::string& candidate, + const std::string& ufrag); + + nsresult GetDatachannelParameters(uint32_t* channels, uint16_t* localport, + uint16_t* remoteport, + uint32_t* maxmessagesize, bool* mmsset, + std::string* transportId, + bool* client) const; + + nsresult AddRtpTransceiverToJsepSession(JsepTransceiver& transceiver); + + void RecordIceRestartStatistics(JsepSdpType type); + + void StoreConfigurationForAboutWebrtc(const RTCConfiguration& aConfig); + + dom::Sequence GetLastSdpParsingErrors() + const; + + MOZ_CAN_RUN_SCRIPT + void RunNextOperation(ErrorResult& aError); + + void SyncToJsep(); + void SyncFromJsep(); + + void DoSetDescriptionSuccessPostProcessing(dom::RTCSdpType aSdpType, + bool aRemote, + const RefPtr& aP); + + // Timecard used to measure processing time. This should be the first class + // attribute so that we accurately measure the time required to instantiate + // any other attributes of this class. + Timecard* mTimeCard; + + // Configuration used to initialize the PeerConnection + dom::RTCConfigurationInternal mJsConfiguration; + + mozilla::dom::RTCSignalingState mSignalingState; + + // ICE State + mozilla::dom::RTCIceConnectionState mIceConnectionState; + mozilla::dom::RTCIceGatheringState mIceGatheringState; + + mozilla::dom::RTCPeerConnectionState mConnectionState; + + RefPtr mPCObserver; + + nsCOMPtr mWindow; + + // The SDP sent in from JS + std::string mLocalRequestedSDP; + std::string mRemoteRequestedSDP; + // Only accessed from main + mozilla::dom::Sequence mSdpHistory; + std::string mPendingLocalDescription; + std::string mPendingRemoteDescription; + std::string mCurrentLocalDescription; + std::string mCurrentRemoteDescription; + Maybe mPendingOfferer; + Maybe mCurrentOfferer; + + // DTLS fingerprint + std::string mFingerprint; + std::string mRemoteFingerprint; + + // identity-related fields + // The entity on the other end of the peer-to-peer connection; + // void if they are not yet identified, and no identity setting has been set + RefPtr mPeerIdentity; + // The certificate we are using. + RefPtr mCertificate; + // Whether an app should be prevented from accessing media produced by the PC + // If this is true, then media will not be sent until mPeerIdentity matches + // local streams PeerIdentity; and remote streams are protected from content + // + // This can be false if mPeerIdentity is set, in the case where identity is + // provided, but the media is not protected from the app on either side + Maybe mRequestedPrivacy; + + // A handle to refer to this PC with + std::string mHandle; + + // A name for this PC that we are willing to expose to content. + std::string mName; + nsCString mHostname; + nsCString mEffectiveTLDPlus1; + + // The target to run stuff on + nsCOMPtr mSTSThread; + + // DataConnection that's used to get all the DataChannels + RefPtr mDataConnection; + unsigned int mDataChannelsOpened = 0; + unsigned int mDataChannelsClosed = 0; + + bool mForceIceTcp; + RefPtr mTransportHandler; + + // The JSEP negotiation session. + mozilla::UniquePtr mUuidGen; + mozilla::UniquePtr mJsepSession; + // There are lots of error cases where we want to abandon an sRD/sLD _after_ + // it has already been applied to the JSEP engine, and revert back to the + // previous state. We also want to ensure that the various modifications + // to the JSEP engine are not exposed to JS until the sRD/sLD completes, + // which is why we have a new "uncommitted" JSEP engine. + mozilla::UniquePtr mUncommittedJsepSession; + unsigned long mIceRestartCount; + unsigned long mIceRollbackCount; + + // The following are used for Telemetry: + bool mCallTelemStarted = false; + bool mCallTelemEnded = false; + + // We _could_ make mFinalStatsQuery be an RTCStatsReportPromise, but that + // would require RTCStatsReportPromise to no longer be exclusive, which is + // a bit of a hassle, and not very performant. + RefPtr mFinalStatsQuery; + UniquePtr mFinalStats; + bool mDisableLongTermStats = false; + + // Start time of ICE. + mozilla::TimeStamp mIceStartTime; + // Hold PeerConnectionAutoTimer instances for each window. + static std::map sCallDurationTimers; + + bool mHaveConfiguredCodecs; + + bool mTrickle; + + bool mPrivateWindow; + + // Whether this PeerConnection is being counted as active by mWindow + bool mActiveOnWindow; + + // storage for Telemetry data + uint16_t mMaxReceiving[SdpMediaSection::kMediaTypes]; + uint16_t mMaxSending[SdpMediaSection::kMediaTypes]; + + // used to store the raw trickle candidate string for display + // on the about:webrtc raw candidates table. + std::vector mRawTrickledCandidates; + + dom::RTCStatsTimestampMaker mTimestampMaker; + + RefPtr mIdGenerator; + // Ordinarily, I would use a std::map here, but this used to be a JS Map + // which iterates in insertion order, and I want to avoid changing this. + nsTArray> mReceiveStreams; + + DOMMediaStream* GetReceiveStream(const std::string& aId) const; + DOMMediaStream* CreateReceiveStream(const std::string& aId); + + void InitLocalAddrs(); // for stun local address IPC request + bool ShouldForceProxy() const; + std::unique_ptr GetProxyConfig() const; + + class StunAddrsHandler : public net::StunAddrsListener { + public: + explicit StunAddrsHandler(PeerConnectionImpl* aPc) + : mPcHandle(aPc->GetHandle()) {} + + void OnMDNSQueryComplete(const nsCString& hostname, + const Maybe& address) override; + + void OnStunAddrsAvailable( + const mozilla::net::NrIceStunAddrArray& addrs) override; + + private: + // This class is not cycle-collected, so we must avoid grabbing a strong + // reference. + const std::string mPcHandle; + virtual ~StunAddrsHandler() {} + }; + + // Manage ICE transports. + void UpdateTransport(const JsepTransceiver& aTransceiver, bool aForceIceTcp); + + void GatherIfReady(); + void FlushIceCtxOperationQueueIfReady(); + void PerformOrEnqueueIceCtxOperation(nsIRunnable* runnable); + nsresult SetTargetForDefaultLocalAddressLookup(); + void EnsureIceGathering(bool aDefaultRouteOnly, bool aObfuscateHostAddresses); + + bool GetPrefDefaultAddressOnly() const; + bool GetPrefObfuscateHostAddresses() const; + + bool IsIceCtxReady() const { + return mLocalAddrsRequestState == STUN_ADDR_REQUEST_COMPLETE; + } + + // Ensure ICE transports exist that we might need when offer/answer concludes + void EnsureTransports(const JsepSession& aSession); + + void UpdateRTCDtlsTransports(bool aMarkAsStable); + void RollbackRTCDtlsTransports(); + void RemoveRTCDtlsTransportsExcept( + const std::set& aTransportIds); + + // Activate ICE transports at the conclusion of offer/answer, + // or when rollback occurs. + nsresult UpdateTransports(const JsepSession& aSession, + const bool forceIceTcp); + + void ResetStunAddrsForIceRestart() { mStunAddrs.Clear(); } + + // Start ICE checks. + void StartIceChecks(const JsepSession& session); + + // Process a trickle ICE candidate. + void AddIceCandidate(const std::string& candidate, + const std::string& aTransportId, + const std::string& aUFrag); + + // Handle complete media pipelines. + // This updates codec parameters, starts/stops send/receive, and other + // stuff that doesn't necessarily require negotiation. This can be called at + // any time, not just when an offer/answer exchange completes. + nsresult UpdateMediaPipelines(); + + already_AddRefed CreateTransceiver( + const std::string& aId, bool aIsVideo, + const dom::RTCRtpTransceiverInit& aInit, + dom::MediaStreamTrack* aSendTrack, bool aAddTrackMagic, ErrorResult& aRv); + + std::string GetTransportIdMatchingSendTrack( + const dom::MediaStreamTrack& aTrack) const; + + // this determines if any track is peerIdentity constrained + bool AnyLocalTrackHasPeerIdentity() const; + + bool AnyCodecHasPluginID(uint64_t aPluginID); + + already_AddRefed GetChannel() const; + + void BreakCycles(); + + bool HasPendingSetParameters() const; + void InvalidateLastReturnedParameters(); + + RefPtr mCall; + + // See Bug 1642419, this can be removed when all sites are working with RTX. + bool mRtxIsAllowed = true; + + nsTArray> mOperations; + bool mChainingOperation = false; + bool mUpdateNegotiationNeededFlagOnEmptyChain = false; + bool mNegotiationNeeded = false; + std::set> mLocalIceCredentialsToReplace; + + nsTArray> mTransceivers; + std::map> + mTransportIdToRTCDtlsTransport; + RefPtr mSctpTransport; + + // Used whenever we need to dispatch a runnable to STS to tweak something + // on our ICE ctx, but are not ready to do so at the moment (eg; we are + // waiting to get a callback with our http proxy config before we start + // gathering or start checking) + std::vector> mQueuedIceCtxOperations; + + // Set if prefs dictate that we should force the use of a web proxy. + bool mForceProxy = false; + + // Used to cancel incoming stun addrs response + RefPtr mStunAddrsRequest; + + enum StunAddrRequestState { + STUN_ADDR_REQUEST_NONE, + STUN_ADDR_REQUEST_PENDING, + STUN_ADDR_REQUEST_COMPLETE + }; + // Used to track the state of the stun addr IPC request + StunAddrRequestState mLocalAddrsRequestState = STUN_ADDR_REQUEST_NONE; + + // Used to store the result of the stun addr IPC request + nsTArray mStunAddrs; + + // Used to ensure the target for default local address lookup is only set + // once. + bool mTargetForDefaultLocalAddressLookupIsSet = false; + + // Keep track of local hostnames to register. Registration is deferred + // until StartIceChecks has run. Accessed on main thread only. + std::map mMDNSHostnamesToRegister; + bool mCanRegisterMDNSHostnamesDirectly = false; + + // Used to store the mDNS hostnames that we have registered + std::set mRegisteredMDNSHostnames; + + // web-compat stopgap + bool mAllowOldSetParameters = false; + + // Used to store the mDNS hostnames that we have queried + struct PendingIceCandidate { + std::vector mTokenizedCandidate; + std::string mTransportId; + std::string mUfrag; + }; + std::map> mQueriedMDNSHostnames; + + // Connecting PCImpl to sigslot is not safe, because sigslot takes strong + // references without any reference counting, and JS holds refcounted strong + // references to PCImpl (meaning JS can cause PCImpl to be destroyed). This + // is not ref-counted (since sigslot holds onto non-refcounted strong refs) + // Must be destroyed on STS. Holds a weak reference to PCImpl. + class SignalHandler : public sigslot::has_slots<> { + public: + SignalHandler(PeerConnectionImpl* aPc, MediaTransportHandler* aSource); + virtual ~SignalHandler(); + + void ConnectSignals(); + + // ICE events + void IceGatheringStateChange_s(dom::RTCIceGatheringState aState); + void IceConnectionStateChange_s(dom::RTCIceConnectionState aState); + void OnCandidateFound_s(const std::string& aTransportId, + const CandidateInfo& aCandidateInfo); + void AlpnNegotiated_s(const std::string& aAlpn, bool aPrivacyRequested); + void ConnectionStateChange_s(const std::string& aTransportId, + TransportLayer::State aState); + + private: + const std::string mHandle; + RefPtr mSource; + RefPtr mSTSThread; + }; + + mozilla::UniquePtr mSignalHandler; + + // Make absolutely sure our refcount does not go to 0 before Close() is called + // This is because Close does a stats query, which needs the + // PeerConnectionImpl to stick around until the query is done. + RefPtr mKungFuDeathGrip; + RefPtr mPacketDumper; + + public: + // these are temporary until the DataChannel Listen/Connect API is removed + unsigned short listenPort; + unsigned short connectPort; + char* connectStr; // XXX ownership/free +}; + +// This is what is returned when you acquire on a handle +class PeerConnectionWrapper { + public: + explicit PeerConnectionWrapper(const std::string& handle); + + PeerConnectionImpl* impl() { return impl_; } + + private: + RefPtr impl_; +}; + +} // namespace mozilla + +#undef NS_IMETHODIMP_TO_ERRORRESULT +#undef NS_IMETHODIMP_TO_ERRORRESULT_RETREF +#endif // _PEER_CONNECTION_IMPL_H_ diff --git a/dom/media/webrtc/jsapi/RTCDTMFSender.cpp b/dom/media/webrtc/jsapi/RTCDTMFSender.cpp new file mode 100644 index 0000000000..30355aca26 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCDTMFSender.cpp @@ -0,0 +1,159 @@ +/* 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 "RTCDTMFSender.h" +#include "libwebrtcglue/MediaConduitInterface.h" +#include "transport/logging.h" +#include "RTCRtpTransceiver.h" +#include "nsITimer.h" +#include "mozilla/dom/RTCDTMFSenderBinding.h" +#include "mozilla/dom/RTCDTMFToneChangeEvent.h" +#include +#include + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_INHERITED(RTCDTMFSender, DOMEventTargetHelper, + mTransceiver, mSendTimer) + +NS_IMPL_ADDREF_INHERITED(RTCDTMFSender, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(RTCDTMFSender, DOMEventTargetHelper) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCDTMFSender) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsITimerCallback) + NS_INTERFACE_MAP_ENTRY(nsINamed) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +LazyLogModule gDtmfLog("RTCDTMFSender"); + +RTCDTMFSender::RTCDTMFSender(nsPIDOMWindowInner* aWindow, + RTCRtpTransceiver* aTransceiver) + : DOMEventTargetHelper(aWindow), mTransceiver(aTransceiver) {} + +JSObject* RTCDTMFSender::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + return RTCDTMFSender_Binding::Wrap(aCx, this, aGivenProto); +} + +static int GetDTMFToneCode(uint16_t c) { + const char* DTMF_TONECODES = "0123456789*#ABCD"; + + if (c == ',') { + // , is a special character indicating a 2 second delay + return -1; + } + + const char* i = strchr(DTMF_TONECODES, c); + MOZ_ASSERT(i); + return static_cast(i - DTMF_TONECODES); +} + +static std::bitset<256> GetCharacterBitset(const std::string& aCharsInSet) { + std::bitset<256> result; + for (auto c : aCharsInSet) { + result[c] = true; + } + return result; +} + +static bool IsUnrecognizedChar(const char c) { + static const std::bitset<256> recognized = + GetCharacterBitset("0123456789ABCD#*,"); + return !recognized[c]; +} + +void RTCDTMFSender::SetPayloadType(int32_t aPayloadType, + int32_t aPayloadFrequency) { + MOZ_ASSERT(NS_IsMainThread()); + mPayloadType = Some(aPayloadType); + mPayloadFrequency = Some(aPayloadFrequency); +} + +void RTCDTMFSender::InsertDTMF(const nsAString& aTones, uint32_t aDuration, + uint32_t aInterToneGap, ErrorResult& aRv) { + if (!mTransceiver->CanSendDTMF()) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + std::string utf8Tones = NS_ConvertUTF16toUTF8(aTones).get(); + + std::transform(utf8Tones.begin(), utf8Tones.end(), utf8Tones.begin(), + [](const char c) { return std::toupper(c); }); + + if (std::any_of(utf8Tones.begin(), utf8Tones.end(), IsUnrecognizedChar)) { + aRv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR); + return; + } + + CopyUTF8toUTF16(utf8Tones, mToneBuffer); + mDuration = std::clamp(aDuration, 40U, 6000U); + mInterToneGap = std::clamp(aInterToneGap, 30U, 6000U); + + if (mToneBuffer.Length()) { + StartPlayout(0); + } +} + +void RTCDTMFSender::StopPlayout() { + if (mSendTimer) { + mSendTimer->Cancel(); + mSendTimer = nullptr; + } +} + +void RTCDTMFSender::StartPlayout(uint32_t aDelay) { + if (!mSendTimer) { + mSendTimer = NS_NewTimer(); + mSendTimer->InitWithCallback(this, aDelay, nsITimer::TYPE_ONE_SHOT); + } +} + +nsresult RTCDTMFSender::Notify(nsITimer* timer) { + MOZ_ASSERT(NS_IsMainThread()); + StopPlayout(); + + if (!mTransceiver->IsSending()) { + return NS_OK; + } + + RTCDTMFToneChangeEventInit init; + if (!mToneBuffer.IsEmpty()) { + uint16_t toneChar = mToneBuffer.CharAt(0); + int tone = GetDTMFToneCode(toneChar); + + init.mTone.Assign(toneChar); + + mToneBuffer.Cut(0, 1); + + if (tone == -1) { + StartPlayout(2000); + } else { + // Reset delay if necessary + StartPlayout(mDuration + mInterToneGap); + mDtmfEvent.Notify(DtmfEvent(mPayloadType.ref(), mPayloadFrequency.ref(), + tone, mDuration)); + } + } + + RefPtr event = + RTCDTMFToneChangeEvent::Constructor(this, u"tonechange"_ns, init); + DispatchTrustedEvent(event); + + return NS_OK; +} + +nsresult RTCDTMFSender::GetName(nsACString& aName) { + aName.AssignLiteral("RTCDTMFSender"); + return NS_OK; +} + +void RTCDTMFSender::GetToneBuffer(nsAString& aOutToneBuffer) { + aOutToneBuffer = mToneBuffer; +} + +} // namespace mozilla::dom + +#undef LOGTAG diff --git a/dom/media/webrtc/jsapi/RTCDTMFSender.h b/dom/media/webrtc/jsapi/RTCDTMFSender.h new file mode 100644 index 0000000000..14be7eb6ee --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCDTMFSender.h @@ -0,0 +1,78 @@ +/* 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/. */ + +#ifndef _RTCDTMFSender_h_ +#define _RTCDTMFSender_h_ + +#include "MediaEventSource.h" +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/RefPtr.h" +#include "js/RootingAPI.h" +#include "nsITimer.h" + +class nsPIDOMWindowInner; +class nsITimer; + +namespace mozilla { +class AudioSessionConduit; + +struct DtmfEvent { + DtmfEvent(int aPayloadType, int aPayloadFrequency, int aEventCode, + int aLengthMs) + : mPayloadType(aPayloadType), + mPayloadFrequency(aPayloadFrequency), + mEventCode(aEventCode), + mLengthMs(aLengthMs) {} + const int mPayloadType; + const int mPayloadFrequency; + const int mEventCode; + const int mLengthMs; +}; + +namespace dom { +class RTCRtpTransceiver; + +class RTCDTMFSender : public DOMEventTargetHelper, + public nsITimerCallback, + public nsINamed { + public: + RTCDTMFSender(nsPIDOMWindowInner* aWindow, RTCRtpTransceiver* aTransceiver); + + // nsISupports + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RTCDTMFSender, DOMEventTargetHelper) + + // webidl + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + void SetPayloadType(int32_t aPayloadType, int32_t aPayloadFrequency); + void InsertDTMF(const nsAString& aTones, uint32_t aDuration, + uint32_t aInterToneGap, ErrorResult& aRv); + void GetToneBuffer(nsAString& aOutToneBuffer); + IMPL_EVENT_HANDLER(tonechange) + + void StopPlayout(); + + MediaEventSource& OnDtmfEvent() { return mDtmfEvent; } + + private: + virtual ~RTCDTMFSender() = default; + + void StartPlayout(uint32_t aDelay); + + RefPtr mTransceiver; + MediaEventProducer mDtmfEvent; + Maybe mPayloadType; + Maybe mPayloadFrequency; + nsString mToneBuffer; + uint32_t mDuration = 0; + uint32_t mInterToneGap = 0; + nsCOMPtr mSendTimer; +}; + +} // namespace dom +} // namespace mozilla +#endif // _RTCDTMFSender_h_ diff --git a/dom/media/webrtc/jsapi/RTCDtlsTransport.cpp b/dom/media/webrtc/jsapi/RTCDtlsTransport.cpp new file mode 100644 index 0000000000..442e9c7a17 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCDtlsTransport.cpp @@ -0,0 +1,69 @@ +/* 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 "RTCDtlsTransport.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/EventBinding.h" +#include "mozilla/dom/RTCDtlsTransportBinding.h" + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_INHERITED(RTCDtlsTransport, DOMEventTargetHelper) + +NS_IMPL_ADDREF_INHERITED(RTCDtlsTransport, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(RTCDtlsTransport, DOMEventTargetHelper) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCDtlsTransport) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +RTCDtlsTransport::RTCDtlsTransport(nsPIDOMWindowInner* aWindow) + : DOMEventTargetHelper(aWindow), mState(RTCDtlsTransportState::New) {} + +JSObject* RTCDtlsTransport::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + return RTCDtlsTransport_Binding::Wrap(aCx, this, aGivenProto); +} + +void RTCDtlsTransport::UpdateState(TransportLayer::State aState) { + if (mState == RTCDtlsTransportState::Closed) { + return; + } + + RTCDtlsTransportState newState = mState; + switch (aState) { + case TransportLayer::TS_NONE: + break; + case TransportLayer::TS_INIT: + break; + case TransportLayer::TS_CONNECTING: + newState = RTCDtlsTransportState::Connecting; + break; + case TransportLayer::TS_OPEN: + newState = RTCDtlsTransportState::Connected; + break; + case TransportLayer::TS_CLOSED: + newState = RTCDtlsTransportState::Closed; + break; + case TransportLayer::TS_ERROR: + newState = RTCDtlsTransportState::Failed; + break; + } + + if (newState == mState) { + return; + } + + mState = newState; + + EventInit init; + init.mBubbles = false; + init.mCancelable = false; + + RefPtr event = Event::Constructor(this, u"statechange"_ns, init); + + DispatchTrustedEvent(event); +} + +} // namespace mozilla::dom diff --git a/dom/media/webrtc/jsapi/RTCDtlsTransport.h b/dom/media/webrtc/jsapi/RTCDtlsTransport.h new file mode 100644 index 0000000000..74a4b5f618 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCDtlsTransport.h @@ -0,0 +1,43 @@ +/* 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/. */ + +#ifndef _RTCDtlsTransport_h_ +#define _RTCDtlsTransport_h_ + +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/RefPtr.h" +#include "js/RootingAPI.h" +#include "transport/transportlayer.h" + +class nsPIDOMWindowInner; + +namespace mozilla::dom { + +enum class RTCDtlsTransportState : uint8_t; + +class RTCDtlsTransport : public DOMEventTargetHelper { + public: + explicit RTCDtlsTransport(nsPIDOMWindowInner* aWindow); + + // nsISupports + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RTCDtlsTransport, + DOMEventTargetHelper) + + // webidl + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + IMPL_EVENT_HANDLER(statechange) + RTCDtlsTransportState State() const { return mState; } + + void UpdateState(TransportLayer::State aState); + + private: + virtual ~RTCDtlsTransport() = default; + + RTCDtlsTransportState mState; +}; + +} // namespace mozilla::dom +#endif // _RTCDtlsTransport_h_ diff --git a/dom/media/webrtc/jsapi/RTCRtpReceiver.cpp b/dom/media/webrtc/jsapi/RTCRtpReceiver.cpp new file mode 100644 index 0000000000..136aa5142f --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCRtpReceiver.cpp @@ -0,0 +1,942 @@ +/* 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 "RTCRtpReceiver.h" +#include "PeerConnectionImpl.h" +#include "mozilla/dom/RTCRtpCapabilitiesBinding.h" +#include "transport/logging.h" +#include "mozilla/dom/MediaStreamTrack.h" +#include "mozilla/dom/Promise.h" +#include "nsPIDOMWindow.h" +#include "PrincipalHandle.h" +#include "nsIPrincipal.h" +#include "mozilla/dom/Document.h" +#include "mozilla/NullPrincipal.h" +#include "MediaTrackGraph.h" +#include "RemoteTrackSource.h" +#include "libwebrtcglue/RtpRtcpConfig.h" +#include "nsString.h" +#include "mozilla/dom/AudioStreamTrack.h" +#include "mozilla/dom/VideoStreamTrack.h" +#include "MediaTransportHandler.h" +#include "jsep/JsepTransceiver.h" +#include "mozilla/dom/RTCRtpReceiverBinding.h" +#include "mozilla/dom/RTCRtpSourcesBinding.h" +#include "RTCStatsReport.h" +#include "mozilla/Preferences.h" +#include "PeerConnectionCtx.h" +#include "RTCRtpTransceiver.h" +#include "libwebrtcglue/AudioConduit.h" +#include "call/call.h" + +namespace mozilla::dom { + +LazyLogModule gReceiverLog("RTCRtpReceiver"); + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCRtpReceiver) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(RTCRtpReceiver) + // We do not do anything here, we wait for BreakCycles to be called + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(RTCRtpReceiver) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow, mPc, mTransceiver, mTrack, + mTrackSource) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCRtpReceiver) +NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCRtpReceiver) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCRtpReceiver) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +static PrincipalHandle GetPrincipalHandle(nsPIDOMWindowInner* aWindow, + PrincipalPrivacy aPrivacy) { + // Set the principal used for creating the tracks. This makes the track + // data (audio/video samples) accessible to the receiving page. We're + // only certain that privacy hasn't been requested if we're connected. + nsCOMPtr winPrincipal = do_QueryInterface(aWindow); + RefPtr principal = winPrincipal->GetPrincipal(); + if (NS_WARN_IF(!principal)) { + principal = NullPrincipal::CreateWithoutOriginAttributes(); + } else if (aPrivacy == PrincipalPrivacy::Private) { + principal = NullPrincipal::CreateWithInheritedAttributes(principal); + } + return MakePrincipalHandle(principal); +} + +#define INIT_CANONICAL(name, val) \ + name(AbstractThread::MainThread(), val, \ + "RTCRtpReceiver::" #name " (Canonical)") + +RTCRtpReceiver::RTCRtpReceiver( + nsPIDOMWindowInner* aWindow, PrincipalPrivacy aPrivacy, + PeerConnectionImpl* aPc, MediaTransportHandler* aTransportHandler, + AbstractThread* aCallThread, nsISerialEventTarget* aStsThread, + MediaSessionConduit* aConduit, RTCRtpTransceiver* aTransceiver, + const TrackingId& aTrackingId) + : mWatchManager(this, AbstractThread::MainThread()), + mWindow(aWindow), + mPc(aPc), + mCallThread(aCallThread), + mStsThread(aStsThread), + mTransportHandler(aTransportHandler), + mTransceiver(aTransceiver), + INIT_CANONICAL(mSsrc, 0), + INIT_CANONICAL(mVideoRtxSsrc, 0), + INIT_CANONICAL(mLocalRtpExtensions, RtpExtList()), + INIT_CANONICAL(mAudioCodecs, std::vector()), + INIT_CANONICAL(mVideoCodecs, std::vector()), + INIT_CANONICAL(mVideoRtpRtcpConfig, Nothing()), + INIT_CANONICAL(mReceiving, false) { + PrincipalHandle principalHandle = GetPrincipalHandle(aWindow, aPrivacy); + const bool isAudio = aConduit->type() == MediaSessionConduit::AUDIO; + + MediaTrackGraph* graph = MediaTrackGraph::GetInstance( + isAudio ? MediaTrackGraph::AUDIO_THREAD_DRIVER + : MediaTrackGraph::SYSTEM_THREAD_DRIVER, + aWindow, MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE, + MediaTrackGraph::DEFAULT_OUTPUT_DEVICE); + + if (isAudio) { + auto* source = graph->CreateSourceTrack(MediaSegment::AUDIO); + mTrackSource = MakeAndAddRef( + source, this, principalHandle, u"remote audio"_ns, aTrackingId); + mTrack = MakeAndAddRef(aWindow, source, mTrackSource); + mPipeline = MakeAndAddRef( + mPc->GetHandle(), aTransportHandler, aCallThread, mStsThread.get(), + *aConduit->AsAudioSessionConduit(), mTrackSource->Stream(), aTrackingId, + principalHandle, aPrivacy); + } else { + auto* source = graph->CreateSourceTrack(MediaSegment::VIDEO); + mTrackSource = MakeAndAddRef( + source, this, principalHandle, u"remote video"_ns, aTrackingId); + mTrack = MakeAndAddRef(aWindow, source, mTrackSource); + mPipeline = MakeAndAddRef( + mPc->GetHandle(), aTransportHandler, aCallThread, mStsThread.get(), + *aConduit->AsVideoSessionConduit(), mTrackSource->Stream(), aTrackingId, + principalHandle, aPrivacy); + } + + mPipeline->InitControl(this); + + // Spec says remote tracks start out muted. + mTrackSource->SetMuted(true); + + // Until Bug 1232234 is fixed, we'll get extra RTCP BYES during renegotiation, + // so we'll disable muting on RTCP BYE and timeout for now. + if (Preferences::GetBool("media.peerconnection.mute_on_bye_or_timeout", + false)) { + mRtcpByeListener = aConduit->RtcpByeEvent().Connect( + GetMainThreadSerialEventTarget(), this, &RTCRtpReceiver::OnRtcpBye); + mRtcpTimeoutListener = aConduit->RtcpTimeoutEvent().Connect( + GetMainThreadSerialEventTarget(), this, &RTCRtpReceiver::OnRtcpTimeout); + } + + mWatchManager.Watch(mReceiveTrackMute, + &RTCRtpReceiver::UpdateReceiveTrackMute); +} + +#undef INIT_CANONICAL + +RTCRtpReceiver::~RTCRtpReceiver() { MOZ_ASSERT(!mPipeline); } + +JSObject* RTCRtpReceiver::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + return RTCRtpReceiver_Binding::Wrap(aCx, this, aGivenProto); +} + +RTCDtlsTransport* RTCRtpReceiver::GetTransport() const { + if (!mTransceiver) { + return nullptr; + } + return mTransceiver->GetDtlsTransport(); +} + +void RTCRtpReceiver::GetCapabilities( + const GlobalObject&, const nsAString& aKind, + Nullable& aResult) { + PeerConnectionImpl::GetCapabilities(aKind, aResult, sdp::Direction::kRecv); +} + +already_AddRefed RTCRtpReceiver::GetStats(ErrorResult& aError) { + nsCOMPtr global = do_QueryInterface(mWindow); + RefPtr promise = Promise::Create(global, aError); + if (NS_WARN_IF(aError.Failed())) { + return nullptr; + } + + if (NS_WARN_IF(!mTransceiver)) { + // TODO(bug 1056433): When we stop nulling this out when the PC is closed + // (or when the transceiver is stopped), we can remove this code. We + // resolve instead of reject in order to make this eventual change in + // behavior a little smaller. + promise->MaybeResolve(new RTCStatsReport(mWindow)); + return promise.forget(); + } + + mTransceiver->ChainToDomPromiseWithCodecStats(GetStatsInternal(), promise); + return promise.forget(); +} + +nsTArray> RTCRtpReceiver::GetStatsInternal( + bool aSkipIceStats) { + MOZ_ASSERT(NS_IsMainThread()); + nsTArray> promises(3); + + if (!mPipeline) { + return promises; + } + + if (!mHaveStartedReceiving) { + return promises; + } + + nsString recvTrackId; + MOZ_ASSERT(mTrack); + if (mTrack) { + mTrack->GetId(recvTrackId); + } + + { + // Add bandwidth estimation stats + promises.AppendElement(InvokeAsync( + mCallThread, __func__, + [conduit = mPipeline->mConduit, recvTrackId]() mutable { + auto report = MakeUnique(); + const Maybe stats = conduit->GetCallStats(); + stats.apply([&](const auto& aStats) { + dom::RTCBandwidthEstimationInternal bw; + bw.mTrackIdentifier = recvTrackId; + bw.mSendBandwidthBps.Construct(aStats.send_bandwidth_bps / 8); + bw.mMaxPaddingBps.Construct(aStats.max_padding_bitrate_bps / 8); + bw.mReceiveBandwidthBps.Construct(aStats.recv_bandwidth_bps / 8); + bw.mPacerDelayMs.Construct(aStats.pacer_delay_ms); + if (aStats.rtt_ms >= 0) { + bw.mRttMs.Construct(aStats.rtt_ms); + } + if (!report->mBandwidthEstimations.AppendElement(std::move(bw), + fallible)) { + mozalloc_handle_oom(0); + } + }); + return RTCStatsPromise::CreateAndResolve(std::move(report), __func__); + })); + } + + promises.AppendElement( + InvokeAsync( + mCallThread, __func__, + [pipeline = mPipeline, recvTrackId] { + auto report = MakeUnique(); + auto asAudio = pipeline->mConduit->AsAudioSessionConduit(); + auto asVideo = pipeline->mConduit->AsVideoSessionConduit(); + + nsString kind = asVideo.isNothing() ? u"audio"_ns : u"video"_ns; + nsString idstr = kind + u"_"_ns; + idstr.AppendInt(static_cast(pipeline->Level())); + + Maybe ssrc = pipeline->mConduit->GetRemoteSSRC(); + + // Add frame history + asVideo.apply([&](const auto& conduit) { + if (conduit->AddFrameHistory(&report->mVideoFrameHistories)) { + auto& history = report->mVideoFrameHistories.LastElement(); + history.mTrackIdentifier = recvTrackId; + } + }); + + // TODO(@@NG):ssrcs handle Conduits having multiple stats at the + // same level. + // This is pending spec work. + // Gather pipeline stats. + nsString localId = u"inbound_rtp_"_ns + idstr; + nsString remoteId; + + auto constructCommonRemoteOutboundRtpStats = + [&](RTCRemoteOutboundRtpStreamStats& aRemote, + const DOMHighResTimeStamp& aTimestamp) { + remoteId = u"inbound_rtcp_"_ns + idstr; + aRemote.mTimestamp.Construct(aTimestamp); + aRemote.mId.Construct(remoteId); + aRemote.mType.Construct(RTCStatsType::Remote_outbound_rtp); + ssrc.apply([&](uint32_t aSsrc) { aRemote.mSsrc = aSsrc; }); + aRemote.mKind = kind; + aRemote.mMediaType.Construct( + kind); // mediaType is the old name for kind. + aRemote.mLocalId.Construct(localId); + }; + + auto constructCommonInboundRtpStats = + [&](RTCInboundRtpStreamStats& aLocal) { + aLocal.mTrackIdentifier = recvTrackId; + aLocal.mTimestamp.Construct( + pipeline->GetTimestampMaker().GetNow().ToDom()); + aLocal.mId.Construct(localId); + aLocal.mType.Construct(RTCStatsType::Inbound_rtp); + ssrc.apply([&](uint32_t aSsrc) { aLocal.mSsrc = aSsrc; }); + aLocal.mKind = kind; + aLocal.mMediaType.Construct( + kind); // mediaType is the old name for kind. + if (remoteId.Length()) { + aLocal.mRemoteId.Construct(remoteId); + } + }; + + asAudio.apply([&](auto& aConduit) { + Maybe audioStats = + aConduit->GetReceiverStats(); + if (audioStats.isNothing()) { + return; + } + + if (!audioStats->last_packet_received_timestamp_ms) { + // By spec: "The lifetime of all RTP monitored objects starts + // when the RTP stream is first used: When the first RTP packet + // is sent or received on the SSRC it represents" + return; + } + + // First, fill in remote stat with rtcp sender data, if present. + if (audioStats->last_sender_report_timestamp_ms) { + RTCRemoteOutboundRtpStreamStats remote; + constructCommonRemoteOutboundRtpStats( + remote, + RTCStatsTimestamp::FromNtp( + aConduit->GetTimestampMaker(), + webrtc::Timestamp::Millis( + *audioStats->last_sender_report_timestamp_ms) + + webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970)) + .ToDom()); + remote.mPacketsSent.Construct( + audioStats->sender_reports_packets_sent); + remote.mBytesSent.Construct( + audioStats->sender_reports_bytes_sent); + remote.mRemoteTimestamp.Construct( + *audioStats->last_sender_report_remote_timestamp_ms); + if (!report->mRemoteOutboundRtpStreamStats.AppendElement( + std::move(remote), fallible)) { + mozalloc_handle_oom(0); + } + } + + // Then, fill in local side (with cross-link to remote only if + // present) + RTCInboundRtpStreamStats local; + constructCommonInboundRtpStats(local); + local.mJitter.Construct(audioStats->jitter_ms / 1000.0); + local.mPacketsLost.Construct(audioStats->packets_lost); + local.mPacketsReceived.Construct(audioStats->packets_rcvd); + local.mPacketsDiscarded.Construct(audioStats->packets_discarded); + local.mBytesReceived.Construct(audioStats->payload_bytes_rcvd); + // Always missing from libwebrtc stats + // if (audioStats->estimated_playout_ntp_timestamp_ms) { + // local.mEstimatedPlayoutTimestamp.Construct( + // RTCStatsTimestamp::FromNtp( + // aConduit->GetTimestampMaker(), + // webrtc::Timestamp::Millis( + // *audioStats->estimated_playout_ntp_timestamp_ms)) + // .ToDom()); + // } + local.mJitterBufferDelay.Construct( + audioStats->jitter_buffer_delay_seconds); + local.mJitterBufferEmittedCount.Construct( + audioStats->jitter_buffer_emitted_count); + local.mTotalSamplesReceived.Construct( + audioStats->total_samples_received); + local.mConcealedSamples.Construct(audioStats->concealed_samples); + local.mSilentConcealedSamples.Construct( + audioStats->silent_concealed_samples); + if (audioStats->last_packet_received_timestamp_ms) { + local.mLastPacketReceivedTimestamp.Construct( + RTCStatsTimestamp::FromNtp( + aConduit->GetTimestampMaker(), + webrtc::Timestamp::Millis( + *audioStats->last_packet_received_timestamp_ms) + + webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970)) + .ToDom()); + } + local.mHeaderBytesReceived.Construct( + audioStats->header_and_padding_bytes_rcvd); + local.mFecPacketsReceived.Construct( + audioStats->fec_packets_received); + local.mFecPacketsDiscarded.Construct( + audioStats->fec_packets_discarded); + local.mConcealmentEvents.Construct( + audioStats->concealment_events); + + local.mInsertedSamplesForDeceleration.Construct( + audioStats->inserted_samples_for_deceleration); + local.mRemovedSamplesForAcceleration.Construct( + audioStats->removed_samples_for_acceleration); + if (audioStats->audio_level >= 0 && + audioStats->audio_level <= 32767) { + local.mAudioLevel.Construct(audioStats->audio_level / 32767.0); + } + local.mTotalAudioEnergy.Construct( + audioStats->total_output_energy); + local.mTotalSamplesDuration.Construct( + audioStats->total_output_duration); + + if (!report->mInboundRtpStreamStats.AppendElement( + std::move(local), fallible)) { + mozalloc_handle_oom(0); + } + }); + + asVideo.apply([&](auto& aConduit) { + Maybe videoStats = + aConduit->GetReceiverStats(); + if (videoStats.isNothing()) { + return; + } + + if (!videoStats->rtp_stats.last_packet_received_timestamp_ms) { + // By spec: "The lifetime of all RTP monitored objects starts + // when the RTP stream is first used: When the first RTP packet + // is sent or received on the SSRC it represents" + return; + } + + // First, fill in remote stat with rtcp sender data, if present. + if (videoStats->rtcp_sender_ntp_timestamp_ms) { + RTCRemoteOutboundRtpStreamStats remote; + constructCommonRemoteOutboundRtpStats( + remote, RTCStatsTimestamp::FromNtp( + aConduit->GetTimestampMaker(), + webrtc::Timestamp::Millis( + videoStats->rtcp_sender_ntp_timestamp_ms)) + .ToDom()); + remote.mPacketsSent.Construct( + videoStats->rtcp_sender_packets_sent); + remote.mBytesSent.Construct( + videoStats->rtcp_sender_octets_sent); + remote.mRemoteTimestamp.Construct( + (webrtc::TimeDelta::Millis( + videoStats->rtcp_sender_remote_ntp_timestamp_ms) - + webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970)) + .ms()); + if (!report->mRemoteOutboundRtpStreamStats.AppendElement( + std::move(remote), fallible)) { + mozalloc_handle_oom(0); + } + } + + // Then, fill in local side (with cross-link to remote only if + // present) + RTCInboundRtpStreamStats local; + constructCommonInboundRtpStats(local); + local.mJitter.Construct( + static_cast(videoStats->rtp_stats.jitter) / + webrtc::kVideoPayloadTypeFrequency); + local.mPacketsLost.Construct(videoStats->rtp_stats.packets_lost); + local.mPacketsReceived.Construct( + videoStats->rtp_stats.packet_counter.packets); + local.mPacketsDiscarded.Construct(videoStats->packets_discarded); + local.mDiscardedPackets.Construct(videoStats->packets_discarded); + local.mBytesReceived.Construct( + videoStats->rtp_stats.packet_counter.payload_bytes); + + // Fill in packet type statistics + local.mNackCount.Construct( + videoStats->rtcp_packet_type_counts.nack_packets); + local.mFirCount.Construct( + videoStats->rtcp_packet_type_counts.fir_packets); + local.mPliCount.Construct( + videoStats->rtcp_packet_type_counts.pli_packets); + + // Lastly, fill in video decoder stats + local.mFramesDecoded.Construct(videoStats->frames_decoded); + + local.mFramesPerSecond.Construct(videoStats->decode_frame_rate); + local.mFrameWidth.Construct(videoStats->width); + local.mFrameHeight.Construct(videoStats->height); + // XXX: key_frames + delta_frames may undercount frames because + // they were dropped in FrameBuffer::InsertFrame. (bug 1766553) + local.mFramesReceived.Construct( + videoStats->frame_counts.key_frames + + videoStats->frame_counts.delta_frames); + local.mJitterBufferDelay.Construct( + videoStats->jitter_buffer_delay_seconds); + local.mJitterBufferEmittedCount.Construct( + videoStats->jitter_buffer_emitted_count); + + if (videoStats->qp_sum) { + local.mQpSum.Construct(videoStats->qp_sum.value()); + } + local.mTotalDecodeTime.Construct( + double(videoStats->total_decode_time.ms()) / 1000); + local.mTotalInterFrameDelay.Construct( + videoStats->total_inter_frame_delay); + local.mTotalSquaredInterFrameDelay.Construct( + videoStats->total_squared_inter_frame_delay); + if (videoStats->rtp_stats.last_packet_received_timestamp_ms) { + local.mLastPacketReceivedTimestamp.Construct( + RTCStatsTimestamp::FromNtp( + aConduit->GetTimestampMaker(), + webrtc::Timestamp::Millis( + *videoStats->rtp_stats + .last_packet_received_timestamp_ms) + + webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970)) + .ToDom()); + } + local.mHeaderBytesReceived.Construct( + videoStats->rtp_stats.packet_counter.header_bytes + + videoStats->rtp_stats.packet_counter.padding_bytes); + local.mTotalProcessingDelay.Construct( + videoStats->total_processing_delay.seconds()); + /* + * Potential new stats that are now available upstream + .if (videoStats->estimated_playout_ntp_timestamp_ms) { + local.mEstimatedPlayoutTimestamp.Construct( + RTCStatsTimestamp::FromNtp( + aConduit->GetTimestampMaker(), + webrtc::Timestamp::Millis( + *videoStats->estimated_playout_ntp_timestamp_ms)) + .ToDom()); + } + */ + // Not including frames dropped in the rendering pipe, which + // is not of webrtc's concern anyway?! + local.mFramesDropped.Construct(videoStats->frames_dropped); + if (!report->mInboundRtpStreamStats.AppendElement( + std::move(local), fallible)) { + mozalloc_handle_oom(0); + } + }); + return RTCStatsPromise::CreateAndResolve(std::move(report), + __func__); + }) + ->Then( + mStsThread, __func__, + [pipeline = mPipeline](UniquePtr aReport) { + // Fill in Contributing Source statistics + if (!aReport->mInboundRtpStreamStats.IsEmpty() && + aReport->mInboundRtpStreamStats[0].mId.WasPassed()) { + pipeline->GetContributingSourceStats( + aReport->mInboundRtpStreamStats[0].mId.Value(), + aReport->mRtpContributingSourceStats); + } + return RTCStatsPromise::CreateAndResolve(std::move(aReport), + __func__); + }, + [] { + MOZ_CRASH("Unexpected reject"); + return RTCStatsPromise::CreateAndReject(NS_ERROR_UNEXPECTED, + __func__); + })); + + if (!aSkipIceStats && GetJsepTransceiver().mTransport.mComponents) { + promises.AppendElement(mTransportHandler->GetIceStats( + GetJsepTransceiver().mTransport.mTransportId, + mPipeline->GetTimestampMaker().GetNow().ToDom())); + } + + return promises; +} + +void RTCRtpReceiver::SetJitterBufferTarget( + const Nullable& aTargetMs, ErrorResult& aError) { + // Spec says jitter buffer target cannot be negative or larger than 4000 + // milliseconds and to throw RangeError if it is. If an invalid value is + // received we return early to preserve the current JitterBufferTarget + // internal slot and jitter buffer values. + if (mPipeline && mPipeline->mConduit) { + if (!aTargetMs.IsNull() && + (aTargetMs.Value() < 0.0 || aTargetMs.Value() > 4000.0)) { + aError.ThrowRangeError("jitterBufferTarget"); + return; + } + + mJitterBufferTarget.reset(); + + if (!aTargetMs.IsNull()) { + mJitterBufferTarget = Some(aTargetMs.Value()); + } + // If aJitterBufferTarget is null then we are resetting the jitter buffer so + // pass the default target of 0.0. + mPipeline->mConduit->SetJitterBufferTarget( + mJitterBufferTarget.valueOr(0.0)); + } +} + +void RTCRtpReceiver::GetContributingSources( + nsTArray& aSources) { + // Duplicate code... + if (mPipeline && mPipeline->mConduit) { + nsTArray sources; + mPipeline->mConduit->GetRtpSources(sources); + sources.RemoveElementsBy([](const dom::RTCRtpSourceEntry& aEntry) { + return aEntry.mSourceType != dom::RTCRtpSourceEntryType::Contributing; + }); + aSources.ReplaceElementsAt(0, aSources.Length(), sources.Elements(), + sources.Length()); + } +} + +void RTCRtpReceiver::GetSynchronizationSources( + nsTArray& aSources) { + // Duplicate code... + if (mPipeline && mPipeline->mConduit) { + nsTArray sources; + mPipeline->mConduit->GetRtpSources(sources); + sources.RemoveElementsBy([](const dom::RTCRtpSourceEntry& aEntry) { + return aEntry.mSourceType != dom::RTCRtpSourceEntryType::Synchronization; + }); + aSources.ReplaceElementsAt(0, aSources.Length(), sources.Elements(), + sources.Length()); + } +} + +nsPIDOMWindowInner* RTCRtpReceiver::GetParentObject() const { return mWindow; } + +void RTCRtpReceiver::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + mWatchManager.Shutdown(); + if (mPipeline) { + mPipeline->Shutdown(); + mPipeline = nullptr; + } + if (mTrackSource) { + mTrackSource->Destroy(); + } + mCallThread = nullptr; + mRtcpByeListener.DisconnectIfExists(); + mRtcpTimeoutListener.DisconnectIfExists(); + mUnmuteListener.DisconnectIfExists(); +} + +void RTCRtpReceiver::BreakCycles() { + mWindow = nullptr; + mPc = nullptr; + mTrack = nullptr; + mTrackSource = nullptr; +} + +void RTCRtpReceiver::UpdateTransport() { + MOZ_ASSERT(NS_IsMainThread()); + if (!mHaveSetupTransport) { + mPipeline->SetLevel(GetJsepTransceiver().GetLevel()); + mHaveSetupTransport = true; + } + + UniquePtr filter; + + auto const& details = GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails(); + if (GetJsepTransceiver().HasBundleLevel() && details) { + std::vector extmaps; + details->ForEachRTPHeaderExtension( + [&extmaps](const SdpExtmapAttributeList::Extmap& extmap) { + extmaps.emplace_back(extmap.extensionname, extmap.entry); + }); + filter = MakeUnique(extmaps); + + // Add remote SSRCs so we can distinguish which RTP packets actually + // belong to this pipeline (also RTCP sender reports). + for (uint32_t ssrc : GetJsepTransceiver().mRecvTrack.GetSsrcs()) { + filter->AddRemoteSSRC(ssrc); + } + for (uint32_t ssrc : GetJsepTransceiver().mRecvTrack.GetRtxSsrcs()) { + filter->AddRemoteSSRC(ssrc); + } + auto mid = Maybe(); + if (GetMid() != "") { + mid = Some(GetMid()); + } + filter->SetRemoteMediaStreamId(mid); + + // Add unique payload types as a last-ditch fallback + auto uniquePts = GetJsepTransceiver() + .mRecvTrack.GetNegotiatedDetails() + ->GetUniquePayloadTypes(); + for (unsigned char& uniquePt : uniquePts) { + filter->AddUniquePT(uniquePt); + } + } + + mPipeline->UpdateTransport_m(GetJsepTransceiver().mTransport.mTransportId, + std::move(filter)); +} + +void RTCRtpReceiver::UpdateConduit() { + if (mPipeline->mConduit->type() == MediaSessionConduit::VIDEO) { + UpdateVideoConduit(); + } else { + UpdateAudioConduit(); + } + + if ((mReceiving = mTransceiver->IsReceiving())) { + mHaveStartedReceiving = true; + } +} + +void RTCRtpReceiver::UpdateVideoConduit() { + RefPtr conduit = + *mPipeline->mConduit->AsVideoSessionConduit(); + + // NOTE(pkerr) - this is new behavior. Needed because the + // CreateVideoReceiveStream method of the Call API will assert (in debug) + // and fail if a value is not provided for the remote_ssrc that will be used + // by the far-end sender. + if (!GetJsepTransceiver().mRecvTrack.GetSsrcs().empty()) { + MOZ_LOG(gReceiverLog, LogLevel::Debug, + ("%s[%s]: %s Setting remote SSRC %u", mPc->GetHandle().c_str(), + GetMid().c_str(), __FUNCTION__, + GetJsepTransceiver().mRecvTrack.GetSsrcs().front())); + uint32_t rtxSsrc = + GetJsepTransceiver().mRecvTrack.GetRtxSsrcs().empty() + ? 0 + : GetJsepTransceiver().mRecvTrack.GetRtxSsrcs().front(); + mSsrc = GetJsepTransceiver().mRecvTrack.GetSsrcs().front(); + mVideoRtxSsrc = rtxSsrc; + + // TODO (bug 1423041) once we pay attention to receiving MID's in RTP + // packets (see bug 1405495) we could make this depending on the presence of + // MID in the RTP packets instead of relying on the signaling. + // In any case, do not disable SSRC changes if no SSRCs were negotiated + if (GetJsepTransceiver().HasBundleLevel() && + (!GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails() || + !GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails()->GetExt( + webrtc::RtpExtension::kMidUri))) { + mCallThread->Dispatch( + NewRunnableMethod("VideoSessionConduit::DisableSsrcChanges", conduit, + &VideoSessionConduit::DisableSsrcChanges)); + } + } + + if (GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails() && + GetJsepTransceiver().mRecvTrack.GetActive()) { + const auto& details( + *GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails()); + + { + std::vector extmaps; + // @@NG read extmap from track + details.ForEachRTPHeaderExtension( + [&extmaps](const SdpExtmapAttributeList::Extmap& extmap) { + extmaps.emplace_back(extmap.extensionname, extmap.entry); + }); + mLocalRtpExtensions = extmaps; + } + + std::vector configs; + RTCRtpTransceiver::NegotiatedDetailsToVideoCodecConfigs(details, &configs); + if (configs.empty()) { + // TODO: Are we supposed to plumb this error back to JS? This does not + // seem like a failure to set an answer, it just means that codec + // negotiation failed. For now, we're just doing the same thing we do + // if negotiation as a whole failed. + MOZ_LOG(gReceiverLog, LogLevel::Error, + ("%s[%s]: %s No video codecs were negotiated (recv).", + mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__)); + return; + } + + mVideoCodecs = configs; + mVideoRtpRtcpConfig = Some(details.GetRtpRtcpConfig()); + } +} + +void RTCRtpReceiver::UpdateAudioConduit() { + RefPtr conduit = + *mPipeline->mConduit->AsAudioSessionConduit(); + + if (!GetJsepTransceiver().mRecvTrack.GetSsrcs().empty()) { + MOZ_LOG(gReceiverLog, LogLevel::Debug, + ("%s[%s]: %s Setting remote SSRC %u", mPc->GetHandle().c_str(), + GetMid().c_str(), __FUNCTION__, + GetJsepTransceiver().mRecvTrack.GetSsrcs().front())); + mSsrc = GetJsepTransceiver().mRecvTrack.GetSsrcs().front(); + + // TODO (bug 1423041) once we pay attention to receiving MID's in RTP + // packets (see bug 1405495) we could make this depending on the presence of + // MID in the RTP packets instead of relying on the signaling. + // In any case, do not disable SSRC changes if no SSRCs were negotiated + if (GetJsepTransceiver().HasBundleLevel() && + (!GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails() || + !GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails()->GetExt( + webrtc::RtpExtension::kMidUri))) { + mCallThread->Dispatch( + NewRunnableMethod("AudioSessionConduit::DisableSsrcChanges", conduit, + &AudioSessionConduit::DisableSsrcChanges)); + } + } + + if (GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails() && + GetJsepTransceiver().mRecvTrack.GetActive()) { + const auto& details( + *GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails()); + std::vector configs; + RTCRtpTransceiver::NegotiatedDetailsToAudioCodecConfigs(details, &configs); + if (configs.empty()) { + // TODO: Are we supposed to plumb this error back to JS? This does not + // seem like a failure to set an answer, it just means that codec + // negotiation failed. For now, we're just doing the same thing we do + // if negotiation as a whole failed. + MOZ_LOG(gReceiverLog, LogLevel::Error, + ("%s[%s]: %s No audio codecs were negotiated (recv)", + mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__)); + return; + } + + // Ensure conduit knows about extensions prior to creating streams + { + std::vector extmaps; + // @@NG read extmap from track + details.ForEachRTPHeaderExtension( + [&extmaps](const SdpExtmapAttributeList::Extmap& extmap) { + extmaps.emplace_back(extmap.extensionname, extmap.entry); + }); + mLocalRtpExtensions = extmaps; + } + + mAudioCodecs = configs; + } +} + +void RTCRtpReceiver::Stop() { + MOZ_ASSERT(mTransceiver->Stopped()); + mReceiving = false; +} + +bool RTCRtpReceiver::HasTrack(const dom::MediaStreamTrack* aTrack) const { + return !aTrack || (mTrack == aTrack); +} + +void RTCRtpReceiver::SyncFromJsep(const JsepTransceiver& aJsepTransceiver) { + if (!mPipeline) { + return; + } + + // Spec says we set [[Receptive]] to true on sLD(sendrecv/recvonly), and to + // false on sRD(recvonly/inactive), sLD(sendonly/inactive), or when stop() + // is called. + bool wasReceptive = mReceptive; + mReceptive = aJsepTransceiver.mRecvTrack.GetReceptive(); + if (!wasReceptive && mReceptive) { + mUnmuteListener = mPipeline->mConduit->RtpPacketEvent().Connect( + GetMainThreadSerialEventTarget(), this, &RTCRtpReceiver::OnRtpPacket); + } else if (wasReceptive && !mReceptive) { + mUnmuteListener.DisconnectIfExists(); + } +} + +void RTCRtpReceiver::SyncToJsep(JsepTransceiver& aJsepTransceiver) const {} + +void RTCRtpReceiver::UpdateStreams(StreamAssociationChanges* aChanges) { + // We don't sort and use set_difference, because we need to report the + // added/removed streams in the order that they appear in the SDP. + std::set newIds( + GetJsepTransceiver().mRecvTrack.GetStreamIds().begin(), + GetJsepTransceiver().mRecvTrack.GetStreamIds().end()); + MOZ_ASSERT(GetJsepTransceiver().mRecvTrack.GetRemoteSetSendBit() || + newIds.empty()); + bool needsTrackEvent = false; + for (const auto& id : mStreamIds) { + if (!newIds.count(id)) { + aChanges->mStreamAssociationsRemoved.push_back({mTrack, id}); + } + } + + std::set oldIds(mStreamIds.begin(), mStreamIds.end()); + for (const auto& id : GetJsepTransceiver().mRecvTrack.GetStreamIds()) { + if (!oldIds.count(id)) { + needsTrackEvent = true; + aChanges->mStreamAssociationsAdded.push_back({mTrack, id}); + } + } + + mStreamIds = GetJsepTransceiver().mRecvTrack.GetStreamIds(); + + if (mRemoteSetSendBit != + GetJsepTransceiver().mRecvTrack.GetRemoteSetSendBit()) { + mRemoteSetSendBit = GetJsepTransceiver().mRecvTrack.GetRemoteSetSendBit(); + if (mRemoteSetSendBit) { + needsTrackEvent = true; + } else { + aChanges->mReceiversToMute.push_back(this); + } + } + + if (needsTrackEvent) { + aChanges->mTrackEvents.push_back({this, mStreamIds}); + } +} + +void RTCRtpReceiver::UpdatePrincipalPrivacy(PrincipalPrivacy aPrivacy) { + if (!mPipeline) { + return; + } + + if (aPrivacy != PrincipalPrivacy::Private) { + return; + } + + mPipeline->SetPrivatePrincipal(GetPrincipalHandle(mWindow, aPrivacy)); +} + +// test-only: adds fake CSRCs and audio data +void RTCRtpReceiver::MozInsertAudioLevelForContributingSource( + const uint32_t aSource, const DOMHighResTimeStamp aTimestamp, + const uint32_t aRtpTimestamp, const bool aHasLevel, const uint8_t aLevel) { + if (!mPipeline || mPipeline->IsVideo() || !mPipeline->mConduit) { + return; + } + mPipeline->mConduit->InsertAudioLevelForContributingSource( + aSource, aTimestamp, aRtpTimestamp, aHasLevel, aLevel); +} + +void RTCRtpReceiver::OnRtcpBye() { mReceiveTrackMute = true; } + +void RTCRtpReceiver::OnRtcpTimeout() { mReceiveTrackMute = true; } + +void RTCRtpReceiver::SetTrackMuteFromRemoteSdp() { + MOZ_ASSERT(!mReceptive, + "PeerConnectionImpl should have blocked unmute events prior to " + "firing mute"); + mReceiveTrackMute = true; + // Set the mute state (and fire the mute event) synchronously. Unmute is + // handled asynchronously after receiving RTP packets. + UpdateReceiveTrackMute(); + MOZ_ASSERT(mTrack->Muted(), "Muted state was indeed set synchronously"); +} + +void RTCRtpReceiver::OnRtpPacket() { + MOZ_ASSERT(mReceptive, "We should not be registered unless this is set!"); + // We should be registered since we're currently getting a callback. + mUnmuteListener.Disconnect(); + if (mReceptive) { + mReceiveTrackMute = false; + } +} + +void RTCRtpReceiver::UpdateReceiveTrackMute() { + if (!mTrack) { + return; + } + if (!mTrackSource) { + return; + } + // This sets the muted state for mTrack and all its clones. + // Idempotent -- only reacts to changes. + mTrackSource->SetMuted(mReceiveTrackMute); +} + +std::string RTCRtpReceiver::GetMid() const { + return mTransceiver->GetMidAscii(); +} + +JsepTransceiver& RTCRtpReceiver::GetJsepTransceiver() { + MOZ_ASSERT(mTransceiver); + return mTransceiver->GetJsepTransceiver(); +} + +const JsepTransceiver& RTCRtpReceiver::GetJsepTransceiver() const { + MOZ_ASSERT(mTransceiver); + return mTransceiver->GetJsepTransceiver(); +} + +} // namespace mozilla::dom + +#undef LOGTAG diff --git a/dom/media/webrtc/jsapi/RTCRtpReceiver.h b/dom/media/webrtc/jsapi/RTCRtpReceiver.h new file mode 100644 index 0000000000..2c050bceb1 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCRtpReceiver.h @@ -0,0 +1,198 @@ +/* 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/. */ + +#ifndef _RTCRtpReceiver_h_ +#define _RTCRtpReceiver_h_ + +#include "nsISupports.h" +#include "nsWrapperCache.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StateMirroring.h" +#include "mozilla/Maybe.h" +#include "js/RootingAPI.h" +#include "libwebrtcglue/RtpRtcpConfig.h" +#include "nsTArray.h" +#include "mozilla/dom/RTCRtpCapabilitiesBinding.h" +#include "mozilla/dom/RTCStatsReportBinding.h" +#include "PerformanceRecorder.h" +#include "RTCStatsReport.h" +#include "transportbridge/MediaPipeline.h" +#include + +class nsPIDOMWindowInner; + +namespace mozilla { +class MediaSessionConduit; +class MediaTransportHandler; +class JsepTransceiver; +class PeerConnectionImpl; +enum class PrincipalPrivacy : uint8_t; +class RemoteTrackSource; + +namespace dom { +class MediaStreamTrack; +class Promise; +class RTCDtlsTransport; +struct RTCRtpCapabilities; +struct RTCRtpContributingSource; +struct RTCRtpSynchronizationSource; +class RTCRtpTransceiver; + +class RTCRtpReceiver : public nsISupports, + public nsWrapperCache, + public MediaPipelineReceiveControlInterface { + public: + RTCRtpReceiver(nsPIDOMWindowInner* aWindow, PrincipalPrivacy aPrivacy, + PeerConnectionImpl* aPc, + MediaTransportHandler* aTransportHandler, + AbstractThread* aCallThread, nsISerialEventTarget* aStsThread, + MediaSessionConduit* aConduit, RTCRtpTransceiver* aTransceiver, + const TrackingId& aTrackingId); + + // nsISupports + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCRtpReceiver) + + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + // webidl + MediaStreamTrack* Track() const { return mTrack; } + RTCDtlsTransport* GetTransport() const; + static void GetCapabilities(const GlobalObject&, const nsAString& aKind, + Nullable& aResult); + already_AddRefed GetStats(ErrorResult& aError); + void GetContributingSources( + nsTArray& aSources); + void GetSynchronizationSources( + nsTArray& aSources); + // test-only: insert fake CSRCs and audio levels for testing + void MozInsertAudioLevelForContributingSource( + const uint32_t aSource, const DOMHighResTimeStamp aTimestamp, + const uint32_t aRtpTimestamp, const bool aHasLevel, const uint8_t aLevel); + + nsPIDOMWindowInner* GetParentObject() const; + nsTArray> GetStatsInternal( + bool aSkipIceStats = false); + Nullable GetJitterBufferTarget( + ErrorResult& aError) const { + return mJitterBufferTarget.isSome() ? Nullable(mJitterBufferTarget.value()) + : Nullable(); + } + void SetJitterBufferTarget(const Nullable& aTargetMs, + ErrorResult& aError); + + void Shutdown(); + void BreakCycles(); + // Terminal state, reached through stopping RTCRtpTransceiver. + void Stop(); + bool HasTrack(const dom::MediaStreamTrack* aTrack) const; + void SyncToJsep(JsepTransceiver& aJsepTransceiver) const; + void SyncFromJsep(const JsepTransceiver& aJsepTransceiver); + const std::vector& GetStreamIds() const { return mStreamIds; } + + struct StreamAssociation { + RefPtr mTrack; + std::string mStreamId; + }; + + struct TrackEventInfo { + RefPtr mReceiver; + std::vector mStreamIds; + }; + + struct StreamAssociationChanges { + std::vector> mReceiversToMute; + std::vector mStreamAssociationsRemoved; + std::vector mStreamAssociationsAdded; + std::vector mTrackEvents; + }; + + // This is called when we set an answer (ie; when the transport is finalized). + void UpdateTransport(); + void UpdateConduit(); + + // This is called when we set a remote description; may be an offer or answer. + void UpdateStreams(StreamAssociationChanges* aChanges); + + // Called when the privacy-needed state changes on the fly, as a result of + // ALPN negotiation. + void UpdatePrincipalPrivacy(PrincipalPrivacy aPrivacy); + + void OnRtcpBye(); + void OnRtcpTimeout(); + + void SetTrackMuteFromRemoteSdp(); + void OnRtpPacket(); + void UpdateUnmuteBlockingState(); + void UpdateReceiveTrackMute(); + + AbstractCanonical* CanonicalSsrc() { return &mSsrc; } + AbstractCanonical* CanonicalVideoRtxSsrc() { return &mVideoRtxSsrc; } + AbstractCanonical* CanonicalLocalRtpExtensions() { + return &mLocalRtpExtensions; + } + + AbstractCanonical>* CanonicalAudioCodecs() { + return &mAudioCodecs; + } + + AbstractCanonical>* CanonicalVideoCodecs() { + return &mVideoCodecs; + } + AbstractCanonical>* CanonicalVideoRtpRtcpConfig() { + return &mVideoRtpRtcpConfig; + } + AbstractCanonical* CanonicalReceiving() override { return &mReceiving; } + + private: + virtual ~RTCRtpReceiver(); + + void UpdateVideoConduit(); + void UpdateAudioConduit(); + + std::string GetMid() const; + JsepTransceiver& GetJsepTransceiver(); + const JsepTransceiver& GetJsepTransceiver() const; + + WatchManager mWatchManager; + nsCOMPtr mWindow; + RefPtr mPc; + bool mHaveStartedReceiving = false; + bool mHaveSetupTransport = false; + RefPtr mCallThread; + nsCOMPtr mStsThread; + RefPtr mTrack; + RefPtr mTrackSource; + RefPtr mPipeline; + RefPtr mTransportHandler; + RefPtr mTransceiver; + // This is [[AssociatedRemoteMediaStreams]], basically. We do not keep the + // streams themselves here, because that would require this object to know + // where the stream list for the whole RTCPeerConnection lives.. + std::vector mStreamIds; + bool mRemoteSetSendBit = false; + Watchable mReceiveTrackMute{true, "RTCRtpReceiver::mReceiveTrackMute"}; + // This corresponds to the [[Receptive]] slot on RTCRtpTransceiver. + // Its only purpose is suppressing unmute events if true. + bool mReceptive = false; + // This is the [[JitterBufferTarget]] internal slot. + Maybe mJitterBufferTarget; + + MediaEventListener mRtcpByeListener; + MediaEventListener mRtcpTimeoutListener; + MediaEventListener mUnmuteListener; + + Canonical mSsrc; + Canonical mVideoRtxSsrc; + Canonical mLocalRtpExtensions; + Canonical> mAudioCodecs; + Canonical> mVideoCodecs; + Canonical> mVideoRtpRtcpConfig; + Canonical mReceiving; +}; + +} // namespace dom +} // namespace mozilla +#endif // _RTCRtpReceiver_h_ diff --git a/dom/media/webrtc/jsapi/RTCRtpSender.cpp b/dom/media/webrtc/jsapi/RTCRtpSender.cpp new file mode 100644 index 0000000000..568a83e8d1 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCRtpSender.cpp @@ -0,0 +1,1654 @@ +/* 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 "RTCRtpSender.h" +#include "transport/logging.h" +#include "mozilla/dom/MediaStreamTrack.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/glean/GleanMetrics.h" +#include "nsPIDOMWindow.h" +#include "nsString.h" +#include "mozilla/dom/VideoStreamTrack.h" +#include "jsep/JsepTransceiver.h" +#include "mozilla/dom/RTCRtpSenderBinding.h" +#include "RTCStatsReport.h" +#include "mozilla/Preferences.h" +#include "RTCRtpTransceiver.h" +#include "PeerConnectionImpl.h" +#include "libwebrtcglue/AudioConduit.h" +#include +#include "call/call.h" + +namespace mozilla::dom { + +LazyLogModule gSenderLog("RTCRtpSender"); + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCRtpSender) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(RTCRtpSender) + // We do not do anything here, we wait for BreakCycles to be called + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(RTCRtpSender) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow, mPc, mSenderTrack, mTransceiver, + mStreams, mDtmf) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCRtpSender) +NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCRtpSender) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCRtpSender) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +#define INIT_CANONICAL(name, val) \ + name(AbstractThread::MainThread(), val, "RTCRtpSender::" #name " (Canonical)") + +RTCRtpSender::RTCRtpSender(nsPIDOMWindowInner* aWindow, PeerConnectionImpl* aPc, + MediaTransportHandler* aTransportHandler, + AbstractThread* aCallThread, + nsISerialEventTarget* aStsThread, + MediaSessionConduit* aConduit, + dom::MediaStreamTrack* aTrack, + const Sequence& aEncodings, + RTCRtpTransceiver* aTransceiver) + : mWatchManager(this, AbstractThread::MainThread()), + mWindow(aWindow), + mPc(aPc), + mSenderTrack(aTrack), + mTransportHandler(aTransportHandler), + mTransceiver(aTransceiver), + INIT_CANONICAL(mSsrcs, Ssrcs()), + INIT_CANONICAL(mVideoRtxSsrcs, Ssrcs()), + INIT_CANONICAL(mLocalRtpExtensions, RtpExtList()), + INIT_CANONICAL(mAudioCodec, Nothing()), + INIT_CANONICAL(mVideoCodec, Nothing()), + INIT_CANONICAL(mVideoRtpRtcpConfig, Nothing()), + INIT_CANONICAL(mVideoCodecMode, webrtc::VideoCodecMode::kRealtimeVideo), + INIT_CANONICAL(mCname, std::string()), + INIT_CANONICAL(mTransmitting, false) { + mPipeline = new MediaPipelineTransmit( + mPc->GetHandle(), aTransportHandler, aCallThread, aStsThread, + aConduit->type() == MediaSessionConduit::VIDEO, aConduit); + mPipeline->InitControl(this); + + if (aConduit->type() == MediaSessionConduit::AUDIO) { + mDtmf = new RTCDTMFSender(aWindow, mTransceiver); + } + mPipeline->SetTrack(mSenderTrack); + + mozilla::glean::rtcrtpsender::count.Add(1); + + if (mPc->ShouldAllowOldSetParameters()) { + mAllowOldSetParameters = true; + mozilla::glean::rtcrtpsender::count_setparameters_compat.Add(1); + } + + if (aEncodings.Length()) { + // This sender was created by addTransceiver with sendEncodings. + mParameters.mEncodings = aEncodings; + mSimulcastEnvelopeSet = true; + mozilla::glean::rtcrtpsender::used_sendencodings.AddToNumerator(1); + } else { + // This sender was created by addTrack, sRD(offer), or addTransceiver + // without sendEncodings. + RTCRtpEncodingParameters defaultEncoding; + defaultEncoding.mActive = true; + if (aConduit->type() == MediaSessionConduit::VIDEO) { + defaultEncoding.mScaleResolutionDownBy.Construct(1.0f); + } + Unused << mParameters.mEncodings.AppendElement(defaultEncoding, fallible); + UpdateRestorableEncodings(mParameters.mEncodings); + MaybeGetJsepRids(); + } + + if (mDtmf) { + mWatchManager.Watch(mTransmitting, &RTCRtpSender::UpdateDtmfSender); + } +} + +#undef INIT_CANONICAL + +RTCRtpSender::~RTCRtpSender() = default; + +JSObject* RTCRtpSender::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + return RTCRtpSender_Binding::Wrap(aCx, this, aGivenProto); +} + +RTCDtlsTransport* RTCRtpSender::GetTransport() const { + if (!mTransceiver) { + return nullptr; + } + return mTransceiver->GetDtlsTransport(); +} + +RTCDTMFSender* RTCRtpSender::GetDtmf() const { return mDtmf; } + +already_AddRefed RTCRtpSender::GetStats(ErrorResult& aError) { + RefPtr promise = MakePromise(aError); + if (aError.Failed()) { + return nullptr; + } + if (NS_WARN_IF(!mPipeline)) { + // TODO(bug 1056433): When we stop nulling this out when the PC is closed + // (or when the transceiver is stopped), we can remove this code. We + // resolve instead of reject in order to make this eventual change in + // behavior a little smaller. + promise->MaybeResolve(new RTCStatsReport(mWindow)); + return promise.forget(); + } + + if (!mSenderTrack) { + promise->MaybeResolve(new RTCStatsReport(mWindow)); + return promise.forget(); + } + + mTransceiver->ChainToDomPromiseWithCodecStats(GetStatsInternal(), promise); + return promise.forget(); +} + +nsTArray> RTCRtpSender::GetStatsInternal( + bool aSkipIceStats) { + MOZ_ASSERT(NS_IsMainThread()); + nsTArray> promises(2); + if (!mSenderTrack || !mPipeline) { + return promises; + } + + nsAutoString trackName; + if (auto track = mPipeline->GetTrack()) { + track->GetId(trackName); + } + + { + // Add bandwidth estimation stats + promises.AppendElement(InvokeAsync( + mPipeline->mCallThread, __func__, + [conduit = mPipeline->mConduit, trackName]() mutable { + auto report = MakeUnique(); + Maybe stats = conduit->GetCallStats(); + stats.apply([&](const auto aStats) { + dom::RTCBandwidthEstimationInternal bw; + bw.mTrackIdentifier = trackName; + bw.mSendBandwidthBps.Construct(aStats.send_bandwidth_bps / 8); + bw.mMaxPaddingBps.Construct(aStats.max_padding_bitrate_bps / 8); + bw.mReceiveBandwidthBps.Construct(aStats.recv_bandwidth_bps / 8); + bw.mPacerDelayMs.Construct(aStats.pacer_delay_ms); + if (aStats.rtt_ms >= 0) { + bw.mRttMs.Construct(aStats.rtt_ms); + } + if (!report->mBandwidthEstimations.AppendElement(std::move(bw), + fallible)) { + mozalloc_handle_oom(0); + } + }); + return RTCStatsPromise::CreateAndResolve(std::move(report), __func__); + })); + } + + promises.AppendElement(InvokeAsync( + mPipeline->mCallThread, __func__, [pipeline = mPipeline, trackName] { + auto report = MakeUnique(); + auto asAudio = pipeline->mConduit->AsAudioSessionConduit(); + auto asVideo = pipeline->mConduit->AsVideoSessionConduit(); + + nsString kind = asVideo.isNothing() ? u"audio"_ns : u"video"_ns; + nsString idstr = kind + u"_"_ns; + idstr.AppendInt(static_cast(pipeline->Level())); + + for (uint32_t ssrc : pipeline->mConduit->GetLocalSSRCs()) { + nsString localId = u"outbound_rtp_"_ns + idstr + u"_"_ns; + localId.AppendInt(ssrc); + nsString remoteId; + Maybe base_seq = + pipeline->mConduit->RtpSendBaseSeqFor(ssrc); + + auto constructCommonRemoteInboundRtpStats = + [&](RTCRemoteInboundRtpStreamStats& aRemote, + const webrtc::ReportBlockData& aRtcpData) { + remoteId = u"outbound_rtcp_"_ns + idstr + u"_"_ns; + remoteId.AppendInt(ssrc); + aRemote.mTimestamp.Construct( + RTCStatsTimestamp::FromNtp( + pipeline->GetTimestampMaker(), + webrtc::Timestamp::Micros( + aRtcpData.report_block_timestamp_utc_us()) + + webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970)) + .ToDom()); + aRemote.mId.Construct(remoteId); + aRemote.mType.Construct(RTCStatsType::Remote_inbound_rtp); + aRemote.mSsrc = ssrc; + aRemote.mKind = kind; + aRemote.mMediaType.Construct( + kind); // mediaType is the old name for kind. + aRemote.mLocalId.Construct(localId); + if (base_seq) { + if (aRtcpData.report_block() + .extended_highest_sequence_number < *base_seq) { + aRemote.mPacketsReceived.Construct(0); + } else { + aRemote.mPacketsReceived.Construct( + aRtcpData.report_block() + .extended_highest_sequence_number - + aRtcpData.report_block().packets_lost - *base_seq + 1); + } + } + }; + + auto constructCommonOutboundRtpStats = + [&](RTCOutboundRtpStreamStats& aLocal) { + aLocal.mSsrc = ssrc; + aLocal.mTimestamp.Construct( + pipeline->GetTimestampMaker().GetNow().ToDom()); + aLocal.mId.Construct(localId); + aLocal.mType.Construct(RTCStatsType::Outbound_rtp); + aLocal.mKind = kind; + aLocal.mMediaType.Construct( + kind); // mediaType is the old name for kind. + if (remoteId.Length()) { + aLocal.mRemoteId.Construct(remoteId); + } + }; + + asAudio.apply([&](auto& aConduit) { + Maybe audioStats = + aConduit->GetSenderStats(); + if (audioStats.isNothing()) { + return; + } + + if (audioStats->packets_sent == 0) { + // By spec: "The lifetime of all RTP monitored objects starts + // when the RTP stream is first used: When the first RTP packet + // is sent or received on the SSRC it represents" + return; + } + + // First, fill in remote stat with rtcp receiver data, if present. + // ReceiverReports have less information than SenderReports, so fill + // in what we can. + Maybe reportBlockData; + { + if (const auto remoteSsrc = aConduit->GetRemoteSSRC(); + remoteSsrc) { + for (auto& data : audioStats->report_block_datas) { + if (data.report_block().source_ssrc == ssrc && + data.report_block().sender_ssrc == *remoteSsrc) { + reportBlockData.emplace(data); + break; + } + } + } + } + reportBlockData.apply([&](auto& aReportBlockData) { + RTCRemoteInboundRtpStreamStats remote; + constructCommonRemoteInboundRtpStats(remote, aReportBlockData); + if (audioStats->jitter_ms >= 0) { + remote.mJitter.Construct(audioStats->jitter_ms / 1000.0); + } + if (audioStats->packets_lost >= 0) { + remote.mPacketsLost.Construct(audioStats->packets_lost); + } + if (audioStats->rtt_ms >= 0) { + remote.mRoundTripTime.Construct( + static_cast(audioStats->rtt_ms) / 1000.0); + } + remote.mFractionLost.Construct(audioStats->fraction_lost); + remote.mTotalRoundTripTime.Construct( + double(aReportBlockData.sum_rtt_ms()) / 1000); + remote.mRoundTripTimeMeasurements.Construct( + aReportBlockData.num_rtts()); + if (!report->mRemoteInboundRtpStreamStats.AppendElement( + std::move(remote), fallible)) { + mozalloc_handle_oom(0); + } + }); + + // Then, fill in local side (with cross-link to remote only if + // present) + RTCOutboundRtpStreamStats local; + constructCommonOutboundRtpStats(local); + local.mPacketsSent.Construct(audioStats->packets_sent); + local.mBytesSent.Construct(audioStats->payload_bytes_sent); + local.mNackCount.Construct( + audioStats->rtcp_packet_type_counts.nack_packets); + local.mHeaderBytesSent.Construct( + audioStats->header_and_padding_bytes_sent); + local.mRetransmittedPacketsSent.Construct( + audioStats->retransmitted_packets_sent); + local.mRetransmittedBytesSent.Construct( + audioStats->retransmitted_bytes_sent); + /* + * Potential new stats that are now available upstream. + * Note: when we last tried exposing this we were getting + * targetBitrate for audio was ending up as 0. We did not + * investigate why. + local.mTargetBitrate.Construct(audioStats->target_bitrate_bps); + */ + if (!report->mOutboundRtpStreamStats.AppendElement(std::move(local), + fallible)) { + mozalloc_handle_oom(0); + } + }); + + asVideo.apply([&](auto& aConduit) { + Maybe videoStats = + aConduit->GetSenderStats(); + if (videoStats.isNothing()) { + return; + } + + Maybe streamStats; + auto kv = videoStats->substreams.find(ssrc); + if (kv != videoStats->substreams.end()) { + streamStats = Some(kv->second); + } + + if (!streamStats) { + // By spec: "The lifetime of all RTP monitored objects starts + // when the RTP stream is first used: When the first RTP packet + // is sent or received on the SSRC it represents" + return; + } + + aConduit->GetAssociatedLocalRtxSSRC(ssrc).apply( + [&](const auto rtxSsrc) { + auto kv = videoStats->substreams.find(rtxSsrc); + if (kv != videoStats->substreams.end()) { + streamStats->rtp_stats.Add(kv->second.rtp_stats); + } + }); + + if (streamStats->rtp_stats.first_packet_time_ms == -1) { + return; + } + + // First, fill in remote stat with rtcp receiver data, if present. + // ReceiverReports have less information than SenderReports, so fill + // in what we can. + if (streamStats->report_block_data) { + const webrtc::ReportBlockData& rtcpReportData = + *streamStats->report_block_data; + RTCRemoteInboundRtpStreamStats remote; + remote.mJitter.Construct( + static_cast(rtcpReportData.report_block().jitter) / + webrtc::kVideoPayloadTypeFrequency); + remote.mPacketsLost.Construct( + rtcpReportData.report_block().packets_lost); + if (rtcpReportData.has_rtt()) { + remote.mRoundTripTime.Construct( + static_cast(rtcpReportData.last_rtt_ms()) / 1000.0); + } + constructCommonRemoteInboundRtpStats(remote, rtcpReportData); + remote.mTotalRoundTripTime.Construct( + streamStats->report_block_data->sum_rtt_ms() / 1000.0); + remote.mFractionLost.Construct( + static_cast( + rtcpReportData.report_block().fraction_lost) / + (1 << 8)); + remote.mRoundTripTimeMeasurements.Construct( + streamStats->report_block_data->num_rtts()); + if (!report->mRemoteInboundRtpStreamStats.AppendElement( + std::move(remote), fallible)) { + mozalloc_handle_oom(0); + } + } + + // Then, fill in local side (with cross-link to remote only if + // present) + RTCOutboundRtpStreamStats local; + constructCommonOutboundRtpStats(local); + local.mPacketsSent.Construct( + streamStats->rtp_stats.transmitted.packets); + local.mBytesSent.Construct( + streamStats->rtp_stats.transmitted.payload_bytes); + local.mNackCount.Construct( + streamStats->rtcp_packet_type_counts.nack_packets); + local.mFirCount.Construct( + streamStats->rtcp_packet_type_counts.fir_packets); + local.mPliCount.Construct( + streamStats->rtcp_packet_type_counts.pli_packets); + local.mFramesEncoded.Construct(streamStats->frames_encoded); + if (streamStats->qp_sum) { + local.mQpSum.Construct(*streamStats->qp_sum); + } + local.mHeaderBytesSent.Construct( + streamStats->rtp_stats.transmitted.header_bytes + + streamStats->rtp_stats.transmitted.padding_bytes); + local.mRetransmittedPacketsSent.Construct( + streamStats->rtp_stats.retransmitted.packets); + local.mRetransmittedBytesSent.Construct( + streamStats->rtp_stats.retransmitted.payload_bytes); + local.mTotalEncodedBytesTarget.Construct( + videoStats->total_encoded_bytes_target); + local.mFrameWidth.Construct(streamStats->width); + local.mFrameHeight.Construct(streamStats->height); + local.mFramesSent.Construct(streamStats->frames_encoded); + local.mHugeFramesSent.Construct(streamStats->huge_frames_sent); + local.mTotalEncodeTime.Construct( + double(streamStats->total_encode_time_ms) / 1000.); + /* + * Potential new stats that are now available upstream. + local.mTargetBitrate.Construct(videoStats->target_media_bitrate_bps); + */ + if (!report->mOutboundRtpStreamStats.AppendElement(std::move(local), + fallible)) { + mozalloc_handle_oom(0); + } + }); + } + + auto constructCommonMediaSourceStats = + [&](RTCMediaSourceStats& aStats) { + nsString id = u"mediasource_"_ns + idstr + trackName; + aStats.mTimestamp.Construct( + pipeline->GetTimestampMaker().GetNow().ToDom()); + aStats.mId.Construct(id); + aStats.mType.Construct(RTCStatsType::Media_source); + aStats.mTrackIdentifier = trackName; + aStats.mKind = kind; + }; + + // TODO(bug 1804678): Use RTCAudioSourceStats/RTCVideoSourceStats + RTCMediaSourceStats mediaSourceStats; + constructCommonMediaSourceStats(mediaSourceStats); + if (!report->mMediaSourceStats.AppendElement( + std::move(mediaSourceStats), fallible)) { + mozalloc_handle_oom(0); + } + + return RTCStatsPromise::CreateAndResolve(std::move(report), __func__); + })); + + if (!aSkipIceStats && GetJsepTransceiver().mTransport.mComponents) { + promises.AppendElement(mTransportHandler->GetIceStats( + GetJsepTransceiver().mTransport.mTransportId, + mPipeline->GetTimestampMaker().GetNow().ToDom())); + } + + return promises; +} + +void RTCRtpSender::GetCapabilities(const GlobalObject&, const nsAString& aKind, + Nullable& aResult) { + PeerConnectionImpl::GetCapabilities(aKind, aResult, sdp::Direction::kSend); +} + +void RTCRtpSender::WarnAboutBadSetParameters(const nsCString& aError) { + nsCString warning( + "WARNING! Invalid setParameters call detected! The good news? Firefox " + "supports sendEncodings in addTransceiver now, so we ask that you switch " + "over to using the parameters code you use for other browsers. Thank you " + "for your patience and support. The specific error was: "); + warning += aError; + mPc->SendWarningToConsole(warning); +} + +nsCString RTCRtpSender::GetEffectiveTLDPlus1() const { + return mPc->GetEffectiveTLDPlus1(); +} + +already_AddRefed RTCRtpSender::SetParameters( + const dom::RTCRtpSendParameters& aParameters, ErrorResult& aError) { + dom::RTCRtpSendParameters paramsCopy(aParameters); + // When the setParameters method is called, the user agent MUST run the + // following steps: + // Let parameters be the method's first argument. + // Let sender be the RTCRtpSender object on which setParameters is invoked. + // Let transceiver be the RTCRtpTransceiver object associated with sender + // (i.e.sender is transceiver.[[Sender]]). + + RefPtr p = MakePromise(aError); + if (aError.Failed()) { + return nullptr; + } + + if (mPc->IsClosed()) { + p->MaybeRejectWithInvalidStateError("Peer connection is closed"); + return p.forget(); + } + + // If transceiver.[[Stopped]] is true, return a promise rejected with a newly + // created InvalidStateError. + if (mTransceiver->Stopped()) { + p->MaybeRejectWithInvalidStateError("This sender's transceiver is stopped"); + return p.forget(); + } + + // If sender.[[LastReturnedParameters]] is null, return a promise rejected + // with a newly created InvalidStateError. + if (!mLastReturnedParameters.isSome()) { + nsCString error( + "Cannot call setParameters without first calling getParameters"); + if (mAllowOldSetParameters) { + if (!mHaveWarnedBecauseNoGetParameters) { + mHaveWarnedBecauseNoGetParameters = true; + mozilla::glean::rtcrtpsender_setparameters::warn_no_getparameters + .AddToNumerator(1); +#ifdef EARLY_BETA_OR_EARLIER + mozilla::glean::rtcrtpsender_setparameters::blame_no_getparameters + .Get(GetEffectiveTLDPlus1()) + .Add(1); +#endif + } + WarnAboutBadSetParameters(error); + } else { + if (!mHaveFailedBecauseNoGetParameters) { + mHaveFailedBecauseNoGetParameters = true; + mozilla::glean::rtcrtpsender_setparameters::fail_no_getparameters + .AddToNumerator(1); + } + p->MaybeRejectWithInvalidStateError(error); + return p.forget(); + } + } + + // According to the spec, our consistency checking is based on + // [[LastReturnedParameters]], but if we're letting + // [[LastReturnedParameters]]==null slide, we still want to do + // consistency checking on _something_ so we can warn implementers if they + // are messing that up also. Just find something, _anything_, to do that + // checking with. + // TODO(bug 1803388): Remove this stuff once it is no longer needed. + // TODO(bug 1803389): Remove the glean errors once they are no longer needed. + Maybe oldParams; + if (mAllowOldSetParameters) { + if (mLastReturnedParameters.isSome()) { + oldParams = mLastReturnedParameters; + } else if (mPendingParameters.isSome()) { + oldParams = mPendingParameters; + } else { + oldParams = Some(mParameters); + } + MOZ_ASSERT(oldParams.isSome()); + } else { + oldParams = mLastReturnedParameters; + } + MOZ_ASSERT(oldParams.isSome()); + + // Validate parameters by running the following steps: + // Let encodings be parameters.encodings. + // Let codecs be parameters.codecs. + // Let N be the number of RTCRtpEncodingParameters stored in + // sender.[[SendEncodings]]. + // If any of the following conditions are met, + // return a promise rejected with a newly created InvalidModificationError: + + bool pendingRidChangeFromCompatMode = false; + // encodings.length is different from N. + if (paramsCopy.mEncodings.Length() != oldParams->mEncodings.Length()) { + nsCString error("Cannot change the number of encodings with setParameters"); + if (!mAllowOldSetParameters) { + if (!mHaveFailedBecauseEncodingCountChange) { + mHaveFailedBecauseEncodingCountChange = true; + mozilla::glean::rtcrtpsender_setparameters::fail_length_changed + .AddToNumerator(1); + } + p->MaybeRejectWithInvalidModificationError(error); + return p.forget(); + } + // Make sure we don't use the old rids in SyncToJsep while we wait for the + // queued task below to update mParameters. + pendingRidChangeFromCompatMode = true; + mSimulcastEnvelopeSet = true; + if (!mHaveWarnedBecauseEncodingCountChange) { + mHaveWarnedBecauseEncodingCountChange = true; + mozilla::glean::rtcrtpsender_setparameters::warn_length_changed + .AddToNumerator(1); +#ifdef EARLY_BETA_OR_EARLIER + mozilla::glean::rtcrtpsender_setparameters::blame_length_changed + .Get(GetEffectiveTLDPlus1()) + .Add(1); +#endif + } + WarnAboutBadSetParameters(error); + } else { + // encodings has been re-ordered. + for (size_t i = 0; i < paramsCopy.mEncodings.Length(); ++i) { + const auto& oldEncoding = oldParams->mEncodings[i]; + const auto& newEncoding = paramsCopy.mEncodings[i]; + if (oldEncoding.mRid != newEncoding.mRid) { + nsCString error("Cannot change rid, or reorder encodings"); + if (!mHaveFailedBecauseRidChange) { + mHaveFailedBecauseRidChange = true; + mozilla::glean::rtcrtpsender_setparameters::fail_rid_changed + .AddToNumerator(1); + } + p->MaybeRejectWithInvalidModificationError(error); + return p.forget(); + } + } + } + + // TODO(bug 1803388): Handle this in webidl, once we stop allowing the old + // setParameters style. + if (!paramsCopy.mTransactionId.WasPassed()) { + nsCString error("transactionId is not set!"); + if (!mAllowOldSetParameters) { + if (!mHaveFailedBecauseNoTransactionId) { + mHaveFailedBecauseNoTransactionId = true; + mozilla::glean::rtcrtpsender_setparameters::fail_no_transactionid + .AddToNumerator(1); + } + p->MaybeRejectWithTypeError(error); + return p.forget(); + } + if (!mHaveWarnedBecauseNoTransactionId) { + mHaveWarnedBecauseNoTransactionId = true; + mozilla::glean::rtcrtpsender_setparameters::warn_no_transactionid + .AddToNumerator(1); +#ifdef EARLY_BETA_OR_EARLIER + mozilla::glean::rtcrtpsender_setparameters::blame_no_transactionid + .Get(GetEffectiveTLDPlus1()) + .Add(1); +#endif + } + WarnAboutBadSetParameters(error); + } else if (oldParams->mTransactionId != paramsCopy.mTransactionId) { + // Any parameter in parameters is marked as a Read-only parameter (such as + // RID) and has a value that is different from the corresponding parameter + // value in sender.[[LastReturnedParameters]]. Note that this also applies + // to transactionId. + nsCString error( + "Cannot change transaction id: call getParameters, modify the result, " + "and then call setParameters"); + if (!mAllowOldSetParameters) { + if (!mHaveFailedBecauseStaleTransactionId) { + mHaveFailedBecauseStaleTransactionId = true; + mozilla::glean::rtcrtpsender_setparameters::fail_stale_transactionid + .AddToNumerator(1); + } + p->MaybeRejectWithInvalidModificationError(error); + return p.forget(); + } + if (!mHaveWarnedBecauseStaleTransactionId) { + mHaveWarnedBecauseStaleTransactionId = true; + mozilla::glean::rtcrtpsender_setparameters::warn_stale_transactionid + .AddToNumerator(1); +#ifdef EARLY_BETA_OR_EARLIER + mozilla::glean::rtcrtpsender_setparameters::blame_stale_transactionid + .Get(GetEffectiveTLDPlus1()) + .Add(1); +#endif + } + WarnAboutBadSetParameters(error); + } + + // This could conceivably happen if we are allowing the old setParameters + // behavior. + if (!paramsCopy.mEncodings.Length()) { + nsCString error("Cannot set an empty encodings array"); + if (!mAllowOldSetParameters) { + if (!mHaveFailedBecauseNoEncodings) { + mHaveFailedBecauseNoEncodings = true; + mozilla::glean::rtcrtpsender_setparameters::fail_no_encodings + .AddToNumerator(1); + } + + p->MaybeRejectWithInvalidModificationError(error); + return p.forget(); + } + // TODO: Add some warning telemetry here + WarnAboutBadSetParameters(error); + // Just don't do this; it's stupid. + paramsCopy.mEncodings = oldParams->mEncodings; + } + + // TODO: Verify remaining read-only parameters + // headerExtensions (bug 1765851) + // rtcp (bug 1765852) + // codecs (bug 1534687) + + // CheckAndRectifyEncodings handles the following steps: + // If transceiver kind is "audio", remove the scaleResolutionDownBy member + // from all encodings that contain one. + // + // If transceiver kind is "video", and any encoding in encodings contains a + // scaleResolutionDownBy member whose value is less than 1.0, return a + // promise rejected with a newly created RangeError. + // + // Verify that each encoding in encodings has a maxFramerate member whose + // value is greater than or equal to 0.0. If one of the maxFramerate values + // does not meet this requirement, return a promise rejected with a newly + // created RangeError. + ErrorResult rv; + CheckAndRectifyEncodings(paramsCopy.mEncodings, mTransceiver->IsVideo(), rv); + if (rv.Failed()) { + if (!mHaveFailedBecauseOtherError) { + mHaveFailedBecauseOtherError = true; + mozilla::glean::rtcrtpsender_setparameters::fail_other.AddToNumerator(1); + } + p->MaybeReject(std::move(rv)); + return p.forget(); + } + + // If transceiver kind is "video", then for each encoding in encodings that + // doesn't contain a scaleResolutionDownBy member, add a + // scaleResolutionDownBy member with the value 1.0. + if (mTransceiver->IsVideo()) { + for (auto& encoding : paramsCopy.mEncodings) { + if (!encoding.mScaleResolutionDownBy.WasPassed()) { + encoding.mScaleResolutionDownBy.Construct(1.0); + } + } + } + + // Let p be a new promise. (see above) + + // In parallel, configure the media stack to use parameters to transmit + // sender.[[SenderTrack]]. + // Right now this is infallible. That may change someday. + + // We need to put this in a member variable, since MaybeUpdateConduit needs it + // This also allows PeerConnectionImpl to detect when there is a pending + // setParameters, which has implcations for the handling of + // setRemoteDescription. + mPendingRidChangeFromCompatMode = pendingRidChangeFromCompatMode; + mPendingParameters = Some(paramsCopy); + uint32_t serialNumber = ++mNumSetParametersCalls; + MaybeUpdateConduit(); + + // If the media stack is successfully configured with parameters, + // queue a task to run the following steps: + GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + __func__, + [this, self = RefPtr(this), p, paramsCopy, serialNumber] { + // Set sender.[[LastReturnedParameters]] to null. + mLastReturnedParameters = Nothing(); + // Set sender.[[SendEncodings]] to parameters.encodings. + mParameters = paramsCopy; + UpdateRestorableEncodings(mParameters.mEncodings); + // Only clear mPendingParameters if it matches; there could have been + // back-to-back calls to setParameters, and we only want to clear this + // if no subsequent setParameters is pending. + if (serialNumber == mNumSetParametersCalls) { + mPendingParameters = Nothing(); + // Ok, nothing has called SyncToJsep while this async task was + // pending. No need for special handling anymore. + mPendingRidChangeFromCompatMode = false; + } + MOZ_ASSERT(mParameters.mEncodings.Length()); + // Resolve p with undefined. + p->MaybeResolveWithUndefined(); + })); + + // Return p. + return p.forget(); +} + +// static +void RTCRtpSender::CheckAndRectifyEncodings( + Sequence& aEncodings, bool aVideo, + ErrorResult& aRv) { + // If any encoding contains a rid member whose value does not conform to the + // grammar requirements specified in Section 10 of [RFC8851], throw a + // TypeError. + for (const auto& encoding : aEncodings) { + if (encoding.mRid.WasPassed()) { + std::string utf8Rid = NS_ConvertUTF16toUTF8(encoding.mRid.Value()).get(); + std::string error; + if (!SdpRidAttributeList::CheckRidValidity(utf8Rid, &error)) { + aRv.ThrowTypeError(nsCString(error)); + return; + } + if (utf8Rid.size() > SdpRidAttributeList::kMaxRidLength) { + std::ostringstream ss; + ss << "Rid can be at most " << SdpRidAttributeList::kMaxRidLength + << " characters long (due to internal limitations)"; + aRv.ThrowTypeError(nsCString(ss.str())); + return; + } + } + } + + if (aEncodings.Length() > 1) { + // If some but not all encodings contain a rid member, throw a TypeError. + // rid must be set if there is more than one encoding + // NOTE: Since rid is read-only, and the number of encodings cannot grow, + // this should never happen in setParameters. + for (const auto& encoding : aEncodings) { + if (!encoding.mRid.WasPassed()) { + aRv.ThrowTypeError("Missing rid"); + return; + } + } + + // If any encoding contains a rid member whose value is the same as that of + // a rid contained in another encoding in sendEncodings, throw a TypeError. + // NOTE: Since rid is read-only, and the number of encodings cannot grow, + // this should never happen in setParameters. + std::set uniqueRids; + for (const auto& encoding : aEncodings) { + if (uniqueRids.count(encoding.mRid.Value())) { + aRv.ThrowTypeError("Duplicate rid"); + return; + } + uniqueRids.insert(encoding.mRid.Value()); + } + } + // TODO: ptime/adaptivePtime validation (bug 1733647) + + // If kind is "audio", remove the scaleResolutionDownBy member from all + // encodings that contain one. + if (!aVideo) { + for (auto& encoding : aEncodings) { + if (encoding.mScaleResolutionDownBy.WasPassed()) { + encoding.mScaleResolutionDownBy.Reset(); + } + if (encoding.mMaxFramerate.WasPassed()) { + encoding.mMaxFramerate.Reset(); + } + } + } + + // If any encoding contains a scaleResolutionDownBy member whose value is + // less than 1.0, throw a RangeError. + for (const auto& encoding : aEncodings) { + if (encoding.mScaleResolutionDownBy.WasPassed()) { + if (encoding.mScaleResolutionDownBy.Value() < 1.0f) { + aRv.ThrowRangeError("scaleResolutionDownBy must be >= 1.0"); + return; + } + } + } + + // Verify that the value of each maxFramerate member in sendEncodings that is + // defined is greater than 0.0. If one of the maxFramerate values does not + // meet this requirement, throw a RangeError. + for (const auto& encoding : aEncodings) { + if (encoding.mMaxFramerate.WasPassed()) { + if (encoding.mMaxFramerate.Value() < 0.0f) { + aRv.ThrowRangeError("maxFramerate must be non-negative"); + return; + } + } + } +} + +void RTCRtpSender::GetParameters(RTCRtpSendParameters& aParameters) { + MOZ_ASSERT(mParameters.mEncodings.Length()); + // If sender.[[LastReturnedParameters]] is not null, return + // sender.[[LastReturnedParameters]], and abort these steps. + if (mLastReturnedParameters.isSome()) { + aParameters = *mLastReturnedParameters; + return; + } + + // Let result be a new RTCRtpSendParameters dictionary constructed as follows: + + // transactionId is set to a new unique identifier + aParameters.mTransactionId.Construct(mPc->GenerateUUID()); + + // encodings is set to the value of the [[SendEncodings]] internal slot. + aParameters.mEncodings = mParameters.mEncodings; + + // The headerExtensions sequence is populated based on the header extensions + // that have been negotiated for sending + // TODO(bug 1765851): We do not support this yet + // aParameters.mHeaderExtensions.Construct(); + + // codecs is set to the value of the [[SendCodecs]] internal slot + // TODO(bug 1534687): We do not support this yet + + // rtcp.cname is set to the CNAME of the associated RTCPeerConnection. + // rtcp.reducedSize is set to true if reduced-size RTCP has been negotiated + // for sending, and false otherwise. + // TODO(bug 1765852): We do not support this yet + aParameters.mRtcp.Construct(); + aParameters.mRtcp.Value().mCname.Construct(); + aParameters.mRtcp.Value().mReducedSize.Construct(false); + aParameters.mHeaderExtensions.Construct(); + aParameters.mCodecs.Construct(); + + // Set sender.[[LastReturnedParameters]] to result. + mLastReturnedParameters = Some(aParameters); + + // Queue a task that sets sender.[[LastReturnedParameters]] to null. + GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + __func__, [this, self = RefPtr(this)] { + mLastReturnedParameters = Nothing(); + })); +} + +bool operator==(const RTCRtpEncodingParameters& a1, + const RTCRtpEncodingParameters& a2) { + // webidl does not generate types that are equality comparable + return a1.mActive == a2.mActive && a1.mFec == a2.mFec && + a1.mMaxBitrate == a2.mMaxBitrate && + a1.mMaxFramerate == a2.mMaxFramerate && a1.mPriority == a2.mPriority && + a1.mRid == a2.mRid && a1.mRtx == a2.mRtx && + a1.mScaleResolutionDownBy == a2.mScaleResolutionDownBy && + a1.mSsrc == a2.mSsrc; +} + +// static +void RTCRtpSender::ApplyJsEncodingToConduitEncoding( + const RTCRtpEncodingParameters& aJsEncoding, + VideoCodecConfig::Encoding* aConduitEncoding) { + aConduitEncoding->active = aJsEncoding.mActive; + if (aJsEncoding.mMaxBitrate.WasPassed()) { + aConduitEncoding->constraints.maxBr = aJsEncoding.mMaxBitrate.Value(); + } + if (aJsEncoding.mMaxFramerate.WasPassed()) { + aConduitEncoding->constraints.maxFps = + Some(aJsEncoding.mMaxFramerate.Value()); + } + if (aJsEncoding.mScaleResolutionDownBy.WasPassed()) { + // Optional does not have a valueOr, despite being based on Maybe + // :( + aConduitEncoding->constraints.scaleDownBy = + aJsEncoding.mScaleResolutionDownBy.Value(); + } else { + aConduitEncoding->constraints.scaleDownBy = 1.0f; + } +} + +void RTCRtpSender::UpdateRestorableEncodings( + const Sequence& aEncodings) { + MOZ_ASSERT(aEncodings.Length()); + + if (GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()) { + // Once initial negotiation completes, we are no longer allowed to restore + // the unicast encoding. + mUnicastEncoding.reset(); + } else if (mParameters.mEncodings.Length() == 1 && + !mParameters.mEncodings[0].mRid.WasPassed()) { + // If we have not completed the initial negotiation, and we currently are + // ridless unicast, we need to save our unicast encoding in case a + // rollback occurs. + mUnicastEncoding = Some(mParameters.mEncodings[0]); + } +} + +Sequence RTCRtpSender::ToSendEncodings( + const std::vector& aRids) const { + MOZ_ASSERT(!aRids.empty()); + + Sequence result; + // If sendEncodings is given as input to this algorithm, and is non-empty, + // set the [[SendEncodings]] slot to sendEncodings. + for (const auto& rid : aRids) { + MOZ_ASSERT(!rid.empty()); + RTCRtpEncodingParameters encoding; + encoding.mActive = true; + encoding.mRid.Construct(NS_ConvertUTF8toUTF16(rid.c_str())); + Unused << result.AppendElement(encoding, fallible); + } + + // If sendEncodings is non-empty, set each encoding's scaleResolutionDownBy + // to 2^(length of sendEncodings - encoding index - 1). + if (mTransceiver->IsVideo()) { + double scale = 1.0f; + for (auto it = result.rbegin(); it != result.rend(); ++it) { + it->mScaleResolutionDownBy.Construct(scale); + scale *= 2; + } + } + + return result; +} + +void RTCRtpSender::MaybeGetJsepRids() { + MOZ_ASSERT(!mSimulcastEnvelopeSet); + MOZ_ASSERT(mParameters.mEncodings.Length()); + + auto jsepRids = GetJsepTransceiver().mSendTrack.GetRids(); + if (!jsepRids.empty()) { + UpdateRestorableEncodings(mParameters.mEncodings); + if (jsepRids.size() != 1 || !jsepRids[0].empty()) { + // JSEP is using at least one rid. Stomp our single ridless encoding + mParameters.mEncodings = ToSendEncodings(jsepRids); + } + mSimulcastEnvelopeSet = true; + mSimulcastEnvelopeSetByJSEP = true; + } +} + +Sequence RTCRtpSender::GetMatchingEncodings( + const std::vector& aRids) const { + Sequence result; + + if (!aRids.empty() && !aRids[0].empty()) { + // Simulcast, or unicast with rid + for (const auto& encoding : mParameters.mEncodings) { + for (const auto& rid : aRids) { + auto utf16Rid = NS_ConvertUTF8toUTF16(rid.c_str()); + if (!encoding.mRid.WasPassed() || (utf16Rid == encoding.mRid.Value())) { + auto encodingCopy(encoding); + if (!encodingCopy.mRid.WasPassed()) { + encodingCopy.mRid.Construct(NS_ConvertUTF8toUTF16(rid.c_str())); + } + Unused << result.AppendElement(encodingCopy, fallible); + break; + } + } + } + } + + // If we're allowing the old setParameters behavior, we _might_ be able to + // get into this situation even if there were rids above. Be extra careful. + // Under normal circumstances, this just handles the ridless case. + if (!result.Length()) { + // Unicast with no specified rid. Restore mUnicastEncoding, if + // it exists, otherwise pick the first encoding. + if (mUnicastEncoding.isSome()) { + Unused << result.AppendElement(*mUnicastEncoding, fallible); + } else { + Unused << result.AppendElement(mParameters.mEncodings[0], fallible); + } + } + + return result; +} + +void RTCRtpSender::SetStreams( + const Sequence>& aStreams, ErrorResult& aRv) { + if (mPc->IsClosed()) { + aRv.ThrowInvalidStateError( + "Cannot call setStreams if the peer connection is closed"); + return; + } + + SetStreamsImpl(aStreams); + mPc->UpdateNegotiationNeeded(); +} + +void RTCRtpSender::SetStreamsImpl( + const Sequence>& aStreams) { + mStreams.Clear(); + std::set ids; + for (const auto& stream : aStreams) { + nsString id; + stream->GetId(id); + if (!ids.count(id)) { + ids.insert(id); + mStreams.AppendElement(stream); + } + } +} + +void RTCRtpSender::GetStreams(nsTArray>& aStreams) { + aStreams = mStreams.Clone(); +} + +class ReplaceTrackOperation final : public PeerConnectionImpl::Operation { + public: + ReplaceTrackOperation(PeerConnectionImpl* aPc, + const RefPtr& aTransceiver, + const RefPtr& aTrack, + ErrorResult& aError); + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ReplaceTrackOperation, + PeerConnectionImpl::Operation) + + private: + MOZ_CAN_RUN_SCRIPT + RefPtr CallImpl(ErrorResult& aError) override; + ~ReplaceTrackOperation() = default; + RefPtr mTransceiver; + RefPtr mNewTrack; +}; + +NS_IMPL_CYCLE_COLLECTION_INHERITED(ReplaceTrackOperation, + PeerConnectionImpl::Operation, mTransceiver, + mNewTrack) + +NS_IMPL_ADDREF_INHERITED(ReplaceTrackOperation, PeerConnectionImpl::Operation) +NS_IMPL_RELEASE_INHERITED(ReplaceTrackOperation, PeerConnectionImpl::Operation) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReplaceTrackOperation) +NS_INTERFACE_MAP_END_INHERITING(PeerConnectionImpl::Operation) + +ReplaceTrackOperation::ReplaceTrackOperation( + PeerConnectionImpl* aPc, const RefPtr& aTransceiver, + const RefPtr& aTrack, ErrorResult& aError) + : PeerConnectionImpl::Operation(aPc, aError), + mTransceiver(aTransceiver), + mNewTrack(aTrack) {} + +RefPtr ReplaceTrackOperation::CallImpl(ErrorResult& aError) { + RefPtr sender = mTransceiver->Sender(); + // If transceiver.[[Stopped]] is true, return a promise rejected with a newly + // created InvalidStateError. + if (mTransceiver->Stopped()) { + RefPtr error = sender->MakePromise(aError); + if (aError.Failed()) { + return nullptr; + } + MOZ_LOG(gSenderLog, LogLevel::Debug, + ("%s Cannot call replaceTrack when transceiver is stopped", + __FUNCTION__)); + error->MaybeRejectWithInvalidStateError( + "Cannot call replaceTrack when transceiver is stopped"); + return error; + } + + // Let p be a new promise. + RefPtr p = sender->MakePromise(aError); + if (aError.Failed()) { + return nullptr; + } + + if (!sender->SeamlessTrackSwitch(mNewTrack)) { + MOZ_LOG(gSenderLog, LogLevel::Info, + ("%s Could not seamlessly replace track", __FUNCTION__)); + p->MaybeRejectWithInvalidModificationError( + "Could not seamlessly replace track"); + return p; + } + + // Queue a task that runs the following steps: + GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + __func__, [p, sender, track = mNewTrack]() MOZ_CAN_RUN_SCRIPT_BOUNDARY { + // If connection.[[IsClosed]] is true, abort these steps. + // Set sender.[[SenderTrack]] to withTrack. + if (sender->SetSenderTrackWithClosedCheck(track)) { + // Resolve p with undefined. + p->MaybeResolveWithUndefined(); + } + })); + + // Return p. + return p; +} + +already_AddRefed RTCRtpSender::ReplaceTrack( + dom::MediaStreamTrack* aWithTrack, ErrorResult& aError) { + // If withTrack is non-null and withTrack.kind differs from the transceiver + // kind of transceiver, return a promise rejected with a newly created + // TypeError. + if (aWithTrack) { + nsString newKind; + aWithTrack->GetKind(newKind); + nsString oldKind; + mTransceiver->GetKind(oldKind); + if (newKind != oldKind) { + RefPtr error = MakePromise(aError); + if (aError.Failed()) { + return nullptr; + } + error->MaybeRejectWithTypeError( + "Cannot replaceTrack with a different kind!"); + return error.forget(); + } + } + + MOZ_LOG(gSenderLog, LogLevel::Debug, + ("%s[%s]: %s (%p to %p)", mPc->GetHandle().c_str(), GetMid().c_str(), + __FUNCTION__, mSenderTrack.get(), aWithTrack)); + + // Return the result of chaining the following steps to connection's + // operations chain: + RefPtr op = + new ReplaceTrackOperation(mPc, mTransceiver, aWithTrack, aError); + if (aError.Failed()) { + return nullptr; + } + // Static analysis forces us to use a temporary. + auto pc = mPc; + return pc->Chain(op, aError); +} + +nsPIDOMWindowInner* RTCRtpSender::GetParentObject() const { return mWindow; } + +already_AddRefed RTCRtpSender::MakePromise( + ErrorResult& aError) const { + return mPc->MakePromise(aError); +} + +bool RTCRtpSender::SeamlessTrackSwitch( + const RefPtr& aWithTrack) { + // We do not actually update mSenderTrack here! Spec says that happens in a + // queued task after this is done (this happens in + // SetSenderTrackWithClosedCheck). + + mPipeline->SetTrack(aWithTrack); + + MaybeUpdateConduit(); + + // There may eventually be cases where a renegotiation is necessary to switch. + return true; +} + +void RTCRtpSender::SetTrack(const RefPtr& aTrack) { + // Used for RTCPeerConnection.removeTrack and RTCPeerConnection.addTrack + mSenderTrack = aTrack; + SeamlessTrackSwitch(aTrack); + if (aTrack) { + // RFC says (in the section on remote rollback): + // However, an RtpTransceiver MUST NOT be removed if a track was attached + // to the RtpTransceiver via the addTrack method. + mAddTrackCalled = true; + } +} + +bool RTCRtpSender::SetSenderTrackWithClosedCheck( + const RefPtr& aTrack) { + if (!mPc->IsClosed()) { + mSenderTrack = aTrack; + return true; + } + + return false; +} + +void RTCRtpSender::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + mWatchManager.Shutdown(); + mPipeline->Shutdown(); + mPipeline = nullptr; +} + +void RTCRtpSender::BreakCycles() { + mWindow = nullptr; + mPc = nullptr; + mSenderTrack = nullptr; + mTransceiver = nullptr; + mStreams.Clear(); + mDtmf = nullptr; +} + +void RTCRtpSender::UpdateTransport() { + MOZ_ASSERT(NS_IsMainThread()); + if (!mHaveSetupTransport) { + mPipeline->SetLevel(GetJsepTransceiver().GetLevel()); + mHaveSetupTransport = true; + } + + mPipeline->UpdateTransport_m(GetJsepTransceiver().mTransport.mTransportId, + nullptr); +} + +void RTCRtpSender::MaybeUpdateConduit() { + // NOTE(pkerr) - the Call API requires the both local_ssrc and remote_ssrc be + // set to a non-zero value or the CreateVideo...Stream call will fail. + if (NS_WARN_IF(GetJsepTransceiver().mSendTrack.GetSsrcs().empty())) { + MOZ_ASSERT( + false, + "No local ssrcs! This is a bug in the jsep engine, and should never " + "happen!"); + return; + } + + if (!mPipeline) { + return; + } + + bool wasTransmitting = mTransmitting; + + if (mPipeline->mConduit->type() == MediaSessionConduit::VIDEO) { + Maybe newConfig = GetNewVideoConfig(); + if (newConfig.isSome()) { + ApplyVideoConfig(*newConfig); + } + } else { + Maybe newConfig = GetNewAudioConfig(); + if (newConfig.isSome()) { + ApplyAudioConfig(*newConfig); + } + } + + if (!mSenderTrack && !wasTransmitting && mTransmitting) { + MOZ_LOG(gSenderLog, LogLevel::Debug, + ("%s[%s]: %s Starting transmit conduit without send track!", + mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__)); + } +} + +void RTCRtpSender::SyncFromJsep(const JsepTransceiver& aJsepTransceiver) { + if (!mSimulcastEnvelopeSet) { + // JSEP is establishing the simulcast envelope for the first time, right now + // This is the addTrack (or addTransceiver without sendEncodings) case. + MaybeGetJsepRids(); + } else if (!aJsepTransceiver.mSendTrack.GetNegotiatedDetails() || + !aJsepTransceiver.mSendTrack.IsInHaveRemote()) { + // Spec says that we do not update our encodings until we're in stable, + // _unless_ this is the first negotiation. + std::vector rids = aJsepTransceiver.mSendTrack.GetRids(); + if (mSimulcastEnvelopeSetByJSEP && rids.empty()) { + // JSEP previously set the simulcast envelope, but now it has no opinion + // regarding unicast/simulcast. This can only happen on rollback of the + // initial remote offer. + mParameters.mEncodings = GetMatchingEncodings(rids); + MOZ_ASSERT(mParameters.mEncodings.Length()); + mSimulcastEnvelopeSetByJSEP = false; + mSimulcastEnvelopeSet = false; + } else if (!rids.empty()) { + // JSEP has an opinion on the simulcast envelope, which trumps anything + // we have already. + mParameters.mEncodings = GetMatchingEncodings(rids); + MOZ_ASSERT(mParameters.mEncodings.Length()); + } + } + + MaybeUpdateConduit(); +} + +void RTCRtpSender::SyncToJsep(JsepTransceiver& aJsepTransceiver) const { + std::vector streamIds; + for (const auto& stream : mStreams) { + nsString wideStreamId; + stream->GetId(wideStreamId); + std::string streamId = NS_ConvertUTF16toUTF8(wideStreamId).get(); + MOZ_ASSERT(!streamId.empty()); + streamIds.push_back(streamId); + } + + aJsepTransceiver.mSendTrack.UpdateStreamIds(streamIds); + + if (mSimulcastEnvelopeSet) { + std::vector rids; + Maybe parameters; + if (mPendingRidChangeFromCompatMode) { + // *sigh* If we have just let a setParameters change our rids, but we have + // not yet updated mParameters because the queued task hasn't run yet, + // we want to set the _new_ rids on the JsepTrack. So, we are forced to + // grab them from mPendingParameters. + parameters = mPendingParameters; + } else { + parameters = Some(mParameters); + } + for (const auto& encoding : parameters->mEncodings) { + if (encoding.mRid.WasPassed()) { + rids.push_back(NS_ConvertUTF16toUTF8(encoding.mRid.Value()).get()); + } else { + rids.push_back(""); + } + } + + aJsepTransceiver.mSendTrack.SetRids(rids); + } + + if (mTransceiver->IsVideo()) { + aJsepTransceiver.mSendTrack.SetMaxEncodings(webrtc::kMaxSimulcastStreams); + } else { + aJsepTransceiver.mSendTrack.SetMaxEncodings(1); + } + + if (mAddTrackCalled) { + aJsepTransceiver.SetOnlyExistsBecauseOfSetRemote(false); + } +} + +Maybe RTCRtpSender::GetNewVideoConfig() { + // It is possible for SDP to signal that there is a send track, but there not + // actually be a send track, according to the specification; all that needs to + // happen is for the transceiver to be configured to send... + if (!GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()) { + return Nothing(); + } + + VideoConfig oldConfig; + oldConfig.mSsrcs = mSsrcs; + oldConfig.mLocalRtpExtensions = mLocalRtpExtensions; + oldConfig.mCname = mCname; + oldConfig.mTransmitting = mTransmitting; + oldConfig.mVideoRtxSsrcs = mVideoRtxSsrcs; + oldConfig.mVideoCodec = mVideoCodec; + oldConfig.mVideoRtpRtcpConfig = mVideoRtpRtcpConfig; + oldConfig.mVideoCodecMode = mVideoCodecMode; + + VideoConfig newConfig(oldConfig); + + UpdateBaseConfig(&newConfig); + + newConfig.mVideoRtxSsrcs = GetJsepTransceiver().mSendTrack.GetRtxSsrcs(); + + const JsepTrackNegotiatedDetails details( + *GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()); + + if (mSenderTrack) { + RefPtr videotrack = + mSenderTrack->AsVideoStreamTrack(); + + if (!videotrack) { + MOZ_CRASH( + "In ConfigureVideoCodecMode, mSenderTrack is not video! This should " + "never happen!"); + } + + dom::MediaSourceEnum source = videotrack->GetSource().GetMediaSource(); + switch (source) { + case dom::MediaSourceEnum::Browser: + case dom::MediaSourceEnum::Screen: + case dom::MediaSourceEnum::Window: + case dom::MediaSourceEnum::Application: + newConfig.mVideoCodecMode = webrtc::VideoCodecMode::kScreensharing; + break; + + case dom::MediaSourceEnum::Camera: + case dom::MediaSourceEnum::Other: + // Other is used by canvas capture, which we treat as realtime video. + // This seems debatable, but we've been doing it this way for a long + // time, so this is likely fine. + newConfig.mVideoCodecMode = webrtc::VideoCodecMode::kRealtimeVideo; + break; + + case dom::MediaSourceEnum::Microphone: + case dom::MediaSourceEnum::AudioCapture: + case dom::MediaSourceEnum::EndGuard_: + MOZ_ASSERT(false); + break; + } + } + + std::vector configs; + RTCRtpTransceiver::NegotiatedDetailsToVideoCodecConfigs(details, &configs); + + if (configs.empty()) { + // TODO: Are we supposed to plumb this error back to JS? This does not + // seem like a failure to set an answer, it just means that codec + // negotiation failed. For now, we're just doing the same thing we do + // if negotiation as a whole failed. + MOZ_LOG(gSenderLog, LogLevel::Error, + ("%s[%s]: %s No video codecs were negotiated (send).", + mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__)); + return Nothing(); + } + + newConfig.mVideoCodec = Some(configs[0]); + // Spec says that we start using new parameters right away, _before_ we + // update the parameters that are visible to JS (ie; mParameters). + const RTCRtpSendParameters& parameters = + mPendingParameters.isSome() ? *mPendingParameters : mParameters; + for (VideoCodecConfig::Encoding& conduitEncoding : + newConfig.mVideoCodec->mEncodings) { + for (const RTCRtpEncodingParameters& jsEncoding : parameters.mEncodings) { + std::string rid; + if (jsEncoding.mRid.WasPassed()) { + rid = NS_ConvertUTF16toUTF8(jsEncoding.mRid.Value()).get(); + } + if (conduitEncoding.rid == rid) { + ApplyJsEncodingToConduitEncoding(jsEncoding, &conduitEncoding); + break; + } + } + } + + newConfig.mVideoRtpRtcpConfig = Some(details.GetRtpRtcpConfig()); + + if (newConfig == oldConfig) { + MOZ_LOG(gSenderLog, LogLevel::Debug, + ("%s[%s]: %s No change in video config", mPc->GetHandle().c_str(), + GetMid().c_str(), __FUNCTION__)); + return Nothing(); + } + + if (newConfig.mVideoCodec.isSome()) { + MOZ_ASSERT(newConfig.mSsrcs.size() == + newConfig.mVideoCodec->mEncodings.size()); + } + return Some(newConfig); +} + +Maybe RTCRtpSender::GetNewAudioConfig() { + AudioConfig oldConfig; + oldConfig.mSsrcs = mSsrcs; + oldConfig.mLocalRtpExtensions = mLocalRtpExtensions; + oldConfig.mCname = mCname; + oldConfig.mTransmitting = mTransmitting; + oldConfig.mAudioCodec = mAudioCodec; + + AudioConfig newConfig(oldConfig); + + UpdateBaseConfig(&newConfig); + + if (GetJsepTransceiver().mSendTrack.GetNegotiatedDetails() && + GetJsepTransceiver().mSendTrack.GetActive()) { + const auto& details( + *GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()); + + std::vector configs; + RTCRtpTransceiver::NegotiatedDetailsToAudioCodecConfigs(details, &configs); + if (configs.empty()) { + // TODO: Are we supposed to plumb this error back to JS? This does not + // seem like a failure to set an answer, it just means that codec + // negotiation failed. For now, we're just doing the same thing we do + // if negotiation as a whole failed. + MOZ_LOG(gSenderLog, LogLevel::Error, + ("%s[%s]: %s No audio codecs were negotiated (send)", + mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__)); + return Nothing(); + } + + std::vector dtmfConfigs; + std::copy_if( + configs.begin(), configs.end(), std::back_inserter(dtmfConfigs), + [](const auto& value) { return value.mName == "telephone-event"; }); + + const AudioCodecConfig& sendCodec = configs[0]; + + if (!dtmfConfigs.empty()) { + // There is at least one telephone-event codec. + // We primarily choose the codec whose frequency matches the send codec. + // Secondarily we choose the one with the lowest frequency. + auto dtmfIterator = + std::find_if(dtmfConfigs.begin(), dtmfConfigs.end(), + [&sendCodec](const auto& dtmfCodec) { + return dtmfCodec.mFreq == sendCodec.mFreq; + }); + if (dtmfIterator == dtmfConfigs.end()) { + dtmfIterator = std::min_element( + dtmfConfigs.begin(), dtmfConfigs.end(), + [](const auto& a, const auto& b) { return a.mFreq < b.mFreq; }); + } + MOZ_ASSERT(dtmfIterator != dtmfConfigs.end()); + newConfig.mDtmfPt = dtmfIterator->mType; + newConfig.mDtmfFreq = dtmfIterator->mFreq; + } + + newConfig.mAudioCodec = Some(sendCodec); + } + + if (newConfig == oldConfig) { + MOZ_LOG(gSenderLog, LogLevel::Debug, + ("%s[%s]: %s No change in audio config", mPc->GetHandle().c_str(), + GetMid().c_str(), __FUNCTION__)); + return Nothing(); + } + + return Some(newConfig); +} + +void RTCRtpSender::UpdateBaseConfig(BaseConfig* aConfig) { + aConfig->mSsrcs = GetJsepTransceiver().mSendTrack.GetSsrcs(); + aConfig->mCname = GetJsepTransceiver().mSendTrack.GetCNAME(); + + if (GetJsepTransceiver().mSendTrack.GetNegotiatedDetails() && + GetJsepTransceiver().mSendTrack.GetActive()) { + const auto& details( + *GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()); + { + std::vector extmaps; + // @@NG read extmap from track + details.ForEachRTPHeaderExtension( + [&extmaps](const SdpExtmapAttributeList::Extmap& extmap) { + extmaps.emplace_back(extmap.extensionname, extmap.entry); + }); + aConfig->mLocalRtpExtensions = extmaps; + } + } + // RTCRtpTransceiver::IsSending is updated after negotiation completes, in a + // queued task (which we may be in right now). Don't use + // JsepTrack::GetActive, because that updates before the queued task, which + // is too early for some of the things we interact with here (eg; + // RTCDTMFSender). + aConfig->mTransmitting = mTransceiver->IsSending(); +} + +void RTCRtpSender::ApplyVideoConfig(const VideoConfig& aConfig) { + if (aConfig.mVideoCodec.isSome()) { + MOZ_ASSERT(aConfig.mSsrcs.size() == aConfig.mVideoCodec->mEncodings.size()); + } + + mSsrcs = aConfig.mSsrcs; + mCname = aConfig.mCname; + mLocalRtpExtensions = aConfig.mLocalRtpExtensions; + + mVideoRtxSsrcs = aConfig.mVideoRtxSsrcs; + mVideoCodec = aConfig.mVideoCodec; + mVideoRtpRtcpConfig = aConfig.mVideoRtpRtcpConfig; + mVideoCodecMode = aConfig.mVideoCodecMode; + + mTransmitting = aConfig.mTransmitting; +} + +void RTCRtpSender::ApplyAudioConfig(const AudioConfig& aConfig) { + mTransmitting = false; + + mSsrcs = aConfig.mSsrcs; + mCname = aConfig.mCname; + mLocalRtpExtensions = aConfig.mLocalRtpExtensions; + + mAudioCodec = aConfig.mAudioCodec; + + if (aConfig.mDtmfPt >= 0) { + mDtmf->SetPayloadType(aConfig.mDtmfPt, aConfig.mDtmfFreq); + } + + mTransmitting = aConfig.mTransmitting; +} + +void RTCRtpSender::Stop() { + MOZ_ASSERT(mTransceiver->Stopped()); + mTransmitting = false; +} + +bool RTCRtpSender::HasTrack(const dom::MediaStreamTrack* aTrack) const { + if (!mSenderTrack) { + return false; + } + + if (!aTrack) { + return true; + } + + return mSenderTrack.get() == aTrack; +} + +RefPtr RTCRtpSender::GetPipeline() const { + return mPipeline; +} + +std::string RTCRtpSender::GetMid() const { return mTransceiver->GetMidAscii(); } + +JsepTransceiver& RTCRtpSender::GetJsepTransceiver() { + return mTransceiver->GetJsepTransceiver(); +} + +void RTCRtpSender::UpdateDtmfSender() { + if (!mDtmf) { + return; + } + + if (mTransmitting) { + return; + } + + mDtmf->StopPlayout(); +} + +} // namespace mozilla::dom + +#undef LOGTAG diff --git a/dom/media/webrtc/jsapi/RTCRtpSender.h b/dom/media/webrtc/jsapi/RTCRtpSender.h new file mode 100644 index 0000000000..0c1282e0db --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCRtpSender.h @@ -0,0 +1,260 @@ +/* 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/. */ + +#ifndef _RTCRtpSender_h_ +#define _RTCRtpSender_h_ + +#include "nsISupports.h" +#include "nsWrapperCache.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StateMirroring.h" +#include "mozilla/Maybe.h" +#include "js/RootingAPI.h" +#include "libwebrtcglue/RtpRtcpConfig.h" +#include "nsTArray.h" +#include "mozilla/dom/RTCStatsReportBinding.h" +#include "mozilla/dom/RTCRtpCapabilitiesBinding.h" +#include "mozilla/dom/RTCRtpParametersBinding.h" +#include "RTCStatsReport.h" +#include "jsep/JsepTrack.h" +#include "transportbridge/MediaPipeline.h" + +class nsPIDOMWindowInner; + +namespace mozilla { +class MediaSessionConduit; +class MediaTransportHandler; +class JsepTransceiver; +class PeerConnectionImpl; +class DOMMediaStream; + +namespace dom { +class MediaStreamTrack; +class Promise; +class RTCDtlsTransport; +class RTCDTMFSender; +struct RTCRtpCapabilities; +class RTCRtpTransceiver; + +class RTCRtpSender : public nsISupports, + public nsWrapperCache, + public MediaPipelineTransmitControlInterface { + public: + RTCRtpSender(nsPIDOMWindowInner* aWindow, PeerConnectionImpl* aPc, + MediaTransportHandler* aTransportHandler, + AbstractThread* aCallThread, nsISerialEventTarget* aStsThread, + MediaSessionConduit* aConduit, dom::MediaStreamTrack* aTrack, + const Sequence& aEncodings, + RTCRtpTransceiver* aTransceiver); + + // nsISupports + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCRtpSender) + + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + // webidl + MediaStreamTrack* GetTrack() const { return mSenderTrack; } + RTCDtlsTransport* GetTransport() const; + RTCDTMFSender* GetDtmf() const; + MOZ_CAN_RUN_SCRIPT + already_AddRefed ReplaceTrack(MediaStreamTrack* aWithTrack, + ErrorResult& aError); + already_AddRefed GetStats(ErrorResult& aError); + static void GetCapabilities(const GlobalObject&, const nsAString& kind, + Nullable& result); + already_AddRefed SetParameters( + const dom::RTCRtpSendParameters& aParameters, ErrorResult& aError); + // Not a simple getter, so not const + // See https://w3c.github.io/webrtc-pc/#dom-rtcrtpsender-getparameters + void GetParameters(RTCRtpSendParameters& aParameters); + + static void CheckAndRectifyEncodings( + Sequence& aEncodings, bool aVideo, + ErrorResult& aRv); + + nsPIDOMWindowInner* GetParentObject() const; + nsTArray> GetStatsInternal( + bool aSkipIceStats = false); + + void SetStreams(const Sequence>& aStreams, + ErrorResult& aRv); + // ChromeOnly webidl + void GetStreams(nsTArray>& aStreams); + // ChromeOnly webidl + void SetStreamsImpl(const Sequence>& aStreams); + // ChromeOnly webidl + void SetTrack(const RefPtr& aTrack); + void Shutdown(); + void BreakCycles(); + // Terminal state, reached through stopping RTCRtpTransceiver. + void Stop(); + bool HasTrack(const dom::MediaStreamTrack* aTrack) const; + bool IsMyPc(const PeerConnectionImpl* aPc) const { return mPc.get() == aPc; } + RefPtr GetPipeline() const; + already_AddRefed MakePromise(ErrorResult& aError) const; + bool SeamlessTrackSwitch(const RefPtr& aWithTrack); + bool SetSenderTrackWithClosedCheck(const RefPtr& aTrack); + + // This is called when we set an answer (ie; when the transport is finalized). + void UpdateTransport(); + void SyncToJsep(JsepTransceiver& aJsepTransceiver) const; + void SyncFromJsep(const JsepTransceiver& aJsepTransceiver); + void MaybeUpdateConduit(); + + AbstractCanonical* CanonicalSsrcs() { return &mSsrcs; } + AbstractCanonical* CanonicalVideoRtxSsrcs() { return &mVideoRtxSsrcs; } + AbstractCanonical* CanonicalLocalRtpExtensions() { + return &mLocalRtpExtensions; + } + + AbstractCanonical>* CanonicalAudioCodec() { + return &mAudioCodec; + } + + AbstractCanonical>* CanonicalVideoCodec() { + return &mVideoCodec; + } + AbstractCanonical>* CanonicalVideoRtpRtcpConfig() { + return &mVideoRtpRtcpConfig; + } + AbstractCanonical* CanonicalVideoCodecMode() { + return &mVideoCodecMode; + } + AbstractCanonical* CanonicalCname() { return &mCname; } + AbstractCanonical* CanonicalTransmitting() override { + return &mTransmitting; + } + + bool HasPendingSetParameters() const { return mPendingParameters.isSome(); } + void InvalidateLastReturnedParameters() { + mLastReturnedParameters = Nothing(); + } + + private: + virtual ~RTCRtpSender(); + + std::string GetMid() const; + JsepTransceiver& GetJsepTransceiver(); + static void ApplyJsEncodingToConduitEncoding( + const RTCRtpEncodingParameters& aJsEncoding, + VideoCodecConfig::Encoding* aConduitEncoding); + void UpdateRestorableEncodings( + const Sequence& aEncodings); + Sequence GetMatchingEncodings( + const std::vector& aRids) const; + Sequence ToSendEncodings( + const std::vector& aRids) const; + void MaybeGetJsepRids(); + void UpdateDtmfSender(); + + void WarnAboutBadSetParameters(const nsCString& aError); + nsCString GetEffectiveTLDPlus1() const; + + WatchManager mWatchManager; + nsCOMPtr mWindow; + RefPtr mPc; + RefPtr mSenderTrack; + bool mAddTrackCalled = false; + RTCRtpSendParameters mParameters; + Maybe mPendingParameters; + uint32_t mNumSetParametersCalls = 0; + // When JSEP goes from simulcast to unicast without a rid, and we started out + // as unicast without a rid, we are supposed to restore that unicast encoding + // from before. + Maybe mUnicastEncoding; + bool mSimulcastEnvelopeSet = false; + bool mSimulcastEnvelopeSetByJSEP = false; + bool mPendingRidChangeFromCompatMode = false; + Maybe mLastReturnedParameters; + RefPtr mPipeline; + RefPtr mTransportHandler; + RefPtr mTransceiver; + nsTArray> mStreams; + bool mHaveSetupTransport = false; + // TODO(bug 1803388): Remove this stuff once it is no longer needed. + bool mAllowOldSetParameters = false; + + // TODO(bug 1803388): Remove the glean warnings once they are no longer needed + bool mHaveWarnedBecauseNoGetParameters = false; + bool mHaveWarnedBecauseEncodingCountChange = false; + bool mHaveWarnedBecauseNoTransactionId = false; + bool mHaveWarnedBecauseStaleTransactionId = false; + // TODO(bug 1803389): Remove the glean errors once they are no longer needed. + bool mHaveFailedBecauseNoGetParameters = false; + bool mHaveFailedBecauseEncodingCountChange = false; + bool mHaveFailedBecauseRidChange = false; + bool mHaveFailedBecauseNoTransactionId = false; + bool mHaveFailedBecauseStaleTransactionId = false; + bool mHaveFailedBecauseNoEncodings = false; + bool mHaveFailedBecauseOtherError = false; + + RefPtr mDtmf; + + class BaseConfig { + public: + // TODO(bug 1744116): Use = default here + bool operator==(const BaseConfig& aOther) const { + return mSsrcs == aOther.mSsrcs && + mLocalRtpExtensions == aOther.mLocalRtpExtensions && + mCname == aOther.mCname && mTransmitting == aOther.mTransmitting; + } + Ssrcs mSsrcs; + RtpExtList mLocalRtpExtensions; + std::string mCname; + bool mTransmitting = false; + }; + + class VideoConfig : public BaseConfig { + public: + // TODO(bug 1744116): Use = default here + bool operator==(const VideoConfig& aOther) const { + return BaseConfig::operator==(aOther) && + mVideoRtxSsrcs == aOther.mVideoRtxSsrcs && + mVideoCodec == aOther.mVideoCodec && + mVideoRtpRtcpConfig == aOther.mVideoRtpRtcpConfig && + mVideoCodecMode == aOther.mVideoCodecMode; + } + Ssrcs mVideoRtxSsrcs; + Maybe mVideoCodec; + Maybe mVideoRtpRtcpConfig; + webrtc::VideoCodecMode mVideoCodecMode = + webrtc::VideoCodecMode::kRealtimeVideo; + }; + + class AudioConfig : public BaseConfig { + public: + // TODO(bug 1744116): Use = default here + bool operator==(const AudioConfig& aOther) const { + return BaseConfig::operator==(aOther) && + mAudioCodec == aOther.mAudioCodec && mDtmfPt == aOther.mDtmfPt && + mDtmfFreq == aOther.mDtmfFreq; + } + Maybe mAudioCodec; + int32_t mDtmfPt = -1; + int32_t mDtmfFreq = 0; + }; + + Maybe GetNewVideoConfig(); + Maybe GetNewAudioConfig(); + void UpdateBaseConfig(BaseConfig* aConfig); + void ApplyVideoConfig(const VideoConfig& aConfig); + void ApplyAudioConfig(const AudioConfig& aConfig); + + Canonical mSsrcs; + Canonical mVideoRtxSsrcs; + Canonical mLocalRtpExtensions; + + Canonical> mAudioCodec; + Canonical> mVideoCodec; + Canonical> mVideoRtpRtcpConfig; + Canonical mVideoCodecMode; + Canonical mCname; + Canonical mTransmitting; +}; + +} // namespace dom +} // namespace mozilla +#endif // _RTCRtpSender_h_ diff --git a/dom/media/webrtc/jsapi/RTCRtpTransceiver.cpp b/dom/media/webrtc/jsapi/RTCRtpTransceiver.cpp new file mode 100644 index 0000000000..bf4f74dd5d --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCRtpTransceiver.cpp @@ -0,0 +1,1080 @@ +/* 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 "jsapi/RTCRtpTransceiver.h" +#include "mozilla/UniquePtr.h" +#include +#include +#include +#include "libwebrtcglue/AudioConduit.h" +#include "libwebrtcglue/VideoConduit.h" +#include "MediaTrackGraph.h" +#include "transportbridge/MediaPipeline.h" +#include "transportbridge/MediaPipelineFilter.h" +#include "jsep/JsepTrack.h" +#include "sdp/SdpHelper.h" +#include "MediaTrackGraphImpl.h" +#include "transport/logging.h" +#include "MediaEngine.h" +#include "nsIPrincipal.h" +#include "MediaSegment.h" +#include "RemoteTrackSource.h" +#include "libwebrtcglue/RtpRtcpConfig.h" +#include "MediaTransportHandler.h" +#include "mozilla/dom/RTCRtpReceiverBinding.h" +#include "mozilla/dom/RTCRtpSenderBinding.h" +#include "mozilla/dom/RTCRtpTransceiverBinding.h" +#include "mozilla/dom/Promise.h" +#include "RTCDtlsTransport.h" +#include "RTCRtpReceiver.h" +#include "RTCRtpSender.h" +#include "RTCDTMFSender.h" +#include "systemservices/MediaUtils.h" +#include "libwebrtcglue/WebrtcCallWrapper.h" +#include "libwebrtcglue/WebrtcGmpVideoCodec.h" +#include "utils/PerformanceRecorder.h" + +namespace mozilla { + +using namespace dom; + +namespace { +struct ConduitControlState : public AudioConduitControlInterface, + public VideoConduitControlInterface { + ConduitControlState(RTCRtpTransceiver* aTransceiver, RTCRtpSender* aSender, + RTCRtpReceiver* aReceiver) + : mTransceiver(new nsMainThreadPtrHolder( + "ConduitControlState::mTransceiver", aTransceiver, false)), + mSender(new nsMainThreadPtrHolder( + "ConduitControlState::mSender", aSender, false)), + mReceiver(new nsMainThreadPtrHolder( + "ConduitControlState::mReceiver", aReceiver, false)) {} + + const nsMainThreadPtrHandle mTransceiver; + const nsMainThreadPtrHandle mSender; + const nsMainThreadPtrHandle mReceiver; + + // MediaConduitControlInterface + AbstractCanonical* CanonicalReceiving() override { + return mReceiver->CanonicalReceiving(); + } + AbstractCanonical* CanonicalTransmitting() override { + return mSender->CanonicalTransmitting(); + } + AbstractCanonical* CanonicalLocalSsrcs() override { + return mSender->CanonicalSsrcs(); + } + AbstractCanonical* CanonicalLocalCname() override { + return mSender->CanonicalCname(); + } + AbstractCanonical* CanonicalMid() override { + return mTransceiver->CanonicalMid(); + } + AbstractCanonical* CanonicalRemoteSsrc() override { + return mReceiver->CanonicalSsrc(); + } + AbstractCanonical* CanonicalSyncGroup() override { + return mTransceiver->CanonicalSyncGroup(); + } + AbstractCanonical* CanonicalLocalRecvRtpExtensions() override { + return mReceiver->CanonicalLocalRtpExtensions(); + } + AbstractCanonical* CanonicalLocalSendRtpExtensions() override { + return mSender->CanonicalLocalRtpExtensions(); + } + + // AudioConduitControlInterface + AbstractCanonical>* CanonicalAudioSendCodec() + override { + return mSender->CanonicalAudioCodec(); + } + AbstractCanonical>* CanonicalAudioRecvCodecs() + override { + return mReceiver->CanonicalAudioCodecs(); + } + MediaEventSource& OnDtmfEvent() override { + return mSender->GetDtmf()->OnDtmfEvent(); + } + + // VideoConduitControlInterface + AbstractCanonical* CanonicalLocalVideoRtxSsrcs() override { + return mSender->CanonicalVideoRtxSsrcs(); + } + AbstractCanonical* CanonicalRemoteVideoRtxSsrc() override { + return mReceiver->CanonicalVideoRtxSsrc(); + } + AbstractCanonical>* CanonicalVideoSendCodec() + override { + return mSender->CanonicalVideoCodec(); + } + AbstractCanonical>* CanonicalVideoSendRtpRtcpConfig() + override { + return mSender->CanonicalVideoRtpRtcpConfig(); + } + AbstractCanonical>* CanonicalVideoRecvCodecs() + override { + return mReceiver->CanonicalVideoCodecs(); + } + AbstractCanonical>* CanonicalVideoRecvRtpRtcpConfig() + override { + return mReceiver->CanonicalVideoRtpRtcpConfig(); + } + AbstractCanonical* CanonicalVideoCodecMode() + override { + return mSender->CanonicalVideoCodecMode(); + } +}; +} // namespace + +MOZ_MTLOG_MODULE("RTCRtpTransceiver") + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCRtpTransceiver) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(RTCRtpTransceiver) + if (tmp->mHandlingUnlink) { + tmp->BreakCycles(); + tmp->mHandlingUnlink = false; + } + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(RTCRtpTransceiver) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow, mPc, mSendTrack, mReceiver, + mSender, mDtlsTransport, + mLastStableDtlsTransport) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCRtpTransceiver) +NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCRtpTransceiver) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCRtpTransceiver) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +#define INIT_CANONICAL(name, val) \ + name(AbstractThread::MainThread(), val, \ + "RTCRtpTransceiver::" #name " (Canonical)") + +RTCRtpTransceiver::RTCRtpTransceiver( + nsPIDOMWindowInner* aWindow, bool aPrivacyNeeded, PeerConnectionImpl* aPc, + MediaTransportHandler* aTransportHandler, JsepSession* aJsepSession, + const std::string& aTransceiverId, bool aIsVideo, + nsISerialEventTarget* aStsThread, dom::MediaStreamTrack* aSendTrack, + WebrtcCallWrapper* aCallWrapper, RTCStatsIdGenerator* aIdGenerator) + : mWindow(aWindow), + mPc(aPc), + mTransportHandler(aTransportHandler), + mTransceiverId(aTransceiverId), + mJsepTransceiver(*aJsepSession->GetTransceiver(mTransceiverId)), + mStsThread(aStsThread), + mCallWrapper(aCallWrapper), + mSendTrack(aSendTrack), + mIdGenerator(aIdGenerator), + mPrincipalPrivacy(aPrivacyNeeded ? PrincipalPrivacy::Private + : PrincipalPrivacy::NonPrivate), + mIsVideo(aIsVideo), + INIT_CANONICAL(mMid, std::string()), + INIT_CANONICAL(mSyncGroup, std::string()) {} + +#undef INIT_CANONICAL + +RTCRtpTransceiver::~RTCRtpTransceiver() = default; + +SdpDirectionAttribute::Direction ToSdpDirection( + RTCRtpTransceiverDirection aDirection) { + switch (aDirection) { + case dom::RTCRtpTransceiverDirection::Sendrecv: + return SdpDirectionAttribute::Direction::kSendrecv; + case dom::RTCRtpTransceiverDirection::Sendonly: + return SdpDirectionAttribute::Direction::kSendonly; + case dom::RTCRtpTransceiverDirection::Recvonly: + return SdpDirectionAttribute::Direction::kRecvonly; + case dom::RTCRtpTransceiverDirection::Inactive: + return SdpDirectionAttribute::Direction::kInactive; + case dom::RTCRtpTransceiverDirection::EndGuard_:; + } + MOZ_CRASH("Invalid transceiver direction!"); +} + +static uint32_t sRemoteSourceId = 0; + +// TODO(bug 1401592): Once we implement the sendEncodings stuff, there will +// need to be validation code in here. +void RTCRtpTransceiver::Init(const RTCRtpTransceiverInit& aInit, + ErrorResult& aRv) { + TrackingId trackingId(TrackingId::Source::RTCRtpReceiver, sRemoteSourceId++, + TrackingId::TrackAcrossProcesses::Yes); + if (IsVideo()) { + InitVideo(trackingId); + } else { + InitAudio(); + } + + if (!IsValid()) { + aRv = NS_ERROR_UNEXPECTED; + return; + } + + mReceiver = new RTCRtpReceiver(mWindow, mPrincipalPrivacy, mPc, + mTransportHandler, mCallWrapper->mCallThread, + mStsThread, mConduit, this, trackingId); + + mSender = new RTCRtpSender(mWindow, mPc, mTransportHandler, + mCallWrapper->mCallThread, mStsThread, mConduit, + mSendTrack, aInit.mSendEncodings, this); + + if (mConduit) { + InitConduitControl(); + } + + mSender->SetStreamsImpl(aInit.mStreams); + mDirection = aInit.mDirection; +} + +void RTCRtpTransceiver::SetDtlsTransport(dom::RTCDtlsTransport* aDtlsTransport, + bool aStable) { + mDtlsTransport = aDtlsTransport; + if (aStable) { + mLastStableDtlsTransport = mDtlsTransport; + } +} + +void RTCRtpTransceiver::RollbackToStableDtlsTransport() { + mDtlsTransport = mLastStableDtlsTransport; +} + +void RTCRtpTransceiver::InitAudio() { + mConduit = AudioSessionConduit::Create(mCallWrapper, mStsThread); + + if (!mConduit) { + MOZ_MTLOG(ML_ERROR, mPc->GetHandle() + << "[" << mMid.Ref() << "]: " << __FUNCTION__ + << ": Failed to create AudioSessionConduit"); + // TODO(bug 1422897): We need a way to record this when it happens in the + // wild. + } +} + +void RTCRtpTransceiver::InitVideo(const TrackingId& aRecvTrackingId) { + VideoSessionConduit::Options options; + options.mVideoLatencyTestEnable = + Preferences::GetBool("media.video.test_latency", false); + options.mMinBitrate = std::max( + 0, + Preferences::GetInt("media.peerconnection.video.min_bitrate", 0) * 1000); + options.mStartBitrate = std::max( + 0, Preferences::GetInt("media.peerconnection.video.start_bitrate", 0) * + 1000); + options.mPrefMaxBitrate = std::max( + 0, + Preferences::GetInt("media.peerconnection.video.max_bitrate", 0) * 1000); + if (options.mMinBitrate != 0 && + options.mMinBitrate < kViEMinCodecBitrate_bps) { + options.mMinBitrate = kViEMinCodecBitrate_bps; + } + if (options.mStartBitrate < options.mMinBitrate) { + options.mStartBitrate = options.mMinBitrate; + } + if (options.mPrefMaxBitrate && + options.mStartBitrate > options.mPrefMaxBitrate) { + options.mStartBitrate = options.mPrefMaxBitrate; + } + // XXX We'd love if this was a live param for testing adaptation/etc + // in automation + options.mMinBitrateEstimate = + std::max(0, Preferences::GetInt( + "media.peerconnection.video.min_bitrate_estimate", 0) * + 1000); + options.mSpatialLayers = std::max( + 1, Preferences::GetInt("media.peerconnection.video.svc.spatial", 0)); + options.mTemporalLayers = std::max( + 1, Preferences::GetInt("media.peerconnection.video.svc.temporal", 0)); + options.mDenoising = + Preferences::GetBool("media.peerconnection.video.denoising", false); + options.mLockScaling = + Preferences::GetBool("media.peerconnection.video.lock_scaling", false); + + mConduit = + VideoSessionConduit::Create(mCallWrapper, mStsThread, std::move(options), + mPc->GetHandle(), aRecvTrackingId); + + if (!mConduit) { + MOZ_MTLOG(ML_ERROR, mPc->GetHandle() + << "[" << mMid.Ref() << "]: " << __FUNCTION__ + << ": Failed to create VideoSessionConduit"); + // TODO(bug 1422897): We need a way to record this when it happens in the + // wild. + } +} + +void RTCRtpTransceiver::InitConduitControl() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mConduit); + ConduitControlState control(this, mSender, mReceiver); + mCallWrapper->mCallThread->Dispatch(NS_NewRunnableFunction( + __func__, [conduit = mConduit, control = std::move(control)]() mutable { + conduit->AsVideoSessionConduit().apply( + [&](VideoSessionConduit* aConduit) { + aConduit->InitControl(&control); + }); + conduit->AsAudioSessionConduit().apply( + [&](AudioSessionConduit* aConduit) { + aConduit->InitControl(&control); + }); + })); +} + +void RTCRtpTransceiver::Close() { + // Called via PCImpl::Close -> PCImpl::CloseInt -> PCImpl::ShutdownMedia -> + // PCMedia::SelfDestruct. Satisfies step 7 of + // https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-close + mShutdown = true; + if (mDtlsTransport) { + mDtlsTransport->UpdateState(TransportLayer::TS_CLOSED); + } + StopImpl(); +} + +void RTCRtpTransceiver::BreakCycles() { + mSender->BreakCycles(); + mReceiver->BreakCycles(); + mWindow = nullptr; + mSendTrack = nullptr; + mSender = nullptr; + mReceiver = nullptr; + mDtlsTransport = nullptr; + mLastStableDtlsTransport = nullptr; + mPc = nullptr; +} + +// TODO: Only called from one place in PeerConnectionImpl, synchronously, when +// the JSEP engine has successfully completed an offer/answer exchange. This is +// a bit squirrely, since identity validation happens asynchronously in +// PeerConnection.jsm. This probably needs to happen once all the "in parallel" +// steps have succeeded, but before we queue the task for JS observable state +// updates. +nsresult RTCRtpTransceiver::UpdateTransport() { + if (!mHasTransport) { + return NS_OK; + } + + mReceiver->UpdateTransport(); + mSender->UpdateTransport(); + return NS_OK; +} + +nsresult RTCRtpTransceiver::UpdateConduit() { + if (mStopped) { + return NS_OK; + } + + mReceiver->UpdateConduit(); + mSender->MaybeUpdateConduit(); + + return NS_OK; +} + +void RTCRtpTransceiver::UpdatePrincipalPrivacy(PrincipalPrivacy aPrivacy) { + if (mPrincipalPrivacy == aPrivacy) { + return; + } + + mPrincipalPrivacy = aPrivacy; + mReceiver->UpdatePrincipalPrivacy(mPrincipalPrivacy); +} + +void RTCRtpTransceiver::ResetSync() { mSyncGroup = std::string(); } + +// TODO: Only called from one place in PeerConnectionImpl, synchronously, when +// the JSEP engine has successfully completed an offer/answer exchange. This is +// a bit squirrely, since identity validation happens asynchronously in +// PeerConnection.jsm. This probably needs to happen once all the "in parallel" +// steps have succeeded, but before we queue the task for JS observable state +// updates. +nsresult RTCRtpTransceiver::SyncWithMatchingVideoConduits( + nsTArray>& transceivers) { + if (mStopped) { + return NS_OK; + } + + if (IsVideo()) { + MOZ_MTLOG(ML_ERROR, mPc->GetHandle() + << "[" << mMid.Ref() << "]: " << __FUNCTION__ + << " called when transceiver is not " + "video! This should never happen."); + MOZ_CRASH(); + return NS_ERROR_UNEXPECTED; + } + + std::set myReceiveStreamIds; + myReceiveStreamIds.insert(mReceiver->GetStreamIds().begin(), + mReceiver->GetStreamIds().end()); + + for (RefPtr& transceiver : transceivers) { + if (!transceiver->IsValid()) { + continue; + } + + if (!transceiver->IsVideo()) { + // |this| is an audio transceiver, so we skip other audio transceivers + continue; + } + + // Maybe could make this more efficient by cacheing this set, but probably + // not worth it. + for (const std::string& streamId : + transceiver->Receiver()->GetStreamIds()) { + if (myReceiveStreamIds.count(streamId)) { + // Ok, we have one video, one non-video - cross the streams! + mSyncGroup = streamId; + transceiver->mSyncGroup = streamId; + + MOZ_MTLOG(ML_DEBUG, mPc->GetHandle() + << "[" << mMid.Ref() << "]: " << __FUNCTION__ + << " Syncing " << mConduit.get() << " to " + << transceiver->mConduit.get()); + + // The sync code in call.cc only permits sync between audio stream and + // one video stream. They take the first match, so there's no point in + // continuing here. If we want to change the default, we should sort + // video streams here and only call SetSyncGroup on the chosen stream. + break; + } + } + } + + return NS_OK; +} + +bool RTCRtpTransceiver::ConduitHasPluginID(uint64_t aPluginID) { + return mConduit && mConduit->HasCodecPluginID(aPluginID); +} + +void RTCRtpTransceiver::SyncFromJsep(const JsepSession& aSession) { + MOZ_MTLOG(ML_DEBUG, mPc->GetHandle() + << "[" << mMid.Ref() << "]: " << __FUNCTION__ + << " Syncing from JSEP transceiver"); + if (mShutdown) { + // Shutdown_m has already been called, probably due to pc.close(). Just + // nod and smile. + return; + } + + mJsepTransceiver = *aSession.GetTransceiver(mTransceiverId); + + // Transceivers can stop due to JSEP negotiation, so we need to check that + if (mJsepTransceiver.IsStopped()) { + StopImpl(); + } + + mReceiver->SyncFromJsep(mJsepTransceiver); + mSender->SyncFromJsep(mJsepTransceiver); + + // mid from JSEP + if (mJsepTransceiver.IsAssociated()) { + mMid = mJsepTransceiver.GetMid(); + } else { + mMid = std::string(); + } + + // currentDirection from JSEP, but not if "this transceiver has never been + // represented in an offer/answer exchange" + if (mJsepTransceiver.HasLevel() && mJsepTransceiver.IsNegotiated()) { + if (mJsepTransceiver.mRecvTrack.GetActive()) { + if (mJsepTransceiver.mSendTrack.GetActive()) { + mCurrentDirection.SetValue(dom::RTCRtpTransceiverDirection::Sendrecv); + mHasBeenUsedToSend = true; + } else { + mCurrentDirection.SetValue(dom::RTCRtpTransceiverDirection::Recvonly); + } + } else { + if (mJsepTransceiver.mSendTrack.GetActive()) { + mCurrentDirection.SetValue(dom::RTCRtpTransceiverDirection::Sendonly); + mHasBeenUsedToSend = true; + } else { + mCurrentDirection.SetValue(dom::RTCRtpTransceiverDirection::Inactive); + } + } + } + + mShouldRemove = mJsepTransceiver.IsRemoved(); + mHasTransport = mJsepTransceiver.HasLevel() && !mJsepTransceiver.IsStopped(); +} + +void RTCRtpTransceiver::SyncToJsep(JsepSession& aSession) const { + MOZ_MTLOG(ML_DEBUG, mPc->GetHandle() + << "[" << mMid.Ref() << "]: " << __FUNCTION__ + << " Syncing to JSEP transceiver"); + + aSession.ApplyToTransceiver( + mTransceiverId, [this, self = RefPtr(this)]( + JsepTransceiver& aTransceiver) { + mReceiver->SyncToJsep(aTransceiver); + mSender->SyncToJsep(aTransceiver); + aTransceiver.mJsDirection = ToSdpDirection(mDirection); + if (mStopped) { + aTransceiver.Stop(); + } + }); +} + +void RTCRtpTransceiver::GetKind(nsAString& aKind) const { + // The transceiver kind of an RTCRtpTransceiver is defined by the kind of the + // associated RTCRtpReceiver's MediaStreamTrack object. + MOZ_ASSERT(mReceiver && mReceiver->Track()); + mReceiver->Track()->GetKind(aKind); +} + +void RTCRtpTransceiver::GetMid(nsAString& aMid) const { + if (!mMid.Ref().empty()) { + aMid = NS_ConvertUTF8toUTF16(mMid.Ref()); + } else { + aMid.SetIsVoid(true); + } +} + +std::string RTCRtpTransceiver::GetMidAscii() const { + if (mMid.Ref().empty()) { + return std::string(); + } + + return mMid.Ref(); +} + +void RTCRtpTransceiver::SetDirection(RTCRtpTransceiverDirection aDirection, + ErrorResult& aRv) { + if (mStopped) { + aRv.ThrowInvalidStateError("Transceiver is stopped!"); + return; + } + + if (aDirection == mDirection) { + return; + } + + SetDirectionInternal(aDirection); + + mPc->UpdateNegotiationNeeded(); +} + +void RTCRtpTransceiver::SetDirectionInternal( + RTCRtpTransceiverDirection aDirection) { + // We do not update the direction on the JsepTransceiver until sync + mDirection = aDirection; +} + +bool RTCRtpTransceiver::ShouldRemove() const { return mShouldRemove; } + +bool RTCRtpTransceiver::CanSendDTMF() const { + // Spec says: "If connection's RTCPeerConnectionState is not "connected" + // return false." We don't support that right now. This is supposed to be + // true once ICE is complete, and _all_ DTLS handshakes are also complete. We + // don't really have access to the state of _all_ of our DTLS states either. + // Our pipeline _does_ know whether SRTP/SRTCP is ready, which happens + // immediately after our transport finishes DTLS (unless there was an error), + // so this is pretty close. + // TODO (bug 1265827): Base this on RTCPeerConnectionState instead. + // TODO (bug 1623193): Tighten this up + if (!IsSending() || !mSender->GetTrack()) { + return false; + } + + // Ok, it looks like the connection is up and sending. Did we negotiate + // telephone-event? + const JsepTrackNegotiatedDetails* details = + mJsepTransceiver.mSendTrack.GetNegotiatedDetails(); + if (NS_WARN_IF(!details || !details->GetEncodingCount())) { + // What? + return false; + } + + for (size_t i = 0; i < details->GetEncodingCount(); ++i) { + const auto& encoding = details->GetEncoding(i); + for (const auto& codec : encoding.GetCodecs()) { + if (codec->mName == "telephone-event") { + return true; + } + } + } + + return false; +} + +JSObject* RTCRtpTransceiver::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + return dom::RTCRtpTransceiver_Binding::Wrap(aCx, this, aGivenProto); +} + +nsPIDOMWindowInner* RTCRtpTransceiver::GetParentObject() const { + return mWindow; +} + +static void JsepCodecDescToAudioCodecConfig( + const JsepAudioCodecDescription& aCodec, Maybe* aConfig) { + uint16_t pt; + + // TODO(bug 1761272): Getting the pt for a JsepAudioCodecDescription should be + // infallible. + if (NS_WARN_IF(!aCodec.GetPtAsInt(&pt))) { + MOZ_MTLOG(ML_ERROR, "Invalid payload type: " << aCodec.mDefaultPt); + MOZ_ASSERT(false); + return; + } + + // libwebrtc crashes if we attempt to configure a mono recv codec + bool sendMono = aCodec.mForceMono && aCodec.mDirection == sdp::kSend; + + *aConfig = Some(AudioCodecConfig( + pt, aCodec.mName, static_cast(aCodec.mClock), + sendMono ? 1 : static_cast(aCodec.mChannels), aCodec.mFECEnabled)); + (*aConfig)->mMaxPlaybackRate = static_cast(aCodec.mMaxPlaybackRate); + (*aConfig)->mDtmfEnabled = aCodec.mDtmfEnabled; + (*aConfig)->mDTXEnabled = aCodec.mDTXEnabled; + (*aConfig)->mMaxAverageBitrate = aCodec.mMaxAverageBitrate; + (*aConfig)->mFrameSizeMs = aCodec.mFrameSizeMs; + (*aConfig)->mMinFrameSizeMs = aCodec.mMinFrameSizeMs; + (*aConfig)->mMaxFrameSizeMs = aCodec.mMaxFrameSizeMs; + (*aConfig)->mCbrEnabled = aCodec.mCbrEnabled; +} + +// TODO: This and the next function probably should move to JsepTransceiver +Maybe>&> +RTCRtpTransceiver::GetNegotiatedSendCodecs() const { + if (!mJsepTransceiver.mSendTrack.GetActive()) { + return Nothing(); + } + + const auto* details = mJsepTransceiver.mSendTrack.GetNegotiatedDetails(); + if (!details) { + return Nothing(); + } + + if (details->GetEncodingCount() == 0) { + return Nothing(); + } + + return SomeRef(details->GetEncoding(0).GetCodecs()); +} + +Maybe>&> +RTCRtpTransceiver::GetNegotiatedRecvCodecs() const { + if (!mJsepTransceiver.mRecvTrack.GetActive()) { + return Nothing(); + } + + const auto* details = mJsepTransceiver.mRecvTrack.GetNegotiatedDetails(); + if (!details) { + return Nothing(); + } + + if (details->GetEncodingCount() == 0) { + return Nothing(); + } + + return SomeRef(details->GetEncoding(0).GetCodecs()); +} + +// TODO: Maybe move this someplace else? +/*static*/ +void RTCRtpTransceiver::NegotiatedDetailsToAudioCodecConfigs( + const JsepTrackNegotiatedDetails& aDetails, + std::vector* aConfigs) { + Maybe telephoneEvent; + + if (aDetails.GetEncodingCount()) { + for (const auto& codec : aDetails.GetEncoding(0).GetCodecs()) { + if (NS_WARN_IF(codec->Type() != SdpMediaSection::kAudio)) { + MOZ_ASSERT(false, "Codec is not audio! This is a JSEP bug."); + return; + } + Maybe config; + const JsepAudioCodecDescription& audio = + static_cast(*codec); + JsepCodecDescToAudioCodecConfig(audio, &config); + if (config->mName == "telephone-event") { + telephoneEvent = std::move(config); + } else { + aConfigs->push_back(std::move(*config)); + } + } + } + + // Put telephone event at the back, because webrtc.org crashes if we don't + // If we need to do even more sorting, we should use std::sort. + if (telephoneEvent) { + aConfigs->push_back(std::move(*telephoneEvent)); + } +} + +auto RTCRtpTransceiver::GetActivePayloadTypes() const + -> RefPtr { + if (!mConduit) { + return ActivePayloadTypesPromise::CreateAndResolve(PayloadTypes(), + __func__); + } + + if (!mCallWrapper) { + return ActivePayloadTypesPromise::CreateAndResolve(PayloadTypes(), + __func__); + } + + return InvokeAsync(mCallWrapper->mCallThread, __func__, + [conduit = mConduit]() { + PayloadTypes pts; + pts.mSendPayloadType = conduit->ActiveSendPayloadType(); + pts.mRecvPayloadType = conduit->ActiveRecvPayloadType(); + return ActivePayloadTypesPromise::CreateAndResolve( + std::move(pts), __func__); + }); +} + +static void JsepCodecDescToVideoCodecConfig( + const JsepVideoCodecDescription& aCodec, Maybe* aConfig) { + uint16_t pt; + + // TODO(bug 1761272): Getting the pt for a JsepVideoCodecDescription should be + // infallible. + if (NS_WARN_IF(!aCodec.GetPtAsInt(&pt))) { + MOZ_MTLOG(ML_ERROR, "Invalid payload type: " << aCodec.mDefaultPt); + MOZ_ASSERT(false); + return; + } + + UniquePtr h264Config; + + if (aCodec.mName == "H264") { + h264Config = MakeUnique(); + size_t spropSize = sizeof(h264Config->sprop_parameter_sets); + strncpy(h264Config->sprop_parameter_sets, + aCodec.mSpropParameterSets.c_str(), spropSize); + h264Config->sprop_parameter_sets[spropSize - 1] = '\0'; + h264Config->packetization_mode = + static_cast(aCodec.mPacketizationMode); + h264Config->profile_level_id = static_cast(aCodec.mProfileLevelId); + h264Config->tias_bw = 0; // TODO(bug 1403206) + } + + *aConfig = Some(VideoCodecConfig(pt, aCodec.mName, aCodec.mConstraints, + h264Config.get())); + + (*aConfig)->mAckFbTypes = aCodec.mAckFbTypes; + (*aConfig)->mNackFbTypes = aCodec.mNackFbTypes; + (*aConfig)->mCcmFbTypes = aCodec.mCcmFbTypes; + (*aConfig)->mRembFbSet = aCodec.RtcpFbRembIsSet(); + (*aConfig)->mFECFbSet = aCodec.mFECEnabled; + (*aConfig)->mTransportCCFbSet = aCodec.RtcpFbTransportCCIsSet(); + if (aCodec.mFECEnabled) { + uint16_t pt; + if (SdpHelper::GetPtAsInt(aCodec.mREDPayloadType, &pt)) { + (*aConfig)->mREDPayloadType = pt; + } + if (SdpHelper::GetPtAsInt(aCodec.mULPFECPayloadType, &pt)) { + (*aConfig)->mULPFECPayloadType = pt; + } + } + if (aCodec.mRtxEnabled) { + uint16_t pt; + if (SdpHelper::GetPtAsInt(aCodec.mRtxPayloadType, &pt)) { + (*aConfig)->mRTXPayloadType = pt; + } + } +} + +// TODO: Maybe move this someplace else? +/*static*/ +void RTCRtpTransceiver::NegotiatedDetailsToVideoCodecConfigs( + const JsepTrackNegotiatedDetails& aDetails, + std::vector* aConfigs) { + if (aDetails.GetEncodingCount()) { + for (const auto& codec : aDetails.GetEncoding(0).GetCodecs()) { + if (NS_WARN_IF(codec->Type() != SdpMediaSection::kVideo)) { + MOZ_ASSERT(false, "Codec is not video! This is a JSEP bug."); + return; + } + Maybe config; + const JsepVideoCodecDescription& video = + static_cast(*codec); + + JsepCodecDescToVideoCodecConfig(video, &config); + + config->mTias = aDetails.GetTias(); + + for (size_t i = 0; i < aDetails.GetEncodingCount(); ++i) { + const JsepTrackEncoding& jsepEncoding(aDetails.GetEncoding(i)); + if (jsepEncoding.HasFormat(video.mDefaultPt)) { + VideoCodecConfig::Encoding encoding; + encoding.rid = jsepEncoding.mRid; + config->mEncodings.push_back(encoding); + } + } + + aConfigs->push_back(std::move(*config)); + } + } +} + +void RTCRtpTransceiver::Stop(ErrorResult& aRv) { + if (mPc->IsClosed()) { + aRv.ThrowInvalidStateError("Peer connection is closed"); + return; + } + + StopImpl(); + mPc->UpdateNegotiationNeeded(); +} + +void RTCRtpTransceiver::StopImpl() { + if (mStopped) { + return; + } + + if (mCallWrapper) { + auto conduit = std::move(mConduit); + (conduit ? conduit->Shutdown() + : GenericPromise::CreateAndResolve(true, __func__)) + ->Then(GetMainThreadSerialEventTarget(), __func__, + [sender = mSender, receiver = mReceiver]() mutable { + // Shutdown pipelines when conduits are guaranteed shut down, + // so that all packets sent from conduits can be delivered. + sender->Shutdown(); + receiver->Shutdown(); + }); + mCallWrapper = nullptr; + } + mStopped = true; + mCurrentDirection.SetNull(); + + mSender->Stop(); + mReceiver->Stop(); + + auto self = nsMainThreadPtrHandle( + new nsMainThreadPtrHolder( + "RTCRtpTransceiver::StopImpl::self", this, false)); + mStsThread->Dispatch(NS_NewRunnableFunction( + __func__, [self] { self->mTransportHandler = nullptr; })); +} + +bool RTCRtpTransceiver::IsVideo() const { return mIsVideo; } + +bool RTCRtpTransceiver::IsSending() const { + return mCurrentDirection == Nullable(RTCRtpTransceiverDirection::Sendonly) || + mCurrentDirection == Nullable(RTCRtpTransceiverDirection::Sendrecv); +} + +bool RTCRtpTransceiver::IsReceiving() const { + return mCurrentDirection == Nullable(RTCRtpTransceiverDirection::Recvonly) || + mCurrentDirection == Nullable(RTCRtpTransceiverDirection::Sendrecv); +} + +void RTCRtpTransceiver::ChainToDomPromiseWithCodecStats( + nsTArray> aStats, + const RefPtr& aDomPromise) { + nsTArray codecStats = + mPc->GetCodecStats(mPc->GetTimestampMaker().GetNow().ToDom()); + + AutoTArray< + std::tuple>, + 1> + statsPromises; + statsPromises.AppendElement(std::make_tuple( + this, RTCStatsPromise::All(GetMainThreadSerialEventTarget(), aStats))); + + ApplyCodecStats(std::move(codecStats), std::move(statsPromises)) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [aDomPromise, window = mWindow, + idGen = mIdGenerator](UniquePtr aStats) mutable { + // Rewrite ids and merge stats collections into the final report. + AutoTArray, 1> stats; + stats.AppendElement(std::move(aStats)); + + RTCStatsCollection opaqueStats; + idGen->RewriteIds(std::move(stats), &opaqueStats); + + RefPtr report(new RTCStatsReport(window)); + report->Incorporate(opaqueStats); + + aDomPromise->MaybeResolve(std::move(report)); + }, + [aDomPromise](nsresult aError) { + aDomPromise->MaybeReject(NS_ERROR_FAILURE); + }); +} + +RefPtr RTCRtpTransceiver::ApplyCodecStats( + nsTArray aCodecStats, + nsTArray< + std::tuple>> + aTransceiverStatsPromises) { + MOZ_ASSERT(NS_IsMainThread()); + // The process here is roughly: + // - Gather all inputs to the codec filtering process, including: + // - Each transceiver's transportIds + // - Each transceiver's active payload types (resolved) + // - Each transceiver's resolved stats + // + // Waiting (async) for multiple promises of different types is not supported + // by the MozPromise API (bug 1752318), so we are a bit finicky here. We + // create media::Refcountables of the types we want to resolve, and let + // these be shared across Then-functions through RefPtrs. + // + // - For each active payload type in a transceiver: + // - Register the codec stats for this payload type and transport if we + // haven't already done so + // - If it was a send payload type, assign the codec stats id for this + // payload type and transport to the transceiver's outbound-rtp and + // remote-inbound-rtp stats as codecId + // - If it was a recv payload type, assign the codec stats id for this + // payload type and transport to the transceiver's inbound-rtp and + // remote-outbound-rtp stats as codecId + // + // - Flatten all transceiver stats collections into one, and set the + // registered codec stats on it + + // Wrap codec stats in a Refcountable<> to allow sharing across promise + // handlers. + auto codecStats = MakeRefPtr>>(); + *codecStats = std::move(aCodecStats); + + struct IdComparator { + bool operator()(const RTCCodecStats& aA, const RTCCodecStats& aB) const { + return aA.mId.Value() < aB.mId.Value(); + } + }; + + // Stores distinct codec stats by id; to avoid dupes within a transport. + auto finalCodecStats = + MakeRefPtr>>(); + + // All the transceiver rtp stream stats in a single array. These stats will, + // when resolved, contain codecIds. + nsTArray> promises( + aTransceiverStatsPromises.Length()); + + for (const auto& [transceiver, allPromise] : aTransceiverStatsPromises) { + // Per transceiver, gather up what we need to assign codecId to this + // transceiver's rtp stream stats. Register codec stats while we're at it. + auto payloadTypes = + MakeRefPtr>(); + promises.AppendElement( + transceiver->GetActivePayloadTypes() + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [payloadTypes, allPromise = allPromise]( + RTCRtpTransceiver::PayloadTypes aPayloadTypes) { + // Forward active payload types to the next Then-handler. + *payloadTypes = std::move(aPayloadTypes); + return allPromise; + }, + [] { + MOZ_CRASH("Unexpected reject"); + return RTCStatsPromise::AllPromiseType::CreateAndReject( + NS_ERROR_UNEXPECTED, __func__); + }) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [codecStats, finalCodecStats, payloadTypes, + transportId = + NS_ConvertASCIItoUTF16(transceiver->GetTransportId())]( + nsTArray> + aTransceiverStats) mutable { + // We have all the data we need to register codec stats and + // assign codecIds for this transceiver's rtp stream stats. + + auto report = MakeUnique(); + FlattenStats(std::move(aTransceiverStats), report.get()); + + // Find the codec stats we are looking for, based on the + // transportId and the active payload types. + Maybe sendCodec; + Maybe recvCodec; + for (auto& codec : *codecStats) { + if (payloadTypes->mSendPayloadType.isSome() == + sendCodec.isSome() && + payloadTypes->mRecvPayloadType.isSome() == + recvCodec.isSome()) { + // We have found all the codec stats we were looking for. + break; + } + if (codec.mTransportId != transportId) { + continue; + } + if (payloadTypes->mSendPayloadType && + *payloadTypes->mSendPayloadType == + static_cast(codec.mPayloadType) && + (!codec.mCodecType.WasPassed() || + codec.mCodecType.Value() == RTCCodecType::Encode)) { + MOZ_ASSERT(!sendCodec, + "At most one send codec stat per transceiver"); + sendCodec = SomeRef(codec); + } + if (payloadTypes->mRecvPayloadType && + *payloadTypes->mRecvPayloadType == + static_cast(codec.mPayloadType) && + (!codec.mCodecType.WasPassed() || + codec.mCodecType.Value() == RTCCodecType::Decode)) { + MOZ_ASSERT(!recvCodec, + "At most one recv codec stat per transceiver"); + recvCodec = SomeRef(codec); + } + } + + // Register and assign codecIds for the found codec stats. + if (sendCodec) { + finalCodecStats->insert(*sendCodec); + for (auto& stat : report->mOutboundRtpStreamStats) { + stat.mCodecId.Construct(sendCodec->mId.Value()); + } + for (auto& stat : report->mRemoteInboundRtpStreamStats) { + stat.mCodecId.Construct(sendCodec->mId.Value()); + } + } + if (recvCodec) { + finalCodecStats->insert(*recvCodec); + for (auto& stat : report->mInboundRtpStreamStats) { + stat.mCodecId.Construct(recvCodec->mId.Value()); + } + for (auto& stat : report->mRemoteOutboundRtpStreamStats) { + stat.mCodecId.Construct(recvCodec->mId.Value()); + } + } + + return RTCStatsPromise::CreateAndResolve(std::move(report), + __func__); + }, + [] { + MOZ_CRASH("Unexpected reject"); + return RTCStatsPromise::CreateAndReject(NS_ERROR_UNEXPECTED, + __func__); + })); + } + + return RTCStatsPromise::All(GetMainThreadSerialEventTarget(), promises) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [finalCodecStats = std::move(finalCodecStats)]( + nsTArray> aStats) mutable { + auto finalStats = MakeUnique(); + FlattenStats(std::move(aStats), finalStats.get()); + MOZ_ASSERT(finalStats->mCodecStats.IsEmpty()); + if (!finalStats->mCodecStats.SetCapacity(finalCodecStats->size(), + fallible)) { + mozalloc_handle_oom(0); + } + while (!finalCodecStats->empty()) { + auto node = finalCodecStats->extract(finalCodecStats->begin()); + if (!finalStats->mCodecStats.AppendElement( + std::move(node.value()), fallible)) { + mozalloc_handle_oom(0); + } + } + return RTCStatsPromise::CreateAndResolve(std::move(finalStats), + __func__); + }, + [] { + MOZ_CRASH("Unexpected reject"); + return RTCStatsPromise::CreateAndReject(NS_ERROR_UNEXPECTED, + __func__); + }); +} + +} // namespace mozilla diff --git a/dom/media/webrtc/jsapi/RTCRtpTransceiver.h b/dom/media/webrtc/jsapi/RTCRtpTransceiver.h new file mode 100644 index 0000000000..4830e465a0 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCRtpTransceiver.h @@ -0,0 +1,243 @@ +/* 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/. */ +#ifndef _TRANSCEIVERIMPL_H_ +#define _TRANSCEIVERIMPL_H_ + +#include +#include "mozilla/StateMirroring.h" +#include "mozilla/RefPtr.h" +#include "nsCOMPtr.h" +#include "nsISerialEventTarget.h" +#include "nsTArray.h" +#include "mozilla/dom/MediaStreamTrack.h" +#include "ErrorList.h" +#include "jsep/JsepSession.h" +#include "transport/transportlayer.h" // For TransportLayer::State +#include "mozilla/dom/RTCRtpTransceiverBinding.h" +#include "RTCStatsReport.h" + +class nsIPrincipal; + +namespace mozilla { +class PeerIdentity; +class MediaSessionConduit; +class VideoSessionConduit; +class AudioSessionConduit; +struct AudioCodecConfig; +class VideoCodecConfig; // Why is this a class, but AudioCodecConfig a struct? +class MediaPipelineTransmit; +class MediaPipeline; +class MediaPipelineFilter; +class MediaTransportHandler; +class RTCStatsIdGenerator; +class WebrtcCallWrapper; +class JsepTrackNegotiatedDetails; +class PeerConnectionImpl; +enum class PrincipalPrivacy : uint8_t; + +namespace dom { +class RTCDtlsTransport; +class RTCDTMFSender; +class RTCRtpTransceiver; +struct RTCRtpSourceEntry; +class RTCRtpReceiver; +class RTCRtpSender; + +/** + * This is what ties all the various pieces that make up a transceiver + * together. This includes: + * MediaStreamTrack for rendering and capture + * MediaTransportHandler for RTP transmission/reception + * Audio/VideoConduit for feeding RTP/RTCP into webrtc.org for decoding, and + * feeding audio/video frames into webrtc.org for encoding into RTP/RTCP. + */ +class RTCRtpTransceiver : public nsISupports, public nsWrapperCache { + public: + /** + * |aSendTrack| might or might not be set. + */ + RTCRtpTransceiver( + nsPIDOMWindowInner* aWindow, bool aPrivacyNeeded, PeerConnectionImpl* aPc, + MediaTransportHandler* aTransportHandler, JsepSession* aJsepSession, + const std::string& aTransceiverId, bool aIsVideo, + nsISerialEventTarget* aStsThread, MediaStreamTrack* aSendTrack, + WebrtcCallWrapper* aCallWrapper, RTCStatsIdGenerator* aIdGenerator); + + void Init(const RTCRtpTransceiverInit& aInit, ErrorResult& aRv); + + bool IsValid() const { return !!mConduit; } + + nsresult UpdateTransport(); + + nsresult UpdateConduit(); + + void UpdatePrincipalPrivacy(PrincipalPrivacy aPrivacy); + + void ResetSync(); + + nsresult SyncWithMatchingVideoConduits( + nsTArray>& transceivers); + + void Close(); + + void BreakCycles(); + + bool ConduitHasPluginID(uint64_t aPluginID); + + // for webidl + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + nsPIDOMWindowInner* GetParentObject() const; + RTCRtpReceiver* Receiver() const { return mReceiver; } + RTCRtpSender* Sender() const { return mSender; } + RTCDtlsTransport* GetDtlsTransport() const { return mDtlsTransport; } + void GetKind(nsAString& aKind) const; + void GetMid(nsAString& aMid) const; + RTCRtpTransceiverDirection Direction() const { return mDirection; } + void SetDirection(RTCRtpTransceiverDirection aDirection, ErrorResult& aRv); + Nullable GetCurrentDirection() { + return mCurrentDirection; + } + void Stop(ErrorResult& aRv); + void SetDirectionInternal(RTCRtpTransceiverDirection aDirection); + bool HasBeenUsedToSend() const { return mHasBeenUsedToSend; } + + bool CanSendDTMF() const; + bool Stopped() const { return mStopped; } + void SyncToJsep(JsepSession& aSession) const; + void SyncFromJsep(const JsepSession& aSession); + std::string GetMidAscii() const; + + void SetDtlsTransport(RTCDtlsTransport* aDtlsTransport, bool aStable); + void RollbackToStableDtlsTransport(); + + std::string GetTransportId() const { + return mJsepTransceiver.mTransport.mTransportId; + } + + JsepTransceiver& GetJsepTransceiver() { return mJsepTransceiver; } + + bool IsVideo() const; + + bool IsSending() const; + + bool IsReceiving() const; + + bool ShouldRemove() const; + + Maybe>&> + GetNegotiatedSendCodecs() const; + + Maybe>&> + GetNegotiatedRecvCodecs() const; + + struct PayloadTypes { + Maybe mSendPayloadType; + Maybe mRecvPayloadType; + }; + using ActivePayloadTypesPromise = MozPromise; + RefPtr GetActivePayloadTypes() const; + + MediaSessionConduit* GetConduit() const { return mConduit; } + + const std::string& GetJsepTransceiverId() const { return mTransceiverId; } + + void SetRemovedFromPc() { mHandlingUnlink = true; } + + // nsISupports + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCRtpTransceiver) + + static void NegotiatedDetailsToAudioCodecConfigs( + const JsepTrackNegotiatedDetails& aDetails, + std::vector* aConfigs); + + static void NegotiatedDetailsToVideoCodecConfigs( + const JsepTrackNegotiatedDetails& aDetails, + std::vector* aConfigs); + + /* Returns a promise that will contain the stats in aStats, along with the + * codec stats (which is a PC-wide thing) */ + void ChainToDomPromiseWithCodecStats(nsTArray> aStats, + const RefPtr& aDomPromise); + + /** + * Takes a set of codec stats (per-peerconnection) and a set of + * transceiver/transceiver-stats-promise tuples. Filters out all referenced + * codec stats based on the transceiver's transport and rtp stream stats. + * Finally returns the flattened stats containing the filtered codec stats and + * all given per-transceiver-stats. + */ + static RefPtr ApplyCodecStats( + nsTArray aCodecStats, + nsTArray>> + aTransceiverStatsPromises); + + AbstractCanonical* CanonicalMid() { return &mMid; } + AbstractCanonical* CanonicalSyncGroup() { return &mSyncGroup; } + + private: + virtual ~RTCRtpTransceiver(); + void InitAudio(); + void InitVideo(const TrackingId& aRecvTrackingId); + void InitConduitControl(); + void StopImpl(); + + nsCOMPtr mWindow; + RefPtr mPc; + RefPtr mTransportHandler; + const std::string mTransceiverId; + // Copy of latest from the JSEP engine. + JsepTransceiver mJsepTransceiver; + nsCOMPtr mStsThread; + // state for webrtc.org that is shared between all transceivers + RefPtr mCallWrapper; + RefPtr mSendTrack; + RefPtr mIdGenerator; + RefPtr mConduit; + // The spec says both RTCRtpReceiver and RTCRtpSender have a slot for + // an RTCDtlsTransport. They are always the same, so we'll store it + // here. + RefPtr mDtlsTransport; + // The spec says both RTCRtpReceiver and RTCRtpSender have a slot for + // a last stable state RTCDtlsTransport. They are always the same, so + // we'll store it here. + RefPtr mLastStableDtlsTransport; + RefPtr mReceiver; + RefPtr mSender; + RTCRtpTransceiverDirection mDirection = RTCRtpTransceiverDirection::Sendrecv; + Nullable mCurrentDirection; + bool mStopped = false; + bool mShutdown = false; + bool mHasBeenUsedToSend = false; + PrincipalPrivacy mPrincipalPrivacy; + bool mShouldRemove = false; + bool mHasTransport = false; + bool mIsVideo; + // This is really nasty. Most of the time, PeerConnectionImpl needs to be in + // charge of unlinking each RTCRtpTransceiver, because it needs to perform + // stats queries on its way out, which requires all of the RTCRtpTransceivers + // (and their transitive dependencies) to stick around until those stats + // queries are finished. However, when an RTCRtpTransceiver is removed from + // the PeerConnectionImpl due to negotiation, the PeerConnectionImpl + // releases its reference, which means the PeerConnectionImpl cannot be in + // charge of the unlink anymore. We cannot do the unlink when this reference + // is released either, because RTCRtpTransceiver might have some work it needs + // to do first. Also, JS may be maintaining a reference to the + // RTCRtpTransceiver (or one of its dependencies), which means it must remain + // fully functional after it is removed (meaning it cannot release any of its + // dependencies, or vice versa). + bool mHandlingUnlink = false; + std::string mTransportId; + + Canonical mMid; + Canonical mSyncGroup; +}; + +} // namespace dom + +} // namespace mozilla + +#endif // _TRANSCEIVERIMPL_H_ diff --git a/dom/media/webrtc/jsapi/RTCSctpTransport.cpp b/dom/media/webrtc/jsapi/RTCSctpTransport.cpp new file mode 100644 index 0000000000..63424968ae --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCSctpTransport.cpp @@ -0,0 +1,53 @@ +/* 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 "RTCSctpTransport.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/EventBinding.h" +#include "mozilla/dom/RTCSctpTransportBinding.h" + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_INHERITED(RTCSctpTransport, DOMEventTargetHelper, + mDtlsTransport) + +NS_IMPL_ADDREF_INHERITED(RTCSctpTransport, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(RTCSctpTransport, DOMEventTargetHelper) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCSctpTransport) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +RTCSctpTransport::RTCSctpTransport(nsPIDOMWindowInner* aWindow, + RTCDtlsTransport& aDtlsTransport, + double aMaxMessageSize, + const Nullable& aMaxChannels) + : DOMEventTargetHelper(aWindow), + mState(RTCSctpTransportState::Connecting), + mDtlsTransport(&aDtlsTransport), + mMaxMessageSize(aMaxMessageSize), + mMaxChannels(aMaxChannels) {} + +JSObject* RTCSctpTransport::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + return RTCSctpTransport_Binding::Wrap(aCx, this, aGivenProto); +} + +void RTCSctpTransport::UpdateState(RTCSctpTransportState aState) { + if (mState == RTCSctpTransportState::Closed || mState == aState) { + return; + } + + mState = aState; + + EventInit init; + init.mBubbles = false; + init.mCancelable = false; + + RefPtr event = Event::Constructor(this, u"statechange"_ns, init); + + DispatchTrustedEvent(event); +} + +} // namespace mozilla::dom diff --git a/dom/media/webrtc/jsapi/RTCSctpTransport.h b/dom/media/webrtc/jsapi/RTCSctpTransport.h new file mode 100644 index 0000000000..8e21db5e73 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCSctpTransport.h @@ -0,0 +1,65 @@ +/* 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/. */ + +#ifndef _RTCSctpTransport_h_ +#define _RTCSctpTransport_h_ + +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/RefPtr.h" +#include "js/RootingAPI.h" +#include "RTCDtlsTransport.h" + +class nsPIDOMWindowInner; + +namespace mozilla::dom { + +enum class RTCSctpTransportState : uint8_t; + +class RTCSctpTransport : public DOMEventTargetHelper { + public: + explicit RTCSctpTransport(nsPIDOMWindowInner* aWindow, + RTCDtlsTransport& aDtlsTransport, + double aMaxMessageSize, + const Nullable& aMaxChannels); + + // nsISupports + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RTCSctpTransport, + DOMEventTargetHelper) + + // webidl + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + IMPL_EVENT_HANDLER(statechange) + + RTCDtlsTransport* Transport() const { return mDtlsTransport; } + RTCSctpTransportState State() const { return mState; } + double MaxMessageSize() const { return mMaxMessageSize; } + Nullable GetMaxChannels() const { return mMaxChannels; } + + void SetTransport(RTCDtlsTransport& aTransport) { + mDtlsTransport = &aTransport; + } + + void SetMaxMessageSize(double aMaxMessageSize) { + mMaxMessageSize = aMaxMessageSize; + } + + void SetMaxChannels(const Nullable& aMaxChannels) { + mMaxChannels = aMaxChannels; + } + + void UpdateState(RTCSctpTransportState aState); + + private: + virtual ~RTCSctpTransport() = default; + + RTCSctpTransportState mState; + RefPtr mDtlsTransport; + double mMaxMessageSize; + Nullable mMaxChannels; +}; + +} // namespace mozilla::dom +#endif // _RTCSctpTransport_h_ diff --git a/dom/media/webrtc/jsapi/RTCStatsIdGenerator.cpp b/dom/media/webrtc/jsapi/RTCStatsIdGenerator.cpp new file mode 100644 index 0000000000..8b0462e223 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCStatsIdGenerator.cpp @@ -0,0 +1,90 @@ +/* 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/. */ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ + +#include "RTCStatsIdGenerator.h" + +#include + +#include "mozilla/RandomNum.h" +#include "RTCStatsReport.h" +#include "WebrtcGlobal.h" + +namespace mozilla { + +RTCStatsIdGenerator::RTCStatsIdGenerator() + : mSalt(RandomUint64().valueOr(0xa5a5a5a5)), mCounter(0) {} + +void RTCStatsIdGenerator::RewriteIds( + nsTArray> aFromStats, + dom::RTCStatsCollection* aIntoReport) { + // Rewrite an Optional id + auto rewriteId = [&](dom::Optional& id) { + if (id.WasPassed()) { + id.Value() = Id(id.Value()); + } + }; + + auto rewriteIds = [&](auto& aList, auto... aParam) { + for (auto& stat : aList) { + (rewriteId(stat.*aParam), ...); + } + }; + + // Involves a lot of copying, since webidl dictionaries don't have + // move semantics. Oh well. + + // Create a temporary to avoid double-rewriting any stats already in + // aIntoReport. + auto stats = MakeUnique(); + dom::FlattenStats(std::move(aFromStats), stats.get()); + + using S = dom::RTCStats; + using ICPS = dom::RTCIceCandidatePairStats; + using RSS = dom::RTCRtpStreamStats; + using IRSS = dom::RTCInboundRtpStreamStats; + using ORSS = dom::RTCOutboundRtpStreamStats; + using RIRSS = dom::RTCRemoteInboundRtpStreamStats; + using RORSS = dom::RTCRemoteOutboundRtpStreamStats; + + rewriteIds(stats->mIceCandidatePairStats, &S::mId, &ICPS::mLocalCandidateId, + &ICPS::mRemoteCandidateId); + rewriteIds(stats->mIceCandidateStats, &S::mId); + rewriteIds(stats->mInboundRtpStreamStats, &S::mId, &IRSS::mRemoteId, + &RSS::mCodecId); + rewriteIds(stats->mOutboundRtpStreamStats, &S::mId, &ORSS::mRemoteId, + &RSS::mCodecId); + rewriteIds(stats->mRemoteInboundRtpStreamStats, &S::mId, &RIRSS::mLocalId, + &RSS::mCodecId); + rewriteIds(stats->mRemoteOutboundRtpStreamStats, &S::mId, &RORSS::mLocalId, + &RSS::mCodecId); + rewriteIds(stats->mCodecStats, &S::mId); + rewriteIds(stats->mRtpContributingSourceStats, &S::mId); + rewriteIds(stats->mTrickledIceCandidateStats, &S::mId); + rewriteIds(stats->mDataChannelStats, &S::mId); + + dom::MergeStats(std::move(stats), aIntoReport); +} + +nsString RTCStatsIdGenerator::Id(const nsString& aKey) { + if (!aKey.Length()) { + MOZ_ASSERT(aKey.Length(), "Stats IDs should never be empty."); + return aKey; + } + if (mAllocated.find(aKey) == mAllocated.end()) { + mAllocated[aKey] = Generate(); + } + return mAllocated[aKey]; +} + +nsString RTCStatsIdGenerator::Generate() { + auto random = RandomUint64().valueOr(0x1a22); + auto idNum = static_cast(mSalt ^ ((mCounter++ << 16) | random)); + nsString id; + id.AppendInt(idNum, 16); // Append as hex + return id; +} + +} // namespace mozilla diff --git a/dom/media/webrtc/jsapi/RTCStatsIdGenerator.h b/dom/media/webrtc/jsapi/RTCStatsIdGenerator.h new file mode 100644 index 0000000000..1391c029ad --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCStatsIdGenerator.h @@ -0,0 +1,42 @@ +/* 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/. */ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ + +#ifndef _RTCSTATSIDGENERATOR_H_ +#define _RTCSTATSIDGENERATOR_H_ + +#include + +#include "mozilla/Atomics.h" +#include "mozilla/UniquePtr.h" +#include "nsISupportsImpl.h" +#include "nsString.h" +#include "nsTArray.h" + +namespace mozilla { +namespace dom { +struct RTCStatsCollection; +} // namespace dom + +class RTCStatsIdGenerator { + public: + RTCStatsIdGenerator(); + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RTCStatsIdGenerator); + + void RewriteIds(nsTArray> aFromStats, + dom::RTCStatsCollection* aIntoReport); + + private: + virtual ~RTCStatsIdGenerator(){}; + nsString Id(const nsString& aKey); + nsString Generate(); + + const uint64_t mSalt; + uint64_t mCounter; + std::map mAllocated; +}; + +} // namespace mozilla +#endif diff --git a/dom/media/webrtc/jsapi/RTCStatsReport.cpp b/dom/media/webrtc/jsapi/RTCStatsReport.cpp new file mode 100644 index 0000000000..9f39eb3865 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCStatsReport.cpp @@ -0,0 +1,213 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 https://mozilla.org/MPL/2.0/. */ + +#include "RTCStatsReport.h" +#include "libwebrtcglue/SystemTime.h" +#include "mozilla/dom/Performance.h" +#include "nsRFPService.h" +#include "WebrtcGlobal.h" + +namespace mozilla::dom { + +RTCStatsTimestampState::RTCStatsTimestampState() + : mRandomTimelineSeed(0), + mStartDomRealtime(WebrtcSystemTimeBase()), + mStartRealtime( + WebrtcSystemTime() - + webrtc::TimeDelta::Micros( + (TimeStamp::Now() - mStartDomRealtime).ToMicroseconds())), + mRTPCallerType(RTPCallerType::Normal), + mStartWallClockRaw( + PerformanceService::GetOrCreate()->TimeOrigin(mStartDomRealtime)) {} + +RTCStatsTimestampState::RTCStatsTimestampState(Performance& aPerformance) + : mRandomTimelineSeed(aPerformance.GetRandomTimelineSeed()), + mStartDomRealtime(aPerformance.CreationTimeStamp()), + mStartRealtime( + WebrtcSystemTime() - + webrtc::TimeDelta::Micros( + (TimeStamp::Now() - mStartDomRealtime).ToMicroseconds())), + mRTPCallerType(aPerformance.GetRTPCallerType()), + mStartWallClockRaw( + PerformanceService::GetOrCreate()->TimeOrigin(mStartDomRealtime)) {} + +TimeStamp RTCStatsTimestamp::ToMozTime() const { return mMozTime; } + +webrtc::Timestamp RTCStatsTimestamp::ToRealtime() const { + return ToDomRealtime() + + webrtc::TimeDelta::Micros(mState.mStartRealtime.us()); +} + +webrtc::Timestamp RTCStatsTimestamp::To1Jan1970() const { + return ToDomRealtime() + webrtc::TimeDelta::Millis(mState.mStartWallClockRaw); +} + +webrtc::Timestamp RTCStatsTimestamp::ToNtp() const { + return To1Jan1970() + webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970); +} + +webrtc::Timestamp RTCStatsTimestamp::ToDomRealtime() const { + return webrtc::Timestamp::Micros( + (mMozTime - mState.mStartDomRealtime).ToMicroseconds()); +} + +DOMHighResTimeStamp RTCStatsTimestamp::ToDom() const { + // webrtc-pc says to use performance.timeOrigin + performance.now(), but + // keeping a Performance object around is difficult because it is + // main-thread-only. So, we perform the same calculation here. Note that this + // can be very different from the current wall-clock time because of changes + // to the wall clock, or monotonic clock drift over long periods of time. + // We are very careful to do exactly what Performance does, to avoid timestamp + // discrepancies. + + DOMHighResTimeStamp realtime = ToDomRealtime().ms(); + // mRandomTimelineSeed is not set in the unit-tests. + if (mState.mRandomTimelineSeed) { + realtime = nsRFPService::ReduceTimePrecisionAsMSecs( + realtime, mState.mRandomTimelineSeed, mState.mRTPCallerType); + } + + // Ugh. Performance::TimeOrigin is not constant, which means we need to + // emulate this weird behavior so our time stamps are consistent with JS + // timeOrigin. This is based on the code here: + // https://searchfox.org/mozilla-central/rev/ + // 053826b10f838f77c27507e5efecc96e34718541/dom/performance/Performance.cpp#111-117 + DOMHighResTimeStamp start = nsRFPService::ReduceTimePrecisionAsMSecs( + mState.mStartWallClockRaw, 0, mState.mRTPCallerType); + + return start + realtime; +} + +/* static */ RTCStatsTimestamp RTCStatsTimestamp::FromMozTime( + const RTCStatsTimestampMaker& aMaker, TimeStamp aMozTime) { + return RTCStatsTimestamp(aMaker.mState, aMozTime); +} + +/* static */ RTCStatsTimestamp RTCStatsTimestamp::FromRealtime( + const RTCStatsTimestampMaker& aMaker, webrtc::Timestamp aRealtime) { + return FromDomRealtime( + aMaker, + aRealtime - webrtc::TimeDelta::Micros(aMaker.mState.mStartRealtime.us())); +} + +/* static */ RTCStatsTimestamp RTCStatsTimestamp::From1Jan1970( + const RTCStatsTimestampMaker& aMaker, webrtc::Timestamp a1Jan1970) { + const auto& state = aMaker.mState; + return FromDomRealtime( + aMaker, a1Jan1970 - webrtc::TimeDelta::Millis(state.mStartWallClockRaw)); +} + +/* static */ RTCStatsTimestamp RTCStatsTimestamp::FromNtp( + const RTCStatsTimestampMaker& aMaker, webrtc::Timestamp aNtpTime) { + const auto& state = aMaker.mState; + const auto domRealtime = aNtpTime - + webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970) - + webrtc::TimeDelta::Millis(state.mStartWallClockRaw); + // Ntp times exposed by libwebrtc to stats are always **rounded** to + // milliseconds. That means they can jump up to half a millisecond into the + // future. We compensate for that here so that things seem consistent to js. + return FromDomRealtime(aMaker, domRealtime - webrtc::TimeDelta::Micros(500)); +} + +/* static */ RTCStatsTimestamp RTCStatsTimestamp::FromDomRealtime( + const RTCStatsTimestampMaker& aMaker, webrtc::Timestamp aDomRealtime) { + return RTCStatsTimestamp(aMaker.mState, aMaker.mState.mStartDomRealtime + + TimeDuration::FromMicroseconds( + aDomRealtime.us())); +} + +RTCStatsTimestamp::RTCStatsTimestamp(RTCStatsTimestampState aState, + TimeStamp aMozTime) + : mState(aState), mMozTime(aMozTime) {} + +RTCStatsTimestampMaker::RTCStatsTimestampMaker(RTCStatsTimestampState aState) + : mState(aState) {} + +/* static */ +RTCStatsTimestampMaker RTCStatsTimestampMaker::Create( + nsPIDOMWindowInner* aWindow /* = nullptr */) { + if (!aWindow) { + return RTCStatsTimestampMaker(RTCStatsTimestampState()); + } + if (Performance* p = aWindow->GetPerformance()) { + return RTCStatsTimestampMaker(RTCStatsTimestampState(*p)); + } + return RTCStatsTimestampMaker(RTCStatsTimestampState()); +} + +RTCStatsTimestamp RTCStatsTimestampMaker::GetNow() const { + return RTCStatsTimestamp::FromMozTime(*this, TimeStamp::Now()); +} + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(RTCStatsReport, mParent) + +RTCStatsReport::RTCStatsReport(nsPIDOMWindowInner* aParent) + : mParent(aParent) {} + +/*static*/ +already_AddRefed RTCStatsReport::Constructor( + const GlobalObject& aGlobal) { + nsCOMPtr window( + do_QueryInterface(aGlobal.GetAsSupports())); + RefPtr report(new RTCStatsReport(window)); + return report.forget(); +} + +JSObject* RTCStatsReport::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + return RTCStatsReport_Binding::Wrap(aCx, this, aGivenProto); +} + +void RTCStatsReport::Incorporate(RTCStatsCollection& aStats) { + ForAllPublicRTCStatsCollectionMembers( + aStats, [&](auto... aMember) { (SetRTCStats(aMember), ...); }); +} + +void RTCStatsReport::Set(const nsAString& aKey, JS::Handle aValue, + ErrorResult& aRv) { + RTCStatsReport_Binding::MaplikeHelpers::Set(this, aKey, aValue, aRv); +} + +namespace { +template +bool MoveInto(std::tuple& aFrom, std::tuple& aInto) { + return std::get(aInto)->AppendElements(std::move(std::get(aFrom)), + fallible); +} + +template +bool MoveInto(std::tuple&& aFrom, std::tuple& aInto, + std::index_sequence) { + return (... && MoveInto(aFrom, aInto)); +} + +template +bool MoveInto(std::tuple&& aFrom, std::tuple& aInto) { + return MoveInto(std::move(aFrom), aInto, std::index_sequence_for()); +} +} // namespace + +void MergeStats(UniquePtr aFromStats, + RTCStatsCollection* aIntoStats) { + auto fromTuple = ForAllRTCStatsCollectionMembers( + *aFromStats, + [&](auto&... aMember) { return std::make_tuple(std::move(aMember)...); }); + auto intoTuple = ForAllRTCStatsCollectionMembers( + *aIntoStats, + [&](auto&... aMember) { return std::make_tuple(&aMember...); }); + if (!MoveInto(std::move(fromTuple), intoTuple)) { + mozalloc_handle_oom(0); + } +} + +void FlattenStats(nsTArray> aFromStats, + RTCStatsCollection* aIntoStats) { + for (auto& stats : aFromStats) { + MergeStats(std::move(stats), aIntoStats); + } +} + +} // namespace mozilla::dom diff --git a/dom/media/webrtc/jsapi/RTCStatsReport.h b/dom/media/webrtc/jsapi/RTCStatsReport.h new file mode 100644 index 0000000000..97bf3daa52 --- /dev/null +++ b/dom/media/webrtc/jsapi/RTCStatsReport.h @@ -0,0 +1,205 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef RTCStatsReport_h_ +#define RTCStatsReport_h_ + +#include "api/units/timestamp.h" // webrtc::Timestamp +#include "js/RootingAPI.h" // JS::Rooted +#include "js/Value.h" +#include "mozilla/dom/AutoEntryScript.h" +#include "mozilla/MozPromise.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/dom/PerformanceService.h" +#include "mozilla/dom/RTCStatsReportBinding.h" // RTCStatsCollection +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/UniquePtr.h" +#include "nsCOMPtr.h" +#include "nsIGlobalObject.h" +#include "nsPIDOMWindow.h" // nsPIDOMWindowInner +#include "nsContentUtils.h" +#include "nsWrapperCache.h" +#include "prtime.h" // PR_Now + +namespace mozilla { + +extern TimeStamp WebrtcSystemTimeBase(); + +namespace dom { + +/** + * Keeps the state needed to convert RTCStatsTimestamps. + */ +struct RTCStatsTimestampState { + RTCStatsTimestampState(); + explicit RTCStatsTimestampState(Performance& aPerformance); + + RTCStatsTimestampState(const RTCStatsTimestampState&) = default; + + // These members are sampled when a non-copy constructor is called. + + // Performance's random timeline seed. + const uint64_t mRandomTimelineSeed; + // TimeStamp::Now() when the members were sampled. This is equivalent to time + // 0 in DomRealtime. + const TimeStamp mStartDomRealtime; + // WebrtcSystemTime() when the members were sampled. This represents the same + // point in time as mStartDomRealtime, but as a webrtc timestamp. + const webrtc::Timestamp mStartRealtime; + // Performance's RTPCallerType. + const RTPCallerType mRTPCallerType; + // Performance.timeOrigin for mStartDomRealtime when the members were sampled. + const DOMHighResTimeStamp mStartWallClockRaw; +}; + +/** + * Classes that facilitate creating timestamps for webrtc stats by mimicking + * dom::Performance, as well as getting and converting timestamps for libwebrtc + * and our integration with it. + * + * They use the same clock to avoid drift and inconsistencies, base on + * mozilla::TimeStamp, and convert to and from these time bases: + * - Moz : Monotonic, unspecified (but constant) and inaccessible epoch, + * as implemented by mozilla::TimeStamp + * - Realtime : Monotonic, unspecified (but constant) epoch. + * - 1Jan1970 : Monotonic, unix epoch (00:00:00 UTC on 1 January 1970). + * - Ntp : Monotonic, ntp epoch (00:00:00 UTC on 1 January 1900). + * - Dom : Monotonic, milliseconds since unix epoch, as the timestamps + * defined by webrtc-pc. Corresponds to Performance.timeOrigin + + * Performance.now(). Has reduced precision. + * - DomRealtime: Like Dom, but with full precision. + * - WallClock : Non-monotonic, unix epoch. Not used here since it is + * non-monotonic and cannot be correlated to the other time + * bases. + */ +class RTCStatsTimestampMaker; +class RTCStatsTimestamp { + public: + TimeStamp ToMozTime() const; + webrtc::Timestamp ToRealtime() const; + webrtc::Timestamp To1Jan1970() const; + webrtc::Timestamp ToNtp() const; + webrtc::Timestamp ToDomRealtime() const; + DOMHighResTimeStamp ToDom() const; + + static RTCStatsTimestamp FromMozTime(const RTCStatsTimestampMaker& aMaker, + TimeStamp aMozTime); + static RTCStatsTimestamp FromRealtime(const RTCStatsTimestampMaker& aMaker, + webrtc::Timestamp aRealtime); + static RTCStatsTimestamp From1Jan1970(const RTCStatsTimestampMaker& aMaker, + webrtc::Timestamp aRealtime); + static RTCStatsTimestamp FromNtp(const RTCStatsTimestampMaker& aMaker, + webrtc::Timestamp aRealtime); + static RTCStatsTimestamp FromDomRealtime(const RTCStatsTimestampMaker& aMaker, + webrtc::Timestamp aDomRealtime); + // There is on purpose no conversion functions from DOMHighResTimeStamp + // because of the loss in precision of a floating point to integer conversion. + + private: + RTCStatsTimestamp(RTCStatsTimestampState aState, TimeStamp aMozTime); + + const RTCStatsTimestampState mState; + const TimeStamp mMozTime; +}; + +class RTCStatsTimestampMaker { + public: + static RTCStatsTimestampMaker Create(nsPIDOMWindowInner* aWindow = nullptr); + + RTCStatsTimestamp GetNow() const; + + const RTCStatsTimestampState mState; + + private: + explicit RTCStatsTimestampMaker(RTCStatsTimestampState aState); +}; + +// TODO(bug 1588303): If we ever get move semantics for webidl dictionaries, we +// can stop wrapping these in UniquePtr, which will allow us to simplify code +// in several places. +typedef MozPromise, nsresult, true> + RTCStatsPromise; + +typedef MozPromise, nsresult, true> + RTCStatsReportPromise; + +class RTCStatsReport final : public nsWrapperCache { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(RTCStatsReport) + NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(RTCStatsReport) + + explicit RTCStatsReport(nsPIDOMWindowInner* aParent); + + // TODO(bug 1586109): Remove this once we no longer have to create empty + // RTCStatsReports from JS. + static already_AddRefed Constructor( + const GlobalObject& aGlobal); + + void Incorporate(RTCStatsCollection& aStats); + + nsPIDOMWindowInner* GetParentObject() const { return mParent; } + + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + private: + ~RTCStatsReport() = default; + void Set(const nsAString& aKey, JS::Handle aValue, + ErrorResult& aRv); + + template + nsresult SetRTCStats(Sequence& aValues) { + for (T& value : aValues) { + nsresult rv = SetRTCStats(value); + if (NS_FAILED(rv)) { + return rv; + } + } + return NS_OK; + } + + // We cannot just declare this as SetRTCStats(RTCStats&), because the + // conversion function that ToJSValue uses is non-virtual. + template + nsresult SetRTCStats(T& aValue) { + static_assert(std::is_base_of::value, + "SetRTCStats is for setting RTCStats only"); + + if (!aValue.mId.WasPassed()) { + return NS_OK; + } + + const nsString key(aValue.mId.Value()); + + // Cargo-culted from dom::Promise; converts aValue to a JSObject + AutoEntryScript aes(mParent->AsGlobal()->GetGlobalJSObject(), + "RTCStatsReport::SetRTCStats"); + JSContext* cx = aes.cx(); + JS::Rooted val(cx); + if (!ToJSValue(cx, std::forward(aValue), &val)) { + return NS_ERROR_FAILURE; + } + JS::Rooted jsObject(cx, &val.toObject()); + + ErrorResult rv; + Set(key, jsObject, rv); + return rv.StealNSResult(); + } + + nsCOMPtr mParent; +}; + +void MergeStats(UniquePtr aFromStats, + dom::RTCStatsCollection* aIntoStats); + +void FlattenStats(nsTArray> aFromStats, + dom::RTCStatsCollection* aIntoStats); + +} // namespace dom +} // namespace mozilla + +#endif // RTCStatsReport_h_ diff --git a/dom/media/webrtc/jsapi/RemoteTrackSource.cpp b/dom/media/webrtc/jsapi/RemoteTrackSource.cpp new file mode 100644 index 0000000000..41c679e431 --- /dev/null +++ b/dom/media/webrtc/jsapi/RemoteTrackSource.cpp @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "RemoteTrackSource.h" + +#include "MediaStreamError.h" +#include "MediaTrackGraph.h" +#include "RTCRtpReceiver.h" + +namespace mozilla { + +NS_IMPL_ADDREF_INHERITED(RemoteTrackSource, dom::MediaStreamTrackSource) +NS_IMPL_RELEASE_INHERITED(RemoteTrackSource, dom::MediaStreamTrackSource) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RemoteTrackSource) +NS_INTERFACE_MAP_END_INHERITING(dom::MediaStreamTrackSource) +NS_IMPL_CYCLE_COLLECTION_CLASS(RemoteTrackSource) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(RemoteTrackSource, + dom::MediaStreamTrackSource) + tmp->Destroy(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mReceiver) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(RemoteTrackSource, + dom::MediaStreamTrackSource) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReceiver) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +RemoteTrackSource::RemoteTrackSource(SourceMediaTrack* aStream, + dom::RTCRtpReceiver* aReceiver, + nsIPrincipal* aPrincipal, + const nsString& aLabel, + TrackingId aTrackingId) + : dom::MediaStreamTrackSource(aPrincipal, aLabel, std::move(aTrackingId)), + mStream(aStream), + mReceiver(aReceiver) {} + +RemoteTrackSource::~RemoteTrackSource() { Destroy(); } + +void RemoteTrackSource::Destroy() { + if (mStream) { + MOZ_ASSERT(!mStream->IsDestroyed()); + mStream->End(); + mStream->Destroy(); + mStream = nullptr; + + GetMainThreadSerialEventTarget()->Dispatch(NewRunnableMethod( + "RemoteTrackSource::ForceEnded", this, &RemoteTrackSource::ForceEnded)); + } +} + +auto RemoteTrackSource::ApplyConstraints( + const dom::MediaTrackConstraints& aConstraints, dom::CallerType aCallerType) + -> RefPtr { + return ApplyConstraintsPromise::CreateAndReject( + MakeRefPtr( + dom::MediaStreamError::Name::OverconstrainedError, ""), + __func__); +} + +void RemoteTrackSource::SetPrincipal(nsIPrincipal* aPrincipal) { + mPrincipal = aPrincipal; + PrincipalChanged(); +} + +void RemoteTrackSource::SetMuted(bool aMuted) { MutedChanged(aMuted); } + +void RemoteTrackSource::ForceEnded() { OverrideEnded(); } + +SourceMediaTrack* RemoteTrackSource::Stream() const { return mStream; } + +} // namespace mozilla diff --git a/dom/media/webrtc/jsapi/RemoteTrackSource.h b/dom/media/webrtc/jsapi/RemoteTrackSource.h new file mode 100644 index 0000000000..be730f0030 --- /dev/null +++ b/dom/media/webrtc/jsapi/RemoteTrackSource.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef DOM_MEDIA_WEBRTC_JSAPI_REMOTETRACKSOURCE_H_ +#define DOM_MEDIA_WEBRTC_JSAPI_REMOTETRACKSOURCE_H_ + +#include "MediaStreamTrack.h" + +namespace mozilla { + +namespace dom { +class RTCRtpReceiver; +} + +class SourceMediaTrack; + +class RemoteTrackSource : public dom::MediaStreamTrackSource { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RemoteTrackSource, + dom::MediaStreamTrackSource) + + RemoteTrackSource(SourceMediaTrack* aStream, dom::RTCRtpReceiver* aReceiver, + nsIPrincipal* aPrincipal, const nsString& aLabel, + TrackingId aTrackingId); + + void Destroy() override; + + dom::MediaSourceEnum GetMediaSource() const override { + return dom::MediaSourceEnum::Other; + } + + RefPtr ApplyConstraints( + const dom::MediaTrackConstraints& aConstraints, + dom::CallerType aCallerType) override; + + void Stop() override { + // XXX (Bug 1314270): Implement rejection logic if necessary when we have + // clarity in the spec. + } + + void Disable() override {} + + void Enable() override {} + + void SetPrincipal(nsIPrincipal* aPrincipal); + void SetMuted(bool aMuted); + void ForceEnded(); + + SourceMediaTrack* Stream() const; + + private: + virtual ~RemoteTrackSource(); + + RefPtr mStream; + RefPtr mReceiver; +}; + +} // namespace mozilla + +#endif // DOM_MEDIA_WEBRTC_JSAPI_REMOTETRACKSOURCE_H_ diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalChild.h b/dom/media/webrtc/jsapi/WebrtcGlobalChild.h new file mode 100644 index 0000000000..147b4b44f0 --- /dev/null +++ b/dom/media/webrtc/jsapi/WebrtcGlobalChild.h @@ -0,0 +1,42 @@ +/* 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/. */ + +#ifndef _WEBRTC_GLOBAL_CHILD_H_ +#define _WEBRTC_GLOBAL_CHILD_H_ + +#include "mozilla/dom/PWebrtcGlobalChild.h" + +namespace mozilla::dom { + +class WebrtcGlobalChild : public PWebrtcGlobalChild { + friend class ContentChild; + + bool mShutdown; + + MOZ_IMPLICIT WebrtcGlobalChild(); + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + virtual mozilla::ipc::IPCResult RecvGetStats( + const nsAString& aPcIdFilter, GetStatsResolver&& aResolve) override; + virtual mozilla::ipc::IPCResult RecvClearStats() override; + // MOZ_CAN_RUN_SCRIPT_BOUNDARY because we can't do MOZ_CAN_RUN_SCRIPT in + // ipdl-generated things yet. + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual mozilla::ipc::IPCResult RecvGetLog( + GetLogResolver&& aResolve) override; + virtual mozilla::ipc::IPCResult RecvClearLog() override; + virtual mozilla::ipc::IPCResult RecvSetAecLogging( + const bool& aEnable) override; + virtual mozilla::ipc::IPCResult RecvSetDebugMode(const int& aLevel) override; + + static WebrtcGlobalChild* GetOrSet(const Maybe& aChild); + + public: + virtual ~WebrtcGlobalChild(); + static WebrtcGlobalChild* Get(); +}; + +} // namespace mozilla::dom + +#endif // _WEBRTC_GLOBAL_CHILD_H_ diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp b/dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp new file mode 100644 index 0000000000..7d0a9e64b1 --- /dev/null +++ b/dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp @@ -0,0 +1,829 @@ +/* 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 "WebrtcGlobalInformation.h" +#include "WebrtcGlobalStatsHistory.h" +#include "mozilla/Assertions.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/dom/PWebrtcGlobal.h" +#include "mozilla/dom/PWebrtcGlobalChild.h" +#include "mozilla/media/webrtc/WebrtcGlobal.h" +#include "WebrtcGlobalChild.h" +#include "WebrtcGlobalParent.h" + +#include +#include +#include + +#include "mozilla/dom/WebrtcGlobalInformationBinding.h" +#include "mozilla/dom/RTCStatsReportBinding.h" // for RTCStatsReportInternal +#include "mozilla/dom/ContentChild.h" + +#include "nsISupports.h" +#include "nsITimer.h" +#include "nsLiteralString.h" +#include "nsNetCID.h" // NS_SOCKETTRANSPORTSERVICE_CONTRACTID +#include "nsServiceManagerUtils.h" // do_GetService +#include "nsXULAppAPI.h" +#include "mozilla/ErrorResult.h" +#include "nsProxyRelease.h" // nsMainThreadPtrHolder +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ClearOnShutdown.h" + +#include "common/browser_logging/WebRtcLog.h" +#include "nsString.h" +#include "transport/runnable_utils.h" +#include "MediaTransportHandler.h" +#include "PeerConnectionCtx.h" +#include "PeerConnectionImpl.h" + +#ifdef XP_WIN +# include +#endif + +namespace mozilla::dom { + +using StatsRequestCallback = + nsMainThreadPtrHandle; + +using LogRequestCallback = nsMainThreadPtrHandle; + +class WebrtcContentParents { + public: + static WebrtcGlobalParent* Alloc(); + static void Dealloc(WebrtcGlobalParent* aParent); + static bool Empty() { return sContentParents.empty(); } + static const std::vector>& GetAll() { + return sContentParents; + } + + WebrtcContentParents() = delete; + WebrtcContentParents(const WebrtcContentParents&) = delete; + WebrtcContentParents& operator=(const WebrtcContentParents&) = delete; + + private: + static std::vector> sContentParents; +}; + +std::vector> WebrtcContentParents::sContentParents; + +WebrtcGlobalParent* WebrtcContentParents::Alloc() { + RefPtr cp = new WebrtcGlobalParent; + sContentParents.push_back(cp); + return cp.get(); +} + +void WebrtcContentParents::Dealloc(WebrtcGlobalParent* aParent) { + if (aParent) { + aParent->mShutdown = true; + auto cp = + std::find(sContentParents.begin(), sContentParents.end(), aParent); + if (cp != sContentParents.end()) { + sContentParents.erase(cp); + } + } +} + +static PeerConnectionCtx* GetPeerConnectionCtx() { + if (PeerConnectionCtx::isActive()) { + MOZ_ASSERT(PeerConnectionCtx::GetInstance()); + return PeerConnectionCtx::GetInstance(); + } + return nullptr; +} + +static nsTArray& GetWebrtcGlobalStatsStash() { + static StaticAutoPtr> sStash; + if (!sStash) { + sStash = new nsTArray(); + ClearOnShutdown(&sStash); + } + return *sStash; +} + +static RefPtr +GetStatsPromiseForThisProcess(const nsAString& aPcIdFilter) { + nsTArray> promises; + + std::set pcids; + if (auto* ctx = GetPeerConnectionCtx()) { + // Grab stats for PCs that still exist + ctx->ForEachPeerConnection([&](PeerConnectionImpl* aPc) { + if (!aPcIdFilter.IsEmpty() && + !aPcIdFilter.EqualsASCII(aPc->GetIdAsAscii().c_str())) { + return; + } + if (!aPc->IsClosed() || !aPc->LongTermStatsIsDisabled()) { + nsString id; + aPc->GetId(id); + pcids.insert(id); + promises.AppendElement(aPc->GetStats(nullptr, true)); + } + }); + + // Grab previously stashed stats, if they aren't dupes, and ensure they + // are marked closed. (In a content process, this should already have + // happened, but in the parent process, the stash will contain the last + // observed stats from the content processes. From the perspective of the + // parent process, these are assumed closed unless we see new stats from the + // content process that say otherwise.) + for (auto& report : GetWebrtcGlobalStatsStash()) { + report.mClosed = true; + if ((aPcIdFilter.IsEmpty() || aPcIdFilter == report.mPcid) && + !pcids.count(report.mPcid)) { + promises.AppendElement(dom::RTCStatsReportPromise::CreateAndResolve( + MakeUnique(report), __func__)); + } + } + } + + auto UnwrapUniquePtrs = [](dom::RTCStatsReportPromise::AllSettledPromiseType:: + ResolveOrRejectValue&& aResult) { + nsTArray reports; + MOZ_RELEASE_ASSERT(aResult.IsResolve(), "AllSettled should never reject!"); + for (auto& reportResult : aResult.ResolveValue()) { + if (reportResult.IsResolve()) { + reports.AppendElement(*reportResult.ResolveValue()); + } + } + return PWebrtcGlobalParent::GetStatsPromise::CreateAndResolve( + std::move(reports), __func__); + }; + + return dom::RTCStatsReportPromise::AllSettled( + GetMainThreadSerialEventTarget(), promises) + ->Then(GetMainThreadSerialEventTarget(), __func__, + std::move(UnwrapUniquePtrs)); +} + +static std::map>& GetWebrtcGlobalLogStash() { + static StaticAutoPtr>> sStash; + if (!sStash) { + sStash = new std::map>(); + ClearOnShutdown(&sStash); + } + return *sStash; +} + +static void ClearLongTermStats() { + if (!NS_IsMainThread()) { + MOZ_ASSERT(NS_IsMainThread()); + return; + } + + GetWebrtcGlobalStatsStash().Clear(); + if (XRE_IsParentProcess()) { + WebrtcGlobalStatsHistory::Clear(); + } + if (auto* ctx = GetPeerConnectionCtx()) { + ctx->ClearClosedStats(); + } +} + +void WebrtcGlobalInformation::ClearAllStats(const GlobalObject& aGlobal) { + if (!NS_IsMainThread()) { + return; + } + + // Chrome-only API + MOZ_ASSERT(XRE_IsParentProcess()); + + if (!WebrtcContentParents::Empty()) { + // Pass on the request to any content process based PeerConnections. + for (const auto& cp : WebrtcContentParents::GetAll()) { + Unused << cp->SendClearStats(); + } + } + + // Flush the history for the chrome process + ClearLongTermStats(); +} + +void WebrtcGlobalInformation::GetStatsHistoryPcIds( + const GlobalObject& aGlobal, + WebrtcGlobalStatisticsHistoryPcIdsCallback& aPcIdsCallback, + ErrorResult& aRv) { + if (!NS_IsMainThread()) { + aRv.Throw(NS_ERROR_NOT_SAME_THREAD); + return; + } + + MOZ_ASSERT(XRE_IsParentProcess()); + + IgnoredErrorResult rv; + aPcIdsCallback.Call(WebrtcGlobalStatsHistory::PcIds(), rv); + aRv = NS_OK; +} + +void WebrtcGlobalInformation::GetStatsHistorySince( + const GlobalObject& aGlobal, + WebrtcGlobalStatisticsHistoryCallback& aStatsCallback, + const nsAString& pcIdFilter, const Optional& aAfter, + const Optional& aSdpAfter, ErrorResult& aRv) { + if (!NS_IsMainThread()) { + aRv.Throw(NS_ERROR_NOT_SAME_THREAD); + return; + } + + MOZ_ASSERT(XRE_IsParentProcess()); + + WebrtcGlobalStatisticsReport history; + + auto statsAfter = aAfter.WasPassed() ? Some(aAfter.Value()) : Nothing(); + auto sdpAfter = aSdpAfter.WasPassed() ? Some(aSdpAfter.Value()) : Nothing(); + + WebrtcGlobalStatsHistory::GetHistory(pcIdFilter).apply([&](auto& hist) { + if (!history.mReports.AppendElements(hist->Since(statsAfter), fallible)) { + mozalloc_handle_oom(0); + } + if (!history.mSdpHistories.AppendElement(hist->SdpSince(sdpAfter), + fallible)) { + mozalloc_handle_oom(0); + } + }); + + IgnoredErrorResult rv; + aStatsCallback.Call(history, rv); + aRv = NS_OK; +} + +using StatsPromiseArray = + nsTArray>; + +void WebrtcGlobalInformation::GatherHistory() { + const nsString emptyFilter; + if (!NS_IsMainThread()) { + MOZ_ASSERT(NS_IsMainThread()); + return; + } + + MOZ_ASSERT(XRE_IsParentProcess()); + using StatsPromise = PWebrtcGlobalParent::GetStatsPromise; + auto resolveThenAppendStatsHistory = [](RefPtr&& promise) { + auto AppendStatsHistory = [](StatsPromise::ResolveOrRejectValue&& result) { + if (result.IsReject()) { + return; + } + for (const auto& report : result.ResolveValue()) { + WebrtcGlobalStatsHistory::Record( + MakeUnique(report)); + } + }; + promise->Then(GetMainThreadSerialEventTarget(), __func__, + std::move(AppendStatsHistory)); + }; + for (const auto& cp : WebrtcContentParents::GetAll()) { + resolveThenAppendStatsHistory(cp->SendGetStats(emptyFilter)); + } + resolveThenAppendStatsHistory(GetStatsPromiseForThisProcess(emptyFilter)); +} + +void WebrtcGlobalInformation::GetAllStats( + const GlobalObject& aGlobal, WebrtcGlobalStatisticsCallback& aStatsCallback, + const Optional& aPcIdFilter, ErrorResult& aRv) { + if (!NS_IsMainThread()) { + aRv.Throw(NS_ERROR_NOT_SAME_THREAD); + return; + } + + MOZ_ASSERT(XRE_IsParentProcess()); + + StatsPromiseArray statsPromises; + + nsString filter; + if (aPcIdFilter.WasPassed()) { + filter = aPcIdFilter.Value(); + } + + for (const auto& cp : WebrtcContentParents::GetAll()) { + statsPromises.AppendElement(cp->SendGetStats(filter)); + } + + // Stats from this (the parent) process. How long do we keep supporting this? + statsPromises.AppendElement(GetStatsPromiseForThisProcess(filter)); + + // CallbackObject does not support threadsafe refcounting, and must be + // used and destroyed on main. + StatsRequestCallback callbackHandle( + new nsMainThreadPtrHolder( + "WebrtcGlobalStatisticsCallback", &aStatsCallback)); + + auto FlattenThenStashThenCallback = + [callbackHandle, + filter](PWebrtcGlobalParent::GetStatsPromise::AllSettledPromiseType:: + ResolveOrRejectValue&& aResult) MOZ_CAN_RUN_SCRIPT_BOUNDARY { + std::set pcids; + WebrtcGlobalStatisticsReport flattened; + MOZ_RELEASE_ASSERT(aResult.IsResolve(), + "AllSettled should never reject!"); + // Flatten stats from content processes and parent process. + // The stats from the parent process (which will come last) might + // contain some stale content-process stats, so skip those. + for (auto& processResult : aResult.ResolveValue()) { + // TODO: Report rejection on individual content processes someday? + if (processResult.IsResolve()) { + for (auto& pcStats : processResult.ResolveValue()) { + if (!pcids.count(pcStats.mPcid)) { + pcids.insert(pcStats.mPcid); + if (!flattened.mReports.AppendElement(std::move(pcStats), + fallible)) { + mozalloc_handle_oom(0); + } + } + } + } + } + + if (filter.IsEmpty()) { + // Unfiltered is simple; the flattened result becomes the new stash. + GetWebrtcGlobalStatsStash() = flattened.mReports; + } else if (!flattened.mReports.IsEmpty()) { + // Update our stash with the single result. + MOZ_ASSERT(flattened.mReports.Length() == 1); + StashStats(flattened.mReports[0]); + } + + IgnoredErrorResult rv; + callbackHandle->Call(flattened, rv); + }; + + PWebrtcGlobalParent::GetStatsPromise::AllSettled( + GetMainThreadSerialEventTarget(), statsPromises) + ->Then(GetMainThreadSerialEventTarget(), __func__, + std::move(FlattenThenStashThenCallback)); + + aRv = NS_OK; +} + +static RefPtr GetLogPromise() { + PeerConnectionCtx* ctx = GetPeerConnectionCtx(); + if (!ctx) { + // This process has never created a PeerConnection, so no ICE logging. + return PWebrtcGlobalParent::GetLogPromise::CreateAndResolve( + Sequence(), __func__); + } + + nsresult rv; + nsCOMPtr stsThread = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + + if (NS_WARN_IF(NS_FAILED(rv) || !stsThread)) { + return PWebrtcGlobalParent::GetLogPromise::CreateAndResolve( + Sequence(), __func__); + } + + RefPtr transportHandler = ctx->GetTransportHandler(); + + auto AddMarkers = + [](MediaTransportHandler::IceLogPromise::ResolveOrRejectValue&& aValue) { + nsString pid; + pid.AppendInt(getpid()); + Sequence logs; + if (aValue.IsResolve() && !aValue.ResolveValue().IsEmpty()) { + bool ok = logs.AppendElement( + u"+++++++ BEGIN (process id "_ns + pid + u") ++++++++"_ns, + fallible); + ok &= + !!logs.AppendElements(std::move(aValue.ResolveValue()), fallible); + ok &= !!logs.AppendElement( + u"+++++++ END (process id "_ns + pid + u") ++++++++"_ns, + fallible); + if (!ok) { + mozalloc_handle_oom(0); + } + } + return PWebrtcGlobalParent::GetLogPromise::CreateAndResolve( + std::move(logs), __func__); + }; + + return transportHandler->GetIceLog(nsCString()) + ->Then(GetMainThreadSerialEventTarget(), __func__, std::move(AddMarkers)); +} + +static nsresult RunLogClear() { + PeerConnectionCtx* ctx = GetPeerConnectionCtx(); + if (!ctx) { + // This process has never created a PeerConnection, so no ICE logging. + return NS_OK; + } + + nsresult rv; + nsCOMPtr stsThread = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + + if (NS_FAILED(rv)) { + return rv; + } + if (!stsThread) { + return NS_ERROR_FAILURE; + } + + RefPtr transportHandler = ctx->GetTransportHandler(); + + return RUN_ON_THREAD( + stsThread, + WrapRunnable(transportHandler, &MediaTransportHandler::ClearIceLog), + NS_DISPATCH_NORMAL); +} + +void WebrtcGlobalInformation::ClearLogging(const GlobalObject& aGlobal) { + if (!NS_IsMainThread()) { + return; + } + + // Chrome-only API + MOZ_ASSERT(XRE_IsParentProcess()); + GetWebrtcGlobalLogStash().clear(); + + if (!WebrtcContentParents::Empty()) { + // Clear content process signaling logs + for (const auto& cp : WebrtcContentParents::GetAll()) { + Unused << cp->SendClearLog(); + } + } + + // Clear chrome process signaling logs + Unused << RunLogClear(); +} + +static RefPtr UpdateLogStash() { + nsTArray> logPromises; + MOZ_ASSERT(XRE_IsParentProcess()); + for (const auto& cp : WebrtcContentParents::GetAll()) { + auto StashLog = + [id = cp->Id() * 2 /* Make sure 1 isn't used */]( + PWebrtcGlobalParent::GetLogPromise::ResolveOrRejectValue&& aValue) { + if (aValue.IsResolve() && !aValue.ResolveValue().IsEmpty()) { + GetWebrtcGlobalLogStash()[id] = aValue.ResolveValue(); + } + return GenericPromise::CreateAndResolve(true, __func__); + }; + logPromises.AppendElement(cp->SendGetLog()->Then( + GetMainThreadSerialEventTarget(), __func__, std::move(StashLog))); + } + + // Get ICE logging for this (the parent) process. How long do we support this? + logPromises.AppendElement(GetLogPromise()->Then( + GetMainThreadSerialEventTarget(), __func__, + [](PWebrtcGlobalParent::GetLogPromise::ResolveOrRejectValue&& aValue) { + if (aValue.IsResolve()) { + GetWebrtcGlobalLogStash()[1] = aValue.ResolveValue(); + } + return GenericPromise::CreateAndResolve(true, __func__); + })); + + return GenericPromise::AllSettled(GetMainThreadSerialEventTarget(), + logPromises) + ->Then(GetMainThreadSerialEventTarget(), __func__, + [](GenericPromise::AllSettledPromiseType::ResolveOrRejectValue&& + aValue) { + // We don't care about the value, since we're just going to copy + // what is in the stash. This ignores failures too, which is what + // we want. + return GenericPromise::CreateAndResolve(true, __func__); + }); +} + +void WebrtcGlobalInformation::GetLogging( + const GlobalObject& aGlobal, const nsAString& aPattern, + WebrtcGlobalLoggingCallback& aLoggingCallback, ErrorResult& aRv) { + if (!NS_IsMainThread()) { + aRv.Throw(NS_ERROR_NOT_SAME_THREAD); + return; + } + + MOZ_ASSERT(XRE_IsParentProcess()); + + nsAutoString pattern(aPattern); + + // CallbackObject does not support threadsafe refcounting, and must be + // destroyed on main. + LogRequestCallback callbackHandle( + new nsMainThreadPtrHolder( + "WebrtcGlobalLoggingCallback", &aLoggingCallback)); + + auto FilterThenCallback = + [pattern, callbackHandle](GenericPromise::ResolveOrRejectValue&& aValue) + MOZ_CAN_RUN_SCRIPT_BOUNDARY { + dom::Sequence flattened; + for (const auto& [id, log] : GetWebrtcGlobalLogStash()) { + (void)id; + for (const auto& line : log) { + if (pattern.IsEmpty() || (line.Find(pattern) != kNotFound)) { + if (!flattened.AppendElement(line, fallible)) { + mozalloc_handle_oom(0); + } + } + } + } + IgnoredErrorResult rv; + callbackHandle->Call(flattened, rv); + }; + + UpdateLogStash()->Then(GetMainThreadSerialEventTarget(), __func__, + std::move(FilterThenCallback)); + aRv = NS_OK; +} + +static int32_t sLastSetLevel = 0; +static bool sLastAECDebug = false; +static Maybe sAecDebugLogDir; + +void WebrtcGlobalInformation::SetDebugLevel(const GlobalObject& aGlobal, + int32_t aLevel) { + if (aLevel) { + StartWebRtcLog(mozilla::LogLevel(aLevel)); + } else { + StopWebRtcLog(); + } + sLastSetLevel = aLevel; + + for (const auto& cp : WebrtcContentParents::GetAll()) { + Unused << cp->SendSetDebugMode(aLevel); + } +} + +int32_t WebrtcGlobalInformation::DebugLevel(const GlobalObject& aGlobal) { + return sLastSetLevel; +} + +void WebrtcGlobalInformation::SetAecDebug(const GlobalObject& aGlobal, + bool aEnable) { + if (aEnable) { + sAecDebugLogDir = Some(StartAecLog()); + } else { + StopAecLog(); + } + + sLastAECDebug = aEnable; + + for (const auto& cp : WebrtcContentParents::GetAll()) { + Unused << cp->SendSetAecLogging(aEnable); + } +} + +bool WebrtcGlobalInformation::AecDebug(const GlobalObject& aGlobal) { + return sLastAECDebug; +} + +void WebrtcGlobalInformation::GetAecDebugLogDir(const GlobalObject& aGlobal, + nsAString& aDir) { + aDir = NS_ConvertASCIItoUTF16(sAecDebugLogDir.valueOr(""_ns)); +} + +/*static*/ +void WebrtcGlobalInformation::StashStats( + const dom::RTCStatsReportInternal& aReport) { + // Remove previous report, if present + // TODO: Make this a map instead of an array? + for (size_t i = 0; i < GetWebrtcGlobalStatsStash().Length();) { + auto& pcStats = GetWebrtcGlobalStatsStash()[i]; + if (pcStats.mPcid == aReport.mPcid) { + GetWebrtcGlobalStatsStash().RemoveElementAt(i); + break; + } + ++i; + } + // Stash final stats + GetWebrtcGlobalStatsStash().AppendElement(aReport); +} + +void WebrtcGlobalInformation::AdjustTimerReferences( + PcTrackingUpdate&& aUpdate) { + static StaticRefPtr sHistoryTimer; + static StaticAutoPtr> sPcids; + + MOZ_ASSERT(NS_IsMainThread()); + + auto HandleAdd = [&](nsString&& aPcid, bool aIsLongTermStatsDisabled) { + if (!sPcids) { + sPcids = new nsTHashSet(); + ClearOnShutdown(&sPcids); + } + sPcids->EnsureInserted(aPcid); + // Reserve a stats history + WebrtcGlobalStatsHistory::InitHistory(nsString(aPcid), + aIsLongTermStatsDisabled); + if (!sHistoryTimer) { + sHistoryTimer = NS_NewTimer(GetMainThreadSerialEventTarget()); + if (sHistoryTimer) { + sHistoryTimer->InitWithNamedFuncCallback( + [](nsITimer* aTimer, void* aClosure) { + if (WebrtcGlobalStatsHistory::Pref::Enabled()) { + WebrtcGlobalInformation::GatherHistory(); + } + }, + nullptr, WebrtcGlobalStatsHistory::Pref::PollIntervalMs(), + nsITimer::TYPE_REPEATING_SLACK, + "WebrtcGlobalInformation::GatherHistory"); + } + ClearOnShutdown(&sHistoryTimer); + } + }; + + auto HandleRemove = [&](const nsString& aRemoved) { + WebrtcGlobalStatsHistory::CloseHistory(nsString(aRemoved)); + if (!sPcids || !sPcids->Count()) { + return; + } + if (!sPcids->Contains(aRemoved)) { + return; + } + sPcids->Remove(aRemoved); + if (!sPcids->Count() && sHistoryTimer) { + sHistoryTimer->Cancel(); + sHistoryTimer = nullptr; + } + }; + + switch (aUpdate.Type()) { + case PcTrackingUpdate::Type::Add: { + HandleAdd(std::move(aUpdate.mPcid), + aUpdate.mLongTermStatsDisabled.valueOrFrom([&]() { + MOZ_ASSERT(aUpdate.mLongTermStatsDisabled.isNothing()); + return true; + })); + return; + } + case PcTrackingUpdate::Type::Remove: { + HandleRemove(aUpdate.mPcid); + return; + } + default: { + MOZ_ASSERT(false, "Invalid PcCount operation"); + } + } +} + +WebrtcGlobalParent* WebrtcGlobalParent::Alloc() { + return WebrtcContentParents::Alloc(); +} + +bool WebrtcGlobalParent::Dealloc(WebrtcGlobalParent* aActor) { + WebrtcContentParents::Dealloc(aActor); + return true; +} + +void WebrtcGlobalParent::ActorDestroy(ActorDestroyReason aWhy) { + mShutdown = true; + for (const auto& pcId : mPcids) { + using Update = WebrtcGlobalInformation::PcTrackingUpdate; + auto update = Update::Remove(nsString(pcId)); + WebrtcGlobalInformation::PeerConnectionTracking(update); + } +} + +mozilla::ipc::IPCResult WebrtcGlobalParent::Recv__delete__() { + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebrtcGlobalParent::RecvPeerConnectionCreated( + const nsAString& aPcId, const bool& aIsLongTermStatsDisabled) { + if (mShutdown) { + return IPC_OK(); + } + mPcids.EnsureInserted(aPcId); + using Update = WebrtcGlobalInformation::PcTrackingUpdate; + auto update = Update::Add(nsString(aPcId), aIsLongTermStatsDisabled); + WebrtcGlobalInformation::PeerConnectionTracking(update); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebrtcGlobalParent::RecvPeerConnectionDestroyed( + const nsAString& aPcId) { + mPcids.EnsureRemoved(aPcId); + using Update = WebrtcGlobalInformation::PcTrackingUpdate; + auto update = Update::Remove(nsString(aPcId)); + WebrtcGlobalStatsHistory::CloseHistory(aPcId); + WebrtcGlobalInformation::PeerConnectionTracking(update); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebrtcGlobalParent::RecvPeerConnectionFinalStats( + const RTCStatsReportInternal& aFinalStats) { + auto finalStats = MakeUnique(aFinalStats); + WebrtcGlobalStatsHistory::Record(std::move(finalStats)); + WebrtcGlobalStatsHistory::CloseHistory(aFinalStats.mPcid); + return IPC_OK(); +} + +MOZ_IMPLICIT WebrtcGlobalParent::WebrtcGlobalParent() : mShutdown(false) { + MOZ_COUNT_CTOR(WebrtcGlobalParent); +} + +MOZ_IMPLICIT WebrtcGlobalParent::~WebrtcGlobalParent() { + MOZ_COUNT_DTOR(WebrtcGlobalParent); +} + +mozilla::ipc::IPCResult WebrtcGlobalChild::RecvGetStats( + const nsAString& aPcIdFilter, GetStatsResolver&& aResolve) { + if (!mShutdown) { + GetStatsPromiseForThisProcess(aPcIdFilter) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [resolve = std::move(aResolve)]( + nsTArray&& aReports) { + resolve(std::move(aReports)); + }, + []() { MOZ_CRASH(); }); + return IPC_OK(); + } + + aResolve(nsTArray()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebrtcGlobalChild::RecvClearStats() { + if (mShutdown) { + return IPC_OK(); + } + + ClearLongTermStats(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebrtcGlobalChild::RecvGetLog( + GetLogResolver&& aResolve) { + if (mShutdown) { + aResolve(Sequence()); + return IPC_OK(); + } + + GetLogPromise()->Then( + GetMainThreadSerialEventTarget(), __func__, + [aResolve = std::move(aResolve)]( + PWebrtcGlobalParent::GetLogPromise::ResolveOrRejectValue&& aValue) { + if (aValue.IsResolve()) { + aResolve(aValue.ResolveValue()); + } else { + aResolve(Sequence()); + } + }); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebrtcGlobalChild::RecvClearLog() { + if (mShutdown) { + return IPC_OK(); + } + + RunLogClear(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebrtcGlobalChild::RecvSetAecLogging( + const bool& aEnable) { + if (!mShutdown) { + if (aEnable) { + StartAecLog(); + } else { + StopAecLog(); + } + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebrtcGlobalChild::RecvSetDebugMode(const int& aLevel) { + if (!mShutdown) { + if (aLevel) { + StartWebRtcLog(mozilla::LogLevel(aLevel)); + } else { + StopWebRtcLog(); + } + } + return IPC_OK(); +} + +WebrtcGlobalChild* WebrtcGlobalChild::GetOrSet( + const Maybe& aChild) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(XRE_IsContentProcess()); + static WebrtcGlobalChild* sChild; + if (!sChild && !aChild) { + sChild = static_cast( + ContentChild::GetSingleton()->SendPWebrtcGlobalConstructor()); + } + aChild.apply([](auto* child) { sChild = child; }); + return sChild; +} + +WebrtcGlobalChild* WebrtcGlobalChild::Get() { return GetOrSet(Nothing()); } + +void WebrtcGlobalChild::ActorDestroy(ActorDestroyReason aWhy) { + mShutdown = true; +} + +MOZ_IMPLICIT WebrtcGlobalChild::WebrtcGlobalChild() : mShutdown(false) { + MOZ_COUNT_CTOR(WebrtcGlobalChild); +} + +MOZ_IMPLICIT WebrtcGlobalChild::~WebrtcGlobalChild() { + MOZ_COUNT_DTOR(WebrtcGlobalChild); + GetOrSet(Some(nullptr)); +} + +} // namespace mozilla::dom diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalInformation.h b/dom/media/webrtc/jsapi/WebrtcGlobalInformation.h new file mode 100644 index 0000000000..d43013bb43 --- /dev/null +++ b/dom/media/webrtc/jsapi/WebrtcGlobalInformation.h @@ -0,0 +1,102 @@ +/* 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/. */ + +#ifndef _WEBRTC_GLOBAL_INFORMATION_H_ +#define _WEBRTC_GLOBAL_INFORMATION_H_ + +#include +#include "mozilla/dom/WebrtcGlobalInformationBinding.h" +#include "nsString.h" +#include "mozilla/dom/BindingDeclarations.h" // for Optional +#include "nsDOMNavigationTiming.h" +#include "WebrtcGlobalStatsHistory.h" + +namespace mozilla { +class PeerConnectionImpl; +class ErrorResult; + +namespace dom { + +class GlobalObject; +class WebrtcGlobalStatisticsCallback; +class WebrtcGlobalStatisticsHistoryPcIdsCallback; +class WebrtcGlobalLoggingCallback; +struct RTCStatsReportInternal; + +class WebrtcGlobalInformation { + public: + MOZ_CAN_RUN_SCRIPT + static void GetAllStats(const GlobalObject& aGlobal, + WebrtcGlobalStatisticsCallback& aStatsCallback, + const Optional& aPcIdFilter, + ErrorResult& aRv); + + MOZ_CAN_RUN_SCRIPT + static void GetStatsHistoryPcIds( + const GlobalObject& aGlobal, + WebrtcGlobalStatisticsHistoryPcIdsCallback& aPcIdsCallback, + ErrorResult& aRv); + + MOZ_CAN_RUN_SCRIPT + static void GetStatsHistorySince( + const GlobalObject& aGlobal, + WebrtcGlobalStatisticsHistoryCallback& aStatsCallback, + const nsAString& aPcIdFilter, const Optional& aAfter, + const Optional& aSdpAfter, ErrorResult& aRv); + + static void GatherHistory(); + + static void ClearAllStats(const GlobalObject& aGlobal); + + MOZ_CAN_RUN_SCRIPT + static void GetLogging(const GlobalObject& aGlobal, const nsAString& aPattern, + WebrtcGlobalLoggingCallback& aLoggingCallback, + ErrorResult& aRv); + + static void ClearLogging(const GlobalObject& aGlobal); + + static void SetDebugLevel(const GlobalObject& aGlobal, int32_t aLevel); + static int32_t DebugLevel(const GlobalObject& aGlobal); + + static void SetAecDebug(const GlobalObject& aGlobal, bool aEnable); + static bool AecDebug(const GlobalObject& aGlobal); + static void GetAecDebugLogDir(const GlobalObject& aGlobal, nsAString& aDir); + + static void StashStats(const RTCStatsReportInternal& aReport); + + WebrtcGlobalInformation() = delete; + WebrtcGlobalInformation(const WebrtcGlobalInformation& aOrig) = delete; + WebrtcGlobalInformation& operator=(const WebrtcGlobalInformation& aRhs) = + delete; + + struct PcTrackingUpdate { + static PcTrackingUpdate Add(const nsString& aPcid, + const bool& aLongTermStatsDisabled) { + return PcTrackingUpdate{aPcid, Some(aLongTermStatsDisabled)}; + } + static PcTrackingUpdate Remove(const nsString& aPcid) { + return PcTrackingUpdate{aPcid, Nothing()}; + } + nsString mPcid; + Maybe mLongTermStatsDisabled; + enum class Type { + Add, + Remove, + }; + Type Type() const { + return mLongTermStatsDisabled ? Type::Add : Type::Remove; + } + }; + static void PeerConnectionTracking(PcTrackingUpdate& aUpdate) { + AdjustTimerReferences(std::move(aUpdate)); + } + + private: + static void AdjustTimerReferences(PcTrackingUpdate&& aUpdate); +}; + +} // namespace dom +} // namespace mozilla + +#endif // _WEBRTC_GLOBAL_INFORMATION_H_ diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalParent.h b/dom/media/webrtc/jsapi/WebrtcGlobalParent.h new file mode 100644 index 0000000000..8372c4182f --- /dev/null +++ b/dom/media/webrtc/jsapi/WebrtcGlobalParent.h @@ -0,0 +1,52 @@ +/* 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/. */ + +#ifndef _WEBRTC_GLOBAL_PARENT_H_ +#define _WEBRTC_GLOBAL_PARENT_H_ + +#include "mozilla/dom/PWebrtcGlobalParent.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "nsISupportsImpl.h" + +namespace mozilla::dom { + +class WebrtcParents; + +class WebrtcGlobalParent : public PWebrtcGlobalParent { + friend class ContentParent; + friend class WebrtcGlobalInformation; + friend class WebrtcContentParents; + + bool mShutdown; + nsTHashSet mPcids; + + MOZ_IMPLICIT WebrtcGlobalParent(); + + static WebrtcGlobalParent* Alloc(); + static bool Dealloc(WebrtcGlobalParent* aActor); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + virtual mozilla::ipc::IPCResult Recv__delete__() override; + // Notification that a PeerConnection exists, and stats polling can begin + // if it hasn't already begun due to a previously created PeerConnection. + virtual mozilla::ipc::IPCResult RecvPeerConnectionCreated( + const nsAString& aPcId, const bool& aIsLongTermStatsDisabled) override; + // Notification that a PeerConnection no longer exists, and stats polling + // can end if there are no other PeerConnections. + virtual mozilla::ipc::IPCResult RecvPeerConnectionDestroyed( + const nsAString& aPcid) override; + // Ditto but we have final stats + virtual mozilla::ipc::IPCResult RecvPeerConnectionFinalStats( + const RTCStatsReportInternal& aFinalStats) override; + virtual ~WebrtcGlobalParent(); + + public: + NS_INLINE_DECL_REFCOUNTING(WebrtcGlobalParent) + + bool IsActive() { return !mShutdown; } +}; + +} // namespace mozilla::dom + +#endif // _WEBRTC_GLOBAL_PARENT_H_ diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.cpp b/dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.cpp new file mode 100644 index 0000000000..1d576b5dca --- /dev/null +++ b/dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.cpp @@ -0,0 +1,282 @@ +/* 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 "WebrtcGlobalStatsHistory.h" +#include + +#include "domstubs.h" +#include "mozilla/LinkedList.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/RTCStatsReportBinding.h" // for RTCStatsReportInternal +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/StaticPrefs_media.h" +#include "mozilla/fallible.h" +#include "mozilla/mozalloc_oom.h" +#include "nsDOMNavigationTiming.h" + +namespace mozilla::dom { + +constexpr auto SEC_TO_MS(const DOMHighResTimeStamp sec) -> DOMHighResTimeStamp { + return sec * 1000.0; +} + +constexpr auto MIN_TO_MS(const DOMHighResTimeStamp min) -> DOMHighResTimeStamp { + return SEC_TO_MS(min * 60.0); +} + +// Prefs +auto WebrtcGlobalStatsHistory::Pref::Enabled() -> bool { + return mozilla::StaticPrefs::media_aboutwebrtc_hist_enabled(); +} + +auto WebrtcGlobalStatsHistory::Pref::PollIntervalMs() -> uint32_t { + return mozilla::StaticPrefs::media_aboutwebrtc_hist_poll_interval_ms(); +} + +auto WebrtcGlobalStatsHistory::Pref::StorageWindowS() -> uint32_t { + return mozilla::StaticPrefs::media_aboutwebrtc_hist_storage_window_s(); +} + +auto WebrtcGlobalStatsHistory::Pref::PruneAfterM() -> uint32_t { + return mozilla::StaticPrefs::media_aboutwebrtc_hist_prune_after_m(); +} + +auto WebrtcGlobalStatsHistory::Pref::ClosedStatsToRetain() -> uint32_t { + return mozilla::StaticPrefs::media_aboutwebrtc_hist_closed_stats_to_retain(); +} + +auto WebrtcGlobalStatsHistory::Get() -> WebrtcGlobalStatsHistory::StatsMap& { + static StaticAutoPtr sHist; + if (!sHist) { + sHist = new StatsMap(); + ClearOnShutdown(&sHist); + } + return *sHist; +} + +auto WebrtcGlobalStatsHistory::Entry::ReportElement::Timestamp() const + -> DOMHighResTimeStamp { + return report->mTimestamp; +} + +auto WebrtcGlobalStatsHistory::Entry::SdpElement::Timestamp() const + -> DOMHighResTimeStamp { + return sdp.mTimestamp; +} + +auto WebrtcGlobalStatsHistory::Entry::MakeReportElement( + UniquePtr aReport) + -> WebrtcGlobalStatsHistory::Entry::ReportElement* { + auto* elem = new ReportElement(); + elem->report = std::move(aReport); + // We don't want to store a copy of the SDP history with each stats entry. + // SDP History is stored seperately, see MakeSdpElements. + elem->report->mSdpHistory.Clear(); + return elem; +} + +auto WebrtcGlobalStatsHistory::Entry::MakeSdpElementsSince( + Sequence&& aSdpHistory, + const Maybe& aSdpAfter) + -> AutoCleanLinkedList { + AutoCleanLinkedList result; + for (auto& sdpHist : aSdpHistory) { + if (!aSdpAfter || aSdpAfter.value() < sdpHist.mTimestamp) { + auto* element = new SdpElement(); + element->sdp = sdpHist; + result.insertBack(element); + } + } + return result; +} + +template +auto FindFirstEntryAfter(const T* first, + const Maybe& aAfter) -> const T* { + const auto* current = first; + while (aAfter && current && current->Timestamp() <= aAfter.value()) { + current = current->getNext(); + } + return current; +} + +template +auto CountElementsToEndInclusive(const LinkedListElement* elem) -> size_t { + size_t count = 0; + const auto* cursor = elem; + while (cursor && cursor->isInList()) { + count++; + cursor = cursor->getNext(); + } + return count; +} + +auto WebrtcGlobalStatsHistory::Entry::Since( + const Maybe& aAfter) const + -> nsTArray { + nsTArray results; + const auto* cursor = FindFirstEntryAfter(mReports.getFirst(), aAfter); + const auto count = CountElementsToEndInclusive(cursor); + if (!results.SetCapacity(count, fallible)) { + mozalloc_handle_oom(0); + } + while (cursor) { + results.AppendElement(RTCStatsReportInternal(*cursor->report)); + cursor = cursor->getNext(); + } + return results; +} + +auto WebrtcGlobalStatsHistory::Entry::SdpSince( + const Maybe& aAfter) const -> RTCSdpHistoryInternal { + RTCSdpHistoryInternal results; + results.mPcid = mPcid; + // If no timestamp was passed copy the entire history + const auto* cursor = FindFirstEntryAfter(mSdp.getFirst(), aAfter); + const auto count = CountElementsToEndInclusive(cursor); + if (!results.mSdpHistory.SetCapacity(count, fallible)) { + mozalloc_handle_oom(0); + } + while (cursor) { + if (!results.mSdpHistory.AppendElement( + RTCSdpHistoryEntryInternal(cursor->sdp), fallible)) { + mozalloc_handle_oom(0); + } + cursor = cursor->getNext(); + } + return results; +} + +auto WebrtcGlobalStatsHistory::Entry::Prune(const DOMHighResTimeStamp aBefore) + -> void { + // Clear everything in the case that we don't keep stats + if (mIsLongTermStatsDisabled) { + mReports.clear(); + } + // Clear everything before the cutoff + for (auto* element = mReports.getFirst(); + element && element->report->mTimestamp < aBefore; + element = mReports.getFirst()) { + delete mReports.popFirst(); + } + // I don't think we should prune SDPs but if we did it would look like this: + // Note: we always keep the most recent SDP +} + +auto WebrtcGlobalStatsHistory::InitHistory(const nsAString& aPcId, + const bool aIsLongTermStatsDisabled) + -> void { + MOZ_ASSERT(XRE_IsParentProcess()); + if (WebrtcGlobalStatsHistory::Get().MaybeGet(aPcId)) { + return; + } + WebrtcGlobalStatsHistory::Get().GetOrInsertNew(aPcId, nsString(aPcId), + aIsLongTermStatsDisabled); +}; + +auto WebrtcGlobalStatsHistory::Record(UniquePtr aReport) + -> void { + MOZ_ASSERT(XRE_IsParentProcess()); + // Use the report timestamp as "now" for determining time depth + // based pruning. + const auto now = aReport->mTimestamp; + const auto earliest = now - SEC_TO_MS(Pref::StorageWindowS()); + + // Store closed state before moving the report + const auto closed = aReport->mClosed; + const auto pcId = aReport->mPcid; + + auto history = WebrtcGlobalStatsHistory::GetHistory(aReport->mPcid); + if (history && Pref::Enabled()) { + auto entry = history.value(); + // Remove expired entries + entry->Prune(earliest); + // Find new SDP entries + auto sdpAfter = Maybe(Nothing()); + if (auto* lastSdp = entry->mSdp.getLast(); lastSdp) { + sdpAfter = Some(lastSdp->Timestamp()); + } + entry->mSdp.extendBack( + Entry::MakeSdpElementsSince(std::move(aReport->mSdpHistory), sdpAfter)); + // Reports must be in ascending order by mTimestamp + const auto* latest = entry->mReports.getLast(); + // Maintain sorted order + if (!latest || latest->report->mTimestamp < aReport->mTimestamp) { + entry->mReports.insertBack(Entry::MakeReportElement(std::move(aReport))); + } + } + // Close the history if needed + if (closed) { + CloseHistory(pcId); + } +} + +auto WebrtcGlobalStatsHistory::CloseHistory(const nsAString& aPcId) -> void { + MOZ_ASSERT(XRE_IsParentProcess()); + auto maybeHist = WebrtcGlobalStatsHistory::Get().MaybeGet(aPcId); + if (!maybeHist) { + return; + } + { + auto&& hist = maybeHist.value(); + hist->mIsClosed = true; + + if (hist->mIsLongTermStatsDisabled) { + WebrtcGlobalStatsHistory::Get().Remove(aPcId); + return; + } + } + size_t remainingClosedStatsToRetain = + WebrtcGlobalStatsHistory::Pref::ClosedStatsToRetain(); + WebrtcGlobalStatsHistory::Get().RemoveIf([&](auto& iter) { + auto& entry = iter.Data(); + if (!entry->mIsClosed) { + return false; + } + if (entry->mIsLongTermStatsDisabled) { + return true; + } + if (remainingClosedStatsToRetain > 0) { + remainingClosedStatsToRetain -= 1; + return false; + } + return true; + }); +} + +auto WebrtcGlobalStatsHistory::Clear() -> void { + MOZ_ASSERT(XRE_IsParentProcess()); + WebrtcGlobalStatsHistory::Get().RemoveIf([](auto& aIter) { + // First clear all the closed histories. + if (aIter.Data()->mIsClosed) { + return true; + } + // For all remaining histories clear their stored reports + aIter.Data()->mReports.clear(); + // As an optimization we don't clear the SDP, because that would + // be reconstitued in the very next stats gathering polling period. + // Those are potentially large allocations which we can skip. + return false; + }); +} + +auto WebrtcGlobalStatsHistory::PcIds() -> dom::Sequence { + MOZ_ASSERT(XRE_IsParentProcess()); + dom::Sequence pcIds; + for (const auto& pcId : WebrtcGlobalStatsHistory::Get().Keys()) { + if (!pcIds.AppendElement(pcId, fallible)) { + mozalloc_handle_oom(0); + } + } + return pcIds; +} + +auto WebrtcGlobalStatsHistory::GetHistory(const nsAString& aPcId) + -> Maybe > { + MOZ_ASSERT(XRE_IsParentProcess()); + const auto pcid = NS_ConvertUTF16toUTF8(aPcId); + + return WebrtcGlobalStatsHistory::Get().MaybeGet(aPcId); +} +} // namespace mozilla::dom diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.h b/dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.h new file mode 100644 index 0000000000..aa820e787d --- /dev/null +++ b/dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.h @@ -0,0 +1,88 @@ +/* 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/. */ + +#pragma once + +#include +#include "domstubs.h" +#include "mozilla/LinkedList.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/DOMString.h" +#include "mozilla/dom/RTCStatsReportBinding.h" +#include "nsDOMNavigationTiming.h" +#include "nsHashKeys.h" +#include "nsTHashMap.h" + +namespace mozilla::dom { +class WebrtcGlobalStatisticsHistoryCallback; +struct RTCStatsReportInternal; + +struct WebrtcGlobalStatsHistory { + // History preferences + struct Pref { + static auto Enabled() -> bool; + static auto PollIntervalMs() -> uint32_t; + static auto StorageWindowS() -> uint32_t; + static auto PruneAfterM() -> uint32_t; + static auto ClosedStatsToRetain() -> uint32_t; + Pref() = delete; + ~Pref() = delete; + }; + + struct Entry { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Entry) + // We need to wrap the report in an element + struct ReportElement : public LinkedListElement { + UniquePtr report; + auto Timestamp() const -> DOMHighResTimeStamp; + virtual ~ReportElement() = default; + }; + // And likewise for the SDP history + struct SdpElement : public LinkedListElement { + RTCSdpHistoryEntryInternal sdp; + auto Timestamp() const -> DOMHighResTimeStamp; + virtual ~SdpElement() = default; + }; + + explicit Entry(const nsString& aPcid, const bool aIsLongTermStatsDisabled) + : mPcid(aPcid), mIsLongTermStatsDisabled(aIsLongTermStatsDisabled) {} + + nsString mPcid; + AutoCleanLinkedList mReports; + AutoCleanLinkedList mSdp; + bool mIsLongTermStatsDisabled; + bool mIsClosed = false; + + auto Since(const Maybe& aAfter) const + -> nsTArray; + auto SdpSince(const Maybe& aAfter) const + -> RTCSdpHistoryInternal; + + static auto MakeReportElement(UniquePtr aReport) + -> ReportElement*; + static auto MakeSdpElementsSince( + Sequence&& aSdpHistory, + const Maybe& aSdpAfter) + -> AutoCleanLinkedList; + auto Prune(const DOMHighResTimeStamp aBefore) -> void; + + private: + virtual ~Entry() = default; + }; + using StatsMap = nsTHashMap >; + static auto InitHistory(const nsAString& aPcId, + const bool aIsLongTermStatsDisabled) -> void; + static auto Record(UniquePtr aReport) -> void; + static auto CloseHistory(const nsAString& aPcId) -> void; + static auto GetHistory(const nsAString& aPcId) -> Maybe >; + static auto Clear() -> void; + static auto PcIds() -> dom::Sequence; + + WebrtcGlobalStatsHistory() = delete; + + private: + static auto Get() -> StatsMap&; +}; +} // namespace mozilla::dom diff --git a/dom/media/webrtc/jsapi/moz.build b/dom/media/webrtc/jsapi/moz.build new file mode 100644 index 0000000000..2c1dbe79f1 --- /dev/null +++ b/dom/media/webrtc/jsapi/moz.build @@ -0,0 +1,51 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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("/dom/media/webrtc/third_party_build/webrtc.mozbuild") + +LOCAL_INCLUDES += [ + "!/ipc/ipdl/_ipdlheaders", + "/dom/base", + "/dom/media", + "/dom/media/webrtc", + "/ipc/chromium/src", + "/media/webrtc", + "/netwerk/dns", # For nsDNSService2.h + "/third_party/libsrtp/src/include", + "/third_party/libwebrtc", + "/third_party/libwebrtc/third_party/abseil-cpp", +] + +UNIFIED_SOURCES += [ + "MediaTransportHandler.cpp", + "MediaTransportHandlerIPC.cpp", + "MediaTransportParent.cpp", + "PacketDumper.cpp", + "PeerConnectionCtx.cpp", + "PeerConnectionImpl.cpp", + "RemoteTrackSource.cpp", + "RTCDtlsTransport.cpp", + "RTCDTMFSender.cpp", + "RTCRtpReceiver.cpp", + "RTCRtpSender.cpp", + "RTCRtpTransceiver.cpp", + "RTCSctpTransport.cpp", + "RTCStatsIdGenerator.cpp", + "RTCStatsReport.cpp", + "WebrtcGlobalInformation.cpp", + "WebrtcGlobalStatsHistory.cpp", +] + +EXPORTS.mozilla.dom += [ + "RTCDtlsTransport.h", + "RTCDTMFSender.h", + "RTCRtpReceiver.h", + "RTCRtpSender.h", + "RTCRtpTransceiver.h", + "RTCSctpTransport.h", + "RTCStatsReport.h", +] + +FINAL_LIBRARY = "xul" diff --git a/dom/media/webrtc/jsep/JsepCodecDescription.h b/dom/media/webrtc/jsep/JsepCodecDescription.h new file mode 100644 index 0000000000..5336182b77 --- /dev/null +++ b/dom/media/webrtc/jsep/JsepCodecDescription.h @@ -0,0 +1,1196 @@ +/* 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/. */ + +#ifndef _JSEPCODECDESCRIPTION_H_ +#define _JSEPCODECDESCRIPTION_H_ + +#include +#include +#include "sdp/SdpMediaSection.h" +#include "sdp/SdpHelper.h" +#include "nsCRT.h" +#include "nsString.h" +#include "mozilla/net/DataChannelProtocol.h" +#include "mozilla/Preferences.h" + +namespace mozilla { + +#define JSEP_CODEC_CLONE(T) \ + virtual JsepCodecDescription* Clone() const override { return new T(*this); } + +// A single entry in our list of known codecs. +class JsepCodecDescription { + public: + JsepCodecDescription(const std::string& defaultPt, const std::string& name, + uint32_t clock, uint32_t channels, bool enabled) + : mDefaultPt(defaultPt), + mName(name), + mClock(clock), + mChannels(channels), + mEnabled(enabled), + mStronglyPreferred(false), + mDirection(sdp::kSend) {} + virtual ~JsepCodecDescription() {} + + virtual SdpMediaSection::MediaType Type() const = 0; + + virtual JsepCodecDescription* Clone() const = 0; + + bool GetPtAsInt(uint16_t* ptOutparam) const { + return SdpHelper::GetPtAsInt(mDefaultPt, ptOutparam); + } + + // The id used for codec stats, to uniquely identify this codec configuration + // within a transport. + const nsString& StatsId() const { + if (!mStatsId) { + mStatsId.emplace(); + mStatsId->AppendPrintf( + "_%s_%s/%s_%u_%u_%s", mDefaultPt.c_str(), + Type() == SdpMediaSection::kVideo ? "video" : "audio", mName.c_str(), + mClock, mChannels, mSdpFmtpLine ? mSdpFmtpLine->c_str() : "nothing"); + } + return *mStatsId; + } + + virtual bool Matches(const std::string& fmt, + const SdpMediaSection& remoteMsection) const { + // note: fmt here is remote fmt (to go with remoteMsection) + if (Type() != remoteMsection.GetMediaType()) { + return false; + } + + const SdpRtpmapAttributeList::Rtpmap* entry(remoteMsection.FindRtpmap(fmt)); + + if (entry) { + if (!nsCRT::strcasecmp(mName.c_str(), entry->name.c_str()) && + (mClock == entry->clock) && (mChannels == entry->channels)) { + return ParametersMatch(fmt, remoteMsection); + } + } else if (!fmt.compare("9") && mName == "G722") { + return true; + } else if (!fmt.compare("0") && mName == "PCMU") { + return true; + } else if (!fmt.compare("8") && mName == "PCMA") { + return true; + } + return false; + } + + virtual bool ParametersMatch(const std::string& fmt, + const SdpMediaSection& remoteMsection) const { + return true; + } + + Maybe GetMatchingFormat( + const SdpMediaSection& remoteMsection) const { + for (const auto& fmt : remoteMsection.GetFormats()) { + if (Matches(fmt, remoteMsection)) { + return Some(fmt); + } + } + return Nothing(); + } + + virtual bool Negotiate(const std::string& pt, + const SdpMediaSection& remoteMsection, + bool remoteIsOffer, + Maybe localMsection) { + // Configuration might change. Invalidate the stats id. + mStatsId = Nothing(); + if (mDirection == sdp::kSend || remoteIsOffer) { + mDefaultPt = pt; + } + if (localMsection) { + // Offer/answer is concluding. Update the sdpFmtpLine. + MOZ_ASSERT(mDirection == sdp::kSend || mDirection == sdp::kRecv); + const SdpMediaSection& msection = + mDirection == sdp::kSend ? remoteMsection : *localMsection; + UpdateSdpFmtpLine(ToMaybeRef(msection.FindFmtp(mDefaultPt))); + } + return true; + } + + virtual void ApplyConfigToFmtp( + UniquePtr& aFmtp) const = 0; + + virtual void AddToMediaSection(SdpMediaSection& msection) const { + if (mEnabled && msection.GetMediaType() == Type()) { + if (mDirection == sdp::kRecv) { + msection.AddCodec(mDefaultPt, mName, mClock, mChannels); + } + + AddParametersToMSection(msection); + } + } + + virtual void AddParametersToMSection(SdpMediaSection& msection) const {} + + virtual void EnsureNoDuplicatePayloadTypes(std::set& aUsedPts) { + mEnabled = EnsurePayloadTypeNotDuplicate(aUsedPts, mDefaultPt); + } + + bool EnsurePayloadTypeNotDuplicate(std::set& aUsedPts, + std::string& aPtToCheck) { + if (!mEnabled) { + return false; + } + + if (!aUsedPts.count(aPtToCheck)) { + aUsedPts.insert(aPtToCheck); + return true; + } + + // |codec| cannot use its current payload type. Try to find another. + for (uint16_t freePt = 96; freePt <= 127; ++freePt) { + // Not super efficient, but readability is probably more important. + std::ostringstream os; + os << freePt; + std::string freePtAsString = os.str(); + + if (!aUsedPts.count(freePtAsString)) { + aUsedPts.insert(freePtAsString); + aPtToCheck = freePtAsString; + return true; + } + } + + return false; + } + + // TODO Bug 1751671: Take a verbatim fmtp line (std::string or eq.) instead + // of fmtp parameters that have to be (re-)serialized. + void UpdateSdpFmtpLine( + const Maybe aParams) { + mSdpFmtpLine = aParams.map([](const auto& aFmtp) { + std::stringstream ss; + aFmtp.Serialize(ss); + return ss.str(); + }); + } + + std::string mDefaultPt; + std::string mName; + Maybe mSdpFmtpLine; + mutable Maybe mStatsId; + uint32_t mClock; + uint32_t mChannels; + bool mEnabled; + bool mStronglyPreferred; + sdp::Direction mDirection; + // Will hold constraints from both fmtp and rid + EncodingConstraints mConstraints; +}; + +class JsepAudioCodecDescription : public JsepCodecDescription { + public: + JsepAudioCodecDescription(const std::string& defaultPt, + const std::string& name, uint32_t clock, + uint32_t channels, bool enabled = true) + : JsepCodecDescription(defaultPt, name, clock, channels, enabled), + mMaxPlaybackRate(0), + mForceMono(false), + mFECEnabled(false), + mDtmfEnabled(false), + mMaxAverageBitrate(0), + mDTXEnabled(false), + mFrameSizeMs(0), + mMinFrameSizeMs(0), + mMaxFrameSizeMs(0), + mCbrEnabled(false) {} + + static constexpr SdpMediaSection::MediaType type = SdpMediaSection::kAudio; + + SdpMediaSection::MediaType Type() const override { return type; } + + JSEP_CODEC_CLONE(JsepAudioCodecDescription) + + static UniquePtr CreateDefaultOpus() { + // Per jmspeex on IRC: + // For 32KHz sampling, 28 is ok, 32 is good, 40 should be really good + // quality. Note that 1-2Kbps will be wasted on a stereo Opus channel + // with mono input compared to configuring it for mono. + // If we reduce bitrate enough Opus will low-pass us; 16000 will kill a + // 9KHz tone. This should be adaptive when we're at the low-end of video + // bandwidth (say <100Kbps), and if we're audio-only, down to 8 or + // 12Kbps. + return MakeUnique("109", "opus", 48000, 2); + } + + static UniquePtr CreateDefaultG722() { + return MakeUnique("9", "G722", 8000, 1); + } + + static UniquePtr CreateDefaultPCMU() { + return MakeUnique("0", "PCMU", 8000, 1); + } + + static UniquePtr CreateDefaultPCMA() { + return MakeUnique("8", "PCMA", 8000, 1); + } + + static UniquePtr CreateDefaultTelephoneEvent() { + return MakeUnique("101", "telephone-event", 8000, + 1); + } + + SdpFmtpAttributeList::OpusParameters GetOpusParameters( + const std::string& pt, const SdpMediaSection& msection) const { + // Will contain defaults if nothing else + SdpFmtpAttributeList::OpusParameters result; + auto* params = msection.FindFmtp(pt); + + if (params && params->codec_type == SdpRtpmapAttributeList::kOpus) { + result = + static_cast(*params); + } + + return result; + } + + SdpFmtpAttributeList::TelephoneEventParameters GetTelephoneEventParameters( + const std::string& pt, const SdpMediaSection& msection) const { + // Will contain defaults if nothing else + SdpFmtpAttributeList::TelephoneEventParameters result; + auto* params = msection.FindFmtp(pt); + + if (params && + params->codec_type == SdpRtpmapAttributeList::kTelephoneEvent) { + result = + static_cast( + *params); + } + + return result; + } + + void AddParametersToMSection(SdpMediaSection& msection) const override { + if (mDirection == sdp::kSend) { + return; + } + + if (mName == "opus") { + UniquePtr opusParams = + MakeUnique( + GetOpusParameters(mDefaultPt, msection)); + + ApplyConfigToFmtp(opusParams); + + msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, *opusParams)); + } else if (mName == "telephone-event") { + // add the default dtmf tones + SdpFmtpAttributeList::TelephoneEventParameters teParams( + GetTelephoneEventParameters(mDefaultPt, msection)); + msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, teParams)); + } + } + + bool Negotiate(const std::string& pt, const SdpMediaSection& remoteMsection, + bool remoteIsOffer, + Maybe localMsection) override { + JsepCodecDescription::Negotiate(pt, remoteMsection, remoteIsOffer, + localMsection); + if (mName == "opus" && mDirection == sdp::kSend) { + SdpFmtpAttributeList::OpusParameters opusParams( + GetOpusParameters(mDefaultPt, remoteMsection)); + + mMaxPlaybackRate = opusParams.maxplaybackrate; + mForceMono = !opusParams.stereo; + // draft-ietf-rtcweb-fec-03.txt section 4.2 says support for FEC + // at the received side is declarative and can be negotiated + // separately for either media direction. + mFECEnabled = opusParams.useInBandFec; + if ((opusParams.maxAverageBitrate >= 6000) && + (opusParams.maxAverageBitrate <= 510000)) { + mMaxAverageBitrate = opusParams.maxAverageBitrate; + } + mDTXEnabled = opusParams.useDTX; + if (remoteMsection.GetAttributeList().HasAttribute( + SdpAttribute::kPtimeAttribute)) { + mFrameSizeMs = remoteMsection.GetAttributeList().GetPtime(); + } else { + mFrameSizeMs = opusParams.frameSizeMs; + } + mMinFrameSizeMs = opusParams.minFrameSizeMs; + if (remoteMsection.GetAttributeList().HasAttribute( + SdpAttribute::kMaxptimeAttribute)) { + mMaxFrameSizeMs = remoteMsection.GetAttributeList().GetMaxptime(); + } else { + mMaxFrameSizeMs = opusParams.maxFrameSizeMs; + } + mCbrEnabled = opusParams.useCbr; + } + + return true; + } + + void ApplyConfigToFmtp( + UniquePtr& aFmtp) const override { + if (mName == "opus") { + SdpFmtpAttributeList::OpusParameters opusParams; + if (aFmtp) { + MOZ_RELEASE_ASSERT(aFmtp->codec_type == SdpRtpmapAttributeList::kOpus); + opusParams = + static_cast(*aFmtp); + opusParams.useInBandFec = mFECEnabled ? 1 : 0; + } else { + // If we weren't passed a fmtp to use then show we can do in band FEC + // for getCapabilities queries. + opusParams.useInBandFec = 1; + } + if (mMaxPlaybackRate) { + opusParams.maxplaybackrate = mMaxPlaybackRate; + } + opusParams.maxAverageBitrate = mMaxAverageBitrate; + + if (mChannels == 2 && + !Preferences::GetBool("media.peerconnection.sdp.disable_stereo_fmtp", + false) && + !mForceMono) { + // We prefer to receive stereo, if available. + opusParams.stereo = 1; + } + opusParams.useDTX = mDTXEnabled; + opusParams.frameSizeMs = mFrameSizeMs; + opusParams.minFrameSizeMs = mMinFrameSizeMs; + opusParams.maxFrameSizeMs = mMaxFrameSizeMs; + opusParams.useCbr = mCbrEnabled; + aFmtp.reset(opusParams.Clone()); + } + }; + + uint32_t mMaxPlaybackRate; + bool mForceMono; + bool mFECEnabled; + bool mDtmfEnabled; + uint32_t mMaxAverageBitrate; + bool mDTXEnabled; + uint32_t mFrameSizeMs; + uint32_t mMinFrameSizeMs; + uint32_t mMaxFrameSizeMs; + bool mCbrEnabled; +}; + +class JsepVideoCodecDescription : public JsepCodecDescription { + public: + JsepVideoCodecDescription(const std::string& defaultPt, + const std::string& name, uint32_t clock, + bool enabled = true) + : JsepCodecDescription(defaultPt, name, clock, 0, enabled), + mTmmbrEnabled(false), + mRembEnabled(false), + mFECEnabled(false), + mTransportCCEnabled(false), + mRtxEnabled(false), + mProfileLevelId(0), + mPacketizationMode(0) { + // Add supported rtcp-fb types + mNackFbTypes.push_back(""); + mNackFbTypes.push_back(SdpRtcpFbAttributeList::pli); + mCcmFbTypes.push_back(SdpRtcpFbAttributeList::fir); + } + + static constexpr SdpMediaSection::MediaType type = SdpMediaSection::kVideo; + + SdpMediaSection::MediaType Type() const override { return type; } + + static UniquePtr CreateDefaultVP8(bool aUseRtx) { + auto codec = MakeUnique("120", "VP8", 90000); + // Defaults for mandatory params + codec->mConstraints.maxFs = 12288; // Enough for 2048x1536 + codec->mConstraints.maxFps = Some(60); + if (aUseRtx) { + codec->EnableRtx("124"); + } + return codec; + } + + static UniquePtr CreateDefaultVP9(bool aUseRtx) { + auto codec = MakeUnique("121", "VP9", 90000); + // Defaults for mandatory params + codec->mConstraints.maxFs = 12288; // Enough for 2048x1536 + codec->mConstraints.maxFps = Some(60); + if (aUseRtx) { + codec->EnableRtx("125"); + } + return codec; + } + + static UniquePtr CreateDefaultH264_0( + bool aUseRtx) { + auto codec = MakeUnique("97", "H264", 90000); + codec->mPacketizationMode = 0; + // Defaults for mandatory params + codec->mProfileLevelId = 0x42E00D; + if (aUseRtx) { + codec->EnableRtx("98"); + } + return codec; + } + + static UniquePtr CreateDefaultH264_1( + bool aUseRtx) { + auto codec = MakeUnique("126", "H264", 90000); + codec->mPacketizationMode = 1; + // Defaults for mandatory params + codec->mProfileLevelId = 0x42E00D; + if (aUseRtx) { + codec->EnableRtx("127"); + } + return codec; + } + + static UniquePtr CreateDefaultUlpFec() { + return MakeUnique( + "123", // payload type + "ulpfec", // codec name + 90000 // clock rate (match other video codecs) + ); + } + + static UniquePtr CreateDefaultRed() { + return MakeUnique( + "122", // payload type + "red", // codec name + 90000 // clock rate (match other video codecs) + ); + } + + void ApplyConfigToFmtp( + UniquePtr& aFmtp) const override { + if (mName == "H264") { + SdpFmtpAttributeList::H264Parameters h264Params; + if (aFmtp) { + MOZ_RELEASE_ASSERT(aFmtp->codec_type == SdpRtpmapAttributeList::kH264); + h264Params = + static_cast(*aFmtp); + } + + if (mDirection == sdp::kSend) { + if (!h264Params.level_asymmetry_allowed) { + // First time the fmtp has been set; set just in case this is for a + // sendonly m-line, since even though we aren't receiving the level + // negotiation still needs to happen (sigh). + h264Params.profile_level_id = mProfileLevelId; + } + } else { + // Parameters that only apply to what we receive + h264Params.max_mbps = mConstraints.maxMbps; + h264Params.max_fs = mConstraints.maxFs; + h264Params.max_cpb = mConstraints.maxCpb; + h264Params.max_dpb = mConstraints.maxDpb; + h264Params.max_br = mConstraints.maxBr; + strncpy(h264Params.sprop_parameter_sets, mSpropParameterSets.c_str(), + sizeof(h264Params.sprop_parameter_sets) - 1); + h264Params.profile_level_id = mProfileLevelId; + } + + // Parameters that apply to both the send and recv directions + h264Params.packetization_mode = mPacketizationMode; + // Hard-coded, may need to change someday? + h264Params.level_asymmetry_allowed = true; + + // Parameters that apply to both the send and recv directions + h264Params.packetization_mode = mPacketizationMode; + // Hard-coded, may need to change someday? + h264Params.level_asymmetry_allowed = true; + aFmtp.reset(h264Params.Clone()); + } else if (mName == "VP8" || mName == "VP9") { + SdpRtpmapAttributeList::CodecType type = + mName == "VP8" ? SdpRtpmapAttributeList::CodecType::kVP8 + : SdpRtpmapAttributeList::CodecType::kVP9; + auto vp8Params = SdpFmtpAttributeList::VP8Parameters(type); + + if (aFmtp) { + MOZ_RELEASE_ASSERT(aFmtp->codec_type == type); + vp8Params = + static_cast(*aFmtp); + } + // VP8 and VP9 share the same SDP parameters thus far + vp8Params.max_fs = mConstraints.maxFs; + if (mConstraints.maxFps.isSome()) { + vp8Params.max_fr = + static_cast(std::round(*mConstraints.maxFps)); + } else { + vp8Params.max_fr = 60; + } + aFmtp.reset(vp8Params.Clone()); + } + } + + virtual void EnableTmmbr() { + // EnableTmmbr can be called multiple times due to multiple calls to + // PeerConnectionImpl::ConfigureJsepSessionCodecs + if (!mTmmbrEnabled) { + mTmmbrEnabled = true; + mCcmFbTypes.push_back(SdpRtcpFbAttributeList::tmmbr); + } + } + + virtual void EnableRemb() { + // EnableRemb can be called multiple times due to multiple calls to + // PeerConnectionImpl::ConfigureJsepSessionCodecs + if (!mRembEnabled) { + mRembEnabled = true; + mOtherFbTypes.push_back({"", SdpRtcpFbAttributeList::kRemb, "", ""}); + } + } + + virtual void EnableFec(std::string redPayloadType, + std::string ulpfecPayloadType) { + // Enabling FEC for video works a little differently than enabling + // REMB or TMMBR. Support for FEC is indicated by the presence of + // particular codes (red and ulpfec) instead of using rtcpfb + // attributes on a given codec. There is no rtcpfb to push for FEC + // as can be seen above when REMB or TMMBR are enabled. + + // Ensure we have valid payload types. This returns zero on failure, which + // is a valid payload type. + uint16_t redPt, ulpfecPt; + if (!SdpHelper::GetPtAsInt(redPayloadType, &redPt) || + !SdpHelper::GetPtAsInt(ulpfecPayloadType, &ulpfecPt)) { + return; + } + + mFECEnabled = true; + mREDPayloadType = redPayloadType; + mULPFECPayloadType = ulpfecPayloadType; + } + + virtual void EnableTransportCC() { + if (!mTransportCCEnabled) { + mTransportCCEnabled = true; + mOtherFbTypes.push_back( + {"", SdpRtcpFbAttributeList::kTransportCC, "", ""}); + } + } + + void EnableRtx(const std::string& rtxPayloadType) { + mRtxEnabled = true; + mRtxPayloadType = rtxPayloadType; + } + + void AddParametersToMSection(SdpMediaSection& msection) const override { + AddFmtpsToMSection(msection); + AddRtcpFbsToMSection(msection); + } + + void AddFmtpsToMSection(SdpMediaSection& msection) const { + if (mName == "H264") { + UniquePtr h264Params = + MakeUnique( + GetH264Parameters(mDefaultPt, msection)); + + ApplyConfigToFmtp(h264Params); + + msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, *h264Params)); + } else if (mName == "red" && !mRedundantEncodings.empty()) { + SdpFmtpAttributeList::RedParameters redParams( + GetRedParameters(mDefaultPt, msection)); + redParams.encodings = mRedundantEncodings; + msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, redParams)); + } else if (mName == "VP8" || mName == "VP9") { + if (mDirection == sdp::kRecv) { + // VP8 and VP9 share the same SDP parameters thus far + UniquePtr vp8Params = + MakeUnique( + GetVP8Parameters(mDefaultPt, msection)); + ApplyConfigToFmtp(vp8Params); + msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, *vp8Params)); + } + } + + if (mRtxEnabled && mDirection == sdp::kRecv) { + SdpFmtpAttributeList::RtxParameters params( + GetRtxParameters(mDefaultPt, msection)); + uint16_t apt; + if (SdpHelper::GetPtAsInt(mDefaultPt, &apt)) { + if (apt <= 127) { + msection.AddCodec(mRtxPayloadType, "rtx", mClock, mChannels); + + params.apt = apt; + msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mRtxPayloadType, params)); + } + } + } + } + + void AddRtcpFbsToMSection(SdpMediaSection& msection) const { + SdpRtcpFbAttributeList rtcpfbs(msection.GetRtcpFbs()); + for (const auto& rtcpfb : rtcpfbs.mFeedbacks) { + if (rtcpfb.pt == mDefaultPt) { + // Already set by the codec for the other direction. + return; + } + } + + for (const std::string& type : mAckFbTypes) { + rtcpfbs.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kAck, type); + } + for (const std::string& type : mNackFbTypes) { + rtcpfbs.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kNack, type); + } + for (const std::string& type : mCcmFbTypes) { + rtcpfbs.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kCcm, type); + } + for (const auto& fb : mOtherFbTypes) { + rtcpfbs.PushEntry(mDefaultPt, fb.type, fb.parameter, fb.extra); + } + + msection.SetRtcpFbs(rtcpfbs); + } + + SdpFmtpAttributeList::H264Parameters GetH264Parameters( + const std::string& pt, const SdpMediaSection& msection) const { + // Will contain defaults if nothing else + SdpFmtpAttributeList::H264Parameters result; + auto* params = msection.FindFmtp(pt); + + if (params && params->codec_type == SdpRtpmapAttributeList::kH264) { + result = + static_cast(*params); + } + + return result; + } + + SdpFmtpAttributeList::RedParameters GetRedParameters( + const std::string& pt, const SdpMediaSection& msection) const { + SdpFmtpAttributeList::RedParameters result; + auto* params = msection.FindFmtp(pt); + + if (params && params->codec_type == SdpRtpmapAttributeList::kRed) { + result = static_cast(*params); + } + + return result; + } + + SdpFmtpAttributeList::RtxParameters GetRtxParameters( + const std::string& pt, const SdpMediaSection& msection) const { + SdpFmtpAttributeList::RtxParameters result; + const auto* params = msection.FindFmtp(pt); + + if (params && params->codec_type == SdpRtpmapAttributeList::kRtx) { + result = static_cast(*params); + } + + return result; + } + + Maybe GetRtxPtByApt(const std::string& apt, + const SdpMediaSection& msection) const { + Maybe result; + uint16_t aptAsInt; + if (!SdpHelper::GetPtAsInt(apt, &aptAsInt)) { + return result; + } + + const SdpAttributeList& attrs = msection.GetAttributeList(); + if (attrs.HasAttribute(SdpAttribute::kFmtpAttribute)) { + for (const auto& fmtpAttr : attrs.GetFmtp().mFmtps) { + if (fmtpAttr.parameters) { + auto* params = fmtpAttr.parameters.get(); + if (params && params->codec_type == SdpRtpmapAttributeList::kRtx) { + const SdpFmtpAttributeList::RtxParameters* rtxParams = + static_cast(params); + if (rtxParams->apt == aptAsInt) { + result = Some(fmtpAttr.format); + break; + } + } + } + } + } + return result; + } + + SdpFmtpAttributeList::VP8Parameters GetVP8Parameters( + const std::string& pt, const SdpMediaSection& msection) const { + SdpRtpmapAttributeList::CodecType expectedType( + mName == "VP8" ? SdpRtpmapAttributeList::kVP8 + : SdpRtpmapAttributeList::kVP9); + + // Will contain defaults if nothing else + SdpFmtpAttributeList::VP8Parameters result(expectedType); + auto* params = msection.FindFmtp(pt); + + if (params && params->codec_type == expectedType) { + result = static_cast(*params); + } + + return result; + } + + void NegotiateRtcpFb(const SdpMediaSection& remoteMsection, + SdpRtcpFbAttributeList::Type type, + std::vector* supportedTypes) { + Maybe remoteFmt = GetMatchingFormat(remoteMsection); + if (!remoteFmt) { + return; + } + std::vector temp; + for (auto& subType : *supportedTypes) { + if (remoteMsection.HasRtcpFb(*remoteFmt, type, subType)) { + temp.push_back(subType); + } + } + *supportedTypes = temp; + } + + void NegotiateRtcpFb( + const SdpMediaSection& remoteMsection, + std::vector* supportedFbs) { + Maybe remoteFmt = GetMatchingFormat(remoteMsection); + if (!remoteFmt) { + return; + } + std::vector temp; + for (auto& fb : *supportedFbs) { + if (remoteMsection.HasRtcpFb(*remoteFmt, fb.type, fb.parameter)) { + temp.push_back(fb); + } + } + *supportedFbs = temp; + } + + void NegotiateRtcpFb(const SdpMediaSection& remote) { + // Removes rtcp-fb types that the other side doesn't support + NegotiateRtcpFb(remote, SdpRtcpFbAttributeList::kAck, &mAckFbTypes); + NegotiateRtcpFb(remote, SdpRtcpFbAttributeList::kNack, &mNackFbTypes); + NegotiateRtcpFb(remote, SdpRtcpFbAttributeList::kCcm, &mCcmFbTypes); + NegotiateRtcpFb(remote, &mOtherFbTypes); + } + + virtual bool Negotiate(const std::string& pt, + const SdpMediaSection& remoteMsection, + bool remoteIsOffer, + Maybe localMsection) override { + JsepCodecDescription::Negotiate(pt, remoteMsection, remoteIsOffer, + localMsection); + if (mName == "H264") { + SdpFmtpAttributeList::H264Parameters h264Params( + GetH264Parameters(mDefaultPt, remoteMsection)); + + // Level is negotiated symmetrically if level asymmetry is disallowed + if (!h264Params.level_asymmetry_allowed) { + SetSaneH264Level(std::min(GetSaneH264Level(h264Params.profile_level_id), + GetSaneH264Level(mProfileLevelId)), + &mProfileLevelId); + } + + if (mDirection == sdp::kSend) { + // Remote values of these apply only to the send codec. + mConstraints.maxFs = h264Params.max_fs; + mConstraints.maxMbps = h264Params.max_mbps; + mConstraints.maxCpb = h264Params.max_cpb; + mConstraints.maxDpb = h264Params.max_dpb; + mConstraints.maxBr = h264Params.max_br; + mSpropParameterSets = h264Params.sprop_parameter_sets; + // Only do this if we didn't symmetrically negotiate above + if (h264Params.level_asymmetry_allowed) { + SetSaneH264Level(GetSaneH264Level(h264Params.profile_level_id), + &mProfileLevelId); + } + } else { + // TODO(bug 1143709): max-recv-level support + } + } else if (mName == "red") { + SdpFmtpAttributeList::RedParameters redParams( + GetRedParameters(mDefaultPt, remoteMsection)); + mRedundantEncodings = redParams.encodings; + } else if (mName == "VP8" || mName == "VP9") { + if (mDirection == sdp::kSend) { + SdpFmtpAttributeList::VP8Parameters vp8Params( + GetVP8Parameters(mDefaultPt, remoteMsection)); + + mConstraints.maxFs = vp8Params.max_fs; + // Right now, we treat max-fr=0 (or the absence of max-fr) as no limit. + // We will eventually want to stop doing this (bug 1762600). + if (vp8Params.max_fr) { + mConstraints.maxFps = Some(vp8Params.max_fr); + } + } + } + + if (mRtxEnabled && (mDirection == sdp::kSend || remoteIsOffer)) { + Maybe rtxPt = GetRtxPtByApt(mDefaultPt, remoteMsection); + if (rtxPt.isSome()) { + EnableRtx(*rtxPt); + } else { + mRtxEnabled = false; + mRtxPayloadType = ""; + } + } + + NegotiateRtcpFb(remoteMsection); + return true; + } + + // Maps the not-so-sane encoding of H264 level into something that is + // ordered in the way one would expect + // 1b is 0xAB, everything else is the level left-shifted one half-byte + // (eg; 1.0 is 0xA0, 1.1 is 0xB0, 3.1 is 0x1F0) + static uint32_t GetSaneH264Level(uint32_t profileLevelId) { + uint32_t profileIdc = (profileLevelId >> 16); + + if (profileIdc == 0x42 || profileIdc == 0x4D || profileIdc == 0x58) { + if ((profileLevelId & 0x10FF) == 0x100B) { + // Level 1b + return 0xAB; + } + } + + uint32_t level = profileLevelId & 0xFF; + + if (level == 0x09) { + // Another way to encode level 1b + return 0xAB; + } + + return level << 4; + } + + static void SetSaneH264Level(uint32_t level, uint32_t* profileLevelId) { + uint32_t profileIdc = (*profileLevelId >> 16); + uint32_t levelMask = 0xFF; + + if (profileIdc == 0x42 || profileIdc == 0x4d || profileIdc == 0x58) { + levelMask = 0x10FF; + if (level == 0xAB) { + // Level 1b + level = 0x100B; + } else { + // Not 1b, just shift + level = level >> 4; + } + } else if (level == 0xAB) { + // Another way to encode 1b + level = 0x09; + } else { + // Not 1b, just shift + level = level >> 4; + } + + *profileLevelId = (*profileLevelId & ~levelMask) | level; + } + + enum Subprofile { + kH264ConstrainedBaseline, + kH264Baseline, + kH264Main, + kH264Extended, + kH264High, + kH264High10, + kH264High42, + kH264High44, + kH264High10I, + kH264High42I, + kH264High44I, + kH264CALVC44, + kH264UnknownSubprofile + }; + + static Subprofile GetSubprofile(uint32_t profileLevelId) { + // Based on Table 5 from RFC 6184: + // Profile profile_idc profile-iop + // (hexadecimal) (binary) + + // CB 42 (B) x1xx0000 + // same as: 4D (M) 1xxx0000 + // same as: 58 (E) 11xx0000 + // B 42 (B) x0xx0000 + // same as: 58 (E) 10xx0000 + // M 4D (M) 0x0x0000 + // E 58 00xx0000 + // H 64 00000000 + // H10 6E 00000000 + // H42 7A 00000000 + // H44 F4 00000000 + // H10I 6E 00010000 + // H42I 7A 00010000 + // H44I F4 00010000 + // C44I 2C 00010000 + + if ((profileLevelId & 0xFF4F00) == 0x424000) { + // 01001111 (mask, 0x4F) + // x1xx0000 (from table) + // 01000000 (expected value, 0x40) + return kH264ConstrainedBaseline; + } + + if ((profileLevelId & 0xFF8F00) == 0x4D8000) { + // 10001111 (mask, 0x8F) + // 1xxx0000 (from table) + // 10000000 (expected value, 0x80) + return kH264ConstrainedBaseline; + } + + if ((profileLevelId & 0xFFCF00) == 0x58C000) { + // 11001111 (mask, 0xCF) + // 11xx0000 (from table) + // 11000000 (expected value, 0xC0) + return kH264ConstrainedBaseline; + } + + if ((profileLevelId & 0xFF4F00) == 0x420000) { + // 01001111 (mask, 0x4F) + // x0xx0000 (from table) + // 00000000 (expected value) + return kH264Baseline; + } + + if ((profileLevelId & 0xFFCF00) == 0x588000) { + // 11001111 (mask, 0xCF) + // 10xx0000 (from table) + // 10000000 (expected value, 0x80) + return kH264Baseline; + } + + if ((profileLevelId & 0xFFAF00) == 0x4D0000) { + // 10101111 (mask, 0xAF) + // 0x0x0000 (from table) + // 00000000 (expected value) + return kH264Main; + } + + if ((profileLevelId & 0xFF0000) == 0x580000) { + // 11001111 (mask, 0xCF) + // 00xx0000 (from table) + // 00000000 (expected value) + return kH264Extended; + } + + if ((profileLevelId & 0xFFFF00) == 0x640000) { + return kH264High; + } + + if ((profileLevelId & 0xFFFF00) == 0x6E0000) { + return kH264High10; + } + + if ((profileLevelId & 0xFFFF00) == 0x7A0000) { + return kH264High42; + } + + if ((profileLevelId & 0xFFFF00) == 0xF40000) { + return kH264High44; + } + + if ((profileLevelId & 0xFFFF00) == 0x6E1000) { + return kH264High10I; + } + + if ((profileLevelId & 0xFFFF00) == 0x7A1000) { + return kH264High42I; + } + + if ((profileLevelId & 0xFFFF00) == 0xF41000) { + return kH264High44I; + } + + if ((profileLevelId & 0xFFFF00) == 0x2C1000) { + return kH264CALVC44; + } + + return kH264UnknownSubprofile; + } + + virtual bool ParametersMatch( + const std::string& fmt, + const SdpMediaSection& remoteMsection) const override { + if (mName == "H264") { + SdpFmtpAttributeList::H264Parameters h264Params( + GetH264Parameters(fmt, remoteMsection)); + + if (h264Params.packetization_mode != mPacketizationMode) { + return false; + } + + if (GetSubprofile(h264Params.profile_level_id) != + GetSubprofile(mProfileLevelId)) { + return false; + } + } + + return true; + } + + virtual bool RtcpFbRembIsSet() const { + for (const auto& fb : mOtherFbTypes) { + if (fb.type == SdpRtcpFbAttributeList::kRemb) { + return true; + } + } + return false; + } + + virtual bool RtcpFbTransportCCIsSet() const { + for (const auto& fb : mOtherFbTypes) { + if (fb.type == SdpRtcpFbAttributeList::kTransportCC) { + return true; + } + } + return false; + } + + virtual void UpdateRedundantEncodings( + const std::vector>& codecs) { + for (const auto& codec : codecs) { + if (codec->Type() == type && codec->mEnabled && codec->mName != "red") { + uint16_t pt; + if (!SdpHelper::GetPtAsInt(codec->mDefaultPt, &pt)) { + continue; + } + mRedundantEncodings.push_back(pt); + } + } + } + + void EnsureNoDuplicatePayloadTypes(std::set& aUsedPts) override { + JsepCodecDescription::EnsureNoDuplicatePayloadTypes(aUsedPts); + if (mFECEnabled) { + mFECEnabled = EnsurePayloadTypeNotDuplicate(aUsedPts, mREDPayloadType) && + EnsurePayloadTypeNotDuplicate(aUsedPts, mULPFECPayloadType); + } + if (mRtxEnabled) { + mRtxEnabled = EnsurePayloadTypeNotDuplicate(aUsedPts, mRtxPayloadType); + } + } + + JSEP_CODEC_CLONE(JsepVideoCodecDescription) + + std::vector mAckFbTypes; + std::vector mNackFbTypes; + std::vector mCcmFbTypes; + std::vector mOtherFbTypes; + bool mTmmbrEnabled; + bool mRembEnabled; + bool mFECEnabled; + bool mTransportCCEnabled; + bool mRtxEnabled; + std::string mREDPayloadType; + std::string mULPFECPayloadType; + std::string mRtxPayloadType; + std::vector mRedundantEncodings; + + // H264-specific stuff + uint32_t mProfileLevelId; + uint32_t mPacketizationMode; + std::string mSpropParameterSets; +}; + +class JsepApplicationCodecDescription : public JsepCodecDescription { + // This is the new draft-21 implementation + public: + JsepApplicationCodecDescription(const std::string& name, uint16_t channels, + uint16_t localPort, + uint32_t localMaxMessageSize, + bool enabled = true) + : JsepCodecDescription("", name, 0, channels, enabled), + mLocalPort(localPort), + mLocalMaxMessageSize(localMaxMessageSize), + mRemotePort(0), + mRemoteMaxMessageSize(0), + mRemoteMMSSet(false) {} + + static constexpr SdpMediaSection::MediaType type = + SdpMediaSection::kApplication; + + SdpMediaSection::MediaType Type() const override { return type; } + + JSEP_CODEC_CLONE(JsepApplicationCodecDescription) + + static UniquePtr CreateDefault() { + return MakeUnique( + "webrtc-datachannel", WEBRTC_DATACHANNEL_STREAMS_DEFAULT, + WEBRTC_DATACHANNEL_PORT_DEFAULT, + WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_LOCAL); + } + + // Override, uses sctpport or sctpmap instead of rtpmap + virtual bool Matches(const std::string& fmt, + const SdpMediaSection& remoteMsection) const override { + if (type != remoteMsection.GetMediaType()) { + return false; + } + + int sctp_port = remoteMsection.GetSctpPort(); + bool fmt_matches = + nsCRT::strcasecmp(mName.c_str(), + remoteMsection.GetFormats()[0].c_str()) == 0; + if (sctp_port && fmt_matches) { + // New sctp draft 21 format + return true; + } + + const SdpSctpmapAttributeList::Sctpmap* sctp_map( + remoteMsection.GetSctpmap()); + if (sctp_map) { + // Old sctp draft 05 format + return nsCRT::strcasecmp(mName.c_str(), sctp_map->name.c_str()) == 0; + } + + return false; + } + + virtual void AddToMediaSection(SdpMediaSection& msection) const override { + if (mEnabled && msection.GetMediaType() == type) { + if (mDirection == sdp::kRecv) { + msection.AddDataChannel(mName, mLocalPort, mChannels, + mLocalMaxMessageSize); + } + + AddParametersToMSection(msection); + } + } + + bool Negotiate(const std::string& pt, const SdpMediaSection& remoteMsection, + bool remoteIsOffer, + Maybe localMsection) override { + JsepCodecDescription::Negotiate(pt, remoteMsection, remoteIsOffer, + localMsection); + + uint32_t message_size; + mRemoteMMSSet = remoteMsection.GetMaxMessageSize(&message_size); + if (mRemoteMMSSet) { + mRemoteMaxMessageSize = message_size; + } else { + mRemoteMaxMessageSize = + WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_REMOTE_DEFAULT; + } + + int sctp_port = remoteMsection.GetSctpPort(); + if (sctp_port) { + mRemotePort = sctp_port; + return true; + } + + const SdpSctpmapAttributeList::Sctpmap* sctp_map( + remoteMsection.GetSctpmap()); + if (sctp_map) { + mRemotePort = std::stoi(sctp_map->pt); + return true; + } + + return false; + } + + // We only support one datachannel per m-section + void EnsureNoDuplicatePayloadTypes(std::set& aUsedPts) override { + } + + void ApplyConfigToFmtp( + UniquePtr& aFmtp) const override{}; + + uint16_t mLocalPort; + uint32_t mLocalMaxMessageSize; + uint16_t mRemotePort; + uint32_t mRemoteMaxMessageSize; + bool mRemoteMMSSet; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/webrtc/jsep/JsepSession.h b/dom/media/webrtc/jsep/JsepSession.h new file mode 100644 index 0000000000..bf68da900b --- /dev/null +++ b/dom/media/webrtc/jsep/JsepSession.h @@ -0,0 +1,287 @@ +/* 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/. */ + +#ifndef _JSEPSESSION_H_ +#define _JSEPSESSION_H_ + +#include +#include +#include +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "nsError.h" + +#include "jsep/JsepTransport.h" +#include "sdp/Sdp.h" + +#include "jsep/JsepTransceiver.h" + +#include "mozilla/dom/PeerConnectionObserverEnumsBinding.h" + +namespace mozilla { + +// Forward declarations +class JsepCodecDescription; + +enum JsepSignalingState { + kJsepStateStable, + kJsepStateHaveLocalOffer, + kJsepStateHaveRemoteOffer, + kJsepStateHaveLocalPranswer, + kJsepStateHaveRemotePranswer, + kJsepStateClosed +}; + +enum JsepSdpType { + kJsepSdpOffer, + kJsepSdpAnswer, + kJsepSdpPranswer, + kJsepSdpRollback +}; + +enum JsepDescriptionPendingOrCurrent { + kJsepDescriptionCurrent, + kJsepDescriptionPending, + kJsepDescriptionPendingOrCurrent +}; + +struct JsepOAOptions {}; +struct JsepOfferOptions : public JsepOAOptions { + Maybe mOfferToReceiveAudio; + Maybe mOfferToReceiveVideo; + Maybe mIceRestart; // currently ignored by JsepSession +}; +struct JsepAnswerOptions : public JsepOAOptions {}; + +enum JsepBundlePolicy { kBundleBalanced, kBundleMaxCompat, kBundleMaxBundle }; + +enum JsepMediaType { kNone = 0, kAudio, kVideo, kAudioVideo }; + +struct JsepExtmapMediaType { + JsepMediaType mMediaType; + SdpExtmapAttributeList::Extmap mExtmap; +}; + +class JsepSession { + public: + explicit JsepSession(const std::string& name) + : mName(name), mState(kJsepStateStable), mNegotiations(0) {} + virtual ~JsepSession() {} + + virtual JsepSession* Clone() const = 0; + + virtual nsresult Init() = 0; + + // Accessors for basic properties. + virtual const std::string& GetName() const { return mName; } + virtual JsepSignalingState GetState() const { return mState; } + virtual uint32_t GetNegotiations() const { return mNegotiations; } + + // Set up the ICE And DTLS data. + virtual nsresult SetBundlePolicy(JsepBundlePolicy policy) = 0; + virtual bool RemoteIsIceLite() const = 0; + virtual std::vector GetIceOptions() const = 0; + + virtual nsresult AddDtlsFingerprint(const std::string& algorithm, + const std::vector& value) = 0; + + virtual nsresult AddRtpExtension( + JsepMediaType mediaType, const std::string& extensionName, + SdpDirectionAttribute::Direction direction) = 0; + virtual nsresult AddAudioRtpExtension( + const std::string& extensionName, + SdpDirectionAttribute::Direction direction) = 0; + virtual nsresult AddVideoRtpExtension( + const std::string& extensionName, + SdpDirectionAttribute::Direction direction) = 0; + virtual nsresult AddAudioVideoRtpExtension( + const std::string& extensionName, + SdpDirectionAttribute::Direction direction) = 0; + + // Kinda gross to be locking down the data structure type like this, but + // returning by value is problematic due to the lack of stl move semantics in + // our build config, since we can't use UniquePtr in the container. The + // alternative is writing a raft of accessor functions that allow arbitrary + // manipulation (which will be unwieldy), or allowing functors to be injected + // that manipulate the data structure (still pretty unwieldy). + virtual std::vector>& Codecs() = 0; + + template + void ForEachCodec(UnaryFunction& function) { + std::for_each(Codecs().begin(), Codecs().end(), function); + for (auto& transceiver : GetTransceivers()) { + transceiver.mSendTrack.ForEachCodec(function); + transceiver.mRecvTrack.ForEachCodec(function); + } + } + + template + void SortCodecs(BinaryPredicate& sorter) { + std::stable_sort(Codecs().begin(), Codecs().end(), sorter); + for (auto& transceiver : GetTransceivers()) { + transceiver.mSendTrack.SortCodecs(sorter); + transceiver.mRecvTrack.SortCodecs(sorter); + } + } + + // Would be nice to have this return a Maybe containing the return of + // |aFunction|, but Maybe cannot contain a void. + template + bool ApplyToTransceiver(const std::string& aId, UnaryFunction&& aFunction) { + for (auto& transceiver : GetTransceivers()) { + if (transceiver.GetUuid() == aId) { + std::forward(aFunction)(transceiver); + return true; + } + } + return false; + } + + template + void ForEachTransceiver(UnaryFunction&& aFunction) { + for (auto& transceiver : GetTransceivers()) { + std::forward(aFunction)(transceiver); + } + } + + template + void ForEachTransceiver(UnaryFunction&& aFunction) const { + for (const auto& transceiver : GetTransceivers()) { + std::forward(aFunction)(transceiver); + } + } + + Maybe GetTransceiver(const std::string& aId) const { + for (const auto& transceiver : GetTransceivers()) { + if (transceiver.GetUuid() == aId) { + return Some(transceiver); + } + } + return Nothing(); + } + + template + Maybe FindTransceiver(MatchFunction&& aFunc) const { + for (const auto& transceiver : GetTransceivers()) { + if (std::forward(aFunc)(transceiver)) { + return Some(transceiver); + } + } + return Nothing(); + } + + bool SetTransceiver(const JsepTransceiver& aNew) { + return ApplyToTransceiver(aNew.GetUuid(), + [aNew](JsepTransceiver& aOld) { aOld = aNew; }); + } + + virtual void AddTransceiver(const JsepTransceiver& transceiver) = 0; + + class Result { + public: + Result() = default; + MOZ_IMPLICIT Result(dom::PCError aError) : mError(Some(aError)) {} + // TODO(bug 1527916): Need c'tor and members for handling RTCError. + Maybe mError; + }; + + // Basic JSEP operations. + virtual Result CreateOffer(const JsepOfferOptions& options, + std::string* offer) = 0; + virtual Result CreateAnswer(const JsepAnswerOptions& options, + std::string* answer) = 0; + virtual std::string GetLocalDescription( + JsepDescriptionPendingOrCurrent type) const = 0; + virtual std::string GetRemoteDescription( + JsepDescriptionPendingOrCurrent type) const = 0; + virtual Result SetLocalDescription(JsepSdpType type, + const std::string& sdp) = 0; + virtual Result SetRemoteDescription(JsepSdpType type, + const std::string& sdp) = 0; + virtual Result AddRemoteIceCandidate(const std::string& candidate, + const std::string& mid, + const Maybe& level, + const std::string& ufrag, + std::string* transportId) = 0; + virtual nsresult AddLocalIceCandidate(const std::string& candidate, + const std::string& transportId, + const std::string& ufrag, + uint16_t* level, std::string* mid, + bool* skipped) = 0; + virtual nsresult UpdateDefaultCandidate( + const std::string& defaultCandidateAddr, uint16_t defaultCandidatePort, + const std::string& defaultRtcpCandidateAddr, + uint16_t defaultRtcpCandidatePort, const std::string& transportId) = 0; + virtual nsresult Close() = 0; + + // ICE controlling or controlled + virtual bool IsIceControlling() const = 0; + virtual Maybe IsPendingOfferer() const = 0; + virtual Maybe IsCurrentOfferer() const = 0; + virtual bool IsIceRestarting() const = 0; + virtual std::set> GetLocalIceCredentials() + const = 0; + + virtual const std::string GetLastError() const { return "Error"; } + + virtual const std::vector>& + GetLastSdpParsingErrors() const = 0; + + static const char* GetStateStr(JsepSignalingState state) { + static const char* states[] = {"stable", + "have-local-offer", + "have-remote-offer", + "have-local-pranswer", + "have-remote-pranswer", + "closed"}; + + return states[state]; + } + + virtual bool CheckNegotiationNeeded() const = 0; + + void CountTracksAndDatachannels( + uint16_t (&receiving)[SdpMediaSection::kMediaTypes], + uint16_t (&sending)[SdpMediaSection::kMediaTypes]) const { + memset(receiving, 0, sizeof(receiving)); + memset(sending, 0, sizeof(sending)); + + for (const auto& transceiver : GetTransceivers()) { + if (transceiver.mRecvTrack.GetActive() || + transceiver.GetMediaType() == SdpMediaSection::kApplication) { + receiving[transceiver.mRecvTrack.GetMediaType()]++; + } + + if (transceiver.mSendTrack.GetActive() || + transceiver.GetMediaType() == SdpMediaSection::kApplication) { + sending[transceiver.mSendTrack.GetMediaType()]++; + } + } + } + + virtual void SetDefaultCodecs( + const std::vector>& aPreferredCodecs) = 0; + + // See Bug 1642419, this can be removed when all sites are working with RTX. + void SetRtxIsAllowed(bool aRtxIsAllowed) { mRtxIsAllowed = aRtxIsAllowed; } + + protected: + friend class JsepSessionTest; + // Returns transceivers in the order they were added. + virtual std::vector& GetTransceivers() = 0; + virtual const std::vector& GetTransceivers() const = 0; + + const std::string mName; + JsepSignalingState mState; + uint32_t mNegotiations; + + // See Bug 1642419, this can be removed when all sites are working with RTX. + bool mRtxIsAllowed = true; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/webrtc/jsep/JsepSessionImpl.cpp b/dom/media/webrtc/jsep/JsepSessionImpl.cpp new file mode 100644 index 0000000000..bb792e7764 --- /dev/null +++ b/dom/media/webrtc/jsep/JsepSessionImpl.cpp @@ -0,0 +1,2457 @@ +/* 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 "jsep/JsepSessionImpl.h" + +#include + +#include +#include +#include +#include +#include + +#include "transport/logging.h" +#include "mozilla/Preferences.h" +#include "mozilla/Telemetry.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/net/DataChannelProtocol.h" +#include "nsDebug.h" +#include "nspr.h" +#include "nss.h" +#include "pk11pub.h" + +#include "api/rtp_parameters.h" + +#include "jsep/JsepTrack.h" +#include "jsep/JsepTransport.h" +#include "sdp/HybridSdpParser.h" +#include "sdp/SipccSdp.h" + +namespace mozilla { + +MOZ_MTLOG_MODULE("jsep") + +#define JSEP_SET_ERROR(error) \ + do { \ + std::ostringstream os; \ + os << error; \ + mLastError = os.str(); \ + MOZ_MTLOG(ML_ERROR, "[" << mName << "]: " << mLastError); \ + } while (0); + +static std::bitset<128> GetForbiddenSdpPayloadTypes() { + std::bitset<128> forbidden(0); + forbidden[1] = true; + forbidden[2] = true; + forbidden[19] = true; + for (uint16_t i = 64; i < 96; ++i) { + forbidden[i] = true; + } + return forbidden; +} + +static std::string GetRandomHex(size_t words) { + std::ostringstream os; + + for (size_t i = 0; i < words; ++i) { + uint32_t rand; + SECStatus rv = PK11_GenerateRandom(reinterpret_cast(&rand), + sizeof(rand)); + if (rv != SECSuccess) { + MOZ_CRASH(); + return ""; + } + + os << std::hex << std::setfill('0') << std::setw(8) << rand; + } + return os.str(); +} + +JsepSessionImpl::JsepSessionImpl(const JsepSessionImpl& aOrig) + : JsepSession(aOrig), + JsepSessionCopyableStuff(aOrig), + mUuidGen(aOrig.mUuidGen->Clone()), + mGeneratedOffer(aOrig.mGeneratedOffer ? aOrig.mGeneratedOffer->Clone() + : nullptr), + mGeneratedAnswer(aOrig.mGeneratedAnswer ? aOrig.mGeneratedAnswer->Clone() + : nullptr), + mCurrentLocalDescription(aOrig.mCurrentLocalDescription + ? aOrig.mCurrentLocalDescription->Clone() + : nullptr), + mCurrentRemoteDescription(aOrig.mCurrentRemoteDescription + ? aOrig.mCurrentRemoteDescription->Clone() + : nullptr), + mPendingLocalDescription(aOrig.mPendingLocalDescription + ? aOrig.mPendingLocalDescription->Clone() + : nullptr), + mPendingRemoteDescription(aOrig.mPendingRemoteDescription + ? aOrig.mPendingRemoteDescription->Clone() + : nullptr), + mSdpHelper(&mLastError), + mParser(new HybridSdpParser()) { + for (const auto& codec : aOrig.mSupportedCodecs) { + mSupportedCodecs.emplace_back(codec->Clone()); + } +} + +nsresult JsepSessionImpl::Init() { + mLastError.clear(); + + MOZ_ASSERT(!mSessionId, "Init called more than once"); + + nsresult rv = SetupIds(); + NS_ENSURE_SUCCESS(rv, rv); + + mEncodeTrackId = + Preferences::GetBool("media.peerconnection.sdp.encode_track_id", true); + + mIceUfrag = GetRandomHex(1); + mIcePwd = GetRandomHex(4); + return NS_OK; +} + +static void GetIceCredentials( + const Sdp& aSdp, + std::set>* aCredentials) { + for (size_t i = 0; i < aSdp.GetMediaSectionCount(); ++i) { + const SdpAttributeList& attrs = aSdp.GetMediaSection(i).GetAttributeList(); + if (attrs.HasAttribute(SdpAttribute::kIceUfragAttribute) && + attrs.HasAttribute(SdpAttribute::kIcePwdAttribute)) { + aCredentials->insert( + std::make_pair(attrs.GetIceUfrag(), attrs.GetIcePwd())); + } + } +} + +std::set> +JsepSessionImpl::GetLocalIceCredentials() const { + std::set> result; + if (mCurrentLocalDescription) { + GetIceCredentials(*mCurrentLocalDescription, &result); + } + if (mPendingLocalDescription) { + GetIceCredentials(*mPendingLocalDescription, &result); + } + return result; +} + +void JsepSessionImpl::AddTransceiver(const JsepTransceiver& aTransceiver) { + mLastError.clear(); + MOZ_MTLOG(ML_DEBUG, + "[" << mName << "]: Adding transceiver " << aTransceiver.GetUuid()); +#ifdef DEBUG + if (aTransceiver.GetMediaType() == SdpMediaSection::kApplication) { + // Make sure we don't add more than one DataChannel transceiver + for (const auto& transceiver : mTransceivers) { + MOZ_ASSERT(transceiver.GetMediaType() != SdpMediaSection::kApplication); + } + } +#endif + mTransceivers.push_back(aTransceiver); + InitTransceiver(mTransceivers.back()); +} + +void JsepSessionImpl::InitTransceiver(JsepTransceiver& aTransceiver) { + mLastError.clear(); + + if (aTransceiver.GetMediaType() != SdpMediaSection::kApplication) { + // Make sure we have an ssrc. Might already be set. + aTransceiver.mSendTrack.EnsureSsrcs(mSsrcGenerator, 1U); + aTransceiver.mSendTrack.SetCNAME(mCNAME); + + // Make sure we have identifiers for send track, just in case. + // (man I hate this) + if (mEncodeTrackId) { + aTransceiver.mSendTrack.SetTrackId(aTransceiver.GetUuid()); + } + } else { + // Datachannel transceivers should always be sendrecv. Just set it instead + // of asserting. + aTransceiver.mJsDirection = SdpDirectionAttribute::kSendrecv; + } + + aTransceiver.mSendTrack.PopulateCodecs(mSupportedCodecs); + aTransceiver.mRecvTrack.PopulateCodecs(mSupportedCodecs); + // We do not set mLevel yet, we do that either on createOffer, or setRemote +} + +nsresult JsepSessionImpl::SetBundlePolicy(JsepBundlePolicy policy) { + mLastError.clear(); + + if (mBundlePolicy == policy) { + return NS_OK; + } + + if (mCurrentLocalDescription) { + JSEP_SET_ERROR( + "Changing the bundle policy is only supported before the " + "first SetLocalDescription."); + return NS_ERROR_UNEXPECTED; + } + + mBundlePolicy = policy; + return NS_OK; +} + +nsresult JsepSessionImpl::AddDtlsFingerprint( + const std::string& algorithm, const std::vector& value) { + mLastError.clear(); + JsepDtlsFingerprint fp; + + fp.mAlgorithm = algorithm; + fp.mValue = value; + + mDtlsFingerprints.push_back(fp); + + return NS_OK; +} + +nsresult JsepSessionImpl::AddRtpExtension( + JsepMediaType mediaType, const std::string& extensionName, + SdpDirectionAttribute::Direction direction) { + mLastError.clear(); + + for (auto& ext : mRtpExtensions) { + if (ext.mExtmap.direction == direction && + ext.mExtmap.extensionname == extensionName) { + if (ext.mMediaType != mediaType) { + ext.mMediaType = JsepMediaType::kAudioVideo; + } + return NS_OK; + } + } + + uint16_t freeEntry = GetNeverUsedExtmapEntry(); + + if (freeEntry == 0) { + return NS_ERROR_FAILURE; + } + + JsepExtmapMediaType extMediaType = { + mediaType, + {freeEntry, direction, + // do we want to specify direction? + direction != SdpDirectionAttribute::kSendrecv, extensionName, ""}}; + + mRtpExtensions.push_back(extMediaType); + return NS_OK; +} + +nsresult JsepSessionImpl::AddAudioRtpExtension( + const std::string& extensionName, + SdpDirectionAttribute::Direction direction) { + return AddRtpExtension(JsepMediaType::kAudio, extensionName, direction); +} + +nsresult JsepSessionImpl::AddVideoRtpExtension( + const std::string& extensionName, + SdpDirectionAttribute::Direction direction) { + return AddRtpExtension(JsepMediaType::kVideo, extensionName, direction); +} + +nsresult JsepSessionImpl::AddAudioVideoRtpExtension( + const std::string& extensionName, + SdpDirectionAttribute::Direction direction) { + return AddRtpExtension(JsepMediaType::kAudioVideo, extensionName, direction); +} + +nsresult JsepSessionImpl::CreateOfferMsection(const JsepOfferOptions& options, + JsepTransceiver& transceiver, + Sdp* local) { + SdpMediaSection::Protocol protocol( + SdpHelper::GetProtocolForMediaType(transceiver.GetMediaType())); + + const Sdp* answer(GetAnswer()); + const SdpMediaSection* lastAnswerMsection = nullptr; + + if (answer && + (local->GetMediaSectionCount() < answer->GetMediaSectionCount())) { + lastAnswerMsection = + &answer->GetMediaSection(local->GetMediaSectionCount()); + // Use the protocol the answer used, even if it is not what we would have + // used. + protocol = lastAnswerMsection->GetProtocol(); + } + + SdpMediaSection* msection = &local->AddMediaSection( + transceiver.GetMediaType(), transceiver.mJsDirection, 0, protocol, + sdp::kIPv4, "0.0.0.0"); + + // Some of this stuff (eg; mid) sticks around even if disabled + if (lastAnswerMsection) { + MOZ_ASSERT(lastAnswerMsection->GetMediaType() == + transceiver.GetMediaType()); + nsresult rv = mSdpHelper.CopyStickyParams(*lastAnswerMsection, msection); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (transceiver.IsStopped()) { + SdpHelper::DisableMsection(local, msection); + return NS_OK; + } + + msection->SetPort(9); + + // We don't do this in AddTransportAttributes because that is also used for + // making answers, and we don't want to unconditionally set rtcp-mux or + // rtcp-rsize there. + if (mSdpHelper.HasRtcp(msection->GetProtocol())) { + // Set RTCP-MUX. + msection->GetAttributeList().SetAttribute( + new SdpFlagAttribute(SdpAttribute::kRtcpMuxAttribute)); + // Set RTCP-RSIZE + if (msection->GetMediaType() == SdpMediaSection::MediaType::kVideo && + Preferences::GetBool("media.navigator.video.offer_rtcp_rsize", false)) { + msection->GetAttributeList().SetAttribute( + new SdpFlagAttribute(SdpAttribute::kRtcpRsizeAttribute)); + } + } + + nsresult rv = AddTransportAttributes(msection, SdpSetupAttribute::kActpass); + NS_ENSURE_SUCCESS(rv, rv); + + transceiver.mSendTrack.AddToOffer(mSsrcGenerator, msection); + transceiver.mRecvTrack.AddToOffer(mSsrcGenerator, msection); + + AddExtmap(msection); + + std::string mid; + // We do not set the mid on the transceiver, that happens when a description + // is set. + if (transceiver.IsAssociated()) { + mid = transceiver.GetMid(); + } else { + mid = GetNewMid(); + } + + msection->GetAttributeList().SetAttribute( + new SdpStringAttribute(SdpAttribute::kMidAttribute, mid)); + + return NS_OK; +} + +void JsepSessionImpl::SetupBundle(Sdp* sdp) const { + std::vector mids; + std::set observedTypes; + + // This has the effect of changing the bundle level if the first m-section + // goes from disabled to enabled. This is kinda inefficient. + + for (size_t i = 0; i < sdp->GetMediaSectionCount(); ++i) { + auto& attrs = sdp->GetMediaSection(i).GetAttributeList(); + if ((sdp->GetMediaSection(i).GetPort() != 0) && + attrs.HasAttribute(SdpAttribute::kMidAttribute)) { + bool useBundleOnly = false; + switch (mBundlePolicy) { + case kBundleMaxCompat: + // We don't use bundle-only for max-compat + break; + case kBundleBalanced: + // balanced means we use bundle-only on everything but the first + // m-section of a given type + if (observedTypes.count(sdp->GetMediaSection(i).GetMediaType())) { + useBundleOnly = true; + } + observedTypes.insert(sdp->GetMediaSection(i).GetMediaType()); + break; + case kBundleMaxBundle: + // max-bundle means we use bundle-only on everything but the first + // m-section + useBundleOnly = !mids.empty(); + break; + } + + if (useBundleOnly) { + attrs.SetAttribute( + new SdpFlagAttribute(SdpAttribute::kBundleOnlyAttribute)); + // Set port to 0 for sections with bundle-only attribute. (mjf) + sdp->GetMediaSection(i).SetPort(0); + } + + mids.push_back(attrs.GetMid()); + } + } + + if (!mids.empty()) { + UniquePtr groupAttr(new SdpGroupAttributeList); + groupAttr->PushEntry(SdpGroupAttributeList::kBundle, mids); + sdp->GetAttributeList().SetAttribute(groupAttr.release()); + } +} + +JsepSession::Result JsepSessionImpl::CreateOffer( + const JsepOfferOptions& options, std::string* offer) { + mLastError.clear(); + + if (mState != kJsepStateStable && mState != kJsepStateHaveLocalOffer) { + JSEP_SET_ERROR("Cannot create offer in state " << GetStateStr(mState)); + // Spec doesn't seem to say this is an error. It probably should. + return dom::PCError::InvalidStateError; + } + + // This is one of those places where CreateOffer sets some state. + SetIceRestarting(options.mIceRestart.isSome() && *(options.mIceRestart)); + + UniquePtr sdp; + + // Make the basic SDP that is common to offer/answer. + nsresult rv = CreateGenericSDP(&sdp); + NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError); + + for (size_t level = 0; + Maybe transceiver = GetTransceiverForLocal(level); + ++level) { + rv = CreateOfferMsection(options, *transceiver, sdp.get()); + NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError); + SetTransceiver(*transceiver); + } + + SetupBundle(sdp.get()); + + if (mCurrentLocalDescription) { + rv = CopyPreviousTransportParams(*GetAnswer(), *mCurrentLocalDescription, + *sdp, sdp.get()); + NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError); + } + + *offer = sdp->ToString(); + mGeneratedOffer = std::move(sdp); + ++mSessionVersion; + MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: CreateOffer \nSDP=\n" << *offer); + + return Result(); +} + +std::string JsepSessionImpl::GetLocalDescription( + JsepDescriptionPendingOrCurrent type) const { + std::ostringstream os; + mozilla::Sdp* sdp = GetParsedLocalDescription(type); + if (sdp) { + sdp->Serialize(os); + } + return os.str(); +} + +std::string JsepSessionImpl::GetRemoteDescription( + JsepDescriptionPendingOrCurrent type) const { + std::ostringstream os; + mozilla::Sdp* sdp = GetParsedRemoteDescription(type); + if (sdp) { + sdp->Serialize(os); + } + return os.str(); +} + +void JsepSessionImpl::AddExtmap(SdpMediaSection* msection) { + auto extensions = GetRtpExtensions(*msection); + + if (!extensions.empty()) { + SdpExtmapAttributeList* extmap = new SdpExtmapAttributeList; + extmap->mExtmaps = extensions; + msection->GetAttributeList().SetAttribute(extmap); + } +} + +std::vector JsepSessionImpl::GetRtpExtensions( + const SdpMediaSection& msection) { + std::vector result; + JsepMediaType mediaType = JsepMediaType::kNone; + switch (msection.GetMediaType()) { + case SdpMediaSection::kAudio: + mediaType = JsepMediaType::kAudio; + break; + case SdpMediaSection::kVideo: + mediaType = JsepMediaType::kVideo; + if (msection.GetAttributeList().HasAttribute( + SdpAttribute::kRidAttribute)) { + // We need RID support + // TODO: Would it be worth checking that the direction is sane? + AddVideoRtpExtension(webrtc::RtpExtension::kRidUri, + SdpDirectionAttribute::kSendonly); + + if (mRtxIsAllowed && + Preferences::GetBool("media.peerconnection.video.use_rtx", false)) { + AddVideoRtpExtension(webrtc::RtpExtension::kRepairedRidUri, + SdpDirectionAttribute::kSendonly); + } + } + break; + default:; + } + if (mediaType != JsepMediaType::kNone) { + for (auto ext = mRtpExtensions.begin(); ext != mRtpExtensions.end(); + ++ext) { + if (ext->mMediaType == mediaType || + ext->mMediaType == JsepMediaType::kAudioVideo) { + result.push_back(ext->mExtmap); + } + } + } + return result; +} + +std::string JsepSessionImpl::GetNewMid() { + std::string mid; + + do { + std::ostringstream osMid; + osMid << mMidCounter++; + mid = osMid.str(); + } while (mUsedMids.count(mid)); + + mUsedMids.insert(mid); + return mid; +} + +void JsepSessionImpl::AddCommonExtmaps(const SdpMediaSection& remoteMsection, + SdpMediaSection* msection) { + auto negotiatedRtpExtensions = GetRtpExtensions(*msection); + mSdpHelper.NegotiateAndAddExtmaps(remoteMsection, negotiatedRtpExtensions, + msection); +} + +uint16_t JsepSessionImpl::GetNeverUsedExtmapEntry() { + uint16_t result = 1; + + // Walk the set in order, and return the first "hole" we find + for (const auto used : mExtmapEntriesEverUsed) { + if (result != used) { + MOZ_ASSERT(result < used); + break; + } + + // RFC 5285 says entries >= 4096 are used in offers to force the answerer + // to pick, so we do not want to actually use these + if (used == 4095) { + JSEP_SET_ERROR( + "Too many rtp extensions have been added. " + "That's 4095. Who _does_ that?"); + return 0; + } + + result = used + 1; + } + + mExtmapEntriesEverUsed.insert(result); + return result; +} + +JsepSession::Result JsepSessionImpl::CreateAnswer( + const JsepAnswerOptions& options, std::string* answer) { + mLastError.clear(); + + if (mState != kJsepStateHaveRemoteOffer) { + JSEP_SET_ERROR("Cannot create answer in state " << GetStateStr(mState)); + return dom::PCError::InvalidStateError; + } + + UniquePtr sdp; + + // Make the basic SDP that is common to offer/answer. + nsresult rv = CreateGenericSDP(&sdp); + NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError); + + const Sdp& offer = *mPendingRemoteDescription; + + // Copy the bundle groups into our answer + UniquePtr groupAttr(new SdpGroupAttributeList); + mSdpHelper.GetBundleGroups(offer, &groupAttr->mGroups); + sdp->GetAttributeList().SetAttribute(groupAttr.release()); + + for (size_t i = 0; i < offer.GetMediaSectionCount(); ++i) { + // The transceivers are already in place, due to setRemote + Maybe transceiver(GetTransceiverForLevel(i)); + if (!transceiver) { + JSEP_SET_ERROR("No transceiver for level " << i); + MOZ_ASSERT(false); + return dom::PCError::OperationError; + } + rv = CreateAnswerMsection(options, *transceiver, offer.GetMediaSection(i), + sdp.get()); + NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError); + SetTransceiver(*transceiver); + } + + // Ensure that each bundle-group starts with a mid that has a transport, in + // case we've disabled what the offerer wanted to use. If the group doesn't + // contain anything that has a transport, remove it. + groupAttr.reset(new SdpGroupAttributeList); + std::vector bundleGroups; + mSdpHelper.GetBundleGroups(*sdp, &bundleGroups); + for (auto& group : bundleGroups) { + for (auto& mid : group.tags) { + const SdpMediaSection* msection = + mSdpHelper.FindMsectionByMid(offer, mid); + + if (msection && !msection->GetAttributeList().HasAttribute( + SdpAttribute::kBundleOnlyAttribute)) { + std::swap(group.tags[0], mid); + groupAttr->mGroups.push_back(group); + break; + } + } + } + sdp->GetAttributeList().SetAttribute(groupAttr.release()); + + if (mCurrentLocalDescription) { + // per discussion with bwc, 3rd parm here should be offer, not *sdp. (mjf) + rv = CopyPreviousTransportParams(*GetAnswer(), *mCurrentRemoteDescription, + offer, sdp.get()); + NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError); + } + + *answer = sdp->ToString(); + mGeneratedAnswer = std::move(sdp); + ++mSessionVersion; + MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: CreateAnswer \nSDP=\n" << *answer); + + return Result(); +} + +nsresult JsepSessionImpl::CreateAnswerMsection( + const JsepAnswerOptions& options, JsepTransceiver& transceiver, + const SdpMediaSection& remoteMsection, Sdp* sdp) { + MOZ_ASSERT(transceiver.GetMediaType() == remoteMsection.GetMediaType()); + SdpDirectionAttribute::Direction direction = + reverse(remoteMsection.GetDirection()) & transceiver.mJsDirection; + SdpMediaSection& msection = + sdp->AddMediaSection(remoteMsection.GetMediaType(), direction, 9, + remoteMsection.GetProtocol(), sdp::kIPv4, "0.0.0.0"); + + nsresult rv = mSdpHelper.CopyStickyParams(remoteMsection, &msection); + NS_ENSURE_SUCCESS(rv, rv); + + if (mSdpHelper.MsectionIsDisabled(remoteMsection) || + // JS might have stopped this + transceiver.IsStopped()) { + SdpHelper::DisableMsection(sdp, &msection); + return NS_OK; + } + + MOZ_ASSERT(transceiver.IsAssociated()); + if (msection.GetAttributeList().GetMid().empty()) { + msection.GetAttributeList().SetAttribute(new SdpStringAttribute( + SdpAttribute::kMidAttribute, transceiver.GetMid())); + } + + MOZ_ASSERT(transceiver.GetMid() == msection.GetAttributeList().GetMid()); + + SdpSetupAttribute::Role role; + if (transceiver.mTransport.mDtls && !IsIceRestarting()) { + role = (transceiver.mTransport.mDtls->mRole == + JsepDtlsTransport::kJsepDtlsClient) + ? SdpSetupAttribute::kActive + : SdpSetupAttribute::kPassive; + } else { + rv = DetermineAnswererSetupRole(remoteMsection, &role); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = AddTransportAttributes(&msection, role); + NS_ENSURE_SUCCESS(rv, rv); + + transceiver.mSendTrack.AddToAnswer(remoteMsection, mSsrcGenerator, &msection); + transceiver.mRecvTrack.AddToAnswer(remoteMsection, mSsrcGenerator, &msection); + + // Add extmap attributes. This logic will probably be moved to the track, + // since it can be specified on a per-sender basis in JS. + // We will need some validation to ensure that the ids are identical for + // RTP streams that are bundled together, though (bug 1406529). + AddCommonExtmaps(remoteMsection, &msection); + + if (msection.GetFormats().empty()) { + // Could not negotiate anything. Disable m-section. + SdpHelper::DisableMsection(sdp, &msection); + } + + return NS_OK; +} + +nsresult JsepSessionImpl::DetermineAnswererSetupRole( + const SdpMediaSection& remoteMsection, SdpSetupAttribute::Role* rolep) { + // Determine the role. + // RFC 5763 says: + // + // The endpoint MUST use the setup attribute defined in [RFC4145]. + // The endpoint that is the offerer MUST use the setup attribute + // value of setup:actpass and be prepared to receive a client_hello + // before it receives the answer. The answerer MUST use either a + // setup attribute value of setup:active or setup:passive. Note that + // if the answerer uses setup:passive, then the DTLS handshake will + // not begin until the answerer is received, which adds additional + // latency. setup:active allows the answer and the DTLS handshake to + // occur in parallel. Thus, setup:active is RECOMMENDED. Whichever + // party is active MUST initiate a DTLS handshake by sending a + // ClientHello over each flow (host/port quartet). + // + // We default to assuming that the offerer is passive and we are active. + SdpSetupAttribute::Role role = SdpSetupAttribute::kActive; + + if (remoteMsection.GetAttributeList().HasAttribute( + SdpAttribute::kSetupAttribute)) { + switch (remoteMsection.GetAttributeList().GetSetup().mRole) { + case SdpSetupAttribute::kActive: + role = SdpSetupAttribute::kPassive; + break; + case SdpSetupAttribute::kPassive: + case SdpSetupAttribute::kActpass: + role = SdpSetupAttribute::kActive; + break; + case SdpSetupAttribute::kHoldconn: + // This should have been caught by ParseSdp + MOZ_ASSERT(false); + JSEP_SET_ERROR( + "The other side used an illegal setup attribute" + " (\"holdconn\")."); + return NS_ERROR_INVALID_ARG; + } + } + + *rolep = role; + return NS_OK; +} + +JsepSession::Result JsepSessionImpl::SetLocalDescription( + JsepSdpType type, const std::string& constSdp) { + mLastError.clear(); + std::string sdp = constSdp; + + MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: SetLocalDescription type=" << type + << "\nSDP=\n" + << sdp); + + switch (type) { + case kJsepSdpOffer: + if (!mGeneratedOffer) { + JSEP_SET_ERROR( + "Cannot set local offer when createOffer has not been called."); + return dom::PCError::InvalidModificationError; + } + if (sdp.empty()) { + sdp = mGeneratedOffer->ToString(); + } + if (mState == kJsepStateHaveLocalOffer) { + // Rollback previous offer before applying the new one. + SetLocalDescription(kJsepSdpRollback, ""); + MOZ_ASSERT(mState == kJsepStateStable); + } + break; + case kJsepSdpAnswer: + case kJsepSdpPranswer: + if (!mGeneratedAnswer) { + JSEP_SET_ERROR( + "Cannot set local answer when createAnswer has not been called."); + return dom::PCError::InvalidModificationError; + } + if (sdp.empty()) { + sdp = mGeneratedAnswer->ToString(); + } + break; + case kJsepSdpRollback: + if (mState != kJsepStateHaveLocalOffer) { + JSEP_SET_ERROR("Cannot rollback local description in " + << GetStateStr(mState)); + // Currently, spec allows this in any state except stable, and + // sRD(rollback) and sLD(rollback) do exactly the same thing. + return dom::PCError::InvalidStateError; + } + + mPendingLocalDescription.reset(); + SetState(kJsepStateStable); + RollbackLocalOffer(); + return Result(); + } + + switch (mState) { + case kJsepStateStable: + if (type != kJsepSdpOffer) { + JSEP_SET_ERROR("Cannot set local answer in state " + << GetStateStr(mState)); + return dom::PCError::InvalidStateError; + } + break; + case kJsepStateHaveRemoteOffer: + if (type != kJsepSdpAnswer && type != kJsepSdpPranswer) { + JSEP_SET_ERROR("Cannot set local offer in state " + << GetStateStr(mState)); + return dom::PCError::InvalidStateError; + } + break; + default: + JSEP_SET_ERROR("Cannot set local offer or answer in state " + << GetStateStr(mState)); + return dom::PCError::InvalidStateError; + } + + UniquePtr parsed; + nsresult rv = ParseSdp(sdp, &parsed); + // Needs to be RTCError with sdp-syntax-error + NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError); + + // Check that content hasn't done anything unsupported with the SDP + rv = ValidateLocalDescription(*parsed, type); + NS_ENSURE_SUCCESS(rv, dom::PCError::InvalidModificationError); + + switch (type) { + case kJsepSdpOffer: + rv = ValidateOffer(*parsed); + break; + case kJsepSdpAnswer: + case kJsepSdpPranswer: + rv = ValidateAnswer(*mPendingRemoteDescription, *parsed); + break; + case kJsepSdpRollback: + MOZ_CRASH(); // Handled above + } + NS_ENSURE_SUCCESS(rv, dom::PCError::InvalidAccessError); + + if (type == kJsepSdpOffer) { + // Save in case we need to rollback + mOldTransceivers = mTransceivers; + } + + SdpHelper::BundledMids bundledMids; + rv = mSdpHelper.GetBundledMids(*parsed, &bundledMids); + NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError); + + SdpHelper::BundledMids remoteBundledMids; + if (type != kJsepSdpOffer) { + rv = mSdpHelper.GetBundledMids(*mPendingRemoteDescription, + &remoteBundledMids); + NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError); + } + + for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) { + Maybe transceiver(GetTransceiverForLevel(i)); + if (!transceiver) { + MOZ_ASSERT(false); + JSEP_SET_ERROR("No transceiver for level " << i); + return dom::PCError::OperationError; + } + + const auto& msection = parsed->GetMediaSection(i); + transceiver->Associate(msection.GetAttributeList().GetMid()); + transceiver->mRecvTrack.RecvTrackSetLocal(msection); + + if (mSdpHelper.MsectionIsDisabled(msection)) { + transceiver->mTransport.Close(); + SetTransceiver(*transceiver); + continue; + } + + bool hasOwnTransport = mSdpHelper.OwnsTransport( + msection, bundledMids, + (type == kJsepSdpOffer) ? sdp::kOffer : sdp::kAnswer); + if (type != kJsepSdpOffer) { + const auto& remoteMsection = + mPendingRemoteDescription->GetMediaSection(i); + // Don't allow the answer to override what the offer allowed for + hasOwnTransport &= mSdpHelper.OwnsTransport( + remoteMsection, remoteBundledMids, sdp::kOffer); + } + + if (hasOwnTransport) { + EnsureHasOwnTransport(parsed->GetMediaSection(i), *transceiver); + } + + if (type == kJsepSdpOffer) { + if (!hasOwnTransport) { + auto it = bundledMids.find(transceiver->GetMid()); + if (it != bundledMids.end()) { + transceiver->SetBundleLevel(it->second->GetLevel()); + } + } + } else { + auto it = remoteBundledMids.find(transceiver->GetMid()); + if (it != remoteBundledMids.end()) { + transceiver->SetBundleLevel(it->second->GetLevel()); + } + } + SetTransceiver(*transceiver); + } + + CopyBundleTransports(); + + switch (type) { + case kJsepSdpOffer: + rv = SetLocalDescriptionOffer(std::move(parsed)); + break; + case kJsepSdpAnswer: + case kJsepSdpPranswer: + rv = SetLocalDescriptionAnswer(type, std::move(parsed)); + break; + case kJsepSdpRollback: + MOZ_CRASH(); // Handled above + } + + NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError); + return Result(); +} + +nsresult JsepSessionImpl::SetLocalDescriptionOffer(UniquePtr offer) { + MOZ_ASSERT(mState == kJsepStateStable); + mPendingLocalDescription = std::move(offer); + mIsPendingOfferer = Some(true); + SetState(kJsepStateHaveLocalOffer); + return NS_OK; +} + +nsresult JsepSessionImpl::SetLocalDescriptionAnswer(JsepSdpType type, + UniquePtr answer) { + MOZ_ASSERT(mState == kJsepStateHaveRemoteOffer); + mPendingLocalDescription = std::move(answer); + + nsresult rv = HandleNegotiatedSession(mPendingLocalDescription, + mPendingRemoteDescription); + NS_ENSURE_SUCCESS(rv, rv); + + mCurrentRemoteDescription = std::move(mPendingRemoteDescription); + mCurrentLocalDescription = std::move(mPendingLocalDescription); + MOZ_ASSERT(mIsPendingOfferer.isSome() && !*mIsPendingOfferer); + mIsPendingOfferer.reset(); + mIsCurrentOfferer = Some(false); + + SetState(kJsepStateStable); + return NS_OK; +} + +JsepSession::Result JsepSessionImpl::SetRemoteDescription( + JsepSdpType type, const std::string& sdp) { + mLastError.clear(); + + MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: SetRemoteDescription type=" << type + << "\nSDP=\n" + << sdp); + + if (mState == kJsepStateHaveRemoteOffer && type == kJsepSdpOffer) { + // Rollback previous offer before applying the new one. + SetRemoteDescription(kJsepSdpRollback, ""); + MOZ_ASSERT(mState == kJsepStateStable); + } + + if (type == kJsepSdpRollback) { + if (mState != kJsepStateHaveRemoteOffer) { + JSEP_SET_ERROR("Cannot rollback remote description in " + << GetStateStr(mState)); + return dom::PCError::InvalidStateError; + } + + mPendingRemoteDescription.reset(); + SetState(kJsepStateStable); + RollbackRemoteOffer(); + + return Result(); + } + + switch (mState) { + case kJsepStateStable: + if (type != kJsepSdpOffer) { + JSEP_SET_ERROR("Cannot set remote answer in state " + << GetStateStr(mState)); + return dom::PCError::InvalidStateError; + } + break; + case kJsepStateHaveLocalOffer: + case kJsepStateHaveRemotePranswer: + if (type != kJsepSdpAnswer && type != kJsepSdpPranswer) { + JSEP_SET_ERROR("Cannot set remote offer in state " + << GetStateStr(mState)); + return dom::PCError::InvalidStateError; + } + break; + default: + JSEP_SET_ERROR("Cannot set remote offer or answer in current state " + << GetStateStr(mState)); + return dom::PCError::InvalidStateError; + } + + // Parse. + UniquePtr parsed; + nsresult rv = ParseSdp(sdp, &parsed); + // Needs to be RTCError with sdp-syntax-error + NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError); + + rv = ValidateRemoteDescription(*parsed); + NS_ENSURE_SUCCESS(rv, dom::PCError::InvalidAccessError); + + switch (type) { + case kJsepSdpOffer: + rv = ValidateOffer(*parsed); + break; + case kJsepSdpAnswer: + case kJsepSdpPranswer: + rv = ValidateAnswer(*mPendingLocalDescription, *parsed); + break; + case kJsepSdpRollback: + MOZ_CRASH(); // Handled above + } + NS_ENSURE_SUCCESS(rv, dom::PCError::InvalidAccessError); + + bool iceLite = + parsed->GetAttributeList().HasAttribute(SdpAttribute::kIceLiteAttribute); + + // check for mismatch ufrag/pwd indicating ice restart + // can't just check the first one because it might be disabled + bool iceRestarting = false; + if (mCurrentRemoteDescription.get()) { + for (size_t i = 0; !iceRestarting && + i < mCurrentRemoteDescription->GetMediaSectionCount(); + ++i) { + const SdpMediaSection& newMsection = parsed->GetMediaSection(i); + const SdpMediaSection& oldMsection = + mCurrentRemoteDescription->GetMediaSection(i); + + if (mSdpHelper.MsectionIsDisabled(newMsection) || + mSdpHelper.MsectionIsDisabled(oldMsection)) { + continue; + } + + iceRestarting = mSdpHelper.IceCredentialsDiffer(newMsection, oldMsection); + } + } + + std::vector iceOptions; + if (parsed->GetAttributeList().HasAttribute( + SdpAttribute::kIceOptionsAttribute)) { + iceOptions = parsed->GetAttributeList().GetIceOptions().mValues; + } + + if (type == kJsepSdpOffer) { + // Save in case we need to rollback. + mOldTransceivers = mTransceivers; + for (auto& transceiver : mTransceivers) { + if (!transceiver.IsNegotiated()) { + // We chose a level for this transceiver, but never negotiated it. + // Discard this state. + transceiver.ClearLevel(); + } + } + } + + // TODO(bug 1095780): Note that we create remote tracks even when + // They contain only codecs we can't negotiate or other craziness. + rv = UpdateTransceiversFromRemoteDescription(*parsed); + NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError); + + for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) { + MOZ_ASSERT(GetTransceiverForLevel(i)); + } + + switch (type) { + case kJsepSdpOffer: + rv = SetRemoteDescriptionOffer(std::move(parsed)); + break; + case kJsepSdpAnswer: + case kJsepSdpPranswer: + rv = SetRemoteDescriptionAnswer(type, std::move(parsed)); + break; + case kJsepSdpRollback: + MOZ_CRASH(); // Handled above + } + + NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError); + + mRemoteIsIceLite = iceLite; + mIceOptions = iceOptions; + SetIceRestarting(iceRestarting); + return Result(); +} + +nsresult JsepSessionImpl::HandleNegotiatedSession( + const UniquePtr& local, const UniquePtr& remote) { + // local ufrag/pwd has been negotiated; we will never go back to the old ones + mOldIceUfrag.clear(); + mOldIcePwd.clear(); + + bool remoteIceLite = + remote->GetAttributeList().HasAttribute(SdpAttribute::kIceLiteAttribute); + + mIceControlling = remoteIceLite || *mIsPendingOfferer; + + const Sdp& answer = *mIsPendingOfferer ? *remote : *local; + + SdpHelper::BundledMids bundledMids; + nsresult rv = mSdpHelper.GetBundledMids(answer, &bundledMids); + NS_ENSURE_SUCCESS(rv, rv); + + // First, set the bundle level on the transceivers + for (auto& [mid, transportOwner] : bundledMids) { + Maybe bundledTransceiver = GetTransceiverForMid(mid); + if (!bundledTransceiver) { + JSEP_SET_ERROR("No transceiver for bundled mid " << mid); + return NS_ERROR_INVALID_ARG; + } + bundledTransceiver->SetBundleLevel(transportOwner->GetLevel()); + SetTransceiver(*bundledTransceiver); + } + + // Now walk through the m-sections, perform negotiation, and update the + // transceivers. + for (size_t i = 0; i < local->GetMediaSectionCount(); ++i) { + Maybe transceiver(GetTransceiverForLevel(i)); + if (!transceiver) { + MOZ_ASSERT(false); + JSEP_SET_ERROR("No transceiver for level " << i); + return NS_ERROR_FAILURE; + } + + // Skip disabled m-sections. + if (answer.GetMediaSection(i).GetPort() == 0) { + transceiver->mTransport.Close(); + transceiver->Stop(); + transceiver->Disassociate(); + transceiver->ClearBundleLevel(); + transceiver->mSendTrack.SetActive(false); + transceiver->mRecvTrack.SetActive(false); + transceiver->SetCanRecycle(); + SetTransceiver(*transceiver); + // Do not clear mLevel yet! That will happen on the next negotiation. + continue; + } + + rv = MakeNegotiatedTransceiver(remote->GetMediaSection(i), + local->GetMediaSection(i), *transceiver); + NS_ENSURE_SUCCESS(rv, rv); + SetTransceiver(*transceiver); + } + + CopyBundleTransports(); + + std::vector remoteTracks; + for (auto& transceiver : mTransceivers) { + remoteTracks.push_back(&transceiver.mRecvTrack); + } + JsepTrack::SetUniquePayloadTypes(remoteTracks); + + mNegotiations++; + + mGeneratedAnswer.reset(); + mGeneratedOffer.reset(); + + return NS_OK; +} + +nsresult JsepSessionImpl::MakeNegotiatedTransceiver( + const SdpMediaSection& remote, const SdpMediaSection& local, + JsepTransceiver& transceiver) { + const SdpMediaSection& answer = *mIsPendingOfferer ? remote : local; + + bool sending = false; + bool receiving = false; + + // JS could stop the transceiver after the answer was created. + if (!transceiver.IsStopped()) { + if (*mIsPendingOfferer) { + receiving = answer.IsSending(); + sending = answer.IsReceiving(); + } else { + sending = answer.IsSending(); + receiving = answer.IsReceiving(); + } + } + + MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: Negotiated m= line" + << " index=" << local.GetLevel() << " type=" + << local.GetMediaType() << " sending=" << sending + << " receiving=" << receiving); + + transceiver.SetNegotiated(); + + // Ensure that this is finalized in case we need to copy it below + nsresult rv = + FinalizeTransport(remote.GetAttributeList(), answer.GetAttributeList(), + &transceiver.mTransport); + NS_ENSURE_SUCCESS(rv, rv); + + transceiver.mSendTrack.SetActive(sending); + rv = transceiver.mSendTrack.Negotiate(answer, remote, local); + if (NS_FAILED(rv)) { + JSEP_SET_ERROR("Answer had no codecs in common with offer in m-section " + << local.GetLevel()); + return rv; + } + + JsepTrack& recvTrack = transceiver.mRecvTrack; + recvTrack.SetActive(receiving); + rv = recvTrack.Negotiate(answer, remote, local); + if (NS_FAILED(rv)) { + JSEP_SET_ERROR("Answer had no codecs in common with offer in m-section " + << local.GetLevel()); + return rv; + } + + if (transceiver.HasBundleLevel() && recvTrack.GetSsrcs().empty() && + recvTrack.GetMediaType() != SdpMediaSection::kApplication) { + // TODO(bug 1105005): Once we have urn:ietf:params:rtp-hdrext:sdes:mid + // support, we should only fire this warning if that extension was not + // negotiated. + MOZ_MTLOG(ML_ERROR, "[" << mName + << "]: Bundled m-section has no ssrc " + "attributes. This may cause media packets to be " + "dropped."); + } + + if (transceiver.mTransport.mComponents == 2) { + // RTCP MUX or not. + // TODO(bug 1095743): verify that the PTs are consistent with mux. + MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: RTCP-MUX is off"); + } + + if (answer.GetAttributeList().HasAttribute(SdpAttribute::kExtmapAttribute)) { + const auto extmaps = answer.GetAttributeList().GetExtmap().mExtmaps; + for (const auto& negotiatedExtension : extmaps) { + if (negotiatedExtension.entry == 0) { + MOZ_ASSERT(false, "This should have been caught sooner"); + continue; + } + + mExtmapEntriesEverNegotiated[negotiatedExtension.entry] = + negotiatedExtension.extensionname; + + for (auto& originalExtension : mRtpExtensions) { + if (negotiatedExtension.extensionname == + originalExtension.mExtmap.extensionname) { + // Update extmap to match what was negotiated + originalExtension.mExtmap.entry = negotiatedExtension.entry; + mExtmapEntriesEverUsed.insert(negotiatedExtension.entry); + } else if (originalExtension.mExtmap.entry == + negotiatedExtension.entry) { + // If this extmap entry was claimed for a different extension, update + // it to a new value so we don't end up with a duplicate. + originalExtension.mExtmap.entry = GetNeverUsedExtmapEntry(); + } + } + } + } + + return NS_OK; +} + +void JsepSessionImpl::EnsureHasOwnTransport(const SdpMediaSection& msection, + JsepTransceiver& transceiver) { + JsepTransport& transport = transceiver.mTransport; + + if (!transceiver.HasOwnTransport()) { + // Transceiver didn't own this transport last time, it won't now either + transport.Close(); + } + + transport.mLocalUfrag = msection.GetAttributeList().GetIceUfrag(); + transport.mLocalPwd = msection.GetAttributeList().GetIcePwd(); + + transceiver.ClearBundleLevel(); + + if (!transport.mComponents) { + if (mSdpHelper.HasRtcp(msection.GetProtocol())) { + transport.mComponents = 2; + } else { + transport.mComponents = 1; + } + } + + if (transport.mTransportId.empty()) { + // TODO: Once we use different ICE ufrag/pass for each m-section, we can + // use that here. + std::ostringstream os; + os << "transport_" << mTransportIdCounter++; + transport.mTransportId = os.str(); + } +} + +void JsepSessionImpl::CopyBundleTransports() { + for (auto& transceiver : mTransceivers) { + if (transceiver.HasBundleLevel()) { + MOZ_MTLOG(ML_DEBUG, + "[" << mName << "] Transceiver " << transceiver.GetLevel() + << " is in a bundle; transceiver " + << transceiver.BundleLevel() << " owns the transport."); + Maybe transportOwner = + GetTransceiverForLevel(transceiver.BundleLevel()); + MOZ_ASSERT(transportOwner); + if (transportOwner) { + transceiver.mTransport = transportOwner->mTransport; + } + } else if (transceiver.HasLevel()) { + MOZ_MTLOG(ML_DEBUG, "[" << mName << "] Transceiver " + << transceiver.GetLevel() + << " is not necessarily in a bundle."); + } + if (transceiver.HasLevel()) { + MOZ_MTLOG(ML_DEBUG, + "[" << mName << "] Transceiver " << transceiver.GetLevel() + << " transport-id: " << transceiver.mTransport.mTransportId + << " components: " << transceiver.mTransport.mComponents); + } + } +} + +nsresult JsepSessionImpl::FinalizeTransport(const SdpAttributeList& remote, + const SdpAttributeList& answer, + JsepTransport* transport) const { + if (!transport->mComponents) { + return NS_OK; + } + + if (!transport->mIce || transport->mIce->mUfrag != remote.GetIceUfrag() || + transport->mIce->mPwd != remote.GetIcePwd()) { + UniquePtr ice = MakeUnique(); + transport->mDtls = nullptr; + + // We do sanity-checking for these in ParseSdp + ice->mUfrag = remote.GetIceUfrag(); + ice->mPwd = remote.GetIcePwd(); + transport->mIce = std::move(ice); + } + + if (remote.HasAttribute(SdpAttribute::kCandidateAttribute)) { + transport->mIce->mCandidates = remote.GetCandidate(); + } + + if (!transport->mDtls) { + // RFC 5763 says: + // + // The endpoint MUST use the setup attribute defined in [RFC4145]. + // The endpoint that is the offerer MUST use the setup attribute + // value of setup:actpass and be prepared to receive a client_hello + // before it receives the answer. The answerer MUST use either a + // setup attribute value of setup:active or setup:passive. Note that + // if the answerer uses setup:passive, then the DTLS handshake will + // not begin until the answerer is received, which adds additional + // latency. setup:active allows the answer and the DTLS handshake to + // occur in parallel. Thus, setup:active is RECOMMENDED. Whichever + // party is active MUST initiate a DTLS handshake by sending a + // ClientHello over each flow (host/port quartet). + UniquePtr dtls = MakeUnique(); + dtls->mFingerprints = remote.GetFingerprint(); + if (!answer.HasAttribute(mozilla::SdpAttribute::kSetupAttribute)) { + dtls->mRole = *mIsPendingOfferer ? JsepDtlsTransport::kJsepDtlsServer + : JsepDtlsTransport::kJsepDtlsClient; + } else { + if (*mIsPendingOfferer) { + dtls->mRole = (answer.GetSetup().mRole == SdpSetupAttribute::kActive) + ? JsepDtlsTransport::kJsepDtlsServer + : JsepDtlsTransport::kJsepDtlsClient; + } else { + dtls->mRole = (answer.GetSetup().mRole == SdpSetupAttribute::kActive) + ? JsepDtlsTransport::kJsepDtlsClient + : JsepDtlsTransport::kJsepDtlsServer; + } + } + + transport->mDtls = std::move(dtls); + } + + if (answer.HasAttribute(SdpAttribute::kRtcpMuxAttribute)) { + transport->mComponents = 1; + } + + return NS_OK; +} + +nsresult JsepSessionImpl::AddTransportAttributes( + SdpMediaSection* msection, SdpSetupAttribute::Role dtlsRole) { + if (mIceUfrag.empty() || mIcePwd.empty()) { + JSEP_SET_ERROR("Missing ICE ufrag or password"); + return NS_ERROR_FAILURE; + } + + SdpAttributeList& attrList = msection->GetAttributeList(); + attrList.SetAttribute( + new SdpStringAttribute(SdpAttribute::kIceUfragAttribute, mIceUfrag)); + attrList.SetAttribute( + new SdpStringAttribute(SdpAttribute::kIcePwdAttribute, mIcePwd)); + + msection->GetAttributeList().SetAttribute(new SdpSetupAttribute(dtlsRole)); + + return NS_OK; +} + +nsresult JsepSessionImpl::CopyPreviousTransportParams( + const Sdp& oldAnswer, const Sdp& offerersPreviousSdp, const Sdp& newOffer, + Sdp* newLocal) { + for (size_t i = 0; i < oldAnswer.GetMediaSectionCount(); ++i) { + if (!mSdpHelper.MsectionIsDisabled(newLocal->GetMediaSection(i)) && + mSdpHelper.AreOldTransportParamsValid(oldAnswer, offerersPreviousSdp, + newOffer, i)) { + // If newLocal is an offer, this will be the number of components we used + // last time, and if it is an answer, this will be the number of + // components we've decided we're using now. + Maybe transceiver(GetTransceiverForLevel(i)); + if (!transceiver) { + MOZ_ASSERT(false); + JSEP_SET_ERROR("No transceiver for level " << i); + return NS_ERROR_FAILURE; + } + size_t numComponents = transceiver->mTransport.mComponents; + nsresult rv = mSdpHelper.CopyTransportParams( + numComponents, mCurrentLocalDescription->GetMediaSection(i), + &newLocal->GetMediaSection(i)); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; +} + +nsresult JsepSessionImpl::ParseSdp(const std::string& sdp, + UniquePtr* parsedp) { + auto results = mParser->Parse(sdp); + auto parsed = std::move(results->Sdp()); + mLastSdpParsingErrors = results->Errors(); + if (!parsed) { + std::string error = results->ParserName() + " Failed to parse SDP: "; + mSdpHelper.AppendSdpParseErrors(mLastSdpParsingErrors, &error); + JSEP_SET_ERROR(error); + return NS_ERROR_INVALID_ARG; + } + // Verify that the JSEP rules for all SDP are followed + for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) { + if (mSdpHelper.MsectionIsDisabled(parsed->GetMediaSection(i))) { + // Disabled, let this stuff slide. + continue; + } + + const SdpMediaSection& msection(parsed->GetMediaSection(i)); + auto& mediaAttrs = msection.GetAttributeList(); + + if (mediaAttrs.HasAttribute(SdpAttribute::kMidAttribute) && + mediaAttrs.GetMid().length() > 16) { + JSEP_SET_ERROR( + "Invalid description, mid length greater than 16 " + "unsupported until 2-byte rtp header extensions are " + "supported in webrtc.org"); + return NS_ERROR_INVALID_ARG; + } + + if (mediaAttrs.HasAttribute(SdpAttribute::kExtmapAttribute)) { + std::set extIds; + for (const auto& ext : mediaAttrs.GetExtmap().mExtmaps) { + uint16_t id = ext.entry; + + if (id < 1 || id > 14) { + JSEP_SET_ERROR("Description contains invalid extension id " + << id << " on level " << i + << " which is unsupported until 2-byte rtp" + " header extensions are supported in webrtc.org"); + return NS_ERROR_INVALID_ARG; + } + + if (extIds.find(id) != extIds.end()) { + JSEP_SET_ERROR("Description contains duplicate extension id " + << id << " on level " << i); + return NS_ERROR_INVALID_ARG; + } + extIds.insert(id); + } + } + + static const std::bitset<128> forbidden = GetForbiddenSdpPayloadTypes(); + if (msection.GetMediaType() == SdpMediaSection::kAudio || + msection.GetMediaType() == SdpMediaSection::kVideo) { + // Sanity-check that payload type can work with RTP + for (const std::string& fmt : msection.GetFormats()) { + uint16_t payloadType; + if (!SdpHelper::GetPtAsInt(fmt, &payloadType)) { + JSEP_SET_ERROR("Payload type \"" + << fmt << "\" is not a 16-bit unsigned int at level " + << i); + return NS_ERROR_INVALID_ARG; + } + if (payloadType > 127) { + JSEP_SET_ERROR("audio/video payload type \"" + << fmt << "\" is too large at level " << i); + return NS_ERROR_INVALID_ARG; + } + if (forbidden.test(payloadType)) { + JSEP_SET_ERROR("Illegal audio/video payload type \"" + << fmt << "\" at level " << i); + return NS_ERROR_INVALID_ARG; + } + } + } + } + + *parsedp = std::move(parsed); + return NS_OK; +} + +nsresult JsepSessionImpl::SetRemoteDescriptionOffer(UniquePtr offer) { + MOZ_ASSERT(mState == kJsepStateStable); + + mPendingRemoteDescription = std::move(offer); + mIsPendingOfferer = Some(false); + + SetState(kJsepStateHaveRemoteOffer); + return NS_OK; +} + +nsresult JsepSessionImpl::SetRemoteDescriptionAnswer(JsepSdpType type, + UniquePtr answer) { + MOZ_ASSERT(mState == kJsepStateHaveLocalOffer || + mState == kJsepStateHaveRemotePranswer); + + mPendingRemoteDescription = std::move(answer); + + nsresult rv = HandleNegotiatedSession(mPendingLocalDescription, + mPendingRemoteDescription); + NS_ENSURE_SUCCESS(rv, rv); + + mCurrentRemoteDescription = std::move(mPendingRemoteDescription); + mCurrentLocalDescription = std::move(mPendingLocalDescription); + MOZ_ASSERT(mIsPendingOfferer.isSome() && *mIsPendingOfferer); + mIsPendingOfferer.reset(); + mIsCurrentOfferer = Some(true); + + SetState(kJsepStateStable); + return NS_OK; +} + +Maybe JsepSessionImpl::GetTransceiverForLevel( + size_t level) const { + return FindTransceiver([level](const JsepTransceiver& transceiver) { + return transceiver.HasLevel() && (transceiver.GetLevel() == level); + }); +} + +Maybe JsepSessionImpl::GetTransceiverForMid( + const std::string& mid) const { + return FindTransceiver([mid](const JsepTransceiver& transceiver) { + return transceiver.IsAssociated() && (transceiver.GetMid() == mid); + }); +} + +Maybe JsepSessionImpl::GetTransceiverForLocal(size_t level) { + if (Maybe transceiver = GetTransceiverForLevel(level)) { + if (transceiver->CanRecycle() && + transceiver->GetMediaType() != SdpMediaSection::kApplication) { + // Attempt to recycle. If this fails, the old transceiver stays put. + transceiver->Disassociate(); + Maybe newTransceiver = + FindUnassociatedTransceiver(transceiver->GetMediaType(), false); + if (newTransceiver) { + newTransceiver->SetLevel(level); + transceiver->ClearLevel(); + transceiver->mSendTrack.ClearRids(); + SetTransceiver(*newTransceiver); + SetTransceiver(*transceiver); + return newTransceiver; + } + } + + SetTransceiver(*transceiver); + return transceiver; + } + + // There is no transceiver for |level| right now. + + // Look for an RTP transceiver + for (auto& transceiver : mTransceivers) { + if (transceiver.GetMediaType() != SdpMediaSection::kApplication && + !transceiver.IsStopped() && !transceiver.HasLevel()) { + transceiver.SetLevel(level); + return Some(transceiver); + } + } + + // Ok, look for a datachannel + for (auto& transceiver : mTransceivers) { + if (!transceiver.IsStopped() && !transceiver.HasLevel()) { + transceiver.SetLevel(level); + return Some(transceiver); + } + } + + return Nothing(); +} + +Maybe JsepSessionImpl::GetTransceiverForRemote( + const SdpMediaSection& msection) { + size_t level = msection.GetLevel(); + Maybe transceiver = GetTransceiverForLevel(level); + if (transceiver) { + if (!transceiver->CanRecycle()) { + return transceiver; + } + transceiver->Disassociate(); + transceiver->ClearLevel(); + transceiver->mSendTrack.ClearRids(); + SetTransceiver(*transceiver); + } + + // No transceiver for |level| + transceiver = FindUnassociatedTransceiver(msection.GetMediaType(), true); + if (transceiver) { + transceiver->SetLevel(level); + SetTransceiver(*transceiver); + return transceiver; + } + + // Make a new transceiver + JsepTransceiver newTransceiver(msection.GetMediaType(), *mUuidGen, + SdpDirectionAttribute::kRecvonly); + newTransceiver.SetLevel(level); + newTransceiver.SetOnlyExistsBecauseOfSetRemote(true); + AddTransceiver(newTransceiver); + return Some(mTransceivers.back()); +} + +Maybe JsepSessionImpl::GetTransceiverWithTransport( + const std::string& transportId) const { + for (const auto& transceiver : mTransceivers) { + if (transceiver.HasOwnTransport() && + (transceiver.mTransport.mTransportId == transportId)) { + MOZ_ASSERT(transceiver.HasLevel(), + "Transceiver has a transport, but no level!"); + return Some(transceiver); + } + } + + return Nothing(); +} + +nsresult JsepSessionImpl::UpdateTransceiversFromRemoteDescription( + const Sdp& remote) { + // Iterate over the sdp, updating remote tracks as we go + for (size_t i = 0; i < remote.GetMediaSectionCount(); ++i) { + const SdpMediaSection& msection = remote.GetMediaSection(i); + + Maybe transceiver(GetTransceiverForRemote(msection)); + if (!transceiver) { + return NS_ERROR_FAILURE; + } + + if (!mSdpHelper.MsectionIsDisabled(msection)) { + if (msection.GetAttributeList().HasAttribute( + SdpAttribute::kMidAttribute)) { + transceiver->Associate(msection.GetAttributeList().GetMid()); + } + if (!transceiver->IsAssociated()) { + transceiver->Associate(GetNewMid()); + } else { + mUsedMids.insert(transceiver->GetMid()); + } + } else { + transceiver->mTransport.Close(); + transceiver->Disassociate(); + // This cannot be rolled back. + transceiver->Stop(); + SetTransceiver(*transceiver); + continue; + } + + if (msection.GetMediaType() == SdpMediaSection::MediaType::kApplication) { + SetTransceiver(*transceiver); + continue; + } + + transceiver->mSendTrack.SendTrackSetRemote(mSsrcGenerator, msection); + + // Interop workaround for endpoints that don't support msid. + // Ensures that there is a default stream id set, provided the remote is + // sending. + // TODO(bug 1426005): Remove this, or at least move it to JsepTrack. + transceiver->mRecvTrack.UpdateStreamIds({mDefaultRemoteStreamId}); + + // This will process a=msid if present, or clear the stream ids if the + // msection is not sending. If the msection is sending, and there are no + // a=msid, the previously set default will stay. + transceiver->mRecvTrack.RecvTrackSetRemote(remote, msection); + SetTransceiver(*transceiver); + } + + return NS_OK; +} + +Maybe JsepSessionImpl::FindUnassociatedTransceiver( + SdpMediaSection::MediaType type, bool magic) { + // Look through transceivers that are not mapped to an m-section + for (auto& transceiver : mTransceivers) { + if (type == SdpMediaSection::kApplication && + type == transceiver.GetMediaType()) { + transceiver.RestartDatachannelTransceiver(); + return Some(transceiver); + } + if (!transceiver.IsStopped() && !transceiver.HasLevel() && + (!magic || transceiver.HasAddTrackMagic()) && + (transceiver.GetMediaType() == type)) { + return Some(transceiver); + } + } + + return Nothing(); +} + +void JsepSessionImpl::RollbackLocalOffer() { + for (size_t i = 0; i < mTransceivers.size(); ++i) { + auto& transceiver = mTransceivers[i]; + if (mOldTransceivers.size() > i) { + transceiver.Rollback(mOldTransceivers[i], false); + mOldTransceivers[i] = transceiver; + continue; + } + + JsepTransceiver temp(transceiver.GetMediaType(), *mUuidGen); + InitTransceiver(temp); + transceiver.Rollback(temp, false); + mOldTransceivers.push_back(transceiver); + } + + mTransceivers = std::move(mOldTransceivers); +} + +void JsepSessionImpl::RollbackRemoteOffer() { + for (size_t i = 0; i < mTransceivers.size(); ++i) { + auto& transceiver = mTransceivers[i]; + if (mOldTransceivers.size() > i) { + // Some stuff cannot be rolled back. Save this information. + transceiver.Rollback(mOldTransceivers[i], true); + mOldTransceivers[i] = transceiver; + continue; + } + + // New transceiver! + // We rollback even for transceivers we will remove, just to ensure we end + // up at the starting state. + JsepTransceiver temp(transceiver.GetMediaType(), *mUuidGen); + InitTransceiver(temp); + transceiver.Rollback(temp, true); + + if (transceiver.OnlyExistsBecauseOfSetRemote()) { + transceiver.Stop(); + transceiver.SetRemoved(); + } + mOldTransceivers.push_back(transceiver); + } + + mTransceivers = std::move(mOldTransceivers); +} + +nsresult JsepSessionImpl::ValidateLocalDescription(const Sdp& description, + JsepSdpType type) { + Sdp* generated = nullptr; + // TODO(bug 1095226): Better checking. + if (type == kJsepSdpOffer) { + generated = mGeneratedOffer.get(); + } else { + generated = mGeneratedAnswer.get(); + } + + if (!generated) { + JSEP_SET_ERROR( + "Calling SetLocal without first calling CreateOffer/Answer" + " is not supported."); + return NS_ERROR_UNEXPECTED; + } + + if (description.GetMediaSectionCount() != generated->GetMediaSectionCount()) { + JSEP_SET_ERROR("Changing the number of m-sections is not allowed"); + return NS_ERROR_INVALID_ARG; + } + + for (size_t i = 0; i < description.GetMediaSectionCount(); ++i) { + auto& origMsection = generated->GetMediaSection(i); + auto& finalMsection = description.GetMediaSection(i); + if (origMsection.GetMediaType() != finalMsection.GetMediaType()) { + JSEP_SET_ERROR("Changing the media-type of m-sections is not allowed"); + return NS_ERROR_INVALID_ARG; + } + + // These will be present in reoffer + if (!mCurrentLocalDescription) { + if (finalMsection.GetAttributeList().HasAttribute( + SdpAttribute::kCandidateAttribute)) { + JSEP_SET_ERROR("Adding your own candidate attributes is not supported"); + return NS_ERROR_INVALID_ARG; + } + + if (finalMsection.GetAttributeList().HasAttribute( + SdpAttribute::kEndOfCandidatesAttribute)) { + JSEP_SET_ERROR("Why are you trying to set a=end-of-candidates?"); + return NS_ERROR_INVALID_ARG; + } + } + + if (mSdpHelper.MsectionIsDisabled(finalMsection)) { + continue; + } + + if (!finalMsection.GetAttributeList().HasAttribute( + SdpAttribute::kMidAttribute)) { + JSEP_SET_ERROR("Local descriptions must have a=mid attributes."); + return NS_ERROR_INVALID_ARG; + } + + if (finalMsection.GetAttributeList().GetMid() != + origMsection.GetAttributeList().GetMid()) { + JSEP_SET_ERROR("Changing the mid of m-sections is not allowed."); + return NS_ERROR_INVALID_ARG; + } + + // TODO(bug 1095218): Check msid + // TODO(bug 1095226): Check ice-ufrag and ice-pwd + // TODO(bug 1095226): Check fingerprints + // TODO(bug 1095226): Check payload types (at least ensure that payload + // types we don't actually support weren't added) + // TODO(bug 1095226): Check ice-options? + } + + if (description.GetAttributeList().HasAttribute( + SdpAttribute::kIceLiteAttribute)) { + JSEP_SET_ERROR("Running ICE in lite mode is unsupported"); + return NS_ERROR_INVALID_ARG; + } + + return NS_OK; +} + +nsresult JsepSessionImpl::ValidateRemoteDescription(const Sdp& description) { + if (!mCurrentLocalDescription) { + // Initial offer; nothing to validate besides the stuff in ParseSdp + return NS_OK; + } + + if (mCurrentLocalDescription->GetMediaSectionCount() > + description.GetMediaSectionCount()) { + JSEP_SET_ERROR( + "New remote description has fewer m-sections than the " + "previous remote description."); + return NS_ERROR_INVALID_ARG; + } + + for (size_t i = 0; i < description.GetMediaSectionCount(); ++i) { + const SdpAttributeList& attrs = + description.GetMediaSection(i).GetAttributeList(); + + if (attrs.HasAttribute(SdpAttribute::kExtmapAttribute)) { + for (const auto& ext : attrs.GetExtmap().mExtmaps) { + if (mExtmapEntriesEverNegotiated.count(ext.entry) && + mExtmapEntriesEverNegotiated[ext.entry] != ext.extensionname) { + JSEP_SET_ERROR( + "Remote description attempted to remap RTP extension id " + << ext.entry << " from " + << mExtmapEntriesEverNegotiated[ext.entry] << " to " + << ext.extensionname); + return NS_ERROR_INVALID_ARG; + } + } + } + } + + if (!mCurrentRemoteDescription) { + // No further checking for initial answers + return NS_OK; + } + + // These are solely to check that bundle is valid + SdpHelper::BundledMids bundledMids; + nsresult rv = GetNegotiatedBundledMids(&bundledMids); + NS_ENSURE_SUCCESS(rv, rv); + + SdpHelper::BundledMids newBundledMids; + rv = mSdpHelper.GetBundledMids(description, &newBundledMids); + NS_ENSURE_SUCCESS(rv, rv); + + // check for partial ice restart, which is not supported + Maybe iceCredsDiffer; + for (size_t i = 0; i < mCurrentRemoteDescription->GetMediaSectionCount(); + ++i) { + const SdpMediaSection& newMsection = description.GetMediaSection(i); + const SdpMediaSection& oldMsection = + mCurrentRemoteDescription->GetMediaSection(i); + + if (mSdpHelper.MsectionIsDisabled(newMsection) || + mSdpHelper.MsectionIsDisabled(oldMsection)) { + continue; + } + + if (oldMsection.GetMediaType() != newMsection.GetMediaType()) { + JSEP_SET_ERROR("Remote description changes the media type of m-line " + << i); + return NS_ERROR_INVALID_ARG; + } + + bool differ = mSdpHelper.IceCredentialsDiffer(newMsection, oldMsection); + + if (mIsPendingOfferer.isSome() && *mIsPendingOfferer && differ && + !IsIceRestarting()) { + JSEP_SET_ERROR( + "Remote description indicates ICE restart but offer did not " + "request ICE restart (new remote description changes either " + "the ice-ufrag or ice-pwd)"); + return NS_ERROR_INVALID_ARG; + } + + // Detect whether all the creds are the same or all are different + if (!iceCredsDiffer.isSome()) { + // for the first msection capture whether creds are different or same + iceCredsDiffer = mozilla::Some(differ); + } else if (iceCredsDiffer.isSome() && *iceCredsDiffer != differ) { + // subsequent msections must match the first sections + JSEP_SET_ERROR( + "Partial ICE restart is unsupported at this time " + "(new remote description changes either the ice-ufrag " + "or ice-pwd on fewer than all msections)"); + return NS_ERROR_INVALID_ARG; + } + } + + return NS_OK; +} + +nsresult JsepSessionImpl::ValidateOffer(const Sdp& offer) { + return mSdpHelper.ValidateTransportAttributes(offer, sdp::kOffer); +} + +nsresult JsepSessionImpl::ValidateAnswer(const Sdp& offer, const Sdp& answer) { + if (offer.GetMediaSectionCount() != answer.GetMediaSectionCount()) { + JSEP_SET_ERROR("Offer and answer have different number of m-lines " + << "(" << offer.GetMediaSectionCount() << " vs " + << answer.GetMediaSectionCount() << ")"); + return NS_ERROR_INVALID_ARG; + } + + nsresult rv = mSdpHelper.ValidateTransportAttributes(answer, sdp::kAnswer); + NS_ENSURE_SUCCESS(rv, rv); + + for (size_t i = 0; i < offer.GetMediaSectionCount(); ++i) { + const SdpMediaSection& offerMsection = offer.GetMediaSection(i); + const SdpMediaSection& answerMsection = answer.GetMediaSection(i); + + if (offerMsection.GetMediaType() != answerMsection.GetMediaType()) { + JSEP_SET_ERROR("Answer and offer have different media types at m-line " + << i); + return NS_ERROR_INVALID_ARG; + } + + if (mSdpHelper.MsectionIsDisabled(answerMsection)) { + continue; + } + + if (mSdpHelper.MsectionIsDisabled(offerMsection)) { + JSEP_SET_ERROR( + "Answer tried to enable an m-section that was disabled in the offer"); + return NS_ERROR_INVALID_ARG; + } + + if (!offerMsection.IsSending() && answerMsection.IsReceiving()) { + JSEP_SET_ERROR("Answer tried to set recv when offer did not set send"); + return NS_ERROR_INVALID_ARG; + } + + if (!offerMsection.IsReceiving() && answerMsection.IsSending()) { + JSEP_SET_ERROR("Answer tried to set send when offer did not set recv"); + return NS_ERROR_INVALID_ARG; + } + + const SdpAttributeList& answerAttrs(answerMsection.GetAttributeList()); + const SdpAttributeList& offerAttrs(offerMsection.GetAttributeList()); + if (answerAttrs.HasAttribute(SdpAttribute::kMidAttribute) && + offerAttrs.HasAttribute(SdpAttribute::kMidAttribute) && + offerAttrs.GetMid() != answerAttrs.GetMid()) { + JSEP_SET_ERROR("Answer changes mid for level, was \'" + << offerMsection.GetAttributeList().GetMid() + << "\', now \'" + << answerMsection.GetAttributeList().GetMid() << "\'"); + return NS_ERROR_INVALID_ARG; + } + + // Sanity check extmap + if (answerAttrs.HasAttribute(SdpAttribute::kExtmapAttribute)) { + if (!offerAttrs.HasAttribute(SdpAttribute::kExtmapAttribute)) { + JSEP_SET_ERROR("Answer adds extmap attributes to level " << i); + return NS_ERROR_INVALID_ARG; + } + + for (const auto& ansExt : answerAttrs.GetExtmap().mExtmaps) { + bool found = false; + for (const auto& offExt : offerAttrs.GetExtmap().mExtmaps) { + if (ansExt.extensionname == offExt.extensionname) { + if ((ansExt.direction & reverse(offExt.direction)) != + ansExt.direction) { + // FIXME we do not return an error here, because Chrome up to + // version 57 is actually tripping over this if they are the + // answerer. See bug 1355010 for details. + MOZ_MTLOG(ML_WARNING, + "[" << mName + << "]: Answer has inconsistent" + " direction on extmap attribute at level " + << i << " (" << ansExt.extensionname + << "). Offer had " << offExt.direction + << ", answer had " << ansExt.direction << "."); + // return NS_ERROR_INVALID_ARG; + } + + if (offExt.entry < 4096 && (offExt.entry != ansExt.entry)) { + JSEP_SET_ERROR("Answer changed id for extmap attribute at level " + << i << " (" << offExt.extensionname << ") from " + << offExt.entry << " to " << ansExt.entry << "."); + return NS_ERROR_INVALID_ARG; + } + + if (ansExt.entry >= 4096) { + JSEP_SET_ERROR("Answer used an invalid id (" + << ansExt.entry + << ") for extmap attribute at level " << i << " (" + << ansExt.extensionname << ")."); + return NS_ERROR_INVALID_ARG; + } + + found = true; + break; + } + } + + if (!found) { + JSEP_SET_ERROR("Answer has extmap " + << ansExt.extensionname + << " at " + "level " + << i << " that was not present in offer."); + return NS_ERROR_INVALID_ARG; + } + } + } + } + + return NS_OK; +} + +nsresult JsepSessionImpl::CreateGenericSDP(UniquePtr* sdpp) { + // draft-ietf-rtcweb-jsep-08 Section 5.2.1: + // o The second SDP line MUST be an "o=" line, as specified in + // [RFC4566], Section 5.2. The value of the field SHOULD + // be "-". The value of the field SHOULD be a + // cryptographically random number. To ensure uniqueness, this + // number SHOULD be at least 64 bits long. The value of the field SHOULD be zero. The value of the + // tuple SHOULD be set to a non- + // meaningful address, such as IN IP4 0.0.0.0, to prevent leaking the + // local address in this field. As mentioned in [RFC4566], the + // entire o= line needs to be unique, but selecting a random number + // for is sufficient to accomplish this. + // + // Historical note: we used to report the actual version number here, after + // "SDPARTA-", but that becomes a problem starting with version 100, since + // some services parse 100 as "10" and give us legacy/broken behavior. So + // we're freezing the version number at 99.0 in this string. + auto origin = SdpOrigin("mozilla...THIS_IS_SDPARTA-99.0", mSessionId, + mSessionVersion, sdp::kIPv4, "0.0.0.0"); + + UniquePtr sdp = MakeUnique(origin); + + if (mDtlsFingerprints.empty()) { + JSEP_SET_ERROR("Missing DTLS fingerprint"); + return NS_ERROR_FAILURE; + } + + UniquePtr fpl = + MakeUnique(); + for (auto& dtlsFingerprint : mDtlsFingerprints) { + fpl->PushEntry(dtlsFingerprint.mAlgorithm, dtlsFingerprint.mValue); + } + sdp->GetAttributeList().SetAttribute(fpl.release()); + + auto* iceOpts = new SdpOptionsAttribute(SdpAttribute::kIceOptionsAttribute); + iceOpts->PushEntry("trickle"); + sdp->GetAttributeList().SetAttribute(iceOpts); + + // This assumes content doesn't add a bunch of msid attributes with a + // different semantic in mind. + std::vector msids; + msids.push_back("*"); + mSdpHelper.SetupMsidSemantic(msids, sdp.get()); + + *sdpp = std::move(sdp); + return NS_OK; +} + +nsresult JsepSessionImpl::SetupIds() { + SECStatus rv = PK11_GenerateRandom( + reinterpret_cast(&mSessionId), sizeof(mSessionId)); + // RFC 3264 says that session-ids MUST be representable as a _signed_ + // 64 bit number, meaning the MSB cannot be set. + mSessionId = mSessionId >> 1; + if (rv != SECSuccess) { + JSEP_SET_ERROR("Failed to generate session id: " << rv); + return NS_ERROR_FAILURE; + } + + if (!mUuidGen->Generate(&mDefaultRemoteStreamId)) { + JSEP_SET_ERROR("Failed to generate default uuid for streams"); + return NS_ERROR_FAILURE; + } + + if (!mUuidGen->Generate(&mCNAME)) { + JSEP_SET_ERROR("Failed to generate CNAME"); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +void JsepSessionImpl::SetDefaultCodecs( + const std::vector>& aPreferredCodecs) { + mSupportedCodecs.clear(); + + for (const auto& codec : aPreferredCodecs) { + mSupportedCodecs.emplace_back(codec->Clone()); + } +} + +void JsepSessionImpl::SetState(JsepSignalingState state) { + if (state == mState) return; + + MOZ_MTLOG(ML_NOTICE, "[" << mName << "]: " << GetStateStr(mState) << " -> " + << GetStateStr(state)); + mState = state; +} + +JsepSession::Result JsepSessionImpl::AddRemoteIceCandidate( + const std::string& candidate, const std::string& mid, + const Maybe& level, const std::string& ufrag, + std::string* transportId) { + mLastError.clear(); + if (!mCurrentRemoteDescription && !mPendingRemoteDescription) { + JSEP_SET_ERROR("Cannot add ICE candidate when there is no remote SDP"); + return dom::PCError::InvalidStateError; + } + + if (mid.empty() && !level.isSome() && candidate.empty()) { + // Set end-of-candidates on SDP + if (mCurrentRemoteDescription) { + nsresult rv = mSdpHelper.SetIceGatheringComplete( + mCurrentRemoteDescription.get(), ufrag); + NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError); + } + + if (mPendingRemoteDescription) { + // If we had an error when adding the candidate to the current + // description, we stomp them here. This is deliberate. + nsresult rv = mSdpHelper.SetIceGatheringComplete( + mPendingRemoteDescription.get(), ufrag); + NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError); + } + return Result(); + } + + Maybe transceiver; + if (!mid.empty()) { + transceiver = GetTransceiverForMid(mid); + } else if (level.isSome()) { + transceiver = GetTransceiverForLevel(level.value()); + } + + if (!transceiver) { + JSEP_SET_ERROR("Cannot set ICE candidate for level=" + << level << " mid=" << mid << ": No such transceiver."); + return dom::PCError::OperationError; + } + + if (level.isSome() && transceiver->GetLevel() != level.value()) { + MOZ_MTLOG(ML_WARNING, "Mismatch between mid and level - \"" + << mid << "\" is not the mid for level " + << level); + } + + *transportId = transceiver->mTransport.mTransportId; + nsresult rv = NS_ERROR_UNEXPECTED; + if (mCurrentRemoteDescription) { + rv = + mSdpHelper.AddCandidateToSdp(mCurrentRemoteDescription.get(), candidate, + transceiver->GetLevel(), ufrag); + } + + if (mPendingRemoteDescription) { + // If we had an error when adding the candidate to the current description, + // we stomp them here. This is deliberate. + rv = + mSdpHelper.AddCandidateToSdp(mPendingRemoteDescription.get(), candidate, + transceiver->GetLevel(), ufrag); + } + + NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError); + return Result(); +} + +nsresult JsepSessionImpl::AddLocalIceCandidate(const std::string& candidate, + const std::string& transportId, + const std::string& ufrag, + uint16_t* level, + std::string* mid, + bool* skipped) { + mLastError.clear(); + *skipped = true; + if (!mCurrentLocalDescription && !mPendingLocalDescription) { + JSEP_SET_ERROR("Cannot add ICE candidate when there is no local SDP"); + return NS_ERROR_UNEXPECTED; + } + + Maybe transceiver = + GetTransceiverWithTransport(transportId); + if (!transceiver || !transceiver->IsAssociated()) { + // mainly here to make some testing less complicated, but also just in case + return NS_OK; + } + + *level = transceiver->GetLevel(); + *mid = transceiver->GetMid(); + + nsresult rv = NS_ERROR_INVALID_ARG; + if (mCurrentLocalDescription) { + rv = mSdpHelper.AddCandidateToSdp(mCurrentLocalDescription.get(), candidate, + *level, ufrag); + } + + if (mPendingLocalDescription) { + // If we had an error when adding the candidate to the current description, + // we stomp them here. This is deliberate. + rv = mSdpHelper.AddCandidateToSdp(mPendingLocalDescription.get(), candidate, + *level, ufrag); + } + + *skipped = false; + return rv; +} + +nsresult JsepSessionImpl::UpdateDefaultCandidate( + const std::string& defaultCandidateAddr, uint16_t defaultCandidatePort, + const std::string& defaultRtcpCandidateAddr, + uint16_t defaultRtcpCandidatePort, const std::string& transportId) { + mLastError.clear(); + + mozilla::Sdp* sdp = + GetParsedLocalDescription(kJsepDescriptionPendingOrCurrent); + + if (!sdp) { + JSEP_SET_ERROR("Cannot add ICE candidate in state " << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + + for (const auto& transceiver : mTransceivers) { + // We set the default address for bundled m-sections, but not candidate + // attributes. Ugh. + if (transceiver.mTransport.mTransportId == transportId) { + MOZ_ASSERT(transceiver.HasLevel(), + "Transceiver has a transport, but no level! " + "This should never happen."); + std::string defaultRtcpCandidateAddrCopy(defaultRtcpCandidateAddr); + if (mState == kJsepStateStable) { + if (transceiver.mTransport.mComponents == 1) { + // We know we're doing rtcp-mux by now. Don't create an rtcp attr. + defaultRtcpCandidateAddrCopy = ""; + defaultRtcpCandidatePort = 0; + } + } + + size_t level = transceiver.GetLevel(); + if (level >= sdp->GetMediaSectionCount()) { + MOZ_ASSERT(false, "Transceiver's level is too large!"); + JSEP_SET_ERROR("Transceiver's level is too large!"); + return NS_ERROR_FAILURE; + } + + auto& msection = sdp->GetMediaSection(level); + + // Do not add default candidate to a bundle-only m-section, sinice that + // might confuse endpoints that do not support bundle-only. + if (!msection.GetAttributeList().HasAttribute( + SdpAttribute::kBundleOnlyAttribute)) { + mSdpHelper.SetDefaultAddresses( + defaultCandidateAddr, defaultCandidatePort, + defaultRtcpCandidateAddrCopy, defaultRtcpCandidatePort, &msection); + } + } + } + + return NS_OK; +} + +nsresult JsepSessionImpl::GetNegotiatedBundledMids( + SdpHelper::BundledMids* bundledMids) { + const Sdp* answerSdp = GetAnswer(); + + if (!answerSdp) { + return NS_OK; + } + + return mSdpHelper.GetBundledMids(*answerSdp, bundledMids); +} + +mozilla::Sdp* JsepSessionImpl::GetParsedLocalDescription( + JsepDescriptionPendingOrCurrent type) const { + if (type == kJsepDescriptionPending) { + return mPendingLocalDescription.get(); + } else if (mPendingLocalDescription && + type == kJsepDescriptionPendingOrCurrent) { + return mPendingLocalDescription.get(); + } + return mCurrentLocalDescription.get(); +} + +mozilla::Sdp* JsepSessionImpl::GetParsedRemoteDescription( + JsepDescriptionPendingOrCurrent type) const { + if (type == kJsepDescriptionPending) { + return mPendingRemoteDescription.get(); + } else if (mPendingRemoteDescription && + type == kJsepDescriptionPendingOrCurrent) { + return mPendingRemoteDescription.get(); + } + return mCurrentRemoteDescription.get(); +} + +const Sdp* JsepSessionImpl::GetAnswer() const { + return (mIsCurrentOfferer.isSome() && *mIsCurrentOfferer) + ? mCurrentRemoteDescription.get() + : mCurrentLocalDescription.get(); +} + +void JsepSessionImpl::SetIceRestarting(bool restarting) { + if (restarting) { + // not restarting -> restarting + if (!IsIceRestarting()) { + // We don't set this more than once, so the old ufrag/pwd is preserved + // even if we CreateOffer({iceRestart:true}) multiple times in a row. + mOldIceUfrag = mIceUfrag; + mOldIcePwd = mIcePwd; + } + mIceUfrag = GetRandomHex(1); + mIcePwd = GetRandomHex(4); + } else if (IsIceRestarting()) { + // restarting -> not restarting, restore old ufrag/pwd + mIceUfrag = mOldIceUfrag; + mIcePwd = mOldIcePwd; + mOldIceUfrag.clear(); + mOldIcePwd.clear(); + } +} + +nsresult JsepSessionImpl::Close() { + mLastError.clear(); + SetState(kJsepStateClosed); + return NS_OK; +} + +const std::string JsepSessionImpl::GetLastError() const { return mLastError; } + +const std::vector>& +JsepSessionImpl::GetLastSdpParsingErrors() const { + return mLastSdpParsingErrors; +} + +bool JsepSessionImpl::CheckNegotiationNeeded() const { + MOZ_ASSERT(mState == kJsepStateStable); + + for (const auto& transceiver : mTransceivers) { + if (transceiver.IsStopped()) { + if (transceiver.IsAssociated()) { + MOZ_MTLOG(ML_DEBUG, "[" << mName + << "]: Negotiation needed because of " + "stopped transceiver that still has a mid."); + return true; + } + continue; + } + + if (!transceiver.IsAssociated()) { + MOZ_MTLOG(ML_DEBUG, "[" << mName + << "]: Negotiation needed because of " + "unassociated (but not stopped) transceiver."); + return true; + } + + if (!mCurrentLocalDescription || !mCurrentRemoteDescription) { + MOZ_CRASH( + "Transceivers should not be associated if we're in stable " + "before the first negotiation."); + continue; + } + + if (!transceiver.HasLevel()) { + MOZ_CRASH("Associated transceivers should always have a level."); + continue; + } + + if (transceiver.GetMediaType() == SdpMediaSection::kApplication) { + continue; + } + + size_t level = transceiver.GetLevel(); + if (NS_WARN_IF(mCurrentLocalDescription->GetMediaSectionCount() <= level) || + NS_WARN_IF(mCurrentRemoteDescription->GetMediaSectionCount() <= + level)) { + MOZ_ASSERT(false); + continue; + } + + const SdpMediaSection& local = + mCurrentLocalDescription->GetMediaSection(level); + const SdpMediaSection& remote = + mCurrentRemoteDescription->GetMediaSection(level); + + if (transceiver.mJsDirection & sdp::kSend) { + std::vector sdpMsids; + if (local.GetAttributeList().HasAttribute(SdpAttribute::kMsidAttribute)) { + for (const auto& msidAttr : local.GetAttributeList().GetMsid().mMsids) { + if (msidAttr.identifier != "-") { + sdpMsids.push_back(msidAttr.identifier); + } + } + } + std::sort(sdpMsids.begin(), sdpMsids.end()); + + std::vector jsepMsids; + for (const auto& jsepMsid : transceiver.mSendTrack.GetStreamIds()) { + jsepMsids.push_back(jsepMsid); + } + std::sort(jsepMsids.begin(), jsepMsids.end()); + + if (!std::equal(sdpMsids.begin(), sdpMsids.end(), jsepMsids.begin(), + jsepMsids.end())) { + MOZ_MTLOG(ML_DEBUG, + "[" << mName + << "]: Negotiation needed because transceiver " + "is sending, and the local SDP has different " + "msids than the send track"); + MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: SDP msids = ["); + for (const auto& msid : sdpMsids) { + MOZ_MTLOG(ML_DEBUG, msid << ", "); + } + MOZ_MTLOG(ML_DEBUG, "]"); + MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: JSEP msids = ["); + for (const auto& msid : jsepMsids) { + MOZ_MTLOG(ML_DEBUG, msid << ", "); + } + MOZ_MTLOG(ML_DEBUG, "]"); + return true; + } + } + + if (mIsCurrentOfferer.isSome() && *mIsCurrentOfferer) { + if ((local.GetDirection() != transceiver.mJsDirection) && + reverse(remote.GetDirection()) != transceiver.mJsDirection) { + MOZ_MTLOG(ML_DEBUG, "[" << mName + << "]: Negotiation needed because " + "the direction on our offer, and the remote " + "answer, does not " + "match the direction on a transceiver."); + return true; + } + } else if (local.GetDirection() != + (transceiver.mJsDirection & reverse(remote.GetDirection()))) { + MOZ_MTLOG( + ML_DEBUG, + "[" << mName + << "]: Negotiation needed because " + "the direction on our answer doesn't match the direction on a " + "transceiver, even though the remote offer would have allowed " + "it."); + return true; + } + } + + return false; +} + +} // namespace mozilla diff --git a/dom/media/webrtc/jsep/JsepSessionImpl.h b/dom/media/webrtc/jsep/JsepSessionImpl.h new file mode 100644 index 0000000000..1cdbba9cfc --- /dev/null +++ b/dom/media/webrtc/jsep/JsepSessionImpl.h @@ -0,0 +1,286 @@ +/* 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/. */ + +#ifndef _JSEPSESSIONIMPL_H_ +#define _JSEPSESSIONIMPL_H_ + +#include +#include +#include +#include + +#include "jsep/JsepCodecDescription.h" +#include "jsep/JsepSession.h" +#include "jsep/JsepTrack.h" +#include "jsep/JsepTransceiver.h" +#include "jsep/SsrcGenerator.h" +#include "sdp/HybridSdpParser.h" +#include "sdp/SdpHelper.h" + +namespace mozilla { + +// JsepSessionImpl members that have default copy c'tors, to simplify the +// implementation of the copy c'tor for JsepSessionImpl +class JsepSessionCopyableStuff { + protected: + struct JsepDtlsFingerprint { + std::string mAlgorithm; + std::vector mValue; + }; + + Maybe mIsPendingOfferer; + Maybe mIsCurrentOfferer; + bool mIceControlling = false; + std::string mIceUfrag; + std::string mIcePwd; + std::string mOldIceUfrag; + std::string mOldIcePwd; + bool mRemoteIsIceLite = false; + std::vector mIceOptions; + JsepBundlePolicy mBundlePolicy = kBundleBalanced; + std::vector mDtlsFingerprints; + uint64_t mSessionId = 0; + uint64_t mSessionVersion = 0; + size_t mMidCounter = 0; + std::set mUsedMids; + size_t mTransportIdCounter = 0; + std::vector mRtpExtensions; + std::set mExtmapEntriesEverUsed; + std::map mExtmapEntriesEverNegotiated; + std::string mDefaultRemoteStreamId; + std::string mCNAME; + // Used to prevent duplicate local SSRCs. Not used to prevent local/remote or + // remote-only duplication, which will be important for EKT but not now. + std::set mSsrcs; + std::string mLastError; + std::vector> mLastSdpParsingErrors; + bool mEncodeTrackId = true; + SsrcGenerator mSsrcGenerator; + // !!!NOT INDEXED BY LEVEL!!! The level mapping is done with + // JsepTransceiver::mLevel. The keys are UUIDs. + std::vector mTransceivers; + // So we can rollback. Not as simple as just going back to the old, though... + std::vector mOldTransceivers; +}; + +class JsepSessionImpl : public JsepSession, public JsepSessionCopyableStuff { + public: + JsepSessionImpl(const std::string& name, UniquePtr uuidgen) + : JsepSession(name), + mUuidGen(std::move(uuidgen)), + mSdpHelper(&mLastError), + mParser(new HybridSdpParser()) {} + + JsepSessionImpl(const JsepSessionImpl& aOrig); + + JsepSession* Clone() const override { return new JsepSessionImpl(*this); } + + // Implement JsepSession methods. + virtual nsresult Init() override; + + nsresult SetBundlePolicy(JsepBundlePolicy policy) override; + + virtual bool RemoteIsIceLite() const override { return mRemoteIsIceLite; } + + virtual std::vector GetIceOptions() const override { + return mIceOptions; + } + + virtual nsresult AddDtlsFingerprint( + const std::string& algorithm, const std::vector& value) override; + + virtual nsresult AddRtpExtension( + JsepMediaType mediaType, const std::string& extensionName, + SdpDirectionAttribute::Direction direction) override; + virtual nsresult AddAudioRtpExtension( + const std::string& extensionName, + SdpDirectionAttribute::Direction direction = + SdpDirectionAttribute::Direction::kSendrecv) override; + + virtual nsresult AddVideoRtpExtension( + const std::string& extensionName, + SdpDirectionAttribute::Direction direction = + SdpDirectionAttribute::Direction::kSendrecv) override; + + virtual nsresult AddAudioVideoRtpExtension( + const std::string& extensionName, + SdpDirectionAttribute::Direction direction = + SdpDirectionAttribute::Direction::kSendrecv) override; + + virtual std::vector>& Codecs() override { + return mSupportedCodecs; + } + + virtual Result CreateOffer(const JsepOfferOptions& options, + std::string* offer) override; + + virtual Result CreateAnswer(const JsepAnswerOptions& options, + std::string* answer) override; + + virtual std::string GetLocalDescription( + JsepDescriptionPendingOrCurrent type) const override; + + virtual std::string GetRemoteDescription( + JsepDescriptionPendingOrCurrent type) const override; + + virtual Result SetLocalDescription(JsepSdpType type, + const std::string& sdp) override; + + virtual Result SetRemoteDescription(JsepSdpType type, + const std::string& sdp) override; + + virtual Result AddRemoteIceCandidate(const std::string& candidate, + const std::string& mid, + const Maybe& level, + const std::string& ufrag, + std::string* transportId) override; + + virtual nsresult AddLocalIceCandidate(const std::string& candidate, + const std::string& transportId, + const std::string& ufrag, + uint16_t* level, std::string* mid, + bool* skipped) override; + + virtual nsresult UpdateDefaultCandidate( + const std::string& defaultCandidateAddr, uint16_t defaultCandidatePort, + const std::string& defaultRtcpCandidateAddr, + uint16_t defaultRtcpCandidatePort, + const std::string& transportId) override; + + virtual nsresult Close() override; + + virtual const std::string GetLastError() const override; + + virtual const std::vector>& + GetLastSdpParsingErrors() const override; + + virtual bool IsIceControlling() const override { return mIceControlling; } + + virtual Maybe IsPendingOfferer() const override { + return mIsPendingOfferer; + } + + virtual Maybe IsCurrentOfferer() const override { + return mIsCurrentOfferer; + } + + virtual bool IsIceRestarting() const override { + return !mOldIceUfrag.empty(); + } + + virtual std::set> GetLocalIceCredentials() + const override; + + virtual void AddTransceiver(const JsepTransceiver& transceiver) override; + + virtual bool CheckNegotiationNeeded() const override; + + virtual void SetDefaultCodecs( + const std::vector>& aPreferredCodecs) + override; + + private: + friend class JsepSessionTest; + virtual const std::vector& GetTransceivers() const override { + return mTransceivers; + } + + virtual std::vector& GetTransceivers() override { + return mTransceivers; + } + + // Non-const so it can set mLastError + nsresult CreateGenericSDP(UniquePtr* sdp); + void AddExtmap(SdpMediaSection* msection); + std::vector GetRtpExtensions( + const SdpMediaSection& msection); + std::string GetNewMid(); + + void AddCommonExtmaps(const SdpMediaSection& remoteMsection, + SdpMediaSection* msection); + uint16_t GetNeverUsedExtmapEntry(); + nsresult SetupIds(); + void SetState(JsepSignalingState state); + // Non-const so it can set mLastError + nsresult ParseSdp(const std::string& sdp, UniquePtr* parsedp); + nsresult SetLocalDescriptionOffer(UniquePtr offer); + nsresult SetLocalDescriptionAnswer(JsepSdpType type, UniquePtr answer); + nsresult SetRemoteDescriptionOffer(UniquePtr offer); + nsresult SetRemoteDescriptionAnswer(JsepSdpType type, UniquePtr answer); + nsresult ValidateLocalDescription(const Sdp& description, JsepSdpType type); + nsresult ValidateRemoteDescription(const Sdp& description); + nsresult ValidateOffer(const Sdp& offer); + nsresult ValidateAnswer(const Sdp& offer, const Sdp& answer); + nsresult UpdateTransceiversFromRemoteDescription(const Sdp& remote); + Maybe GetTransceiverForLevel(size_t level) const; + Maybe GetTransceiverForMid(const std::string& mid) const; + Maybe GetTransceiverForLocal(size_t level); + Maybe GetTransceiverForRemote( + const SdpMediaSection& msection); + Maybe GetTransceiverWithTransport( + const std::string& transportId) const; + // The w3c and IETF specs have a lot of "magical" behavior that happens when + // addTrack is used. This was a deliberate design choice. Sadface. + Maybe FindUnassociatedTransceiver( + SdpMediaSection::MediaType type, bool magic); + // Called for rollback of local description + void RollbackLocalOffer(); + // Called for rollback of remote description + void RollbackRemoteOffer(); + nsresult HandleNegotiatedSession(const UniquePtr& local, + const UniquePtr& remote); + nsresult AddTransportAttributes(SdpMediaSection* msection, + SdpSetupAttribute::Role dtlsRole); + nsresult CopyPreviousTransportParams(const Sdp& oldAnswer, + const Sdp& offerersPreviousSdp, + const Sdp& newOffer, Sdp* newLocal); + void EnsureMsid(Sdp* remote); + void SetupBundle(Sdp* sdp) const; + nsresult CreateOfferMsection(const JsepOfferOptions& options, + JsepTransceiver& transceiver, Sdp* local); + nsresult CreateAnswerMsection(const JsepAnswerOptions& options, + JsepTransceiver& transceiver, + const SdpMediaSection& remoteMsection, + Sdp* sdp); + nsresult DetermineAnswererSetupRole(const SdpMediaSection& remoteMsection, + SdpSetupAttribute::Role* rolep); + nsresult MakeNegotiatedTransceiver(const SdpMediaSection& remote, + const SdpMediaSection& local, + JsepTransceiver& transceiverOut); + void EnsureHasOwnTransport(const SdpMediaSection& msection, + JsepTransceiver& transceiver); + void CopyBundleTransports(); + + nsresult FinalizeTransport(const SdpAttributeList& remote, + const SdpAttributeList& answer, + JsepTransport* transport) const; + + nsresult GetNegotiatedBundledMids(SdpHelper::BundledMids* bundledMids); + + nsresult EnableOfferMsection(SdpMediaSection* msection); + + mozilla::Sdp* GetParsedLocalDescription( + JsepDescriptionPendingOrCurrent type) const; + mozilla::Sdp* GetParsedRemoteDescription( + JsepDescriptionPendingOrCurrent type) const; + const Sdp* GetAnswer() const; + void SetIceRestarting(bool restarting); + + void InitTransceiver(JsepTransceiver& aTransceiver); + + UniquePtr mUuidGen; + UniquePtr mGeneratedOffer; // Created but not set. + UniquePtr mGeneratedAnswer; // Created but not set. + UniquePtr mCurrentLocalDescription; + UniquePtr mCurrentRemoteDescription; + UniquePtr mPendingLocalDescription; + UniquePtr mPendingRemoteDescription; + std::vector> mSupportedCodecs; + SdpHelper mSdpHelper; + UniquePtr mParser; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/webrtc/jsep/JsepTrack.cpp b/dom/media/webrtc/jsep/JsepTrack.cpp new file mode 100644 index 0000000000..f60e4ce0d7 --- /dev/null +++ b/dom/media/webrtc/jsep/JsepTrack.cpp @@ -0,0 +1,670 @@ +/* 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 "jsep/JsepTrack.h" +#include "jsep/JsepCodecDescription.h" +#include "jsep/JsepTrackEncoding.h" + +#include + +namespace mozilla { +void JsepTrack::GetNegotiatedPayloadTypes( + std::vector* payloadTypes) const { + if (!mNegotiatedDetails) { + return; + } + + for (const auto& encoding : mNegotiatedDetails->mEncodings) { + GetPayloadTypes(encoding->GetCodecs(), payloadTypes); + } + + // Prune out dupes + std::sort(payloadTypes->begin(), payloadTypes->end()); + auto newEnd = std::unique(payloadTypes->begin(), payloadTypes->end()); + payloadTypes->erase(newEnd, payloadTypes->end()); +} + +/* static */ +void JsepTrack::GetPayloadTypes( + const std::vector>& codecs, + std::vector* payloadTypes) { + for (const auto& codec : codecs) { + uint16_t pt; + if (!codec->GetPtAsInt(&pt)) { + MOZ_ASSERT(false); + continue; + } + payloadTypes->push_back(pt); + } +} + +void JsepTrack::EnsureNoDuplicatePayloadTypes( + std::vector>* codecs) { + std::set uniquePayloadTypes; + for (auto& codec : *codecs) { + codec->EnsureNoDuplicatePayloadTypes(uniquePayloadTypes); + } +} + +void JsepTrack::EnsureSsrcs(SsrcGenerator& ssrcGenerator, size_t aNumber) { + while (mSsrcs.size() < aNumber) { + uint32_t ssrc, rtxSsrc; + if (!ssrcGenerator.GenerateSsrc(&ssrc) || + !ssrcGenerator.GenerateSsrc(&rtxSsrc)) { + return; + } + mSsrcs.push_back(ssrc); + mSsrcToRtxSsrc[ssrc] = rtxSsrc; + MOZ_ASSERT(mSsrcs.size() == mSsrcToRtxSsrc.size()); + } +} + +void JsepTrack::PopulateCodecs( + const std::vector>& prototype) { + mPrototypeCodecs.clear(); + for (const auto& prototypeCodec : prototype) { + if (prototypeCodec->Type() == mType) { + mPrototypeCodecs.emplace_back(prototypeCodec->Clone()); + mPrototypeCodecs.back()->mDirection = mDirection; + } + } + + EnsureNoDuplicatePayloadTypes(&mPrototypeCodecs); +} + +void JsepTrack::AddToOffer(SsrcGenerator& ssrcGenerator, + SdpMediaSection* offer) { + AddToMsection(mPrototypeCodecs, offer); + + if (mDirection == sdp::kSend) { + std::vector rids; + if (offer->IsSending()) { + rids = mRids; + } + + AddToMsection(rids, sdp::kSend, ssrcGenerator, + IsRtxEnabled(mPrototypeCodecs), offer); + } +} + +void JsepTrack::AddToAnswer(const SdpMediaSection& offer, + SsrcGenerator& ssrcGenerator, + SdpMediaSection* answer) { + // We do not modify mPrototypeCodecs here, since we're only creating an + // answer. Once offer/answer concludes, we will update mPrototypeCodecs. + std::vector> codecs = + NegotiateCodecs(offer, true, Nothing()); + if (codecs.empty()) { + return; + } + + AddToMsection(codecs, answer); + + if (mDirection == sdp::kSend) { + AddToMsection(mRids, sdp::kSend, ssrcGenerator, IsRtxEnabled(codecs), + answer); + } +} + +void JsepTrack::SetRids(const std::vector& aRids) { + MOZ_ASSERT(!aRids.empty()); + if (!mRids.empty()) { + return; + } + mRids = aRids; +} + +void JsepTrack::SetMaxEncodings(size_t aMax) { + mMaxEncodings = aMax; + if (mRids.size() > mMaxEncodings) { + mRids.resize(mMaxEncodings); + } +} + +void JsepTrack::RecvTrackSetRemote(const Sdp& aSdp, + const SdpMediaSection& aMsection) { + mInHaveRemote = true; + MOZ_ASSERT(mDirection == sdp::kRecv); + MOZ_ASSERT(aMsection.GetMediaType() != + SdpMediaSection::MediaType::kApplication); + std::string error; + SdpHelper helper(&error); + + mRemoteSetSendBit = aMsection.IsSending(); + if (!mRemoteSetSendBit) { + mReceptive = false; + } + + if (aMsection.IsSending()) { + (void)helper.GetIdsFromMsid(aSdp, aMsection, &mStreamIds); + } else { + mStreamIds.clear(); + } + + // We do this whether or not the track is active + SetCNAME(helper.GetCNAME(aMsection)); + mSsrcs.clear(); + if (aMsection.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) { + for (const auto& ssrcAttr : aMsection.GetAttributeList().GetSsrc().mSsrcs) { + mSsrcs.push_back(ssrcAttr.ssrc); + } + } + + // Use FID ssrc-group to associate rtx ssrcs with "regular" ssrcs. Despite + // not being part of RFC 4588, this is how rtx is negotiated by libwebrtc + // and jitsi. + mSsrcToRtxSsrc.clear(); + if (aMsection.GetAttributeList().HasAttribute( + SdpAttribute::kSsrcGroupAttribute)) { + for (const auto& group : + aMsection.GetAttributeList().GetSsrcGroup().mSsrcGroups) { + if (group.semantics == SdpSsrcGroupAttributeList::kFid && + group.ssrcs.size() == 2) { + // Ensure we have a "regular" ssrc for each rtx ssrc. + if (std::find(mSsrcs.begin(), mSsrcs.end(), group.ssrcs[0]) != + mSsrcs.end()) { + mSsrcToRtxSsrc[group.ssrcs[0]] = group.ssrcs[1]; + + // Remove rtx ssrcs from mSsrcs + auto res = std::remove_if( + mSsrcs.begin(), mSsrcs.end(), + [group](uint32_t ssrc) { return ssrc == group.ssrcs[1]; }); + mSsrcs.erase(res, mSsrcs.end()); + } + } + } + } +} + +void JsepTrack::RecvTrackSetLocal(const SdpMediaSection& aMsection) { + MOZ_ASSERT(mDirection == sdp::kRecv); + + // TODO: Should more stuff live in here? Anything that needs to happen when we + // decide we're ready to receive packets should probably go in here. + mReceptive = aMsection.IsReceiving(); +} + +void JsepTrack::SendTrackSetRemote(SsrcGenerator& aSsrcGenerator, + const SdpMediaSection& aRemoteMsection) { + mInHaveRemote = true; + if (mType == SdpMediaSection::kApplication) { + return; + } + + std::vector rids; + + // TODO: Current language in webrtc-pc is completely broken, and so I will + // not be quoting it here. + if ((mType == SdpMediaSection::kVideo) && + aRemoteMsection.GetAttributeList().HasAttribute( + SdpAttribute::kSimulcastAttribute)) { + // Note: webrtc-pc does not appear to support the full IETF simulcast + // spec. In particular, the IETF simulcast spec supports requesting + // multiple different sets of encodings. For example, "a=simulcast:send + // 1,2;3,4;5,6" means that there are three simulcast streams, the first of + // which can use either rid 1 or 2 (but not both), the second of which can + // use rid 3 or 4 (but not both), and the third of which can use rid 5 or + // 6 (but not both). webrtc-pc does not support this either/or stuff for + // rid; each simulcast stream gets exactly one rid. + // Also, webrtc-pc does not support the '~' pause syntax at all + // See https://github.com/w3c/webrtc-pc/issues/2769 + GetRids(aRemoteMsection, sdp::kRecv, &rids); + } + + if (mRids.empty()) { + // Initial configuration + for (const auto& ridAttr : rids) { + // TODO: Spec might change, making a length > 16 invalid SDP. + std::string dummy; + if (SdpRidAttributeList::CheckRidValidity(ridAttr.id, &dummy) && + ridAttr.id.size() <= SdpRidAttributeList::kMaxRidLength) { + mRids.push_back(ridAttr.id); + } + } + if (mRids.size() > mMaxEncodings) { + mRids.resize(mMaxEncodings); + } + } else { + // JSEP is allowed to remove or reorder rids. RTCRtpSender won't pay + // attention to reordering. + std::vector newRids; + for (const auto& ridAttr : rids) { + for (const auto& oldRid : mRids) { + if (oldRid == ridAttr.id) { + newRids.push_back(oldRid); + break; + } + } + } + mRids = std::move(newRids); + } + + if (mRids.empty()) { + mRids.push_back(""); + } + + UpdateSsrcs(aSsrcGenerator, mRids.size()); +} + +void JsepTrack::AddToMsection( + const std::vector>& codecs, + SdpMediaSection* msection) const { + MOZ_ASSERT(msection->GetMediaType() == mType); + MOZ_ASSERT(!codecs.empty()); + + for (const auto& codec : codecs) { + codec->AddToMediaSection(*msection); + } + + if ((mDirection == sdp::kSend) && (mType != SdpMediaSection::kApplication) && + msection->IsSending()) { + if (mStreamIds.empty()) { + msection->AddMsid("-", mTrackId); + } else { + for (const std::string& streamId : mStreamIds) { + msection->AddMsid(streamId, mTrackId); + } + } + } +} + +void JsepTrack::UpdateSsrcs(SsrcGenerator& ssrcGenerator, size_t encodings) { + MOZ_ASSERT(mDirection == sdp::kSend); + MOZ_ASSERT(mType != SdpMediaSection::kApplication); + size_t numSsrcs = std::max(encodings, 1U); + + EnsureSsrcs(ssrcGenerator, numSsrcs); + PruneSsrcs(numSsrcs); + if (mNegotiatedDetails && mNegotiatedDetails->GetEncodingCount() > numSsrcs) { + mNegotiatedDetails->TruncateEncodings(numSsrcs); + } + + MOZ_ASSERT(!mSsrcs.empty()); +} + +void JsepTrack::PruneSsrcs(size_t aNumSsrcs) { + mSsrcs.resize(aNumSsrcs); + + // We might have duplicate entries in mSsrcs, so we need to resize first and + // then remove ummatched rtx ssrcs. + auto itor = mSsrcToRtxSsrc.begin(); + while (itor != mSsrcToRtxSsrc.end()) { + if (std::find(mSsrcs.begin(), mSsrcs.end(), itor->first) == mSsrcs.end()) { + itor = mSsrcToRtxSsrc.erase(itor); + } else { + ++itor; + } + } +} + +bool JsepTrack::IsRtxEnabled( + const std::vector>& codecs) const { + for (const auto& codec : codecs) { + if (codec->Type() == SdpMediaSection::kVideo && + static_cast(codec.get()) + ->mRtxEnabled) { + return true; + } + } + + return false; +} + +void JsepTrack::AddToMsection(const std::vector& aRids, + sdp::Direction direction, + SsrcGenerator& ssrcGenerator, bool rtxEnabled, + SdpMediaSection* msection) { + if (aRids.size() > 1) { + UniquePtr simulcast(new SdpSimulcastAttribute); + UniquePtr ridAttrs(new SdpRidAttributeList); + for (const std::string& rid : aRids) { + SdpRidAttributeList::Rid ridAttr; + ridAttr.id = rid; + ridAttr.direction = direction; + ridAttrs->mRids.push_back(ridAttr); + + SdpSimulcastAttribute::Version version; + version.choices.push_back(SdpSimulcastAttribute::Encoding(rid, false)); + if (direction == sdp::kSend) { + simulcast->sendVersions.push_back(version); + } else { + simulcast->recvVersions.push_back(version); + } + } + + msection->GetAttributeList().SetAttribute(simulcast.release()); + msection->GetAttributeList().SetAttribute(ridAttrs.release()); + } + + bool requireRtxSsrcs = rtxEnabled && msection->IsSending(); + + if (mType != SdpMediaSection::kApplication && mDirection == sdp::kSend) { + UpdateSsrcs(ssrcGenerator, aRids.size()); + + if (requireRtxSsrcs) { + MOZ_ASSERT(mSsrcs.size() == mSsrcToRtxSsrc.size()); + std::vector allSsrcs; + UniquePtr group(new SdpSsrcGroupAttributeList); + for (const auto& ssrc : mSsrcs) { + const auto rtxSsrc = mSsrcToRtxSsrc[ssrc]; + allSsrcs.push_back(ssrc); + allSsrcs.push_back(rtxSsrc); + group->PushEntry(SdpSsrcGroupAttributeList::kFid, {ssrc, rtxSsrc}); + } + msection->SetSsrcs(allSsrcs, mCNAME); + msection->GetAttributeList().SetAttribute(group.release()); + } else { + msection->SetSsrcs(mSsrcs, mCNAME); + } + } +} + +void JsepTrack::GetRids(const SdpMediaSection& msection, + sdp::Direction direction, + std::vector* rids) const { + rids->clear(); + if (!msection.GetAttributeList().HasAttribute( + SdpAttribute::kSimulcastAttribute)) { + return; + } + + const SdpSimulcastAttribute& simulcast( + msection.GetAttributeList().GetSimulcast()); + + const SdpSimulcastAttribute::Versions* versions = nullptr; + switch (direction) { + case sdp::kSend: + versions = &simulcast.sendVersions; + break; + case sdp::kRecv: + versions = &simulcast.recvVersions; + break; + } + + if (!versions->IsSet()) { + return; + } + + // RFC 8853 does not seem to forbid duplicate rids in a simulcast attribute. + // So, while this is obviously silly, we should be prepared for it and + // ignore those duplicate rids. + std::set uniqueRids; + for (const SdpSimulcastAttribute::Version& version : *versions) { + if (!version.choices.empty() && !uniqueRids.count(version.choices[0].rid)) { + // We validate that rids are present (and sane) elsewhere. + rids->push_back(*msection.FindRid(version.choices[0].rid)); + uniqueRids.insert(version.choices[0].rid); + } + } +} + +void JsepTrack::CreateEncodings( + const SdpMediaSection& remote, + const std::vector>& negotiatedCodecs, + JsepTrackNegotiatedDetails* negotiatedDetails) { + negotiatedDetails->mTias = remote.GetBandwidth("TIAS"); + + webrtc::RtcpMode rtcpMode = webrtc::RtcpMode::kCompound; + // rtcp-rsize (video only) + if (remote.GetMediaType() == SdpMediaSection::kVideo && + remote.GetAttributeList().HasAttribute( + SdpAttribute::kRtcpRsizeAttribute)) { + rtcpMode = webrtc::RtcpMode::kReducedSize; + } + negotiatedDetails->mRtpRtcpConf = RtpRtcpConfig(rtcpMode); + + // TODO add support for b=AS if TIAS is not set (bug 976521) + + if (mRids.empty()) { + mRids.push_back(""); + } + + size_t numEncodings = mRids.size(); + + // Drop SSRCs if fewer RIDs were offered than we have encodings + if (mSsrcs.size() > numEncodings) { + PruneSsrcs(numEncodings); + } + + // For each stream make sure we have an encoding, and configure + // that encoding appropriately. + for (size_t i = 0; i < numEncodings; ++i) { + UniquePtr encoding(new JsepTrackEncoding); + if (mRids.size() > i) { + encoding->mRid = mRids[i]; + } + for (const auto& codec : negotiatedCodecs) { + encoding->AddCodec(*codec); + } + negotiatedDetails->mEncodings.push_back(std::move(encoding)); + } +} + +std::vector> JsepTrack::GetCodecClones() const { + std::vector> clones; + for (const auto& codec : mPrototypeCodecs) { + clones.emplace_back(codec->Clone()); + } + return clones; +} + +static bool CompareCodec(const UniquePtr& lhs, + const UniquePtr& rhs) { + return lhs->mStronglyPreferred && !rhs->mStronglyPreferred; +} + +std::vector> JsepTrack::NegotiateCodecs( + const SdpMediaSection& remote, bool remoteIsOffer, + Maybe local) { + std::vector> negotiatedCodecs; + std::vector> newPrototypeCodecs; + + // Outer loop establishes the remote side's preference + for (const std::string& fmt : remote.GetFormats()) { + for (auto& codec : mPrototypeCodecs) { + if (!codec || !codec->mEnabled || !codec->Matches(fmt, remote)) { + continue; + } + + // First codec of ours that matches. See if we can negotiate it. + UniquePtr clone(codec->Clone()); + if (clone->Negotiate(fmt, remote, remoteIsOffer, local)) { + // If negotiation succeeded, remember the payload type the other side + // used for reoffers. + codec->mDefaultPt = fmt; + + // Remember whether we negotiated rtx and the associated pt for later. + if (codec->Type() == SdpMediaSection::kVideo) { + JsepVideoCodecDescription* videoCodec = + static_cast(codec.get()); + JsepVideoCodecDescription* cloneVideoCodec = + static_cast(clone.get()); + bool useRtx = + mRtxIsAllowed && + Preferences::GetBool("media.peerconnection.video.use_rtx", false); + videoCodec->mRtxEnabled = useRtx && cloneVideoCodec->mRtxEnabled; + videoCodec->mRtxPayloadType = cloneVideoCodec->mRtxPayloadType; + } + + // Moves the codec out of mPrototypeCodecs, leaving an empty + // UniquePtr, so we don't use it again. Also causes successfully + // negotiated codecs to be placed up front in the future. + newPrototypeCodecs.emplace_back(std::move(codec)); + negotiatedCodecs.emplace_back(std::move(clone)); + break; + } + } + } + + // newPrototypeCodecs contains just the negotiated stuff so far. Add the rest. + for (auto& codec : mPrototypeCodecs) { + if (codec) { + newPrototypeCodecs.emplace_back(std::move(codec)); + } + } + + // Negotiated stuff is up front, so it will take precedence when ensuring + // there are no duplicate payload types. + EnsureNoDuplicatePayloadTypes(&newPrototypeCodecs); + std::swap(newPrototypeCodecs, mPrototypeCodecs); + + // Find the (potential) red codec and ulpfec codec or telephone-event + JsepVideoCodecDescription* red = nullptr; + JsepVideoCodecDescription* ulpfec = nullptr; + JsepAudioCodecDescription* dtmf = nullptr; + // We can safely cast here since JsepTrack has a MediaType and only codecs + // that match that MediaType (kAudio or kVideo) are added. + for (auto& codec : negotiatedCodecs) { + if (codec->mName == "red") { + red = static_cast(codec.get()); + } else if (codec->mName == "ulpfec") { + ulpfec = static_cast(codec.get()); + } else if (codec->mName == "telephone-event") { + dtmf = static_cast(codec.get()); + } + } + // if we have a red codec remove redundant encodings that don't exist + if (red) { + // Since we could have an externally specified redundant endcodings + // list, we shouldn't simply rebuild the redundant encodings list + // based on the current list of codecs. + std::vector unnegotiatedEncodings; + std::swap(unnegotiatedEncodings, red->mRedundantEncodings); + for (auto redundantPt : unnegotiatedEncodings) { + std::string pt = std::to_string(redundantPt); + for (const auto& codec : negotiatedCodecs) { + if (pt == codec->mDefaultPt) { + red->mRedundantEncodings.push_back(redundantPt); + break; + } + } + } + } + // Video FEC is indicated by the existence of the red and ulpfec + // codecs and not an attribute on the particular video codec (like in + // a rtcpfb attr). If we see both red and ulpfec codecs, we enable FEC + // on all the other codecs. + if (red && ulpfec) { + for (auto& codec : negotiatedCodecs) { + if (codec->mName != "red" && codec->mName != "ulpfec") { + JsepVideoCodecDescription* videoCodec = + static_cast(codec.get()); + videoCodec->EnableFec(red->mDefaultPt, ulpfec->mDefaultPt); + } + } + } + + // Dtmf support is indicated by the existence of the telephone-event + // codec, and not an attribute on the particular audio codec (like in a + // rtcpfb attr). If we see the telephone-event codec, we enabled dtmf + // support on all the other audio codecs. + if (dtmf) { + for (auto& codec : negotiatedCodecs) { + JsepAudioCodecDescription* audioCodec = + static_cast(codec.get()); + audioCodec->mDtmfEnabled = true; + } + } + + // Make sure strongly preferred codecs are up front, overriding the remote + // side's preference. + std::stable_sort(negotiatedCodecs.begin(), negotiatedCodecs.end(), + CompareCodec); + + if (!red) { + // No red, remove ulpfec + negotiatedCodecs.erase( + std::remove_if(negotiatedCodecs.begin(), negotiatedCodecs.end(), + [ulpfec](const UniquePtr& codec) { + return codec.get() == ulpfec; + }), + negotiatedCodecs.end()); + // Make sure there's no dangling ptr here + ulpfec = nullptr; + } + + return negotiatedCodecs; +} + +nsresult JsepTrack::Negotiate(const SdpMediaSection& answer, + const SdpMediaSection& remote, + const SdpMediaSection& local) { + std::vector> negotiatedCodecs = + NegotiateCodecs(remote, &answer != &remote, SomeRef(local)); + + if (negotiatedCodecs.empty()) { + return NS_ERROR_FAILURE; + } + + UniquePtr negotiatedDetails = + MakeUnique(); + + CreateEncodings(remote, negotiatedCodecs, negotiatedDetails.get()); + + if (answer.GetAttributeList().HasAttribute(SdpAttribute::kExtmapAttribute)) { + for (auto& extmapAttr : answer.GetAttributeList().GetExtmap().mExtmaps) { + SdpDirectionAttribute::Direction direction = extmapAttr.direction; + if (&remote == &answer) { + // Answer is remote, we need to flip this. + direction = reverse(direction); + } + + if (direction & mDirection) { + negotiatedDetails->mExtmap[extmapAttr.extensionname] = extmapAttr; + } + } + } + + mInHaveRemote = false; + mNegotiatedDetails = std::move(negotiatedDetails); + return NS_OK; +} + +// When doing bundle, if all else fails we can try to figure out which m-line a +// given RTP packet belongs to by looking at the payload type field. This only +// works, however, if that payload type appeared in only one m-section. +// We figure that out here. +/* static */ +void JsepTrack::SetUniquePayloadTypes(std::vector& tracks) { + // Maps to track details if no other track contains the payload type, + // otherwise maps to nullptr. + std::map payloadTypeToDetailsMap; + + for (JsepTrack* track : tracks) { + if (track->GetMediaType() == SdpMediaSection::kApplication) { + continue; + } + + auto* details = track->GetNegotiatedDetails(); + if (!details) { + // Can happen if negotiation fails on a track + continue; + } + + std::vector payloadTypesForTrack; + track->GetNegotiatedPayloadTypes(&payloadTypesForTrack); + + for (uint16_t pt : payloadTypesForTrack) { + if (payloadTypeToDetailsMap.count(pt)) { + // Found in more than one track, not unique + payloadTypeToDetailsMap[pt] = nullptr; + } else { + payloadTypeToDetailsMap[pt] = details; + } + } + } + + for (auto ptAndDetails : payloadTypeToDetailsMap) { + uint16_t uniquePt = ptAndDetails.first; + MOZ_ASSERT(uniquePt <= UINT8_MAX); + auto trackDetails = ptAndDetails.second; + + if (trackDetails) { + trackDetails->mUniquePayloadTypes.push_back( + static_cast(uniquePt)); + } + } +} + +} // namespace mozilla diff --git a/dom/media/webrtc/jsep/JsepTrack.h b/dom/media/webrtc/jsep/JsepTrack.h new file mode 100644 index 0000000000..70583ccb71 --- /dev/null +++ b/dom/media/webrtc/jsep/JsepTrack.h @@ -0,0 +1,305 @@ +/* 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/. */ + +#ifndef _JSEPTRACK_H_ +#define _JSEPTRACK_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include "mozilla/Preferences.h" +#include "nsError.h" + +#include "jsep/JsepTrackEncoding.h" +#include "jsep/SsrcGenerator.h" +#include "sdp/Sdp.h" +#include "sdp/SdpAttribute.h" +#include "sdp/SdpMediaSection.h" +#include "libwebrtcglue/RtpRtcpConfig.h" +namespace mozilla { + +class JsepTrackNegotiatedDetails { + public: + JsepTrackNegotiatedDetails() + : mTias(0), mRtpRtcpConf(webrtc::RtcpMode::kCompound) {} + + JsepTrackNegotiatedDetails(const JsepTrackNegotiatedDetails& orig) + : mExtmap(orig.mExtmap), + mUniquePayloadTypes(orig.mUniquePayloadTypes), + mTias(orig.mTias), + mRtpRtcpConf(orig.mRtpRtcpConf) { + for (const auto& encoding : orig.mEncodings) { + mEncodings.emplace_back(new JsepTrackEncoding(*encoding)); + } + } + + size_t GetEncodingCount() const { return mEncodings.size(); } + + const JsepTrackEncoding& GetEncoding(size_t index) const { + MOZ_RELEASE_ASSERT(index < mEncodings.size()); + return *mEncodings[index]; + } + + void TruncateEncodings(size_t aSize) { + if (mEncodings.size() < aSize) { + MOZ_ASSERT(false); + return; + } + mEncodings.resize(aSize); + } + + const SdpExtmapAttributeList::Extmap* GetExt( + const std::string& ext_name) const { + auto it = mExtmap.find(ext_name); + if (it != mExtmap.end()) { + return &it->second; + } + return nullptr; + } + + void ForEachRTPHeaderExtension( + const std::function& + fn) const { + for (auto entry : mExtmap) { + fn(entry.second); + } + } + + std::vector GetUniquePayloadTypes() const { + return mUniquePayloadTypes; + } + + uint32_t GetTias() const { return mTias; } + + RtpRtcpConfig GetRtpRtcpConfig() const { return mRtpRtcpConf; } + + private: + friend class JsepTrack; + + std::map mExtmap; + std::vector mUniquePayloadTypes; + std::vector> mEncodings; + uint32_t mTias; // bits per second + RtpRtcpConfig mRtpRtcpConf; +}; + +class JsepTrack { + public: + JsepTrack(mozilla::SdpMediaSection::MediaType type, sdp::Direction direction) + : mType(type), + mDirection(direction), + mActive(false), + mRemoteSetSendBit(false) {} + + virtual ~JsepTrack() {} + + void UpdateStreamIds(const std::vector& streamIds) { + mStreamIds = streamIds; + } + + void SetTrackId(const std::string& aTrackId) { mTrackId = aTrackId; } + + void ClearStreamIds() { mStreamIds.clear(); } + + void RecvTrackSetRemote(const Sdp& aSdp, const SdpMediaSection& aMsection); + void RecvTrackSetLocal(const SdpMediaSection& aMsection); + + // This is called whenever a remote description is set; we do not wait for + // offer/answer to complete, since there's nothing to actually negotiate here. + void SendTrackSetRemote(SsrcGenerator& aSsrcGenerator, + const SdpMediaSection& aRemoteMsection); + + JsepTrack(const JsepTrack& orig) { *this = orig; } + + JsepTrack(JsepTrack&& orig) = default; + JsepTrack& operator=(JsepTrack&& rhs) = default; + + JsepTrack& operator=(const JsepTrack& rhs) { + if (this != &rhs) { + mType = rhs.mType; + mStreamIds = rhs.mStreamIds; + mTrackId = rhs.mTrackId; + mCNAME = rhs.mCNAME; + mDirection = rhs.mDirection; + mRids = rhs.mRids; + mSsrcs = rhs.mSsrcs; + mSsrcToRtxSsrc = rhs.mSsrcToRtxSsrc; + mActive = rhs.mActive; + mRemoteSetSendBit = rhs.mRemoteSetSendBit; + mReceptive = rhs.mReceptive; + mMaxEncodings = rhs.mMaxEncodings; + mInHaveRemote = rhs.mInHaveRemote; + mRtxIsAllowed = rhs.mRtxIsAllowed; + + mPrototypeCodecs.clear(); + for (const auto& codec : rhs.mPrototypeCodecs) { + mPrototypeCodecs.emplace_back(codec->Clone()); + } + if (rhs.mNegotiatedDetails) { + mNegotiatedDetails.reset( + new JsepTrackNegotiatedDetails(*rhs.mNegotiatedDetails)); + } + } + return *this; + } + + virtual mozilla::SdpMediaSection::MediaType GetMediaType() const { + return mType; + } + + virtual const std::vector& GetStreamIds() const { + return mStreamIds; + } + + virtual const std::string& GetCNAME() const { return mCNAME; } + + virtual void SetCNAME(const std::string& cname) { mCNAME = cname; } + + virtual sdp::Direction GetDirection() const { return mDirection; } + + virtual const std::vector& GetSsrcs() const { return mSsrcs; } + + virtual std::vector GetRtxSsrcs() const { + std::vector result; + if (mRtxIsAllowed && + Preferences::GetBool("media.peerconnection.video.use_rtx", false)) { + std::for_each( + mSsrcToRtxSsrc.begin(), mSsrcToRtxSsrc.end(), + [&result](const auto& pair) { result.push_back(pair.second); }); + } + return result; + } + + virtual void EnsureSsrcs(SsrcGenerator& ssrcGenerator, size_t aNumber); + + bool GetActive() const { return mActive; } + + void SetActive(bool active) { mActive = active; } + + bool GetRemoteSetSendBit() const { return mRemoteSetSendBit; } + bool GetReceptive() const { return mReceptive; } + + virtual void PopulateCodecs( + const std::vector>& prototype); + + template + void ForEachCodec(UnaryFunction func) { + std::for_each(mPrototypeCodecs.begin(), mPrototypeCodecs.end(), func); + } + + template + void SortCodecs(BinaryPredicate sorter) { + std::stable_sort(mPrototypeCodecs.begin(), mPrototypeCodecs.end(), sorter); + } + + // These two are non-const because this is where ssrcs are chosen. + virtual void AddToOffer(SsrcGenerator& ssrcGenerator, SdpMediaSection* offer); + virtual void AddToAnswer(const SdpMediaSection& offer, + SsrcGenerator& ssrcGenerator, + SdpMediaSection* answer); + + virtual nsresult Negotiate(const SdpMediaSection& answer, + const SdpMediaSection& remote, + const SdpMediaSection& local); + static void SetUniquePayloadTypes(std::vector& tracks); + virtual void GetNegotiatedPayloadTypes( + std::vector* payloadTypes) const; + + // This will be set when negotiation is carried out. + virtual const JsepTrackNegotiatedDetails* GetNegotiatedDetails() const { + if (mNegotiatedDetails) { + return mNegotiatedDetails.get(); + } + return nullptr; + } + + virtual JsepTrackNegotiatedDetails* GetNegotiatedDetails() { + if (mNegotiatedDetails) { + return mNegotiatedDetails.get(); + } + return nullptr; + } + + virtual void ClearNegotiatedDetails() { mNegotiatedDetails.reset(); } + + void SetRids(const std::vector& aRids); + void ClearRids() { mRids.clear(); } + const std::vector& GetRids() const { return mRids; } + + void AddToMsection(const std::vector& aRids, + sdp::Direction direction, SsrcGenerator& ssrcGenerator, + bool rtxEnabled, SdpMediaSection* msection); + + // See Bug 1642419, this can be removed when all sites are working with RTX. + void SetRtxIsAllowed(bool aRtxIsAllowed) { mRtxIsAllowed = aRtxIsAllowed; } + + void SetMaxEncodings(size_t aMax); + bool IsInHaveRemote() const { return mInHaveRemote; } + + private: + std::vector> GetCodecClones() const; + static void EnsureNoDuplicatePayloadTypes( + std::vector>* codecs); + static void GetPayloadTypes( + const std::vector>& codecs, + std::vector* pts); + void AddToMsection(const std::vector>& codecs, + SdpMediaSection* msection) const; + void GetRids(const SdpMediaSection& msection, sdp::Direction direction, + std::vector* rids) const; + void CreateEncodings( + const SdpMediaSection& remote, + const std::vector>& negotiatedCodecs, + JsepTrackNegotiatedDetails* details); + + virtual std::vector> NegotiateCodecs( + const SdpMediaSection& remote, bool remoteIsOffer, + Maybe local); + + void UpdateSsrcs(SsrcGenerator& ssrcGenerator, size_t encodings); + void PruneSsrcs(size_t aNumSsrcs); + bool IsRtxEnabled( + const std::vector>& codecs) const; + + mozilla::SdpMediaSection::MediaType mType; + // These are the ids that everyone outside of JsepSession care about + std::vector mStreamIds; + std::string mTrackId; + std::string mCNAME; + sdp::Direction mDirection; + std::vector> mPrototypeCodecs; + // List of rids. May be initially populated from JS, or from a remote SDP. + // Can be updated by remote SDP. If no negotiation has taken place at all, + // this will be empty. If negotiation has taken place, but no simulcast + // attr was negotiated, this will contain the empty string as a single + // element. If a simulcast attribute was negotiated, this will contain the + // negotiated rids. + std::vector mRids; + UniquePtr mNegotiatedDetails; + std::vector mSsrcs; + std::map mSsrcToRtxSsrc; + bool mActive; + bool mRemoteSetSendBit; + // This is used to drive RTCRtpTransceiver.[[Receptive]]. Basically, this + // denotes whether we are prepared to receive RTP. When we apply a local + // description with the recv bit set, this is set to true, even if we have + // not seen the remote description yet. If we apply either a local or remote + // description without the recv bit set (from our perspective), this is set + // to false. + bool mReceptive = false; + size_t mMaxEncodings = 3; + bool mInHaveRemote = false; + + // See Bug 1642419, this can be removed when all sites are working with RTX. + bool mRtxIsAllowed = true; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/webrtc/jsep/JsepTrackEncoding.h b/dom/media/webrtc/jsep/JsepTrackEncoding.h new file mode 100644 index 0000000000..c17dd8534e --- /dev/null +++ b/dom/media/webrtc/jsep/JsepTrackEncoding.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef _JESPTRACKENCODING_H_ +#define _JESPTRACKENCODING_H_ + +#include "jsep/JsepCodecDescription.h" +#include "common/EncodingConstraints.h" + +#include + +namespace mozilla { +// Represents a single encoding of a media track. When simulcast is used, there +// may be multiple. Each encoding may have some constraints (imposed by JS), and +// may be able to use any one of multiple codecs (JsepCodecDescription) at any +// given time. +class JsepTrackEncoding { + public: + JsepTrackEncoding() = default; + JsepTrackEncoding(const JsepTrackEncoding& orig) { *this = orig; } + + JsepTrackEncoding(JsepTrackEncoding&& aOrig) = default; + + JsepTrackEncoding& operator=(const JsepTrackEncoding& aRhs) { + if (this != &aRhs) { + mRid = aRhs.mRid; + mCodecs.clear(); + for (const auto& codec : aRhs.mCodecs) { + mCodecs.emplace_back(codec->Clone()); + } + } + return *this; + } + + const std::vector>& GetCodecs() const { + return mCodecs; + } + + void AddCodec(const JsepCodecDescription& codec) { + mCodecs.emplace_back(codec.Clone()); + } + + bool HasFormat(const std::string& format) const { + for (const auto& codec : mCodecs) { + if (codec->mDefaultPt == format) { + return true; + } + } + return false; + } + + std::string mRid; + + private: + std::vector> mCodecs; +}; +} // namespace mozilla + +#endif // _JESPTRACKENCODING_H_ diff --git a/dom/media/webrtc/jsep/JsepTransceiver.h b/dom/media/webrtc/jsep/JsepTransceiver.h new file mode 100644 index 0000000000..45c9b85120 --- /dev/null +++ b/dom/media/webrtc/jsep/JsepTransceiver.h @@ -0,0 +1,220 @@ +/* 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/. */ + +#ifndef _JSEPTRANSCEIVER_H_ +#define _JSEPTRANSCEIVER_H_ + +#include + +#include "sdp/SdpAttribute.h" +#include "sdp/SdpMediaSection.h" +#include "sdp/Sdp.h" +#include "jsep/JsepTransport.h" +#include "jsep/JsepTrack.h" + +#include "nsError.h" + +namespace mozilla { + +class JsepUuidGenerator { + public: + virtual ~JsepUuidGenerator() = default; + virtual bool Generate(std::string* id) = 0; + virtual JsepUuidGenerator* Clone() const = 0; +}; + +class JsepTransceiver { + public: + explicit JsepTransceiver(SdpMediaSection::MediaType type, + JsepUuidGenerator& aUuidGen, + SdpDirectionAttribute::Direction jsDirection = + SdpDirectionAttribute::kSendrecv) + : mJsDirection(jsDirection), + mSendTrack(type, sdp::kSend), + mRecvTrack(type, sdp::kRecv), + mLevel(SIZE_MAX), + mBundleLevel(SIZE_MAX), + mAddTrackMagic(false), + mOnlyExistsBecauseOfSetRemote(false), + mStopped(false), + mRemoved(false), + mNegotiated(false), + mCanRecycle(false) { + if (!aUuidGen.Generate(&mUuid)) { + MOZ_CRASH(); + } + } + + JsepTransceiver(const JsepTransceiver& orig) = default; + JsepTransceiver(JsepTransceiver&& orig) = default; + JsepTransceiver& operator=(const JsepTransceiver& aRhs) = default; + JsepTransceiver& operator=(JsepTransceiver&& aRhs) = default; + + ~JsepTransceiver() = default; + + void Rollback(JsepTransceiver& oldTransceiver, bool aRemote) { + MOZ_ASSERT(oldTransceiver.GetMediaType() == GetMediaType()); + MOZ_ASSERT(!oldTransceiver.IsNegotiated() || !oldTransceiver.HasLevel() || + !HasLevel() || oldTransceiver.GetLevel() == GetLevel()); + mTransport = oldTransceiver.mTransport; + if (aRemote) { + mLevel = oldTransceiver.mLevel; + mBundleLevel = oldTransceiver.mBundleLevel; + mSendTrack = oldTransceiver.mSendTrack; + } + mRecvTrack = oldTransceiver.mRecvTrack; + + // Don't allow rollback to re-associate a transceiver. + if (!oldTransceiver.IsAssociated()) { + Disassociate(); + } + } + + bool IsAssociated() const { return !mMid.empty(); } + + const std::string& GetMid() const { + MOZ_ASSERT(IsAssociated()); + return mMid; + } + + void Associate(const std::string& mid) { + MOZ_ASSERT(HasLevel()); + mMid = mid; + } + + void Disassociate() { mMid.clear(); } + + bool HasLevel() const { return mLevel != SIZE_MAX; } + + void SetLevel(size_t level) { + MOZ_ASSERT(level != SIZE_MAX); + MOZ_ASSERT(!HasLevel()); + MOZ_ASSERT(!IsStopped()); + + mLevel = level; + } + + void ClearLevel() { + MOZ_ASSERT(!IsAssociated()); + mLevel = SIZE_MAX; + mBundleLevel = SIZE_MAX; + } + + size_t GetLevel() const { + MOZ_ASSERT(HasLevel()); + return mLevel; + } + + void Stop() { mStopped = true; } + + bool IsStopped() const { return mStopped; } + + void RestartDatachannelTransceiver() { + MOZ_RELEASE_ASSERT(GetMediaType() == SdpMediaSection::kApplication); + mStopped = false; + mCanRecycle = false; + } + + void SetRemoved() { mRemoved = true; } + + bool IsRemoved() const { return mRemoved; } + + bool HasBundleLevel() const { return mBundleLevel != SIZE_MAX; } + + size_t BundleLevel() const { + MOZ_ASSERT(HasBundleLevel()); + return mBundleLevel; + } + + void SetBundleLevel(size_t aBundleLevel) { + MOZ_ASSERT(aBundleLevel != SIZE_MAX); + mBundleLevel = aBundleLevel; + } + + void ClearBundleLevel() { mBundleLevel = SIZE_MAX; } + + size_t GetTransportLevel() const { + MOZ_ASSERT(HasLevel()); + if (HasBundleLevel()) { + return BundleLevel(); + } + return GetLevel(); + } + + void SetAddTrackMagic() { mAddTrackMagic = true; } + + bool HasAddTrackMagic() const { return mAddTrackMagic; } + + void SetOnlyExistsBecauseOfSetRemote(bool aValue) { + mOnlyExistsBecauseOfSetRemote = aValue; + } + + bool OnlyExistsBecauseOfSetRemote() const { + return mOnlyExistsBecauseOfSetRemote; + } + + void SetNegotiated() { + MOZ_ASSERT(IsAssociated()); + MOZ_ASSERT(HasLevel()); + mNegotiated = true; + } + + bool IsNegotiated() const { return mNegotiated; } + + void SetCanRecycle() { mCanRecycle = true; } + + bool CanRecycle() const { return mCanRecycle; } + + const std::string& GetUuid() const { return mUuid; } + + // Convenience function + SdpMediaSection::MediaType GetMediaType() const { + MOZ_ASSERT(mRecvTrack.GetMediaType() == mSendTrack.GetMediaType()); + return mRecvTrack.GetMediaType(); + } + + bool HasTransport() const { return mTransport.mComponents != 0; } + + bool HasOwnTransport() const { + if (HasTransport() && + (!HasBundleLevel() || (GetLevel() == BundleLevel()))) { + return true; + } + return false; + } + + // See Bug 1642419, this can be removed when all sites are working with RTX. + void SetRtxIsAllowed(bool aRtxIsAllowed) { + mSendTrack.SetRtxIsAllowed(aRtxIsAllowed); + mRecvTrack.SetRtxIsAllowed(aRtxIsAllowed); + } + + // This is the direction JS wants. It might not actually happen. + SdpDirectionAttribute::Direction mJsDirection; + + JsepTrack mSendTrack; + JsepTrack mRecvTrack; + JsepTransport mTransport; + + private: + std::string mUuid; + // Stuff that is not negotiated + std::string mMid; + size_t mLevel; // SIZE_MAX if no level + // Is this track pair sharing a transport with another? + size_t mBundleLevel; // SIZE_MAX if no bundle level + // The w3c and IETF specs have a lot of "magical" behavior that happens + // when addTrack is used to create a transceiver. This was a deliberate + // design choice. Sadface. + bool mAddTrackMagic; + bool mOnlyExistsBecauseOfSetRemote; + bool mStopped; + bool mRemoved; + bool mNegotiated; + bool mCanRecycle; +}; + +} // namespace mozilla + +#endif // _JSEPTRANSCEIVER_H_ diff --git a/dom/media/webrtc/jsep/JsepTransport.h b/dom/media/webrtc/jsep/JsepTransport.h new file mode 100644 index 0000000000..67690b1fd4 --- /dev/null +++ b/dom/media/webrtc/jsep/JsepTransport.h @@ -0,0 +1,102 @@ +/* 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/. */ + +#ifndef _JSEPTRANSPORT_H_ +#define _JSEPTRANSPORT_H_ + +#include +#include + +#include +#include +#include "nsISupportsImpl.h" + +#include "sdp/SdpAttribute.h" + +namespace mozilla { + +class JsepDtlsTransport { + public: + JsepDtlsTransport() : mRole(kJsepDtlsInvalidRole) {} + + virtual ~JsepDtlsTransport() {} + + enum Role { kJsepDtlsClient, kJsepDtlsServer, kJsepDtlsInvalidRole }; + + virtual const SdpFingerprintAttributeList& GetFingerprints() const { + return mFingerprints; + } + + virtual Role GetRole() const { return mRole; } + + private: + friend class JsepSessionImpl; + + SdpFingerprintAttributeList mFingerprints; + Role mRole; +}; + +class JsepIceTransport { + public: + JsepIceTransport() {} + + virtual ~JsepIceTransport() {} + + const std::string& GetUfrag() const { return mUfrag; } + const std::string& GetPassword() const { return mPwd; } + const std::vector& GetCandidates() const { return mCandidates; } + + private: + friend class JsepSessionImpl; + + std::string mUfrag; + std::string mPwd; + std::vector mCandidates; +}; + +class JsepTransport { + public: + JsepTransport() : mComponents(0) {} + + JsepTransport(const JsepTransport& orig) { *this = orig; } + + ~JsepTransport() {} + + JsepTransport& operator=(const JsepTransport& orig) { + if (this != &orig) { + mIce.reset(orig.mIce ? new JsepIceTransport(*orig.mIce) : nullptr); + mDtls.reset(orig.mDtls ? new JsepDtlsTransport(*orig.mDtls) : nullptr); + mTransportId = orig.mTransportId; + mComponents = orig.mComponents; + mLocalUfrag = orig.mLocalUfrag; + mLocalPwd = orig.mLocalPwd; + } + return *this; + } + + void Close() { + mComponents = 0; + mTransportId.clear(); + mIce.reset(); + mDtls.reset(); + mLocalUfrag.clear(); + mLocalPwd.clear(); + } + + // Unique identifier for this transport within this call. Group? + std::string mTransportId; + + // ICE stuff. + UniquePtr mIce; + UniquePtr mDtls; + + // Number of required components. + size_t mComponents; + std::string mLocalUfrag; + std::string mLocalPwd; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/webrtc/jsep/SsrcGenerator.cpp b/dom/media/webrtc/jsep/SsrcGenerator.cpp new file mode 100644 index 0000000000..50025007d1 --- /dev/null +++ b/dom/media/webrtc/jsep/SsrcGenerator.cpp @@ -0,0 +1,22 @@ +/* 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 "jsep/SsrcGenerator.h" +#include "pk11pub.h" + +namespace mozilla { + +bool SsrcGenerator::GenerateSsrc(uint32_t* ssrc) { + do { + SECStatus rv = PK11_GenerateRandom(reinterpret_cast(ssrc), + sizeof(uint32_t)); + if (rv != SECSuccess) { + return false; + } + } while (mSsrcs.count(*ssrc)); + mSsrcs.insert(*ssrc); + + return true; +} +} // namespace mozilla diff --git a/dom/media/webrtc/jsep/SsrcGenerator.h b/dom/media/webrtc/jsep/SsrcGenerator.h new file mode 100644 index 0000000000..b106358f32 --- /dev/null +++ b/dom/media/webrtc/jsep/SsrcGenerator.h @@ -0,0 +1,20 @@ +/* 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/. */ + +#ifndef _SSRCGENERATOR_H_ +#define _SSRCGENERATOR_H_ + +#include + +namespace mozilla { +class SsrcGenerator { + public: + bool GenerateSsrc(uint32_t* ssrc); + + private: + std::set mSsrcs; +}; +} // namespace mozilla + +#endif // _SSRCGENERATOR_H_ diff --git a/dom/media/webrtc/jsep/moz.build b/dom/media/webrtc/jsep/moz.build new file mode 100644 index 0000000000..750ac76a30 --- /dev/null +++ b/dom/media/webrtc/jsep/moz.build @@ -0,0 +1,18 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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("/dom/media/webrtc/third_party_build/webrtc.mozbuild") + +LOCAL_INCLUDES += [ + "/dom/media/webrtc", + "/media/webrtc", + "/third_party/libwebrtc", + "/third_party/libwebrtc/third_party/abseil-cpp", + "/third_party/sipcc", +] + +UNIFIED_SOURCES += ["JsepSessionImpl.cpp", "JsepTrack.cpp", "SsrcGenerator.cpp"] + +FINAL_LIBRARY = "xul" diff --git a/dom/media/webrtc/libwebrtcglue/AudioConduit.cpp b/dom/media/webrtc/libwebrtcglue/AudioConduit.cpp new file mode 100644 index 0000000000..d8492f779a --- /dev/null +++ b/dom/media/webrtc/libwebrtcglue/AudioConduit.cpp @@ -0,0 +1,975 @@ +/* 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 "AudioConduit.h" + +#include "common/browser_logging/CSFLog.h" +#include "MediaConduitControl.h" +#include "mozilla/media/MediaUtils.h" +#include "mozilla/Telemetry.h" +#include "transport/runnable_utils.h" +#include "transport/SrtpFlow.h" // For SRTP_MAX_EXPANSION +#include "WebrtcCallWrapper.h" + +// libwebrtc includes +#include "api/audio_codecs/builtin_audio_encoder_factory.h" +#include "audio/audio_receive_stream.h" +#include "media/base/media_constants.h" + +// for ntohs +#ifdef HAVE_NETINET_IN_H +# include +#elif defined XP_WIN +# include +#endif + +#ifdef MOZ_WIDGET_ANDROID +# include "AndroidBridge.h" +#endif + +namespace mozilla { + +namespace { + +static const char* acLogTag = "WebrtcAudioSessionConduit"; +#ifdef LOGTAG +# undef LOGTAG +#endif +#define LOGTAG acLogTag + +using namespace cricket; +using LocalDirection = MediaSessionConduitLocalDirection; + +const char kCodecParamCbr[] = "cbr"; + +} // namespace + +/** + * Factory Method for AudioConduit + */ +RefPtr AudioSessionConduit::Create( + RefPtr aCall, + nsCOMPtr aStsThread) { + CSFLogDebug(LOGTAG, "%s ", __FUNCTION__); + MOZ_ASSERT(NS_IsMainThread()); + + return MakeRefPtr(std::move(aCall), + std::move(aStsThread)); +} + +#define INIT_MIRROR(name, val) \ + name(aCallThread, val, "WebrtcAudioConduit::Control::" #name " (Mirror)") +WebrtcAudioConduit::Control::Control(const RefPtr& aCallThread) + : INIT_MIRROR(mReceiving, false), + INIT_MIRROR(mTransmitting, false), + INIT_MIRROR(mLocalSsrcs, Ssrcs()), + INIT_MIRROR(mLocalCname, std::string()), + INIT_MIRROR(mMid, std::string()), + INIT_MIRROR(mRemoteSsrc, 0), + INIT_MIRROR(mSyncGroup, std::string()), + INIT_MIRROR(mLocalRecvRtpExtensions, RtpExtList()), + INIT_MIRROR(mLocalSendRtpExtensions, RtpExtList()), + INIT_MIRROR(mSendCodec, Nothing()), + INIT_MIRROR(mRecvCodecs, std::vector()) {} +#undef INIT_MIRROR + +RefPtr WebrtcAudioConduit::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + + return InvokeAsync(mCallThread, "WebrtcAudioConduit::Shutdown (main thread)", + [this, self = RefPtr(this)] { + mControl.mReceiving.DisconnectIfConnected(); + mControl.mTransmitting.DisconnectIfConnected(); + mControl.mLocalSsrcs.DisconnectIfConnected(); + mControl.mLocalCname.DisconnectIfConnected(); + mControl.mMid.DisconnectIfConnected(); + mControl.mRemoteSsrc.DisconnectIfConnected(); + mControl.mSyncGroup.DisconnectIfConnected(); + mControl.mLocalRecvRtpExtensions.DisconnectIfConnected(); + mControl.mLocalSendRtpExtensions.DisconnectIfConnected(); + mControl.mSendCodec.DisconnectIfConnected(); + mControl.mRecvCodecs.DisconnectIfConnected(); + mControl.mOnDtmfEventListener.DisconnectIfExists(); + mWatchManager.Shutdown(); + + { + AutoWriteLock lock(mLock); + DeleteSendStream(); + DeleteRecvStream(); + } + + return GenericPromise::CreateAndResolve( + true, "WebrtcAudioConduit::Shutdown (call thread)"); + }); +} + +WebrtcAudioConduit::WebrtcAudioConduit( + RefPtr aCall, nsCOMPtr aStsThread) + : mCall(std::move(aCall)), + mSendTransport(this), + mRecvTransport(this), + mRecvStreamConfig(), + mRecvStream(nullptr), + mSendStreamConfig(&mSendTransport), + mSendStream(nullptr), + mSendStreamRunning(false), + mRecvStreamRunning(false), + mDtmfEnabled(false), + mLock("WebrtcAudioConduit::mLock"), + mCallThread(std::move(mCall->mCallThread)), + mStsThread(std::move(aStsThread)), + mControl(mCall->mCallThread), + mWatchManager(this, mCall->mCallThread) { + mRecvStreamConfig.rtcp_send_transport = &mRecvTransport; + mRecvStreamConfig.rtp.rtcp_event_observer = this; +} + +/** + * Destruction defines for our super-classes + */ +WebrtcAudioConduit::~WebrtcAudioConduit() { + CSFLogDebug(LOGTAG, "%s ", __FUNCTION__); + MOZ_ASSERT(!mSendStream && !mRecvStream, + "Call DeleteStreams prior to ~WebrtcAudioConduit."); +} + +#define CONNECT(aCanonical, aMirror) \ + do { \ + (aMirror).Connect(aCanonical); \ + mWatchManager.Watch(aMirror, &WebrtcAudioConduit::OnControlConfigChange); \ + } while (0) + +void WebrtcAudioConduit::InitControl(AudioConduitControlInterface* aControl) { + MOZ_ASSERT(mCallThread->IsOnCurrentThread()); + + CONNECT(aControl->CanonicalReceiving(), mControl.mReceiving); + CONNECT(aControl->CanonicalTransmitting(), mControl.mTransmitting); + CONNECT(aControl->CanonicalLocalSsrcs(), mControl.mLocalSsrcs); + CONNECT(aControl->CanonicalLocalCname(), mControl.mLocalCname); + CONNECT(aControl->CanonicalMid(), mControl.mMid); + CONNECT(aControl->CanonicalRemoteSsrc(), mControl.mRemoteSsrc); + CONNECT(aControl->CanonicalSyncGroup(), mControl.mSyncGroup); + CONNECT(aControl->CanonicalLocalRecvRtpExtensions(), + mControl.mLocalRecvRtpExtensions); + CONNECT(aControl->CanonicalLocalSendRtpExtensions(), + mControl.mLocalSendRtpExtensions); + CONNECT(aControl->CanonicalAudioSendCodec(), mControl.mSendCodec); + CONNECT(aControl->CanonicalAudioRecvCodecs(), mControl.mRecvCodecs); + mControl.mOnDtmfEventListener = aControl->OnDtmfEvent().Connect( + mCall->mCallThread, this, &WebrtcAudioConduit::OnDtmfEvent); +} + +#undef CONNECT + +void WebrtcAudioConduit::OnDtmfEvent(const DtmfEvent& aEvent) { + MOZ_ASSERT(mCallThread->IsOnCurrentThread()); + MOZ_ASSERT(mSendStream); + MOZ_ASSERT(mDtmfEnabled); + mSendStream->SendTelephoneEvent(aEvent.mPayloadType, aEvent.mPayloadFrequency, + aEvent.mEventCode, aEvent.mLengthMs); +} + +void WebrtcAudioConduit::OnControlConfigChange() { + MOZ_ASSERT(mCallThread->IsOnCurrentThread()); + + bool recvStreamReconfigureNeeded = false; + bool sendStreamReconfigureNeeded = false; + bool recvStreamRecreationNeeded = false; + bool sendStreamRecreationNeeded = false; + + if (!mControl.mLocalSsrcs.Ref().empty()) { + if (mControl.mLocalSsrcs.Ref()[0] != mSendStreamConfig.rtp.ssrc) { + sendStreamRecreationNeeded = true; + + // For now... + recvStreamRecreationNeeded = true; + } + mRecvStreamConfig.rtp.local_ssrc = mControl.mLocalSsrcs.Ref()[0]; + mSendStreamConfig.rtp.ssrc = mControl.mLocalSsrcs.Ref()[0]; + + // In the future we can do this instead of recreating the recv stream: + // if (mRecvStream) { + // mCall->Call()->OnLocalSsrcUpdated(mRecvStream, + // mControl.mLocalSsrcs.Ref()[0]); + // } + } + + if (mControl.mLocalCname.Ref() != mSendStreamConfig.rtp.c_name) { + mSendStreamConfig.rtp.c_name = mControl.mLocalCname.Ref(); + sendStreamReconfigureNeeded = true; + } + + if (mControl.mMid.Ref() != mSendStreamConfig.rtp.mid) { + mSendStreamConfig.rtp.mid = mControl.mMid.Ref(); + sendStreamReconfigureNeeded = true; + } + + if (mControl.mRemoteSsrc.Ref() != mControl.mConfiguredRemoteSsrc) { + mRecvStreamConfig.rtp.remote_ssrc = mControl.mConfiguredRemoteSsrc = + mControl.mRemoteSsrc.Ref(); + recvStreamRecreationNeeded = true; + } + + if (mControl.mSyncGroup.Ref() != mRecvStreamConfig.sync_group) { + mRecvStreamConfig.sync_group = mControl.mSyncGroup.Ref(); + // For now... + recvStreamRecreationNeeded = true; + // In the future we can do this instead of recreating the recv stream: + // if (mRecvStream) { + // mCall->Call()->OnUpdateSyncGroup(mRecvStream, + // mRecvStreamConfig.sync_group); + // } + } + + if (auto filteredExtensions = FilterExtensions( + LocalDirection::kRecv, mControl.mLocalRecvRtpExtensions); + filteredExtensions != mRecvStreamConfig.rtp.extensions) { + mRecvStreamConfig.rtp.extensions = std::move(filteredExtensions); + // For now... + recvStreamRecreationNeeded = true; + // In the future we can do this instead of recreating the recv stream: + // if (mRecvStream) { + // mRecvStream->SetRtpExtensions(mRecvStreamConfig.rtp.extensions); + //} + } + + if (auto filteredExtensions = FilterExtensions( + LocalDirection::kSend, mControl.mLocalSendRtpExtensions); + filteredExtensions != mSendStreamConfig.rtp.extensions) { + // At the very least, we need a reconfigure. Recreation needed if the + // extmap for any extension has changed, but not for adding/removing + // extensions. + sendStreamReconfigureNeeded = true; + + for (const auto& newExt : filteredExtensions) { + if (sendStreamRecreationNeeded) { + break; + } + for (const auto& oldExt : mSendStreamConfig.rtp.extensions) { + if (newExt.uri == oldExt.uri) { + if (newExt.id != oldExt.id) { + sendStreamRecreationNeeded = true; + } + // We're done handling newExt, one way or another + break; + } + } + } + + mSendStreamConfig.rtp.extensions = std::move(filteredExtensions); + } + + mControl.mSendCodec.Ref().apply([&](const auto& aConfig) { + if (mControl.mConfiguredSendCodec != mControl.mSendCodec.Ref()) { + mControl.mConfiguredSendCodec = mControl.mSendCodec; + if (ValidateCodecConfig(aConfig, true) == kMediaConduitNoError) { + mSendStreamConfig.encoder_factory = + webrtc::CreateBuiltinAudioEncoderFactory(); + + webrtc::AudioSendStream::Config::SendCodecSpec spec( + aConfig.mType, CodecConfigToLibwebrtcFormat(aConfig)); + mSendStreamConfig.send_codec_spec = spec; + + mDtmfEnabled = aConfig.mDtmfEnabled; + sendStreamReconfigureNeeded = true; + } + } + }); + + if (mControl.mConfiguredRecvCodecs != mControl.mRecvCodecs.Ref()) { + mControl.mConfiguredRecvCodecs = mControl.mRecvCodecs; + mRecvStreamConfig.decoder_factory = mCall->mAudioDecoderFactory; + mRecvStreamConfig.decoder_map.clear(); + + for (const auto& codec : mControl.mRecvCodecs.Ref()) { + if (ValidateCodecConfig(codec, false) != kMediaConduitNoError) { + continue; + } + mRecvStreamConfig.decoder_map.emplace( + codec.mType, CodecConfigToLibwebrtcFormat(codec)); + } + + recvStreamReconfigureNeeded = true; + } + + if (!recvStreamReconfigureNeeded && !sendStreamReconfigureNeeded && + !recvStreamRecreationNeeded && !sendStreamRecreationNeeded && + mControl.mReceiving == mRecvStreamRunning && + mControl.mTransmitting == mSendStreamRunning) { + // No changes applied -- no need to lock. + return; + } + + if (recvStreamRecreationNeeded) { + recvStreamReconfigureNeeded = false; + } + if (sendStreamRecreationNeeded) { + sendStreamReconfigureNeeded = false; + } + + { + AutoWriteLock lock(mLock); + // Recreate/Stop/Start streams as needed. + if (recvStreamRecreationNeeded) { + DeleteRecvStream(); + } + if (mControl.mReceiving) { + CreateRecvStream(); + } + if (sendStreamRecreationNeeded) { + DeleteSendStream(); + } + if (mControl.mTransmitting) { + CreateSendStream(); + } + } + + // We make sure to not hold the lock while stopping/starting/reconfiguring + // streams, so as to not cause deadlocks. These methods can cause our platform + // codecs to dispatch sync runnables to main, and main may grab the lock. + + if (mRecvStream && recvStreamReconfigureNeeded) { + MOZ_ASSERT(!recvStreamRecreationNeeded); + mRecvStream->SetDecoderMap(mRecvStreamConfig.decoder_map); + } + + if (mSendStream && sendStreamReconfigureNeeded) { + MOZ_ASSERT(!sendStreamRecreationNeeded); + // TODO: Pass a callback here, so we can react to RTCErrors thrown by + // libwebrtc. + mSendStream->Reconfigure(mSendStreamConfig, nullptr); + } + + if (!mControl.mReceiving) { + StopReceiving(); + } + if (!mControl.mTransmitting) { + StopTransmitting(); + } + + if (mControl.mReceiving) { + StartReceiving(); + } + if (mControl.mTransmitting) { + StartTransmitting(); + } +} + +std::vector WebrtcAudioConduit::GetLocalSSRCs() const { + MOZ_ASSERT(mCallThread->IsOnCurrentThread()); + return std::vector(1, mRecvStreamConfig.rtp.local_ssrc); +} + +bool WebrtcAudioConduit::OverrideRemoteSSRC(uint32_t aSsrc) { + MOZ_ASSERT(mCallThread->IsOnCurrentThread()); + + if (mRecvStreamConfig.rtp.remote_ssrc == aSsrc) { + return true; + } + mRecvStreamConfig.rtp.remote_ssrc = aSsrc; + + const bool wasReceiving = mRecvStreamRunning; + const bool hadRecvStream = mRecvStream; + + StopReceiving(); + + if (hadRecvStream) { + AutoWriteLock lock(mLock); + DeleteRecvStream(); + CreateRecvStream(); + } + + if (wasReceiving) { + StartReceiving(); + } + return true; +} + +Maybe WebrtcAudioConduit::GetRemoteSSRC() const { + MOZ_ASSERT(mCallThread->IsOnCurrentThread()); + // libwebrtc uses 0 to mean a lack of SSRC. That is not to spec. + return mRecvStreamConfig.rtp.remote_ssrc == 0 + ? Nothing() + : Some(mRecvStreamConfig.rtp.remote_ssrc); +} + +Maybe +WebrtcAudioConduit::GetReceiverStats() const { + MOZ_ASSERT(mCallThread->IsOnCurrentThread()); + if (!mRecvStream) { + return Nothing(); + } + return Some(mRecvStream->GetStats()); +} + +Maybe WebrtcAudioConduit::GetSenderStats() + const { + MOZ_ASSERT(mCallThread->IsOnCurrentThread()); + if (!mSendStream) { + return Nothing(); + } + return Some(mSendStream->GetStats()); +} + +Maybe WebrtcAudioConduit::GetCallStats() const { + MOZ_ASSERT(mCallThread->IsOnCurrentThread()); + if (!mCall->Call()) { + return Nothing(); + } + return Some(mCall->Call()->GetStats()); +} + +void WebrtcAudioConduit::OnRtcpBye() { mRtcpByeEvent.Notify(); } + +void WebrtcAudioConduit::OnRtcpTimeout() { mRtcpTimeoutEvent.Notify(); } + +void WebrtcAudioConduit::SetTransportActive(bool aActive) { + MOZ_ASSERT(mStsThread->IsOnCurrentThread()); + if (mTransportActive == aActive) { + return; + } + + // If false, This stops us from sending + mTransportActive = aActive; + + // We queue this because there might be notifications to these listeners + // pending, and we don't want to drop them by letting this jump ahead of + // those notifications. We move the listeners into the lambda in case the + // transport comes back up before we disconnect them. (The Connect calls + // happen in MediaPipeline) + // We retain a strong reference to ourself, because the listeners are holding + // a non-refcounted reference to us, and moving them into the lambda could + // conceivably allow them to outlive us. + if (!aActive) { + MOZ_ALWAYS_SUCCEEDS(mCallThread->Dispatch(NS_NewRunnableFunction( + __func__, + [self = RefPtr(this), + recvRtpListener = std::move(mReceiverRtpEventListener), + recvRtcpListener = std::move(mReceiverRtcpEventListener), + sendRtcpListener = std::move(mSenderRtcpEventListener)]() mutable { + recvRtpListener.DisconnectIfExists(); + recvRtcpListener.DisconnectIfExists(); + sendRtcpListener.DisconnectIfExists(); + }))); + } +} + +// AudioSessionConduit Implementation +MediaConduitErrorCode WebrtcAudioConduit::SendAudioFrame( + std::unique_ptr frame) { + CSFLogDebug(LOGTAG, "%s ", __FUNCTION__); + // Following checks need to be performed + // 1. Non null audio buffer pointer, and + // 2. Valid sample rate, and + // 3. Appropriate Sample Length for 10 ms audio-frame. This represents the + // block size used upstream for processing. + // Ex: for 16000 sample rate , valid block-length is 160. + // Similarly for 32000 sample rate, valid block length is 320. + + if (!frame->data() || + (IsSamplingFreqSupported(frame->sample_rate_hz()) == false) || + ((frame->samples_per_channel() % (frame->sample_rate_hz() / 100) != 0))) { + CSFLogError(LOGTAG, "%s Invalid Parameters ", __FUNCTION__); + MOZ_ASSERT(PR_FALSE); + return kMediaConduitMalformedArgument; + } + + // This is the AudioProxyThread, blocking it for a bit is fine. + AutoReadLock lock(mLock); + if (!mSendStreamRunning) { + CSFLogError(LOGTAG, "%s Engine not transmitting ", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + + mSendStream->SendAudioData(std::move(frame)); + return kMediaConduitNoError; +} + +MediaConduitErrorCode WebrtcAudioConduit::GetAudioFrame( + int32_t samplingFreqHz, webrtc::AudioFrame* frame) { + CSFLogDebug(LOGTAG, "%s ", __FUNCTION__); + + // validate params + if (!frame) { + CSFLogError(LOGTAG, "%s Null Audio Buffer Pointer", __FUNCTION__); + MOZ_ASSERT(PR_FALSE); + return kMediaConduitMalformedArgument; + } + + // Validate sample length + if (GetNum10msSamplesForFrequency(samplingFreqHz) == 0) { + CSFLogError(LOGTAG, "%s Invalid Sampling Frequency ", __FUNCTION__); + MOZ_ASSERT(PR_FALSE); + return kMediaConduitMalformedArgument; + } + + // If the lock is taken, skip this chunk to avoid blocking the audio thread. + AutoTryReadLock tryLock(mLock); + if (!tryLock) { + CSFLogError(LOGTAG, "%s Conduit going through negotiation ", __FUNCTION__); + return kMediaConduitPlayoutError; + } + + // Conduit should have reception enabled before we ask for decoded + // samples + if (!mRecvStreamRunning) { + CSFLogError(LOGTAG, "%s Engine not Receiving ", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + + // Unfortunate to have to cast to an internal class, but that looks like the + // only way short of interfacing with a layer above (which mixes all streams, + // which we don't want) or a layer below (which we try to avoid because it is + // less stable). + auto info = static_cast(mRecvStream) + ->GetAudioFrameWithInfo(samplingFreqHz, frame); + + if (info == webrtc::AudioMixer::Source::AudioFrameInfo::kError) { + CSFLogError(LOGTAG, "%s Getting audio frame failed", __FUNCTION__); + return kMediaConduitPlayoutError; + } + + CSFLogDebug(LOGTAG, "%s Got %zu channels of %zu samples", __FUNCTION__, + frame->num_channels(), frame->samples_per_channel()); + return kMediaConduitNoError; +} + +// Transport Layer Callbacks +void WebrtcAudioConduit::OnRtpReceived(webrtc::RtpPacketReceived&& aPacket, + webrtc::RTPHeader&& aHeader) { + MOZ_ASSERT(mCallThread->IsOnCurrentThread()); + + if (mAllowSsrcChange && mRecvStreamConfig.rtp.remote_ssrc != aHeader.ssrc) { + CSFLogDebug(LOGTAG, "%s: switching from SSRC %u to %u", __FUNCTION__, + mRecvStreamConfig.rtp.remote_ssrc, aHeader.ssrc); + OverrideRemoteSSRC(aHeader.ssrc); + } + + CSFLogVerbose(LOGTAG, "%s: seq# %u, Len %zu, SSRC %u (0x%x) ", __FUNCTION__, + aPacket.SequenceNumber(), aPacket.size(), aPacket.Ssrc(), + aPacket.Ssrc()); + + mRtpPacketEvent.Notify(); + if (mCall->Call()) { + mCall->Call()->Receiver()->DeliverRtpPacket( + webrtc::MediaType::AUDIO, std::move(aPacket), + [self = RefPtr(this)]( + const webrtc::RtpPacketReceived& packet) { + CSFLogVerbose( + LOGTAG, + "AudioConduit %p: failed demuxing packet, ssrc: %u seq: %u", + self.get(), packet.Ssrc(), packet.SequenceNumber()); + return false; + }); + } +} + +void WebrtcAudioConduit::OnRtcpReceived(MediaPacket&& aPacket) { + CSFLogDebug(LOGTAG, "%s", __FUNCTION__); + MOZ_ASSERT(mCallThread->IsOnCurrentThread()); + + if (mCall->Call()) { + mCall->Call()->Receiver()->DeliverRtcpPacket( + rtc::CopyOnWriteBuffer(aPacket.data(), aPacket.len())); + } +} + +Maybe WebrtcAudioConduit::RtpSendBaseSeqFor(uint32_t aSsrc) const { + MOZ_ASSERT(mCallThread->IsOnCurrentThread()); + auto it = mRtpSendBaseSeqs.find(aSsrc); + if (it == mRtpSendBaseSeqs.end()) { + return Nothing(); + } + return Some(it->second); +} + +const dom::RTCStatsTimestampMaker& WebrtcAudioConduit::GetTimestampMaker() + const { + return mCall->GetTimestampMaker(); +} + +void WebrtcAudioConduit::StopTransmitting() { + MOZ_ASSERT(mCallThread->IsOnCurrentThread()); + MOZ_ASSERT(!mLock.LockedForWritingByCurrentThread()); + + if (!mSendStreamRunning) { + return; + } + + if (mSendStream) { + CSFLogDebug(LOGTAG, "%s Stopping send stream", __FUNCTION__); + mSendStream->Stop(); + } + + mSendStreamRunning = false; +} + +void WebrtcAudioConduit::StartTransmitting() { + MOZ_ASSERT(mCallThread->IsOnCurrentThread()); + MOZ_ASSERT(mSendStream); + MOZ_ASSERT(!mLock.LockedForWritingByCurrentThread()); + + if (mSendStreamRunning) { + return; + } + + CSFLogDebug(LOGTAG, "%s Starting send stream", __FUNCTION__); + + mCall->Call()->SignalChannelNetworkState(webrtc::MediaType::AUDIO, + webrtc::kNetworkUp); + mSendStream->Start(); + mSendStreamRunning = true; +} + +void WebrtcAudioConduit::StopReceiving() { + MOZ_ASSERT(mCallThread->IsOnCurrentThread()); + MOZ_ASSERT(!mLock.LockedForWritingByCurrentThread()); + + if (!mRecvStreamRunning) { + return; + } + + if (mRecvStream) { + CSFLogDebug(LOGTAG, "%s Stopping recv stream", __FUNCTION__); + mRecvStream->Stop(); + } + + mRecvStreamRunning = false; +} + +void WebrtcAudioConduit::StartReceiving() { + MOZ_ASSERT(mCallThread->IsOnCurrentThread()); + MOZ_ASSERT(mRecvStream); + MOZ_ASSERT(!mLock.LockedForWritingByCurrentThread()); + + if (mRecvStreamRunning) { + return; + } + + CSFLogDebug(LOGTAG, "%s Starting receive stream (SSRC %u (0x%x))", + __FUNCTION__, mRecvStreamConfig.rtp.remote_ssrc, + mRecvStreamConfig.rtp.remote_ssrc); + + mCall->Call()->SignalChannelNetworkState(webrtc::MediaType::AUDIO, + webrtc::kNetworkUp); + mRecvStream->Start(); + mRecvStreamRunning = true; +} + +bool WebrtcAudioConduit::SendRtp(const uint8_t* aData, size_t aLength, + const webrtc::PacketOptions& aOptions) { + MOZ_ASSERT(aLength >= 12); + const uint16_t seqno = ntohs(*((uint16_t*)&aData[2])); + const uint32_t ssrc = ntohl(*((uint32_t*)&aData[8])); + + CSFLogVerbose( + LOGTAG, + "AudioConduit %p: Sending RTP Packet seq# %u, len %zu, SSRC %u (0x%x)", + this, seqno, aLength, ssrc, ssrc); + + if (!mTransportActive) { + CSFLogError(LOGTAG, "AudioConduit %p: RTP Packet Send Failed ", this); + return false; + } + + MediaPacket packet; + packet.Copy(aData, aLength, aLength + SRTP_MAX_EXPANSION); + packet.SetType(MediaPacket::RTP); + mSenderRtpSendEvent.Notify(std::move(packet)); + + // Parse the sequence number of the first rtp packet as base_seq. + const auto inserted = mRtpSendBaseSeqs_n.insert({ssrc, seqno}).second; + + if (inserted || aOptions.packet_id >= 0) { + int64_t now_ms = PR_Now() / 1000; + MOZ_ALWAYS_SUCCEEDS(mCallThread->Dispatch(NS_NewRunnableFunction( + __func__, [this, self = RefPtr(this), + packet_id = aOptions.packet_id, now_ms, ssrc, seqno] { + mRtpSendBaseSeqs.insert({ssrc, seqno}); + if (packet_id >= 0) { + if (mCall->Call()) { + // TODO: This notification should ideally happen after the + // transport layer has sent the packet on the wire. + mCall->Call()->OnSentPacket({packet_id, now_ms}); + } + } + }))); + } + return true; +} + +bool WebrtcAudioConduit::SendSenderRtcp(const uint8_t* aData, size_t aLength) { + CSFLogVerbose( + LOGTAG, + "AudioConduit %p: Sending RTCP SR Packet, len %zu, SSRC %u (0x%x)", this, + aLength, (uint32_t)ntohl(*((uint32_t*)&aData[4])), + (uint32_t)ntohl(*((uint32_t*)&aData[4]))); + + if (!mTransportActive) { + CSFLogError(LOGTAG, "%s RTCP SR Packet Send Failed ", __FUNCTION__); + return false; + } + + MediaPacket packet; + packet.Copy(aData, aLength, aLength + SRTP_MAX_EXPANSION); + packet.SetType(MediaPacket::RTCP); + mSenderRtcpSendEvent.Notify(std::move(packet)); + return true; +} + +bool WebrtcAudioConduit::SendReceiverRtcp(const uint8_t* aData, + size_t aLength) { + CSFLogVerbose( + LOGTAG, + "AudioConduit %p: Sending RTCP RR Packet, len %zu, SSRC %u (0x%x)", this, + aLength, (uint32_t)ntohl(*((uint32_t*)&aData[4])), + (uint32_t)ntohl(*((uint32_t*)&aData[4]))); + + if (!mTransportActive) { + CSFLogError(LOGTAG, "AudioConduit %p: RTCP RR Packet Send Failed", this); + return false; + } + + MediaPacket packet; + packet.Copy(aData, aLength, aLength + SRTP_MAX_EXPANSION); + packet.SetType(MediaPacket::RTCP); + mReceiverRtcpSendEvent.Notify(std::move(packet)); + return true; +} + +/** + * Supported Sampling Frequencies. + */ +bool WebrtcAudioConduit::IsSamplingFreqSupported(int freq) const { + return GetNum10msSamplesForFrequency(freq) != 0; +} + +std::vector WebrtcAudioConduit::GetUpstreamRtpSources() + const { + MOZ_ASSERT(NS_IsMainThread()); + std::vector sources; + { + AutoReadLock lock(mLock); + if (mRecvStream) { + sources = mRecvStream->GetSources(); + } + } + return sources; +} + +/* Return block-length of 10 ms audio frame in number of samples */ +unsigned int WebrtcAudioConduit::GetNum10msSamplesForFrequency( + int samplingFreqHz) const { + switch (samplingFreqHz) { + case 16000: + return 160; // 160 samples + case 32000: + return 320; // 320 samples + case 44100: + return 441; // 441 samples + case 48000: + return 480; // 480 samples + default: + return 0; // invalid or unsupported + } +} + +/** + * Perform validation on the codecConfig to be applied. + * Verifies if the codec is already applied. + */ +MediaConduitErrorCode WebrtcAudioConduit::ValidateCodecConfig( + const AudioCodecConfig& codecInfo, bool send) { + if (codecInfo.mName.empty()) { + CSFLogError(LOGTAG, "%s Empty Payload Name ", __FUNCTION__); + return kMediaConduitMalformedArgument; + } + + // Only mono or stereo channels supported + if ((codecInfo.mChannels != 1) && (codecInfo.mChannels != 2)) { + CSFLogError(LOGTAG, "%s Channel Unsupported ", __FUNCTION__); + return kMediaConduitMalformedArgument; + } + + return kMediaConduitNoError; +} + +RtpExtList WebrtcAudioConduit::FilterExtensions(LocalDirection aDirection, + const RtpExtList& aExtensions) { + const bool isSend = aDirection == LocalDirection::kSend; + RtpExtList filteredExtensions; + + for (const auto& extension : aExtensions) { + // ssrc-audio-level RTP header extension + if (extension.uri == webrtc::RtpExtension::kAudioLevelUri) { + filteredExtensions.push_back( + webrtc::RtpExtension(extension.uri, extension.id)); + } + + // csrc-audio-level RTP header extension + if (extension.uri == webrtc::RtpExtension::kCsrcAudioLevelsUri) { + if (isSend) { + continue; + } + filteredExtensions.push_back( + webrtc::RtpExtension(extension.uri, extension.id)); + } + + // MID RTP header extension + if (extension.uri == webrtc::RtpExtension::kMidUri) { + if (!isSend) { + // TODO: recv mid support, see also bug 1727211 + continue; + } + filteredExtensions.push_back( + webrtc::RtpExtension(extension.uri, extension.id)); + } + } + + return filteredExtensions; +} + +webrtc::SdpAudioFormat WebrtcAudioConduit::CodecConfigToLibwebrtcFormat( + const AudioCodecConfig& aConfig) { + webrtc::SdpAudioFormat::Parameters parameters; + if (aConfig.mName == kOpusCodecName) { + if (aConfig.mChannels == 2) { + parameters[kCodecParamStereo] = kParamValueTrue; + } + if (aConfig.mFECEnabled) { + parameters[kCodecParamUseInbandFec] = kParamValueTrue; + } + if (aConfig.mDTXEnabled) { + parameters[kCodecParamUseDtx] = kParamValueTrue; + } + if (aConfig.mMaxPlaybackRate) { + parameters[kCodecParamMaxPlaybackRate] = + std::to_string(aConfig.mMaxPlaybackRate); + } + if (aConfig.mMaxAverageBitrate) { + parameters[kCodecParamMaxAverageBitrate] = + std::to_string(aConfig.mMaxAverageBitrate); + } + if (aConfig.mFrameSizeMs) { + parameters[kCodecParamPTime] = std::to_string(aConfig.mFrameSizeMs); + } + if (aConfig.mMinFrameSizeMs) { + parameters[kCodecParamMinPTime] = std::to_string(aConfig.mMinFrameSizeMs); + } + if (aConfig.mMaxFrameSizeMs) { + parameters[kCodecParamMaxPTime] = std::to_string(aConfig.mMaxFrameSizeMs); + } + if (aConfig.mCbrEnabled) { + parameters[kCodecParamCbr] = kParamValueTrue; + } + } + + return webrtc::SdpAudioFormat(aConfig.mName, aConfig.mFreq, aConfig.mChannels, + parameters); +} + +void WebrtcAudioConduit::DeleteSendStream() { + MOZ_ASSERT(mCallThread->IsOnCurrentThread()); + MOZ_ASSERT(mLock.LockedForWritingByCurrentThread()); + + if (!mSendStream) { + return; + } + + mCall->Call()->DestroyAudioSendStream(mSendStream); + mSendStreamRunning = false; + mSendStream = nullptr; + + // Reset base_seqs in case ssrcs get re-used. + mRtpSendBaseSeqs.clear(); +} + +void WebrtcAudioConduit::CreateSendStream() { + MOZ_ASSERT(mCallThread->IsOnCurrentThread()); + MOZ_ASSERT(mLock.LockedForWritingByCurrentThread()); + + if (mSendStream) { + return; + } + + mSendStream = mCall->Call()->CreateAudioSendStream(mSendStreamConfig); +} + +void WebrtcAudioConduit::DeleteRecvStream() { + MOZ_ASSERT(mCallThread->IsOnCurrentThread()); + MOZ_ASSERT(mLock.LockedForWritingByCurrentThread()); + + if (!mRecvStream) { + return; + } + + mCall->Call()->DestroyAudioReceiveStream(mRecvStream); + mRecvStreamRunning = false; + mRecvStream = nullptr; +} + +void WebrtcAudioConduit::CreateRecvStream() { + MOZ_ASSERT(mCallThread->IsOnCurrentThread()); + MOZ_ASSERT(mLock.LockedForWritingByCurrentThread()); + + if (mRecvStream) { + return; + } + + mRecvStream = mCall->Call()->CreateAudioReceiveStream(mRecvStreamConfig); + // Ensure that we set the jitter buffer target on this stream. + mRecvStream->SetBaseMinimumPlayoutDelayMs(mJitterBufferTargetMs); +} + +void WebrtcAudioConduit::SetJitterBufferTarget(DOMHighResTimeStamp aTargetMs) { + MOZ_RELEASE_ASSERT(aTargetMs <= std::numeric_limits::max()); + MOZ_RELEASE_ASSERT(aTargetMs >= 0); + + MOZ_ALWAYS_SUCCEEDS(mCallThread->Dispatch(NS_NewRunnableFunction( + __func__, + [this, self = RefPtr(this), targetMs = aTargetMs] { + mJitterBufferTargetMs = static_cast(targetMs); + if (mRecvStream) { + mRecvStream->SetBaseMinimumPlayoutDelayMs(targetMs); + } + }))); +} + +void WebrtcAudioConduit::DeliverPacket(rtc::CopyOnWriteBuffer packet, + PacketType type) { + // Currently unused. + MOZ_ASSERT(false); +} + +Maybe WebrtcAudioConduit::ActiveSendPayloadType() const { + MOZ_ASSERT(mCallThread->IsOnCurrentThread()); + + auto stats = GetSenderStats(); + if (!stats) { + return Nothing(); + } + + if (!stats->codec_payload_type) { + return Nothing(); + } + + return Some(*stats->codec_payload_type); +} + +Maybe WebrtcAudioConduit::ActiveRecvPayloadType() const { + MOZ_ASSERT(mCallThread->IsOnCurrentThread()); + + auto stats = GetReceiverStats(); + if (!stats) { + return Nothing(); + } + + if (!stats->codec_payload_type) { + return Nothing(); + } + + return Some(*stats->codec_payload_type); +} + +} // namespace mozilla diff --git a/dom/media/webrtc/libwebrtcglue/AudioConduit.h b/dom/media/webrtc/libwebrtcglue/AudioConduit.h new file mode 100644 index 0000000000..e8de331e12 --- /dev/null +++ b/dom/media/webrtc/libwebrtcglue/AudioConduit.h @@ -0,0 +1,303 @@ +/* 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/. */ + +#ifndef AUDIO_SESSION_H_ +#define AUDIO_SESSION_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/RWLock.h" +#include "mozilla/StateMirroring.h" +#include "mozilla/TimeStamp.h" + +#include "MediaConduitInterface.h" +#include "common/MediaEngineWrapper.h" + +/** + * This file hosts several structures identifying different aspects of a RTP + * Session. + */ +namespace mozilla { + +struct DtmfEvent; + +/** + * Concrete class for Audio session. Hooks up + * - media-source and target to external transport + */ +class WebrtcAudioConduit : public AudioSessionConduit, + public webrtc::RtcpEventObserver { + public: + Maybe ActiveSendPayloadType() const override; + Maybe ActiveRecvPayloadType() const override; + + void OnRtpReceived(webrtc::RtpPacketReceived&& aPacket, + webrtc::RTPHeader&& aHeader); + void OnRtcpReceived(MediaPacket&& aPacket); + + void OnRtcpBye() override; + void OnRtcpTimeout() override; + + void SetTransportActive(bool aActive) override; + + MediaEventSourceExc& SenderRtpSendEvent() override { + return mSenderRtpSendEvent; + } + MediaEventSourceExc& SenderRtcpSendEvent() override { + return mSenderRtcpSendEvent; + } + MediaEventSourceExc& ReceiverRtcpSendEvent() override { + return mReceiverRtcpSendEvent; + } + void ConnectReceiverRtpEvent( + MediaEventSourceExc& aEvent) + override { + mReceiverRtpEventListener = + aEvent.Connect(mCallThread, this, &WebrtcAudioConduit::OnRtpReceived); + } + void ConnectReceiverRtcpEvent( + MediaEventSourceExc& aEvent) override { + mReceiverRtcpEventListener = + aEvent.Connect(mCallThread, this, &WebrtcAudioConduit::OnRtcpReceived); + } + void ConnectSenderRtcpEvent( + MediaEventSourceExc& aEvent) override { + mSenderRtcpEventListener = + aEvent.Connect(mCallThread, this, &WebrtcAudioConduit::OnRtcpReceived); + } + + Maybe RtpSendBaseSeqFor(uint32_t aSsrc) const override; + + const dom::RTCStatsTimestampMaker& GetTimestampMaker() const override; + + void StopTransmitting(); + void StartTransmitting(); + void StopReceiving(); + void StartReceiving(); + + /** + * Function to deliver externally captured audio sample for encoding and + * transport + * @param frame [in]: AudioFrame in upstream's format for forwarding to the + * send stream. Ownership is passed along. + * NOTE: ConfigureSendMediaCodec() SHOULD be called before this function can + * be invoked. This ensures the inserted audio-samples can be transmitted by + * the conduit. + */ + MediaConduitErrorCode SendAudioFrame( + std::unique_ptr frame) override; + + /** + * Function to grab a decoded audio-sample from the media engine for + * rendering / playout of length 10 milliseconds. + * + * @param samplingFreqHz [in]: Frequency of the sampling for playback in + * Hertz (16000, 32000,..) + * @param frame [in/out]: Pointer to an AudioFrame to which audio data will be + * copied + * NOTE: This function should be invoked every 10 milliseconds for the best + * performance + * NOTE: ConfigureRecvMediaCodec() SHOULD be called before this function can + * be invoked + * This ensures the decoded samples are ready for reading and playout is + * enabled. + */ + MediaConduitErrorCode GetAudioFrame(int32_t samplingFreqHz, + webrtc::AudioFrame* frame) override; + + bool SendRtp(const uint8_t* aData, size_t aLength, + const webrtc::PacketOptions& aOptions) override; + bool SendSenderRtcp(const uint8_t* aData, size_t aLength) override; + bool SendReceiverRtcp(const uint8_t* aData, size_t aLength) override; + + bool HasCodecPluginID(uint64_t aPluginID) const override { return false; } + + void SetJitterBufferTarget(DOMHighResTimeStamp aTargetMs) override; + + void DeliverPacket(rtc::CopyOnWriteBuffer packet, PacketType type) override; + + RefPtr Shutdown() override; + + WebrtcAudioConduit(RefPtr aCall, + nsCOMPtr aStsThread); + + virtual ~WebrtcAudioConduit(); + + // Call thread. + void InitControl(AudioConduitControlInterface* aControl) override; + + // Handle a DTMF event from mControl.mOnDtmfEventListener. + void OnDtmfEvent(const DtmfEvent& aEvent); + + // Called when a parameter in mControl has changed. Call thread. + void OnControlConfigChange(); + + Ssrcs GetLocalSSRCs() const override; + Maybe GetRemoteSSRC() const override; + + void DisableSsrcChanges() override { + MOZ_ASSERT(mCallThread->IsOnCurrentThread()); + mAllowSsrcChange = false; + } + + private: + /** + * Override the remote ssrc configured on mRecvStreamConfig. + * + * Recreates and restarts the recv stream if needed. The overriden value is + * overwritten the next time the mControl.mRemoteSsrc mirror changes value. + * + * Call thread only. + */ + bool OverrideRemoteSSRC(uint32_t aSsrc); + + public: + void UnsetRemoteSSRC(uint32_t aSsrc) override {} + + Maybe GetReceiverStats() + const override; + Maybe GetSenderStats() const override; + Maybe GetCallStats() const override; + + bool IsSamplingFreqSupported(int freq) const override; + + MediaEventSource& RtcpByeEvent() override { return mRtcpByeEvent; } + MediaEventSource& RtcpTimeoutEvent() override { + return mRtcpTimeoutEvent; + } + MediaEventSource& RtpPacketEvent() override { return mRtpPacketEvent; } + + std::vector GetUpstreamRtpSources() const override; + + private: + WebrtcAudioConduit(const WebrtcAudioConduit& other) = delete; + void operator=(const WebrtcAudioConduit& other) = delete; + + // Generate block size in sample length for a given sampling frequency + unsigned int GetNum10msSamplesForFrequency(int samplingFreqHz) const; + + // Checks the codec to be applied + static MediaConduitErrorCode ValidateCodecConfig( + const AudioCodecConfig& codecInfo, bool send); + /** + * Of all extensions in aExtensions, returns a list of supported extensions. + */ + static RtpExtList FilterExtensions( + MediaSessionConduitLocalDirection aDirection, + const RtpExtList& aExtensions); + static webrtc::SdpAudioFormat CodecConfigToLibwebrtcFormat( + const AudioCodecConfig& aConfig); + + void CreateSendStream(); + void DeleteSendStream(); + void CreateRecvStream(); + void DeleteRecvStream(); + + // Are SSRC changes without signaling allowed or not. + // Call thread only. + bool mAllowSsrcChange = true; + + // Const so can be accessed on any thread. Most methods are called on the Call + // thread. + const RefPtr mCall; + + // Set up in the ctor and then not touched. Called through by the streams on + // any thread. + WebrtcSendTransport mSendTransport; + WebrtcReceiveTransport mRecvTransport; + + // Accessed only on the Call thread. + webrtc::AudioReceiveStreamInterface::Config mRecvStreamConfig; + + // Written only on the Call thread. Guarded by mLock, except for reads on the + // Call thread. + webrtc::AudioReceiveStreamInterface* mRecvStream; + + // Accessed only on the Call thread. + webrtc::AudioSendStream::Config mSendStreamConfig; + + // Written only on the Call thread. Guarded by mLock, except for reads on the + // Call thread. + webrtc::AudioSendStream* mSendStream; + + // If true => mSendStream started and not stopped + // Written only on the Call thread. + Atomic mSendStreamRunning; + // If true => mRecvStream started and not stopped + // Written only on the Call thread. + Atomic mRecvStreamRunning; + + // Accessed only on the Call thread. + bool mDtmfEnabled; + + mutable RWLock mLock MOZ_UNANNOTATED; + + // Call worker thread. All access to mCall->Call() happens here. + const RefPtr mCallThread; + + // Socket transport service thread. Any thread. + const nsCOMPtr mStsThread; + + // Target jitter buffer to be applied to the receive stream in milliseconds. + uint16_t mJitterBufferTargetMs = 0; + + struct Control { + // Mirrors and events that map to AudioConduitControlInterface for control. + // Call thread only. + Mirror mReceiving; + Mirror mTransmitting; + Mirror mLocalSsrcs; + Mirror mLocalCname; + Mirror mMid; + Mirror mRemoteSsrc; + Mirror mSyncGroup; + Mirror mLocalRecvRtpExtensions; + Mirror mLocalSendRtpExtensions; + Mirror> mSendCodec; + Mirror> mRecvCodecs; + MediaEventListener mOnDtmfEventListener; + + // For caching mRemoteSsrc, since another caller may change the remote ssrc + // in the stream config directly. + Ssrc mConfiguredRemoteSsrc = 0; + // For tracking changes to mSendCodec. + Maybe mConfiguredSendCodec; + // For tracking changes to mRecvCodecs. + std::vector mConfiguredRecvCodecs; + + Control() = delete; + explicit Control(const RefPtr& aCallThread); + } mControl; + + // WatchManager allowing Mirrors to trigger functions that will update the + // webrtc.org configuration. + WatchManager mWatchManager; + + // Accessed from mStsThread. Last successfully polled RTT + Maybe mRttSec; + + // Call thread only. ssrc -> base_seq + std::map mRtpSendBaseSeqs; + // libwebrtc network thread only. ssrc -> base_seq. + // To track changes needed to mRtpSendBaseSeqs. + std::map mRtpSendBaseSeqs_n; + + // Thread safe + Atomic mTransportActive = Atomic(false); + MediaEventProducer mRtcpByeEvent; + MediaEventProducer mRtcpTimeoutEvent; + MediaEventProducer mRtpPacketEvent; + MediaEventProducerExc mSenderRtpSendEvent; + MediaEventProducerExc mSenderRtcpSendEvent; + MediaEventProducerExc mReceiverRtcpSendEvent; + + // Assigned and revoked on mStsThread. Listeners for receiving packets. + MediaEventListener mSenderRtcpEventListener; // Rtp-transmitting pipeline + MediaEventListener mReceiverRtcpEventListener; // Rtp-receiving pipeline + MediaEventListener mReceiverRtpEventListener; // Rtp-receiving pipeline +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/webrtc/libwebrtcglue/CallWorkerThread.h b/dom/media/webrtc/libwebrtcglue/CallWorkerThread.h new file mode 100644 index 0000000000..12d21fbee4 --- /dev/null +++ b/dom/media/webrtc/libwebrtcglue/CallWorkerThread.h @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_MEDIA_WEBRTC_LIBWEBRTCGLUE_CALLWORKERTHREAD_H_ +#define DOM_MEDIA_WEBRTC_LIBWEBRTCGLUE_CALLWORKERTHREAD_H_ + +#include "mozilla/AbstractThread.h" +#include "nsIDirectTaskDispatcher.h" +#include "TaskQueueWrapper.h" + +namespace mozilla { + +// Implements AbstractThread for running things on the webrtc TaskQueue. +// Webrtc TaskQueues are not refcounted so cannot implement AbstractThread +// directly. +class CallWorkerThread final : public AbstractThread, + public nsIDirectTaskDispatcher { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDIRECTTASKDISPATCHER + + explicit CallWorkerThread( + UniquePtr> aWebrtcTaskQueue) + : AbstractThread(aWebrtcTaskQueue->mTaskQueue->SupportsTailDispatch()), + mWebrtcTaskQueue(std::move(aWebrtcTaskQueue)) {} + + // AbstractThread overrides + nsresult Dispatch(already_AddRefed aRunnable, + DispatchReason aReason) override; + bool IsCurrentThreadIn() const override; + TaskDispatcher& TailDispatcher() override; + nsIEventTarget* AsEventTarget() override; + NS_IMETHOD + DelayedDispatch(already_AddRefed aEvent, + uint32_t aDelayMs) override; + + NS_IMETHOD RegisterShutdownTask(nsITargetShutdownTask* aTask) override; + NS_IMETHOD UnregisterShutdownTask(nsITargetShutdownTask* aTask) override; + + const UniquePtr> + mWebrtcTaskQueue; + + protected: + ~CallWorkerThread() = default; +}; + +NS_IMPL_ISUPPORTS(CallWorkerThread, nsIDirectTaskDispatcher, + nsISerialEventTarget, nsIEventTarget); + +//----------------------------------------------------------------------------- +// AbstractThread +//----------------------------------------------------------------------------- + +nsresult CallWorkerThread::Dispatch(already_AddRefed aRunnable, + DispatchReason aReason) { + RefPtr runnable = aRunnable; + return mWebrtcTaskQueue->mTaskQueue->Dispatch( + mWebrtcTaskQueue->CreateTaskRunner(std::move(runnable)), aReason); +} + +bool CallWorkerThread::IsCurrentThreadIn() const { + return mWebrtcTaskQueue->mTaskQueue->IsOnCurrentThreadInfallible() && + mWebrtcTaskQueue->IsCurrent(); +} + +TaskDispatcher& CallWorkerThread::TailDispatcher() { + return mWebrtcTaskQueue->mTaskQueue->TailDispatcher(); +} + +nsIEventTarget* CallWorkerThread::AsEventTarget() { + return mWebrtcTaskQueue->mTaskQueue->AsEventTarget(); +} + +NS_IMETHODIMP +CallWorkerThread::DelayedDispatch(already_AddRefed aEvent, + uint32_t aDelayMs) { + RefPtr event = aEvent; + return mWebrtcTaskQueue->mTaskQueue->DelayedDispatch( + mWebrtcTaskQueue->CreateTaskRunner(std::move(event)), aDelayMs); +} + +NS_IMETHODIMP CallWorkerThread::RegisterShutdownTask( + nsITargetShutdownTask* aTask) { + return mWebrtcTaskQueue->mTaskQueue->RegisterShutdownTask(aTask); +} + +NS_IMETHODIMP CallWorkerThread::UnregisterShutdownTask( + nsITargetShutdownTask* aTask) { + return mWebrtcTaskQueue->mTaskQueue->UnregisterShutdownTask(aTask); +} + +//----------------------------------------------------------------------------- +// nsIDirectTaskDispatcher +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +CallWorkerThread::DispatchDirectTask(already_AddRefed aEvent) { + nsCOMPtr event = aEvent; + return mWebrtcTaskQueue->mTaskQueue->DispatchDirectTask( + mWebrtcTaskQueue->CreateTaskRunner(std::move(event))); +} + +NS_IMETHODIMP CallWorkerThread::DrainDirectTasks() { + return mWebrtcTaskQueue->mTaskQueue->DrainDirectTasks(); +} + +NS_IMETHODIMP CallWorkerThread::HaveDirectTasks(bool* aValue) { + return mWebrtcTaskQueue->mTaskQueue->HaveDirectTasks(aValue); +} + +} // namespace mozilla + +#endif diff --git a/dom/media/webrtc/libwebrtcglue/CodecConfig.h b/dom/media/webrtc/libwebrtcglue/CodecConfig.h new file mode 100644 index 0000000000..023ea98783 --- /dev/null +++ b/dom/media/webrtc/libwebrtcglue/CodecConfig.h @@ -0,0 +1,237 @@ + +/* 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/. */ + +#ifndef CODEC_CONFIG_H_ +#define CODEC_CONFIG_H_ + +#include +#include + +#include "common/EncodingConstraints.h" + +namespace mozilla { + +/** + * Minimalistic Audio Codec Config Params + */ +struct AudioCodecConfig { + /* + * The data-types for these properties mimic the + * corresponding webrtc::CodecInst data-types. + */ + int mType; + std::string mName; + int mFreq; + int mChannels; + + bool mFECEnabled; + bool mDtmfEnabled; + uint32_t mFrameSizeMs; + uint32_t mMaxFrameSizeMs; + uint32_t mMinFrameSizeMs; + + // OPUS-specific + bool mDTXEnabled; + uint32_t mMaxAverageBitrate; + int mMaxPlaybackRate; + bool mCbrEnabled; + + AudioCodecConfig(int type, std::string name, int freq, int channels, + bool FECEnabled) + : mType(type), + mName(name), + mFreq(freq), + mChannels(channels), + mFECEnabled(FECEnabled), + mDtmfEnabled(false), + mFrameSizeMs(0), + mMaxFrameSizeMs(0), + mMinFrameSizeMs(0), + mDTXEnabled(false), + mMaxAverageBitrate(0), + mMaxPlaybackRate(0), + mCbrEnabled(false) {} + + bool operator==(const AudioCodecConfig& aOther) const { + return mType == aOther.mType && mName == aOther.mName && + mFreq == aOther.mFreq && mChannels == aOther.mChannels && + mFECEnabled == aOther.mFECEnabled && + mDtmfEnabled == aOther.mDtmfEnabled && + mFrameSizeMs == aOther.mFrameSizeMs && + mMaxFrameSizeMs == aOther.mMaxFrameSizeMs && + mMinFrameSizeMs == aOther.mMinFrameSizeMs && + mDTXEnabled == aOther.mDTXEnabled && + mMaxAverageBitrate == aOther.mMaxAverageBitrate && + mMaxPlaybackRate == aOther.mMaxPlaybackRate && + mCbrEnabled == aOther.mCbrEnabled; + } +}; + +/* + * Minimalistic video codec configuration + * More to be added later depending on the use-case + */ + +#define MAX_SPROP_LEN 128 + +// used for holding SDP negotiation results +struct VideoCodecConfigH264 { + char sprop_parameter_sets[MAX_SPROP_LEN]; + int packetization_mode; + int profile_level_id; + int tias_bw; + + bool operator==(const VideoCodecConfigH264& aOther) const { + return strncmp(sprop_parameter_sets, aOther.sprop_parameter_sets, + MAX_SPROP_LEN) == 0 && + packetization_mode == aOther.packetization_mode && + profile_level_id == aOther.profile_level_id && + tias_bw == aOther.tias_bw; + } +}; + +// class so the std::strings can get freed more easily/reliably +class VideoCodecConfig { + public: + /* + * The data-types for these properties mimic the + * corresponding webrtc::VideoCodec data-types. + */ + int mType; // payload type + std::string mName; + + std::vector mAckFbTypes; + std::vector mNackFbTypes; + std::vector mCcmFbTypes; + // Don't pass mOtherFbTypes from JsepVideoCodecDescription because we'd have + // to drag SdpRtcpFbAttributeList::Feedback along too. + bool mRembFbSet; + bool mFECFbSet; + bool mTransportCCFbSet; + + int mULPFECPayloadType; + int mREDPayloadType; + int mREDRTXPayloadType; + int mRTXPayloadType; + + uint32_t mTias; + EncodingConstraints mEncodingConstraints; + struct Encoding { + std::string rid; + EncodingConstraints constraints; + bool active = true; + // TODO(bug 1744116): Use = default here + bool operator==(const Encoding& aOther) const { + return rid == aOther.rid && constraints == aOther.constraints && + active == aOther.active; + } + }; + std::vector mEncodings; + std::string mSpropParameterSets; + uint8_t mProfile; + uint8_t mConstraints; + uint8_t mLevel; + uint8_t mPacketizationMode; + // TODO: add external negotiated SPS/PPS + + // TODO(bug 1744116): Use = default here + bool operator==(const VideoCodecConfig& aRhs) const { + return mType == aRhs.mType && mName == aRhs.mName && + mAckFbTypes == aRhs.mAckFbTypes && + mNackFbTypes == aRhs.mNackFbTypes && + mCcmFbTypes == aRhs.mCcmFbTypes && mRembFbSet == aRhs.mRembFbSet && + mFECFbSet == aRhs.mFECFbSet && + mTransportCCFbSet == aRhs.mTransportCCFbSet && + mULPFECPayloadType == aRhs.mULPFECPayloadType && + mREDPayloadType == aRhs.mREDPayloadType && + mREDRTXPayloadType == aRhs.mREDRTXPayloadType && + mRTXPayloadType == aRhs.mRTXPayloadType && mTias == aRhs.mTias && + mEncodingConstraints == aRhs.mEncodingConstraints && + mEncodings == aRhs.mEncodings && + mSpropParameterSets == aRhs.mSpropParameterSets && + mProfile == aRhs.mProfile && mConstraints == aRhs.mConstraints && + mLevel == aRhs.mLevel && + mPacketizationMode == aRhs.mPacketizationMode; + } + + VideoCodecConfig(int type, std::string name, + const EncodingConstraints& constraints, + const struct VideoCodecConfigH264* h264 = nullptr) + : mType(type), + mName(name), + mRembFbSet(false), + mFECFbSet(false), + mTransportCCFbSet(false), + mULPFECPayloadType(-1), + mREDPayloadType(-1), + mREDRTXPayloadType(-1), + mRTXPayloadType(-1), + mTias(0), + mEncodingConstraints(constraints), + mProfile(0x42), + mConstraints(0xE0), + mLevel(0x0C), + mPacketizationMode(1) { + if (h264) { + mProfile = (h264->profile_level_id & 0x00FF0000) >> 16; + mConstraints = (h264->profile_level_id & 0x0000FF00) >> 8; + mLevel = (h264->profile_level_id & 0x000000FF); + mPacketizationMode = h264->packetization_mode; + mSpropParameterSets = h264->sprop_parameter_sets; + } + } + + bool ResolutionEquals(const VideoCodecConfig& aConfig) const { + if (mEncodings.size() != aConfig.mEncodings.size()) { + return false; + } + for (size_t i = 0; i < mEncodings.size(); ++i) { + if (!mEncodings[i].constraints.ResolutionEquals( + aConfig.mEncodings[i].constraints)) { + return false; + } + } + return true; + } + + // Nothing seems to use this right now. Do we intend to support this + // someday? + bool RtcpFbAckIsSet(const std::string& type) const { + for (auto i = mAckFbTypes.begin(); i != mAckFbTypes.end(); ++i) { + if (*i == type) { + return true; + } + } + return false; + } + + bool RtcpFbNackIsSet(const std::string& type) const { + for (auto i = mNackFbTypes.begin(); i != mNackFbTypes.end(); ++i) { + if (*i == type) { + return true; + } + } + return false; + } + + bool RtcpFbCcmIsSet(const std::string& type) const { + for (auto i = mCcmFbTypes.begin(); i != mCcmFbTypes.end(); ++i) { + if (*i == type) { + return true; + } + } + return false; + } + + bool RtcpFbRembIsSet() const { return mRembFbSet; } + + bool RtcpFbFECIsSet() const { return mFECFbSet; } + + bool RtcpFbTransportCCIsSet() const { return mTransportCCFbSet; } + + bool RtxPayloadTypeIsSet() const { return mRTXPayloadType != -1; } +}; +} // namespace mozilla +#endif diff --git a/dom/media/webrtc/libwebrtcglue/GmpVideoCodec.cpp b/dom/media/webrtc/libwebrtcglue/GmpVideoCodec.cpp new file mode 100644 index 0000000000..ccadd846e2 --- /dev/null +++ b/dom/media/webrtc/libwebrtcglue/GmpVideoCodec.cpp @@ -0,0 +1,22 @@ +/* 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 "WebrtcGmpVideoCodec.h" +#include "GmpVideoCodec.h" + +namespace mozilla { + +WebrtcVideoEncoder* GmpVideoCodec::CreateEncoder( + const webrtc::SdpVideoFormat& aFormat, std::string aPCHandle) { + return new WebrtcVideoEncoderProxy( + new WebrtcGmpVideoEncoder(aFormat, std::move(aPCHandle))); +} + +WebrtcVideoDecoder* GmpVideoCodec::CreateDecoder(std::string aPCHandle, + TrackingId aTrackingId) { + return new WebrtcVideoDecoderProxy(std::move(aPCHandle), + std::move(aTrackingId)); +} + +} // namespace mozilla diff --git a/dom/media/webrtc/libwebrtcglue/GmpVideoCodec.h b/dom/media/webrtc/libwebrtcglue/GmpVideoCodec.h new file mode 100644 index 0000000000..caf125c809 --- /dev/null +++ b/dom/media/webrtc/libwebrtcglue/GmpVideoCodec.h @@ -0,0 +1,27 @@ +/* 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/. */ + +#ifndef GMPVIDEOCODEC_H_ +#define GMPVIDEOCODEC_H_ + +#include + +#include "PerformanceRecorder.h" + +namespace mozilla { + +class WebrtcVideoDecoder; +class WebrtcVideoEncoder; + +class GmpVideoCodec { + public: + static WebrtcVideoEncoder* CreateEncoder( + const webrtc::SdpVideoFormat& aFormat, std::string aPCHandle); + static WebrtcVideoDecoder* CreateDecoder(std::string aPCHandle, + TrackingId aTrackingId); +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/webrtc/libwebrtcglue/MediaConduitControl.h b/dom/media/webrtc/libwebrtcglue/MediaConduitControl.h new file mode 100644 index 0000000000..a860fab146 --- /dev/null +++ b/dom/media/webrtc/libwebrtcglue/MediaConduitControl.h @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_MEDIA_WEBRTC_LIBWEBRTCGLUE_MEDIACONDUITCONTROL_H_ +#define DOM_MEDIA_WEBRTC_LIBWEBRTCGLUE_MEDIACONDUITCONTROL_H_ + +#include "jsapi/RTCDTMFSender.h" // For DtmfEvent +#include "mozilla/StateMirroring.h" +#include "RtpRtcpConfig.h" +#include +#include +#include "mozilla/Maybe.h" +#include "CodecConfig.h" // For Audio/VideoCodecConfig +#include "api/rtp_parameters.h" // For webrtc::RtpExtension +#include "api/video_codecs/video_codec.h" // For webrtc::VideoCodecMode + +namespace mozilla { + +using RtpExtList = std::vector; +using Ssrc = uint32_t; +using Ssrcs = std::vector; + +/** + * These are the interfaces used to control the async conduits. Some parameters + * are common, and some are tied to the conduit type. See + * MediaSessionConduit::InitConduitControl for how they are used. + * + * Put simply, the implementer of the interfaces below may set its canonicals on + * any thread, and the conduits will react to those changes accordingly, on + * their dedicated worker thread. One instance of these interfaces could control + * multiple conduits as each canonical can connect to any number of mirrors. + */ + +class MediaConduitControlInterface { + public: + virtual AbstractCanonical* CanonicalReceiving() = 0; + virtual AbstractCanonical* CanonicalTransmitting() = 0; + virtual AbstractCanonical* CanonicalLocalSsrcs() = 0; + virtual AbstractCanonical* CanonicalLocalCname() = 0; + virtual AbstractCanonical* CanonicalMid() = 0; + virtual AbstractCanonical* CanonicalRemoteSsrc() = 0; + virtual AbstractCanonical* CanonicalSyncGroup() = 0; + virtual AbstractCanonical* CanonicalLocalRecvRtpExtensions() = 0; + virtual AbstractCanonical* CanonicalLocalSendRtpExtensions() = 0; +}; + +class AudioConduitControlInterface : public MediaConduitControlInterface { + public: + virtual AbstractCanonical>* + CanonicalAudioSendCodec() = 0; + virtual AbstractCanonical>* + CanonicalAudioRecvCodecs() = 0; + virtual MediaEventSource& OnDtmfEvent() = 0; +}; + +class VideoConduitControlInterface : public MediaConduitControlInterface { + public: + virtual AbstractCanonical* CanonicalLocalVideoRtxSsrcs() = 0; + virtual AbstractCanonical* CanonicalRemoteVideoRtxSsrc() = 0; + virtual AbstractCanonical>* + CanonicalVideoSendCodec() = 0; + virtual AbstractCanonical>* + CanonicalVideoSendRtpRtcpConfig() = 0; + virtual AbstractCanonical>* + CanonicalVideoRecvCodecs() = 0; + virtual AbstractCanonical>* + CanonicalVideoRecvRtpRtcpConfig() = 0; + virtual AbstractCanonical* + CanonicalVideoCodecMode() = 0; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/webrtc/libwebrtcglue/MediaConduitErrors.h b/dom/media/webrtc/libwebrtcglue/MediaConduitErrors.h new file mode 100644 index 0000000000..34487d77a0 --- /dev/null +++ b/dom/media/webrtc/libwebrtcglue/MediaConduitErrors.h @@ -0,0 +1,46 @@ +/* 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/. */ + +#ifndef MEDIA_SESSION_ERRORS_H_ +#define MEDIA_SESSION_ERRORS_H_ + +namespace mozilla { +enum MediaConduitErrorCode { + kMediaConduitNoError = 0, // 0 for Success,greater than 0 imples error + kMediaConduitSessionNotInited = + 10100, // Session not initialized.10100 serves as + // base for the conduit errors + kMediaConduitMalformedArgument, // Malformed input to Conduit API + kMediaConduitCaptureError, // WebRTC capture APIs failed + kMediaConduitInvalidSendCodec, // Wrong Send codec + kMediaConduitInvalidReceiveCodec, // Wrong Recv Codec + kMediaConduitCodecInUse, // Already applied Codec + kMediaConduitInvalidRenderer, // Null or Wrong Renderer object + kMediaConduitRendererFail, // Add Render called multiple times + kMediaConduitSendingAlready, // Engine already trasmitting + kMediaConduitReceivingAlready, // Engine already receiving + kMediaConduitTransportRegistrationFail, // Null or wrong transport interface + kMediaConduitInvalidTransport, // Null or wrong transport interface + kMediaConduitChannelError, // Configuration Error + kMediaConduitSocketError, // Media Engine transport socket error + kMediaConduitRTPRTCPModuleError, // Couldn't start RTP/RTCP processing + kMediaConduitRTPProcessingFailed, // Processing incoming RTP frame failed + kMediaConduitUnknownError, // More information can be found in logs + kMediaConduitExternalRecordingError, // Couldn't start external recording + kMediaConduitRecordingError, // Runtime recording error + kMediaConduitExternalPlayoutError, // Couldn't start external playout + kMediaConduitPlayoutError, // Runtime playout error + kMediaConduitMTUError, // Can't set MTU + kMediaConduitRTCPStatusError, // Can't set RTCP mode + kMediaConduitKeyFrameRequestError, // Can't set KeyFrameRequest mode + kMediaConduitNACKStatusError, // Can't set NACK mode + kMediaConduitTMMBRStatusError, // Can't set TMMBR mode + kMediaConduitFECStatusError, // Can't set FEC mode + kMediaConduitHybridNACKFECStatusError, // Can't set Hybrid NACK / FEC mode + kMediaConduitVideoSendStreamError // WebRTC video send stream failure +}; + +} + +#endif diff --git a/dom/media/webrtc/libwebrtcglue/MediaConduitInterface.cpp b/dom/media/webrtc/libwebrtcglue/MediaConduitInterface.cpp new file mode 100644 index 0000000000..28079b2478 --- /dev/null +++ b/dom/media/webrtc/libwebrtcglue/MediaConduitInterface.cpp @@ -0,0 +1,151 @@ +/* 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 "MediaConduitInterface.h" + +#include "nsTArray.h" +#include "mozilla/Assertions.h" +#include "MainThreadUtils.h" +#include "SystemTime.h" + +#include "system_wrappers/include/clock.h" + +namespace mozilla { + +void MediaSessionConduit::GetRtpSources( + nsTArray& outSources) const { + MOZ_ASSERT(NS_IsMainThread()); + if (mSourcesUpdateNeeded) { + UpdateRtpSources(GetUpstreamRtpSources()); + OnSourcesUpdated(); + } + outSources.Clear(); + for (auto& [key, entry] : mSourcesCache) { + (void)key; + outSources.AppendElement(entry); + } + + struct TimestampComparator { + bool LessThan(const dom::RTCRtpSourceEntry& aLhs, + const dom::RTCRtpSourceEntry& aRhs) const { + // Sort descending! + return aLhs.mTimestamp > aRhs.mTimestamp; + } + + bool Equals(const dom::RTCRtpSourceEntry& aLhs, + const dom::RTCRtpSourceEntry& aRhs) const { + return aLhs.mTimestamp == aRhs.mTimestamp; + } + }; + + // *sigh* We have to re-sort this by JS timestamp; we can run into cases + // where the libwebrtc timestamps are not in exactly the same order as JS + // timestamps due to clock differences (wibbly-wobbly, timey-wimey stuff) + outSources.Sort(TimestampComparator()); +} + +static double rtpToDomAudioLevel(uint8_t aAudioLevel) { + if (aAudioLevel == 127) { + // Spec indicates that a value of 127 should be set to 0 + return 0; + } + + // All other values are calculated as 10^(-rfc_level/20) + return std::pow(10, -aAudioLevel / 20.0); +} + +void MediaSessionConduit::UpdateRtpSources( + const std::vector& aSources) const { + MOZ_ASSERT(NS_IsMainThread()); + // Empty out the cache; we'll copy things back as needed + auto cache = std::move(mSourcesCache); + + for (const auto& source : aSources) { + SourceKey key(source); + auto it = cache.find(key); + if (it != cache.end()) { + // This source entry was already in the cache, and should continue to be + // present in exactly the same form as before. This means we do _not_ + // want to perform the timestamp adjustment again, since it might yield a + // slightly different result. This is why we copy this entry from the old + // cache instead of simply rebuilding it, and is also why we key the + // cache based on timestamp (keying the cache based on timestamp also + // gets us the ordering we want, conveniently). + mSourcesCache[key] = it->second; + continue; + } + + // This is something we did not already have in the cache. + dom::RTCRtpSourceEntry domEntry; + domEntry.mSource = source.source_id(); + switch (source.source_type()) { + case webrtc::RtpSourceType::SSRC: + domEntry.mSourceType = dom::RTCRtpSourceEntryType::Synchronization; + break; + case webrtc::RtpSourceType::CSRC: + domEntry.mSourceType = dom::RTCRtpSourceEntryType::Contributing; + break; + default: + MOZ_CRASH("Unexpected RTCRtpSourceEntryType"); + } + + if (source.audio_level()) { + domEntry.mAudioLevel.Construct(rtpToDomAudioLevel(*source.audio_level())); + } + + // These timestamps are always **rounded** to milliseconds. That means they + // can jump up to half a millisecond into the future. We compensate for that + // here so that things seem consistent to js. + domEntry.mTimestamp = dom::RTCStatsTimestamp::FromRealtime( + GetTimestampMaker(), + webrtc::Timestamp::Millis(source.timestamp_ms()) - + webrtc::TimeDelta::Micros(500)) + .ToDom(); + domEntry.mRtpTimestamp = source.rtp_timestamp(); + mSourcesCache[key] = domEntry; + } +} + +void MediaSessionConduit::OnSourcesUpdated() const { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mSourcesUpdateNeeded); + mSourcesUpdateNeeded = false; + // Reset the updateNeeded flag and clear the cache in a direct task, i.e., + // as soon as the current task has finished. + AbstractThread::GetCurrent()->TailDispatcher().AddDirectTask( + NS_NewRunnableFunction( + __func__, [this, self = RefPtr(this)] { + mSourcesUpdateNeeded = true; + mSourcesCache.clear(); + })); +} + +void MediaSessionConduit::InsertAudioLevelForContributingSource( + const uint32_t aCsrcSource, const int64_t aTimestamp, + const uint32_t aRtpTimestamp, const bool aHasAudioLevel, + const uint8_t aAudioLevel) { + MOZ_ASSERT(NS_IsMainThread()); + + if (mSourcesUpdateNeeded) { + OnSourcesUpdated(); + } + + dom::RTCRtpSourceEntry domEntry; + domEntry.mSource = aCsrcSource; + domEntry.mSourceType = dom::RTCRtpSourceEntryType::Contributing; + domEntry.mTimestamp = aTimestamp; + domEntry.mRtpTimestamp = aRtpTimestamp; + if (aHasAudioLevel) { + domEntry.mAudioLevel.Construct(rtpToDomAudioLevel(aAudioLevel)); + } + + auto now = GetTimestampMaker().GetNow(); + webrtc::Timestamp convertedTimestamp = + now.ToRealtime() - webrtc::TimeDelta::Millis(now.ToDom() - aTimestamp); + + SourceKey key(convertedTimestamp.ms(), aCsrcSource); + mSourcesCache[key] = domEntry; +} + +} // namespace mozilla diff --git a/dom/media/webrtc/libwebrtcglue/MediaConduitInterface.h b/dom/media/webrtc/libwebrtcglue/MediaConduitInterface.h new file mode 100644 index 0000000000..86e148dc5c --- /dev/null +++ b/dom/media/webrtc/libwebrtcglue/MediaConduitInterface.h @@ -0,0 +1,495 @@ +/* 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/. */ + +#ifndef MEDIA_CONDUIT_ABSTRACTION_ +#define MEDIA_CONDUIT_ABSTRACTION_ + +#include +#include +#include + +#include "CodecConfig.h" +#include "ImageContainer.h" +#include "jsapi/RTCStatsReport.h" +#include "MediaConduitErrors.h" +#include "mozilla/media/MediaUtils.h" +#include "mozilla/MozPromise.h" +#include "WebrtcVideoCodecFactory.h" +#include "nsTArray.h" +#include "mozilla/dom/RTCRtpSourcesBinding.h" +#include "PerformanceRecorder.h" +#include "transport/mediapacket.h" +#include "MediaConduitControl.h" + +// libwebrtc includes +#include "api/audio/audio_frame.h" +#include "api/call/transport.h" +#include "api/rtp_headers.h" +#include "api/rtp_parameters.h" +#include "api/transport/rtp/rtp_source.h" +#include "api/video/video_frame_buffer.h" +#include "call/audio_receive_stream.h" +#include "call/audio_send_stream.h" +#include "call/call_basic_stats.h" +#include "call/video_receive_stream.h" +#include "call/video_send_stream.h" +#include "rtc_base/copy_on_write_buffer.h" + +namespace webrtc { +class RtpPacketReceived; +class VideoFrame; +} // namespace webrtc + +namespace mozilla { +namespace dom { +struct RTCRtpSourceEntry; +} + +namespace dom { +struct RTCRtpSourceEntry; +} + +enum class MediaSessionConduitLocalDirection : int { kSend, kRecv }; + +class VideoSessionConduit; +class AudioSessionConduit; +class WebrtcCallWrapper; + +/** + * 1. Abstract renderer for video data + * 2. This class acts as abstract interface between the video-engine and + * video-engine agnostic renderer implementation. + * 3. Concrete implementation of this interface is responsible for + * processing and/or rendering the obtained raw video frame to appropriate + * output , say,