From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- media/webrtc/signaling/signaling.gyp | 379 ++ media/webrtc/signaling/src/common/CommonTypes.h | 63 + .../signaling/src/common/EncodingConstraints.h | 58 + .../signaling/src/common/MediaEngineWrapper.h | 39 + media/webrtc/signaling/src/common/NullDeleter.h | 16 + media/webrtc/signaling/src/common/NullTransport.h | 44 + media/webrtc/signaling/src/common/PtrVector.h | 43 + media/webrtc/signaling/src/common/Wrapper.h | 175 + media/webrtc/signaling/src/common/YuvStamper.cpp | 469 ++ media/webrtc/signaling/src/common/YuvStamper.h | 83 + .../src/common/browser_logging/CSFLog.cpp | 103 + .../signaling/src/common/browser_logging/CSFLog.h | 50 + .../src/common/browser_logging/WebRtcLog.cpp | 258 + .../src/common/browser_logging/WebRtcLog.h | 16 + media/webrtc/signaling/src/common/csf_common.h | 79 + .../signaling/src/common/time_profiling/timecard.c | 125 + .../signaling/src/common/time_profiling/timecard.h | 81 + .../signaling/src/jsep/JsepCodecDescription.h | 780 +++ media/webrtc/signaling/src/jsep/JsepSession.h | 243 + .../webrtc/signaling/src/jsep/JsepSessionImpl.cpp | 2497 ++++++++ media/webrtc/signaling/src/jsep/JsepSessionImpl.h | 352 ++ media/webrtc/signaling/src/jsep/JsepTrack.cpp | 531 ++ media/webrtc/signaling/src/jsep/JsepTrack.h | 292 + .../webrtc/signaling/src/jsep/JsepTrackEncoding.h | 60 + media/webrtc/signaling/src/jsep/JsepTransport.h | 116 + .../signaling/src/media-conduit/AudioConduit.cpp | 1134 ++++ .../signaling/src/media-conduit/AudioConduit.h | 304 + .../signaling/src/media-conduit/CodecConfig.h | 166 + .../src/media-conduit/CodecStatistics.cpp | 183 + .../signaling/src/media-conduit/CodecStatistics.h | 111 + .../signaling/src/media-conduit/GmpVideoCodec.cpp | 18 + .../signaling/src/media-conduit/GmpVideoCodec.h | 19 + .../src/media-conduit/MediaCodecVideoCodec.cpp | 31 + .../src/media-conduit/MediaCodecVideoCodec.h | 31 + .../src/media-conduit/MediaConduitErrors.h | 48 + .../src/media-conduit/MediaConduitInterface.h | 495 ++ .../signaling/src/media-conduit/OMXVideoCodec.cpp | 30 + .../signaling/src/media-conduit/OMXVideoCodec.h | 32 + .../signaling/src/media-conduit/RunningStat.h | 66 + .../signaling/src/media-conduit/VideoConduit.cpp | 2129 +++++++ .../signaling/src/media-conduit/VideoConduit.h | 429 ++ .../signaling/src/media-conduit/VideoTypes.h | 62 + .../src/media-conduit/WebrtcGmpVideoCodec.cpp | 965 +++ .../src/media-conduit/WebrtcGmpVideoCodec.h | 528 ++ .../WebrtcMediaCodecVP8VideoCodec.cpp | 1004 +++ .../media-conduit/WebrtcMediaCodecVP8VideoCodec.h | 114 + .../src/media-conduit/WebrtcOMXH264VideoCodec.cpp | 1253 ++++ .../src/media-conduit/WebrtcOMXH264VideoCodec.h | 108 + .../signaling/src/media/CSFAudioControlWrapper.cpp | 149 + .../signaling/src/media/CSFAudioControlWrapper.h | 42 + .../signaling/src/media/CSFAudioTermination.h | 117 + .../webrtc/signaling/src/media/CSFMediaProvider.h | 54 + .../signaling/src/media/CSFMediaTermination.h | 55 + .../signaling/src/media/CSFToneDefinitions.h | 137 + .../signaling/src/media/CSFVideoCallMediaControl.h | 28 + .../signaling/src/media/CSFVideoControlWrapper.h | 48 + .../signaling/src/media/CSFVideoTermination.h | 36 + .../src/media/cip_mmgr_mediadefinitions.h | 125 + .../signaling/src/mediapipeline/MediaPipeline.cpp | 2377 ++++++++ .../signaling/src/mediapipeline/MediaPipeline.h | 479 ++ .../src/mediapipeline/MediaPipelineFilter.cpp | 97 + .../src/mediapipeline/MediaPipelineFilter.h | 86 + .../signaling/src/mediapipeline/SrtpFlow.cpp | 251 + .../webrtc/signaling/src/mediapipeline/SrtpFlow.h | 68 + .../src/peerconnection/MediaPipelineFactory.cpp | 1076 ++++ .../src/peerconnection/MediaPipelineFactory.h | 82 + .../src/peerconnection/MediaStreamList.cpp | 104 + .../signaling/src/peerconnection/MediaStreamList.h | 54 + .../src/peerconnection/PeerConnectionCtx.cpp | 452 ++ .../src/peerconnection/PeerConnectionCtx.h | 109 + .../src/peerconnection/PeerConnectionImpl.cpp | 4176 +++++++++++++ .../src/peerconnection/PeerConnectionImpl.h | 894 +++ .../src/peerconnection/PeerConnectionMedia.cpp | 1672 +++++ .../src/peerconnection/PeerConnectionMedia.h | 586 ++ .../src/peerconnection/WebrtcGlobalChild.h | 40 + .../src/peerconnection/WebrtcGlobalInformation.cpp | 1241 ++++ .../src/peerconnection/WebrtcGlobalInformation.h | 56 + .../src/peerconnection/WebrtcGlobalParent.h | 53 + media/webrtc/signaling/src/sdp/Sdp.h | 195 + media/webrtc/signaling/src/sdp/SdpAttribute.cpp | 1674 +++++ media/webrtc/signaling/src/sdp/SdpAttribute.h | 1788 ++++++ media/webrtc/signaling/src/sdp/SdpAttributeList.h | 94 + media/webrtc/signaling/src/sdp/SdpEnum.h | 70 + media/webrtc/signaling/src/sdp/SdpErrorHolder.h | 50 + media/webrtc/signaling/src/sdp/SdpHelper.cpp | 811 +++ media/webrtc/signaling/src/sdp/SdpHelper.h | 131 + media/webrtc/signaling/src/sdp/SdpMediaSection.cpp | 196 + media/webrtc/signaling/src/sdp/SdpMediaSection.h | 361 ++ media/webrtc/signaling/src/sdp/SipccSdp.cpp | 180 + media/webrtc/signaling/src/sdp/SipccSdp.h | 88 + .../signaling/src/sdp/SipccSdpAttributeList.cpp | 1413 +++++ .../signaling/src/sdp/SipccSdpAttributeList.h | 147 + .../signaling/src/sdp/SipccSdpMediaSection.cpp | 423 ++ .../signaling/src/sdp/SipccSdpMediaSection.h | 102 + media/webrtc/signaling/src/sdp/SipccSdpParser.cpp | 83 + media/webrtc/signaling/src/sdp/SipccSdpParser.h | 35 + media/webrtc/signaling/src/sdp/sipcc/ccsdp.h | 207 + .../webrtc/signaling/src/sdp/sipcc/ccsdp_rtcp_fb.h | 63 + .../signaling/src/sdp/sipcc/cpr_darwin_types.h | 68 + .../signaling/src/sdp/sipcc/cpr_linux_types.h | 82 + media/webrtc/signaling/src/sdp/sipcc/cpr_string.c | 272 + media/webrtc/signaling/src/sdp/sipcc/cpr_string.h | 139 + media/webrtc/signaling/src/sdp/sipcc/cpr_strings.h | 22 + media/webrtc/signaling/src/sdp/sipcc/cpr_types.h | 126 + .../webrtc/signaling/src/sdp/sipcc/cpr_win_types.h | 71 + media/webrtc/signaling/src/sdp/sipcc/sdp.h | 1794 ++++++ media/webrtc/signaling/src/sdp/sipcc/sdp_access.c | 2083 +++++++ media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c | 5120 ++++++++++++++++ .../signaling/src/sdp/sipcc/sdp_attr_access.c | 6372 ++++++++++++++++++++ media/webrtc/signaling/src/sdp/sipcc/sdp_base64.c | 403 ++ media/webrtc/signaling/src/sdp/sipcc/sdp_base64.h | 42 + media/webrtc/signaling/src/sdp/sipcc/sdp_config.c | 241 + media/webrtc/signaling/src/sdp/sipcc/sdp_main.c | 1342 +++++ media/webrtc/signaling/src/sdp/sipcc/sdp_os_defs.h | 27 + media/webrtc/signaling/src/sdp/sipcc/sdp_private.h | 364 ++ .../signaling/src/sdp/sipcc/sdp_services_unix.c | 41 + .../signaling/src/sdp/sipcc/sdp_services_win32.c | 40 + media/webrtc/signaling/src/sdp/sipcc/sdp_token.c | 1812 ++++++ media/webrtc/signaling/src/sdp/sipcc/sdp_utils.c | 781 +++ media/webrtc/signaling/test/FakeIPC.cpp | 35 + media/webrtc/signaling/test/FakeIPC.h | 22 + media/webrtc/signaling/test/FakeLogging.h | 26 + media/webrtc/signaling/test/FakeMediaStreams.h | 656 ++ media/webrtc/signaling/test/FakeMediaStreamsImpl.h | 236 + media/webrtc/signaling/test/FakePCObserver.h | 112 + media/webrtc/signaling/test/common.build | 134 + .../signaling/test/jsep_session_unittest.cpp | 4235 +++++++++++++ .../webrtc/signaling/test/jsep_track_unittest.cpp | 1269 ++++ .../signaling/test/mediaconduit_unittests.cpp | 1091 ++++ .../signaling/test/mediapipeline_unittest.cpp | 720 +++ media/webrtc/signaling/test/moz.build | 33 + media/webrtc/signaling/test/sdp_file_parser.cpp | 85 + media/webrtc/signaling/test/sdp_unittests.cpp | 5377 +++++++++++++++++ .../webrtc/signaling/test/signaling_unittests.cpp | 4851 +++++++++++++++ 134 files changed, 79191 insertions(+) create mode 100644 media/webrtc/signaling/signaling.gyp create mode 100644 media/webrtc/signaling/src/common/CommonTypes.h create mode 100644 media/webrtc/signaling/src/common/EncodingConstraints.h create mode 100755 media/webrtc/signaling/src/common/MediaEngineWrapper.h create mode 100644 media/webrtc/signaling/src/common/NullDeleter.h create mode 100644 media/webrtc/signaling/src/common/NullTransport.h create mode 100644 media/webrtc/signaling/src/common/PtrVector.h create mode 100644 media/webrtc/signaling/src/common/Wrapper.h create mode 100644 media/webrtc/signaling/src/common/YuvStamper.cpp create mode 100644 media/webrtc/signaling/src/common/YuvStamper.h create mode 100644 media/webrtc/signaling/src/common/browser_logging/CSFLog.cpp create mode 100644 media/webrtc/signaling/src/common/browser_logging/CSFLog.h create mode 100644 media/webrtc/signaling/src/common/browser_logging/WebRtcLog.cpp create mode 100644 media/webrtc/signaling/src/common/browser_logging/WebRtcLog.h create mode 100644 media/webrtc/signaling/src/common/csf_common.h create mode 100644 media/webrtc/signaling/src/common/time_profiling/timecard.c create mode 100644 media/webrtc/signaling/src/common/time_profiling/timecard.h create mode 100644 media/webrtc/signaling/src/jsep/JsepCodecDescription.h create mode 100644 media/webrtc/signaling/src/jsep/JsepSession.h create mode 100644 media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp create mode 100644 media/webrtc/signaling/src/jsep/JsepSessionImpl.h create mode 100644 media/webrtc/signaling/src/jsep/JsepTrack.cpp create mode 100644 media/webrtc/signaling/src/jsep/JsepTrack.h create mode 100644 media/webrtc/signaling/src/jsep/JsepTrackEncoding.h create mode 100644 media/webrtc/signaling/src/jsep/JsepTransport.h create mode 100755 media/webrtc/signaling/src/media-conduit/AudioConduit.cpp create mode 100755 media/webrtc/signaling/src/media-conduit/AudioConduit.h create mode 100755 media/webrtc/signaling/src/media-conduit/CodecConfig.h create mode 100644 media/webrtc/signaling/src/media-conduit/CodecStatistics.cpp create mode 100644 media/webrtc/signaling/src/media-conduit/CodecStatistics.h create mode 100644 media/webrtc/signaling/src/media-conduit/GmpVideoCodec.cpp create mode 100644 media/webrtc/signaling/src/media-conduit/GmpVideoCodec.h create mode 100644 media/webrtc/signaling/src/media-conduit/MediaCodecVideoCodec.cpp create mode 100644 media/webrtc/signaling/src/media-conduit/MediaCodecVideoCodec.h create mode 100755 media/webrtc/signaling/src/media-conduit/MediaConduitErrors.h create mode 100755 media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h create mode 100644 media/webrtc/signaling/src/media-conduit/OMXVideoCodec.cpp create mode 100644 media/webrtc/signaling/src/media-conduit/OMXVideoCodec.h create mode 100644 media/webrtc/signaling/src/media-conduit/RunningStat.h create mode 100755 media/webrtc/signaling/src/media-conduit/VideoConduit.cpp create mode 100755 media/webrtc/signaling/src/media-conduit/VideoConduit.h create mode 100755 media/webrtc/signaling/src/media-conduit/VideoTypes.h create mode 100644 media/webrtc/signaling/src/media-conduit/WebrtcGmpVideoCodec.cpp create mode 100644 media/webrtc/signaling/src/media-conduit/WebrtcGmpVideoCodec.h create mode 100644 media/webrtc/signaling/src/media-conduit/WebrtcMediaCodecVP8VideoCodec.cpp create mode 100644 media/webrtc/signaling/src/media-conduit/WebrtcMediaCodecVP8VideoCodec.h create mode 100644 media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.cpp create mode 100644 media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.h create mode 100644 media/webrtc/signaling/src/media/CSFAudioControlWrapper.cpp create mode 100644 media/webrtc/signaling/src/media/CSFAudioControlWrapper.h create mode 100644 media/webrtc/signaling/src/media/CSFAudioTermination.h create mode 100644 media/webrtc/signaling/src/media/CSFMediaProvider.h create mode 100644 media/webrtc/signaling/src/media/CSFMediaTermination.h create mode 100644 media/webrtc/signaling/src/media/CSFToneDefinitions.h create mode 100644 media/webrtc/signaling/src/media/CSFVideoCallMediaControl.h create mode 100644 media/webrtc/signaling/src/media/CSFVideoControlWrapper.h create mode 100644 media/webrtc/signaling/src/media/CSFVideoTermination.h create mode 100644 media/webrtc/signaling/src/media/cip_mmgr_mediadefinitions.h create mode 100644 media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp create mode 100644 media/webrtc/signaling/src/mediapipeline/MediaPipeline.h create mode 100644 media/webrtc/signaling/src/mediapipeline/MediaPipelineFilter.cpp create mode 100644 media/webrtc/signaling/src/mediapipeline/MediaPipelineFilter.h create mode 100644 media/webrtc/signaling/src/mediapipeline/SrtpFlow.cpp create mode 100644 media/webrtc/signaling/src/mediapipeline/SrtpFlow.h create mode 100644 media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.cpp create mode 100644 media/webrtc/signaling/src/peerconnection/MediaPipelineFactory.h create mode 100644 media/webrtc/signaling/src/peerconnection/MediaStreamList.cpp create mode 100644 media/webrtc/signaling/src/peerconnection/MediaStreamList.h create mode 100644 media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp create mode 100644 media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.h create mode 100644 media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp create mode 100644 media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h create mode 100644 media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp create mode 100644 media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h create mode 100644 media/webrtc/signaling/src/peerconnection/WebrtcGlobalChild.h create mode 100644 media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.cpp create mode 100644 media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.h create mode 100644 media/webrtc/signaling/src/peerconnection/WebrtcGlobalParent.h create mode 100644 media/webrtc/signaling/src/sdp/Sdp.h create mode 100644 media/webrtc/signaling/src/sdp/SdpAttribute.cpp create mode 100644 media/webrtc/signaling/src/sdp/SdpAttribute.h create mode 100644 media/webrtc/signaling/src/sdp/SdpAttributeList.h create mode 100644 media/webrtc/signaling/src/sdp/SdpEnum.h create mode 100644 media/webrtc/signaling/src/sdp/SdpErrorHolder.h create mode 100644 media/webrtc/signaling/src/sdp/SdpHelper.cpp create mode 100644 media/webrtc/signaling/src/sdp/SdpHelper.h create mode 100644 media/webrtc/signaling/src/sdp/SdpMediaSection.cpp create mode 100644 media/webrtc/signaling/src/sdp/SdpMediaSection.h create mode 100644 media/webrtc/signaling/src/sdp/SipccSdp.cpp create mode 100644 media/webrtc/signaling/src/sdp/SipccSdp.h create mode 100644 media/webrtc/signaling/src/sdp/SipccSdpAttributeList.cpp create mode 100644 media/webrtc/signaling/src/sdp/SipccSdpAttributeList.h create mode 100644 media/webrtc/signaling/src/sdp/SipccSdpMediaSection.cpp create mode 100644 media/webrtc/signaling/src/sdp/SipccSdpMediaSection.h create mode 100644 media/webrtc/signaling/src/sdp/SipccSdpParser.cpp create mode 100644 media/webrtc/signaling/src/sdp/SipccSdpParser.h create mode 100644 media/webrtc/signaling/src/sdp/sipcc/ccsdp.h create mode 100644 media/webrtc/signaling/src/sdp/sipcc/ccsdp_rtcp_fb.h create mode 100644 media/webrtc/signaling/src/sdp/sipcc/cpr_darwin_types.h create mode 100644 media/webrtc/signaling/src/sdp/sipcc/cpr_linux_types.h create mode 100644 media/webrtc/signaling/src/sdp/sipcc/cpr_string.c create mode 100644 media/webrtc/signaling/src/sdp/sipcc/cpr_string.h create mode 100644 media/webrtc/signaling/src/sdp/sipcc/cpr_strings.h create mode 100644 media/webrtc/signaling/src/sdp/sipcc/cpr_types.h create mode 100644 media/webrtc/signaling/src/sdp/sipcc/cpr_win_types.h create mode 100644 media/webrtc/signaling/src/sdp/sipcc/sdp.h create mode 100644 media/webrtc/signaling/src/sdp/sipcc/sdp_access.c create mode 100644 media/webrtc/signaling/src/sdp/sipcc/sdp_attr.c create mode 100644 media/webrtc/signaling/src/sdp/sipcc/sdp_attr_access.c create mode 100644 media/webrtc/signaling/src/sdp/sipcc/sdp_base64.c create mode 100644 media/webrtc/signaling/src/sdp/sipcc/sdp_base64.h create mode 100644 media/webrtc/signaling/src/sdp/sipcc/sdp_config.c create mode 100644 media/webrtc/signaling/src/sdp/sipcc/sdp_main.c create mode 100644 media/webrtc/signaling/src/sdp/sipcc/sdp_os_defs.h create mode 100644 media/webrtc/signaling/src/sdp/sipcc/sdp_private.h create mode 100644 media/webrtc/signaling/src/sdp/sipcc/sdp_services_unix.c create mode 100644 media/webrtc/signaling/src/sdp/sipcc/sdp_services_win32.c create mode 100644 media/webrtc/signaling/src/sdp/sipcc/sdp_token.c create mode 100644 media/webrtc/signaling/src/sdp/sipcc/sdp_utils.c create mode 100644 media/webrtc/signaling/test/FakeIPC.cpp create mode 100644 media/webrtc/signaling/test/FakeIPC.h create mode 100644 media/webrtc/signaling/test/FakeLogging.h create mode 100644 media/webrtc/signaling/test/FakeMediaStreams.h create mode 100644 media/webrtc/signaling/test/FakeMediaStreamsImpl.h create mode 100644 media/webrtc/signaling/test/FakePCObserver.h create mode 100644 media/webrtc/signaling/test/common.build create mode 100644 media/webrtc/signaling/test/jsep_session_unittest.cpp create mode 100644 media/webrtc/signaling/test/jsep_track_unittest.cpp create mode 100644 media/webrtc/signaling/test/mediaconduit_unittests.cpp create mode 100644 media/webrtc/signaling/test/mediapipeline_unittest.cpp create mode 100644 media/webrtc/signaling/test/moz.build create mode 100644 media/webrtc/signaling/test/sdp_file_parser.cpp create mode 100644 media/webrtc/signaling/test/sdp_unittests.cpp create mode 100644 media/webrtc/signaling/test/signaling_unittests.cpp (limited to 'media/webrtc/signaling') diff --git a/media/webrtc/signaling/signaling.gyp b/media/webrtc/signaling/signaling.gyp new file mode 100644 index 000000000..ee1941151 --- /dev/null +++ b/media/webrtc/signaling/signaling.gyp @@ -0,0 +1,379 @@ +# Copyright (c) 2011, The WebRTC project authors. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# * Neither the name of Google nor the names of its contributors may +# be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Indent 2 spaces, no tabs. +# +# +# sip.gyp - a library for SIP +# + +{ + 'variables': { + 'chromium_code': 1, + }, + + 'target_defaults': { + 'conditions': [ + ['moz_widget_toolkit_gonk==1', { + 'defines' : [ + 'WEBRTC_GONK', + ], + }], + ], + }, + + 'targets': [ + + # + # ECC + # + { + 'target_name': 'ecc', + 'type': 'static_library', + + # + # INCLUDES + # + 'include_dirs': [ + '..', + './src', + './src/common', + './src/common/browser_logging', + './src/common/time_profiling', + './src/media', + './src/media-conduit', + './src/mediapipeline', + './src/peerconnection', + './src/sdp/sipcc', + '../../../dom/base', + '../../../dom/media', + '../../../media/mtransport', + '../trunk', + '../../libyuv/include', + '../../mtransport/third_party/nrappkit/src/util/libekr', + ], + + # + # DEPENDENCIES + # + 'dependencies': [ + ], + + 'export_dependent_settings': [ + ], + + + # + # SOURCES + # + 'sources': [ + # Media Conduit + './src/media-conduit/AudioConduit.h', + './src/media-conduit/AudioConduit.cpp', + './src/media-conduit/VideoConduit.h', + './src/media-conduit/VideoConduit.cpp', + './src/media-conduit/CodecStatistics.h', + './src/media-conduit/CodecStatistics.cpp', + './src/media-conduit/RunningStat.h', + # Common + './src/common/CommonTypes.h', + './src/common/csf_common.h', + './src/common/NullDeleter.h', + './src/common/PtrVector.h', + './src/common/Wrapper.h', + './src/common/NullTransport.h', + './src/common/YuvStamper.cpp', + # Browser Logging + './src/common/browser_logging/CSFLog.cpp', + './src/common/browser_logging/CSFLog.h', + './src/common/browser_logging/WebRtcLog.cpp', + './src/common/browser_logging/WebRtcLog.h', + # Browser Logging + './src/common/time_profiling/timecard.c', + './src/common/time_profiling/timecard.h', + # PeerConnection + './src/peerconnection/MediaPipelineFactory.cpp', + './src/peerconnection/MediaPipelineFactory.h', + './src/peerconnection/PeerConnectionCtx.cpp', + './src/peerconnection/PeerConnectionCtx.h', + './src/peerconnection/PeerConnectionImpl.cpp', + './src/peerconnection/PeerConnectionImpl.h', + './src/peerconnection/PeerConnectionMedia.cpp', + './src/peerconnection/PeerConnectionMedia.h', + # Media pipeline + './src/mediapipeline/MediaPipeline.h', + './src/mediapipeline/MediaPipeline.cpp', + './src/mediapipeline/MediaPipelineFilter.h', + './src/mediapipeline/MediaPipelineFilter.cpp', + # SDP + './src/sdp/sipcc/ccsdp.h', + './src/sdp/sipcc/cpr_string.c', + './src/sdp/sipcc/sdp_access.c', + './src/sdp/sipcc/sdp_attr.c', + './src/sdp/sipcc/sdp_attr_access.c', + './src/sdp/sipcc/sdp_base64.c', + './src/sdp/sipcc/sdp_config.c', + './src/sdp/sipcc/sdp_main.c', + './src/sdp/sipcc/sdp_token.c', + './src/sdp/sipcc/sdp.h', + './src/sdp/sipcc/sdp_base64.h', + './src/sdp/sipcc/sdp_os_defs.h', + './src/sdp/sipcc/sdp_private.h', + './src/sdp/sipcc/sdp_utils.c', + './src/sdp/sipcc/sdp_services_unix.c', + + # SDP Wrapper + './src/sdp/Sdp.h', + './src/sdp/SdpAttribute.h', + './src/sdp/SdpAttribute.cpp', + './src/sdp/SdpAttributeList.h', + './src/sdp/SdpErrorHolder.h', + './src/sdp/SdpHelper.h', + './src/sdp/SdpHelper.cpp', + './src/sdp/SdpMediaSection.h', + './src/sdp/SdpMediaSection.cpp', + './src/sdp/SipccSdp.h', + './src/sdp/SipccSdpAttributeList.h', + './src/sdp/SipccSdpAttributeList.cpp', + './src/sdp/SipccSdpMediaSection.h', + './src/sdp/SipccSdpParser.h', + './src/sdp/SipccSdp.cpp', + './src/sdp/SipccSdpMediaSection.cpp', + './src/sdp/SipccSdpParser.cpp', + + # JSEP + './src/jsep/JsepCodecDescription.h', + './src/jsep/JsepSession.h', + './src/jsep/JsepSessionImpl.cpp', + './src/jsep/JsepSessionImpl.h', + './src/jsep/JsepTrack.cpp', + './src/jsep/JsepTrack.h', + './src/jsep/JsepTrackEncoding.h', + './src/jsep/JsepTransport.h' + ], + + # + # DEFINES + # + + 'defines' : [ + 'LOG4CXX_STATIC', + '_NO_LOG4CXX', + 'USE_SSLEAY', + '_CPR_USE_EXTERNAL_LOGGER', + 'WEBRTC_RELATIVE_PATH', + 'HAVE_WEBRTC_VIDEO', + 'HAVE_WEBRTC_VOICE', + 'HAVE_STDINT_H=1', + 'HAVE_STDLIB_H=1', + 'HAVE_UINT8_T=1', + 'HAVE_UINT16_T=1', + 'HAVE_UINT32_T=1', + 'HAVE_UINT64_T=1', + ], + + 'cflags_mozilla': [ + '$(NSPR_CFLAGS)', + '$(NSS_CFLAGS)', + '$(MOZ_PIXMAN_CFLAGS)', + ], + + + # + # Conditionals + # + 'conditions': [ + # hack so I can change the include flow for SrtpFlow + ['build_with_mozilla==1', { + 'sources': [ + './src/mediapipeline/SrtpFlow.h', + './src/mediapipeline/SrtpFlow.cpp', + ], + 'include_dirs!': [ + '../trunk/webrtc', + ], + 'include_dirs': [ + '../../../netwerk/srtp/src/include', + '../../../netwerk/srtp/src/crypto/include', + ], + }], + ['moz_webrtc_omx==1', { + 'sources': [ + './src/media-conduit/WebrtcOMXH264VideoCodec.cpp', + './src/media-conduit/OMXVideoCodec.cpp', + ], + 'include_dirs': [ + # hack on hack to re-add it after SrtpFlow removes it + '../../../dom/media/omx', + '../../../gfx/layers/client', + ], + 'cflags_mozilla': [ + '-I$(ANDROID_SOURCE)/frameworks/av/include/media/stagefright', + '-I$(ANDROID_SOURCE)/frameworks/av/include', + '-I$(ANDROID_SOURCE)/frameworks/native/include/media/openmax', + '-I$(ANDROID_SOURCE)/frameworks/native/include', + '-I$(ANDROID_SOURCE)/frameworks/native/opengl/include', + ], + 'defines' : [ + 'MOZ_WEBRTC_OMX' + ], + }], + ['moz_webrtc_mediacodec==1', { + 'include_dirs': [ + '../../../widget/android', + ], + 'sources': [ + './src/media-conduit/MediaCodecVideoCodec.h', + './src/media-conduit/WebrtcMediaCodecVP8VideoCodec.h', + './src/media-conduit/MediaCodecVideoCodec.cpp', + './src/media-conduit/WebrtcMediaCodecVP8VideoCodec.cpp', + ], + 'defines' : [ + 'MOZ_WEBRTC_MEDIACODEC', + ], + }], + ['(build_for_test==0) and (build_for_standalone==0)', { + 'defines' : [ + 'MOZILLA_INTERNAL_API', + ], + 'sources': [ + './src/peerconnection/MediaStreamList.cpp', + './src/peerconnection/MediaStreamList.h', + './src/peerconnection/WebrtcGlobalInformation.cpp', + './src/peerconnection/WebrtcGlobalInformation.h', + ], + }], + ['build_for_test!=0', { + 'include_dirs': [ + './test' + ], + 'defines' : [ + 'NO_CHROMIUM_LOGGING', + 'USE_FAKE_MEDIA_STREAMS', + 'USE_FAKE_PCOBSERVER', + 'MOZILLA_EXTERNAL_LINKAGE', + ], + }], + ['build_for_standalone==0', { + 'sources': [ + './src/media-conduit/GmpVideoCodec.cpp', + './src/media-conduit/WebrtcGmpVideoCodec.cpp', + ], + }], + ['build_for_standalone!=0', { + 'include_dirs': [ + './test' + ], + 'defines' : [ + 'MOZILLA_INTERNAL_API', + 'MOZILLA_EXTERNAL_LINKAGE', + 'NO_CHROMIUM_LOGGING', + 'USE_FAKE_MEDIA_STREAMS', + 'USE_FAKE_PCOBSERVER', + ], + }], + ['(OS=="linux") or (OS=="android")', { + 'include_dirs': [ + ], + + 'defines': [ + 'OS_LINUX', + 'SIP_OS_LINUX', + 'WEBRTC_POSIX', + '_GNU_SOURCE', + 'LINUX', + 'GIPS_VER=3510', + 'SECLIB_OPENSSL', + ], + + 'cflags_mozilla': [ + ], + }], + ['OS=="android" or moz_widget_toolkit_gonk==1', { + 'cflags_mozilla': [ + # This warning complains about important MOZ_EXPORT attributes + # on forward declarations for Android API types. + '-Wno-error=attributes', + ], + }], + ['OS=="win"', { + 'include_dirs': [ + ], + 'defines': [ + 'OS_WIN', + 'SIP_OS_WINDOWS', + 'WEBRTC_WIN', + 'WIN32', + 'GIPS_VER=3480', + 'SIPCC_BUILD', + 'HAVE_WINSOCK2_H' + ], + + 'cflags_mozilla': [ + ], + }], + ['os_bsd==1', { + 'include_dirs': [ + ], + 'defines': [ + # avoiding pointless ifdef churn + 'WEBRTC_POSIX', + 'SIP_OS_OSX', + 'OSX', + 'SECLIB_OPENSSL', + ], + + 'cflags_mozilla': [ + ], + }], + ['OS=="mac" or OS=="ios"', { + 'include_dirs': [ + ], + 'defines': [ + 'WEBRTC_POSIX', + 'OS_MACOSX', + 'SIP_OS_OSX', + 'OSX', + '_FORTIFY_SOURCE=2', + ], + + 'cflags_mozilla': [ + ], + }], + ], + }, + ], +} + +# Local Variables: +# tab-width:2 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=2 shiftwidth=2: diff --git a/media/webrtc/signaling/src/common/CommonTypes.h b/media/webrtc/signaling/src/common/CommonTypes.h new file mode 100644 index 000000000..96f1f3423 --- /dev/null +++ b/media/webrtc/signaling/src/common/CommonTypes.h @@ -0,0 +1,63 @@ +/* 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 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 ErrorCodeEnum +{ + enum ErrorCode + { + Ok, + Unknown, + InvalidState, + InvalidArgument + }; + const std::string toString(ErrorCode); +} + +} // namespace csf + diff --git a/media/webrtc/signaling/src/common/EncodingConstraints.h b/media/webrtc/signaling/src/common/EncodingConstraints.h new file mode 100644 index 000000000..efba7c51c --- /dev/null +++ b/media/webrtc/signaling/src/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 + +namespace mozilla +{ +class EncodingConstraints +{ +public: + EncodingConstraints() : + maxWidth(0), + maxHeight(0), + maxFps(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; + } + + uint32_t maxWidth; + uint32_t maxHeight; + uint32_t 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/media/webrtc/signaling/src/common/MediaEngineWrapper.h b/media/webrtc/signaling/src/common/MediaEngineWrapper.h new file mode 100755 index 000000000..f9b1a3415 --- /dev/null +++ b/media/webrtc/signaling/src/common/MediaEngineWrapper.h @@ -0,0 +1,39 @@ +/* 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 + + +#endif diff --git a/media/webrtc/signaling/src/common/NullDeleter.h b/media/webrtc/signaling/src/common/NullDeleter.h new file mode 100644 index 000000000..9b4628390 --- /dev/null +++ b/media/webrtc/signaling/src/common/NullDeleter.h @@ -0,0 +1,16 @@ +/* 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/media/webrtc/signaling/src/common/NullTransport.h b/media/webrtc/signaling/src/common/NullTransport.h new file mode 100644 index 000000000..bce793304 --- /dev/null +++ b/media/webrtc/signaling/src/common/NullTransport.h @@ -0,0 +1,44 @@ +/* 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 "webrtc/common_types.h" + +namespace mozilla { + +/** + * NullTransport is registered as ExternalTransport to throw away data + */ +class NullTransport : public webrtc::Transport +{ +public: + 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; + } + + NullTransport() {} + + virtual ~NullTransport() {} + +private: + NullTransport(const NullTransport& other) = delete; + void operator=(const NullTransport& other) = delete; +}; + +} // end namespace + +#endif diff --git a/media/webrtc/signaling/src/common/PtrVector.h b/media/webrtc/signaling/src/common/PtrVector.h new file mode 100644 index 000000000..68c760472 --- /dev/null +++ b/media/webrtc/signaling/src/common/PtrVector.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 PtrVector_h +#define PtrVector_h + +#include +#include + +namespace mozilla +{ + +// Trivial wrapper class around a vector of ptrs. +// TODO: Remove this once our buildconfig allows us to put unique_ptr in stl +// containers, and just use std::vector> instead. +template class PtrVector +{ +public: + PtrVector() = default; + PtrVector(const PtrVector&) = delete; + PtrVector(PtrVector&& aOther) + : values(Move(aOther.values)) + {} + PtrVector& operator=(const PtrVector&) = delete; + PtrVector& operator=(PtrVector&& aOther) + { + Swap(values, aOther.values); + return *this; + } + + ~PtrVector() + { + for (T* value : values) { delete value; } + } + + std::vector values; +}; + +} // namespace mozilla + +#endif // PtrVector_h + diff --git a/media/webrtc/signaling/src/common/Wrapper.h b/media/webrtc/signaling/src/common/Wrapper.h new file mode 100644 index 000000000..a88cbd4bf --- /dev/null +++ b/media/webrtc/signaling/src/common/Wrapper.h @@ -0,0 +1,175 @@ +/* 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/media/webrtc/signaling/src/common/YuvStamper.cpp b/media/webrtc/signaling/src/common/YuvStamper.cpp new file mode 100644 index 000000000..892b640bf --- /dev/null +++ b/media/webrtc/signaling/src/common/YuvStamper.cpp @@ -0,0 +1,469 @@ +/* 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/media/webrtc/signaling/src/common/YuvStamper.h b/media/webrtc/signaling/src/common/YuvStamper.h new file mode 100644 index 000000000..fb2d6e466 --- /dev/null +++ b/media/webrtc/signaling/src/common/YuvStamper.h @@ -0,0 +1,83 @@ +/* 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 "nptypes.h" + +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; +}; + +} + +#endif + diff --git a/media/webrtc/signaling/src/common/browser_logging/CSFLog.cpp b/media/webrtc/signaling/src/common/browser_logging/CSFLog.cpp new file mode 100644 index 000000000..3d7e2d6dc --- /dev/null +++ b/media/webrtc/signaling/src/common/browser_logging/CSFLog.cpp @@ -0,0 +1,103 @@ +/* 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 "base/basictypes.h" + +#include +#include "prrwlock.h" +#include "prthread.h" +#include "nsThreadUtils.h" + +#include "mozilla/Logging.h" +#include "mozilla/Sprintf.h" + +static PRLogModuleInfo *gLogModuleInfo = nullptr; + +PRLogModuleInfo *GetSignalingLogInfo() +{ + if (gLogModuleInfo == nullptr) + gLogModuleInfo = PR_NewLogModule("signaling"); + + return gLogModuleInfo; +} + +static PRLogModuleInfo *gWebRTCLogModuleInfo = nullptr; + +PRLogModuleInfo *GetWebRTCLogInfo() +{ + if (gWebRTCLogModuleInfo == nullptr) + gWebRTCLogModuleInfo = PR_NewLogModule("webrtc_trace"); + + return gWebRTCLogModuleInfo; +} + + +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(priority); + + GetSignalingLogInfo(); + + // Skip doing any of this work if we're not logging the indicated level... + if (!MOZ_LOG_TEST(gLogModuleInfo,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(gLogModuleInfo, 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); +} + diff --git a/media/webrtc/signaling/src/common/browser_logging/CSFLog.h b/media/webrtc/signaling/src/common/browser_logging/CSFLog.h new file mode 100644 index 000000000..a20157992 --- /dev/null +++ b/media/webrtc/signaling/src/common/browser_logging/CSFLog.h @@ -0,0 +1,50 @@ +/* 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 + +struct PRLogModuleInfo; + +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); + +struct PRLogModuleInfo *GetSignalingLogInfo(); +struct PRLogModuleInfo *GetWebRTCLogInfo(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/media/webrtc/signaling/src/common/browser_logging/WebRtcLog.cpp b/media/webrtc/signaling/src/common/browser_logging/WebRtcLog.cpp new file mode 100644 index 000000000..875e0ed2c --- /dev/null +++ b/media/webrtc/signaling/src/common/browser_logging/WebRtcLog.cpp @@ -0,0 +1,258 @@ +/* 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 "prenv.h" +#include "webrtc/system_wrappers/interface/trace.h" + +#include "nscore.h" +#ifdef MOZILLA_INTERNAL_API +#include "nsString.h" +#include "nsXULAppAPI.h" +#include "mozilla/Preferences.h" +#else +#include "nsStringAPI.h" +#endif + +#include "nsIFile.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" + +using mozilla::LogLevel; + +static int gWebRtcTraceLoggingOn = 0; + + +#if defined(ANDROID) +static const char *default_tmp_dir = "/dev/null"; +static const char *default_log_name = "nspr"; +#else // Assume a POSIX environment +NS_NAMED_LITERAL_CSTRING(default_log_name, "WebRTC.log"); +#endif + +static PRLogModuleInfo* GetWebRtcTraceLog() +{ + static PRLogModuleInfo *sLog; + if (!sLog) { + sLog = PR_NewLogModule("webrtc_trace"); + } + return sLog; +} + +static PRLogModuleInfo* GetWebRtcAECLog() +{ + static PRLogModuleInfo *sLog; + if (!sLog) { + sLog = PR_NewLogModule("AEC"); + } + return sLog; +} + +class WebRtcTraceCallback: public webrtc::TraceCallback +{ +public: + void Print(webrtc::TraceLevel level, const char* message, int length) + { + PRLogModuleInfo *log = GetWebRtcTraceLog(); + MOZ_LOG(log, LogLevel::Debug, ("%s", message)); + } +}; + +static WebRtcTraceCallback gWebRtcCallback; + +#ifdef MOZILLA_INTERNAL_API +void GetWebRtcLogPrefs(uint32_t *aTraceMask, nsACString* aLogFile, nsACString *aAECLogDir, bool *aMultiLog) +{ + *aMultiLog = mozilla::Preferences::GetBool("media.webrtc.debug.multi_log"); + *aTraceMask = mozilla::Preferences::GetUint("media.webrtc.debug.trace_mask"); + mozilla::Preferences::GetCString("media.webrtc.debug.log_file", aLogFile); + mozilla::Preferences::GetCString("media.webrtc.debug.aec_log_dir", aAECLogDir); + webrtc::Trace::set_aec_debug_size(mozilla::Preferences::GetUint("media.webrtc.debug.aec_dump_max_size")); +} +#endif + +void CheckOverrides(uint32_t *aTraceMask, nsACString *aLogFile, bool *aMultiLog) +{ + if (!aTraceMask || !aLogFile || !aMultiLog) { + return; + } + + // Override or fill in attributes from the environment if possible. + + PRLogModuleInfo *log_info = GetWebRtcTraceLog(); + /* When webrtc_trace:x is not part of the NSPR_LOG_MODULES var the structure returned from + the GetWebRTCLogInfo call will be non-null and show a level of 0. This cannot + be reliably used to turn off the trace and override a log level from about:config as + there is no way to differentiate between NSPR_LOG_MODULES=webrtc_trace:0 and the complete + absense of the webrtc_trace in the environment string at all. + */ + if (log_info && (log_info->level != 0)) { + *aTraceMask = log_info->level; + } + + log_info = GetWebRtcAECLog(); + if (log_info && (log_info->level != 0)) { + webrtc::Trace::set_aec_debug(true); + } + + const char *file_name = PR_GetEnv("WEBRTC_TRACE_FILE"); + if (file_name) { + aLogFile->Assign(file_name); + } +} + +void ConfigWebRtcLog(uint32_t trace_mask, nsCString &aLogFile, nsCString &aAECLogDir, bool multi_log) +{ + if (gWebRtcTraceLoggingOn) { + return; + } + +#if defined(ANDROID) + // Special case: use callback to pipe to NSPR logging. + aLogFile.Assign(default_log_name); +#else + + webrtc::Trace::set_level_filter(trace_mask); + + if (trace_mask != 0) { + if (aLogFile.EqualsLiteral("nspr")) { + webrtc::Trace::SetTraceCallback(&gWebRtcCallback); + } else { + webrtc::Trace::SetTraceFile(aLogFile.get(), multi_log); + } + } + + if (aLogFile.IsEmpty()) { + nsCOMPtr tempDir; + nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tempDir)); + if (NS_SUCCEEDED(rv)) { + tempDir->AppendNative(default_log_name); + tempDir->GetNativePath(aLogFile); + } + } +#endif + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + if (XRE_IsParentProcess()) { + // Capture the final choice for the trace setting. + mozilla::Preferences::SetCString("media.webrtc.debug.log_file", aLogFile); + } +#endif + return; +} + +void StartWebRtcLog(uint32_t log_level) +{ + if (gWebRtcTraceLoggingOn && log_level != 0) { + return; + } + + if (log_level == 0) { + if (gWebRtcTraceLoggingOn) { + gWebRtcTraceLoggingOn = false; + webrtc::Trace::set_level_filter(webrtc::kTraceNone); + } + return; + } + + uint32_t trace_mask = 0; + bool multi_log = false; + nsAutoCString log_file; + nsAutoCString aec_log_dir; + +#ifdef MOZILLA_INTERNAL_API + GetWebRtcLogPrefs(&trace_mask, &log_file, &aec_log_dir, &multi_log); +#endif + CheckOverrides(&trace_mask, &log_file, &multi_log); + + if (trace_mask == 0) { + trace_mask = log_level; + } + + ConfigWebRtcLog(trace_mask, log_file, aec_log_dir, multi_log); + return; + +} + +void EnableWebRtcLog() +{ + if (gWebRtcTraceLoggingOn) { + return; + } + + uint32_t trace_mask = 0; + bool multi_log = false; + nsAutoCString log_file; + nsAutoCString aec_log_dir; + +#ifdef MOZILLA_INTERNAL_API + GetWebRtcLogPrefs(&trace_mask, &log_file, &aec_log_dir, &multi_log); +#endif + CheckOverrides(&trace_mask, &log_file, &multi_log); + ConfigWebRtcLog(trace_mask, log_file, aec_log_dir, multi_log); + return; +} + +void StopWebRtcLog() +{ + // TODO(NG) strip/fix gWebRtcTraceLoggingOn which is never set to true + webrtc::Trace::set_level_filter(webrtc::kTraceNone); + webrtc::Trace::SetTraceCallback(nullptr); + webrtc::Trace::SetTraceFile(nullptr); +} + +void ConfigAecLog(nsCString &aAECLogDir) { + if (webrtc::Trace::aec_debug()) { + return; + } +#if defined(ANDROID) + // For AEC, do not use a default value: force the user to specify a directory. + if (aAECLogDir.IsEmpty()) { + aAECLogDir.Assign(default_tmp_dir); + } +#else + if (aAECLogDir.IsEmpty()) { + nsCOMPtr tempDir; + nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tempDir)); + if (NS_SUCCEEDED(rv)) { + if (aAECLogDir.IsEmpty()) { + tempDir->GetNativePath(aAECLogDir); + } + } + } +#endif + webrtc::Trace::set_aec_debug_filename(aAECLogDir.get()); +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + if (XRE_IsParentProcess()) { + // Capture the final choice for the aec_log_dir setting. + mozilla::Preferences::SetCString("media.webrtc.debug.aec_log_dir", aAECLogDir); + } +#endif +} + +void StartAecLog() +{ + if (webrtc::Trace::aec_debug()) { + return; + } + uint32_t trace_mask = 0; + bool multi_log = false; + nsAutoCString log_file; + nsAutoCString aec_log_dir; + +#ifdef MOZILLA_INTERNAL_API + GetWebRtcLogPrefs(&trace_mask, &log_file, &aec_log_dir, &multi_log); +#endif + CheckOverrides(&trace_mask, &log_file, &multi_log); + ConfigAecLog(aec_log_dir); + + webrtc::Trace::set_aec_debug(true); +} + +void StopAecLog() +{ + webrtc::Trace::set_aec_debug(false); +} diff --git a/media/webrtc/signaling/src/common/browser_logging/WebRtcLog.h b/media/webrtc/signaling/src/common/browser_logging/WebRtcLog.h new file mode 100644 index 000000000..58a824bee --- /dev/null +++ b/media/webrtc/signaling/src/common/browser_logging/WebRtcLog.h @@ -0,0 +1,16 @@ +/* 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 "webrtc/common_types.h" + +void StartAecLog(); +void StopAecLog(); +void StartWebRtcLog(uint32_t log_level = webrtc::kTraceDefault); +void EnableWebRtcLog(); +void StopWebRtcLog(); + +#endif diff --git a/media/webrtc/signaling/src/common/csf_common.h b/media/webrtc/signaling/src/common/csf_common.h new file mode 100644 index 000000000..f46abf69e --- /dev/null +++ b/media/webrtc/signaling/src/common/csf_common.h @@ -0,0 +1,79 @@ +/* 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/media/webrtc/signaling/src/common/time_profiling/timecard.c b/media/webrtc/signaling/src/common/time_profiling/timecard.c new file mode 100644 index 000000000..e56377534 --- /dev/null +++ b/media/webrtc/signaling/src/common/time_profiling/timecard.c @@ -0,0 +1,125 @@ +/* -*- 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 "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/media/webrtc/signaling/src/common/time_profiling/timecard.h b/media/webrtc/signaling/src/common/time_profiling/timecard.h new file mode 100644 index 000000000..ca53b8dfe --- /dev/null +++ b/media/webrtc/signaling/src/common/time_profiling/timecard.h @@ -0,0 +1,81 @@ +/* -*- 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/media/webrtc/signaling/src/jsep/JsepCodecDescription.h b/media/webrtc/signaling/src/jsep/JsepCodecDescription.h new file mode 100644 index 000000000..6ae5c9380 --- /dev/null +++ b/media/webrtc/signaling/src/jsep/JsepCodecDescription.h @@ -0,0 +1,780 @@ +/* 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 "signaling/src/sdp/SdpMediaSection.h" +#include "signaling/src/sdp/SdpHelper.h" +#include "nsCRT.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(mozilla::SdpMediaSection::MediaType type, + const std::string& defaultPt, + const std::string& name, + uint32_t clock, + uint32_t channels, + bool enabled) + : mType(type), + mDefaultPt(defaultPt), + mName(name), + mClock(clock), + mChannels(channels), + mEnabled(enabled), + mStronglyPreferred(false), + mDirection(sdp::kSend) + { + } + virtual ~JsepCodecDescription() {} + + virtual JsepCodecDescription* Clone() const = 0; + + bool + GetPtAsInt(uint16_t* ptOutparam) const + { + return SdpHelper::GetPtAsInt(mDefaultPt, ptOutparam); + } + + virtual bool + Matches(const std::string& fmt, const SdpMediaSection& remoteMsection) const + { + // note: fmt here is remote fmt (to go with remoteMsection) + if (mType != 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; + } + + virtual bool + Negotiate(const std::string& pt, const SdpMediaSection& remoteMsection) + { + mDefaultPt = pt; + return true; + } + + virtual void + AddToMediaSection(SdpMediaSection& msection) const + { + if (mEnabled && msection.GetMediaType() == mType) { + // Both send and recv codec will have the same pt, so don't add twice + if (!msection.HasFormat(mDefaultPt)) { + if (mType == SdpMediaSection::kApplication) { + // Hack: using mChannels for number of streams + msection.AddDataChannel(mDefaultPt, mName, mChannels); + } else { + msection.AddCodec(mDefaultPt, mName, mClock, mChannels); + } + } + + AddParametersToMSection(msection); + } + } + + virtual void AddParametersToMSection(SdpMediaSection& msection) const {} + + mozilla::SdpMediaSection::MediaType mType; + std::string mDefaultPt; + std::string mName; + 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, + uint32_t packetSize, + uint32_t bitRate, + bool enabled = true) + : JsepCodecDescription(mozilla::SdpMediaSection::kAudio, defaultPt, name, + clock, channels, enabled), + mPacketSize(packetSize), + mBitrate(bitRate), + mMaxPlaybackRate(0), + mForceMono(false), + mFECEnabled(false), + mDtmfEnabled(false) + { + } + + JSEP_CODEC_CLONE(JsepAudioCodecDescription) + + 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") { + SdpFmtpAttributeList::OpusParameters opusParams( + GetOpusParameters(mDefaultPt, msection)); + if (mMaxPlaybackRate) { + opusParams.maxplaybackrate = mMaxPlaybackRate; + } + if (mChannels == 2 && !mForceMono) { + // We prefer to receive stereo, if available. + opusParams.stereo = 1; + } + opusParams.useInBandFec = mFECEnabled ? 1 : 0; + 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) override + { + JsepCodecDescription::Negotiate(pt, remoteMsection); + 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; + } + + return true; + } + + uint32_t mPacketSize; + uint32_t mBitrate; + uint32_t mMaxPlaybackRate; + bool mForceMono; + bool mFECEnabled; + bool mDtmfEnabled; +}; + +class JsepVideoCodecDescription : public JsepCodecDescription { + public: + JsepVideoCodecDescription(const std::string& defaultPt, + const std::string& name, + uint32_t clock, + bool enabled = true) + : JsepCodecDescription(mozilla::SdpMediaSection::kVideo, defaultPt, name, + clock, 0, enabled), + mTmmbrEnabled(false), + mRembEnabled(false), + mFECEnabled(false), + mPacketizationMode(0) + { + // Add supported rtcp-fb types + mNackFbTypes.push_back(""); + mNackFbTypes.push_back(SdpRtcpFbAttributeList::pli); + mCcmFbTypes.push_back(SdpRtcpFbAttributeList::fir); + } + + 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() { + // 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. + mFECEnabled = true; + } + + void + AddParametersToMSection(SdpMediaSection& msection) const override + { + AddFmtpsToMSection(msection); + AddRtcpFbsToMSection(msection); + } + + void + AddFmtpsToMSection(SdpMediaSection& msection) const + { + if (mName == "H264") { + SdpFmtpAttributeList::H264Parameters h264Params( + GetH264Parameters(mDefaultPt, msection)); + + 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; + + msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, h264Params)); + } else if (mName == "red") { + 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 + SdpFmtpAttributeList::VP8Parameters vp8Params( + GetVP8Parameters(mDefaultPt, msection)); + + vp8Params.max_fs = mConstraints.maxFs; + vp8Params.max_fr = mConstraints.maxFps; + msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, vp8Params)); + } + } + } + + 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::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) + { + std::vector temp; + for (auto& subType : *supportedTypes) { + if (remoteMsection.HasRtcpFb(mDefaultPt, type, subType)) { + temp.push_back(subType); + } + } + *supportedTypes = temp; + } + + void + NegotiateRtcpFb(const SdpMediaSection& remoteMsection, + std::vector* supportedFbs) { + std::vector temp; + for (auto& fb : *supportedFbs) { + if (remoteMsection.HasRtcpFb(mDefaultPt, 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) override + { + JsepCodecDescription::Negotiate(pt, remoteMsection); + 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; + mConstraints.maxFps = vp8Params.max_fr; + } + } + + 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 void + UpdateRedundantEncodings(std::vector codecs) + { + for (const auto codec : codecs) { + if (codec->mType == SdpMediaSection::kVideo && + codec->mEnabled && + codec->mName != "red") { + uint8_t pt = (uint8_t)strtoul(codec->mDefaultPt.c_str(), nullptr, 10); + // returns 0 if failed to convert, and since zero could + // be valid, check the defaultPt for 0 + if (pt == 0 && codec->mDefaultPt != "0") { + continue; + } + mRedundantEncodings.push_back(pt); + } + } + } + + JSEP_CODEC_CLONE(JsepVideoCodecDescription) + + std::vector mAckFbTypes; + std::vector mNackFbTypes; + std::vector mCcmFbTypes; + std::vector mOtherFbTypes; + bool mTmmbrEnabled; + bool mRembEnabled; + bool mFECEnabled; + std::vector mRedundantEncodings; + + // H264-specific stuff + uint32_t mProfileLevelId; + uint32_t mPacketizationMode; + std::string mSpropParameterSets; +}; + +class JsepApplicationCodecDescription : public JsepCodecDescription { + public: + JsepApplicationCodecDescription(const std::string& defaultPt, + const std::string& name, + uint16_t channels, + bool enabled = true) + : JsepCodecDescription(mozilla::SdpMediaSection::kApplication, defaultPt, + name, 0, channels, enabled) + { + } + + JSEP_CODEC_CLONE(JsepApplicationCodecDescription) + + // Override, uses sctpmap instead of rtpmap + virtual bool + Matches(const std::string& fmt, + const SdpMediaSection& remoteMsection) const override + { + if (mType != remoteMsection.GetMediaType()) { + return false; + } + + const SdpSctpmapAttributeList::Sctpmap* entry( + remoteMsection.FindSctpmap(fmt)); + + if (entry && !nsCRT::strcasecmp(mName.c_str(), entry->name.c_str())) { + return true; + } + return false; + } +}; + +} // namespace mozilla + +#endif diff --git a/media/webrtc/signaling/src/jsep/JsepSession.h b/media/webrtc/signaling/src/jsep/JsepSession.h new file mode 100644 index 000000000..29bcbde05 --- /dev/null +++ b/media/webrtc/signaling/src/jsep/JsepSession.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 _JSEPSESSION_H_ +#define _JSEPSESSION_H_ + +#include +#include +#include "mozilla/Maybe.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "nsError.h" + +#include "signaling/src/jsep/JsepTransport.h" +#include "signaling/src/sdp/Sdp.h" + +#include "JsepTrack.h" + +namespace mozilla { + +// Forward declarations +class JsepCodecDescription; +class JsepTrack; + +enum JsepSignalingState { + kJsepStateStable, + kJsepStateHaveLocalOffer, + kJsepStateHaveRemoteOffer, + kJsepStateHaveLocalPranswer, + kJsepStateHaveRemotePranswer, + kJsepStateClosed +}; + +enum JsepSdpType { + kJsepSdpOffer, + kJsepSdpAnswer, + kJsepSdpPranswer, + kJsepSdpRollback +}; + +struct JsepOAOptions {}; +struct JsepOfferOptions : public JsepOAOptions { + Maybe mOfferToReceiveAudio; + Maybe mOfferToReceiveVideo; + Maybe mDontOfferDataChannel; + Maybe mIceRestart; // currently ignored by JsepSession +}; +struct JsepAnswerOptions : public JsepOAOptions {}; + +enum JsepBundlePolicy { + kBundleBalanced, + kBundleMaxCompat, + kBundleMaxBundle +}; + +class JsepSession +{ +public: + explicit JsepSession(const std::string& name) + : mName(name), mState(kJsepStateStable), mNegotiations(0) + { + } + virtual ~JsepSession() {} + + 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 SetIceCredentials(const std::string& ufrag, + const std::string& pwd) = 0; + virtual const std::string& GetUfrag() const = 0; + virtual const std::string& GetPwd() const = 0; + virtual nsresult SetBundlePolicy(JsepBundlePolicy policy) = 0; + virtual bool RemoteIsIceLite() const = 0; + virtual bool RemoteIceIsRestarting() const = 0; + virtual std::vector GetIceOptions() const = 0; + + virtual nsresult AddDtlsFingerprint(const std::string& algorithm, + const std::vector& value) = 0; + + virtual nsresult AddAudioRtpExtension(const std::string& extensionName, + SdpDirectionAttribute::Direction direction) = 0; + virtual nsresult AddVideoRtpExtension(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 (RefPtr& track : GetLocalTracks()) { + track->ForEachCodec(function); + } + for (RefPtr& track : GetRemoteTracks()) { + track->ForEachCodec(function); + } + } + + template + void SortCodecs(BinaryPredicate& sorter) + { + std::stable_sort(Codecs().begin(), Codecs().end(), sorter); + for (RefPtr& track : GetLocalTracks()) { + track->SortCodecs(sorter); + } + for (RefPtr& track : GetRemoteTracks()) { + track->SortCodecs(sorter); + } + } + + // Manage tracks. We take shared ownership of any track. + virtual nsresult AddTrack(const RefPtr& track) = 0; + virtual nsresult RemoveTrack(const std::string& streamId, + const std::string& trackId) = 0; + virtual nsresult ReplaceTrack(const std::string& oldStreamId, + const std::string& oldTrackId, + const std::string& newStreamId, + const std::string& newTrackId) = 0; + virtual nsresult SetParameters( + const std::string& streamId, + const std::string& trackId, + const std::vector& constraints) = 0; + + virtual nsresult GetParameters( + const std::string& streamId, + const std::string& trackId, + std::vector* outConstraints) = 0; + + virtual std::vector> GetLocalTracks() const = 0; + + virtual std::vector> GetRemoteTracks() const = 0; + + virtual std::vector> GetRemoteTracksAdded() const = 0; + + virtual std::vector> GetRemoteTracksRemoved() const = 0; + + // Access the negotiated track pairs. + virtual std::vector GetNegotiatedTrackPairs() const = 0; + + // Access transports. + virtual std::vector> GetTransports() const = 0; + + // Basic JSEP operations. + virtual nsresult CreateOffer(const JsepOfferOptions& options, + std::string* offer) = 0; + virtual nsresult CreateAnswer(const JsepAnswerOptions& options, + std::string* answer) = 0; + virtual std::string GetLocalDescription() const = 0; + virtual std::string GetRemoteDescription() const = 0; + virtual nsresult SetLocalDescription(JsepSdpType type, + const std::string& sdp) = 0; + virtual nsresult SetRemoteDescription(JsepSdpType type, + const std::string& sdp) = 0; + virtual nsresult AddRemoteIceCandidate(const std::string& candidate, + const std::string& mid, + uint16_t level) = 0; + virtual nsresult AddLocalIceCandidate(const std::string& candidate, + 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, + uint16_t level) = 0; + virtual nsresult EndOfLocalCandidates(uint16_t level) = 0; + virtual nsresult Close() = 0; + + // ICE controlling or controlled + virtual bool IsIceControlling() const = 0; + + virtual const std::string + GetLastError() const + { + return "Error"; + } + + 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 AllLocalTracksAreAssigned() const = 0; + + void + CountTracks(uint16_t (&receiving)[SdpMediaSection::kMediaTypes], + uint16_t (&sending)[SdpMediaSection::kMediaTypes]) const + { + auto trackPairs = GetNegotiatedTrackPairs(); + + memset(receiving, 0, sizeof(receiving)); + memset(sending, 0, sizeof(sending)); + + for (auto& pair : trackPairs) { + if (pair.mReceiving) { + receiving[pair.mReceiving->GetMediaType()]++; + } + + if (pair.mSending) { + sending[pair.mSending->GetMediaType()]++; + } + } + } + +protected: + const std::string mName; + JsepSignalingState mState; + uint32_t mNegotiations; +}; + +} // namespace mozilla + +#endif diff --git a/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp new file mode 100644 index 000000000..f5015dda2 --- /dev/null +++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp @@ -0,0 +1,2497 @@ +/* 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 "logging.h" + +#include "signaling/src/jsep/JsepSessionImpl.h" +#include +#include +#include +#include + +#include "nspr.h" +#include "nss.h" +#include "pk11pub.h" +#include "nsDebug.h" + +#include +#include + +#include "signaling/src/jsep/JsepTrack.h" +#include "signaling/src/jsep/JsepTrack.h" +#include "signaling/src/jsep/JsepTransport.h" +#include "signaling/src/sdp/Sdp.h" +#include "signaling/src/sdp/SipccSdp.h" +#include "signaling/src/sdp/SipccSdpParser.h" +#include "mozilla/net/DataChannelProtocol.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, 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; +} + +nsresult +JsepSessionImpl::Init() +{ + mLastError.clear(); + + MOZ_ASSERT(!mSessionId, "Init called more than once"); + + nsresult rv = SetupIds(); + NS_ENSURE_SUCCESS(rv, rv); + + SetupDefaultCodecs(); + SetupDefaultRtpExtensions(); + + return NS_OK; +} + +// Helper function to find the track for a given m= section. +template +typename std::vector::iterator +FindTrackByLevel(std::vector& tracks, size_t level) +{ + for (auto t = tracks.begin(); t != tracks.end(); ++t) { + if (t->mAssignedMLine.isSome() && + (*t->mAssignedMLine == level)) { + return t; + } + } + + return tracks.end(); +} + +template +typename std::vector::iterator +FindTrackByIds(std::vector& tracks, + const std::string& streamId, + const std::string& trackId) +{ + for (auto t = tracks.begin(); t != tracks.end(); ++t) { + if (t->mTrack->GetStreamId() == streamId && + (t->mTrack->GetTrackId() == trackId)) { + return t; + } + } + + return tracks.end(); +} + +template +typename std::vector::iterator +FindUnassignedTrackByType(std::vector& tracks, + SdpMediaSection::MediaType type) +{ + for (auto t = tracks.begin(); t != tracks.end(); ++t) { + if (!t->mAssignedMLine.isSome() && + (t->mTrack->GetMediaType() == type)) { + return t; + } + } + + return tracks.end(); +} + +nsresult +JsepSessionImpl::AddTrack(const RefPtr& track) +{ + mLastError.clear(); + MOZ_ASSERT(track->GetDirection() == sdp::kSend); + + if (track->GetMediaType() != SdpMediaSection::kApplication) { + track->SetCNAME(mCNAME); + + if (track->GetSsrcs().empty()) { + uint32_t ssrc; + nsresult rv = CreateSsrc(&ssrc); + NS_ENSURE_SUCCESS(rv, rv); + track->AddSsrc(ssrc); + } + } + + track->PopulateCodecs(mSupportedCodecs.values); + + JsepSendingTrack strack; + strack.mTrack = track; + + mLocalTracks.push_back(strack); + + return NS_OK; +} + +nsresult +JsepSessionImpl::RemoveTrack(const std::string& streamId, + const std::string& trackId) +{ + if (mState != kJsepStateStable) { + JSEP_SET_ERROR("Removing tracks outside of stable is unsupported."); + return NS_ERROR_UNEXPECTED; + } + + auto track = FindTrackByIds(mLocalTracks, streamId, trackId); + + if (track == mLocalTracks.end()) { + return NS_ERROR_INVALID_ARG; + } + + mLocalTracks.erase(track); + return NS_OK; +} + +nsresult +JsepSessionImpl::SetIceCredentials(const std::string& ufrag, + const std::string& pwd) +{ + mLastError.clear(); + mIceUfrag = ufrag; + mIcePwd = pwd; + + return NS_OK; +} + +nsresult +JsepSessionImpl::SetBundlePolicy(JsepBundlePolicy policy) +{ + mLastError.clear(); + 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(std::vector& extensions, + const std::string& extensionName, + SdpDirectionAttribute::Direction direction) +{ + mLastError.clear(); + + if (extensions.size() + 1 > UINT16_MAX) { + JSEP_SET_ERROR("Too many rtp extensions have been added"); + return NS_ERROR_FAILURE; + } + + SdpExtmapAttributeList::Extmap extmap = + { static_cast(extensions.size() + 1), + direction, + direction != SdpDirectionAttribute::kSendrecv, // do we want to specify direction? + extensionName, + "" }; + + extensions.push_back(extmap); + return NS_OK; +} + +nsresult +JsepSessionImpl::AddAudioRtpExtension(const std::string& extensionName, + SdpDirectionAttribute::Direction direction) +{ + return AddRtpExtension(mAudioRtpExtensions, extensionName, direction); +} + +nsresult +JsepSessionImpl::AddVideoRtpExtension(const std::string& extensionName, + SdpDirectionAttribute::Direction direction) +{ + return AddRtpExtension(mVideoRtpExtensions, extensionName, direction); +} + +template +std::vector> +GetTracks(const std::vector& wrappedTracks) +{ + std::vector> result; + for (auto i = wrappedTracks.begin(); i != wrappedTracks.end(); ++i) { + result.push_back(i->mTrack); + } + return result; +} + +nsresult +JsepSessionImpl::ReplaceTrack(const std::string& oldStreamId, + const std::string& oldTrackId, + const std::string& newStreamId, + const std::string& newTrackId) +{ + auto it = FindTrackByIds(mLocalTracks, oldStreamId, oldTrackId); + + if (it == mLocalTracks.end()) { + JSEP_SET_ERROR("Track " << oldStreamId << "/" << oldTrackId + << " was never added."); + return NS_ERROR_INVALID_ARG; + } + + if (FindTrackByIds(mLocalTracks, newStreamId, newTrackId) != + mLocalTracks.end()) { + JSEP_SET_ERROR("Track " << newStreamId << "/" << newTrackId + << " was already added."); + return NS_ERROR_INVALID_ARG; + } + + it->mTrack->SetStreamId(newStreamId); + it->mTrack->SetTrackId(newTrackId); + + return NS_OK; +} + +nsresult +JsepSessionImpl::SetParameters(const std::string& streamId, + const std::string& trackId, + const std::vector& constraints) +{ + auto it = FindTrackByIds(mLocalTracks, streamId, trackId); + + if (it == mLocalTracks.end()) { + JSEP_SET_ERROR("Track " << streamId << "/" << trackId << " was never added."); + return NS_ERROR_INVALID_ARG; + } + + // Add RtpStreamId Extmap + // SdpDirectionAttribute::Direction is a bitmask + SdpDirectionAttribute::Direction addVideoExt = SdpDirectionAttribute::kInactive; + for (auto constraintEntry: constraints) { + if (constraintEntry.rid != "") { + if (it->mTrack->GetMediaType() == SdpMediaSection::kVideo) { + addVideoExt = static_cast(addVideoExt + | it->mTrack->GetDirection()); + } + } + } + if (addVideoExt != SdpDirectionAttribute::kInactive) { + AddVideoRtpExtension("urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id", addVideoExt); + } + + it->mTrack->SetJsConstraints(constraints); + return NS_OK; +} + +nsresult +JsepSessionImpl::GetParameters(const std::string& streamId, + const std::string& trackId, + std::vector* outConstraints) +{ + auto it = FindTrackByIds(mLocalTracks, streamId, trackId); + + if (it == mLocalTracks.end()) { + JSEP_SET_ERROR("Track " << streamId << "/" << trackId << " was never added."); + return NS_ERROR_INVALID_ARG; + } + + it->mTrack->GetJsConstraints(outConstraints); + return NS_OK; +} + +std::vector> +JsepSessionImpl::GetLocalTracks() const +{ + return GetTracks(mLocalTracks); +} + +std::vector> +JsepSessionImpl::GetRemoteTracks() const +{ + return GetTracks(mRemoteTracks); +} + +std::vector> +JsepSessionImpl::GetRemoteTracksAdded() const +{ + return GetTracks(mRemoteTracksAdded); +} + +std::vector> +JsepSessionImpl::GetRemoteTracksRemoved() const +{ + return GetTracks(mRemoteTracksRemoved); +} + +nsresult +JsepSessionImpl::SetupOfferMSections(const JsepOfferOptions& options, Sdp* sdp) +{ + // First audio, then video, then datachannel, for interop + // TODO(bug 1121756): We need to group these by stream-id, _then_ by media + // type, according to the spec. However, this is not going to interop with + // older versions of Firefox if a video-only stream is added before an + // audio-only stream. + // We should probably wait until 38 is ESR before trying to do this. + nsresult rv = SetupOfferMSectionsByType( + SdpMediaSection::kAudio, options.mOfferToReceiveAudio, sdp); + + NS_ENSURE_SUCCESS(rv, rv); + + rv = SetupOfferMSectionsByType( + SdpMediaSection::kVideo, options.mOfferToReceiveVideo, sdp); + + NS_ENSURE_SUCCESS(rv, rv); + + if (!(options.mDontOfferDataChannel.isSome() && + *options.mDontOfferDataChannel)) { + rv = SetupOfferMSectionsByType( + SdpMediaSection::kApplication, Maybe(), sdp); + + NS_ENSURE_SUCCESS(rv, rv); + } + + if (!sdp->GetMediaSectionCount()) { + JSEP_SET_ERROR("Cannot create an offer with no local tracks, " + "no offerToReceiveAudio/Video, and no DataChannel."); + return NS_ERROR_INVALID_ARG; + } + + return NS_OK; +} + +nsresult +JsepSessionImpl::SetupOfferMSectionsByType(SdpMediaSection::MediaType mediatype, + Maybe offerToReceiveMaybe, + Sdp* sdp) +{ + // Convert the Maybe into a size_t*, since that is more readable, especially + // when using it as an in/out param. + size_t offerToReceiveCount; + size_t* offerToReceiveCountPtr = nullptr; + + if (offerToReceiveMaybe) { + offerToReceiveCount = *offerToReceiveMaybe; + offerToReceiveCountPtr = &offerToReceiveCount; + } + + // Make sure every local track has an m-section + nsresult rv = BindLocalTracks(mediatype, sdp); + NS_ENSURE_SUCCESS(rv, rv); + + // Make sure that m-sections that previously had a remote track have the + // recv bit set. Only matters for renegotiation. + rv = BindRemoteTracks(mediatype, sdp, offerToReceiveCountPtr); + NS_ENSURE_SUCCESS(rv, rv); + + // If we need more recv sections, start setting the recv bit on other + // msections. If not, disable msections that have no tracks. + rv = SetRecvAsNeededOrDisable(mediatype, + sdp, + offerToReceiveCountPtr); + NS_ENSURE_SUCCESS(rv, rv); + + // If we still don't have enough recv m-sections, add some. + if (offerToReceiveCountPtr && *offerToReceiveCountPtr) { + rv = AddRecvonlyMsections(mediatype, *offerToReceiveCountPtr, sdp); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult +JsepSessionImpl::BindLocalTracks(SdpMediaSection::MediaType mediatype, Sdp* sdp) +{ + for (JsepSendingTrack& track : mLocalTracks) { + if (mediatype != track.mTrack->GetMediaType()) { + continue; + } + + SdpMediaSection* msection; + if (track.mAssignedMLine.isSome()) { + msection = &sdp->GetMediaSection(*track.mAssignedMLine); + } else { + nsresult rv = GetFreeMsectionForSend(track.mTrack->GetMediaType(), + sdp, + &msection); + NS_ENSURE_SUCCESS(rv, rv); + track.mAssignedMLine = Some(msection->GetLevel()); + } + + track.mTrack->AddToOffer(msection); + } + return NS_OK; +} + +nsresult +JsepSessionImpl::BindRemoteTracks(SdpMediaSection::MediaType mediatype, + Sdp* sdp, + size_t* offerToReceive) +{ + for (JsepReceivingTrack& track : mRemoteTracks) { + if (mediatype != track.mTrack->GetMediaType()) { + continue; + } + + if (!track.mAssignedMLine.isSome()) { + MOZ_ASSERT(false); + continue; + } + + auto& msection = sdp->GetMediaSection(*track.mAssignedMLine); + + if (mSdpHelper.MsectionIsDisabled(msection)) { + // TODO(bug 1095226) Content probably disabled this? Should we allow + // content to do this? + continue; + } + + track.mTrack->AddToOffer(&msection); + + if (offerToReceive && *offerToReceive) { + --(*offerToReceive); + } + } + + return NS_OK; +} + +nsresult +JsepSessionImpl::SetRecvAsNeededOrDisable(SdpMediaSection::MediaType mediatype, + Sdp* sdp, + size_t* offerToRecv) +{ + for (size_t i = 0; i < sdp->GetMediaSectionCount(); ++i) { + auto& msection = sdp->GetMediaSection(i); + + if (mSdpHelper.MsectionIsDisabled(msection) || + msection.GetMediaType() != mediatype || + msection.IsReceiving()) { + continue; + } + + if (offerToRecv) { + if (*offerToRecv) { + SetupOfferToReceiveMsection(&msection); + --(*offerToRecv); + continue; + } + } else if (msection.IsSending()) { + SetupOfferToReceiveMsection(&msection); + continue; + } + + if (!msection.IsSending()) { + // Unused m-section, and no reason to offer to recv on it + mSdpHelper.DisableMsection(sdp, &msection); + } + } + + return NS_OK; +} + +void +JsepSessionImpl::SetupOfferToReceiveMsection(SdpMediaSection* offer) +{ + // Create a dummy recv track, and have it add codecs, set direction, etc. + RefPtr dummy = new JsepTrack(offer->GetMediaType(), + "", + "", + sdp::kRecv); + dummy->PopulateCodecs(mSupportedCodecs.values); + dummy->AddToOffer(offer); +} + +nsresult +JsepSessionImpl::AddRecvonlyMsections(SdpMediaSection::MediaType mediatype, + size_t count, + Sdp* sdp) +{ + while (count--) { + nsresult rv = CreateOfferMSection( + mediatype, + mSdpHelper.GetProtocolForMediaType(mediatype), + SdpDirectionAttribute::kRecvonly, + sdp); + + NS_ENSURE_SUCCESS(rv, rv); + SetupOfferToReceiveMsection( + &sdp->GetMediaSection(sdp->GetMediaSectionCount() - 1)); + } + return NS_OK; +} + +// This function creates a skeleton SDP based on the old descriptions +// (ie; all m-sections are inactive). +nsresult +JsepSessionImpl::AddReofferMsections(const Sdp& oldLocalSdp, + const Sdp& oldAnswer, + Sdp* newSdp) +{ + nsresult rv; + + for (size_t i = 0; i < oldLocalSdp.GetMediaSectionCount(); ++i) { + // We do not set the direction in this function (or disable when previously + // disabled), that happens in |SetupOfferMSectionsByType| + rv = CreateOfferMSection(oldLocalSdp.GetMediaSection(i).GetMediaType(), + oldLocalSdp.GetMediaSection(i).GetProtocol(), + SdpDirectionAttribute::kInactive, + newSdp); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mSdpHelper.CopyStickyParams(oldAnswer.GetMediaSection(i), + &newSdp->GetMediaSection(i)); + NS_ENSURE_SUCCESS(rv, rv); + } + + 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 (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)); + } + + mids.push_back(attrs.GetMid()); + } + } + + if (mids.size() > 1) { + UniquePtr groupAttr(new SdpGroupAttributeList); + groupAttr->PushEntry(SdpGroupAttributeList::kBundle, mids); + sdp->GetAttributeList().SetAttribute(groupAttr.release()); + } +} + +nsresult +JsepSessionImpl::GetRemoteIds(const Sdp& sdp, + const SdpMediaSection& msection, + std::string* streamId, + std::string* trackId) +{ + nsresult rv = mSdpHelper.GetIdsFromMsid(sdp, msection, streamId, trackId); + if (rv == NS_ERROR_NOT_AVAILABLE) { + *streamId = mDefaultRemoteStreamId; + + if (!mDefaultRemoteTrackIdsByLevel.count(msection.GetLevel())) { + // Generate random track ids. + if (!mUuidGen->Generate(trackId)) { + JSEP_SET_ERROR("Failed to generate UUID for JsepTrack"); + return NS_ERROR_FAILURE; + } + + mDefaultRemoteTrackIdsByLevel[msection.GetLevel()] = *trackId; + } else { + *trackId = mDefaultRemoteTrackIdsByLevel[msection.GetLevel()]; + } + return NS_OK; + } + + if (NS_SUCCEEDED(rv)) { + // If, for whatever reason, the other end renegotiates with an msid where + // there wasn't one before, don't allow the old default to pop up again + // later. + mDefaultRemoteTrackIdsByLevel.erase(msection.GetLevel()); + } + + return rv; +} + +nsresult +JsepSessionImpl::CreateOffer(const JsepOfferOptions& options, + std::string* offer) +{ + mLastError.clear(); + + if (mState != kJsepStateStable) { + JSEP_SET_ERROR("Cannot create offer in state " << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + + // Undo track assignments from a previous call to CreateOffer + // (ie; if the track has not been negotiated yet, it doesn't necessarily need + // to stay in the same m-section that it was in) + for (JsepSendingTrack& trackWrapper : mLocalTracks) { + if (!trackWrapper.mTrack->GetNegotiatedDetails()) { + trackWrapper.mAssignedMLine.reset(); + } + } + + UniquePtr sdp; + + // Make the basic SDP that is common to offer/answer. + nsresult rv = CreateGenericSDP(&sdp); + NS_ENSURE_SUCCESS(rv, rv); + + if (mCurrentLocalDescription) { + rv = AddReofferMsections(*mCurrentLocalDescription, + *GetAnswer(), + sdp.get()); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Ensure that we have all the m-sections we need, and disable extras + rv = SetupOfferMSections(options, sdp.get()); + NS_ENSURE_SUCCESS(rv, rv); + + SetupBundle(sdp.get()); + + if (mCurrentLocalDescription) { + rv = CopyPreviousTransportParams(*GetAnswer(), + *mCurrentLocalDescription, + *sdp, + sdp.get()); + NS_ENSURE_SUCCESS(rv,rv); + } + + *offer = sdp->ToString(); + mGeneratedLocalDescription = Move(sdp); + ++mSessionVersion; + + return NS_OK; +} + +std::string +JsepSessionImpl::GetLocalDescription() const +{ + std::ostringstream os; + mozilla::Sdp* sdp = GetParsedLocalDescription(); + if (sdp) { + sdp->Serialize(os); + } + return os.str(); +} + +std::string +JsepSessionImpl::GetRemoteDescription() const +{ + std::ostringstream os; + mozilla::Sdp* sdp = GetParsedRemoteDescription(); + if (sdp) { + sdp->Serialize(os); + } + return os.str(); +} + +void +JsepSessionImpl::AddExtmap(SdpMediaSection* msection) const +{ + const auto* extensions = GetRtpExtensions(msection->GetMediaType()); + + if (extensions && !extensions->empty()) { + SdpExtmapAttributeList* extmap = new SdpExtmapAttributeList; + extmap->mExtmaps = *extensions; + msection->GetAttributeList().SetAttribute(extmap); + } +} + +void +JsepSessionImpl::AddMid(const std::string& mid, + SdpMediaSection* msection) const +{ + msection->GetAttributeList().SetAttribute(new SdpStringAttribute( + SdpAttribute::kMidAttribute, mid)); +} + +const std::vector* +JsepSessionImpl::GetRtpExtensions(SdpMediaSection::MediaType type) const +{ + switch (type) { + case SdpMediaSection::kAudio: + return &mAudioRtpExtensions; + case SdpMediaSection::kVideo: + return &mVideoRtpExtensions; + default: + return nullptr; + } +} + +void +JsepSessionImpl::AddCommonExtmaps(const SdpMediaSection& remoteMsection, + SdpMediaSection* msection) +{ + auto* ourExtensions = GetRtpExtensions(remoteMsection.GetMediaType()); + + if (ourExtensions) { + mSdpHelper.AddCommonExtmaps(remoteMsection, *ourExtensions, msection); + } +} + +nsresult +JsepSessionImpl::CreateAnswer(const JsepAnswerOptions& options, + std::string* answer) +{ + mLastError.clear(); + + if (mState != kJsepStateHaveRemoteOffer) { + JSEP_SET_ERROR("Cannot create answer in state " << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + + // This is the heart of the negotiation code. Depressing that it's + // so bad. + // + // Here's the current algorithm: + // 1. Walk through all the m-lines on the other side. + // 2. For each m-line, walk through all of our local tracks + // in sequence and see if any are unassigned. If so, assign + // them and mark it sendrecv, otherwise it's recvonly. + // 3. Just replicate their media attributes. + // 4. Profit. + UniquePtr sdp; + + // Make the basic SDP that is common to offer/answer. + nsresult rv = CreateGenericSDP(&sdp); + NS_ENSURE_SUCCESS(rv, rv); + + 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()); + + // Disable send for local tracks if the offer no longer allows it + // (i.e., the m-section is recvonly, inactive or disabled) + for (JsepSendingTrack& trackWrapper : mLocalTracks) { + if (!trackWrapper.mAssignedMLine.isSome()) { + continue; + } + + // Get rid of all m-line assignments that have not been negotiated + if (!trackWrapper.mTrack->GetNegotiatedDetails()) { + trackWrapper.mAssignedMLine.reset(); + continue; + } + + if (!offer.GetMediaSection(*trackWrapper.mAssignedMLine).IsReceiving()) { + trackWrapper.mAssignedMLine.reset(); + } + } + + size_t numMsections = offer.GetMediaSectionCount(); + + for (size_t i = 0; i < numMsections; ++i) { + const SdpMediaSection& remoteMsection = offer.GetMediaSection(i); + rv = CreateAnswerMSection(options, i, remoteMsection, sdp.get()); + NS_ENSURE_SUCCESS(rv, rv); + } + + 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,rv); + } + + *answer = sdp->ToString(); + mGeneratedLocalDescription = Move(sdp); + ++mSessionVersion; + + return NS_OK; +} + +nsresult +JsepSessionImpl::CreateOfferMSection(SdpMediaSection::MediaType mediatype, + SdpMediaSection::Protocol proto, + SdpDirectionAttribute::Direction dir, + Sdp* sdp) +{ + SdpMediaSection* msection = + &sdp->AddMediaSection(mediatype, dir, 0, proto, sdp::kIPv4, "0.0.0.0"); + + return EnableOfferMsection(msection); +} + +nsresult +JsepSessionImpl::GetFreeMsectionForSend( + SdpMediaSection::MediaType type, + Sdp* sdp, + SdpMediaSection** msectionOut) +{ + for (size_t i = 0; i < sdp->GetMediaSectionCount(); ++i) { + SdpMediaSection& msection = sdp->GetMediaSection(i); + // draft-ietf-rtcweb-jsep-08 says we should reclaim disabled m-sections + // regardless of media type. This breaks some pretty fundamental rules of + // SDP offer/answer, so we probably should not do it. + if (msection.GetMediaType() != type) { + continue; + } + + if (FindTrackByLevel(mLocalTracks, i) != mLocalTracks.end()) { + // Not free + continue; + } + + if (mSdpHelper.MsectionIsDisabled(msection)) { + // Was disabled; revive + nsresult rv = EnableOfferMsection(&msection); + NS_ENSURE_SUCCESS(rv, rv); + } + + *msectionOut = &msection; + return NS_OK; + } + + // Ok, no pre-existing m-section. Make a new one. + nsresult rv = CreateOfferMSection(type, + mSdpHelper.GetProtocolForMediaType(type), + SdpDirectionAttribute::kInactive, + sdp); + NS_ENSURE_SUCCESS(rv, rv); + + *msectionOut = &sdp->GetMediaSection(sdp->GetMediaSectionCount() - 1); + return NS_OK; +} + +nsresult +JsepSessionImpl::CreateAnswerMSection(const JsepAnswerOptions& options, + size_t mlineIndex, + const SdpMediaSection& remoteMsection, + Sdp* sdp) +{ + SdpMediaSection& msection = + sdp->AddMediaSection(remoteMsection.GetMediaType(), + SdpDirectionAttribute::kInactive, + 9, + remoteMsection.GetProtocol(), + sdp::kIPv4, + "0.0.0.0"); + + nsresult rv = mSdpHelper.CopyStickyParams(remoteMsection, &msection); + NS_ENSURE_SUCCESS(rv, rv); + + if (mSdpHelper.MsectionIsDisabled(remoteMsection)) { + mSdpHelper.DisableMsection(sdp, &msection); + return NS_OK; + } + + SdpSetupAttribute::Role role; + rv = DetermineAnswererSetupRole(remoteMsection, &role); + NS_ENSURE_SUCCESS(rv, rv); + + rv = AddTransportAttributes(&msection, role); + NS_ENSURE_SUCCESS(rv, rv); + + rv = SetRecvonlySsrc(&msection); + NS_ENSURE_SUCCESS(rv, rv); + + // Only attempt to match up local tracks if the offerer has elected to + // receive traffic. + if (remoteMsection.IsReceiving()) { + rv = BindMatchingLocalTrackToAnswer(&msection); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (remoteMsection.IsSending()) { + BindMatchingRemoteTrackToAnswer(&msection); + } + + if (!msection.IsReceiving() && !msection.IsSending()) { + mSdpHelper.DisableMsection(sdp, &msection); + return NS_OK; + } + + // Add extmap attributes. + AddCommonExtmaps(remoteMsection, &msection); + + if (msection.GetFormats().empty()) { + // Could not negotiate anything. Disable m-section. + mSdpHelper.DisableMsection(sdp, &msection); + } + + return NS_OK; +} + +nsresult +JsepSessionImpl::SetRecvonlySsrc(SdpMediaSection* msection) +{ + // If previous m-sections are disabled, we do not call this function for them + while (mRecvonlySsrcs.size() <= msection->GetLevel()) { + uint32_t ssrc; + nsresult rv = CreateSsrc(&ssrc); + NS_ENSURE_SUCCESS(rv, rv); + mRecvonlySsrcs.push_back(ssrc); + } + + std::vector ssrcs; + ssrcs.push_back(mRecvonlySsrcs[msection->GetLevel()]); + msection->SetSsrcs(ssrcs, mCNAME); + return NS_OK; +} + +nsresult +JsepSessionImpl::BindMatchingLocalTrackToAnswer(SdpMediaSection* msection) +{ + auto track = FindTrackByLevel(mLocalTracks, msection->GetLevel()); + + if (track == mLocalTracks.end()) { + track = FindUnassignedTrackByType(mLocalTracks, msection->GetMediaType()); + } + + if (track == mLocalTracks.end() && + msection->GetMediaType() == SdpMediaSection::kApplication) { + // If we are offered datachannel, we need to play along even if no track + // for it has been added yet. + std::string streamId; + std::string trackId; + + if (!mUuidGen->Generate(&streamId) || !mUuidGen->Generate(&trackId)) { + JSEP_SET_ERROR("Failed to generate UUIDs for datachannel track"); + return NS_ERROR_FAILURE; + } + + AddTrack(RefPtr( + new JsepTrack(SdpMediaSection::kApplication, streamId, trackId))); + track = FindUnassignedTrackByType(mLocalTracks, msection->GetMediaType()); + MOZ_ASSERT(track != mLocalTracks.end()); + } + + if (track != mLocalTracks.end()) { + track->mAssignedMLine = Some(msection->GetLevel()); + track->mTrack->AddToAnswer( + mPendingRemoteDescription->GetMediaSection(msection->GetLevel()), + msection); + } + + return NS_OK; +} + +nsresult +JsepSessionImpl::BindMatchingRemoteTrackToAnswer(SdpMediaSection* msection) +{ + auto it = FindTrackByLevel(mRemoteTracks, msection->GetLevel()); + if (it == mRemoteTracks.end()) { + MOZ_ASSERT(false); + JSEP_SET_ERROR("Failed to find remote track for local answer m-section"); + return NS_ERROR_FAILURE; + } + + it->mTrack->AddToAnswer( + mPendingRemoteDescription->GetMediaSection(msection->GetLevel()), + 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; +} + +nsresult +JsepSessionImpl::SetLocalDescription(JsepSdpType type, const std::string& sdp) +{ + mLastError.clear(); + + MOZ_MTLOG(ML_DEBUG, "SetLocalDescription type=" << type << "\nSDP=\n" + << sdp); + + if (type == kJsepSdpRollback) { + if (mState != kJsepStateHaveLocalOffer) { + JSEP_SET_ERROR("Cannot rollback local description in " + << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + + mPendingLocalDescription.reset(); + SetState(kJsepStateStable); + mTransports = mOldTransports; + mOldTransports.clear(); + return NS_OK; + } + + switch (mState) { + case kJsepStateStable: + if (type != kJsepSdpOffer) { + JSEP_SET_ERROR("Cannot set local answer in state " + << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + mIsOfferer = true; + break; + case kJsepStateHaveRemoteOffer: + if (type != kJsepSdpAnswer && type != kJsepSdpPranswer) { + JSEP_SET_ERROR("Cannot set local offer in state " + << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + break; + default: + JSEP_SET_ERROR("Cannot set local offer or answer in state " + << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + + UniquePtr parsed; + nsresult rv = ParseSdp(sdp, &parsed); + NS_ENSURE_SUCCESS(rv, rv); + + // Check that content hasn't done anything unsupported with the SDP + rv = ValidateLocalDescription(*parsed); + NS_ENSURE_SUCCESS(rv, rv); + + // Create transport objects. + mOldTransports = mTransports; // Save in case we need to rollback + mTransports.clear(); + for (size_t t = 0; t < parsed->GetMediaSectionCount(); ++t) { + mTransports.push_back(RefPtr(new JsepTransport)); + InitTransport(parsed->GetMediaSection(t), mTransports[t].get()); + } + + switch (type) { + case kJsepSdpOffer: + rv = SetLocalDescriptionOffer(Move(parsed)); + break; + case kJsepSdpAnswer: + case kJsepSdpPranswer: + rv = SetLocalDescriptionAnswer(type, Move(parsed)); + break; + case kJsepSdpRollback: + MOZ_CRASH(); // Handled above + } + + return rv; +} + +nsresult +JsepSessionImpl::SetLocalDescriptionOffer(UniquePtr offer) +{ + MOZ_ASSERT(mState == kJsepStateStable); + mPendingLocalDescription = Move(offer); + SetState(kJsepStateHaveLocalOffer); + return NS_OK; +} + +nsresult +JsepSessionImpl::SetLocalDescriptionAnswer(JsepSdpType type, + UniquePtr answer) +{ + MOZ_ASSERT(mState == kJsepStateHaveRemoteOffer); + mPendingLocalDescription = Move(answer); + + nsresult rv = ValidateAnswer(*mPendingRemoteDescription, + *mPendingLocalDescription); + NS_ENSURE_SUCCESS(rv, rv); + + rv = HandleNegotiatedSession(mPendingLocalDescription, + mPendingRemoteDescription); + NS_ENSURE_SUCCESS(rv, rv); + + mCurrentRemoteDescription = Move(mPendingRemoteDescription); + mCurrentLocalDescription = Move(mPendingLocalDescription); + mWasOffererLastTime = mIsOfferer; + + SetState(kJsepStateStable); + return NS_OK; +} + +nsresult +JsepSessionImpl::SetRemoteDescription(JsepSdpType type, const std::string& sdp) +{ + mLastError.clear(); + mRemoteTracksAdded.clear(); + mRemoteTracksRemoved.clear(); + + MOZ_MTLOG(ML_DEBUG, "SetRemoteDescription type=" << type << "\nSDP=\n" + << sdp); + + if (type == kJsepSdpRollback) { + if (mState != kJsepStateHaveRemoteOffer) { + JSEP_SET_ERROR("Cannot rollback remote description in " + << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + + mPendingRemoteDescription.reset(); + SetState(kJsepStateStable); + + // Update the remote tracks to what they were before the SetRemote + return SetRemoteTracksFromDescription(mCurrentRemoteDescription.get()); + } + + switch (mState) { + case kJsepStateStable: + if (type != kJsepSdpOffer) { + JSEP_SET_ERROR("Cannot set remote answer in state " + << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + mIsOfferer = false; + break; + case kJsepStateHaveLocalOffer: + case kJsepStateHaveRemotePranswer: + if (type != kJsepSdpAnswer && type != kJsepSdpPranswer) { + JSEP_SET_ERROR("Cannot set remote offer in state " + << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + break; + default: + JSEP_SET_ERROR("Cannot set remote offer or answer in current state " + << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + + // Parse. + UniquePtr parsed; + nsresult rv = ParseSdp(sdp, &parsed); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ValidateRemoteDescription(*parsed); + NS_ENSURE_SUCCESS(rv, rv); + + 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; + } + + switch (type) { + case kJsepSdpOffer: + rv = SetRemoteDescriptionOffer(Move(parsed)); + break; + case kJsepSdpAnswer: + case kJsepSdpPranswer: + rv = SetRemoteDescriptionAnswer(type, Move(parsed)); + break; + case kJsepSdpRollback: + MOZ_CRASH(); // Handled above + } + + if (NS_SUCCEEDED(rv)) { + mRemoteIsIceLite = iceLite; + mIceOptions = iceOptions; + mRemoteIceIsRestarting = iceRestarting; + } + + return rv; +} + +nsresult +JsepSessionImpl::HandleNegotiatedSession(const UniquePtr& local, + const UniquePtr& remote) +{ + bool remoteIceLite = + remote->GetAttributeList().HasAttribute(SdpAttribute::kIceLiteAttribute); + + mIceControlling = remoteIceLite || mIsOfferer; + + const Sdp& answer = mIsOfferer ? *remote : *local; + + SdpHelper::BundledMids bundledMids; + nsresult rv = mSdpHelper.GetBundledMids(answer, &bundledMids); + NS_ENSURE_SUCCESS(rv, rv); + + if (mTransports.size() < local->GetMediaSectionCount()) { + JSEP_SET_ERROR("Fewer transports set up than m-lines"); + MOZ_ASSERT(false); + return NS_ERROR_FAILURE; + } + + for (JsepSendingTrack& trackWrapper : mLocalTracks) { + trackWrapper.mTrack->ClearNegotiatedDetails(); + } + + for (JsepReceivingTrack& trackWrapper : mRemoteTracks) { + trackWrapper.mTrack->ClearNegotiatedDetails(); + } + + std::vector trackPairs; + + // Now walk through the m-sections, make sure they match, and create + // track pairs that describe the media to be set up. + for (size_t i = 0; i < local->GetMediaSectionCount(); ++i) { + // Skip disabled m-sections. + if (answer.GetMediaSection(i).GetPort() == 0) { + mTransports[i]->Close(); + continue; + } + + // The transport details are not necessarily on the m-section we're + // currently processing. + size_t transportLevel = i; + bool usingBundle = false; + { + const SdpMediaSection& answerMsection(answer.GetMediaSection(i)); + if (answerMsection.GetAttributeList().HasAttribute( + SdpAttribute::kMidAttribute)) { + if (bundledMids.count(answerMsection.GetAttributeList().GetMid())) { + const SdpMediaSection* masterBundleMsection = + bundledMids[answerMsection.GetAttributeList().GetMid()]; + transportLevel = masterBundleMsection->GetLevel(); + usingBundle = true; + if (i != transportLevel) { + mTransports[i]->Close(); + } + } + } + } + + RefPtr transport = mTransports[transportLevel]; + + rv = FinalizeTransport( + remote->GetMediaSection(transportLevel).GetAttributeList(), + answer.GetMediaSection(transportLevel).GetAttributeList(), + transport); + NS_ENSURE_SUCCESS(rv, rv); + + JsepTrackPair trackPair; + rv = MakeNegotiatedTrackPair(remote->GetMediaSection(i), + local->GetMediaSection(i), + transport, + usingBundle, + transportLevel, + &trackPair); + NS_ENSURE_SUCCESS(rv, rv); + + trackPairs.push_back(trackPair); + } + + JsepTrack::SetUniquePayloadTypes(GetTracks(mRemoteTracks)); + + // Ouch, this probably needs some dirty bit instead of just clearing + // stuff for renegotiation. + mNegotiatedTrackPairs = trackPairs; + + mGeneratedLocalDescription.reset(); + + mNegotiations++; + return NS_OK; +} + +nsresult +JsepSessionImpl::MakeNegotiatedTrackPair(const SdpMediaSection& remote, + const SdpMediaSection& local, + const RefPtr& transport, + bool usingBundle, + size_t transportLevel, + JsepTrackPair* trackPairOut) +{ + MOZ_ASSERT(transport->mComponents); + const SdpMediaSection& answer = mIsOfferer ? remote : local; + + bool sending; + bool receiving; + + if (mIsOfferer) { + receiving = answer.IsSending(); + sending = answer.IsReceiving(); + } else { + sending = answer.IsSending(); + receiving = answer.IsReceiving(); + } + + MOZ_MTLOG(ML_DEBUG, "Negotiated m= line" + << " index=" << local.GetLevel() + << " type=" << local.GetMediaType() + << " sending=" << sending + << " receiving=" << receiving); + + trackPairOut->mLevel = local.GetLevel(); + + MOZ_ASSERT(mRecvonlySsrcs.size() > local.GetLevel(), + "Failed to set the default ssrc for an active m-section"); + trackPairOut->mRecvonlySsrc = mRecvonlySsrcs[local.GetLevel()]; + + if (usingBundle) { + trackPairOut->mBundleLevel = Some(transportLevel); + } + + auto sendTrack = FindTrackByLevel(mLocalTracks, local.GetLevel()); + if (sendTrack != mLocalTracks.end()) { + sendTrack->mTrack->Negotiate(answer, remote); + sendTrack->mTrack->SetActive(sending); + trackPairOut->mSending = sendTrack->mTrack; + } else if (sending) { + JSEP_SET_ERROR("Failed to find local track for level " << + local.GetLevel() + << " in local SDP. This should never happen."); + NS_ASSERTION(false, "Failed to find local track for level"); + return NS_ERROR_FAILURE; + } + + auto recvTrack = FindTrackByLevel(mRemoteTracks, local.GetLevel()); + if (recvTrack != mRemoteTracks.end()) { + recvTrack->mTrack->Negotiate(answer, remote); + recvTrack->mTrack->SetActive(receiving); + trackPairOut->mReceiving = recvTrack->mTrack; + + if (receiving && + trackPairOut->mBundleLevel.isSome() && + recvTrack->mTrack->GetSsrcs().empty() && + recvTrack->mTrack->GetMediaType() != SdpMediaSection::kApplication) { + MOZ_MTLOG(ML_ERROR, "Bundled m-section has no ssrc attributes. " + "This may cause media packets to be dropped."); + } + } else if (receiving) { + JSEP_SET_ERROR("Failed to find remote track for level " + << local.GetLevel() + << " in remote SDP. This should never happen."); + NS_ASSERTION(false, "Failed to find remote track for level"); + return NS_ERROR_FAILURE; + } + + trackPairOut->mRtpTransport = transport; + + if (transport->mComponents == 2) { + // RTCP MUX or not. + // TODO(bug 1095743): verify that the PTs are consistent with mux. + MOZ_MTLOG(ML_DEBUG, "RTCP-MUX is off"); + trackPairOut->mRtcpTransport = transport; + } + + return NS_OK; +} + +void +JsepSessionImpl::InitTransport(const SdpMediaSection& msection, + JsepTransport* transport) +{ + if (mSdpHelper.MsectionIsDisabled(msection)) { + transport->Close(); + return; + } + + if (mSdpHelper.HasRtcp(msection.GetProtocol())) { + transport->mComponents = 2; + } else { + transport->mComponents = 1; + } + + if (msection.GetAttributeList().HasAttribute(SdpAttribute::kMidAttribute)) { + transport->mTransportId = msection.GetAttributeList().GetMid(); + } else { + std::ostringstream os; + os << "level_" << msection.GetLevel() << "(no mid)"; + transport->mTransportId = os.str(); + } +} + +nsresult +JsepSessionImpl::FinalizeTransport(const SdpAttributeList& remote, + const SdpAttributeList& answer, + const RefPtr& transport) +{ + UniquePtr ice = MakeUnique(); + + // We do sanity-checking for these in ParseSdp + ice->mUfrag = remote.GetIceUfrag(); + ice->mPwd = remote.GetIcePwd(); + if (remote.HasAttribute(SdpAttribute::kCandidateAttribute)) { + ice->mCandidates = remote.GetCandidate(); + } + + // 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 = mIsOfferer ? JsepDtlsTransport::kJsepDtlsServer + : JsepDtlsTransport::kJsepDtlsClient; + } else { + if (mIsOfferer) { + dtls->mRole = (answer.GetSetup().mRole == SdpSetupAttribute::kActive) + ? JsepDtlsTransport::kJsepDtlsServer + : JsepDtlsTransport::kJsepDtlsClient; + } else { + dtls->mRole = (answer.GetSetup().mRole == SdpSetupAttribute::kActive) + ? JsepDtlsTransport::kJsepDtlsClient + : JsepDtlsTransport::kJsepDtlsServer; + } + } + + transport->mIce = Move(ice); + transport->mDtls = 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) && + !mRemoteIceIsRestarting + ) { + // 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. + size_t numComponents = mTransports[i]->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) +{ + UniquePtr parsed = mParser.Parse(sdp); + if (!parsed) { + std::string error = "Failed to parse SDP: "; + mSdpHelper.appendSdpParseErrors(mParser.GetParseErrors(), &error); + JSEP_SET_ERROR(error); + return NS_ERROR_INVALID_ARG; + } + + // Verify that the JSEP rules for all SDP are followed + if (!parsed->GetMediaSectionCount()) { + JSEP_SET_ERROR("Description has no media sections"); + return NS_ERROR_INVALID_ARG; + } + + std::set trackIds; + + 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.GetIceUfrag().empty()) { + JSEP_SET_ERROR("Invalid description, no ice-ufrag attribute"); + return NS_ERROR_INVALID_ARG; + } + + if (mediaAttrs.GetIcePwd().empty()) { + JSEP_SET_ERROR("Invalid description, no ice-pwd attribute"); + return NS_ERROR_INVALID_ARG; + } + + if (!mediaAttrs.HasAttribute(SdpAttribute::kFingerprintAttribute)) { + JSEP_SET_ERROR("Invalid description, no fingerprint attribute"); + return NS_ERROR_INVALID_ARG; + } + + const SdpFingerprintAttributeList& fingerprints( + mediaAttrs.GetFingerprint()); + if (fingerprints.mFingerprints.empty()) { + JSEP_SET_ERROR("Invalid description, no supported fingerprint algorithms " + "present"); + return NS_ERROR_INVALID_ARG; + } + + if (mediaAttrs.HasAttribute(SdpAttribute::kSetupAttribute) && + mediaAttrs.GetSetup().mRole == SdpSetupAttribute::kHoldconn) { + JSEP_SET_ERROR("Description has illegal setup attribute " + "\"holdconn\" at level " + << i); + return NS_ERROR_INVALID_ARG; + } + + auto& formats = parsed->GetMediaSection(i).GetFormats(); + for (auto f = formats.begin(); f != formats.end(); ++f) { + uint16_t pt; + if (!SdpHelper::GetPtAsInt(*f, &pt)) { + JSEP_SET_ERROR("Payload type \"" + << *f << "\" is not a 16-bit unsigned int at level " + << i); + return NS_ERROR_INVALID_ARG; + } + } + + std::string streamId; + std::string trackId; + nsresult rv = mSdpHelper.GetIdsFromMsid(*parsed, + parsed->GetMediaSection(i), + &streamId, + &trackId); + + if (NS_SUCCEEDED(rv)) { + if (trackIds.count(trackId)) { + JSEP_SET_ERROR("track id:" << trackId + << " appears in more than one m-section at level " << i); + return NS_ERROR_INVALID_ARG; + } + + trackIds.insert(trackId); + } else if (rv != NS_ERROR_NOT_AVAILABLE) { + // Error has already been set + return rv; + } + + 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; + // TODO (bug 1204099): Make this check for reserved ranges. + if (!SdpHelper::GetPtAsInt(fmt, &payloadType) || payloadType > 127) { + JSEP_SET_ERROR("audio/video payload type is too large: " << fmt); + return NS_ERROR_INVALID_ARG; + } + if (forbidden.test(payloadType)) { + JSEP_SET_ERROR("Illegal audio/video payload type: " << fmt); + return NS_ERROR_INVALID_ARG; + } + } + } + } + + *parsedp = Move(parsed); + return NS_OK; +} + +nsresult +JsepSessionImpl::SetRemoteDescriptionOffer(UniquePtr offer) +{ + MOZ_ASSERT(mState == kJsepStateStable); + + // TODO(bug 1095780): Note that we create remote tracks even when + // They contain only codecs we can't negotiate or other craziness. + nsresult rv = SetRemoteTracksFromDescription(offer.get()); + NS_ENSURE_SUCCESS(rv, rv); + + mPendingRemoteDescription = Move(offer); + + SetState(kJsepStateHaveRemoteOffer); + return NS_OK; +} + +nsresult +JsepSessionImpl::SetRemoteDescriptionAnswer(JsepSdpType type, + UniquePtr answer) +{ + MOZ_ASSERT(mState == kJsepStateHaveLocalOffer || + mState == kJsepStateHaveRemotePranswer); + + mPendingRemoteDescription = Move(answer); + + nsresult rv = ValidateAnswer(*mPendingLocalDescription, + *mPendingRemoteDescription); + NS_ENSURE_SUCCESS(rv, rv); + + // TODO(bug 1095780): Note that this creates remote tracks even if + // we offered sendonly and other side offered sendrecv or recvonly. + rv = SetRemoteTracksFromDescription(mPendingRemoteDescription.get()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = HandleNegotiatedSession(mPendingLocalDescription, + mPendingRemoteDescription); + NS_ENSURE_SUCCESS(rv, rv); + + mCurrentRemoteDescription = Move(mPendingRemoteDescription); + mCurrentLocalDescription = Move(mPendingLocalDescription); + mWasOffererLastTime = mIsOfferer; + + SetState(kJsepStateStable); + return NS_OK; +} + +nsresult +JsepSessionImpl::SetRemoteTracksFromDescription(const Sdp* remoteDescription) +{ + // Unassign all remote tracks + for (auto i = mRemoteTracks.begin(); i != mRemoteTracks.end(); ++i) { + i->mAssignedMLine.reset(); + } + + // This will not exist if we're rolling back the first remote description + if (remoteDescription) { + size_t numMlines = remoteDescription->GetMediaSectionCount(); + nsresult rv; + + // Iterate over the sdp, re-assigning or creating remote tracks as we go + for (size_t i = 0; i < numMlines; ++i) { + const SdpMediaSection& msection = remoteDescription->GetMediaSection(i); + + if (mSdpHelper.MsectionIsDisabled(msection) || !msection.IsSending()) { + continue; + } + + std::vector::iterator track; + + if (msection.GetMediaType() == SdpMediaSection::kApplication) { + // Datachannel doesn't have msid, just search by type + track = FindUnassignedTrackByType(mRemoteTracks, + msection.GetMediaType()); + } else { + std::string streamId; + std::string trackId; + rv = GetRemoteIds(*remoteDescription, msection, &streamId, &trackId); + NS_ENSURE_SUCCESS(rv, rv); + + track = FindTrackByIds(mRemoteTracks, streamId, trackId); + } + + if (track == mRemoteTracks.end()) { + RefPtr track; + rv = CreateReceivingTrack(i, *remoteDescription, msection, &track); + NS_ENSURE_SUCCESS(rv, rv); + + JsepReceivingTrack rtrack; + rtrack.mTrack = track; + rtrack.mAssignedMLine = Some(i); + mRemoteTracks.push_back(rtrack); + mRemoteTracksAdded.push_back(rtrack); + } else { + track->mAssignedMLine = Some(i); + } + } + } + + // Remove any unassigned remote track ids + for (size_t i = 0; i < mRemoteTracks.size();) { + if (!mRemoteTracks[i].mAssignedMLine.isSome()) { + mRemoteTracksRemoved.push_back(mRemoteTracks[i]); + mRemoteTracks.erase(mRemoteTracks.begin() + i); + } else { + ++i; + } + } + + return NS_OK; +} + +nsresult +JsepSessionImpl::ValidateLocalDescription(const Sdp& description) +{ + // TODO(bug 1095226): Better checking. + if (!mGeneratedLocalDescription) { + JSEP_SET_ERROR("Calling SetLocal without first calling CreateOffer/Answer" + " is not supported."); + return NS_ERROR_UNEXPECTED; + } + + if (description.GetMediaSectionCount() != + mGeneratedLocalDescription->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 = mGeneratedLocalDescription->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; + } + } + + // 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 (!mCurrentRemoteDescription || !mCurrentLocalDescription) { + // Not renegotiation; checks for whether a remote answer are consistent + // with our offer are handled in ValidateAnswer() + return NS_OK; + } + + if (mCurrentRemoteDescription->GetMediaSectionCount() > + description.GetMediaSectionCount()) { + JSEP_SET_ERROR("New remote description has fewer m-sections than the " + "previous remote description."); + return NS_ERROR_INVALID_ARG; + } + + // 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); + // 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::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; + } + + 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 (!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; + } + } + + return NS_OK; +} + +nsresult +JsepSessionImpl::CreateReceivingTrack(size_t mline, + const Sdp& sdp, + const SdpMediaSection& msection, + RefPtr* track) +{ + std::string streamId; + std::string trackId; + + nsresult rv = GetRemoteIds(sdp, msection, &streamId, &trackId); + NS_ENSURE_SUCCESS(rv, rv); + + *track = new JsepTrack(msection.GetMediaType(), + streamId, + trackId, + sdp::kRecv); + + (*track)->SetCNAME(mSdpHelper.GetCNAME(msection)); + (*track)->PopulateCodecs(mSupportedCodecs.values); + + 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. + + auto origin = + SdpOrigin("mozilla...THIS_IS_SDPARTA-" MOZ_APP_UA_VERSION, + 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 fp = mDtlsFingerprints.begin(); fp != mDtlsFingerprints.end(); + ++fp) { + fpl->PushEntry(fp->mAlgorithm, fp->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 = 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; +} + +nsresult +JsepSessionImpl::CreateSsrc(uint32_t* ssrc) +{ + do { + SECStatus rv = PK11_GenerateRandom( + reinterpret_cast(ssrc), sizeof(uint32_t)); + if (rv != SECSuccess) { + JSEP_SET_ERROR("Failed to generate SSRC, error=" << rv); + return NS_ERROR_FAILURE; + } + } while (mSsrcs.count(*ssrc)); + mSsrcs.insert(*ssrc); + + return NS_OK; +} + +void +JsepSessionImpl::SetupDefaultCodecs() +{ + // Supported audio codecs. + // 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. + mSupportedCodecs.values.push_back(new JsepAudioCodecDescription( + "109", + "opus", + 48000, + 2, + 960, +#ifdef WEBRTC_GONK + // TODO Move this elsewhere to be adaptive to rate - Bug 1207925 + 16000 // B2G uses lower capture sampling rate +#else + 40000 +#endif + )); + + mSupportedCodecs.values.push_back(new JsepAudioCodecDescription( + "9", + "G722", + 8000, + 1, + 320, + 64000)); + + // packet size and bitrate values below copied from sipcc. + // May need reevaluation from a media expert. + mSupportedCodecs.values.push_back( + new JsepAudioCodecDescription("0", + "PCMU", + 8000, + 1, + 8000 / 50, // frequency / 50 + 8 * 8000 * 1 // 8 * frequency * channels + )); + + mSupportedCodecs.values.push_back( + new JsepAudioCodecDescription("8", + "PCMA", + 8000, + 1, + 8000 / 50, // frequency / 50 + 8 * 8000 * 1 // 8 * frequency * channels + )); + + // note: because telephone-event is effectively a marker codec that indicates + // that dtmf rtp packets may be passed, the packetSize and bitRate fields + // don't make sense here. For now, use zero. (mjf) + mSupportedCodecs.values.push_back( + new JsepAudioCodecDescription("101", + "telephone-event", + 8000, + 1, + 0, // packetSize doesn't make sense here + 0 // bitRate doesn't make sense here + )); + + // Supported video codecs. + // Note: order here implies priority for building offers! + JsepVideoCodecDescription* vp8 = new JsepVideoCodecDescription( + "120", + "VP8", + 90000 + ); + // Defaults for mandatory params + vp8->mConstraints.maxFs = 12288; // Enough for 2048x1536 + vp8->mConstraints.maxFps = 60; + mSupportedCodecs.values.push_back(vp8); + + JsepVideoCodecDescription* vp9 = new JsepVideoCodecDescription( + "121", + "VP9", + 90000 + ); + // Defaults for mandatory params + vp9->mConstraints.maxFs = 12288; // Enough for 2048x1536 + vp9->mConstraints.maxFps = 60; + mSupportedCodecs.values.push_back(vp9); + + JsepVideoCodecDescription* h264_1 = new JsepVideoCodecDescription( + "126", + "H264", + 90000 + ); + h264_1->mPacketizationMode = 1; + // Defaults for mandatory params + h264_1->mProfileLevelId = 0x42E00D; + mSupportedCodecs.values.push_back(h264_1); + + JsepVideoCodecDescription* h264_0 = new JsepVideoCodecDescription( + "97", + "H264", + 90000 + ); + h264_0->mPacketizationMode = 0; + // Defaults for mandatory params + h264_0->mProfileLevelId = 0x42E00D; + mSupportedCodecs.values.push_back(h264_0); + + JsepVideoCodecDescription* red = new JsepVideoCodecDescription( + "122", // payload type + "red", // codec name + 90000 // clock rate (match other video codecs) + ); + mSupportedCodecs.values.push_back(red); + + JsepVideoCodecDescription* ulpfec = new JsepVideoCodecDescription( + "123", // payload type + "ulpfec", // codec name + 90000 // clock rate (match other video codecs) + ); + mSupportedCodecs.values.push_back(ulpfec); + + mSupportedCodecs.values.push_back(new JsepApplicationCodecDescription( + "5000", + "webrtc-datachannel", + WEBRTC_DATACHANNEL_STREAMS_DEFAULT + )); + + // Update the redundant encodings for the RED codec with the supported + // codecs. Note: only uses the video codecs. + red->UpdateRedundantEncodings(mSupportedCodecs.values); +} + +void +JsepSessionImpl::SetupDefaultRtpExtensions() +{ + AddAudioRtpExtension("urn:ietf:params:rtp-hdrext:ssrc-audio-level", + SdpDirectionAttribute::Direction::kSendonly); +} + +void +JsepSessionImpl::SetState(JsepSignalingState state) +{ + if (state == mState) + return; + + MOZ_MTLOG(ML_NOTICE, "[" << mName << "]: " << + GetStateStr(mState) << " -> " << GetStateStr(state)); + mState = state; +} + +nsresult +JsepSessionImpl::AddRemoteIceCandidate(const std::string& candidate, + const std::string& mid, + uint16_t level) +{ + mLastError.clear(); + + mozilla::Sdp* sdp = GetParsedRemoteDescription(); + + if (!sdp) { + JSEP_SET_ERROR("Cannot add ICE candidate in state " << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + + return mSdpHelper.AddCandidateToSdp(sdp, candidate, mid, level); +} + +nsresult +JsepSessionImpl::AddLocalIceCandidate(const std::string& candidate, + uint16_t level, + std::string* mid, + bool* skipped) +{ + mLastError.clear(); + + mozilla::Sdp* sdp = GetParsedLocalDescription(); + + if (!sdp) { + JSEP_SET_ERROR("Cannot add ICE candidate in state " << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + + if (sdp->GetMediaSectionCount() <= level) { + // mainly here to make some testing less complicated, but also just in case + *skipped = true; + return NS_OK; + } + + if (mState == kJsepStateStable) { + const Sdp* answer(GetAnswer()); + if (mSdpHelper.IsBundleSlave(*answer, level)) { + // We do not add candidate attributes to bundled m-sections unless they + // are the "master" bundle m-section. + *skipped = true; + return NS_OK; + } + } + + nsresult rv = mSdpHelper.GetMidFromLevel(*sdp, level, mid); + if (NS_FAILED(rv)) { + return rv; + } + + *skipped = false; + + return mSdpHelper.AddCandidateToSdp(sdp, candidate, *mid, level); +} + +nsresult +JsepSessionImpl::UpdateDefaultCandidate( + const std::string& defaultCandidateAddr, + uint16_t defaultCandidatePort, + const std::string& defaultRtcpCandidateAddr, + uint16_t defaultRtcpCandidatePort, + uint16_t level) +{ + mLastError.clear(); + + mozilla::Sdp* sdp = GetParsedLocalDescription(); + + if (!sdp) { + JSEP_SET_ERROR("Cannot add ICE candidate in state " << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + + if (level >= sdp->GetMediaSectionCount()) { + return NS_OK; + } + + std::string defaultRtcpCandidateAddrCopy(defaultRtcpCandidateAddr); + if (mState == kJsepStateStable && mTransports[level]->mComponents == 1) { + // We know we're doing rtcp-mux by now. Don't create an rtcp attr. + defaultRtcpCandidateAddrCopy = ""; + defaultRtcpCandidatePort = 0; + } + + // If offer/answer isn't done, it is too early to tell whether these defaults + // need to be applied to other m-sections. + SdpHelper::BundledMids bundledMids; + if (mState == kJsepStateStable) { + nsresult rv = GetNegotiatedBundledMids(&bundledMids); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false); + mLastError += " (This should have been caught sooner!)"; + return NS_ERROR_FAILURE; + } + } + + mSdpHelper.SetDefaultAddresses( + defaultCandidateAddr, + defaultCandidatePort, + defaultRtcpCandidateAddrCopy, + defaultRtcpCandidatePort, + sdp, + level, + bundledMids); + + return NS_OK; +} + +nsresult +JsepSessionImpl::EndOfLocalCandidates(uint16_t level) +{ + mLastError.clear(); + + mozilla::Sdp* sdp = GetParsedLocalDescription(); + + if (!sdp) { + JSEP_SET_ERROR("Cannot mark end of local ICE candidates in state " + << GetStateStr(mState)); + return NS_ERROR_UNEXPECTED; + } + + if (level >= sdp->GetMediaSectionCount()) { + return NS_OK; + } + + // If offer/answer isn't done, it is too early to tell whether this update + // needs to be applied to other m-sections. + SdpHelper::BundledMids bundledMids; + if (mState == kJsepStateStable) { + nsresult rv = GetNegotiatedBundledMids(&bundledMids); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false); + mLastError += " (This should have been caught sooner!)"; + return NS_ERROR_FAILURE; + } + } + + mSdpHelper.SetIceGatheringComplete(sdp, + level, + bundledMids); + + return NS_OK; +} + +nsresult +JsepSessionImpl::GetNegotiatedBundledMids(SdpHelper::BundledMids* bundledMids) +{ + const Sdp* answerSdp = GetAnswer(); + + if (!answerSdp) { + return NS_OK; + } + + return mSdpHelper.GetBundledMids(*answerSdp, bundledMids); +} + +nsresult +JsepSessionImpl::EnableOfferMsection(SdpMediaSection* msection) +{ + // We assert here because adding rtcp-mux to a non-disabled m-section that + // did not already have rtcp-mux can cause problems. + MOZ_ASSERT(mSdpHelper.MsectionIsDisabled(*msection)); + + 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 there. + if (mSdpHelper.HasRtcp(msection->GetProtocol())) { + // Set RTCP-MUX. + msection->GetAttributeList().SetAttribute( + new SdpFlagAttribute(SdpAttribute::kRtcpMuxAttribute)); + } + + nsresult rv = AddTransportAttributes(msection, SdpSetupAttribute::kActpass); + NS_ENSURE_SUCCESS(rv, rv); + + rv = SetRecvonlySsrc(msection); + NS_ENSURE_SUCCESS(rv, rv); + + AddExtmap(msection); + + std::ostringstream osMid; + osMid << "sdparta_" << msection->GetLevel(); + AddMid(osMid.str(), msection); + + return NS_OK; +} + +mozilla::Sdp* +JsepSessionImpl::GetParsedLocalDescription() const +{ + if (mPendingLocalDescription) { + return mPendingLocalDescription.get(); + } else if (mCurrentLocalDescription) { + return mCurrentLocalDescription.get(); + } + + return nullptr; +} + +mozilla::Sdp* +JsepSessionImpl::GetParsedRemoteDescription() const +{ + if (mPendingRemoteDescription) { + return mPendingRemoteDescription.get(); + } else if (mCurrentRemoteDescription) { + return mCurrentRemoteDescription.get(); + } + + return nullptr; +} + +const Sdp* +JsepSessionImpl::GetAnswer() const +{ + return mWasOffererLastTime ? mCurrentRemoteDescription.get() + : mCurrentLocalDescription.get(); +} + +nsresult +JsepSessionImpl::Close() +{ + mLastError.clear(); + SetState(kJsepStateClosed); + return NS_OK; +} + +const std::string +JsepSessionImpl::GetLastError() const +{ + return mLastError; +} + +bool +JsepSessionImpl::AllLocalTracksAreAssigned() const +{ + for (auto i = mLocalTracks.begin(); i != mLocalTracks.end(); ++i) { + if (!i->mAssignedMLine.isSome()) { + return false; + } + } + + return true; +} + +} // namespace mozilla diff --git a/media/webrtc/signaling/src/jsep/JsepSessionImpl.h b/media/webrtc/signaling/src/jsep/JsepSessionImpl.h new file mode 100644 index 000000000..00c07d25e --- /dev/null +++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.h @@ -0,0 +1,352 @@ +/* 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 "signaling/src/jsep/JsepCodecDescription.h" +#include "signaling/src/jsep/JsepTrack.h" +#include "signaling/src/jsep/JsepSession.h" +#include "signaling/src/jsep/JsepTrack.h" +#include "signaling/src/sdp/SipccSdpParser.h" +#include "signaling/src/sdp/SdpHelper.h" +#include "signaling/src/common/PtrVector.h" + +namespace mozilla { + +class JsepUuidGenerator +{ +public: + virtual ~JsepUuidGenerator() {} + virtual bool Generate(std::string* id) = 0; +}; + +class JsepSessionImpl : public JsepSession +{ +public: + JsepSessionImpl(const std::string& name, UniquePtr uuidgen) + : JsepSession(name), + mIsOfferer(false), + mWasOffererLastTime(false), + mIceControlling(false), + mRemoteIsIceLite(false), + mRemoteIceIsRestarting(false), + mBundlePolicy(kBundleBalanced), + mSessionId(0), + mSessionVersion(0), + mUuidGen(Move(uuidgen)), + mSdpHelper(&mLastError) + { + } + + // Implement JsepSession methods. + virtual nsresult Init() override; + + virtual nsresult AddTrack(const RefPtr& track) override; + + virtual nsresult RemoveTrack(const std::string& streamId, + const std::string& trackId) override; + + virtual nsresult SetIceCredentials(const std::string& ufrag, + const std::string& pwd) override; + virtual const std::string& GetUfrag() const override { return mIceUfrag; } + virtual const std::string& GetPwd() const override { return mIcePwd; } + nsresult SetBundlePolicy(JsepBundlePolicy policy) override; + + virtual bool + RemoteIsIceLite() const override + { + return mRemoteIsIceLite; + } + + virtual bool + RemoteIceIsRestarting() const override + { + return mRemoteIceIsRestarting; + } + + virtual std::vector + GetIceOptions() const override + { + return mIceOptions; + } + + virtual nsresult AddDtlsFingerprint(const std::string& algorithm, + const std::vector& value) override; + + nsresult AddRtpExtension(std::vector& extensions, + const std::string& extensionName, + SdpDirectionAttribute::Direction direction); + 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 std::vector& + Codecs() override + { + return mSupportedCodecs.values; + } + + virtual nsresult ReplaceTrack(const std::string& oldStreamId, + const std::string& oldTrackId, + const std::string& newStreamId, + const std::string& newTrackId) override; + + virtual nsresult SetParameters( + const std::string& streamId, + const std::string& trackId, + const std::vector& constraints) override; + + virtual nsresult GetParameters( + const std::string& streamId, + const std::string& trackId, + std::vector* outConstraints) override; + + virtual std::vector> GetLocalTracks() const override; + + virtual std::vector> GetRemoteTracks() const override; + + virtual std::vector> + GetRemoteTracksAdded() const override; + + virtual std::vector> + GetRemoteTracksRemoved() const override; + + virtual nsresult CreateOffer(const JsepOfferOptions& options, + std::string* offer) override; + + virtual nsresult CreateAnswer(const JsepAnswerOptions& options, + std::string* answer) override; + + virtual std::string GetLocalDescription() const override; + + virtual std::string GetRemoteDescription() const override; + + virtual nsresult SetLocalDescription(JsepSdpType type, + const std::string& sdp) override; + + virtual nsresult SetRemoteDescription(JsepSdpType type, + const std::string& sdp) override; + + virtual nsresult AddRemoteIceCandidate(const std::string& candidate, + const std::string& mid, + uint16_t level) override; + + virtual nsresult AddLocalIceCandidate(const std::string& candidate, + 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, + uint16_t level) override; + + virtual nsresult EndOfLocalCandidates(uint16_t level) override; + + virtual nsresult Close() override; + + virtual const std::string GetLastError() const override; + + virtual bool + IsIceControlling() const override + { + return mIceControlling; + } + + virtual bool + IsOfferer() const + { + return mIsOfferer; + } + + // Access transports. + virtual std::vector> + GetTransports() const override + { + return mTransports; + } + + virtual std::vector + GetNegotiatedTrackPairs() const override + { + return mNegotiatedTrackPairs; + } + + virtual bool AllLocalTracksAreAssigned() const override; + +private: + struct JsepDtlsFingerprint { + std::string mAlgorithm; + std::vector mValue; + }; + + struct JsepSendingTrack { + RefPtr mTrack; + Maybe mAssignedMLine; + }; + + struct JsepReceivingTrack { + RefPtr mTrack; + Maybe mAssignedMLine; + }; + + // Non-const so it can set mLastError + nsresult CreateGenericSDP(UniquePtr* sdp); + void AddExtmap(SdpMediaSection* msection) const; + void AddMid(const std::string& mid, SdpMediaSection* msection) const; + const std::vector* GetRtpExtensions( + SdpMediaSection::MediaType type) const; + + void AddCommonExtmaps(const SdpMediaSection& remoteMsection, + SdpMediaSection* msection); + nsresult SetupIds(); + nsresult CreateSsrc(uint32_t* ssrc); + void SetupDefaultCodecs(); + void SetupDefaultRtpExtensions(); + 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); + nsresult ValidateRemoteDescription(const Sdp& description); + nsresult ValidateAnswer(const Sdp& offer, const Sdp& answer); + nsresult SetRemoteTracksFromDescription(const Sdp* remoteDescription); + // Non-const because we use our Uuid generator + nsresult CreateReceivingTrack(size_t mline, + const Sdp& sdp, + const SdpMediaSection& msection, + RefPtr* track); + 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); + nsresult SetupOfferMSections(const JsepOfferOptions& options, Sdp* sdp); + // Non-const so it can assign m-line index to tracks + nsresult SetupOfferMSectionsByType(SdpMediaSection::MediaType type, + Maybe offerToReceive, + Sdp* sdp); + nsresult BindLocalTracks(SdpMediaSection::MediaType mediatype, + Sdp* sdp); + nsresult BindRemoteTracks(SdpMediaSection::MediaType mediatype, + Sdp* sdp, + size_t* offerToReceive); + nsresult SetRecvAsNeededOrDisable(SdpMediaSection::MediaType mediatype, + Sdp* sdp, + size_t* offerToRecv); + void SetupOfferToReceiveMsection(SdpMediaSection* offer); + nsresult AddRecvonlyMsections(SdpMediaSection::MediaType mediatype, + size_t count, + Sdp* sdp); + nsresult AddReofferMsections(const Sdp& oldLocalSdp, + const Sdp& oldAnswer, + Sdp* newSdp); + void SetupBundle(Sdp* sdp) const; + nsresult GetRemoteIds(const Sdp& sdp, + const SdpMediaSection& msection, + std::string* streamId, + std::string* trackId); + nsresult CreateOfferMSection(SdpMediaSection::MediaType type, + SdpMediaSection::Protocol proto, + SdpDirectionAttribute::Direction direction, + Sdp* sdp); + nsresult GetFreeMsectionForSend(SdpMediaSection::MediaType type, + Sdp* sdp, + SdpMediaSection** msection); + nsresult CreateAnswerMSection(const JsepAnswerOptions& options, + size_t mlineIndex, + const SdpMediaSection& remoteMsection, + Sdp* sdp); + nsresult SetRecvonlySsrc(SdpMediaSection* msection); + nsresult BindMatchingLocalTrackToAnswer(SdpMediaSection* msection); + nsresult BindMatchingRemoteTrackToAnswer(SdpMediaSection* msection); + nsresult DetermineAnswererSetupRole(const SdpMediaSection& remoteMsection, + SdpSetupAttribute::Role* rolep); + nsresult MakeNegotiatedTrackPair(const SdpMediaSection& remote, + const SdpMediaSection& local, + const RefPtr& transport, + bool usingBundle, + size_t transportLevel, + JsepTrackPair* trackPairOut); + void InitTransport(const SdpMediaSection& msection, JsepTransport* transport); + + nsresult FinalizeTransport(const SdpAttributeList& remote, + const SdpAttributeList& answer, + const RefPtr& transport); + + nsresult GetNegotiatedBundledMids(SdpHelper::BundledMids* bundledMids); + + nsresult EnableOfferMsection(SdpMediaSection* msection); + + mozilla::Sdp* GetParsedLocalDescription() const; + mozilla::Sdp* GetParsedRemoteDescription() const; + const Sdp* GetAnswer() const; + + std::vector mLocalTracks; + std::vector mRemoteTracks; + // By the most recent SetRemoteDescription + std::vector mRemoteTracksAdded; + std::vector mRemoteTracksRemoved; + std::vector > mTransports; + // So we can rollback + std::vector > mOldTransports; + std::vector mNegotiatedTrackPairs; + + bool mIsOfferer; + bool mWasOffererLastTime; + bool mIceControlling; + std::string mIceUfrag; + std::string mIcePwd; + bool mRemoteIsIceLite; + bool mRemoteIceIsRestarting; + std::vector mIceOptions; + JsepBundlePolicy mBundlePolicy; + std::vector mDtlsFingerprints; + uint64_t mSessionId; + uint64_t mSessionVersion; + std::vector mAudioRtpExtensions; + std::vector mVideoRtpExtensions; + UniquePtr mUuidGen; + std::string mDefaultRemoteStreamId; + std::map mDefaultRemoteTrackIdsByLevel; + 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; + // When an m-section doesn't have a local track, it still needs an ssrc, which + // is stored here. + std::vector mRecvonlySsrcs; + UniquePtr mGeneratedLocalDescription; // Created but not set. + UniquePtr mCurrentLocalDescription; + UniquePtr mCurrentRemoteDescription; + UniquePtr mPendingLocalDescription; + UniquePtr mPendingRemoteDescription; + PtrVector mSupportedCodecs; + std::string mLastError; + SipccSdpParser mParser; + SdpHelper mSdpHelper; +}; + +} // namespace mozilla + +#endif diff --git a/media/webrtc/signaling/src/jsep/JsepTrack.cpp b/media/webrtc/signaling/src/jsep/JsepTrack.cpp new file mode 100644 index 000000000..1b045d8ec --- /dev/null +++ b/media/webrtc/signaling/src/jsep/JsepTrack.cpp @@ -0,0 +1,531 @@ +/* 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 "signaling/src/jsep/JsepTrack.h" +#include "signaling/src/jsep/JsepCodecDescription.h" +#include "signaling/src/jsep/JsepTrackEncoding.h" + +#include + +namespace mozilla +{ +void +JsepTrack::GetNegotiatedPayloadTypes(std::vector* payloadTypes) +{ + if (!mNegotiatedDetails) { + return; + } + + for (const auto* encoding : mNegotiatedDetails->mEncodings.values) { + 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 (JsepCodecDescription* 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 (JsepCodecDescription* codec : *codecs) { + // We assume there are no dupes in negotiated codecs; unnegotiated codecs + // need to change if there is a clash. + if (!codec->mEnabled) { + continue; + } + + // Disable, and only re-enable if we can ensure it has a unique pt. + codec->mEnabled = false; + + uint16_t currentPt; + if (!codec->GetPtAsInt(¤tPt)) { + MOZ_ASSERT(false); + continue; + } + + if (!uniquePayloadTypes.count(currentPt)) { + codec->mEnabled = true; + uniquePayloadTypes.insert(currentPt); + continue; + } + + // |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. + if (!uniquePayloadTypes.count(freePt)) { + uniquePayloadTypes.insert(freePt); + codec->mEnabled = true; + std::ostringstream os; + os << freePt; + codec->mDefaultPt = os.str(); + break; + } + } + } +} + +void +JsepTrack::PopulateCodecs(const std::vector& prototype) +{ + for (const JsepCodecDescription* prototypeCodec : prototype) { + if (prototypeCodec->mType == mType) { + mPrototypeCodecs.values.push_back(prototypeCodec->Clone()); + mPrototypeCodecs.values.back()->mDirection = mDirection; + } + } + + EnsureNoDuplicatePayloadTypes(&mPrototypeCodecs.values); +} + +void +JsepTrack::AddToOffer(SdpMediaSection* offer) const +{ + AddToMsection(mPrototypeCodecs.values, offer); + if (mDirection == sdp::kSend) { + AddToMsection(mJsEncodeConstraints, sdp::kSend, offer); + } +} + +void +JsepTrack::AddToAnswer(const SdpMediaSection& offer, + SdpMediaSection* answer) const +{ + // We do not modify mPrototypeCodecs here, since we're only creating an + // answer. Once offer/answer concludes, we will update mPrototypeCodecs. + PtrVector codecs; + codecs.values = GetCodecClones(); + NegotiateCodecs(offer, &codecs.values); + if (codecs.values.empty()) { + return; + } + + AddToMsection(codecs.values, answer); + + if (mDirection == sdp::kSend) { + std::vector constraints; + std::vector rids; + GetRids(offer, sdp::kRecv, &rids); + NegotiateRids(rids, &constraints); + AddToMsection(constraints, sdp::kSend, answer); + } +} + +void +JsepTrack::AddToMsection(const std::vector& codecs, + SdpMediaSection* msection) const +{ + MOZ_ASSERT(msection->GetMediaType() == mType); + MOZ_ASSERT(!codecs.empty()); + + for (const JsepCodecDescription* codec : codecs) { + codec->AddToMediaSection(*msection); + } + + if (mDirection == sdp::kSend) { + if (msection->GetMediaType() != SdpMediaSection::kApplication) { + msection->SetSsrcs(mSsrcs, mCNAME); + msection->AddMsid(mStreamId, mTrackId); + } + msection->SetSending(true); + } else { + msection->SetReceiving(true); + } +} + +// Updates the |id| values in |constraintsList| with the rid values in |rids|, +// where necessary. +void +JsepTrack::NegotiateRids(const std::vector& rids, + std::vector* constraintsList) const +{ + for (const SdpRidAttributeList::Rid& rid : rids) { + if (!FindConstraints(rid.id, *constraintsList)) { + // Pair up the first JsConstraints with an empty id, if it exists. + JsConstraints* constraints = FindConstraints("", *constraintsList); + if (constraints) { + constraints->rid = rid.id; + } + } + } +} + +/* static */ +void +JsepTrack::AddToMsection(const std::vector& constraintsList, + sdp::Direction direction, + SdpMediaSection* msection) +{ + UniquePtr simulcast(new SdpSimulcastAttribute); + UniquePtr rids(new SdpRidAttributeList); + for (const JsConstraints& constraints : constraintsList) { + if (!constraints.rid.empty()) { + SdpRidAttributeList::Rid rid; + rid.id = constraints.rid; + rid.direction = direction; + rids->mRids.push_back(rid); + + SdpSimulcastAttribute::Version version; + version.choices.push_back(constraints.rid); + if (direction == sdp::kSend) { + simulcast->sendVersions.push_back(version); + } else { + simulcast->recvVersions.push_back(version); + } + } + } + + if (!rids->mRids.empty()) { + msection->GetAttributeList().SetAttribute(simulcast.release()); + msection->GetAttributeList().SetAttribute(rids.release()); + } +} + +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; + } + + if (versions->type != SdpSimulcastAttribute::Versions::kRid) { + // No support for PT-based simulcast, yet. + return; + } + + for (const SdpSimulcastAttribute::Version& version : *versions) { + if (!version.choices.empty()) { + // We validate that rids are present (and sane) elsewhere. + rids->push_back(*msection.FindRid(version.choices[0])); + } + } +} + +JsepTrack::JsConstraints* +JsepTrack::FindConstraints(const std::string& id, + std::vector& constraintsList) const +{ + for (JsConstraints& constraints : constraintsList) { + if (constraints.rid == id) { + return &constraints; + } + } + return nullptr; +} + +void +JsepTrack::CreateEncodings( + const SdpMediaSection& remote, + const std::vector& negotiatedCodecs, + JsepTrackNegotiatedDetails* negotiatedDetails) +{ + std::vector rids; + GetRids(remote, sdp::kRecv, &rids); // Get rids we will send + NegotiateRids(rids, &mJsEncodeConstraints); + if (rids.empty()) { + // Add dummy value with an empty id to make sure we get a single unicast + // stream. + rids.push_back(SdpRidAttributeList::Rid()); + } + + // For each rid in the remote, make sure we have an encoding, and configure + // that encoding appropriately. + for (size_t i = 0; i < rids.size(); ++i) { + if (i == negotiatedDetails->mEncodings.values.size()) { + negotiatedDetails->mEncodings.values.push_back(new JsepTrackEncoding); + } + + JsepTrackEncoding* encoding = negotiatedDetails->mEncodings.values[i]; + + for (const JsepCodecDescription* codec : negotiatedCodecs) { + if (rids[i].HasFormat(codec->mDefaultPt)) { + encoding->AddCodec(*codec); + } + } + + encoding->mRid = rids[i].id; + // If we end up supporting params for rid, we would handle that here. + + // Incorporate the corresponding JS encoding constraints, if they exist + for (const JsConstraints& jsConstraints : mJsEncodeConstraints) { + if (jsConstraints.rid == rids[i].id) { + encoding->mConstraints = jsConstraints.constraints; + } + } + + encoding->UpdateMaxBitrate(remote); + } +} + +std::vector +JsepTrack::GetCodecClones() const +{ + std::vector clones; + for (const JsepCodecDescription* codec : mPrototypeCodecs.values) { + clones.push_back(codec->Clone()); + } + return clones; +} + +static bool +CompareCodec(const JsepCodecDescription* lhs, const JsepCodecDescription* rhs) +{ + return lhs->mStronglyPreferred && !rhs->mStronglyPreferred; +} + +void +JsepTrack::NegotiateCodecs( + const SdpMediaSection& remote, + std::vector* codecs, + std::map* formatChanges) const +{ + PtrVector unnegotiatedCodecs; + std::swap(unnegotiatedCodecs.values, *codecs); + + // Outer loop establishes the remote side's preference + for (const std::string& fmt : remote.GetFormats()) { + for (size_t i = 0; i < unnegotiatedCodecs.values.size(); ++i) { + JsepCodecDescription* codec = unnegotiatedCodecs.values[i]; + if (!codec || !codec->mEnabled || !codec->Matches(fmt, remote)) { + continue; + } + + std::string originalFormat = codec->mDefaultPt; + if(codec->Negotiate(fmt, remote)) { + codecs->push_back(codec); + unnegotiatedCodecs.values[i] = nullptr; + if (formatChanges) { + (*formatChanges)[originalFormat] = codec->mDefaultPt; + } + break; + } + } + } + + // 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 : *codecs) { + if (codec->mName == "red") { + red = static_cast(codec); + } + else if (codec->mName == "ulpfec") { + ulpfec = static_cast(codec); + } + else if (codec->mName == "telephone-event") { + dtmf = static_cast(codec); + } + } + // 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 (auto codec : *codecs) { + 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 : *codecs) { + if (codec->mName != "red" && codec->mName != "ulpfec") { + JsepVideoCodecDescription* videoCodec = + static_cast(codec); + videoCodec->EnableFec(); + } + } + } + + // 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 : *codecs) { + JsepAudioCodecDescription* audioCodec = + static_cast(codec); + audioCodec->mDtmfEnabled = true; + } + } + + // Make sure strongly preferred codecs are up front, overriding the remote + // side's preference. + std::stable_sort(codecs->begin(), codecs->end(), CompareCodec); + + // TODO(bug 814227): Remove this once we're ready to put multiple codecs in an + // answer. For now, remove all but the first codec unless the red codec + // exists, and then we include the others per RFC 5109, section 14.2. + // Note: now allows keeping the telephone-event codec, if it appears, as the + // last codec in the list. + if (!codecs->empty() && !red) { + int newSize = dtmf ? 2 : 1; + for (size_t i = 1; i < codecs->size(); ++i) { + if (!dtmf || dtmf != (*codecs)[i]) { + delete (*codecs)[i]; + (*codecs)[i] = nullptr; + } + } + if (dtmf) { + (*codecs)[newSize-1] = dtmf; + } + codecs->resize(newSize); + } +} + +void +JsepTrack::Negotiate(const SdpMediaSection& answer, + const SdpMediaSection& remote) +{ + PtrVector negotiatedCodecs; + negotiatedCodecs.values = GetCodecClones(); + + std::map formatChanges; + NegotiateCodecs(remote, + &negotiatedCodecs.values, + &formatChanges); + + // Use |formatChanges| to update mPrototypeCodecs + size_t insertPos = 0; + for (size_t i = 0; i < mPrototypeCodecs.values.size(); ++i) { + if (formatChanges.count(mPrototypeCodecs.values[i]->mDefaultPt)) { + // Update the payload type to what was negotiated + mPrototypeCodecs.values[i]->mDefaultPt = + formatChanges[mPrototypeCodecs.values[i]->mDefaultPt]; + // Move this negotiated codec up front + std::swap(mPrototypeCodecs.values[insertPos], + mPrototypeCodecs.values[i]); + ++insertPos; + } + } + + EnsureNoDuplicatePayloadTypes(&mPrototypeCodecs.values); + + UniquePtr negotiatedDetails = + MakeUnique(); + + CreateEncodings(remote, negotiatedCodecs.values, negotiatedDetails.get()); + + if (answer.GetAttributeList().HasAttribute(SdpAttribute::kExtmapAttribute)) { + for (auto& extmapAttr : answer.GetAttributeList().GetExtmap().mExtmaps) { + negotiatedDetails->mExtmap[extmapAttr.extensionname] = extmapAttr; + } + } + + if (mDirection == sdp::kRecv) { + mSsrcs.clear(); + if (remote.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) { + for (auto& ssrcAttr : remote.GetAttributeList().GetSsrc().mSsrcs) { + AddSsrc(ssrcAttr.ssrc); + } + } + } + + mNegotiatedDetails = Move(negotiatedDetails); +} + +// 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(const std::vector>& tracks) +{ + // Maps to track details if no other track contains the payload type, + // otherwise maps to nullptr. + std::map payloadTypeToDetailsMap; + + for (const RefPtr& 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/media/webrtc/signaling/src/jsep/JsepTrack.h b/media/webrtc/signaling/src/jsep/JsepTrack.h new file mode 100644 index 000000000..5aa37404f --- /dev/null +++ b/media/webrtc/signaling/src/jsep/JsepTrack.h @@ -0,0 +1,292 @@ +/* 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 "nsISupportsImpl.h" +#include "nsError.h" + +#include "signaling/src/jsep/JsepTransport.h" +#include "signaling/src/jsep/JsepTrackEncoding.h" +#include "signaling/src/sdp/Sdp.h" +#include "signaling/src/sdp/SdpAttribute.h" +#include "signaling/src/sdp/SdpMediaSection.h" +#include "signaling/src/common/PtrVector.h" + +namespace mozilla { + +class JsepTrackNegotiatedDetails +{ +public: + size_t + GetEncodingCount() const + { + return mEncodings.values.size(); + } + + const JsepTrackEncoding& + GetEncoding(size_t index) const + { + MOZ_RELEASE_ASSERT(index < mEncodings.values.size()); + return *mEncodings.values[index]; + } + + 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; + } + + std::vector GetUniquePayloadTypes() const + { + return mUniquePayloadTypes; + } + +private: + friend class JsepTrack; + + std::map mExtmap; + std::vector mUniquePayloadTypes; + PtrVector mEncodings; +}; + +class JsepTrack +{ +public: + JsepTrack(mozilla::SdpMediaSection::MediaType type, + const std::string& streamid, + const std::string& trackid, + sdp::Direction direction = sdp::kSend) + : mType(type), + mStreamId(streamid), + mTrackId(trackid), + mDirection(direction), + mActive(false) + {} + + virtual mozilla::SdpMediaSection::MediaType + GetMediaType() const + { + return mType; + } + + virtual const std::string& + GetStreamId() const + { + return mStreamId; + } + + virtual void + SetStreamId(const std::string& id) + { + mStreamId = id; + } + + virtual const std::string& + GetTrackId() const + { + return mTrackId; + } + + virtual void + SetTrackId(const std::string& id) + { + mTrackId = id; + } + + 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 void + AddSsrc(uint32_t ssrc) + { + mSsrcs.push_back(ssrc); + } + + bool + GetActive() const + { + return mActive; + } + + void + SetActive(bool active) + { + mActive = active; + } + + virtual void PopulateCodecs( + const std::vector& prototype); + + template + void ForEachCodec(UnaryFunction func) + { + std::for_each(mPrototypeCodecs.values.begin(), + mPrototypeCodecs.values.end(), func); + } + + template + void SortCodecs(BinaryPredicate sorter) + { + std::stable_sort(mPrototypeCodecs.values.begin(), + mPrototypeCodecs.values.end(), sorter); + } + + virtual void AddToOffer(SdpMediaSection* offer) const; + virtual void AddToAnswer(const SdpMediaSection& offer, + SdpMediaSection* answer) const; + virtual void Negotiate(const SdpMediaSection& answer, + const SdpMediaSection& remote); + static void SetUniquePayloadTypes( + const std::vector>& tracks); + virtual void GetNegotiatedPayloadTypes(std::vector* payloadTypes); + + // 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(); + } + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JsepTrack); + + struct JsConstraints + { + std::string rid; + EncodingConstraints constraints; + }; + + void SetJsConstraints(const std::vector& constraintsList) + { + mJsEncodeConstraints = constraintsList; + } + + void GetJsConstraints(std::vector* outConstraintsList) const + { + MOZ_ASSERT(outConstraintsList); + *outConstraintsList = mJsEncodeConstraints; + } + + static void AddToMsection(const std::vector& constraintsList, + sdp::Direction direction, + SdpMediaSection* msection); + +protected: + virtual ~JsepTrack() {} + +private: + std::vector GetCodecClones() const; + static void EnsureNoDuplicatePayloadTypes( + std::vector* codecs); + static void GetPayloadTypes( + const std::vector& codecs, + std::vector* pts); + static void EnsurePayloadTypeIsUnique(std::set* uniquePayloadTypes, + JsepCodecDescription* codec); + 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); + + // |formatChanges| is set on completion of offer/answer, and records how the + // formats in |codecs| were changed, which is used by |Negotiate| to update + // |mPrototypeCodecs|. + virtual void NegotiateCodecs( + const SdpMediaSection& remote, + std::vector* codecs, + std::map* formatChanges = nullptr) const; + + JsConstraints* FindConstraints( + const std::string& rid, + std::vector& constraintsList) const; + void NegotiateRids(const std::vector& rids, + std::vector* constraints) const; + + const mozilla::SdpMediaSection::MediaType mType; + std::string mStreamId; + std::string mTrackId; + std::string mCNAME; + const sdp::Direction mDirection; + PtrVector mPrototypeCodecs; + // Holds encoding params/constraints from JS. Simulcast happens when there are + // multiple of these. If there are none, we assume unconstrained unicast with + // no rid. + std::vector mJsEncodeConstraints; + UniquePtr mNegotiatedDetails; + std::vector mSsrcs; + bool mActive; +}; + +// Need a better name for this. +struct JsepTrackPair { + size_t mLevel; + // Is this track pair sharing a transport with another? + Maybe mBundleLevel; + uint32_t mRecvonlySsrc; + RefPtr mSending; + RefPtr mReceiving; + RefPtr mRtpTransport; + RefPtr mRtcpTransport; +}; + +} // namespace mozilla + +#endif diff --git a/media/webrtc/signaling/src/jsep/JsepTrackEncoding.h b/media/webrtc/signaling/src/jsep/JsepTrackEncoding.h new file mode 100644 index 000000000..b59b672e4 --- /dev/null +++ b/media/webrtc/signaling/src/jsep/JsepTrackEncoding.h @@ -0,0 +1,60 @@ +/* -*- 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 "signaling/src/jsep/JsepCodecDescription.h" +#include "signaling/src/common/EncodingConstraints.h" +#include "signaling/src/common/PtrVector.h" + +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: + const std::vector& GetCodecs() const + { + return mCodecs.values; + } + + void AddCodec(const JsepCodecDescription& codec) + { + mCodecs.values.push_back(codec.Clone()); + } + + bool HasFormat(const std::string& format) const + { + for (const JsepCodecDescription* codec : mCodecs.values) { + if (codec->mDefaultPt == format) { + return true; + } + } + return false; + } + + void UpdateMaxBitrate(const SdpMediaSection& remote) + { + uint32_t tias = remote.GetBandwidth("TIAS"); + // select minimum of the two which is not zero + mConstraints.maxBr = std::min(tias ? tias : mConstraints.maxBr, + mConstraints.maxBr ? mConstraints.maxBr : + tias); + // TODO add support for b=AS if TIAS is not set (bug 976521) + } + + EncodingConstraints mConstraints; + std::string mRid; + +private: + PtrVector mCodecs; +}; +} + +#endif // _JESPTRACKENCODING_H_ diff --git a/media/webrtc/signaling/src/jsep/JsepTransport.h b/media/webrtc/signaling/src/jsep/JsepTransport.h new file mode 100644 index 000000000..3b0d38ad6 --- /dev/null +++ b/media/webrtc/signaling/src/jsep/JsepTransport.h @@ -0,0 +1,116 @@ +/* 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 "signaling/src/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) + { + } + + void Close() + { + mComponents = 0; + mTransportId.clear(); + mIce.reset(); + mDtls.reset(); + } + + // 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; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JsepTransport); + +protected: + ~JsepTransport() {} +}; + +} // namespace mozilla + +#endif diff --git a/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp b/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp new file mode 100755 index 000000000..2c57431e7 --- /dev/null +++ b/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp @@ -0,0 +1,1134 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CSFLog.h" +#include "nspr.h" + +#ifdef HAVE_NETINET_IN_H +#include +#elif defined XP_WIN +#include +#endif + +#include "AudioConduit.h" +#include "nsCOMPtr.h" +#include "mozilla/Services.h" +#include "nsServiceManagerUtils.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsThreadUtils.h" +#if !defined(MOZILLA_EXTERNAL_LINKAGE) +#include "Latency.h" +#include "mozilla/Telemetry.h" +#endif + +#include "webrtc/common.h" +#include "webrtc/modules/audio_processing/include/audio_processing.h" +#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h" +#include "webrtc/voice_engine/include/voe_dtmf.h" +#include "webrtc/voice_engine/include/voe_errors.h" +#include "webrtc/voice_engine/voice_engine_impl.h" +#include "webrtc/system_wrappers/interface/clock.h" + +#ifdef MOZ_WIDGET_ANDROID +#include "AndroidJNIWrapper.h" +#endif + +namespace mozilla { + +static const char* logTag ="WebrtcAudioSessionConduit"; + +// 32 bytes is what WebRTC CodecInst expects +const unsigned int WebrtcAudioConduit::CODEC_PLNAME_SIZE = 32; + +/** + * Factory Method for AudioConduit + */ +RefPtr AudioSessionConduit::Create() +{ + CSFLogDebug(logTag, "%s ", __FUNCTION__); + NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); + + WebrtcAudioConduit* obj = new WebrtcAudioConduit(); + if(obj->Init() != kMediaConduitNoError) + { + CSFLogError(logTag, "%s AudioConduit Init Failed ", __FUNCTION__); + delete obj; + return nullptr; + } + CSFLogDebug(logTag, "%s Successfully created AudioConduit ", __FUNCTION__); + return obj; +} + +/** + * Destruction defines for our super-classes + */ +WebrtcAudioConduit::~WebrtcAudioConduit() +{ + NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); + + CSFLogDebug(logTag, "%s ", __FUNCTION__); + for(std::vector::size_type i=0;i < mRecvCodecList.size();i++) + { + delete mRecvCodecList[i]; + } + + // The first one of a pair to be deleted shuts down media for both + if(mPtrVoEXmedia) + { + mPtrVoEXmedia->SetExternalRecordingStatus(false); + mPtrVoEXmedia->SetExternalPlayoutStatus(false); + } + + //Deal with the transport + if(mPtrVoENetwork) + { + mPtrVoENetwork->DeRegisterExternalTransport(mChannel); + } + + if(mPtrVoEBase) + { + mPtrVoEBase->StopPlayout(mChannel); + mPtrVoEBase->StopSend(mChannel); + mPtrVoEBase->StopReceive(mChannel); + mPtrVoEBase->DeleteChannel(mChannel); + mPtrVoEBase->Terminate(); + } + + // We shouldn't delete the VoiceEngine until all these are released! + // And we can't use a Scoped ptr, since the order is arbitrary + mPtrVoENetwork = nullptr; + mPtrVoEBase = nullptr; + mPtrVoECodec = nullptr; + mPtrVoEXmedia = nullptr; + mPtrVoEProcessing = nullptr; + mPtrVoEVideoSync = nullptr; + mPtrVoERTP_RTCP = nullptr; + mPtrRTP = nullptr; + + if(mVoiceEngine) + { + webrtc::VoiceEngine::Delete(mVoiceEngine); + } +} + +bool WebrtcAudioConduit::SetLocalSSRC(unsigned int ssrc) +{ + unsigned int oldSsrc; + if (!GetLocalSSRC(&oldSsrc)) { + MOZ_ASSERT(false, "GetLocalSSRC failed"); + return false; + } + + if (oldSsrc == ssrc) { + return true; + } + + bool wasTransmitting = mEngineTransmitting; + if (StopTransmitting() != kMediaConduitNoError) { + return false; + } + + if (mPtrRTP->SetLocalSSRC(mChannel, ssrc)) { + return false; + } + + if (wasTransmitting) { + if (StartTransmitting() != kMediaConduitNoError) { + return false; + } + } + return true; +} + +bool WebrtcAudioConduit::GetLocalSSRC(unsigned int* ssrc) { + return !mPtrRTP->GetLocalSSRC(mChannel, *ssrc); +} + +bool WebrtcAudioConduit::GetRemoteSSRC(unsigned int* ssrc) { + return !mPtrRTP->GetRemoteSSRC(mChannel, *ssrc); +} + +bool WebrtcAudioConduit::SetLocalCNAME(const char* cname) +{ + char temp[256]; + strncpy(temp, cname, sizeof(temp) - 1); + temp[sizeof(temp) - 1] = 0; + return !mPtrRTP->SetRTCP_CNAME(mChannel, temp); +} + +bool WebrtcAudioConduit::GetAVStats(int32_t* jitterBufferDelayMs, + int32_t* playoutBufferDelayMs, + int32_t* avSyncOffsetMs) { + return !mPtrVoEVideoSync->GetDelayEstimate(mChannel, + jitterBufferDelayMs, + playoutBufferDelayMs, + avSyncOffsetMs); +} + +bool WebrtcAudioConduit::GetRTPStats(unsigned int* jitterMs, + unsigned int* cumulativeLost) { + unsigned int maxJitterMs = 0; + unsigned int discardedPackets; + *jitterMs = 0; + *cumulativeLost = 0; + return !mPtrRTP->GetRTPStatistics(mChannel, *jitterMs, maxJitterMs, + discardedPackets, *cumulativeLost); +} + +DOMHighResTimeStamp +NTPtoDOMHighResTimeStamp(uint32_t ntpHigh, uint32_t ntpLow) { + return (uint32_t(ntpHigh - webrtc::kNtpJan1970) + + double(ntpLow) / webrtc::kMagicNtpFractionalUnit) * 1000; +} + +bool WebrtcAudioConduit::GetRTCPReceiverReport(DOMHighResTimeStamp* timestamp, + uint32_t* jitterMs, + uint32_t* packetsReceived, + uint64_t* bytesReceived, + uint32_t* cumulativeLost, + int32_t* rttMs) { + uint32_t ntpHigh, ntpLow; + uint16_t fractionLost; + bool result = !mPtrRTP->GetRemoteRTCPReceiverInfo(mChannel, ntpHigh, ntpLow, + *packetsReceived, + *bytesReceived, + *jitterMs, + fractionLost, + *cumulativeLost, + *rttMs); + if (result) { + *timestamp = NTPtoDOMHighResTimeStamp(ntpHigh, ntpLow); + } + return result; +} + +bool WebrtcAudioConduit::GetRTCPSenderReport(DOMHighResTimeStamp* timestamp, + unsigned int* packetsSent, + uint64_t* bytesSent) { + webrtc::RTCPSenderInfo senderInfo; + webrtc::RtpRtcp * rtpRtcpModule; + webrtc::RtpReceiver * rtp_receiver; + bool result = + !mPtrVoEVideoSync->GetRtpRtcp(mChannel,&rtpRtcpModule,&rtp_receiver) && + !rtpRtcpModule->RemoteRTCPStat(&senderInfo); + if (result){ + *timestamp = NTPtoDOMHighResTimeStamp(senderInfo.NTPseconds, + senderInfo.NTPfraction); + *packetsSent = senderInfo.sendPacketCount; + *bytesSent = senderInfo.sendOctetCount; + } + return result; + } + +bool WebrtcAudioConduit::SetDtmfPayloadType(unsigned char type) { + CSFLogInfo(logTag, "%s : setting dtmf payload %d", __FUNCTION__, (int)type); + + ScopedCustomReleasePtr mPtrVoEDtmf; + mPtrVoEDtmf = webrtc::VoEDtmf::GetInterface(mVoiceEngine); + if (!mPtrVoEDtmf) { + CSFLogError(logTag, "%s Unable to initialize VoEDtmf", __FUNCTION__); + return false; + } + + int result = mPtrVoEDtmf->SetSendTelephoneEventPayloadType(mChannel, type); + if (result == -1) { + CSFLogError(logTag, "%s Failed call to SetSendTelephoneEventPayloadType", + __FUNCTION__); + } + return result != -1; +} + +bool WebrtcAudioConduit::InsertDTMFTone(int channel, int eventCode, + bool outOfBand, int lengthMs, + int attenuationDb) { + NS_ASSERTION(!NS_IsMainThread(), "Do not call on main thread"); + + if (!mVoiceEngine || !mDtmfEnabled) { + return false; + } + + webrtc::VoiceEngineImpl* s = static_cast(mVoiceEngine); + int result = s->SendTelephoneEvent(channel, eventCode, outOfBand, lengthMs, attenuationDb); + return result != -1; +} + +/* + * WebRTCAudioConduit Implementation + */ +MediaConduitErrorCode WebrtcAudioConduit::Init() +{ + CSFLogDebug(logTag, "%s this=%p", __FUNCTION__, this); + +#ifdef MOZ_WIDGET_ANDROID + jobject context = jsjni_GetGlobalContextRef(); + // get the JVM + JavaVM *jvm = jsjni_GetVM(); + + if (webrtc::VoiceEngine::SetAndroidObjects(jvm, (void*)context) != 0) { + CSFLogError(logTag, "%s Unable to set Android objects", __FUNCTION__); + return kMediaConduitSessionNotInited; + } +#endif + + // Per WebRTC APIs below function calls return nullptr on failure + if(!(mVoiceEngine = webrtc::VoiceEngine::Create())) + { + CSFLogError(logTag, "%s Unable to create voice engine", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + + if(!(mPtrVoEBase = VoEBase::GetInterface(mVoiceEngine))) + { + CSFLogError(logTag, "%s Unable to initialize VoEBase", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + + if(!(mPtrVoENetwork = VoENetwork::GetInterface(mVoiceEngine))) + { + CSFLogError(logTag, "%s Unable to initialize VoENetwork", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + + if(!(mPtrVoECodec = VoECodec::GetInterface(mVoiceEngine))) + { + CSFLogError(logTag, "%s Unable to initialize VoEBCodec", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + + if(!(mPtrVoEProcessing = VoEAudioProcessing::GetInterface(mVoiceEngine))) + { + CSFLogError(logTag, "%s Unable to initialize VoEProcessing", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + if(!(mPtrVoEXmedia = VoEExternalMedia::GetInterface(mVoiceEngine))) + { + CSFLogError(logTag, "%s Unable to initialize VoEExternalMedia", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + if(!(mPtrVoERTP_RTCP = VoERTP_RTCP::GetInterface(mVoiceEngine))) + { + CSFLogError(logTag, "%s Unable to initialize VoERTP_RTCP", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + + if(!(mPtrVoEVideoSync = VoEVideoSync::GetInterface(mVoiceEngine))) + { + CSFLogError(logTag, "%s Unable to initialize VoEVideoSync", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + if (!(mPtrRTP = webrtc::VoERTP_RTCP::GetInterface(mVoiceEngine))) + { + CSFLogError(logTag, "%s Unable to get audio RTP/RTCP interface ", + __FUNCTION__); + return kMediaConduitSessionNotInited; + } + + // init the engine with our audio device layer + if(mPtrVoEBase->Init() == -1) + { + CSFLogError(logTag, "%s VoiceEngine Base Not Initialized", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + + if( (mChannel = mPtrVoEBase->CreateChannel()) == -1) + { + CSFLogError(logTag, "%s VoiceEngine Channel creation failed",__FUNCTION__); + return kMediaConduitChannelError; + } + + CSFLogDebug(logTag, "%s Channel Created %d ",__FUNCTION__, mChannel); + + if(mPtrVoENetwork->RegisterExternalTransport(mChannel, *this) == -1) + { + CSFLogError(logTag, "%s VoiceEngine, External Transport Failed",__FUNCTION__); + return kMediaConduitTransportRegistrationFail; + } + + if(mPtrVoEXmedia->SetExternalRecordingStatus(true) == -1) + { + CSFLogError(logTag, "%s SetExternalRecordingStatus Failed %d",__FUNCTION__, + mPtrVoEBase->LastError()); + return kMediaConduitExternalPlayoutError; + } + + if(mPtrVoEXmedia->SetExternalPlayoutStatus(true) == -1) + { + CSFLogError(logTag, "%s SetExternalPlayoutStatus Failed %d ",__FUNCTION__, + mPtrVoEBase->LastError()); + return kMediaConduitExternalRecordingError; + } + + CSFLogDebug(logTag , "%s AudioSessionConduit Initialization Done (%p)",__FUNCTION__, this); + return kMediaConduitNoError; +} + +// AudioSessionConduit Implementation +MediaConduitErrorCode +WebrtcAudioConduit::SetTransmitterTransport(RefPtr aTransport) +{ + CSFLogDebug(logTag, "%s ", __FUNCTION__); + + ReentrantMonitorAutoEnter enter(mTransportMonitor); + // set the transport + mTransmitterTransport = aTransport; + return kMediaConduitNoError; +} + +MediaConduitErrorCode +WebrtcAudioConduit::SetReceiverTransport(RefPtr aTransport) +{ + CSFLogDebug(logTag, "%s ", __FUNCTION__); + + ReentrantMonitorAutoEnter enter(mTransportMonitor); + // set the transport + mReceiverTransport = aTransport; + return kMediaConduitNoError; +} + +MediaConduitErrorCode +WebrtcAudioConduit::ConfigureSendMediaCodec(const AudioCodecConfig* codecConfig) +{ + CSFLogDebug(logTag, "%s ", __FUNCTION__); + MediaConduitErrorCode condError = kMediaConduitNoError; + int error = 0;//webrtc engine errors + webrtc::CodecInst cinst; + + { + //validate codec param + if((condError = ValidateCodecConfig(codecConfig, true)) != kMediaConduitNoError) + { + return condError; + } + } + + condError = StopTransmitting(); + if (condError != kMediaConduitNoError) { + return condError; + } + + if(!CodecConfigToWebRTCCodec(codecConfig,cinst)) + { + CSFLogError(logTag,"%s CodecConfig to WebRTC Codec Failed ",__FUNCTION__); + return kMediaConduitMalformedArgument; + } + + if(mPtrVoECodec->SetSendCodec(mChannel, cinst) == -1) + { + error = mPtrVoEBase->LastError(); + CSFLogError(logTag, "%s SetSendCodec - Invalid Codec %d ",__FUNCTION__, + error); + + if(error == VE_CANNOT_SET_SEND_CODEC || error == VE_CODEC_ERROR) + { + CSFLogError(logTag, "%s Invalid Send Codec", __FUNCTION__); + return kMediaConduitInvalidSendCodec; + } + CSFLogError(logTag, "%s SetSendCodec Failed %d ", __FUNCTION__, + mPtrVoEBase->LastError()); + return kMediaConduitUnknownError; + } + + // This must be called after SetSendCodec + if (mPtrVoECodec->SetFECStatus(mChannel, codecConfig->mFECEnabled) == -1) { + CSFLogError(logTag, "%s SetFECStatus Failed %d ", __FUNCTION__, + mPtrVoEBase->LastError()); + return kMediaConduitFECStatusError; + } + + mDtmfEnabled = codecConfig->mDtmfEnabled; + + if (codecConfig->mName == "opus" && codecConfig->mMaxPlaybackRate) { + if (mPtrVoECodec->SetOpusMaxPlaybackRate( + mChannel, + codecConfig->mMaxPlaybackRate) == -1) { + CSFLogError(logTag, "%s SetOpusMaxPlaybackRate Failed %d ", __FUNCTION__, + mPtrVoEBase->LastError()); + return kMediaConduitUnknownError; + } + } + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + // TEMPORARY - see bug 694814 comment 2 + nsresult rv; + nsCOMPtr prefs = do_GetService("@mozilla.org/preferences-service;1", &rv); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr branch = do_QueryInterface(prefs); + + if (branch) { + branch->GetIntPref("media.peerconnection.capture_delay", &mCaptureDelay); + } + } +#endif + + condError = StartTransmitting(); + if (condError != kMediaConduitNoError) { + return condError; + } + + { + MutexAutoLock lock(mCodecMutex); + + //Copy the applied config for future reference. + mCurSendCodecConfig = new AudioCodecConfig(codecConfig->mType, + codecConfig->mName, + codecConfig->mFreq, + codecConfig->mPacSize, + codecConfig->mChannels, + codecConfig->mRate, + codecConfig->mFECEnabled); + } + return kMediaConduitNoError; +} + +MediaConduitErrorCode +WebrtcAudioConduit::ConfigureRecvMediaCodecs( + const std::vector& codecConfigList) +{ + CSFLogDebug(logTag, "%s ", __FUNCTION__); + MediaConduitErrorCode condError = kMediaConduitNoError; + int error = 0; //webrtc engine errors + bool success = false; + + // Are we receiving already? If so, stop receiving and playout + // since we can't apply new recv codec when the engine is playing. + condError = StopReceiving(); + if (condError != kMediaConduitNoError) { + return condError; + } + + if(codecConfigList.empty()) + { + CSFLogError(logTag, "%s Zero number of codecs to configure", __FUNCTION__); + return kMediaConduitMalformedArgument; + } + + // Try Applying the codecs in the list. + // We succeed if at least one codec was applied and reception was + // started successfully. + for(std::vector::size_type i=0 ;iSetRecPayloadType(mChannel,cinst) == -1) + { + error = mPtrVoEBase->LastError(); + CSFLogError(logTag, "%s SetRecvCodec Failed %d ",__FUNCTION__, error); + continue; + } else { + CSFLogDebug(logTag, "%s Successfully Set RecvCodec %s", __FUNCTION__, + codecConfigList[i]->mName.c_str()); + //copy this to local database + if(CopyCodecToDB(codecConfigList[i])) + { + success = true; + } else { + CSFLogError(logTag,"%s Unable to updated Codec Database", __FUNCTION__); + return kMediaConduitUnknownError; + } + + } + + } //end for + + if(!success) + { + CSFLogError(logTag, "%s Setting Receive Codec Failed ", __FUNCTION__); + return kMediaConduitInvalidReceiveCodec; + } + + //If we are here, atleast one codec should have been set + condError = StartReceiving(); + if (condError != kMediaConduitNoError) { + return condError; + } + + DumpCodecDB(); + return kMediaConduitNoError; +} +MediaConduitErrorCode +WebrtcAudioConduit::EnableAudioLevelExtension(bool enabled, uint8_t id) +{ + CSFLogDebug(logTag, "%s %d %d ", __FUNCTION__, enabled, id); + + if (mPtrVoERTP_RTCP->SetSendAudioLevelIndicationStatus(mChannel, enabled, id) == -1) + { + CSFLogError(logTag, "%s SetSendAudioLevelIndicationStatus Failed", __FUNCTION__); + return kMediaConduitUnknownError; + } + + return kMediaConduitNoError; +} + +MediaConduitErrorCode +WebrtcAudioConduit::SendAudioFrame(const int16_t audio_data[], + int32_t lengthSamples, + int32_t samplingFreqHz, + int32_t capture_delay) +{ + CSFLogDebug(logTag, "%s ", __FUNCTION__); + // Following checks need to be performed + // 1. Non null audio buffer pointer, + // 2. invalid sampling frequency - less than 0 or unsupported ones + // 3. Appropriate Sample Length for 10 ms audio-frame. This represents + // block size the VoiceEngine feeds into encoder for passed in audio-frame + // Ex: for 16000 sampling rate , valid block-length is 160 + // Similarly for 32000 sampling rate, valid block length is 320 + // We do the check by the verify modular operator below to be zero + + if(!audio_data || (lengthSamples <= 0) || + (IsSamplingFreqSupported(samplingFreqHz) == false) || + ((lengthSamples % (samplingFreqHz / 100) != 0)) ) + { + CSFLogError(logTag, "%s Invalid Parameters ",__FUNCTION__); + MOZ_ASSERT(PR_FALSE); + return kMediaConduitMalformedArgument; + } + + //validate capture time + if(capture_delay < 0 ) + { + CSFLogError(logTag,"%s Invalid Capture Delay ", __FUNCTION__); + MOZ_ASSERT(PR_FALSE); + return kMediaConduitMalformedArgument; + } + + // if transmission is not started .. conduit cannot insert frames + if(!mEngineTransmitting) + { + CSFLogError(logTag, "%s Engine not transmitting ", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + if (MOZ_LOG_TEST(GetLatencyLog(), LogLevel::Debug)) { + struct Processing insert = { TimeStamp::Now(), 0 }; + mProcessing.AppendElement(insert); + } +#endif + + capture_delay = mCaptureDelay; + //Insert the samples + if(mPtrVoEXmedia->ExternalRecordingInsertData(audio_data, + lengthSamples, + samplingFreqHz, + capture_delay) == -1) + { + int error = mPtrVoEBase->LastError(); + CSFLogError(logTag, "%s Inserting audio data Failed %d", __FUNCTION__, error); + if(error == VE_RUNTIME_REC_ERROR) + { + return kMediaConduitRecordingError; + } + return kMediaConduitUnknownError; + } + // we should be good here + return kMediaConduitNoError; +} + +MediaConduitErrorCode +WebrtcAudioConduit::GetAudioFrame(int16_t speechData[], + int32_t samplingFreqHz, + int32_t capture_delay, + int& lengthSamples) +{ + + CSFLogDebug(logTag, "%s ", __FUNCTION__); + unsigned int numSamples = 0; + + //validate params + if(!speechData ) + { + CSFLogError(logTag,"%s Null Audio Buffer Pointer", __FUNCTION__); + MOZ_ASSERT(PR_FALSE); + return kMediaConduitMalformedArgument; + } + + // Validate sample length + if((numSamples = GetNum10msSamplesForFrequency(samplingFreqHz)) == 0 ) + { + CSFLogError(logTag,"%s Invalid Sampling Frequency ", __FUNCTION__); + MOZ_ASSERT(PR_FALSE); + return kMediaConduitMalformedArgument; + } + + //validate capture time + if(capture_delay < 0 ) + { + CSFLogError(logTag,"%s Invalid Capture Delay ", __FUNCTION__); + MOZ_ASSERT(PR_FALSE); + return kMediaConduitMalformedArgument; + } + + //Conduit should have reception enabled before we ask for decoded + // samples + if(!mEngineReceiving) + { + CSFLogError(logTag, "%s Engine not Receiving ", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + + + lengthSamples = 0; //output paramter + + if(mPtrVoEXmedia->ExternalPlayoutGetData( speechData, + samplingFreqHz, + capture_delay, + lengthSamples) == -1) + { + int error = mPtrVoEBase->LastError(); + CSFLogError(logTag, "%s Getting audio data Failed %d", __FUNCTION__, error); + if(error == VE_RUNTIME_PLAY_ERROR) + { + return kMediaConduitPlayoutError; + } + return kMediaConduitUnknownError; + } + + // Not #ifdef DEBUG or on a log module so we can use it for about:webrtc/etc + mSamples += lengthSamples; + if (mSamples >= mLastSyncLog + samplingFreqHz) { + int jitter_buffer_delay_ms; + int playout_buffer_delay_ms; + int avsync_offset_ms; + if (GetAVStats(&jitter_buffer_delay_ms, + &playout_buffer_delay_ms, + &avsync_offset_ms)) { +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + if (avsync_offset_ms < 0) { + Telemetry::Accumulate(Telemetry::WEBRTC_AVSYNC_WHEN_VIDEO_LAGS_AUDIO_MS, + -avsync_offset_ms); + } else { + Telemetry::Accumulate(Telemetry::WEBRTC_AVSYNC_WHEN_AUDIO_LAGS_VIDEO_MS, + avsync_offset_ms); + } +#endif + CSFLogError(logTag, + "A/V sync: sync delta: %dms, audio jitter delay %dms, playout delay %dms", + avsync_offset_ms, jitter_buffer_delay_ms, playout_buffer_delay_ms); + } else { + CSFLogError(logTag, "A/V sync: GetAVStats failed"); + } + mLastSyncLog = mSamples; + } + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + if (MOZ_LOG_TEST(GetLatencyLog(), LogLevel::Debug)) { + if (mProcessing.Length() > 0) { + unsigned int now; + mPtrVoEVideoSync->GetPlayoutTimestamp(mChannel, now); + if (static_cast(now) != mLastTimestamp) { + mLastTimestamp = static_cast(now); + // Find the block that includes this timestamp in the network input + while (mProcessing.Length() > 0) { + // FIX! assumes 20ms @ 48000Hz + // FIX handle wrap-around + if (mProcessing[0].mRTPTimeStamp + 20*(48000/1000) >= now) { + TimeDuration t = TimeStamp::Now() - mProcessing[0].mTimeStamp; + // Wrap-around? + int64_t delta = t.ToMilliseconds() + (now - mProcessing[0].mRTPTimeStamp)/(48000/1000); + LogTime(AsyncLatencyLogger::AudioRecvRTP, ((uint64_t) this), delta); + break; + } + mProcessing.RemoveElementAt(0); + } + } + } + } +#endif + CSFLogDebug(logTag,"%s GetAudioFrame:Got samples: length %d ",__FUNCTION__, + lengthSamples); + return kMediaConduitNoError; +} + +// Transport Layer Callbacks +MediaConduitErrorCode +WebrtcAudioConduit::ReceivedRTPPacket(const void *data, int len) +{ + CSFLogDebug(logTag, "%s : channel %d", __FUNCTION__, mChannel); + + if(mEngineReceiving) + { +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + if (MOZ_LOG_TEST(GetLatencyLog(), LogLevel::Debug)) { + // timestamp is at 32 bits in ([1]) + struct Processing insert = { TimeStamp::Now(), + ntohl(static_cast(data)[1]) }; + mProcessing.AppendElement(insert); + } +#endif + + // XXX we need to get passed the time the packet was received + if(mPtrVoENetwork->ReceivedRTPPacket(mChannel, data, len) == -1) + { + int error = mPtrVoEBase->LastError(); + CSFLogError(logTag, "%s RTP Processing Error %d", __FUNCTION__, error); + if(error == VE_RTP_RTCP_MODULE_ERROR) + { + return kMediaConduitRTPRTCPModuleError; + } + return kMediaConduitUnknownError; + } + } else { + CSFLogError(logTag, "Error: %s when not receiving", __FUNCTION__); + return kMediaConduitSessionNotInited; + } + + return kMediaConduitNoError; +} + +MediaConduitErrorCode +WebrtcAudioConduit::ReceivedRTCPPacket(const void *data, int len) +{ + CSFLogDebug(logTag, "%s : channel %d",__FUNCTION__, mChannel); + + if(mPtrVoENetwork->ReceivedRTCPPacket(mChannel, data, len) == -1) + { + int error = mPtrVoEBase->LastError(); + CSFLogError(logTag, "%s RTCP Processing Error %d", __FUNCTION__, error); + if(error == VE_RTP_RTCP_MODULE_ERROR) + { + return kMediaConduitRTPRTCPModuleError; + } + return kMediaConduitUnknownError; + } + return kMediaConduitNoError; +} + +MediaConduitErrorCode +WebrtcAudioConduit::StopTransmitting() +{ + if(mEngineTransmitting) + { + CSFLogDebug(logTag, "%s Engine Already Sending. Attemping to Stop ", __FUNCTION__); + if(mPtrVoEBase->StopSend(mChannel) == -1) + { + CSFLogError(logTag, "%s StopSend() Failed %d ", __FUNCTION__, + mPtrVoEBase->LastError()); + return kMediaConduitUnknownError; + } + mEngineTransmitting = false; + } + + return kMediaConduitNoError; +} + +MediaConduitErrorCode +WebrtcAudioConduit::StartTransmitting() +{ + if (!mEngineTransmitting) { + //Let's Send Transport State-machine on the Engine + if(mPtrVoEBase->StartSend(mChannel) == -1) + { + int error = mPtrVoEBase->LastError(); + CSFLogError(logTag, "%s StartSend failed %d", __FUNCTION__, error); + return kMediaConduitUnknownError; + } + mEngineTransmitting = true; + } + + return kMediaConduitNoError; +} + +MediaConduitErrorCode +WebrtcAudioConduit::StopReceiving() +{ + if(mEngineReceiving) + { + CSFLogDebug(logTag, "%s Engine Already Receiving. Attemping to Stop ", __FUNCTION__); + // AudioEngine doesn't fail fatally on stopping reception. Ref:voe_errors.h. + // hence we need not be strict in failing here on errors + mPtrVoEBase->StopReceive(mChannel); + CSFLogDebug(logTag, "%s Attemping to Stop playout ", __FUNCTION__); + if(mPtrVoEBase->StopPlayout(mChannel) == -1) + { + if( mPtrVoEBase->LastError() == VE_CANNOT_STOP_PLAYOUT) + { + CSFLogDebug(logTag, "%s Stop-Playout Failed %d", __FUNCTION__, mPtrVoEBase->LastError()); + return kMediaConduitPlayoutError; + } + } + mEngineReceiving = false; + } + + return kMediaConduitNoError; +} + +MediaConduitErrorCode +WebrtcAudioConduit::StartReceiving() +{ + if (!mEngineReceiving) { + if(mPtrVoEBase->StartReceive(mChannel) == -1) + { + int error = mPtrVoEBase->LastError(); + CSFLogError(logTag , "%s StartReceive Failed %d ",__FUNCTION__, error); + if(error == VE_RECV_SOCKET_ERROR) + { + return kMediaConduitSocketError; + } + return kMediaConduitUnknownError; + } + + + if(mPtrVoEBase->StartPlayout(mChannel) == -1) + { + CSFLogError(logTag, "%s Starting playout Failed", __FUNCTION__); + return kMediaConduitPlayoutError; + } + mEngineReceiving = true; + } + + return kMediaConduitNoError; +} + +//WebRTC::RTP Callback Implementation +// Called on AudioGUM or MSG thread +int WebrtcAudioConduit::SendPacket(int channel, const void* data, size_t len) +{ + CSFLogDebug(logTag, "%s : channel %d", __FUNCTION__, channel); + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + if (MOZ_LOG_TEST(GetLatencyLog(), LogLevel::Debug)) { + if (mProcessing.Length() > 0) { + TimeStamp started = mProcessing[0].mTimeStamp; + mProcessing.RemoveElementAt(0); + mProcessing.RemoveElementAt(0); // 20ms packetization! Could automate this by watching sizes + TimeDuration t = TimeStamp::Now() - started; + int64_t delta = t.ToMilliseconds(); + LogTime(AsyncLatencyLogger::AudioSendRTP, ((uint64_t) this), delta); + } + } +#endif + ReentrantMonitorAutoEnter enter(mTransportMonitor); + if(mTransmitterTransport && + (mTransmitterTransport->SendRtpPacket(data, len) == NS_OK)) + { + CSFLogDebug(logTag, "%s Sent RTP Packet ", __FUNCTION__); + return len; + } else { + CSFLogError(logTag, "%s RTP Packet Send Failed ", __FUNCTION__); + return -1; + } +} + +// Called on WebRTC Process thread and perhaps others +int WebrtcAudioConduit::SendRTCPPacket(int channel, const void* data, size_t len) +{ + CSFLogDebug(logTag, "%s : channel %d , len %lu, first rtcp = %u ", + __FUNCTION__, + channel, + (unsigned long) len, + static_cast(((uint8_t *) data)[1])); + + // We come here if we have only one pipeline/conduit setup, + // such as for unidirectional streams. + // We also end up here if we are receiving + ReentrantMonitorAutoEnter enter(mTransportMonitor); + if(mReceiverTransport && + mReceiverTransport->SendRtcpPacket(data, len) == NS_OK) + { + // Might be a sender report, might be a receiver report, we don't know. + CSFLogDebug(logTag, "%s Sent RTCP Packet ", __FUNCTION__); + return len; + } else if(mTransmitterTransport && + (mTransmitterTransport->SendRtcpPacket(data, len) == NS_OK)) { + CSFLogDebug(logTag, "%s Sent RTCP Packet (sender report) ", __FUNCTION__); + return len; + } else { + CSFLogError(logTag, "%s RTCP Packet Send Failed ", __FUNCTION__); + return -1; + } +} + +/** + * Converts between CodecConfig to WebRTC Codec Structure. + */ + +bool +WebrtcAudioConduit::CodecConfigToWebRTCCodec(const AudioCodecConfig* codecInfo, + webrtc::CodecInst& cinst) + { + const unsigned int plNameLength = codecInfo->mName.length(); + memset(&cinst, 0, sizeof(webrtc::CodecInst)); + if(sizeof(cinst.plname) < plNameLength+1) + { + CSFLogError(logTag, "%s Payload name buffer capacity mismatch ", + __FUNCTION__); + return false; + } + memcpy(cinst.plname, codecInfo->mName.c_str(), plNameLength); + cinst.plname[plNameLength]='\0'; + cinst.pltype = codecInfo->mType; + cinst.rate = codecInfo->mRate; + cinst.pacsize = codecInfo->mPacSize; + cinst.plfreq = codecInfo->mFreq; + if (codecInfo->mName == "G722") { + // Compensate for G.722 spec error in RFC 1890 + cinst.plfreq = 16000; + } + cinst.channels = codecInfo->mChannels; + return true; + } + +/** + * Supported Sampling Frequncies. + */ +bool +WebrtcAudioConduit::IsSamplingFreqSupported(int freq) const +{ + if(GetNum10msSamplesForFrequency(freq)) + { + return true; + } else { + return false; + } +} + +/* 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 + } +} + +//Copy the codec passed into Conduit's database +bool +WebrtcAudioConduit::CopyCodecToDB(const AudioCodecConfig* codecInfo) +{ + + AudioCodecConfig* cdcConfig = new AudioCodecConfig(codecInfo->mType, + codecInfo->mName, + codecInfo->mFreq, + codecInfo->mPacSize, + codecInfo->mChannels, + codecInfo->mRate, + codecInfo->mFECEnabled); + mRecvCodecList.push_back(cdcConfig); + return true; +} + +/** + * Checks if 2 codec structs are same + */ +bool +WebrtcAudioConduit::CheckCodecsForMatch(const AudioCodecConfig* curCodecConfig, + const AudioCodecConfig* codecInfo) const +{ + if(!curCodecConfig) + { + return false; + } + + if(curCodecConfig->mType == codecInfo->mType && + (curCodecConfig->mName.compare(codecInfo->mName) == 0) && + curCodecConfig->mFreq == codecInfo->mFreq && + curCodecConfig->mPacSize == codecInfo->mPacSize && + curCodecConfig->mChannels == codecInfo->mChannels && + curCodecConfig->mRate == codecInfo->mRate) + { + return true; + } + + return false; +} + +/** + * Checks if the codec is already in Conduit's database + */ +bool +WebrtcAudioConduit::CheckCodecForMatch(const AudioCodecConfig* codecInfo) const +{ + //the db should have atleast one codec + for(std::vector::size_type i=0;i < mRecvCodecList.size();i++) + { + if(CheckCodecsForMatch(mRecvCodecList[i],codecInfo)) + { + //match + return true; + } + } + //no match or empty local db + return false; +} + + +/** + * Perform validation on the codecConfig to be applied. + * Verifies if the codec is already applied. + */ +MediaConduitErrorCode +WebrtcAudioConduit::ValidateCodecConfig(const AudioCodecConfig* codecInfo, + bool send) +{ + bool codecAppliedAlready = false; + + if(!codecInfo) + { + CSFLogError(logTag, "%s Null CodecConfig ", __FUNCTION__); + return kMediaConduitMalformedArgument; + } + + if((codecInfo->mName.empty()) || + (codecInfo->mName.length() >= CODEC_PLNAME_SIZE)) + { + CSFLogError(logTag, "%s Invalid Payload Name Length ", __FUNCTION__); + return kMediaConduitMalformedArgument; + } + + //Only mono or stereo channels supported + if( (codecInfo->mChannels != 1) && (codecInfo->mChannels != 2)) + { + CSFLogError(logTag, "%s Channel Unsupported ", __FUNCTION__); + return kMediaConduitMalformedArgument; + } + + //check if we have the same codec already applied + if(send) + { + MutexAutoLock lock(mCodecMutex); + + codecAppliedAlready = CheckCodecsForMatch(mCurSendCodecConfig,codecInfo); + } else { + codecAppliedAlready = CheckCodecForMatch(codecInfo); + } + + if(codecAppliedAlready) + { + CSFLogDebug(logTag, "%s Codec %s Already Applied ", __FUNCTION__, codecInfo->mName.c_str()); + } + return kMediaConduitNoError; +} + +void +WebrtcAudioConduit::DumpCodecDB() const + { + for(std::vector::size_type i=0;i < mRecvCodecList.size();i++) + { + CSFLogDebug(logTag,"Payload Name: %s", mRecvCodecList[i]->mName.c_str()); + CSFLogDebug(logTag,"Payload Type: %d", mRecvCodecList[i]->mType); + CSFLogDebug(logTag,"Payload Frequency: %d", mRecvCodecList[i]->mFreq); + CSFLogDebug(logTag,"Payload PacketSize: %d", mRecvCodecList[i]->mPacSize); + CSFLogDebug(logTag,"Payload Channels: %d", mRecvCodecList[i]->mChannels); + CSFLogDebug(logTag,"Payload Sampling Rate: %d", mRecvCodecList[i]->mRate); + } + } +}// end namespace diff --git a/media/webrtc/signaling/src/media-conduit/AudioConduit.h b/media/webrtc/signaling/src/media-conduit/AudioConduit.h new file mode 100755 index 000000000..228736dcc --- /dev/null +++ b/media/webrtc/signaling/src/media-conduit/AudioConduit.h @@ -0,0 +1,304 @@ +/* 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/TimeStamp.h" +#include "nsTArray.h" + +#include "MediaConduitInterface.h" +#include "MediaEngineWrapper.h" + +// Audio Engine Includes +#include "webrtc/common_types.h" +#include "webrtc/voice_engine/include/voe_base.h" +#include "webrtc/voice_engine/include/voe_volume_control.h" +#include "webrtc/voice_engine/include/voe_codec.h" +#include "webrtc/voice_engine/include/voe_file.h" +#include "webrtc/voice_engine/include/voe_network.h" +#include "webrtc/voice_engine/include/voe_external_media.h" +#include "webrtc/voice_engine/include/voe_audio_processing.h" +#include "webrtc/voice_engine/include/voe_video_sync.h" +#include "webrtc/voice_engine/include/voe_rtp_rtcp.h" +//Some WebRTC types for short notations + using webrtc::VoEBase; + using webrtc::VoENetwork; + using webrtc::VoECodec; + using webrtc::VoEExternalMedia; + using webrtc::VoEAudioProcessing; + using webrtc::VoEVideoSync; + using webrtc::VoERTP_RTCP; +/** This file hosts several structures identifying different aspects + * of a RTP Session. + */ +namespace mozilla { +// Helper function + +DOMHighResTimeStamp +NTPtoDOMHighResTimeStamp(uint32_t ntpHigh, uint32_t ntpLow); + +/** + * Concrete class for Audio session. Hooks up + * - media-source and target to external transport + */ +class WebrtcAudioConduit:public AudioSessionConduit + ,public webrtc::Transport +{ +public: + //VoiceEngine defined constant for Payload Name Size. + static const unsigned int CODEC_PLNAME_SIZE; + + /** + * APIs used by the registered external transport to this Conduit to + * feed in received RTP Frames to the VoiceEngine for decoding + */ + virtual MediaConduitErrorCode ReceivedRTPPacket(const void *data, int len) override; + + /** + * APIs used by the registered external transport to this Conduit to + * feed in received RTCP Frames to the VoiceEngine for decoding + */ + virtual MediaConduitErrorCode ReceivedRTCPPacket(const void *data, int len) override; + + virtual MediaConduitErrorCode StopTransmitting() override; + virtual MediaConduitErrorCode StartTransmitting() override; + virtual MediaConduitErrorCode StopReceiving() override; + virtual MediaConduitErrorCode StartReceiving() override; + + /** + * Function to configure send codec for the audio session + * @param sendSessionConfig: CodecConfiguration + * @result: On Success, the audio engine is configured with passed in codec for send + * On failure, audio engine transmit functionality is disabled. + * NOTE: This API can be invoked multiple time. Invoking this API may involve restarting + * transmission sub-system on the engine. + */ + virtual MediaConduitErrorCode ConfigureSendMediaCodec(const AudioCodecConfig* codecConfig) override; + /** + * Function to configure list of receive codecs for the audio session + * @param sendSessionConfig: CodecConfiguration + * @result: On Success, the audio engine is configured with passed in codec for send + * Also the playout is enabled. + * On failure, audio engine transmit functionality is disabled. + * NOTE: This API can be invoked multiple time. Invoking this API may involve restarting + * transmission sub-system on the engine. + */ + virtual MediaConduitErrorCode ConfigureRecvMediaCodecs( + const std::vector& codecConfigList) override; + /** + * Function to enable the audio level extension + * @param enabled: enable extension + */ + virtual MediaConduitErrorCode EnableAudioLevelExtension(bool enabled, uint8_t id) override; + + /** + * Register External Transport to this Conduit. RTP and RTCP frames from the VoiceEngine + * shall be passed to the registered transport for transporting externally. + */ + virtual MediaConduitErrorCode SetTransmitterTransport(RefPtr aTransport) override; + + virtual MediaConduitErrorCode SetReceiverTransport(RefPtr aTransport) override; + + /** + * Function to deliver externally captured audio sample for encoding and transport + * @param audioData [in]: Pointer to array containing a frame of audio + * @param lengthSamples [in]: Length of audio frame in samples in multiple of 10 milliseconds + * Ex: Frame length is 160, 320, 440 for 16, 32, 44 kHz sampling rates + respectively. + audioData[] should be of lengthSamples in size + say, for 16kz sampling rate, audioData[] should contain 160 + samples of 16-bits each for a 10m audio frame. + * @param samplingFreqHz [in]: Frequency/rate of the sampling in Hz ( 16000, 32000 ...) + * @param capture_delay [in]: Approx Delay from recording until it is delivered to VoiceEngine + in milliseconds. + * NOTE: ConfigureSendMediaCodec() SHOULD be called before this function can be invoked + * This ensures the inserted audio-samples can be transmitted by the conduit + * + */ + virtual MediaConduitErrorCode SendAudioFrame(const int16_t speechData[], + int32_t lengthSamples, + int32_t samplingFreqHz, + int32_t capture_time) override; + + /** + * Function to grab a decoded audio-sample from the media engine for rendering + * / playoutof length 10 milliseconds. + * + * @param speechData [in]: Pointer to a array to which a 10ms frame of audio will be copied + * @param samplingFreqHz [in]: Frequency of the sampling for playback in Hertz (16000, 32000,..) + * @param capture_delay [in]: Estimated Time between reading of the samples to rendering/playback + * @param lengthSamples [out]: Will contain length of the audio frame in samples at return. + Ex: A value of 160 implies 160 samples each of 16-bits was copied + into speechData + * NOTE: This function should be invoked every 10 milliseconds for the best + * peformance + * NOTE: ConfigureRecvMediaCodec() SHOULD be called before this function can be invoked + * This ensures the decoded samples are ready for reading and playout is enabled. + * + */ + virtual MediaConduitErrorCode GetAudioFrame(int16_t speechData[], + int32_t samplingFreqHz, + int32_t capture_delay, + int& lengthSamples) override; + + + /** + * Webrtc transport implementation to send and receive RTP packet. + * AudioConduit registers itself as ExternalTransport to the VoiceEngine + */ + virtual int SendPacket(int channel, const void *data, size_t len) override; + + /** + * Webrtc transport implementation to send and receive RTCP packet. + * AudioConduit registers itself as ExternalTransport to the VoiceEngine + */ + virtual int SendRTCPPacket(int channel, const void *data, size_t len) override; + + + virtual uint64_t CodecPluginID() override { return 0; } + + WebrtcAudioConduit(): + mVoiceEngine(nullptr), + mTransportMonitor("WebrtcAudioConduit"), + mTransmitterTransport(nullptr), + mReceiverTransport(nullptr), + mEngineTransmitting(false), + mEngineReceiving(false), + mChannel(-1), + mDtmfEnabled(false), + mCodecMutex("AudioConduit codec db"), + mCaptureDelay(150), +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + mLastTimestamp(0), +#endif // MOZILLA_INTERNAL_API + mSamples(0), + mLastSyncLog(0) + { + } + + virtual ~WebrtcAudioConduit(); + + MediaConduitErrorCode Init(); + + int GetChannel() { return mChannel; } + webrtc::VoiceEngine* GetVoiceEngine() { return mVoiceEngine; } + bool SetLocalSSRC(unsigned int ssrc) override; + bool GetLocalSSRC(unsigned int* ssrc) override; + bool GetRemoteSSRC(unsigned int* ssrc) override; + bool SetLocalCNAME(const char* cname) override; + bool GetVideoEncoderStats(double* framerateMean, + double* framerateStdDev, + double* bitrateMean, + double* bitrateStdDev, + uint32_t* droppedFrames) override + { + return false; + } + bool GetVideoDecoderStats(double* framerateMean, + double* framerateStdDev, + double* bitrateMean, + double* bitrateStdDev, + uint32_t* discardedPackets) override + { + return false; + } + bool GetAVStats(int32_t* jitterBufferDelayMs, + int32_t* playoutBufferDelayMs, + int32_t* avSyncOffsetMs) override; + bool GetRTPStats(unsigned int* jitterMs, unsigned int* cumulativeLost) override; + bool GetRTCPReceiverReport(DOMHighResTimeStamp* timestamp, + uint32_t* jitterMs, + uint32_t* packetsReceived, + uint64_t* bytesReceived, + uint32_t *cumulativeLost, + int32_t* rttMs) override; + bool GetRTCPSenderReport(DOMHighResTimeStamp* timestamp, + unsigned int* packetsSent, + uint64_t* bytesSent) override; + + bool SetDtmfPayloadType(unsigned char type) override; + + bool InsertDTMFTone(int channel, int eventCode, bool outOfBand, + int lengthMs, int attenuationDb) override; + +private: + WebrtcAudioConduit(const WebrtcAudioConduit& other) = delete; + void operator=(const WebrtcAudioConduit& other) = delete; + + //Local database of currently applied receive codecs + typedef std::vector RecvCodecList; + + //Function to convert between WebRTC and Conduit codec structures + bool CodecConfigToWebRTCCodec(const AudioCodecConfig* codecInfo, + webrtc::CodecInst& cinst); + + //Checks if given sampling frequency is supported + bool IsSamplingFreqSupported(int freq) const; + + //Generate block size in sample lenght for a given sampling frequency + unsigned int GetNum10msSamplesForFrequency(int samplingFreqHz) const; + + // Function to copy a codec structure to Conduit's database + bool CopyCodecToDB(const AudioCodecConfig* codecInfo); + + // Functions to verify if the codec passed is already in + // conduits database + bool CheckCodecForMatch(const AudioCodecConfig* codecInfo) const; + bool CheckCodecsForMatch(const AudioCodecConfig* curCodecConfig, + const AudioCodecConfig* codecInfo) const; + //Checks the codec to be applied + MediaConduitErrorCode ValidateCodecConfig(const AudioCodecConfig* codecInfo, bool send); + + //Utility function to dump recv codec database + void DumpCodecDB() const; + + webrtc::VoiceEngine* mVoiceEngine; + mozilla::ReentrantMonitor mTransportMonitor; + RefPtr mTransmitterTransport; + RefPtr mReceiverTransport; + ScopedCustomReleasePtr mPtrVoENetwork; + ScopedCustomReleasePtr mPtrVoEBase; + ScopedCustomReleasePtr mPtrVoECodec; + ScopedCustomReleasePtr mPtrVoEXmedia; + ScopedCustomReleasePtr mPtrVoEProcessing; + ScopedCustomReleasePtr mPtrVoEVideoSync; + ScopedCustomReleasePtr mPtrVoERTP_RTCP; + ScopedCustomReleasePtr mPtrRTP; + //engine states of our interets + mozilla::Atomic mEngineTransmitting; // If true => VoiceEngine Send-subsystem is up + mozilla::Atomic mEngineReceiving; // If true => VoiceEngine Receive-subsystem is up + // and playout is enabled + // Keep track of each inserted RTP block and the time it was inserted + // so we can estimate the clock time for a specific TimeStamp coming out + // (for when we send data to MediaStreamTracks). Blocks are aged out as needed. + struct Processing { + TimeStamp mTimeStamp; + uint32_t mRTPTimeStamp; // RTP timestamps received + }; + AutoTArray mProcessing; + + int mChannel; + bool mDtmfEnabled; + RecvCodecList mRecvCodecList; + + Mutex mCodecMutex; // protects mCurSendCodecConfig + nsAutoPtr mCurSendCodecConfig; + + // Current "capture" delay (really output plus input delay) + int32_t mCaptureDelay; + +#if !defined(MOZILLA_EXTERNAL_LINKAGE) + uint32_t mLastTimestamp; +#endif // MOZILLA_INTERNAL_API + + uint32_t mSamples; + uint32_t mLastSyncLog; +}; + +} // end namespace + +#endif diff --git a/media/webrtc/signaling/src/media-conduit/CodecConfig.h b/media/webrtc/signaling/src/media-conduit/CodecConfig.h new file mode 100755 index 000000000..308c97948 --- /dev/null +++ b/media/webrtc/signaling/src/media-conduit/CodecConfig.h @@ -0,0 +1,166 @@ + +/* 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 "signaling/src/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 mPacSize; + int mChannels; + int mRate; + + bool mFECEnabled; + bool mDtmfEnabled; + + // OPUS-specific + int mMaxPlaybackRate; + + /* Default constructor is not provided since as a consumer, we + * can't decide the default configuration for the codec + */ + explicit AudioCodecConfig(int type, std::string name, + int freq, int pacSize, + int channels, int rate, bool FECEnabled) + : mType(type), + mName(name), + mFreq(freq), + mPacSize(pacSize), + mChannels(channels), + mRate(rate), + mFECEnabled(FECEnabled), + mDtmfEnabled(false), + mMaxPlaybackRate(0) + { + } +}; + +/* + * 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; +}; + + +// 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; + + EncodingConstraints mEncodingConstraints; + struct SimulcastEncoding { + std::string rid; + EncodingConstraints constraints; + }; + std::vector mSimulcastEncodings; + std::string mSpropParameterSets; + uint8_t mProfile; + uint8_t mConstraints; + uint8_t mLevel; + uint8_t mPacketizationMode; + // TODO: add external negotiated SPS/PPS + + VideoCodecConfig(int type, + std::string name, + const EncodingConstraints& constraints, + const struct VideoCodecConfigH264 *h264 = nullptr) : + mType(type), + mName(name), + mFECFbSet(false), + 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; + } + } + + // 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; } + +}; +} +#endif diff --git a/media/webrtc/signaling/src/media-conduit/CodecStatistics.cpp b/media/webrtc/signaling/src/media-conduit/CodecStatistics.cpp new file mode 100644 index 000000000..eb03c0bf8 --- /dev/null +++ b/media/webrtc/signaling/src/media-conduit/CodecStatistics.cpp @@ -0,0 +1,183 @@ +/* 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 "CodecStatistics.h" + +#include "CSFLog.h" +#include "mozilla/Telemetry.h" + +using namespace mozilla; +using namespace webrtc; + +// use the same tag as VideoConduit +static const char* logTag ="WebrtcVideoSessionConduit"; + +VideoCodecStatistics::VideoCodecStatistics(int channel, + ViECodec* codec) : + mChannel(channel), + mSentRawFrames(0), + mPtrViECodec(codec), + mEncoderDroppedFrames(0), + mDecoderDiscardedPackets(0), + mRegisteredEncode(false), + mRegisteredDecode(false), + mReceiveState(kReceiveStateInitial) +#ifdef MOZILLA_INTERNAL_API + , mRecoveredBeforeLoss(0) + , mRecoveredLosses(0) +#endif +{ + MOZ_ASSERT(mPtrViECodec); +} + +VideoCodecStatistics::~VideoCodecStatistics() +{ + if (mRegisteredEncode) { + mPtrViECodec->DeregisterEncoderObserver(mChannel); + } + if (mRegisteredDecode) { + mPtrViECodec->DeregisterDecoderObserver(mChannel); + } +} + +void VideoCodecStatistics::Register(bool encoder) +{ + if (encoder && !mRegisteredEncode) { + mPtrViECodec->RegisterEncoderObserver(mChannel, *this); + mRegisteredEncode = true; + } else if (!encoder && !mRegisteredDecode) { + mPtrViECodec->RegisterDecoderObserver(mChannel, *this); + mRegisteredDecode = true; + } +} + +void VideoCodecStatistics::OutgoingRate(const int video_channel, + const uint32_t framerate, + const uint32_t bitrate) +{ + unsigned int keyFrames, deltaFrames; + mPtrViECodec->GetSendCodecStatistics(video_channel, keyFrames, deltaFrames); + uint32_t dropped = mSentRawFrames - (keyFrames + deltaFrames); + CSFLogDebug(logTag, + "encoder statistics - framerate: %u, bitrate: %u, dropped frames: %u", + framerate, bitrate, dropped); + mEncoderBitRate.Push(bitrate); + mEncoderFps.Push(framerate); + mEncoderDroppedFrames += dropped; +} + +void VideoCodecStatistics::IncomingCodecChanged(const int video_channel, + const VideoCodec& video_codec) +{ + CSFLogDebug(logTag, + "channel %d change codec to \"%s\" ", + video_channel, video_codec.plName); +} + +void VideoCodecStatistics::IncomingRate(const int video_channel, + const unsigned int framerate, + const unsigned int bitrate) +{ + unsigned int discarded = mPtrViECodec->GetDiscardedPackets(video_channel); + CSFLogDebug(logTag, + "decoder statistics - framerate: %u, bitrate: %u, discarded packets %u", + framerate, bitrate, discarded); + mDecoderBitRate.Push(bitrate); + mDecoderFps.Push(framerate); + mDecoderDiscardedPackets += discarded; +} + +void VideoCodecStatistics::ReceiveStateChange(const int aChannel, + VideoReceiveState aState) +{ + CSFLogDebug(logTag,"New state for %d: %d (was %d)", aChannel, aState, mReceiveState); +#ifdef MOZILLA_INTERNAL_API + if (mFirstDecodeTime.IsNull()) { + mFirstDecodeTime = TimeStamp::Now(); + } + /* + * Invalid transitions: + * WaitingKey -> PreemptiveNACK + * DecodingWithErrors -> PreemptiveNACK + */ + + switch (mReceiveState) { + case kReceiveStateNormal: + case kReceiveStateInitial: + // in a normal state + if (aState != kReceiveStateNormal && aState != kReceiveStateInitial) { + // no longer in a normal state + if (aState != kReceiveStatePreemptiveNACK) { + mReceiveFailureTime = TimeStamp::Now(); + } + } // else Normal<->Initial transition + break; + default: + // not in a normal state + if (aState == kReceiveStateNormal || aState == kReceiveStateInitial) { + + if (mReceiveState == kReceiveStatePreemptiveNACK) { + mRecoveredBeforeLoss++; + CSFLogError(logTag, "Video error avoided by NACK recovery"); + } else if (!mReceiveFailureTime.IsNull()) { // safety + TimeDuration timeDelta = TimeStamp::Now() - mReceiveFailureTime; + CSFLogError(logTag, "Video error duration: %u ms", + static_cast(timeDelta.ToMilliseconds())); + Telemetry::Accumulate(Telemetry::WEBRTC_VIDEO_ERROR_RECOVERY_MS, + static_cast(timeDelta.ToMilliseconds())); + + mRecoveredLosses++; // to calculate losses per minute + mTotalLossTime += timeDelta; // To calculate % time in recovery + } + } // else non-Normal to different non-normal transition + break; + } + +#endif + + mReceiveState = aState; +} + +void VideoCodecStatistics::EndOfCallStats() +{ +#ifdef MOZILLA_INTERNAL_API + if (!mFirstDecodeTime.IsNull()) { + TimeDuration callDelta = TimeStamp::Now() - mFirstDecodeTime; + if (callDelta.ToSeconds() != 0) { + uint32_t recovered_per_min = mRecoveredBeforeLoss/(callDelta.ToSeconds()/60); + CSFLogError(logTag, "Video recovery before error per min %u", recovered_per_min); + Telemetry::Accumulate(Telemetry::WEBRTC_VIDEO_RECOVERY_BEFORE_ERROR_PER_MIN, + recovered_per_min); + uint32_t err_per_min = mRecoveredLosses/(callDelta.ToSeconds()/60); + CSFLogError(logTag, "Video recovery after error per min %u", err_per_min); + Telemetry::Accumulate(Telemetry::WEBRTC_VIDEO_RECOVERY_AFTER_ERROR_PER_MIN, + err_per_min); + float percent = (mTotalLossTime.ToSeconds()*100)/callDelta.ToSeconds(); + CSFLogError(logTag, "Video error time percentage %f%%", percent); + Telemetry::Accumulate(Telemetry::WEBRTC_VIDEO_DECODE_ERROR_TIME_PERMILLE, + static_cast(percent*10)); + } + } +#endif +} + +void VideoCodecStatistics::SentFrame() +{ + mSentRawFrames++; +} + +void VideoCodecStatistics::Dump() +{ + Dump(mEncoderBitRate, "encoder bitrate"); + Dump(mEncoderFps, "encoder fps"); + Dump(mDecoderBitRate, "decoder bitrate"); + Dump(mDecoderFps, "decoder fps"); +} + +void VideoCodecStatistics::Dump(RunningStat& s, const char *name) +{ + CSFLogDebug(logTag, + "%s, mean: %f, variance: %f, standard deviation: %f", + name, s.Mean(), s.Variance(), s.StandardDeviation()); +} diff --git a/media/webrtc/signaling/src/media-conduit/CodecStatistics.h b/media/webrtc/signaling/src/media-conduit/CodecStatistics.h new file mode 100644 index 000000000..ab81a6f33 --- /dev/null +++ b/media/webrtc/signaling/src/media-conduit/CodecStatistics.h @@ -0,0 +1,111 @@ +/* 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_STATISTICS_H_ +#define CODEC_STATISTICS_H_ +#include + +#include "nsTArray.h" +#include "nsISupportsImpl.h" +#include "mozilla/TimeStamp.h" +#include "webrtc/common_types.h" +#include "webrtc/video_engine/include/vie_codec.h" +#include "MediaEngineWrapper.h" +#include "RunningStat.h" + +namespace mozilla { + +// Statistics-gathering observer for Video Encoder and Decoder + +class VideoCodecStatistics : public webrtc::ViEEncoderObserver + , public webrtc::ViEDecoderObserver +{ +public: + VideoCodecStatistics(int channel, webrtc::ViECodec* vieCodec); + ~VideoCodecStatistics(); + void Register(bool encoder); + + void SentFrame(); + virtual void OutgoingRate(const int video_channel, + const unsigned int framerate, const unsigned int bitrate) override; + + virtual void IncomingCodecChanged(const int video_channel, + const webrtc::VideoCodec& video_codec) override; + + virtual void IncomingRate(const int video_channel, + const unsigned int framerate, + const unsigned int bitrate) override; + + void ReceiveStateChange(const int video_channel, webrtc::VideoReceiveState state) override; + + void EndOfCallStats(); + + virtual void RequestNewKeyFrame(const int video_channel) override {}; + + virtual void SuspendChange(int video_channel, bool is_suspended) override {}; + virtual void DecoderTiming(int decode_ms, + int max_decode_ms, + int current_delay_ms, + int target_delay_ms, + int jitter_buffer_ms, + int min_playout_delay_ms, + int render_delay_ms) override {} + + bool GetEncoderStats(double* framerateMean, + double* framerateStdDev, + double* bitrateMean, + double* bitrateStdDev, + uint32_t* droppedFrames) + { + *framerateMean = mEncoderFps.Mean(); + *framerateStdDev = mEncoderFps.StandardDeviation(); + *bitrateMean = mEncoderBitRate.Mean(); + *bitrateStdDev = mEncoderBitRate.StandardDeviation(); + *droppedFrames = mEncoderDroppedFrames; + return true; + } + + bool GetDecoderStats(double* framerateMean, + double* framerateStdDev, + double* bitrateMean, + double* bitrateStdDev, + uint32_t* discardedPackets) + { + *framerateMean = mDecoderFps.Mean(); + *framerateStdDev = mDecoderFps.StandardDeviation(); + *bitrateMean = mDecoderBitRate.Mean(); + *bitrateStdDev = mDecoderBitRate.StandardDeviation(); + *discardedPackets = mDecoderDiscardedPackets; + return true; + } + + void Dump(); +private: + void Dump(RunningStat& s, const char *name); + + int mChannel; + uint32_t mSentRawFrames; + ScopedCustomReleasePtr mPtrViECodec; // back-pointer + + RunningStat mEncoderBitRate; + RunningStat mEncoderFps; + uint32_t mEncoderDroppedFrames; + RunningStat mDecoderBitRate; + RunningStat mDecoderFps; + uint32_t mDecoderDiscardedPackets; + bool mRegisteredEncode; + bool mRegisteredDecode; + + webrtc::VideoReceiveState mReceiveState; +#ifdef MOZILLA_INTERNAL_API + TimeStamp mFirstDecodeTime; + TimeStamp mReceiveFailureTime; + TimeDuration mTotalLossTime; + uint32_t mRecoveredBeforeLoss; + uint32_t mRecoveredLosses; +#endif +}; + +} + +#endif //CODEC_STATISTICS_H_ diff --git a/media/webrtc/signaling/src/media-conduit/GmpVideoCodec.cpp b/media/webrtc/signaling/src/media-conduit/GmpVideoCodec.cpp new file mode 100644 index 000000000..0c4d81e44 --- /dev/null +++ b/media/webrtc/signaling/src/media-conduit/GmpVideoCodec.cpp @@ -0,0 +1,18 @@ +/* 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 { + +VideoEncoder* GmpVideoCodec::CreateEncoder() { + return static_cast(new WebrtcVideoEncoderProxy()); +} + +VideoDecoder* GmpVideoCodec::CreateDecoder() { + return static_cast(new WebrtcVideoDecoderProxy()); +} + +} diff --git a/media/webrtc/signaling/src/media-conduit/GmpVideoCodec.h b/media/webrtc/signaling/src/media-conduit/GmpVideoCodec.h new file mode 100644 index 000000000..340150409 --- /dev/null +++ b/media/webrtc/signaling/src/media-conduit/GmpVideoCodec.h @@ -0,0 +1,19 @@ +/* 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 "MediaConduitInterface.h" + +namespace mozilla { +class GmpVideoCodec { + public: + static VideoEncoder* CreateEncoder(); + static VideoDecoder* CreateDecoder(); +}; + +} + +#endif diff --git a/media/webrtc/signaling/src/media-conduit/MediaCodecVideoCodec.cpp b/media/webrtc/signaling/src/media-conduit/MediaCodecVideoCodec.cpp new file mode 100644 index 000000000..0c6c2fdde --- /dev/null +++ b/media/webrtc/signaling/src/media-conduit/MediaCodecVideoCodec.cpp @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CSFLog.h" +#include "nspr.h" + +#include "WebrtcMediaCodecVP8VideoCodec.h" +#include "MediaCodecVideoCodec.h" + +namespace mozilla { + +static const char* logTag ="MediaCodecVideoCodec"; + +VideoEncoder* MediaCodecVideoCodec::CreateEncoder(CodecType aCodecType) { + CSFLogDebug(logTag, "%s ", __FUNCTION__); + if (aCodecType == CODEC_VP8) { + return new WebrtcMediaCodecVP8VideoEncoder(); + } + return nullptr; +} + +VideoDecoder* MediaCodecVideoCodec::CreateDecoder(CodecType aCodecType) { + CSFLogDebug(logTag, "%s ", __FUNCTION__); + if (aCodecType == CODEC_VP8) { + return new WebrtcMediaCodecVP8VideoDecoder(); + } + return nullptr; +} + +} diff --git a/media/webrtc/signaling/src/media-conduit/MediaCodecVideoCodec.h b/media/webrtc/signaling/src/media-conduit/MediaCodecVideoCodec.h new file mode 100644 index 000000000..50dde8211 --- /dev/null +++ b/media/webrtc/signaling/src/media-conduit/MediaCodecVideoCodec.h @@ -0,0 +1,31 @@ +/* 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 MediaCodecVideoCodec_h__ +#define MediaCodecVideoCodec_h__ + +#include "MediaConduitInterface.h" + +namespace mozilla { +class MediaCodecVideoCodec { + public: + enum CodecType { + CODEC_VP8, + }; + /** + * Create encoder object for codec type |aCodecType|. Return |nullptr| when + * failed. + */ + static VideoEncoder* CreateEncoder(CodecType aCodecType); + + /** + * Create decoder object for codec type |aCodecType|. Return |nullptr| when + * failed. + */ + static VideoDecoder* CreateDecoder(CodecType aCodecType); +}; + +} + +#endif // MediaCodecVideoCodec_h__ diff --git a/media/webrtc/signaling/src/media-conduit/MediaConduitErrors.h b/media/webrtc/signaling/src/media-conduit/MediaConduitErrors.h new file mode 100755 index 000000000..3709d59a0 --- /dev/null +++ b/media/webrtc/signaling/src/media-conduit/MediaConduitErrors.h @@ -0,0 +1,48 @@ +/* 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 +}; + +} + +#endif + diff --git a/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h b/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h new file mode 100755 index 000000000..05c34fea0 --- /dev/null +++ b/media/webrtc/signaling/src/media-conduit/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 "nsISupportsImpl.h" +#include "nsXPCOM.h" +#include "nsDOMNavigationTiming.h" +#include "mozilla/RefPtr.h" +#include "CodecConfig.h" +#include "VideoTypes.h" +#include "MediaConduitErrors.h" + +#include "ImageContainer.h" + +#include "webrtc/common_types.h" +namespace webrtc { +class I420VideoFrame; +} + +#include + +namespace mozilla { +/** + * Abstract Interface for transporting RTP packets - audio/vidoeo + * The consumers of this interface are responsible for passing in + * the RTPfied media packets + */ +class TransportInterface +{ +protected: + virtual ~TransportInterface() {} + +public: + /** + * RTP Transport Function to be implemented by concrete transport implementation + * @param data : RTP Packet (audio/video) to be transported + * @param len : Length of the media packet + * @result : NS_OK on success, NS_ERROR_FAILURE otherwise + */ + virtual nsresult SendRtpPacket(const void* data, int len) = 0; + + /** + * RTCP Transport Function to be implemented by concrete transport implementation + * @param data : RTCP Packet to be transported + * @param len : Length of the RTCP packet + * @result : NS_OK on success, NS_ERROR_FAILURE otherwise + */ + virtual nsresult SendRtcpPacket(const void* data, int len) = 0; + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TransportInterface) +}; + +/** + * This class wraps image object for VideoRenderer::RenderVideoFrame() + * callback implementation to use for rendering. + */ +class ImageHandle +{ +public: + explicit ImageHandle(layers::Image* image) : mImage(image) {} + + const RefPtr& GetImage() const { return mImage; } + +private: + RefPtr mImage; +}; + +/** + * 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,