├── .npmrc ├── .gitattributes ├── TODO.md ├── .gitignore ├── include ├── version.hpp ├── scalabilityMode.hpp ├── mediasoupclient.hpp ├── sdp │ ├── Utils.hpp │ ├── MediaSection.hpp │ └── RemoteSdp.hpp ├── ortc.hpp ├── DataProducer.hpp ├── Consumer.hpp ├── DataConsumer.hpp ├── Device.hpp ├── Producer.hpp ├── MediaSoupClientErrors.hpp ├── Utils.hpp ├── Logger.hpp ├── Handler.hpp ├── PeerConnection.hpp └── Transport.hpp ├── package.json ├── version.hpp.in ├── test ├── Info.plist ├── include │ ├── fakeParameters.hpp │ ├── helpers.hpp │ ├── MediaStreamTrackFactory.hpp │ └── FakeTransportListener.hpp ├── deps │ └── libwebrtc │ │ ├── CMakeLists.txt │ │ └── pc │ │ └── test │ │ └── fake_audio_capture_module.h ├── src │ ├── scalabilityMode.test.cpp │ ├── tests.cpp │ ├── SdpUtils.test.cpp │ ├── PeerConnection.test.cpp │ ├── Device.test.cpp │ ├── MediaStreamTrackFactory.cpp │ ├── Handler.test.cpp │ ├── ortc.test.cpp │ ├── fakeParameters.cpp │ └── RemoteSdp.test.cpp ├── data │ ├── webrtc.sdp │ ├── jssip.sdp │ └── audio_video.sdp └── CMakeLists.txt ├── gulpfile.js ├── .github └── FUNDING.yml ├── README.md ├── LICENSE ├── src ├── mediasoupclient.cpp ├── Logger.cpp ├── scalabilityMode.cpp ├── Consumer.cpp ├── DataProducer.cpp ├── DataConsumer.cpp ├── Producer.cpp ├── Device.cpp └── sdp │ └── RemoteSdp.cpp ├── scripts ├── test.sh ├── get-dep.sh ├── tidy.sh └── clang-tidy.py ├── CHANGELOG.md ├── .clang-format └── CMakeLists.txt /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.hpp text eol=lf 2 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # libmediasoupclient TODO 2 | 3 | * To compile tests, libwebrtc must be built with arg `rtc_enable_tests=true` in `gn gen` command. 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /node_modules/ 3 | 4 | compile_commands.json 5 | 6 | # Vim temporal files. 7 | *.swp 8 | *.swo 9 | deps 10 | .vscode 11 | -------------------------------------------------------------------------------- /include/version.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MSC_VERSION_HPP 2 | #define MSC_VERSION_HPP 3 | 4 | // Version configuration. 5 | #define MEDIASOUPCLIENT_VERSION_MAJOR 3 6 | #define MEDIASOUPCLIENT_VERSION_MINOR 5 7 | #define MEDIASOUPCLIENT_VERSION_PATCH 0 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "libmediasoupclient-node-utils", 3 | "version": "0.0.0", 4 | "private": true, 5 | "os": [ 6 | "!win32" 7 | ], 8 | "devDependencies": { 9 | "clang-tools-prebuilt": "^0.1.4", 10 | "gulp": "^4.0.2", 11 | "gulp-clang-format": "^1.0.27" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /include/scalabilityMode.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MSC_SCALABILITY_MODE_HPP 2 | #define MSC_SCALABILITY_MODE_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace mediasoupclient 8 | { 9 | nlohmann::json parseScalabilityMode(const std::string& scalabilityMode); 10 | } // namespace mediasoupclient 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /version.hpp.in: -------------------------------------------------------------------------------- 1 | #ifndef MSC_VERSION_HPP 2 | #define MSC_VERSION_HPP 3 | 4 | // Version configuration. 5 | #define MEDIASOUPCLIENT_VERSION_MAJOR @mediasoupclient_VERSION_MAJOR@ 6 | #define MEDIASOUPCLIENT_VERSION_MINOR @mediasoupclient_VERSION_MINOR@ 7 | #define MEDIASOUPCLIENT_VERSION_PATCH @mediasoupclient_VERSION_PATCH@ 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /include/mediasoupclient.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MEDIASOUP_CLIENT_HPP 2 | #define MEDIASOUP_CLIENT_HPP 3 | 4 | #include "Device.hpp" 5 | #include "Logger.hpp" 6 | 7 | namespace mediasoupclient 8 | { 9 | void Initialize(); // NOLINT(readability-identifier-naming) 10 | void Cleanup(); // NOLINT(readability-identifier-naming) 11 | std::string Version(); // NOLINT(readability-identifier-naming) 12 | } // namespace mediasoupclient 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /test/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSCameraUsageDescription 6 | This script needs access to your camera to run. 7 | NSMicrophoneUsageDescription 8 | This script needs access to your microphone to run. 9 | 10 | 11 | -------------------------------------------------------------------------------- /test/include/fakeParameters.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MSC_TEST_FAKE_PARAMETERS 2 | #define MSC_TEST_FAKE_PARAMETERS 3 | 4 | #include 5 | 6 | using json = nlohmann::json; 7 | 8 | json generateRouterRtpCapabilities(); 9 | json generateRtpParametersByKind(); 10 | json generateLocalDtlsParameters(); 11 | json generateTransportRemoteParameters(); 12 | std::string generateProducerRemoteId(); 13 | json generateConsumerRemoteParameters(const std::string& codecMimeType); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const clangFormat = require('gulp-clang-format'); 3 | 4 | const files = 5 | [ 6 | 'src/**/*.cpp', 7 | 'include/**/*.hpp', 8 | 'test/src/**/*.cpp', 9 | 'test/include/**/*.hpp' 10 | ]; 11 | 12 | gulp.task('lint', () => 13 | { 14 | const src = files; 15 | 16 | return gulp.src(src) 17 | .pipe(clangFormat.checkFormat('file', null, { verbose: true, fail: true })); 18 | }); 19 | 20 | gulp.task('format', () => 21 | { 22 | const src = files; 23 | 24 | return gulp.src(src, { base: '.' }) 25 | .pipe(clangFormat.format('file')) 26 | .pipe(gulp.dest('.')); 27 | }); 28 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: mediasoup 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with a single custom sponsorship URL 13 | -------------------------------------------------------------------------------- /test/include/helpers.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MSC_TEST_HELPERS_HPP 2 | #define MSC_TEST_HELPERS_HPP 3 | 4 | #include // std::ifstream 5 | #include 6 | #include // std::istreambuf_iterator 7 | #include 8 | 9 | namespace helpers 10 | { 11 | inline std::string readFile(const char* file) 12 | { 13 | std::ifstream in(file); 14 | 15 | if (!in) 16 | throw std::invalid_argument("could not open file"); 17 | 18 | std::string content; 19 | 20 | in.seekg(0, std::ios::end); 21 | content.reserve(in.tellg()); 22 | in.seekg(0, std::ios::beg); 23 | 24 | content.assign((std::istreambuf_iterator(in)), std::istreambuf_iterator()); 25 | 26 | return content; 27 | } 28 | } // namespace helpers 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libmediasoupclient 2 | 3 | C++ client side library for building [mediasoup][mediasoup-website] based applications built on top of Google's libwebrtc C++ library. 4 | 5 | 6 | 7 | ## Website and Documentation 8 | 9 | * [mediasoup.org][mediasoup-website] 10 | 11 | 12 | ## Support Forum 13 | 14 | * [mediasoup.discourse.group][mediasoup-discourse] 15 | 16 | 17 | ## Authors 18 | 19 | * José Luis Millán [[website](https://jssip.net)|[github](https://github.com/jmillan/)] 20 | * Iñaki Baz Castillo [[website](https://inakibaz.me)|[github](https://github.com/ibc/)] 21 | 22 | 23 | 24 | ## License 25 | 26 | [ISC](./LICENSE) 27 | 28 | 29 | 30 | 31 | [mediasoup-website]: https://mediasoup.org 32 | [mediasoup-discourse]: https://mediasoup.discourse.group 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright © 2019, José Luis Millán 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /test/deps/libwebrtc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SOURCE_FILES 2 | pc/test/fake_audio_capture_module.cc 3 | ) 4 | 5 | # Create target. 6 | add_library(webrtc STATIC ${SOURCE_FILES}) 7 | 8 | # Private (implementation) header files. 9 | target_include_directories(webrtc PRIVATE ${PROJECT_SOURCE_DIR}/deps/libwebrtc) 10 | 11 | # Public (interface) headers from dependencies. 12 | target_include_directories(webrtc PUBLIC 13 | "${LIBWEBRTC_INCLUDE_PATH}" 14 | "${LIBWEBRTC_INCLUDE_PATH}/third_party/abseil-cpp" 15 | ) 16 | 17 | # Compile definitions for libwebrtc. 18 | target_compile_definitions(webrtc PUBLIC 19 | $<$>:WEBRTC_POSIX> 20 | $<$:WEBRTC_WIN> 21 | $<$:NOMINMAX> 22 | $<$:WIN32_LEAN_AND_MEAN> 23 | $<$:WEBRTC_MAC> 24 | ) 25 | 26 | -------------------------------------------------------------------------------- /src/mediasoupclient.cpp: -------------------------------------------------------------------------------- 1 | #define MSC_CLASS "mediasoupclient" 2 | 3 | #include "mediasoupclient.hpp" 4 | #include "Logger.hpp" 5 | #include "version.hpp" 6 | #include 7 | #include 8 | 9 | namespace mediasoupclient 10 | { 11 | void Initialize() // NOLINT(readability-identifier-naming) 12 | { 13 | MSC_TRACE(); 14 | 15 | MSC_DEBUG("mediasoupclient v%s", Version().c_str()); 16 | 17 | webrtc::InitializeSSL(); 18 | } 19 | 20 | void Cleanup() // NOLINT(readability-identifier-naming) 21 | { 22 | MSC_TRACE(); 23 | 24 | webrtc::CleanupSSL(); 25 | } 26 | 27 | std::string Version() // NOLINT(readability-identifier-naming) 28 | { 29 | std::stringstream ss; 30 | 31 | ss << MEDIASOUPCLIENT_VERSION_MAJOR << "." << MEDIASOUPCLIENT_VERSION_MINOR << "." 32 | << MEDIASOUPCLIENT_VERSION_PATCH; 33 | 34 | return ss.str(); 35 | } 36 | } // namespace mediasoupclient 37 | -------------------------------------------------------------------------------- /include/sdp/Utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MSC_SDP_UTILS_HPP 2 | #define MSC_SDP_UTILS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace mediasoupclient 10 | { 11 | namespace Sdp 12 | { 13 | namespace Utils 14 | { 15 | json extractRtpCapabilities(const json& sdpObject); 16 | json extractDtlsParameters(const json& sdpObject); 17 | void fillRtpParametersForTrack(json& rtpParameters, const json& sdpObject, const std::string& mid); 18 | void addLegacySimulcast(json& offerMediaObject, uint8_t numStreams); 19 | std::string getCname(const json& offerMediaObject); 20 | json getRtpEncodings(const json& offerMediaObject); 21 | void applyCodecParameters(const json& offerRtpParameters, json& answerMediaObject); 22 | } // namespace Utils 23 | } // namespace Sdp 24 | } // namespace mediasoupclient 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/Logger.cpp: -------------------------------------------------------------------------------- 1 | #define MSC_CLASS "Logger" 2 | 3 | #include "Logger.hpp" 4 | #include 5 | 6 | namespace mediasoupclient 7 | { 8 | /* Class variables. */ 9 | 10 | Logger::LogHandlerInterface* Logger::handler{ nullptr }; 11 | char Logger::buffer[Logger::bufferSize]; 12 | Logger::LogLevel Logger::logLevel = Logger::LogLevel::LOG_NONE; 13 | 14 | /* Class methods. */ 15 | 16 | void Logger::SetLogLevel(Logger::LogLevel level) 17 | { 18 | Logger::logLevel = level; 19 | } 20 | 21 | void Logger::SetHandler(LogHandlerInterface* handler) 22 | { 23 | Logger::handler = handler; 24 | } 25 | 26 | void Logger::SetDefaultHandler() 27 | { 28 | Logger::handler = new Logger::DefaultLogHandler(); 29 | } 30 | 31 | /* DefaultLogHandler */ 32 | 33 | void Logger::DefaultLogHandler::OnLog(LogLevel /*level*/, char* payload, size_t /*len*/) 34 | { 35 | std::cout << payload << std::endl; 36 | } 37 | } // namespace mediasoupclient 38 | -------------------------------------------------------------------------------- /src/scalabilityMode.cpp: -------------------------------------------------------------------------------- 1 | #define MSC_CLASS "scalabilityMode" 2 | 3 | #include "scalabilityMode.hpp" 4 | #include "Logger.hpp" 5 | #include 6 | 7 | using json = nlohmann::json; 8 | 9 | static const std::regex ScalabilityModeRegex( 10 | "^[LS]([1-9]\\d{0,1})T([1-9]\\d{0,1}).*", std::regex_constants::ECMAScript); 11 | 12 | namespace mediasoupclient 13 | { 14 | json parseScalabilityMode(const std::string& scalabilityMode) 15 | { 16 | /* clang-format off */ 17 | json jsonScalabilityMode 18 | { 19 | { "spatialLayers", 1 }, 20 | { "temporalLayers", 1 } 21 | }; 22 | /* clang-format on */ 23 | 24 | std::smatch match; 25 | 26 | std::regex_match(scalabilityMode, match, ScalabilityModeRegex); 27 | 28 | if (!match.empty()) 29 | { 30 | try 31 | { 32 | jsonScalabilityMode["spatialLayers"] = std::stoul(match[1].str()); 33 | jsonScalabilityMode["temporalLayers"] = std::stoul(match[2].str()); 34 | } 35 | catch (std::exception& e) 36 | { 37 | MSC_WARN("invalid scalabilityMode: %s", e.what()); 38 | } 39 | } 40 | else 41 | { 42 | MSC_WARN("invalid scalabilityMode: %s", scalabilityMode.c_str()); 43 | } 44 | 45 | return jsonScalabilityMode; 46 | } 47 | } // namespace mediasoupclient 48 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | PROJECT_PWD=${PWD} 6 | OS="$(uname -s)" 7 | 8 | current_dir_name=${PROJECT_PWD##*/} 9 | 10 | if [ "${current_dir_name}" != "libmediasoupclient" ] && [ "${current_dir_name}" != "v3-libmediasoupclient" ] ; then 11 | echo "[ERROR] $(basename $0) must be called from libmediasoupclient/ root directory" >&2 12 | 13 | exit 1 14 | fi 15 | 16 | # Rebuild everything. 17 | if [ "$1" == "rebuild" ]; then 18 | echo "[INFO] rebuilding CMake project: cmake . -Bbuild [...]" 19 | 20 | rm -rf build/ 21 | cmake . -Bbuild \ 22 | -DLIBWEBRTC_INCLUDE_PATH:PATH=${PATH_TO_LIBWEBRTC_SOURCES} \ 23 | -DLIBWEBRTC_BINARY_PATH:PATH=${PATH_TO_LIBWEBRTC_BINARY} \ 24 | -DMEDIASOUPCLIENT_BUILD_TESTS="true" \ 25 | -DCMAKE_CXX_FLAGS="-fvisibility=hidden" 26 | 27 | # Remove the 'rebuild' argument. 28 | shift 29 | fi 30 | 31 | # Compile. 32 | echo "[INFO] compiling mediasoupclient and test_mediasoupclient: cmake --build build" 33 | 34 | cmake --build build 35 | 36 | # Run test. 37 | if [ "${OS}" = "Darwin" ]; then 38 | TEST_BINARY=./build/test/test_mediasoupclient.app/Contents/MacOS/test_mediasoupclient 39 | else 40 | TEST_BINARY=./build/test/test_mediasoupclient 41 | fi 42 | 43 | echo "[INFO] running tests: ${TEST_BINARY} $@" 44 | 45 | ${TEST_BINARY} $@ 46 | -------------------------------------------------------------------------------- /test/src/scalabilityMode.test.cpp: -------------------------------------------------------------------------------- 1 | #include "scalabilityMode.hpp" 2 | #include 3 | 4 | TEST_CASE("scalabilityMode", "[scalabilityMode]") 5 | { 6 | SECTION("parses correctly") 7 | { 8 | nlohmann::json jsonScalabilityMode; 9 | 10 | REQUIRE_NOTHROW(jsonScalabilityMode = mediasoupclient::parseScalabilityMode("L1T3")); 11 | REQUIRE(jsonScalabilityMode["spatialLayers"].get() == 1); 12 | REQUIRE(jsonScalabilityMode["temporalLayers"].get() == 3); 13 | 14 | REQUIRE_NOTHROW(jsonScalabilityMode = mediasoupclient::parseScalabilityMode("L30T3")); 15 | REQUIRE(jsonScalabilityMode["spatialLayers"].get() == 30); 16 | REQUIRE(jsonScalabilityMode["temporalLayers"].get() == 3); 17 | 18 | REQUIRE_NOTHROW(jsonScalabilityMode = mediasoupclient::parseScalabilityMode("L1T6")); 19 | REQUIRE(jsonScalabilityMode["spatialLayers"].get() == 1); 20 | REQUIRE(jsonScalabilityMode["temporalLayers"].get() == 6); 21 | } 22 | 23 | SECTION("return deault layers if input is incorrect") 24 | { 25 | nlohmann::json jsonScalabilityMode; 26 | 27 | REQUIRE_NOTHROW(jsonScalabilityMode = mediasoupclient::parseScalabilityMode("1T3")); 28 | REQUIRE(jsonScalabilityMode["spatialLayers"].get() == 1); 29 | REQUIRE(jsonScalabilityMode["temporalLayers"].get() == 1); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/data/webrtc.sdp: -------------------------------------------------------------------------------- 1 | v=0 2 | o=- 1334496563563564720 2 IN IP4 127.0.0.1 3 | s=- 4 | t=0 0 5 | a=group:BUNDLE audio 6 | a=msid-semantic: WMS KOaPIn6F0Qm9PuOA6WHfjdfqWMt9sGl6uOqg 7 | m=audio 60017 RTP/SAVPF 111 103 104 0 8 106 105 13 126 8 | c=IN IP4 193.84.77.194 9 | a=rtcp:60017 IN IP4 193.84.77.194 10 | a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0 11 | a=candidate:1162875081 2 udp 2113937151 192.168.34.75 60017 typ host generation 0 12 | a=candidate:3289912957 1 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0 13 | a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0 14 | a=candidate:198437945 1 tcp 1509957375 192.168.34.75 0 typ host generation 0 15 | a=candidate:198437945 2 tcp 1509957375 192.168.34.75 0 typ host generation 0 16 | a=ice-ufrag:5I2uVefP13X1wzOY 17 | a=ice-pwd:e46UjXntt0K/xTncQcDBQePn 18 | a=ice-options:google-ice 19 | a=fingerprint:sha-256 79:14:AB:AB:93:7F:07:E8:91:1A:11:16:36:D0:11:66:C4:4F:31:A0:74:46:65:58:70:E5:09:95:48:F4:4B:D9 20 | a=setup:actpass 21 | a=mid:audio 22 | a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level 23 | a=sendrecv 24 | a=rtcp-mux 25 | a=rtpmap:111 opus/48000/2 26 | a=fmtp:111 minptime=10 27 | a=rtpmap:103 ISAC/16000 28 | a=rtpmap:104 ISAC/32000 29 | a=rtpmap:0 PCMU/8000 30 | a=rtpmap:8 PCMA/8000 31 | a=rtpmap:106 CN/32000 32 | a=rtpmap:105 CN/16000 33 | a=rtpmap:13 CN/8000 34 | a=rtpmap:126 telephone-event/8000 35 | a=maxptime:60 36 | a=ssrc:1399694169 cname:w7AkLB30C7pk/PFE 37 | a=ssrc:1399694169 msid:KOaPIn6F0Qm9PuOA6WHfjdfqWMt9sGl6uOqg 775dca64-4698-455b-8a02-89833bd24773 38 | a=ssrc:1399694169 mslabel:KOaPIn6F0Qm9PuOA6WHfjdfqWMt9sGl6uOqg 39 | a=ssrc:1399694169 label:775dca64-4698-455b-8a02-89833bd24773 40 | -------------------------------------------------------------------------------- /scripts/get-dep.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | PROJECT_PWD=${PWD} 6 | DEP=$1 7 | 8 | current_dir_name=${PROJECT_PWD##*/} 9 | if [ "${current_dir_name}" != "libmediasoupclient" ] && [ "${current_dir_name}" != "v3-libmediasoupclient" ] ; then 10 | echo ">>> [ERROR] $(basename $0) must be called from libmediasoupclient/ root directory" >&2 11 | exit 1 12 | fi 13 | 14 | function get_dep() 15 | { 16 | GIT_REPO="$1" 17 | GIT_TAG="$2" 18 | DEST="$3" 19 | 20 | echo ">>> [INFO] getting dep '${DEP}' ..." 21 | 22 | if [ -d "${DEST}" ] ; then 23 | echo ">>> [INFO] deleting ${DEST} ..." 24 | git rm -rf --ignore-unmatch ${DEST} > /dev/null 25 | rm -rf ${DEST} 26 | fi 27 | 28 | echo ">>> [INFO] cloning ${GIT_REPO} ..." 29 | git clone ${GIT_REPO} ${DEST} 30 | 31 | cd ${DEST} 32 | 33 | echo ">>> [INFO] setting '${GIT_TAG}' git tag ..." 34 | git checkout --quiet ${GIT_TAG} 35 | set -e 36 | 37 | echo ">>> [INFO] adding dep source code to the repository ..." 38 | rm -rf .git 39 | git add . 40 | 41 | echo ">>> [INFO] got dep '${DEP}'" 42 | 43 | cd ${PROJECT_PWD} 44 | } 45 | 46 | function get_libsdptransform() 47 | { 48 | GIT_REPO="https://github.com/ibc/libsdptransform.git" 49 | GIT_TAG="1.2.9" 50 | DEST="deps/libsdptransform" 51 | 52 | get_dep "${GIT_REPO}" "${GIT_TAG}" "${DEST}" 53 | } 54 | 55 | function get_catch() 56 | { 57 | GIT_REPO="https://github.com/philsquared/Catch.git" 58 | GIT_TAG="v2.11.1" 59 | DEST="deps/catch" 60 | 61 | get_dep "${GIT_REPO}" "${GIT_TAG}" "${DEST}" 62 | } 63 | 64 | case "${DEP}" in 65 | '-h') 66 | echo "Usage:" 67 | echo " ./scripts/$(basename $0) [libsdptransform|catch]" 68 | echo 69 | ;; 70 | libsdptransform) 71 | get_libsdptransform 72 | ;; 73 | catch) 74 | get_catch 75 | ;; 76 | *) 77 | echo ">>> [ERROR] unknown dep '${DEP}'" >&2 78 | exit 1 79 | esac 80 | 81 | if [ $? -eq 0 ] ; then 82 | echo ">>> [INFO] done" 83 | else 84 | echo ">>> [ERROR] failed" >&2 85 | exit 1 86 | fi 87 | -------------------------------------------------------------------------------- /test/data/jssip.sdp: -------------------------------------------------------------------------------- 1 | v=0 2 | o=- 1334496563563564720 2 IN IP4 127.0.0.1 3 | s=- 4 | t=0 0 5 | a=group:BUNDLE audio 6 | a=msid-semantic: WMS KOaPIn6F0Qm9PuOA6WHfjdfqWMt9sGl6uOqg 7 | m=audio 60017 RTP/SAVPF 111 103 104 0 8 106 105 13 126 8 | c=IN IP4 193.84.77.194 9 | a=rtcp:60017 IN IP4 193.84.77.194 10 | a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0 11 | a=candidate:1162875081 2 udp 2113937151 192.168.34.75 60017 typ host generation 0 12 | a=candidate:3289912957 1 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0 13 | a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0 14 | a=candidate:198437945 1 tcp 1509957375 192.168.34.75 0 typ host generation 0 15 | a=candidate:198437945 2 tcp 1509957375 192.168.34.75 0 typ host generation 0 16 | a=ice-ufrag:5I2uVefP13X1wzOY 17 | a=ice-pwd:e46UjXntt0K/xTncQcDBQePn 18 | a=ice-options:google-ice 19 | a=fingerprint:sha-256 79:14:AB:AB:93:7F:07:E8:91:1A:11:16:36:D0:11:66:C4:4F:31:A0:74:46:65:58:70:E5:09:95:48:F4:4B:D9 20 | a=setup:actpass 21 | a=mid:audio 22 | a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level 23 | a=sendrecv 24 | a=rtcp-mux 25 | a=crypto:0 AES_CM_128_HMAC_SHA1_32 inline:6JYKxLF+o2nhouDHr5J0oNb3CEGK3I/HHv9idGTY 26 | a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:ayId2M5kCitGTEEI9OjgEqatTA0IXGpQhFjmKOGk 27 | a=rtpmap:111 opus/48000/2 28 | a=fmtp:111 minptime=10 29 | a=rtpmap:103 ISAC/16000 30 | a=rtpmap:104 ISAC/32000 31 | a=rtpmap:0 PCMU/8000 32 | a=rtpmap:8 PCMA/8000 33 | a=rtpmap:106 CN/32000 34 | a=rtpmap:105 CN/16000 35 | a=rtpmap:13 CN/8000 36 | a=rtpmap:126 telephone-event/8000 37 | a=maxptime:60 38 | a=ssrc:1399694169 cname:w7AkLB30C7pk/PFE 39 | a=ssrc:1399694169 msid:KOaPIn6F0Qm9PuOA6WHfjdfqWMt9sGl6uOqg 775dca64-4698-455b-8a02-89833bd24773 40 | a=ssrc:1399694169 mslabel:KOaPIn6F0Qm9PuOA6WHfjdfqWMt9sGl6uOqg 41 | a=ssrc:1399694169 label:775dca64-4698-455b-8a02-89833bd24773 42 | -------------------------------------------------------------------------------- /test/include/MediaStreamTrackFactory.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MSC_TEST_MEDIA_STREAM_TRACK_FACTORY_HPP 2 | #define MSC_TEST_MEDIA_STREAM_TRACK_FACTORY_HPP 3 | 4 | #include "MediaSoupClientErrors.hpp" 5 | #include "MediaStreamTrackFactory.hpp" 6 | #include "api/audio_codecs/builtin_audio_decoder_factory.h" 7 | #include "api/audio_codecs/builtin_audio_encoder_factory.h" 8 | #include "api/create_peerconnection_factory.h" 9 | #include "api/media_stream_interface.h" 10 | #include "api/video_codecs/builtin_video_decoder_factory.h" 11 | #include "api/video_codecs/builtin_video_encoder_factory.h" 12 | #include "mediasoupclient.hpp" 13 | #include "pc/test/fake_audio_capture_module.h" 14 | #include "pc/test/fake_video_track_source.h" 15 | #include 16 | 17 | class MediaStreamTrackFactory 18 | { 19 | public: 20 | static MediaStreamTrackFactory& getInstance() 21 | { 22 | static MediaStreamTrackFactory instance; 23 | return instance; 24 | } 25 | 26 | MediaStreamTrackFactory(const MediaStreamTrackFactory&) = delete; 27 | MediaStreamTrackFactory& operator=(const MediaStreamTrackFactory&) = delete; 28 | 29 | void Create(); 30 | 31 | void ReleaseThreads(); 32 | 33 | webrtc::scoped_refptr Factory; 34 | mediasoupclient::PeerConnection::Options PeerConnectionOptions; 35 | 36 | private: 37 | MediaStreamTrackFactory() 38 | { 39 | Create(); 40 | } 41 | ~MediaStreamTrackFactory() 42 | { 43 | ReleaseThreads(); 44 | } 45 | 46 | /** 47 | * MediaStreamTrack holds reference to the threads of the PeerConnectionFactory. 48 | * Use plain pointers in order to avoid threads being destructed before tracks. 49 | */ 50 | std::unique_ptr NetworkThread; 51 | std::unique_ptr WorkerThread; 52 | std::unique_ptr SignalingThread; 53 | }; 54 | 55 | webrtc::scoped_refptr createAudioTrack(const std::string& label); 56 | 57 | webrtc::scoped_refptr createVideoTrack(const std::string& label); 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /include/sdp/MediaSection.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MSC_MEDIA_SECTION_HPP 2 | #define MSC_MEDIA_SECTION_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace mediasoupclient 8 | { 9 | namespace Sdp 10 | { 11 | class MediaSection 12 | { 13 | public: 14 | MediaSection() = default; 15 | virtual ~MediaSection() = default; 16 | MediaSection(const nlohmann::json& iceParameters, const nlohmann::json& iceCandidates); 17 | 18 | public: 19 | std::string GetMid() const; 20 | bool IsClosed() const; 21 | nlohmann::json GetObject() const; 22 | void SetIceParameters(const nlohmann::json& iceParameters); 23 | void Disable(); 24 | void Close(); 25 | 26 | public: 27 | virtual void SetDtlsRole(const std::string& role) = 0; 28 | 29 | protected: 30 | nlohmann::json mediaObject = nlohmann::json::object(); 31 | }; 32 | 33 | class AnswerMediaSection : public MediaSection 34 | { 35 | public: 36 | AnswerMediaSection( 37 | const nlohmann::json& iceParameters, 38 | const nlohmann::json& iceCandidates, 39 | const nlohmann::json& dtlsParameters, 40 | const nlohmann::json& sctpParameters, 41 | const nlohmann::json& offerMediaObject, 42 | nlohmann::json& offerRtpParameters, 43 | nlohmann::json& answerRtpParameters, 44 | const nlohmann::json* codecOptions); 45 | 46 | public: 47 | void SetDtlsRole(const std::string& role) override; 48 | }; 49 | 50 | class OfferMediaSection : public MediaSection 51 | { 52 | public: 53 | OfferMediaSection( 54 | const nlohmann::json& iceParameters, 55 | const nlohmann::json& iceCandidates, 56 | const nlohmann::json& dtlsParameters, 57 | const nlohmann::json& sctpParameters, 58 | const std::string& mid, 59 | const std::string& kind, 60 | const nlohmann::json& offerRtpParameters, 61 | const std::string& streamId, 62 | const std::string& trackId); 63 | 64 | public: 65 | void SetDtlsRole(const std::string& role) override; 66 | }; 67 | } // namespace Sdp 68 | } // namespace mediasoupclient 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /include/ortc.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MSC_ORTC_HPP 2 | #define MSC_ORTC_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace mediasoupclient 8 | { 9 | namespace ortc 10 | { 11 | void validateRtpCapabilities(nlohmann::json& caps); 12 | void validateRtpCodecCapability(nlohmann::json& codec); 13 | void validateRtcpFeedback(nlohmann::json& fb); 14 | void validateRtpHeaderExtension(nlohmann::json& ext); 15 | void validateRtpParameters(nlohmann::json& params); 16 | void validateRtpCodecParameters(nlohmann::json& codec); 17 | void validateRtpHeaderExtensionParameters(nlohmann::json& ext); 18 | void validateRtpEncodingParameters(nlohmann::json& encoding); 19 | void validateRtcpParameters(nlohmann::json& rtcp); 20 | void validateSctpCapabilities(nlohmann::json& caps); 21 | void validateNumSctpStreams(nlohmann::json& numStreams); 22 | void validateSctpParameters(nlohmann::json& params); 23 | void validateSctpStreamParameters(nlohmann::json& params); 24 | void validateIceParameters(nlohmann::json& params); 25 | void validateIceCandidates(nlohmann::json& params); 26 | void validateDtlsParameters(nlohmann::json& params); 27 | void validateProducerCodecOptions(nlohmann::json& params); 28 | nlohmann::json getExtendedRtpCapabilities(nlohmann::json& localCaps, nlohmann::json& remoteCaps); 29 | nlohmann::json getRecvRtpCapabilities(const nlohmann::json& extendedRtpCapabilities); 30 | nlohmann::json getSendingRtpParameters( 31 | const std::string& kind, const nlohmann::json& extendedRtpCapabilities); 32 | nlohmann::json getSendingRemoteRtpParameters( 33 | const std::string& kind, const nlohmann::json& extendedRtpCapabilities); 34 | const nlohmann::json generateProbatorRtpParameters(const nlohmann::json& videoRtpParameters); 35 | bool canSend(const std::string& kind, const nlohmann::json& extendedRtpCapabilities); 36 | bool canReceive(nlohmann::json& rtpParameters, const nlohmann::json& extendedRtpCapabilities); 37 | nlohmann::json reduceCodecs(nlohmann::json& codecs, const nlohmann::json* capCodec = nullptr); 38 | } // namespace ortc 39 | } // namespace mediasoupclient 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /test/src/tests.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_RUNNER 2 | 3 | #include "Logger.hpp" 4 | #include "PeerConnection.hpp" 5 | #include "mediasoupclient.hpp" 6 | #include 7 | #include 8 | 9 | int main(int argc, char* argv[]) 10 | { 11 | mediasoupclient::Logger::LogLevel logLevel{ mediasoupclient::Logger::LogLevel::LOG_NONE }; 12 | webrtc::LoggingSeverity webrtcLogLevel{ webrtc::LoggingSeverity::LS_NONE }; 13 | Catch::Session session; 14 | std::string logLevelStr; 15 | std::string webrtcLogLevelStr; 16 | int ret; 17 | 18 | // Build a new parser on top of Catch. 19 | // clang-format off 20 | auto cli = session.cli() 21 | | Catch::clara::Opt(logLevelStr, "debug|warn|error|none")["-L"]["--log-level"]("libmediasoupclient log level (default: none)") 22 | | Catch::clara::Opt(webrtcLogLevelStr, "verbose|info|warn|error|none")["-W"]["--webrtc-log-level"]("libwebrtc log level (default: none)"); 23 | // clang-format on 24 | 25 | // Now pass the new composite back to Catch so it uses that. 26 | session.cli(cli); 27 | 28 | // Let Catch (using Clara) parse the command line. 29 | ret = session.applyCommandLine(argc, argv); 30 | 31 | if (ret != 0) // Indicates a command line error. 32 | return ret; 33 | 34 | // Apply log levels. 35 | if (logLevelStr == "debug") 36 | logLevel = mediasoupclient::Logger::LogLevel::LOG_DEBUG; 37 | else if (logLevelStr == "warn") 38 | logLevel = mediasoupclient::Logger::LogLevel::LOG_WARN; 39 | else if (logLevelStr == "error") 40 | logLevel = mediasoupclient::Logger::LogLevel::LOG_ERROR; 41 | 42 | if (webrtcLogLevelStr == "verbose") 43 | webrtcLogLevel = webrtc::LoggingSeverity::LS_VERBOSE; 44 | else if (webrtcLogLevelStr == "info") 45 | webrtcLogLevel = webrtc::LoggingSeverity::LS_INFO; 46 | else if (webrtcLogLevelStr == "warn") 47 | webrtcLogLevel = webrtc::LoggingSeverity::LS_WARNING; 48 | else if (webrtcLogLevelStr == "error") 49 | webrtcLogLevel = webrtc::LoggingSeverity::LS_ERROR; 50 | 51 | mediasoupclient::Logger::SetLogLevel(logLevel); 52 | mediasoupclient::Logger::SetHandler(new mediasoupclient::Logger::DefaultLogHandler()); 53 | 54 | webrtc::LogMessage::LogToDebug(webrtcLogLevel); 55 | 56 | // Initialization. 57 | mediasoupclient::Initialize(); 58 | 59 | ret = session.run(argc, argv); 60 | 61 | // Cleanup. 62 | mediasoupclient::Cleanup(); 63 | 64 | return ret; 65 | } 66 | -------------------------------------------------------------------------------- /scripts/tidy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | PROJECT_PWD=${PWD} 6 | OS="$(uname -s)" 7 | NUM_CORES=1 8 | 9 | current_dir_name=${PROJECT_PWD##*/} 10 | 11 | if [ "${current_dir_name}" != "libmediasoupclient" ] && [ "${current_dir_name}" != "v3-libmediasoupclient" ] ; then 12 | echo "[ERROR] $(basename $0) must be called from libmediasoupclient/ root directory" >&2 13 | 14 | exit 1 15 | fi 16 | 17 | case "${OS}" in 18 | Linux*) NUM_CORES=$(nproc);; 19 | Darwin*) NUM_CORES=$(sysctl -n hw.ncpu);; 20 | *) NUM_CORES=1;; 21 | esac 22 | 23 | if [ "${OS}" != "Darwin" ] && [ "${OS}" != "Linux" ] ; then 24 | echo "[ERROR] only available for MacOS and Linux" >&2 25 | 26 | exit 1 27 | fi 28 | 29 | # Excluded checks. 30 | EXCLUDED_CHECKS="\ 31 | -google-runtime-references,\ 32 | -llvm-header-guard,\ 33 | -misc-throw-by-value-catch-by-reference,\ 34 | -readability-function-size 35 | " 36 | 37 | # Filterer files/lineeing checked. 38 | # LINE_FILTER="[\ 39 | # {\"name\":\"file.cpp, \"lines\":[[1, 1000]]}\ 40 | # ]" 41 | LINE_FILTER="" 42 | 43 | # Checks to be performed. 44 | CHECKS="" 45 | 46 | # If certains checks are defined, just run those. 47 | if [ ! -z ${MSC_TIDY_CHECKS} ] ; then 48 | CHECKS="-*,${MSC_TIDY_CHECKS}" 49 | # Otherwise run all the checks except the excluded ones. 50 | else 51 | CHECKS="${EXCLUDED_CHECKS}" 52 | fi 53 | 54 | # Whether replacements should be done. 55 | FIX=${MSC_TIDY_FIX:=} 56 | 57 | if [ ! -z ${MSC_TIDY_FIX} ] ; then 58 | FIX="-fix" 59 | fi 60 | 61 | # Exclude dependencies, files which absolute path containins /deps/. 62 | FILES="^((?!\/deps\/).)*$" 63 | 64 | HEADER_FILTER_REGEX="(Consumer.hpp|Device.hpp|Handler.hpp|Logger.hpp|MediaSoupClientErrors.hpp|PeerConnection.hpp|Producer.hpp|Transport.hpp|mediasoupclient.hpp|ortc.hpp|scalabilityMode.hpp|sdp/RemoteSdp.hpp|sdp/Utils.hpp)" 65 | 66 | BIN_PATH="node_modules/.bin" 67 | 68 | # Generate compile_commands.json. 69 | pushd build 70 | cmake -DMSC_LOG_DEV=1 -DCMAKE_EXPORT_COMPILE_COMMANDS=ON . 71 | mv compile_commands.json ../ 72 | popd 73 | 74 | # Run clang-tidy.py. 75 | echo "[INFO] running scripts/clang-tidy.py" 76 | 77 | scripts/clang-tidy.py \ 78 | -clang-tidy-binary=${BIN_PATH}/clang-tidy \ 79 | -clang-apply-replacements-binary=${BIN_PATH}/clang-apply-replacements \ 80 | -header-filter=${HEADER_FILTER_REGEX} \ 81 | -line-filter=${LINE_FILTER} \ 82 | -p=. \ 83 | -j=${NUM_CORES} \ 84 | -checks=${CHECKS} \ 85 | ${FIX} \ 86 | files $FILES 87 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | include(FetchContent) 4 | 5 | set( 6 | SOURCE_FILES 7 | src/Device.test.cpp 8 | src/Handler.test.cpp 9 | src/PeerConnection.test.cpp 10 | src/RemoteSdp.test.cpp 11 | src/SdpUtils.test.cpp 12 | src/mediasoupclient.test.cpp 13 | src/MediaStreamTrackFactory.cpp 14 | src/ortc.test.cpp 15 | src/fakeParameters.cpp 16 | src/scalabilityMode.test.cpp 17 | src/tests.cpp 18 | include/FakeTransportListener.hpp 19 | include/MediaStreamTrackFactory.hpp 20 | include/helpers.hpp 21 | include/fakeParameters.hpp 22 | ) 23 | 24 | # Create target. 25 | add_executable(test_mediasoupclient ${SOURCE_FILES}) 26 | 27 | # Source deps 28 | message(STATUS "\nFetching catch2...\n") 29 | FetchContent_Declare( 30 | catch2 31 | GIT_REPOSITORY https://github.com/catchorg/Catch2.git 32 | GIT_TAG v2.13.10 33 | ) 34 | FetchContent_MakeAvailable(catch2) 35 | 36 | # Private (implementation) header files. 37 | target_include_directories(test_mediasoupclient PRIVATE 38 | include 39 | ${mediasoupclient_SOURCE_DIR}/include 40 | ${catch2_SOURCE_DIR}/single_include/catch2 41 | ) 42 | 43 | if(APPLE) 44 | find_library(APPLICATION_SERVICES ApplicationServices) 45 | find_library(AUDIO_TOOLBOX AudioToolbox) 46 | find_library(CORE_AUDIO CoreAudio) 47 | find_library(CORE_FOUNDATION Foundation) 48 | 49 | target_link_libraries(test_mediasoupclient PRIVATE 50 | ${APPLICATION_SERVICES} 51 | ${AUDIO_TOOLBOX} 52 | ${CORE_AUDIO} 53 | ${CORE_FOUNDATION} 54 | ) 55 | 56 | # Bundle it. 57 | set_target_properties(test_mediasoupclient PROPERTIES 58 | MACOSX_BUNDLE TRUE 59 | MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist 60 | ) 61 | endif(APPLE) 62 | 63 | if(UNIX) 64 | find_package(Threads REQUIRED) 65 | target_link_libraries(test_mediasoupclient PRIVATE Threads::Threads) 66 | endif(UNIX) 67 | 68 | target_compile_definitions(test_mediasoupclient PUBLIC 69 | $<$:NOMINMAX> 70 | $<$:WIN32_LEAN_AND_MEAN> 71 | ) 72 | 73 | # Private dependencies. 74 | target_link_libraries(test_mediasoupclient PRIVATE mediasoupclient) 75 | target_link_libraries(test_mediasoupclient PRIVATE ${CMAKE_DL_LIBS}) 76 | 77 | # Source Dependencies. 78 | add_subdirectory(deps/libwebrtc "${CMAKE_CURRENT_BINARY_DIR}/libwebrtc") 79 | 80 | # Public (interface) dependencies. 81 | target_link_libraries(test_mediasoupclient PUBLIC 82 | webrtc 83 | ${LIBWEBRTC_BINARY_PATH}/libwebrtc${CMAKE_STATIC_LIBRARY_SUFFIX} 84 | ) 85 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | ### 3.5.0 5 | 6 | * Update to libwebrtc M140/7339 ([#173](https://github.com/versatica/libmediasoupclient/pull/188)). Credits to @revidee. 7 | 8 | ### 3.4.3 9 | 10 | * Update to libwebrtc M120/6099 ([#173](https://github.com/versatica/libmediasoupclient/pull/173)). Thanks @Poldraunic, @janreyho, @copiltembel. 11 | 12 | ### 3.4.2 13 | 14 | * Fix explicit codec selection ([#164](https://github.com/versatica/libmediasoupclient/pull/164)). Thanks @fedulvtubudul. 15 | 16 | 17 | ### 3.4.1 18 | 19 | * Clear the stored transceivers before closing the PeerConnection ([#156](https://github.com/versatica/libmediasoupclient/pull/156)). Thanks @adriancretu. 20 | * Fix non-virtual destructors ([PR #157](https://github.com/versatica/libmediasoupclient/pull/157)). Thanks @adriancretu. 21 | * Fix min max range inclusion. ([PR #158](https://github.com/versatica/libmediasoupclient/pull/158)). Thanks @adriancretu. 22 | * Update libsdptransform to 1.2.10 ([PR #171](https://github.com/versatica/libmediasoupclient/pull/171)). Thanks@copiltembel. 23 | 24 | 25 | ### 3.4.0 26 | 27 | * 'maxaveragebitrate' support for Opus. Thanks @PeterCang. 28 | * Enable VP9 SVC ([#131](https://github.com/versatica/libmediasoupclient/pull/131)). Thanks @harvestsure. 29 | * Reuse closed m= sections in remote SDP offer ([#99](https://github.com/versatica/libmediasoupclient/pull/99)). 30 | * Allow forcing local DTLS role ([#133](https://github.com/versatica/libmediasoupclient/pull/133)). 31 | * Add cbr config option for opus ([#138](https://github.com/versatica/libmediasoupclient/pull/138)). Thanks @GEverding. 32 | 33 | ### 3.3.0 34 | 35 | * Update to libwebrtc M94/4606. 36 | 37 | ### 3.2.0 38 | 39 | * Do not auto generate the stream ID for the receiving dataChannel, 40 | but provide it via API. Fixes ([#126](https://github.com/versatica/libmediasoupclient/pull/126)). 41 | 42 | 43 | ### 3.1.5 44 | 45 | * Fix profile-id codec parameter by converting parsed value into integer. Fixes ([#115](https://github.com/versatica/libmediasoupclient/pull/115)) 46 | 47 | 48 | ### 3.1.4 49 | 50 | * Convert `RecvTransport::Listener` to a subclass. Thanks @maxweisel. 51 | * Fix ambiguous cast error when compiling with MSVC. Thanks @maxweisel. 52 | 53 | 54 | ### 3.1.3 55 | 56 | * Fix H264 `profile-level-id` value in SDP answer. 57 | - Same as in https://github.com/versatica/mediasoup-client/issues/148 58 | 59 | 60 | ### 3.1.2 61 | 62 | * Fix memory leak ([#105](https://github.com/versatica/libmediasoupclient/pull/105)). Thanks @ploverlake. 63 | 64 | 65 | ### 3.1.1 66 | 67 | * Update `libsdptransform` dep to 1.2.8. 68 | 69 | 70 | ### 3.1.0 71 | 72 | * DataChannel support. 73 | -------------------------------------------------------------------------------- /include/DataProducer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MSC_DATAPRODUCER_HPP 2 | #define MSC_DATAPRODUCER_HPP 3 | 4 | #include "Handler.hpp" 5 | #include 6 | #include 7 | #include 8 | 9 | namespace mediasoupclient 10 | { 11 | class SendTransport; 12 | 13 | class DataProducer : public webrtc::DataChannelObserver 14 | { 15 | public: 16 | class PrivateListener 17 | { 18 | public: 19 | virtual ~PrivateListener() = default; 20 | virtual void OnClose(DataProducer* dataProducer) = 0; 21 | }; 22 | 23 | /* Public Listener API */ 24 | class Listener 25 | { 26 | public: 27 | virtual ~Listener() = default; 28 | // DataChannel state changes. 29 | virtual void OnOpen(DataProducer* dataProducer) = 0; 30 | virtual void OnClose(DataProducer* dataProducer) = 0; 31 | virtual void OnBufferedAmountChange(DataProducer* dataProducer, uint64_t sentDataSize) = 0; 32 | 33 | virtual void OnTransportClose(DataProducer* dataProducer) = 0; 34 | }; 35 | 36 | private: 37 | PrivateListener* privateListener; 38 | Listener* listener; 39 | std::string id; 40 | webrtc::scoped_refptr dataChannel; 41 | bool closed{ false }; 42 | nlohmann::json sctpStreamParameters; 43 | nlohmann::json appData; 44 | void TransportClosed(); 45 | 46 | friend SendTransport; 47 | 48 | private: 49 | DataProducer( 50 | DataProducer::PrivateListener* privateListener, 51 | DataProducer::Listener* listener, 52 | const std::string& id, 53 | webrtc::scoped_refptr dataChannel, 54 | const nlohmann::json& sctpStreamParameters, 55 | const nlohmann::json& appData); 56 | 57 | public: 58 | const std::string& GetId() const; 59 | std::string GetLocalId() const; 60 | const nlohmann::json& GetSctpStreamParameters() const; 61 | webrtc::DataChannelInterface::DataState GetReadyState() const; 62 | std::string GetLabel(); 63 | std::string GetProtocol(); 64 | uint64_t GetBufferedAmount() const; 65 | const nlohmann::json& GetAppData() const; 66 | bool IsClosed() const; 67 | void Close(); 68 | void Send(const webrtc::DataBuffer& buffer); 69 | 70 | /* Virtual methods inherited from webrtc::DataChannelObserver. */ 71 | public: 72 | void OnStateChange() override; 73 | // A data buffer was successfully received. 74 | void OnMessage(const webrtc::DataBuffer& buffer) override; 75 | // The data channel's buffered_amount has changed. 76 | void OnBufferedAmountChange(uint64_t sentDataSize) override; 77 | }; 78 | } // namespace mediasoupclient 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /test/src/SdpUtils.test.cpp: -------------------------------------------------------------------------------- 1 | #include "helpers.hpp" 2 | #include "sdp/Utils.hpp" 3 | #include 4 | 5 | TEST_CASE("Sdp::Utils", "[Sdp][Utils]") 6 | { 7 | SECTION("extractRtpCapabilities") 8 | { 9 | auto sdp = helpers::readFile("test/data/audio_video.sdp"); 10 | auto session = sdptransform::parse(sdp); 11 | auto rtpCapabilities = mediasoupclient::Sdp::Utils::extractRtpCapabilities(session); 12 | 13 | auto codecs = rtpCapabilities.at("codecs"); 14 | 15 | for (const auto& codec : rtpCapabilities["codecs"]) 16 | { 17 | // Verify that 'profile-id' is a number. 18 | if (codec["parameters"].contains("profile-id")) 19 | { 20 | REQUIRE_NOTHROW(codec["parameters"]["profile-id"].get()); 21 | } 22 | } 23 | } 24 | 25 | SECTION("extractDtlsParameters") 26 | { 27 | auto sdp = helpers::readFile("test/data/jssip.sdp"); 28 | auto session = sdptransform::parse(sdp); 29 | auto dtlsParameters = mediasoupclient::Sdp::Utils::extractDtlsParameters(session); 30 | 31 | REQUIRE(dtlsParameters.at("role") == "auto"); 32 | 33 | auto& fingerprints = dtlsParameters["fingerprints"]; 34 | 35 | REQUIRE(fingerprints.size() == 1); 36 | REQUIRE( 37 | fingerprints[0] == 38 | R"({ 39 | "algorithm" : "sha-256", 40 | "value" : "79:14:AB:AB:93:7F:07:E8:91:1A:11:16:36:D0:11:66:C4:4F:31:A0:74:46:65:58:70:E5:09:95:48:F4:4B:D9" 41 | })"_json); 42 | } 43 | 44 | SECTION("getRtpEncodings respect the given SSRC order") 45 | { 46 | auto offerMediaObject = R"({ 47 | "ssrcs": 48 | [ 49 | { 50 | "attribute": "cname", 51 | "id": 3142507807, 52 | "value": "xP/I5Utgvn9wJsho" 53 | }, 54 | { 55 | "attribute": "msid", 56 | "id": 3142507807, 57 | "value": "0 audio-track-id" 58 | }, 59 | { 60 | "attribute": "mslabel", 61 | "id": 3142507807, 62 | "value": "0" 63 | }, 64 | { 65 | "attribute": "label", 66 | "id": 3142507807, 67 | "value": "audio-track-id" 68 | }, 69 | { 70 | "attribute": "cname", 71 | "id": 3142507806, 72 | "value": "xP/I5Utgvn9wJsho" 73 | }, 74 | { 75 | "attribute": "msid", 76 | "id": 3142507806, 77 | "value": "0 audio-track-id" 78 | }, 79 | { 80 | "attribute": "mslabel", 81 | "id": 3142507806, 82 | "value": "0" 83 | }, 84 | { 85 | "attribute": "label", 86 | "id": 3142507806, 87 | "value": "audio-track-id" 88 | } 89 | ], 90 | "type": "audio" 91 | })"_json; 92 | 93 | auto rtpEncodings = mediasoupclient::Sdp::Utils::getRtpEncodings(offerMediaObject); 94 | 95 | REQUIRE(rtpEncodings[0]["ssrc"] == 3142507807); 96 | REQUIRE(rtpEncodings[1]["ssrc"] == 3142507806); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /include/Consumer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MSC_CONSUMER_HPP 2 | #define MSC_CONSUMER_HPP 3 | 4 | #include 5 | #include // webrtc::MediaStreamTrackInterface 6 | #include // webrtc::RtpReceiverInterface 7 | #include 8 | 9 | namespace mediasoupclient 10 | { 11 | // Fast forward declarations. 12 | class Transport; 13 | class RecvTransport; 14 | 15 | class Consumer 16 | { 17 | public: 18 | class PrivateListener 19 | { 20 | public: 21 | virtual ~PrivateListener() = default; 22 | virtual void OnClose(Consumer* consumer) = 0; 23 | virtual nlohmann::json OnGetStats(const Consumer* consumer) = 0; 24 | }; 25 | 26 | /* Public Listener API */ 27 | class Listener 28 | { 29 | public: 30 | virtual ~Listener() = default; 31 | virtual void OnTransportClose(Consumer* consumer) = 0; 32 | }; 33 | 34 | private: 35 | Consumer( 36 | PrivateListener* privateListener, 37 | Listener* listener, 38 | const std::string& id, 39 | const std::string& localId, 40 | const std::string& producerId, 41 | webrtc::RtpReceiverInterface* rtpReceiver, 42 | webrtc::MediaStreamTrackInterface* track, 43 | const nlohmann::json& rtpParameters, 44 | const nlohmann::json& appData); 45 | 46 | public: 47 | const std::string& GetId() const; 48 | const std::string& GetLocalId() const; 49 | const std::string& GetProducerId() const; 50 | bool IsClosed() const; 51 | const std::string GetKind() const; 52 | webrtc::RtpReceiverInterface* GetRtpReceiver() const; 53 | webrtc::MediaStreamTrackInterface* GetTrack() const; 54 | const nlohmann::json& GetRtpParameters() const; 55 | bool IsPaused() const; 56 | nlohmann::json& GetAppData(); 57 | void Close(); 58 | nlohmann::json GetStats() const; 59 | void Pause(); 60 | void Resume(); 61 | 62 | private: 63 | void TransportClosed(); 64 | 65 | // RecvTransport will create instances and call private member TransporClosed. 66 | friend RecvTransport; 67 | 68 | private: 69 | // PrivateListener instance. 70 | PrivateListener* privateListener; 71 | // Public Listener instance. 72 | Listener* listener; 73 | // Id. 74 | std::string id; 75 | // localId. 76 | std::string localId; 77 | // Producer Id. 78 | std::string producerId; 79 | // Closed flag. 80 | bool closed{ false }; 81 | // Associated RTCRtpReceiver. 82 | webrtc::RtpReceiverInterface* rtpReceiver{ nullptr }; 83 | // Local track. 84 | webrtc::MediaStreamTrackInterface* track{ nullptr }; 85 | // RTP parameters. 86 | nlohmann::json rtpParameters; 87 | // Paused flag. 88 | bool paused{ false }; 89 | // App custom data. 90 | nlohmann::json appData{}; 91 | }; 92 | } // namespace mediasoupclient 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /include/sdp/RemoteSdp.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MSC_REMOTESDP_HPP 2 | #define MSC_REMOTESDP_HPP 3 | 4 | #include "sdp/MediaSection.hpp" 5 | #include 6 | #include 7 | #include 8 | 9 | namespace mediasoupclient 10 | { 11 | namespace Sdp 12 | { 13 | class RemoteSdp 14 | { 15 | public: 16 | struct MediaSectionIdx 17 | { 18 | size_t idx; 19 | std::string reuseMid; 20 | }; 21 | 22 | public: 23 | RemoteSdp( 24 | const nlohmann::json& iceParameters, 25 | const nlohmann::json& iceCandidates, 26 | const nlohmann::json& dtlsParameters, 27 | const nlohmann::json& sctpParameters); 28 | ~RemoteSdp(); 29 | 30 | public: 31 | Sdp::RemoteSdp::MediaSectionIdx GetNextMediaSectionIdx(); 32 | void Send( 33 | nlohmann::json& offerMediaObject, 34 | const std::string& reuseMid, 35 | nlohmann::json& offerRtpParameters, 36 | nlohmann::json& answerRtpParameters, 37 | const nlohmann::json* codecOptions); 38 | 39 | void SendSctpAssociation(nlohmann::json& offerMediaObject); 40 | void RecvSctpAssociation(); 41 | 42 | void Receive( 43 | const std::string& mid, 44 | const std::string& kind, 45 | const nlohmann::json& offerRtpParameters, 46 | const std::string& streamId, 47 | const std::string& trackId); 48 | void UpdateIceParameters(const nlohmann::json& iceParameters); 49 | void UpdateDtlsRole(const std::string& role); 50 | void DisableMediaSection(const std::string& mid); 51 | void CloseMediaSection(const std::string& mid); 52 | std::string GetSdp(); 53 | 54 | private: 55 | void AddMediaSection(MediaSection* newMediaSection); 56 | void ReplaceMediaSection(MediaSection* newMediaSection, const std::string& reuseMid); 57 | void RegenerateBundleMids(); 58 | 59 | protected: 60 | // Generic sending RTP parameters for audio and video. 61 | nlohmann::json rtpParametersByKind = nlohmann::json::object(); 62 | // Transport remote parameters, including ICE parameters, ICE candidates, 63 | // DTLS parameteres and SCTP parameters. 64 | nlohmann::json iceParameters = nlohmann::json::object(); 65 | nlohmann::json iceCandidates = nlohmann::json::object(); 66 | nlohmann::json dtlsParameters = nlohmann::json::object(); 67 | nlohmann::json sctpParameters = nlohmann::json::object(); 68 | // MediaSection instances. 69 | std::vector mediaSections; 70 | // MediaSection indices indexed by MID. 71 | std::map midToIndex; 72 | // First MID. 73 | std::string firstMid; 74 | // Generic sending RTP parameters for audio and video. 75 | nlohmann::json sendingRtpParametersByKind = nlohmann::json::object(); 76 | // SDP global fields. 77 | nlohmann::json sdpObject = nlohmann::json::object(); 78 | }; 79 | } // namespace Sdp 80 | } // namespace mediasoupclient 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | AccessModifierOffset: -2 3 | AlignAfterOpenBracket: AlwaysBreak 4 | AlignConsecutiveAssignments: true 5 | AlignConsecutiveDeclarations: false 6 | AlignEscapedNewlinesLeft: false 7 | AlignOperands: true 8 | AlignTrailingComments: true 9 | AllowAllParametersOfDeclarationOnNextLine: true 10 | AllowShortBlocksOnASingleLine: false 11 | AllowShortCaseLabelsOnASingleLine: false 12 | AllowShortFunctionsOnASingleLine: false 13 | AllowShortIfStatementsOnASingleLine: false 14 | AllowShortLoopsOnASingleLine: false 15 | AlwaysBreakAfterReturnType: None 16 | AlwaysBreakBeforeMultilineStrings: true 17 | AlwaysBreakTemplateDeclarations: true 18 | BinPackArguments: false 19 | BinPackParameters: false 20 | BreakBeforeBraces: Custom 21 | BraceWrapping: 22 | AfterClass: true 23 | AfterControlStatement: true 24 | AfterEnum: true 25 | AfterFunction: true 26 | AfterNamespace: true 27 | AfterStruct: true 28 | AfterUnion: true 29 | AfterCaseLabel: true 30 | AfterExternBlock: true 31 | BeforeCatch: true 32 | BeforeElse: true 33 | IndentBraces: false 34 | BreakBeforeBraces: Allman 35 | BreakBeforeBinaryOperators: None 36 | BreakBeforeInheritanceComma: false 37 | BreakBeforeTernaryOperators: true 38 | BreakConstructorInitializersBeforeComma: false 39 | BreakStringLiterals: false 40 | ColumnLimit: 100 41 | CommentPragmas: 'NOLINT' 42 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 43 | ConstructorInitializerIndentWidth: 2 44 | ContinuationIndentWidth: 2 45 | Cpp11BracedListStyle: false 46 | DerivePointerAlignment: false 47 | DisableFormat: false 48 | ExperimentalAutoDetectBinPacking: true 49 | FixNamespaceComments: true 50 | IncludeCategories: 51 | - Regex: '^"(sdp)/' 52 | Priority: 2 53 | - Regex: '"*"' 54 | Priority: 1 55 | - Regex: '^<(json|sdptransform)(.|/)' 56 | Priority: 3 57 | - Regex: '^<(api|media|rtc_base|pc)(.|/)' 58 | Priority: 4 59 | - Regex: '<*>' 60 | Priority: 5 61 | IncludeIsMainRegex: '$' 62 | IndentCaseLabels: true 63 | IndentWidth: 2 64 | IndentWrappedFunctionNames: false 65 | KeepEmptyLinesAtTheStartOfBlocks: false 66 | MaxEmptyLinesToKeep: 1 67 | NamespaceIndentation: All 68 | PenaltyBreakBeforeFirstCallParameter: 5 69 | PenaltyBreakComment: 100 70 | PenaltyBreakFirstLessLess: 200 71 | PenaltyBreakString: 20 72 | PenaltyExcessCharacter: 10 73 | PenaltyReturnTypeOnItsOwnLine: 1000 74 | PointerAlignment: Left 75 | ReflowComments: true 76 | SortIncludes: true 77 | SpaceAfterCStyleCast: false 78 | SpaceAfterTemplateKeyword: false 79 | SpaceBeforeAssignmentOperators: true 80 | SpaceBeforeParens: ControlStatements 81 | SpaceInEmptyParentheses: false 82 | SpacesBeforeTrailingComments: 1 83 | SpacesInAngles: false 84 | SpacesInCStyleCastParentheses: false 85 | SpacesInContainerLiterals: true 86 | SpacesInParentheses: false 87 | SpacesInSquareBrackets: false 88 | Standard: Cpp11 89 | TabWidth: 2 90 | UseTab: ForIndentation 91 | -------------------------------------------------------------------------------- /include/DataConsumer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MSC_DATACONSUMER_HPP 2 | #define MSC_DATACONSUMER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace mediasoupclient 9 | { 10 | class RecvTransport; 11 | 12 | class DataConsumer : public webrtc::DataChannelObserver 13 | { 14 | public: 15 | class PrivateListener 16 | { 17 | public: 18 | virtual ~PrivateListener() = default; 19 | virtual void OnClose(DataConsumer* dataConsumer) = 0; 20 | }; 21 | 22 | class Listener 23 | { 24 | public: 25 | virtual ~Listener() = default; 26 | // DataChannel state changes. 27 | virtual void OnConnecting(DataConsumer* dataConsumer) = 0; 28 | virtual void OnOpen(DataConsumer* dataConsumer) = 0; 29 | virtual void OnClosing(DataConsumer* dataConsumer) = 0; 30 | virtual void OnClose(DataConsumer* dataConsumer) = 0; 31 | 32 | // A data buffer was successfully received. 33 | virtual void OnMessage(DataConsumer* dataConsumer, const webrtc::DataBuffer& buffer) = 0; 34 | 35 | virtual void OnTransportClose(DataConsumer* dataConsumer) = 0; 36 | }; 37 | 38 | private: 39 | DataConsumer( 40 | Listener* listener, 41 | PrivateListener* privateListener, 42 | const std::string& id, 43 | const std::string& dataProducerId, 44 | webrtc::scoped_refptr dataChannel, 45 | const nlohmann::json& sctpStreamParameters, 46 | const nlohmann::json& appData); 47 | 48 | public: 49 | const std::string& GetId() const; 50 | std::string GetLocalId() const; 51 | const std::string& GetDataProducerId() const; 52 | const nlohmann::json& GetSctpStreamParameters() const; 53 | webrtc::DataChannelInterface::DataState GetReadyState() const; 54 | std::string GetLabel() const; 55 | std::string GetProtocol() const; 56 | const nlohmann::json& GetAppData() const; 57 | bool IsClosed() const; 58 | void Close(); 59 | 60 | private: 61 | void TransportClosed(); 62 | 63 | // RecvTransport will create instances and call private member TransporClosed. 64 | friend RecvTransport; 65 | 66 | private: 67 | Listener* listener; 68 | PrivateListener* privateListener; 69 | std::string id; 70 | std::string dataProducerId; 71 | webrtc::scoped_refptr dataChannel; 72 | bool closed{ false }; 73 | nlohmann::json sctpParameters; 74 | nlohmann::json appData; 75 | 76 | /* Virtual methods inherited from webrtc::DataChannelObserver. */ 77 | public: 78 | // The data channel state has changed. 79 | void OnStateChange() override; 80 | // A data buffer was successfully received. 81 | void OnMessage(const webrtc::DataBuffer& buffer) override; 82 | // The data channel's buffered_amount has changed. 83 | void OnBufferedAmountChange(uint64_t sentDataSize) override; 84 | }; 85 | } // namespace mediasoupclient 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /test/src/PeerConnection.test.cpp: -------------------------------------------------------------------------------- 1 | #include "PeerConnection.hpp" 2 | #include "MediaSoupClientErrors.hpp" 3 | #include "helpers.hpp" 4 | #include "sdp/Utils.hpp" 5 | #include 6 | 7 | TEST_CASE("PeerConnection", "[PeerConnection]") 8 | { 9 | static std::list iceServerUris; 10 | static mediasoupclient::PeerConnection::PrivateListener listener; 11 | static mediasoupclient::PeerConnection::Options peerConnectionOptions; 12 | static mediasoupclient::PeerConnection pc(&listener, &peerConnectionOptions); 13 | 14 | static std::string offer; 15 | 16 | SECTION("'pc.GetConfiguration()' succeeds") 17 | { 18 | auto configuration = pc.GetConfiguration(); 19 | } 20 | 21 | /* 22 | * NOTE: Fails if peerconnection is created with Unified Plan SDP semantics. 23 | * See: src/PeerConnection.cpp (constructor). 24 | SECTION("SetConfiguration (right options)") 25 | { 26 | webrtc::PeerConnectionInterface::RTCConfiguration configuration; 27 | 28 | REQUIRE(pc.SetConfiguration(configuration) == true); 29 | } 30 | */ 31 | 32 | SECTION("'pc.SetConfiguration()' fails if wrong options are provided") 33 | { 34 | webrtc::PeerConnectionInterface::RTCConfiguration configuration; 35 | webrtc::PeerConnectionInterface::IceServer iceServer; 36 | 37 | iceServer.uri = "Wrong URI"; 38 | configuration.servers.push_back(iceServer); 39 | 40 | mediasoupclient::PeerConnection::PrivateListener listener; 41 | mediasoupclient::PeerConnection pc(&listener, &peerConnectionOptions); 42 | 43 | REQUIRE(!pc.SetConfiguration(configuration)); 44 | } 45 | 46 | SECTION("'pc.GetStats()' succeeds") 47 | { 48 | REQUIRE_NOTHROW(pc.GetStats()); 49 | } 50 | 51 | SECTION("'pc.CreateAnswer()' fails if no remote offer has been provided") 52 | { 53 | webrtc::PeerConnectionInterface::RTCOfferAnswerOptions options; 54 | 55 | REQUIRE_THROWS_AS(pc.CreateAnswer(options), MediaSoupClientError); 56 | } 57 | 58 | SECTION("'pc.SetRemoteDescription()' fails if incorrect SDP is provided") 59 | { 60 | auto sdp = std::string(); 61 | 62 | REQUIRE_THROWS_AS( 63 | pc.SetLocalDescription(webrtc::SdpType::kOffer, sdp), 64 | MediaSoupClientError); 65 | } 66 | 67 | SECTION("'pc.SetRemoteDescription()' succeeds if correct SDP is provided") 68 | { 69 | auto sdp = helpers::readFile("test/data/webrtc.sdp"); 70 | 71 | REQUIRE_NOTHROW(pc.SetRemoteDescription(webrtc::SdpType::kOffer, sdp)); 72 | } 73 | 74 | SECTION("'pc.CreateOffer()' succeeds") 75 | { 76 | webrtc::PeerConnectionInterface::RTCOfferAnswerOptions options; 77 | 78 | REQUIRE_NOTHROW(offer = pc.CreateOffer(options)); 79 | } 80 | 81 | SECTION("'pc.SetRemoteDescription()' succeeds") 82 | { 83 | REQUIRE_NOTHROW(pc.SetRemoteDescription(webrtc::SdpType::kOffer, offer)); 84 | } 85 | 86 | SECTION("'pc.CreateAnswer()' succeeds if remote offer is provided") 87 | { 88 | webrtc::PeerConnectionInterface::RTCOfferAnswerOptions options; 89 | 90 | REQUIRE_NOTHROW(pc.CreateAnswer(options)); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /include/Device.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MSC_DEVICE_HPP 2 | #define MSC_DEVICE_HPP 3 | 4 | #include "Handler.hpp" 5 | #include "Transport.hpp" 6 | #include 7 | #include 8 | #include 9 | 10 | namespace mediasoupclient 11 | { 12 | class Device 13 | { 14 | public: 15 | Device() = default; 16 | ~Device() = default; 17 | 18 | bool IsLoaded() const; 19 | const nlohmann::json& GetRtpCapabilities() const; 20 | const nlohmann::json& GetSctpCapabilities() const; 21 | void Load( 22 | nlohmann::json routerRtpCapabilities, 23 | const PeerConnection::Options* peerConnectionOptions = nullptr); 24 | bool CanProduce(const std::string& kind); 25 | SendTransport* CreateSendTransport( 26 | SendTransport::Listener* listener, 27 | const std::string& id, 28 | const nlohmann::json& iceParameters, 29 | const nlohmann::json& iceCandidates, 30 | const nlohmann::json& dtlsParameters, 31 | const nlohmann::json& sctpParameters, 32 | const PeerConnection::Options* peerConnectionOptions = nullptr, 33 | const nlohmann::json& appData = nlohmann::json::object()) const; 34 | SendTransport* CreateSendTransport( 35 | SendTransport::Listener* listener, 36 | const std::string& id, 37 | const nlohmann::json& iceParameters, 38 | const nlohmann::json& iceCandidates, 39 | const nlohmann::json& dtlsParameters, 40 | const PeerConnection::Options* peerConnectionOptions = nullptr, 41 | const nlohmann::json& appData = nlohmann::json::object()) const; 42 | RecvTransport* CreateRecvTransport( 43 | RecvTransport::Listener* listener, 44 | const std::string& id, 45 | const nlohmann::json& iceParameters, 46 | const nlohmann::json& iceCandidates, 47 | const nlohmann::json& dtlsParameters, 48 | const nlohmann::json& sctpParameters, 49 | const PeerConnection::Options* peerConnectionOptions = nullptr, 50 | const nlohmann::json& appData = nlohmann::json::object()) const; 51 | RecvTransport* CreateRecvTransport( 52 | RecvTransport::Listener* listener, 53 | const std::string& id, 54 | const nlohmann::json& iceParameters, 55 | const nlohmann::json& iceCandidates, 56 | const nlohmann::json& dtlsParameters, 57 | const PeerConnection::Options* peerConnectionOptions = nullptr, 58 | const nlohmann::json& appData = nlohmann::json::object()) const; 59 | 60 | private: 61 | // Loaded flag. 62 | bool loaded{ false }; 63 | // Extended RTP capabilities. 64 | std::function getSendExtendedRtpCapabilities; 65 | // Local RTP capabilities for receiving media. 66 | nlohmann::json recvRtpCapabilities; 67 | // Whether we can produce audio/video based on computed extended RTP capabilities. 68 | // clang-format off 69 | std::map canProduceByKind = 70 | { 71 | { "audio", false }, 72 | { "video", false } 73 | }; 74 | // clang-format on 75 | // Local SCTP capabilities. 76 | nlohmann::json sctpCapabilities; 77 | }; 78 | } // namespace mediasoupclient 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /include/Producer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MSC_PRODUCER_HPP 2 | #define MSC_PRODUCER_HPP 3 | 4 | #include 5 | #include // webrtc::MediaStreamTrackInterface 6 | #include // webrtc::RtpSenderInterface 7 | #include 8 | 9 | namespace mediasoupclient 10 | { 11 | // Fast forward declarations. 12 | class Transport; 13 | class SendTransport; 14 | 15 | class Producer 16 | { 17 | public: 18 | class PrivateListener 19 | { 20 | public: 21 | virtual ~PrivateListener() = default; 22 | virtual void OnClose(Producer* producer) = 0; 23 | virtual void OnReplaceTrack( 24 | const Producer* producer, webrtc::MediaStreamTrackInterface* newTrack) = 0; 25 | virtual void OnSetMaxSpatialLayer(const Producer* producer, uint8_t maxSpatialLayer) = 0; 26 | virtual nlohmann::json OnGetStats(const Producer* producer) = 0; 27 | }; 28 | 29 | /* Public Listener API */ 30 | class Listener 31 | { 32 | public: 33 | virtual ~Listener() = default; 34 | virtual void OnTransportClose(Producer* producer) = 0; 35 | }; 36 | 37 | private: 38 | Producer( 39 | PrivateListener* privateListener, 40 | Listener* listener, 41 | const std::string& id, 42 | const std::string& localId, 43 | webrtc::RtpSenderInterface* rtpSender, 44 | webrtc::MediaStreamTrackInterface* track, 45 | const nlohmann::json& rtpParameters, 46 | const nlohmann::json& appData); 47 | 48 | public: 49 | const std::string& GetId() const; 50 | const std::string& GetLocalId() const; 51 | bool IsClosed() const; 52 | std::string GetKind() const; 53 | webrtc::RtpSenderInterface* GetRtpSender() const; 54 | webrtc::MediaStreamTrackInterface* GetTrack() const; 55 | const nlohmann::json& GetRtpParameters() const; 56 | bool IsPaused() const; 57 | uint8_t GetMaxSpatialLayer() const; 58 | nlohmann::json& GetAppData(); 59 | void Close(); 60 | nlohmann::json GetStats() const; 61 | void Pause(); 62 | void Resume(); 63 | void ReplaceTrack(webrtc::MediaStreamTrackInterface* track); 64 | void SetMaxSpatialLayer(uint8_t spatialLayer); 65 | 66 | private: 67 | void TransportClosed(); 68 | 69 | /* SendTransport will create instances and call private member TransportClosed */ 70 | friend SendTransport; 71 | 72 | private: 73 | // PrivateListener instance. 74 | PrivateListener* privateListener; 75 | // Public Listener instance. 76 | Listener* listener; 77 | // Id. 78 | std::string id; 79 | // localId. 80 | std::string localId; 81 | // Closed flag. 82 | bool closed{ false }; 83 | // Associated RTCRtpSender. 84 | webrtc::RtpSenderInterface* rtpSender{ nullptr }; 85 | // Local track. 86 | webrtc::MediaStreamTrackInterface* track{ nullptr }; 87 | // RTP parameters. 88 | nlohmann::json rtpParameters; 89 | // Paused flag. 90 | bool paused{ false }; 91 | // Video Max spatial layer. 92 | uint8_t maxSpatialLayer{ 0 }; 93 | // App custom data. 94 | nlohmann::json appData; 95 | }; 96 | } // namespace mediasoupclient 97 | 98 | #endif 99 | -------------------------------------------------------------------------------- /include/MediaSoupClientErrors.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MSC_MEDIASOUP_CLIENT_ERRORS_HPP 2 | #define MSC_MEDIASOUP_CLIENT_ERRORS_HPP 3 | 4 | #include "Logger.hpp" 5 | #include // std::snprintf() 6 | #include 7 | 8 | class MediaSoupClientError : public std::runtime_error 9 | { 10 | public: 11 | explicit MediaSoupClientError(const char* description); 12 | }; 13 | 14 | /* Inline methods. */ 15 | 16 | inline MediaSoupClientError::MediaSoupClientError(const char* description) 17 | : std::runtime_error(description) 18 | { 19 | } 20 | 21 | class MediaSoupClientTypeError : public MediaSoupClientError 22 | { 23 | public: 24 | explicit MediaSoupClientTypeError(const char* description); 25 | }; 26 | 27 | /* Inline methods. */ 28 | 29 | inline MediaSoupClientTypeError::MediaSoupClientTypeError(const char* description) 30 | : MediaSoupClientError(description) 31 | { 32 | } 33 | 34 | class MediaSoupClientUnsupportedError : public MediaSoupClientError 35 | { 36 | public: 37 | explicit MediaSoupClientUnsupportedError(const char* description); 38 | }; 39 | 40 | /* Inline methods. */ 41 | 42 | inline MediaSoupClientUnsupportedError::MediaSoupClientUnsupportedError(const char* description) 43 | : MediaSoupClientError(description) 44 | { 45 | } 46 | 47 | class MediaSoupClientInvalidStateError : public MediaSoupClientError 48 | { 49 | public: 50 | explicit MediaSoupClientInvalidStateError(const char* description); 51 | }; 52 | 53 | /* Inline methods. */ 54 | 55 | inline MediaSoupClientInvalidStateError::MediaSoupClientInvalidStateError(const char* description) 56 | : MediaSoupClientError(description) 57 | { 58 | } 59 | 60 | // clang-format off 61 | #define MSC_THROW_ERROR(desc, ...) \ 62 | do \ 63 | { \ 64 | MSC_ERROR("throwing MediaSoupClientError: " desc, ##__VA_ARGS__); \ 65 | \ 66 | static char buffer[2000]; \ 67 | \ 68 | std::snprintf(buffer, 2000, desc, ##__VA_ARGS__); \ 69 | throw MediaSoupClientError(buffer); \ 70 | } while (false) 71 | 72 | #define MSC_THROW_TYPE_ERROR(desc, ...) \ 73 | do \ 74 | { \ 75 | MSC_ERROR("throwing MediaSoupClientTypeError: " desc, ##__VA_ARGS__); \ 76 | \ 77 | static char buffer[2000]; \ 78 | \ 79 | std::snprintf(buffer, 2000, desc, ##__VA_ARGS__); \ 80 | throw MediaSoupClientTypeError(buffer); \ 81 | } while (false) 82 | 83 | #define MSC_THROW_UNSUPPORTED_ERROR(desc, ...) \ 84 | do \ 85 | { \ 86 | MSC_ERROR("throwing MediaSoupClientUnsupportedError: " desc, ##__VA_ARGS__); \ 87 | \ 88 | static char buffer[2000]; \ 89 | \ 90 | std::snprintf(buffer, 2000, desc, ##__VA_ARGS__); \ 91 | throw MediaSoupClientUnsupportedError(buffer); \ 92 | } while (false) 93 | 94 | #define MSC_THROW_INVALID_STATE_ERROR(desc, ...) \ 95 | do \ 96 | { \ 97 | MSC_ERROR("throwing MediaSoupClientInvalidStateError: " desc, ##__VA_ARGS__); \ 98 | \ 99 | static char buffer[2000]; \ 100 | \ 101 | std::snprintf(buffer, 2000, desc, ##__VA_ARGS__); \ 102 | throw MediaSoupClientInvalidStateError(buffer); \ 103 | } while (false) 104 | 105 | // clang-format on 106 | 107 | #endif 108 | -------------------------------------------------------------------------------- /src/Consumer.cpp: -------------------------------------------------------------------------------- 1 | #define MSC_CLASS "Consumer" 2 | 3 | #include "Consumer.hpp" 4 | #include "Logger.hpp" 5 | #include "MediaSoupClientErrors.hpp" 6 | 7 | using json = nlohmann::json; 8 | 9 | namespace mediasoupclient 10 | { 11 | Consumer::Consumer( 12 | Consumer::PrivateListener* privateListener, 13 | Consumer::Listener* listener, 14 | const std::string& id, 15 | const std::string& localId, 16 | const std::string& producerId, 17 | webrtc::RtpReceiverInterface* rtpReceiver, 18 | webrtc::MediaStreamTrackInterface* track, 19 | const json& rtpParameters, 20 | const json& appData) 21 | : privateListener(privateListener), listener(listener), id(id), localId(localId), 22 | producerId(producerId), rtpReceiver(rtpReceiver), track(track), rtpParameters(rtpParameters), 23 | appData(appData) 24 | { 25 | MSC_TRACE(); 26 | } 27 | 28 | const std::string& Consumer::GetId() const 29 | { 30 | MSC_TRACE(); 31 | 32 | return this->id; 33 | } 34 | 35 | const std::string& Consumer::GetLocalId() const 36 | { 37 | MSC_TRACE(); 38 | 39 | return this->localId; 40 | } 41 | 42 | const std::string& Consumer::GetProducerId() const 43 | { 44 | MSC_TRACE(); 45 | 46 | return this->producerId; 47 | } 48 | 49 | bool Consumer::IsClosed() const 50 | { 51 | MSC_TRACE(); 52 | 53 | return this->closed; 54 | } 55 | 56 | const std::string Consumer::GetKind() const 57 | { 58 | MSC_TRACE(); 59 | 60 | return this->track->kind(); 61 | } 62 | 63 | webrtc::RtpReceiverInterface* Consumer::GetRtpReceiver() const 64 | { 65 | MSC_TRACE(); 66 | 67 | return this->rtpReceiver; 68 | } 69 | 70 | webrtc::MediaStreamTrackInterface* Consumer::GetTrack() const 71 | { 72 | MSC_TRACE(); 73 | 74 | return this->track; 75 | } 76 | 77 | const json& Consumer::GetRtpParameters() const 78 | { 79 | MSC_TRACE(); 80 | 81 | return this->rtpParameters; 82 | } 83 | 84 | bool Consumer::IsPaused() const 85 | { 86 | MSC_TRACE(); 87 | 88 | return !this->track->enabled(); 89 | } 90 | 91 | json& Consumer::GetAppData() 92 | { 93 | MSC_TRACE(); 94 | 95 | return this->appData; 96 | } 97 | 98 | /** 99 | * Closes the Consumer. 100 | */ 101 | void Consumer::Close() 102 | { 103 | MSC_TRACE(); 104 | 105 | if (this->closed) 106 | return; 107 | 108 | this->closed = true; 109 | 110 | this->privateListener->OnClose(this); 111 | } 112 | 113 | json Consumer::GetStats() const 114 | { 115 | if (this->closed) 116 | MSC_THROW_INVALID_STATE_ERROR("Consumer closed"); 117 | 118 | return this->privateListener->OnGetStats(this); 119 | } 120 | 121 | /** 122 | * Pauses sending media. 123 | */ 124 | void Consumer::Pause() 125 | { 126 | MSC_TRACE(); 127 | 128 | if (this->closed) 129 | { 130 | MSC_ERROR("Consumer closed"); 131 | 132 | return; 133 | } 134 | 135 | this->track->set_enabled(false); 136 | } 137 | 138 | /** 139 | * Resumes sending media. 140 | */ 141 | void Consumer::Resume() 142 | { 143 | MSC_TRACE(); 144 | 145 | if (this->closed) 146 | { 147 | MSC_ERROR("Consumer closed"); 148 | 149 | return; 150 | } 151 | 152 | this->track->set_enabled(true); 153 | } 154 | 155 | /** 156 | * Transport was closed. 157 | */ 158 | void Consumer::TransportClosed() 159 | { 160 | MSC_TRACE(); 161 | 162 | if (this->closed) 163 | return; 164 | 165 | this->closed = true; 166 | 167 | this->listener->OnTransportClose(this); 168 | } 169 | } // namespace mediasoupclient 170 | -------------------------------------------------------------------------------- /test/src/Device.test.cpp: -------------------------------------------------------------------------------- 1 | #include "Device.hpp" 2 | #include "FakeTransportListener.hpp" 3 | #include "MediaSoupClientErrors.hpp" 4 | #include "fakeParameters.hpp" 5 | #include "ortc.hpp" 6 | #include 7 | 8 | TEST_CASE("Device", "[Device]") 9 | { 10 | static const json TransportRemoteParameters = generateTransportRemoteParameters(); 11 | 12 | static FakeSendTransportListener sendTransportListener; 13 | static FakeRecvTransportListener recvTransportListener; 14 | 15 | static std::unique_ptr device(new mediasoupclient::Device()); 16 | 17 | static json routerRtpCapabilities; 18 | 19 | SECTION("device.IsLoaded() is false if not loaded") 20 | { 21 | REQUIRE(!device->IsLoaded()); 22 | } 23 | 24 | SECTION("device.GetRtpCapabilities() throws if not loaded") 25 | { 26 | REQUIRE_THROWS_AS(device->GetRtpCapabilities(), MediaSoupClientInvalidStateError); 27 | } 28 | 29 | SECTION("device.CanProduce() with audio/video throws if not loaded") 30 | { 31 | REQUIRE_THROWS_AS(device->CanProduce("audio"), MediaSoupClientInvalidStateError); 32 | REQUIRE_THROWS_AS(device->CanProduce("video"), MediaSoupClientInvalidStateError); 33 | } 34 | 35 | SECTION("device.CreateSendTransport() fails if not loaded") 36 | { 37 | REQUIRE_THROWS_AS( 38 | device->CreateSendTransport( 39 | &sendTransportListener, 40 | TransportRemoteParameters["id"], 41 | TransportRemoteParameters["iceParameters"], 42 | TransportRemoteParameters["iceCandidates"], 43 | TransportRemoteParameters["dtlsParameters"]), 44 | MediaSoupClientInvalidStateError); 45 | } 46 | 47 | SECTION("device.CreateRecvTransport() fails if not loaded") 48 | { 49 | REQUIRE_THROWS_AS( 50 | device->CreateRecvTransport( 51 | &recvTransportListener, 52 | TransportRemoteParameters["id"], 53 | TransportRemoteParameters["iceParameters"], 54 | TransportRemoteParameters["iceCandidates"], 55 | TransportRemoteParameters["dtlsParameters"]), 56 | MediaSoupClientInvalidStateError); 57 | } 58 | 59 | SECTION("device.Load() succeeds") 60 | { 61 | routerRtpCapabilities = generateRouterRtpCapabilities(); 62 | 63 | REQUIRE_NOTHROW(device->Load(routerRtpCapabilities)); 64 | REQUIRE(device->IsLoaded()); 65 | } 66 | 67 | SECTION("device.Load() fails if already loaded") 68 | { 69 | REQUIRE_THROWS_AS(device->Load(routerRtpCapabilities), MediaSoupClientInvalidStateError); 70 | } 71 | 72 | SECTION("device.GetRtpCapabilities() succeeds") 73 | { 74 | REQUIRE(device->GetRtpCapabilities().is_object()); 75 | } 76 | 77 | SECTION("device.CanProduce() with 'audio'/'video' kind returns true") 78 | { 79 | REQUIRE(device->CanProduce("audio")); 80 | REQUIRE(device->CanProduce("video")); 81 | } 82 | 83 | SECTION("device.CanProduce() with invalid kind throws exception") 84 | { 85 | REQUIRE_THROWS_AS(device->CanProduce("chicken"), MediaSoupClientError); 86 | } 87 | 88 | SECTION("device.CreateSendTransport() succeeds") 89 | { 90 | REQUIRE_NOTHROW(device->CreateSendTransport( 91 | &sendTransportListener, 92 | TransportRemoteParameters["id"], 93 | TransportRemoteParameters["iceParameters"], 94 | TransportRemoteParameters["iceCandidates"], 95 | TransportRemoteParameters["dtlsParameters"])); 96 | } 97 | 98 | SECTION("device.CreateRecvTransport() succeeds") 99 | { 100 | REQUIRE_NOTHROW(device->CreateRecvTransport( 101 | &recvTransportListener, 102 | TransportRemoteParameters["id"], 103 | TransportRemoteParameters["iceParameters"], 104 | TransportRemoteParameters["iceCandidates"], 105 | TransportRemoteParameters["dtlsParameters"])); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /test/src/MediaStreamTrackFactory.cpp: -------------------------------------------------------------------------------- 1 | #ifndef MEDIA_STREAM_TRACK_FACTORY_H 2 | #define MEDIA_STREAM_TRACK_FACTORY_H 3 | 4 | #define MSC_CLASS "MediaStreamTrackFactory" 5 | 6 | #include "MediaStreamTrackFactory.hpp" 7 | #include "MediaSoupClientErrors.hpp" 8 | #include "api/audio_codecs/builtin_audio_decoder_factory.h" 9 | #include "api/audio_codecs/builtin_audio_encoder_factory.h" 10 | #include "api/create_peerconnection_factory.h" 11 | #include "api/video_codecs/builtin_video_decoder_factory.h" 12 | #include "api/video_codecs/builtin_video_encoder_factory.h" 13 | #include "mediasoupclient.hpp" 14 | #include "pc/test/fake_audio_capture_module.h" 15 | #include "pc/test/fake_video_track_source.h" 16 | #include 17 | 18 | using namespace mediasoupclient; 19 | 20 | void MediaStreamTrackFactory::Create() 21 | { 22 | if (Factory) 23 | return; 24 | NetworkThread = webrtc::Thread::CreateWithSocketServer(); 25 | WorkerThread = webrtc::Thread::Create(); 26 | SignalingThread = webrtc::Thread::Create(); 27 | 28 | NetworkThread->SetName("network_thread", nullptr); 29 | SignalingThread->SetName("signaling_thread", nullptr); 30 | WorkerThread->SetName("worker_thread", nullptr); 31 | 32 | if (!NetworkThread->Start() || !SignalingThread->Start() || !WorkerThread->Start()) 33 | { 34 | MSC_THROW_INVALID_STATE_ERROR("thread start errored"); 35 | } 36 | 37 | webrtc::PeerConnectionInterface::RTCConfiguration config; 38 | 39 | auto fakeAudioCaptureModule = FakeAudioCaptureModule::Create(); 40 | if (!fakeAudioCaptureModule) 41 | { 42 | MSC_THROW_INVALID_STATE_ERROR("audio capture module creation errored"); 43 | } 44 | 45 | Factory = webrtc::CreatePeerConnectionFactory( 46 | NetworkThread.get(), 47 | WorkerThread.get(), 48 | SignalingThread.get(), 49 | fakeAudioCaptureModule, 50 | webrtc::CreateBuiltinAudioEncoderFactory(), 51 | webrtc::CreateBuiltinAudioDecoderFactory(), 52 | webrtc::CreateBuiltinVideoEncoderFactory(), 53 | webrtc::CreateBuiltinVideoDecoderFactory(), 54 | nullptr /*audio_mixer*/, 55 | nullptr /*audio_processing*/); 56 | 57 | if (!Factory) 58 | { 59 | MSC_THROW_ERROR("error ocurred creating peerconnection factory"); 60 | } 61 | PeerConnectionOptions.factory = Factory.get(); 62 | } 63 | 64 | void MediaStreamTrackFactory::ReleaseThreads() 65 | { 66 | if (Factory) 67 | { 68 | PeerConnectionOptions.factory = nullptr; 69 | Factory = nullptr; 70 | } 71 | 72 | if (NetworkThread) 73 | { 74 | NetworkThread->Stop(); 75 | NetworkThread.reset(); 76 | NetworkThread = nullptr; 77 | } 78 | 79 | if (WorkerThread) 80 | { 81 | WorkerThread->Stop(); 82 | WorkerThread.reset(); 83 | WorkerThread = nullptr; 84 | } 85 | 86 | if (SignalingThread) 87 | { 88 | SignalingThread->Stop(); 89 | SignalingThread.reset(); 90 | SignalingThread = nullptr; 91 | } 92 | } 93 | 94 | // Audio track creation. 95 | webrtc::scoped_refptr createAudioTrack(const std::string& label) 96 | { 97 | MediaStreamTrackFactory& singleton = MediaStreamTrackFactory::getInstance(); 98 | 99 | webrtc::AudioOptions options; 100 | options.highpass_filter = false; 101 | 102 | webrtc::scoped_refptr source = 103 | singleton.Factory->CreateAudioSource(options); 104 | 105 | return singleton.Factory->CreateAudioTrack(label, source.get()); 106 | } 107 | 108 | // Video track creation. 109 | webrtc::scoped_refptr createVideoTrack(const std::string& label) 110 | { 111 | MediaStreamTrackFactory& singleton = MediaStreamTrackFactory::getInstance(); 112 | 113 | webrtc::scoped_refptr source = webrtc::FakeVideoTrackSource::Create(); 114 | 115 | return singleton.Factory->CreateVideoTrack(source, label); 116 | } 117 | 118 | #endif // MEDIA_STREAM_TRACK_FACTORY_H 119 | -------------------------------------------------------------------------------- /src/DataProducer.cpp: -------------------------------------------------------------------------------- 1 | #define MSC_CLASS "DataProducer" 2 | 3 | #include "DataProducer.hpp" 4 | #include "Logger.hpp" 5 | 6 | using json = nlohmann::json; 7 | 8 | namespace mediasoupclient 9 | { 10 | DataProducer::DataProducer( 11 | DataProducer::PrivateListener* privateListener, 12 | DataProducer::Listener* listener, 13 | const std::string& id, 14 | webrtc::scoped_refptr dataChannel, 15 | const json& sctpStreamParameters, 16 | const json& appData) 17 | : privateListener(privateListener), listener(listener), id(id), dataChannel(dataChannel), 18 | sctpStreamParameters(sctpStreamParameters), appData(appData) 19 | { 20 | MSC_TRACE(); 21 | 22 | this->dataChannel->RegisterObserver(this); 23 | }; 24 | 25 | const std::string& DataProducer::GetId() const 26 | { 27 | MSC_TRACE(); 28 | 29 | return this->id; 30 | } 31 | 32 | std::string DataProducer::GetLocalId() const 33 | { 34 | MSC_TRACE(); 35 | 36 | return std::to_string(this->dataChannel->id()); 37 | } 38 | 39 | const json& DataProducer::GetSctpStreamParameters() const 40 | { 41 | MSC_TRACE(); 42 | 43 | return this->sctpStreamParameters; 44 | } 45 | 46 | webrtc::DataChannelInterface::DataState DataProducer::GetReadyState() const 47 | { 48 | MSC_TRACE(); 49 | 50 | return this->dataChannel->state(); 51 | } 52 | 53 | std::string DataProducer::GetLabel() 54 | { 55 | MSC_TRACE(); 56 | 57 | return this->dataChannel->label(); 58 | } 59 | 60 | std::string DataProducer::GetProtocol() 61 | { 62 | MSC_TRACE(); 63 | 64 | return this->dataChannel->protocol(); 65 | } 66 | 67 | uint64_t DataProducer::GetBufferedAmount() const 68 | { 69 | MSC_TRACE(); 70 | 71 | return this->dataChannel->buffered_amount(); 72 | } 73 | 74 | const json& DataProducer::GetAppData() const 75 | { 76 | MSC_TRACE(); 77 | 78 | return this->appData; 79 | } 80 | 81 | bool DataProducer::IsClosed() const 82 | { 83 | MSC_TRACE(); 84 | 85 | return this->closed; 86 | } 87 | 88 | void DataProducer::Close() 89 | { 90 | MSC_TRACE(); 91 | 92 | if (this->closed) 93 | return; 94 | 95 | this->closed = true; 96 | this->dataChannel->Close(); 97 | this->privateListener->OnClose(this); 98 | } 99 | 100 | void DataProducer::Send(const webrtc::DataBuffer& buffer) 101 | { 102 | MSC_TRACE(); 103 | 104 | this->dataChannel->Send(buffer); 105 | } 106 | 107 | /** 108 | * Transport was closed. 109 | */ 110 | void DataProducer::TransportClosed() 111 | { 112 | MSC_TRACE(); 113 | 114 | if (this->closed) 115 | return; 116 | 117 | this->closed = true; 118 | this->dataChannel->Close(); 119 | this->listener->OnTransportClose(this); 120 | } 121 | 122 | // The data channel state has changed. 123 | void DataProducer::OnStateChange() 124 | { 125 | MSC_TRACE(); 126 | 127 | webrtc::DataChannelInterface::DataState state = this->dataChannel->state(); 128 | 129 | switch (state) 130 | { 131 | case webrtc::DataChannelInterface::DataState::kConnecting: 132 | break; 133 | case webrtc::DataChannelInterface::DataState::kOpen: 134 | this->listener->OnOpen(this); 135 | break; 136 | case webrtc::DataChannelInterface::DataState::kClosing: 137 | break; 138 | case webrtc::DataChannelInterface::DataState::kClosed: 139 | this->listener->OnClose(this); 140 | break; 141 | default: 142 | MSC_ERROR("unknown state %s", webrtc::DataChannelInterface::DataStateString(state)); 143 | break; 144 | } 145 | } 146 | 147 | // A data buffer was successfully received. 148 | void DataProducer::OnMessage(const webrtc::DataBuffer& /*buffer*/) 149 | { 150 | MSC_ERROR("message received on DataProducer [dataProducer.id:%s]", this->GetId().c_str()); 151 | } 152 | 153 | // The data channel's buffered_amount has changed. 154 | void DataProducer::OnBufferedAmountChange(uint64_t sentDataSize) 155 | { 156 | MSC_TRACE(); 157 | 158 | this->listener->OnBufferedAmountChange(this, sentDataSize); 159 | } 160 | } // namespace mediasoupclient 161 | -------------------------------------------------------------------------------- /src/DataConsumer.cpp: -------------------------------------------------------------------------------- 1 | #define MSC_CLASS "DataConsumer" 2 | 3 | #include "DataConsumer.hpp" 4 | #include "Logger.hpp" 5 | #include "MediaSoupClientErrors.hpp" 6 | 7 | using json = nlohmann::json; 8 | 9 | namespace mediasoupclient 10 | { 11 | DataConsumer::DataConsumer( 12 | DataConsumer::Listener* listener, 13 | DataConsumer::PrivateListener* privateListener, 14 | const std::string& id, 15 | const std::string& dataProducerId, 16 | webrtc::scoped_refptr dataChannel, 17 | const json& sctpStreamParameters, 18 | const json& appData) 19 | : listener(listener), privateListener(privateListener), id(id), dataProducerId(dataProducerId), 20 | dataChannel(dataChannel), sctpParameters(sctpStreamParameters), appData(appData) 21 | { 22 | MSC_TRACE(); 23 | 24 | this->dataChannel->RegisterObserver(this); 25 | } 26 | 27 | // The data channel state has changed. 28 | void DataConsumer::OnStateChange() 29 | { 30 | MSC_TRACE(); 31 | 32 | webrtc::DataChannelInterface::DataState state = this->dataChannel->state(); 33 | 34 | switch (state) 35 | { 36 | case webrtc::DataChannelInterface::DataState::kConnecting: 37 | this->listener->OnConnecting(this); 38 | break; 39 | case webrtc::DataChannelInterface::DataState::kOpen: 40 | this->listener->OnOpen(this); 41 | break; 42 | case webrtc::DataChannelInterface::DataState::kClosing: 43 | this->listener->OnClosing(this); 44 | break; 45 | case webrtc::DataChannelInterface::DataState::kClosed: 46 | this->listener->OnClose(this); 47 | break; 48 | default: 49 | MSC_ERROR("unknown state %s", webrtc::DataChannelInterface::DataStateString(state)); 50 | break; 51 | } 52 | } 53 | 54 | // A data buffer was successfully received. 55 | void DataConsumer::OnMessage(const webrtc::DataBuffer& buffer) 56 | { 57 | MSC_TRACE(); 58 | 59 | this->listener->OnMessage(this, buffer); 60 | } 61 | 62 | // The data channel's buffered_amount has changed. 63 | void DataConsumer::OnBufferedAmountChange(uint64_t /*sentDataSize*/) 64 | { 65 | MSC_TRACE(); 66 | // Should not happen on consumer. 67 | } 68 | 69 | /** 70 | * DataConsumer id. 71 | */ 72 | const std::string& DataConsumer::GetId() const 73 | { 74 | MSC_TRACE(); 75 | 76 | return this->id; 77 | } 78 | 79 | std::string DataConsumer::GetLocalId() const 80 | { 81 | MSC_TRACE(); 82 | 83 | return std::to_string(this->dataChannel->id()); 84 | } 85 | 86 | /** 87 | * Associated DataProducer id. 88 | */ 89 | const std::string& DataConsumer::GetDataProducerId() const 90 | { 91 | MSC_TRACE(); 92 | 93 | return this->dataProducerId; 94 | } 95 | 96 | /** 97 | * SCTP stream parameters. 98 | */ 99 | const json& DataConsumer::GetSctpStreamParameters() const 100 | { 101 | MSC_TRACE(); 102 | 103 | return this->sctpParameters; 104 | } 105 | 106 | /** 107 | * DataChannel readyState. 108 | */ 109 | webrtc::DataChannelInterface::DataState DataConsumer::GetReadyState() const 110 | { 111 | MSC_TRACE(); 112 | 113 | return this->dataChannel->state(); 114 | } 115 | 116 | /** 117 | * DataChannel label. 118 | */ 119 | std::string DataConsumer::GetLabel() const 120 | { 121 | MSC_TRACE(); 122 | 123 | return this->dataChannel->label(); 124 | } 125 | 126 | /** 127 | * DataChannel protocol. 128 | */ 129 | std::string DataConsumer::GetProtocol() const 130 | { 131 | MSC_TRACE(); 132 | 133 | return this->dataChannel->protocol(); 134 | } 135 | 136 | /** 137 | * App custom data. 138 | */ 139 | const json& DataConsumer::GetAppData() const 140 | { 141 | MSC_TRACE(); 142 | 143 | return this->appData; 144 | } 145 | 146 | /** 147 | * Whether the DataConsumer is closed. 148 | */ 149 | bool DataConsumer::IsClosed() const 150 | { 151 | MSC_TRACE(); 152 | 153 | return this->closed; 154 | } 155 | 156 | /** 157 | * Closes the DataConsumer. 158 | */ 159 | void DataConsumer::Close() 160 | { 161 | MSC_TRACE(); 162 | 163 | if (this->closed) 164 | return; 165 | 166 | this->closed = true; 167 | this->dataChannel->Close(); 168 | this->privateListener->OnClose(this); 169 | } 170 | 171 | /** 172 | * Transport was closed. 173 | */ 174 | void DataConsumer::TransportClosed() 175 | { 176 | MSC_TRACE(); 177 | 178 | if (this->closed) 179 | return; 180 | 181 | this->closed = true; 182 | this->dataChannel->Close(); 183 | this->listener->OnTransportClose(this); 184 | } 185 | } // namespace mediasoupclient 186 | -------------------------------------------------------------------------------- /include/Utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MSC_UTILS_HPP 2 | #define MSC_UTILS_HPP 3 | 4 | #include // uint32_t 5 | #include // generator seed 6 | #include // generators header 7 | #include 8 | #include // istringstream 9 | #include 10 | #include 11 | 12 | namespace mediasoupclient 13 | { 14 | namespace Utils 15 | { 16 | template 17 | T getRandomInteger(T min, T max); 18 | std::string getRandomInteger(size_t len); 19 | 20 | std::vector split(const std::string& s, char delimiter); 21 | std::string join(const std::vector& v, char delimiter); 22 | std::string join(const std::vector& v, char delimiter); 23 | 24 | // https://stackoverflow.com/a/447307/4827838. 25 | bool isInt(const std::string& str); 26 | bool isFloat(const std::string& str); 27 | int toInt(const std::string& str); 28 | float toFloat(const std::string& str); 29 | 30 | /* Inline utils implementations */ 31 | 32 | template 33 | inline T getRandomInteger(T min, T max) 34 | { 35 | // Seed with time. 36 | static unsigned int seed = time(nullptr); 37 | 38 | // Engine based on the Mersenne Twister 19937 (64 bits). 39 | static std::mt19937_64 rng(seed); 40 | 41 | // Uniform distribution for integers in the [min, max) range. 42 | // https://en.cppreference.com/w/cpp/numeric/random/uniform_int_distribution, [min, max] 43 | std::uniform_int_distribution dis(min, max - 1); 44 | 45 | return dis(rng); 46 | } 47 | 48 | inline std::string getRandomString(size_t len) 49 | { 50 | /* clang-format off */ 51 | static std::vector chars = 52 | { 53 | '0','1','2','3','4','5','6','7','8','9', 54 | 'a','b','c','d','e','f','g','h','i','j', 55 | 'k','l','m','n','o','p','q','r','s','t', 56 | 'u','v','w','x','y','z', 57 | 'A','B','C','D','E','F','G','H','I','J', 58 | 'K','L','M','N','O','P','Q','R','S','T', 59 | 'U','V','W','X','Y','Z' 60 | }; 61 | /* clang-format on */ 62 | 63 | // Seed with time. 64 | static unsigned int seed = time(nullptr); 65 | 66 | // Engine based on the Mersenne Twister 19937 (64 bits). 67 | static std::mt19937_64 rng(seed); 68 | 69 | // Uniform distribution for integers in the [min, max) range. 70 | std::uniform_int_distribution dis(0, chars.size() - 1); 71 | 72 | std::string s; 73 | 74 | s.reserve(len); 75 | 76 | while ((len--) != 0u) 77 | s += chars[dis(rng)]; 78 | 79 | return s; 80 | } 81 | 82 | inline std::vector split(const std::string& s, char delimiter) 83 | { 84 | std::vector tokens; 85 | std::string token; 86 | std::istringstream tokenStream(s); 87 | while (std::getline(tokenStream, token, delimiter)) 88 | { 89 | tokens.push_back(token); 90 | } 91 | return tokens; 92 | } 93 | 94 | inline std::string join(const std::vector& v, char delimiter) 95 | { 96 | std::string s; 97 | 98 | auto it = v.begin(); 99 | for (; it != v.end(); ++it) 100 | { 101 | s += *it; 102 | if (it != v.end() - 1) 103 | s += delimiter; 104 | } 105 | 106 | return s; 107 | } 108 | 109 | inline std::string join(const std::vector& v, char delimiter) 110 | { 111 | std::string s; 112 | 113 | auto it = v.begin(); 114 | for (; it != v.end(); ++it) 115 | { 116 | s += std::to_string(*it); 117 | if (it != v.end() - 1) 118 | s += delimiter; 119 | } 120 | 121 | return s; 122 | } 123 | 124 | inline bool isInt(const std::string& str) 125 | { 126 | std::istringstream iss(str); 127 | int64_t l; 128 | 129 | iss >> std::noskipws >> l; 130 | 131 | return iss.eof() && !iss.fail(); 132 | } 133 | 134 | inline bool isFloat(const std::string& str) 135 | { 136 | std::istringstream iss(str); 137 | float f; 138 | 139 | iss >> std::noskipws >> f; 140 | 141 | return iss.eof() && !iss.fail(); 142 | } 143 | 144 | inline int toInt(const std::string& str) 145 | { 146 | std::istringstream iss(str); 147 | int64_t ll; 148 | 149 | iss >> std::noskipws >> ll; 150 | 151 | if (iss.eof() && !iss.fail()) 152 | return std::stoll(str); 153 | 154 | return 0; 155 | } 156 | 157 | inline float toFloat(const std::string& str) 158 | { 159 | std::istringstream iss(str); 160 | double d; 161 | 162 | iss >> std::noskipws >> d; 163 | 164 | if (iss.eof() && !iss.fail()) 165 | return std::stod(str); 166 | 167 | return 0.0f; 168 | } 169 | } // namespace Utils 170 | } // namespace mediasoupclient 171 | 172 | #endif 173 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | project(mediasoupclient LANGUAGES CXX) 4 | 5 | include(FetchContent) 6 | 7 | # Set version number. 8 | set(mediasoupclient_VERSION_MAJOR 3) 9 | set(mediasoupclient_VERSION_MINOR 5) 10 | set(mediasoupclient_VERSION_PATCH 0) 11 | 12 | # Configure a header file to pass the version. 13 | configure_file ( 14 | "${PROJECT_SOURCE_DIR}/version.hpp.in" 15 | "${PROJECT_SOURCE_DIR}/include/version.hpp" 16 | NEWLINE_STYLE LF 17 | ) 18 | 19 | # C++ standard requirements. 20 | set(CMAKE_CXX_STANDARD 20) 21 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 22 | 23 | # Project options. 24 | option(MEDIASOUPCLIENT_BUILD_TESTS "Build unit tests" OFF) 25 | option(MEDIASOUPCLIENT_LOG_TRACE "Enable MSC_LOG_TRACE (See Logger.hpp)" OFF) 26 | option(MEDIASOUPCLIENT_LOG_DEV "Enable MSC_LOG_DEV (See Logger.hpp)" OFF) 27 | 28 | # Project configuration. 29 | if(NOT LIBWEBRTC_INCLUDE_PATH) 30 | set(LIBWEBRTC_INCLUDE_PATH "" CACHE STRING "libwebrtc include path") 31 | endif() 32 | if(NOT LIBWEBRTC_BINARY_PATH) 33 | set(LIBWEBRTC_BINARY_PATH "" CACHE STRING "libwebrtc binary path") 34 | endif() 35 | 36 | if(NOT LIBWEBRTC_INCLUDE_PATH) 37 | message(FATAL_ERROR "LIBWEBRTC_INCLUDE_PATH not provided") 38 | endif() 39 | 40 | if(NOT LIBWEBRTC_BINARY_PATH) 41 | message(FATAL_ERROR "LIBWEBRTC_BINARY_PATH not provided") 42 | endif() 43 | 44 | message("\n=========== libmediasoupclient Build Configuration ===========\n") 45 | message(STATUS "MEDIASOUPCLIENT_BUILD_TESTS : " ${MEDIASOUPCLIENT_BUILD_TESTS}) 46 | message(STATUS "MEDIASOUPCLIENT_LOG_TRACE : " ${MEDIASOUPCLIENT_LOG_TRACE}) 47 | message(STATUS "MEDIASOUPCLIENT_LOG_DEV : " ${MEDIASOUPCLIENT_LOG_DEV}) 48 | message(STATUS "LIBWEBRTC_INCLUDE_PATH : " ${LIBWEBRTC_INCLUDE_PATH}) 49 | message(STATUS "LIBWEBRTC_BINARY_PATH : " ${LIBWEBRTC_BINARY_PATH}) 50 | message("") 51 | 52 | # GCC >= 4.9 is required due to std::regex use. 53 | if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") 54 | if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9) 55 | message(FATAL_ERROR "GCC version must be at least 4.9!") 56 | endif() 57 | endif() 58 | 59 | if (${MEDIASOUPCLIENT_BUILD_TESTS}) 60 | add_subdirectory(test) 61 | endif() 62 | 63 | set(HEADER_FILES 64 | include/mediasoupclient.hpp 65 | ) 66 | 67 | set( 68 | SOURCE_FILES 69 | src/Consumer.cpp 70 | src/DataConsumer.cpp 71 | src/DataProducer.cpp 72 | src/Device.cpp 73 | src/Handler.cpp 74 | src/Logger.cpp 75 | src/PeerConnection.cpp 76 | src/Producer.cpp 77 | src/Transport.cpp 78 | src/mediasoupclient.cpp 79 | src/ortc.cpp 80 | src/scalabilityMode.cpp 81 | src/sdp/MediaSection.cpp 82 | src/sdp/RemoteSdp.cpp 83 | src/sdp/Utils.cpp 84 | include/Consumer.hpp 85 | include/Device.hpp 86 | include/Handler.hpp 87 | include/Logger.hpp 88 | include/MediaSoupClientErrors.hpp 89 | include/PeerConnection.hpp 90 | include/Producer.hpp 91 | include/Transport.hpp 92 | include/mediasoupclient.hpp 93 | include/ortc.hpp 94 | include/scalabilityMode.hpp 95 | include/sdp/MediaSection.hpp 96 | include/sdp/RemoteSdp.hpp 97 | include/sdp/Utils.hpp 98 | ) 99 | 100 | # Create target. 101 | add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) 102 | 103 | if(${MEDIASOUPCLIENT_LOG_TRACE}) 104 | target_compile_definitions(${PROJECT_NAME} 105 | PRIVATE MSC_LOG_TRACE=1 106 | ) 107 | endif() 108 | 109 | if(${MEDIASOUPCLIENT_LOG_DEV}) 110 | target_compile_definitions(${PROJECT_NAME} 111 | PRIVATE MSC_LOG_DEV=1 112 | ) 113 | endif() 114 | 115 | # Source Dependencies. 116 | 117 | message(STATUS "\nFetching libsdptransform...\n") 118 | FetchContent_Declare( 119 | libsdptransform 120 | GIT_REPOSITORY https://github.com/ibc/libsdptransform.git 121 | GIT_TAG 1.2.10 122 | ) 123 | FetchContent_MakeAvailable(libsdptransform) 124 | 125 | # Add some compile flags to our source files. 126 | if(MSVC) 127 | set_source_files_properties(${SOURCE_FILES} 128 | PROPERTIES COMPILE_FLAGS "/W3") 129 | else() 130 | set_source_files_properties(${SOURCE_FILES} 131 | PROPERTIES COMPILE_FLAGS -Wall -Wextra -Wpedantic) 132 | endif() 133 | 134 | # Private (implementation) header files. 135 | target_include_directories(${PROJECT_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/include) 136 | 137 | # Public (interface) headers from dependencies. 138 | target_include_directories(${PROJECT_NAME} PUBLIC 139 | ${libsdptransform_SOURCE_DIR}/include 140 | "${LIBWEBRTC_INCLUDE_PATH}" 141 | "${LIBWEBRTC_INCLUDE_PATH}/third_party/abseil-cpp" 142 | ) 143 | 144 | # Public (interface) dependencies. 145 | target_link_libraries(${PROJECT_NAME} PUBLIC 146 | sdptransform 147 | ${LIBWEBRTC_BINARY_PATH}/libwebrtc${CMAKE_STATIC_LIBRARY_SUFFIX} 148 | ) 149 | 150 | # Compile definitions for libwebrtc. 151 | target_compile_definitions(${PROJECT_NAME} PUBLIC 152 | $<$>:WEBRTC_POSIX> 153 | $<$:WEBRTC_WIN> 154 | $<$:NOMINMAX> 155 | $<$:WIN32_LEAN_AND_MEAN> 156 | $<$:WEBRTC_MAC> 157 | ) 158 | 159 | install(TARGETS mediasoupclient DESTINATION lib) 160 | install(FILES ${HEADER_FILES} DESTINATION include/mediasoupclient) 161 | -------------------------------------------------------------------------------- /src/Producer.cpp: -------------------------------------------------------------------------------- 1 | #define MSC_CLASS "Producer" 2 | 3 | #include "Producer.hpp" 4 | #include "Logger.hpp" 5 | #include "MediaSoupClientErrors.hpp" 6 | 7 | using json = nlohmann::json; 8 | 9 | namespace mediasoupclient 10 | { 11 | Producer::Producer( 12 | Producer::PrivateListener* privateListener, 13 | Producer::Listener* listener, 14 | const std::string& id, 15 | const std::string& localId, 16 | webrtc::RtpSenderInterface* rtpSender, 17 | webrtc::MediaStreamTrackInterface* track, 18 | const json& rtpParameters, 19 | const json& appData) 20 | : privateListener(privateListener), listener(listener), id(id), localId(localId), 21 | rtpSender(rtpSender), track(track), rtpParameters(rtpParameters), appData(appData) 22 | { 23 | MSC_TRACE(); 24 | } 25 | 26 | const std::string& Producer::GetId() const 27 | { 28 | MSC_TRACE(); 29 | 30 | return this->id; 31 | } 32 | 33 | const std::string& Producer::GetLocalId() const 34 | { 35 | MSC_TRACE(); 36 | 37 | return this->localId; 38 | } 39 | 40 | bool Producer::IsClosed() const 41 | { 42 | MSC_TRACE(); 43 | 44 | return this->closed; 45 | } 46 | 47 | std::string Producer::GetKind() const 48 | { 49 | MSC_TRACE(); 50 | 51 | return this->track->kind(); 52 | } 53 | 54 | webrtc::RtpSenderInterface* Producer::GetRtpSender() const 55 | { 56 | MSC_TRACE(); 57 | 58 | return this->rtpSender; 59 | } 60 | 61 | webrtc::MediaStreamTrackInterface* Producer::GetTrack() const 62 | { 63 | MSC_TRACE(); 64 | 65 | return this->track; 66 | } 67 | 68 | const json& Producer::GetRtpParameters() const 69 | { 70 | MSC_TRACE(); 71 | 72 | return this->rtpParameters; 73 | } 74 | 75 | bool Producer::IsPaused() const 76 | { 77 | MSC_TRACE(); 78 | 79 | return !this->track->enabled(); 80 | } 81 | 82 | uint8_t Producer::GetMaxSpatialLayer() const 83 | { 84 | MSC_TRACE(); 85 | 86 | return this->maxSpatialLayer; 87 | } 88 | 89 | json& Producer::GetAppData() 90 | { 91 | MSC_TRACE(); 92 | 93 | return this->appData; 94 | } 95 | 96 | /** 97 | * Closes the Producer. 98 | */ 99 | void Producer::Close() 100 | { 101 | MSC_TRACE(); 102 | 103 | if (this->closed) 104 | return; 105 | 106 | this->closed = true; 107 | 108 | this->privateListener->OnClose(this); 109 | } 110 | 111 | json Producer::GetStats() const 112 | { 113 | if (this->closed) 114 | MSC_THROW_INVALID_STATE_ERROR("Producer closed"); 115 | 116 | return this->privateListener->OnGetStats(this); 117 | } 118 | 119 | /** 120 | * Pauses sending media. 121 | */ 122 | void Producer::Pause() 123 | { 124 | MSC_TRACE(); 125 | 126 | if (this->closed) 127 | { 128 | MSC_ERROR("Producer closed"); 129 | 130 | return; 131 | } 132 | 133 | this->track->set_enabled(false); 134 | } 135 | 136 | /** 137 | * Resumes sending media. 138 | */ 139 | void Producer::Resume() 140 | { 141 | MSC_TRACE(); 142 | 143 | if (this->closed) 144 | { 145 | MSC_ERROR("Producer closed"); 146 | 147 | return; 148 | } 149 | 150 | this->track->set_enabled(true); 151 | } 152 | 153 | /** 154 | * Replaces the current track with a new one. 155 | */ 156 | void Producer::ReplaceTrack(webrtc::MediaStreamTrackInterface* track) 157 | { 158 | MSC_TRACE(); 159 | 160 | if (this->closed) 161 | MSC_THROW_INVALID_STATE_ERROR("Producer closed"); 162 | else if (track == nullptr) 163 | MSC_THROW_TYPE_ERROR("missing track"); 164 | else if (track->state() == webrtc::MediaStreamTrackInterface::TrackState::kEnded) 165 | MSC_THROW_INVALID_STATE_ERROR("track ended"); 166 | 167 | // Do nothing if this is the same track as the current handled one. 168 | if (track == this->track) 169 | { 170 | MSC_DEBUG("same track, ignored"); 171 | 172 | return; 173 | } 174 | 175 | // May throw. 176 | this->privateListener->OnReplaceTrack(this, track); 177 | 178 | // Keep current paused state. 179 | auto paused = IsPaused(); 180 | 181 | // Set the new track. 182 | this->track = track; 183 | 184 | // If this Producer was paused/resumed and the state of the new 185 | // track does not match, fix it. 186 | if (!paused) 187 | this->track->set_enabled(true); 188 | else 189 | this->track->set_enabled(false); 190 | } 191 | 192 | /** 193 | * Sets the max spatial layer to be sent. 194 | */ 195 | void Producer::SetMaxSpatialLayer(const uint8_t spatialLayer) 196 | { 197 | MSC_TRACE(); 198 | 199 | if (this->closed) 200 | MSC_THROW_INVALID_STATE_ERROR("Producer closed"); 201 | else if (this->track->kind() != "video") 202 | MSC_THROW_TYPE_ERROR("not a video Producer"); 203 | 204 | if (spatialLayer == this->maxSpatialLayer) 205 | return; 206 | 207 | // May throw. 208 | this->privateListener->OnSetMaxSpatialLayer(this, spatialLayer); 209 | 210 | this->maxSpatialLayer = spatialLayer; 211 | } 212 | 213 | /** 214 | * Transport was closed. 215 | */ 216 | void Producer::TransportClosed() 217 | { 218 | MSC_TRACE(); 219 | 220 | if (this->closed) 221 | return; 222 | 223 | this->closed = true; 224 | 225 | this->listener->OnTransportClose(this); 226 | } 227 | } // namespace mediasoupclient 228 | -------------------------------------------------------------------------------- /include/Logger.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Logger facility. 3 | * 4 | * This include file defines logging macros for source files (.cpp). Each 5 | * source file including Logger.hpp MUST define its own MSC_CLASS macro. Include 6 | * files (.hpp) MUST NOT include Logger.hpp. 7 | * 8 | * All the logging macros use the same format as printf(). 9 | * 10 | * If the macro MSC_LOG_FILE_LINE is defied, all the logging macros print more 11 | * verbose information, including current file and line. 12 | * 13 | * MSC_TRACE() 14 | * 15 | * Logs the current method/function if MSC_LOG_TRACE macro is defined and 16 | * logLevel is 'debug'. 17 | * 18 | * MSC_DEBUG(...) 19 | * 20 | * Logs a debug. 21 | * 22 | * MSC_WARN(...) 23 | * 24 | * Logs a warning. 25 | * 26 | * MSC_ERROR(...) 27 | * 28 | * Logs an error. 29 | * 30 | * MSC_DUMP(...) 31 | * 32 | * Logs always to stdout. 33 | * 34 | * MSC_ABORT(...) 35 | * 36 | * Logs the given error to stderr and aborts the process. 37 | * 38 | * MSC_ASSERT(condition, ...) 39 | * 40 | * If the condition is not satisfied, it calls MSC_ABORT(). 41 | */ 42 | 43 | #ifndef MSC_LOGGER_HPP 44 | #define MSC_LOGGER_HPP 45 | 46 | #include // uint8_t 47 | #include // std::snprintf(), std::fprintf(), stdout, stderr 48 | #include // std::abort() 49 | #include 50 | 51 | namespace mediasoupclient 52 | { 53 | class Logger 54 | { 55 | public: 56 | enum class LogLevel : uint8_t 57 | { 58 | LOG_NONE = 0, 59 | LOG_ERROR = 1, 60 | LOG_WARN = 2, 61 | LOG_DEBUG = 3, 62 | LOG_TRACE = 4 63 | }; 64 | 65 | class LogHandlerInterface 66 | { 67 | public: 68 | virtual ~LogHandlerInterface() = default; 69 | virtual void OnLog(LogLevel level, char* payload, size_t len) = 0; 70 | }; 71 | 72 | class DefaultLogHandler : public LogHandlerInterface 73 | { 74 | void OnLog(LogLevel level, char* payload, size_t len) override; 75 | }; 76 | 77 | static void SetLogLevel(LogLevel level); 78 | static void SetHandler(LogHandlerInterface* handler); 79 | static void SetDefaultHandler(); 80 | 81 | public: 82 | static LogLevel logLevel; 83 | static LogHandlerInterface* handler; 84 | static const size_t bufferSize{ 50000 }; 85 | static char buffer[]; 86 | }; 87 | } // namespace mediasoupclient 88 | 89 | // clang-format off 90 | 91 | /* Logging macros. */ 92 | 93 | using Logger = mediasoupclient::Logger; 94 | 95 | #define _MSC_LOG_SEPARATOR_CHAR "\n" 96 | 97 | #ifdef MSC_LOG_FILE_LINE 98 | #define _MSC_LOG_STR " %s:%d | %s::%s()" 99 | #define _MSC_LOG_STR_DESC _MSC_LOG_STR " | " 100 | #define _MSC_FILE (std::strchr(__FILE__, '/') ? std::strchr(__FILE__, '/') + 1 : __FILE__) 101 | #define _MSC_LOG_ARG _MSC_FILE, __LINE__, MSC_CLASS, __FUNCTION__ 102 | #else 103 | #define _MSC_LOG_STR " %s::%s()" 104 | #define _MSC_LOG_STR_DESC _MSC_LOG_STR " | " 105 | #define _MSC_LOG_ARG MSC_CLASS, __FUNCTION__ 106 | #endif 107 | 108 | #ifdef MSC_LOG_TRACE 109 | #define MSC_TRACE() \ 110 | do \ 111 | { \ 112 | if (Logger::handler && Logger::logLevel == Logger::LogLevel::LOG_DEBUG) \ 113 | { \ 114 | int loggerWritten = std::snprintf(Logger::buffer, Logger::bufferSize, "[TRACE]" _MSC_LOG_STR, _MSC_LOG_ARG); \ 115 | Logger::handler->OnLog(Logger::LogLevel::LOG_TRACE, Logger::buffer, loggerWritten); \ 116 | } \ 117 | } \ 118 | while (false) 119 | #else 120 | #define MSC_TRACE() ; 121 | #endif 122 | 123 | #define MSC_DEBUG(desc, ...) \ 124 | do \ 125 | { \ 126 | if (Logger::handler && Logger::logLevel == Logger::LogLevel::LOG_DEBUG) \ 127 | { \ 128 | int loggerWritten = std::snprintf(Logger::buffer, Logger::bufferSize, "[DEBUG]" _MSC_LOG_STR_DESC desc, _MSC_LOG_ARG, ##__VA_ARGS__); \ 129 | Logger::handler->OnLog(Logger::LogLevel::LOG_DEBUG, Logger::buffer, loggerWritten); \ 130 | } \ 131 | } \ 132 | while (false) 133 | 134 | #define MSC_WARN(desc, ...) \ 135 | do \ 136 | { \ 137 | if (Logger::handler && Logger::logLevel >= Logger::LogLevel::LOG_WARN) \ 138 | { \ 139 | int loggerWritten = std::snprintf(Logger::buffer, Logger::bufferSize, "[WARN]" _MSC_LOG_STR_DESC desc, _MSC_LOG_ARG, ##__VA_ARGS__); \ 140 | Logger::handler->OnLog(Logger::LogLevel::LOG_WARN, Logger::buffer, loggerWritten); \ 141 | } \ 142 | } \ 143 | while (false) 144 | 145 | #define MSC_ERROR(desc, ...) \ 146 | do \ 147 | { \ 148 | if (Logger::handler && Logger::logLevel >= Logger::LogLevel::LOG_ERROR) \ 149 | { \ 150 | int loggerWritten = std::snprintf(Logger::buffer, Logger::bufferSize, "[ERROR]" _MSC_LOG_STR_DESC desc, _MSC_LOG_ARG, ##__VA_ARGS__); \ 151 | Logger::handler->OnLog(Logger::LogLevel::LOG_ERROR, Logger::buffer, loggerWritten); \ 152 | } \ 153 | } \ 154 | while (false) 155 | 156 | #define MSC_DUMP(desc, ...) \ 157 | do \ 158 | { \ 159 | std::fprintf(stdout, _MSC_LOG_STR_DESC desc _MSC_LOG_SEPARATOR_CHAR, _MSC_LOG_ARG, ##__VA_ARGS__); \ 160 | std::fflush(stdout); \ 161 | } \ 162 | while (false) 163 | 164 | #define MSC_ABORT(desc, ...) \ 165 | do \ 166 | { \ 167 | std::fprintf(stderr, "[ABORT]" _MSC_LOG_STR_DESC desc _MSC_LOG_SEPARATOR_CHAR, _MSC_LOG_ARG, ##__VA_ARGS__); \ 168 | std::fflush(stderr); \ 169 | std::abort(); \ 170 | } \ 171 | while (false) 172 | 173 | #define MSC_ASSERT(condition, desc, ...) \ 174 | if (!(condition)) \ 175 | { \ 176 | MSC_ABORT("failed assertion `%s': " desc, #condition, ##__VA_ARGS__); \ 177 | } 178 | 179 | // clang-format on 180 | 181 | #endif 182 | -------------------------------------------------------------------------------- /test/include/FakeTransportListener.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MSC_TEST_FAKE_TRANSPORT_LISTENER_HPP 2 | #define MSC_TEST_FAKE_TRANSPORT_LISTENER_HPP 3 | 4 | #include "fakeParameters.hpp" 5 | #include "mediasoupclient.hpp" 6 | #include 7 | 8 | class FakeSendTransportListener : public mediasoupclient::SendTransport::Listener 9 | { 10 | public: 11 | virtual std::future OnProduce( 12 | mediasoupclient::SendTransport* /*transport*/, 13 | const std::string& kind, 14 | nlohmann::json rtpParameters, 15 | const nlohmann::json& appData) override 16 | { 17 | this->onProduceTimesCalled++; 18 | 19 | std::promise promise; 20 | 21 | json producerId; 22 | 23 | this->appData = appData; 24 | 25 | if (kind == "audio") 26 | { 27 | this->audioProducerLocalParameters = rtpParameters; 28 | this->audioProducerId = generateProducerRemoteId(); 29 | producerId = this->audioProducerId; 30 | } 31 | else if (kind == "video") 32 | { 33 | this->videoProducerLocalParameters = rtpParameters; 34 | this->videoProducerId = generateProducerRemoteId(); 35 | producerId = this->videoProducerId; 36 | } 37 | else 38 | { 39 | throw std::runtime_error("Unknown producerLocalParameters[\"kind\"]"); 40 | } 41 | 42 | promise.set_value(producerId); 43 | 44 | return promise.get_future(); 45 | }; 46 | 47 | std::future OnProduceData( 48 | mediasoupclient::SendTransport* /*transport*/, 49 | const nlohmann::json& /*sctpStreamParameters*/, 50 | const std::string& /*label*/, 51 | const std::string& /*protocol*/, 52 | const nlohmann::json& appData) override 53 | { 54 | this->onProduceDataTimesCalled++; 55 | 56 | std::promise promise; 57 | 58 | this->appData = appData; 59 | 60 | // this->audioProducerLocalParameters = rtpParameters; 61 | this->dataProducerId = generateProducerRemoteId(); 62 | 63 | promise.set_value(this->dataProducerId); 64 | 65 | return promise.get_future(); 66 | } 67 | 68 | std::future OnConnect(mediasoupclient::Transport* transport, const json& dtlsParameters) override 69 | { 70 | this->onConnectTimesCalled++; 71 | this->id = transport->GetId(); 72 | this->dtlsParameters = dtlsParameters; 73 | 74 | std::promise promise; 75 | 76 | promise.set_value(); 77 | 78 | return promise.get_future(); 79 | }; 80 | 81 | void OnConnectionStateChange( 82 | mediasoupclient::Transport* transport, const std::string& /*connectionState*/) override 83 | { 84 | this->onConnectionStateChangeTimesCalled++; 85 | }; 86 | 87 | public: 88 | std::string id; 89 | json dtlsParameters; 90 | 91 | json audioProducerLocalParameters; 92 | std::string audioProducerId; 93 | json videoProducerLocalParameters; 94 | std::string videoProducerId; 95 | json appData; 96 | std::string dataProducerId; 97 | std::string dataConsumerId; 98 | 99 | size_t onProduceTimesCalled{ 0 }; 100 | size_t onConnectTimesCalled{ 0 }; 101 | size_t onConnectionStateChangeTimesCalled{ 0 }; 102 | 103 | size_t onProduceExpectedTimesCalled{ 0 }; 104 | size_t onConnectExpectedTimesCalled{ 0 }; 105 | size_t onConnectionStateChangeExpectedTimesCalled{ 0 }; 106 | 107 | size_t onProduceDataTimesCalled{ 0 }; 108 | size_t onProduceDataExpectedTimesCalled{ 0 }; 109 | }; 110 | 111 | class FakeRecvTransportListener : public mediasoupclient::RecvTransport::Listener 112 | { 113 | public: 114 | std::future OnConnect(mediasoupclient::Transport* transport, const json& dtlsParameters) override 115 | { 116 | this->id = transport->GetId(); 117 | this->dtlsParameters = dtlsParameters; 118 | this->onConnectTimesCalled++; 119 | 120 | std::promise promise; 121 | 122 | promise.set_value(); 123 | 124 | return promise.get_future(); 125 | }; 126 | 127 | void OnConnectionStateChange( 128 | mediasoupclient::Transport* transport, const std::string& /*connectionState*/) override 129 | { 130 | this->onConnectionStateChangeTimesCalled++; 131 | }; 132 | 133 | public: 134 | std::string id; 135 | json dtlsParameters; 136 | 137 | size_t onConnectTimesCalled{ 0 }; 138 | size_t onConnectionStateChangeTimesCalled{ 0 }; 139 | 140 | size_t onConnectExpectedTimesCalled{ 0 }; 141 | size_t onConnectionStateChangeExpectedTimesCalled{ 0 }; 142 | }; 143 | 144 | class FakeProducerListener : public mediasoupclient::Producer::Listener, 145 | public mediasoupclient::DataProducer::Listener 146 | { 147 | public: 148 | void OnOpen(mediasoupclient::DataProducer* dataProducer) override 149 | { 150 | } 151 | void OnClose(mediasoupclient::DataProducer* dataProducer) override{}; 152 | void OnBufferedAmountChange( 153 | mediasoupclient::DataProducer* dataProducer, uint64_t sent_data_size) override{}; 154 | 155 | void OnTransportClose(mediasoupclient::DataProducer* /*dataProducer*/) override{}; 156 | 157 | void OnTransportClose(mediasoupclient::Producer* /*producer*/) override 158 | { 159 | this->onTransportCloseTimesCalled++; 160 | } 161 | 162 | public: 163 | size_t onTransportCloseTimesCalled{ 0 }; 164 | size_t onTransportCloseExpectedTimesCalled{ 0 }; 165 | }; 166 | 167 | class FakeConsumerListener : public mediasoupclient::Consumer::Listener 168 | { 169 | public: 170 | void OnTransportClose(mediasoupclient::Consumer* /*consumer*/) override 171 | { 172 | this->onTransportCloseTimesCalled++; 173 | } 174 | 175 | public: 176 | size_t onTransportCloseTimesCalled{ 0 }; 177 | size_t onTransportCloseExpectedTimesCalled{ 0 }; 178 | }; 179 | 180 | #endif 181 | -------------------------------------------------------------------------------- /include/Handler.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MSC_HANDLER_HPP 2 | #define MSC_HANDLER_HPP 3 | 4 | #include "PeerConnection.hpp" 5 | #include "sdp/RemoteSdp.hpp" 6 | #include 7 | #include // webrtc::MediaStreamTrackInterface 8 | #include // webrtc::PeerConnectionInterface 9 | #include // webrtc::RtpEncodingParameters 10 | #include // webrtc::RtpReceiverInterface 11 | #include // webrtc::RtpSenderInterface 12 | #include // webrtc::RtpTransceiverInterface 13 | #include 14 | #include 15 | 16 | namespace mediasoupclient 17 | { 18 | class Handler : public PeerConnection::PrivateListener 19 | { 20 | public: 21 | class PrivateListener 22 | { 23 | public: 24 | virtual ~PrivateListener() = default; 25 | virtual void OnConnect(nlohmann::json& dtlsParameters) = 0; 26 | virtual void OnConnectionStateChange( 27 | webrtc::PeerConnectionInterface::IceConnectionState connectionState) = 0; 28 | }; 29 | 30 | public: 31 | struct DataChannel 32 | { 33 | webrtc::scoped_refptr dataChannel; 34 | nlohmann::json sctpStreamParameters; 35 | }; 36 | 37 | public: 38 | static nlohmann::json GetNativeRtpCapabilities( 39 | const PeerConnection::Options* peerConnectionOptions = nullptr); 40 | static nlohmann::json GetNativeSctpCapabilities(); 41 | 42 | public: 43 | explicit Handler( 44 | PrivateListener* privateListener, 45 | const nlohmann::json& iceParameters, 46 | const nlohmann::json& iceCandidates, 47 | const nlohmann::json& dtlsParameters, 48 | const nlohmann::json& sctpParameters, 49 | const PeerConnection::Options* peerConnectionOptions); 50 | 51 | public: 52 | void Close(); 53 | nlohmann::json GetTransportStats(); 54 | void UpdateIceServers(const nlohmann::json& iceServerUris); 55 | virtual void RestartIce(const nlohmann::json& iceParameters) = 0; 56 | 57 | protected: 58 | void SetupTransport(const std::string& localDtlsRole, nlohmann::json& localSdpObject); 59 | 60 | /* Methods inherited from PeerConnectionListener. */ 61 | public: 62 | void OnIceConnectionChange(webrtc::PeerConnectionInterface::IceConnectionState newState) override; 63 | 64 | protected: 65 | // PrivateListener instance. 66 | PrivateListener* privateListener{ nullptr }; 67 | // Remote SDP instance. 68 | std::unique_ptr remoteSdp{ nullptr }; 69 | // Got transport local and remote parameters. 70 | bool transportReady{ false }; 71 | // Map of RTCTransceivers indexed by MID. 72 | std::unordered_map> 73 | mapMidTransceiver{}; 74 | // PeerConnection instance. 75 | std::unique_ptr pc{ nullptr }; 76 | bool hasDataChannelMediaSection = false; 77 | uint32_t nextSendSctpStreamId = 0; 78 | // Initial server side DTLS role. If not 'auto', it will force the opposite 79 | // value in client side. 80 | std::string forcedLocalDtlsRole; 81 | }; 82 | 83 | class SendHandler : public Handler 84 | { 85 | public: 86 | struct SendResult 87 | { 88 | std::string localId; 89 | webrtc::RtpSenderInterface* rtpSender{ nullptr }; 90 | nlohmann::json rtpParameters; 91 | }; 92 | 93 | public: 94 | SendHandler( 95 | Handler::PrivateListener* privateListener, 96 | const nlohmann::json& iceParameters, 97 | const nlohmann::json& iceCandidates, 98 | const nlohmann::json& dtlsParameters, 99 | const nlohmann::json& sctpParameters, 100 | const PeerConnection::Options* peerConnectionOptions, 101 | const std::function getSendExtendedRtpCapabilities); 102 | 103 | public: 104 | SendResult Send( 105 | webrtc::MediaStreamTrackInterface* track, 106 | std::vector* encodings, 107 | const nlohmann::json* codecOptions, 108 | const nlohmann::json* codec); 109 | void StopSending(const std::string& localId); 110 | void ReplaceTrack(const std::string& localId, webrtc::MediaStreamTrackInterface* track); 111 | void SetMaxSpatialLayer(const std::string& localId, uint8_t spatialLayer); 112 | nlohmann::json GetSenderStats(const std::string& localId); 113 | void RestartIce(const nlohmann::json& iceParameters) override; 114 | DataChannel SendDataChannel(const std::string& label, webrtc::DataChannelInit dataChannelInit); 115 | 116 | private: 117 | const std::function getSendExtendedRtpCapabilities; 118 | }; 119 | 120 | class RecvHandler : public Handler 121 | { 122 | public: 123 | struct RecvResult 124 | { 125 | std::string localId; 126 | webrtc::RtpReceiverInterface* rtpReceiver{ nullptr }; 127 | webrtc::MediaStreamTrackInterface* track{ nullptr }; 128 | }; 129 | 130 | public: 131 | RecvHandler( 132 | Handler::PrivateListener* privateListener, 133 | const nlohmann::json& iceParameters, 134 | const nlohmann::json& iceCandidates, 135 | const nlohmann::json& dtlsParameters, 136 | const nlohmann::json& sctpParameters, 137 | const PeerConnection::Options* peerConnectionOptions); 138 | 139 | RecvResult Receive( 140 | const std::string& id, const std::string& kind, const nlohmann::json* rtpParameters); 141 | void StopReceiving(const std::string& localId); 142 | nlohmann::json GetReceiverStats(const std::string& localId); 143 | void RestartIce(const nlohmann::json& iceParameters) override; 144 | DataChannel ReceiveDataChannel(const std::string& label, webrtc::DataChannelInit dataChannelInit); 145 | }; 146 | } // namespace mediasoupclient 147 | 148 | #endif 149 | -------------------------------------------------------------------------------- /test/data/audio_video.sdp: -------------------------------------------------------------------------------- 1 | v=0 2 | o=- 7215835315300292143 2 IN IP4 127.0.0.1 3 | s=- 4 | t=0 0 5 | a=group:BUNDLE audio video 6 | a=msid-semantic: WMS D8ciewK6O21HF2fvUTMLcPH3Oej7O5g1sQhi 7 | m=audio 54379 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126 8 | c=IN IP4 186.101.218.61 9 | a=rtcp:64803 IN IP4 186.101.218.61 10 | a=candidate:1362583084 1 udp 2122260223 192.168.3.117 54379 typ host generation 0 network-id 1 network-cost 10 11 | a=candidate:1362583084 2 udp 2122260222 192.168.3.117 64803 typ host generation 0 network-id 1 network-cost 10 12 | a=candidate:531931868 1 tcp 1518280447 192.168.3.117 9 typ host tcptype active generation 0 network-id 1 network-cost 10 13 | a=candidate:531931868 2 tcp 1518280446 192.168.3.117 9 typ host tcptype active generation 0 network-id 1 network-cost 10 14 | a=candidate:3498006680 1 udp 1686052607 186.101.218.61 54379 typ srflx raddr 192.168.3.117 rport 54379 generation 0 network-id 1 network-cost 10 15 | a=candidate:3498006680 2 udp 1686052606 186.101.218.61 64803 typ srflx raddr 192.168.3.117 rport 64803 generation 0 network-id 1 network-cost 10 16 | a=ice-ufrag:TEvw 17 | a=ice-pwd:JhC25WE1d4bsDRIWu1KCR7Xa 18 | a=ice-options:trickle 19 | a=fingerprint:sha-256 C9:48:B3:AB:4E:58:98:35:F9:85:96:19:6B:C2:6A:CA:AB:D1:DE:A2:0B:AF:CD:C6:DB:6E:EF:BB:6F:1A:11:40 20 | a=setup:actpass 21 | a=mid:audio 22 | a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level 23 | a=sendrecv 24 | a=rtcp-mux 25 | a=rtpmap:111 opus/48000/2 26 | a=rtcp-fb:111 transport-cc 27 | a=fmtp:111 minptime=10;useinbandfec=1 28 | a=rtpmap:103 ISAC/16000 29 | a=rtpmap:104 ISAC/32000 30 | a=rtpmap:9 G722/8000 31 | a=rtpmap:0 PCMU/8000 32 | a=rtpmap:8 PCMA/8000 33 | a=rtpmap:106 CN/32000 34 | a=rtpmap:105 CN/16000 35 | a=rtpmap:13 CN/8000 36 | a=rtpmap:110 telephone-event/48000 37 | a=rtpmap:112 telephone-event/32000 38 | a=rtpmap:113 telephone-event/16000 39 | a=rtpmap:126 telephone-event/8000 40 | a=ssrc:2839725608 cname:i0gPhMzBdw0sn7Oq 41 | a=ssrc:2839725608 msid:D8ciewK6O21HF2fvUTMLcPH3Oej7O5g1sQhi 37af76fe-4280-4922-ac22-2f4067db9545 42 | a=ssrc:2839725608 mslabel:D8ciewK6O21HF2fvUTMLcPH3Oej7O5g1sQhi 43 | a=ssrc:2839725608 label:37af76fe-4280-4922-ac22-2f4067db9545 44 | m=video 62679 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 123 127 122 125 107 108 109 124 45 | c=IN IP4 186.101.218.61 46 | a=rtcp:61795 IN IP4 186.101.218.61 47 | a=candidate:1362583084 1 udp 2122260223 192.168.3.117 62679 typ host generation 0 network-id 1 network-cost 10 48 | a=candidate:1362583084 2 udp 2122260222 192.168.3.117 61795 typ host generation 0 network-id 1 network-cost 10 49 | a=candidate:531931868 1 tcp 1518280447 192.168.3.117 9 typ host tcptype active generation 0 network-id 1 network-cost 10 50 | a=candidate:531931868 2 tcp 1518280446 192.168.3.117 9 typ host tcptype active generation 0 network-id 1 network-cost 10 51 | a=candidate:3498006680 2 udp 1686052606 186.101.218.61 61795 typ srflx raddr 192.168.3.117 rport 61795 generation 0 network-id 1 network-cost 10 52 | a=candidate:3498006680 1 udp 1686052607 186.101.218.61 62679 typ srflx raddr 192.168.3.117 rport 62679 generation 0 network-id 1 network-cost 10 53 | a=ice-ufrag:TEvw 54 | a=ice-pwd:JhC25WE1d4bsDRIWu1KCR7Xa 55 | a=ice-options:trickle 56 | a=fingerprint:sha-256 C9:48:B3:AB:4E:58:98:35:F9:85:96:19:6B:C2:6A:CA:AB:D1:DE:A2:0B:AF:CD:C6:DB:6E:EF:BB:6F:1A:11:40 57 | a=setup:actpass 58 | a=mid:video 59 | a=extmap:2 urn:ietf:params:rtp-hdrext:toffset 60 | a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time 61 | a=extmap:4 urn:3gpp:video-orientation 62 | a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 63 | a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay 64 | a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type 65 | a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/video-timing 66 | a=extmap:10 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07 67 | a=sendrecv 68 | a=rtcp-mux 69 | a=rtcp-rsize 70 | a=rtpmap:96 VP8/90000 71 | a=rtcp-fb:96 goog-remb 72 | a=rtcp-fb:96 transport-cc 73 | a=rtcp-fb:96 ccm fir 74 | a=rtcp-fb:96 nack 75 | a=rtcp-fb:96 nack pli 76 | a=rtpmap:97 rtx/90000 77 | a=fmtp:97 apt=96 78 | a=rtpmap:98 VP9/90000 79 | a=rtcp-fb:98 goog-remb 80 | a=rtcp-fb:98 transport-cc 81 | a=rtcp-fb:98 ccm fir 82 | a=rtcp-fb:98 nack 83 | a=rtcp-fb:98 nack pli 84 | a=fmtp:98 profile-id=0 85 | a=rtpmap:99 rtx/90000 86 | a=fmtp:99 apt=98 87 | a=rtpmap:100 H264/90000 88 | a=rtcp-fb:100 goog-remb 89 | a=rtcp-fb:100 transport-cc 90 | a=rtcp-fb:100 ccm fir 91 | a=rtcp-fb:100 nack 92 | a=rtcp-fb:100 nack pli 93 | a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f 94 | a=rtpmap:101 rtx/90000 95 | a=fmtp:101 apt=100 96 | a=rtpmap:102 H264/90000 97 | a=rtcp-fb:102 goog-remb 98 | a=rtcp-fb:102 transport-cc 99 | a=rtcp-fb:102 ccm fir 100 | a=rtcp-fb:102 nack 101 | a=rtcp-fb:102 nack pli 102 | a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f 103 | a=rtpmap:123 rtx/90000 104 | a=fmtp:123 apt=102 105 | a=rtpmap:127 H264/90000 106 | a=rtcp-fb:127 goog-remb 107 | a=rtcp-fb:127 transport-cc 108 | a=rtcp-fb:127 ccm fir 109 | a=rtcp-fb:127 nack 110 | a=rtcp-fb:127 nack pli 111 | a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f 112 | a=rtpmap:122 rtx/90000 113 | a=fmtp:122 apt=127 114 | a=rtpmap:125 H264/90000 115 | a=rtcp-fb:125 goog-remb 116 | a=rtcp-fb:125 transport-cc 117 | a=rtcp-fb:125 ccm fir 118 | a=rtcp-fb:125 nack 119 | a=rtcp-fb:125 nack pli 120 | a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f 121 | a=rtpmap:107 rtx/90000 122 | a=fmtp:107 apt=125 123 | a=rtpmap:108 red/90000 124 | a=rtpmap:109 rtx/90000 125 | a=fmtp:109 apt=108 126 | a=rtpmap:124 ulpfec/90000 127 | a=ssrc-group:FID 2682047759 3197660117 128 | a=ssrc:2682047759 cname:i0gPhMzBdw0sn7Oq 129 | a=ssrc:2682047759 msid:D8ciewK6O21HF2fvUTMLcPH3Oej7O5g1sQhi 644d938d-e474-4bed-af3e-dda670b46dcf 130 | a=ssrc:2682047759 mslabel:D8ciewK6O21HF2fvUTMLcPH3Oej7O5g1sQhi 131 | a=ssrc:2682047759 label:644d938d-e474-4bed-af3e-dda670b46dcf 132 | a=ssrc:3197660117 cname:i0gPhMzBdw0sn7Oq 133 | a=ssrc:3197660117 msid:D8ciewK6O21HF2fvUTMLcPH3Oej7O5g1sQhi 644d938d-e474-4bed-af3e-dda670b46dcf 134 | a=ssrc:3197660117 mslabel:D8ciewK6O21HF2fvUTMLcPH3Oej7O5g1sQhi 135 | a=ssrc:3197660117 label:644d938d-e474-4bed-af3e-dda670b46dcf 136 | -------------------------------------------------------------------------------- /src/Device.cpp: -------------------------------------------------------------------------------- 1 | #include "Transport.hpp" 2 | #define MSC_CLASS "Device" 3 | 4 | #include "Device.hpp" 5 | #include "Logger.hpp" 6 | #include "MediaSoupClientErrors.hpp" 7 | #include "ortc.hpp" 8 | 9 | using json = nlohmann::json; 10 | 11 | namespace mediasoupclient 12 | { 13 | /** 14 | * Whether the Device is loaded. 15 | */ 16 | bool Device::IsLoaded() const 17 | { 18 | MSC_TRACE(); 19 | 20 | return this->loaded; 21 | } 22 | 23 | /** 24 | * RTP capabilities of the Device for receiving media. 25 | */ 26 | const json& Device::GetRtpCapabilities() const 27 | { 28 | MSC_TRACE(); 29 | 30 | if (!this->loaded) 31 | MSC_THROW_INVALID_STATE_ERROR("not loaded"); 32 | 33 | return this->recvRtpCapabilities; 34 | } 35 | 36 | /** 37 | * SCTP capabilities of the Device for receiving media. 38 | */ 39 | const json& Device::GetSctpCapabilities() const 40 | { 41 | MSC_TRACE(); 42 | 43 | if (!this->loaded) 44 | MSC_THROW_INVALID_STATE_ERROR("not loaded"); 45 | 46 | return this->sctpCapabilities; 47 | } 48 | 49 | /** 50 | * Initialize the Device. 51 | */ 52 | void Device::Load(json routerRtpCapabilities, const PeerConnection::Options* peerConnectionOptions) 53 | { 54 | MSC_TRACE(); 55 | 56 | if (this->loaded) 57 | MSC_THROW_INVALID_STATE_ERROR("already loaded"); 58 | 59 | // This may throw. 60 | ortc::validateRtpCapabilities(routerRtpCapabilities); 61 | 62 | // Get Native RTP capabilities. 63 | auto nativeRtpCapabilities = Handler::GetNativeRtpCapabilities(peerConnectionOptions); 64 | 65 | MSC_DEBUG("got native RTP capabilities:\n%s", nativeRtpCapabilities.dump(4).c_str()); 66 | 67 | // This may throw. 68 | ortc::validateRtpCapabilities(nativeRtpCapabilities); 69 | 70 | // Get extended RTP capabilities as a function, that SendHandler can invoke to compute the matching capabilities from the current local capabilities. 71 | // This is required for WebRTC M140+ where header extension ids may differ from the previosly pre-computed sendExtendedRtpCapabilities, resulting in 72 | // failure when setting the generated SDP answer as RemoteDescription, since the header extension ids in the answer do not match those in the offer. 73 | // See: https://github.com/versatica/mediasoup-client/pull/336 for the JS counterpart and more rationale. 74 | this->getSendExtendedRtpCapabilities = [routerRtpCapabilities](json& currentLocalRtpCapabilities) { 75 | auto routerRtpCapabilitiesCopy = routerRtpCapabilities; 76 | return ortc::getExtendedRtpCapabilities(currentLocalRtpCapabilities, routerRtpCapabilitiesCopy); 77 | }; 78 | const auto recvExtendedRtpCapabilities = 79 | ortc::getExtendedRtpCapabilities(nativeRtpCapabilities, routerRtpCapabilities); 80 | 81 | // Check whether we can produce audio/video. 82 | this->canProduceByKind["audio"] = ortc::canSend("audio", recvExtendedRtpCapabilities); 83 | this->canProduceByKind["video"] = ortc::canSend("video", recvExtendedRtpCapabilities); 84 | 85 | // Generate our receiving RTP capabilities for receiving media. 86 | this->recvRtpCapabilities = ortc::getRecvRtpCapabilities(recvExtendedRtpCapabilities); 87 | 88 | // This may throw. 89 | ortc::validateRtpCapabilities(this->recvRtpCapabilities); 90 | 91 | MSC_DEBUG("got receiving RTP capabilities:\n%s", this->recvRtpCapabilities.dump(4).c_str()); 92 | 93 | // Generate our SCTP capabilities. 94 | this->sctpCapabilities = Handler::GetNativeSctpCapabilities(); 95 | 96 | // This may throw. 97 | ortc::validateSctpCapabilities(this->sctpCapabilities); 98 | 99 | MSC_DEBUG("got receiving SCTP capabilities:\n%s", this->sctpCapabilities.dump(4).c_str()); 100 | 101 | 102 | MSC_DEBUG("succeeded"); 103 | 104 | this->loaded = true; 105 | } 106 | 107 | /** 108 | * Whether we can produce audio/video. 109 | * 110 | */ 111 | bool Device::CanProduce(const std::string& kind) 112 | { 113 | MSC_TRACE(); 114 | 115 | if (!this->loaded) 116 | MSC_THROW_INVALID_STATE_ERROR("not loaded"); 117 | else if (kind != "audio" && kind != "video") 118 | MSC_THROW_TYPE_ERROR("invalid kind"); 119 | 120 | return this->canProduceByKind[kind]; 121 | } 122 | 123 | SendTransport* Device::CreateSendTransport( 124 | SendTransport::Listener* listener, 125 | const std::string& id, 126 | const json& iceParameters, 127 | const json& iceCandidates, 128 | const json& dtlsParameters, 129 | const json& sctpParameters, 130 | const PeerConnection::Options* peerConnectionOptions, 131 | const json& appData) const 132 | { 133 | MSC_TRACE(); 134 | 135 | if (!this->loaded) 136 | MSC_THROW_INVALID_STATE_ERROR("not loaded"); 137 | else if (!appData.is_object()) 138 | MSC_THROW_TYPE_ERROR("appData must be a JSON object"); 139 | 140 | // Validate arguments. 141 | ortc::validateIceParameters(const_cast(iceParameters)); 142 | ortc::validateIceCandidates(const_cast(iceCandidates)); 143 | ortc::validateDtlsParameters(const_cast(dtlsParameters)); 144 | 145 | if (!sctpParameters.is_null()) 146 | ortc::validateSctpParameters(const_cast(sctpParameters)); 147 | 148 | // Create a new Transport. 149 | auto* transport = new SendTransport( 150 | listener, 151 | id, 152 | iceParameters, 153 | iceCandidates, 154 | dtlsParameters, 155 | sctpParameters, 156 | peerConnectionOptions, 157 | this->getSendExtendedRtpCapabilities, 158 | &this->canProduceByKind, 159 | appData); 160 | 161 | return transport; 162 | } 163 | 164 | SendTransport* Device::CreateSendTransport( 165 | SendTransport::Listener* listener, 166 | const std::string& id, 167 | const json& iceParameters, 168 | const json& iceCandidates, 169 | const json& dtlsParameters, 170 | const PeerConnection::Options* peerConnectionOptions, 171 | const json& appData) const 172 | { 173 | MSC_TRACE(); 174 | 175 | return Device::CreateSendTransport( 176 | listener, id, iceParameters, iceCandidates, dtlsParameters, nullptr, peerConnectionOptions, appData); 177 | } 178 | 179 | RecvTransport* Device::CreateRecvTransport( 180 | RecvTransport::Listener* listener, 181 | const std::string& id, 182 | const json& iceParameters, 183 | const json& iceCandidates, 184 | const json& dtlsParameters, 185 | const json& sctpParameters, 186 | const PeerConnection::Options* peerConnectionOptions, 187 | const json& appData) const 188 | { 189 | MSC_TRACE(); 190 | 191 | if (!this->loaded) 192 | MSC_THROW_INVALID_STATE_ERROR("not loaded"); 193 | else if (!appData.is_object()) 194 | MSC_THROW_TYPE_ERROR("appData must be a JSON object"); 195 | 196 | // Validate arguments. 197 | ortc::validateIceParameters(const_cast(iceParameters)); 198 | ortc::validateIceCandidates(const_cast(iceCandidates)); 199 | ortc::validateDtlsParameters(const_cast(dtlsParameters)); 200 | 201 | if (!sctpParameters.is_null()) 202 | ortc::validateSctpParameters(const_cast(sctpParameters)); 203 | 204 | // Create a new Transport. 205 | auto* transport = new RecvTransport( 206 | listener, 207 | id, 208 | iceParameters, 209 | iceCandidates, 210 | dtlsParameters, 211 | sctpParameters, 212 | peerConnectionOptions, 213 | &this->recvRtpCapabilities, 214 | appData); 215 | 216 | return transport; 217 | } 218 | 219 | RecvTransport* Device::CreateRecvTransport( 220 | RecvTransport::Listener* listener, 221 | const std::string& id, 222 | const json& iceParameters, 223 | const json& iceCandidates, 224 | const json& dtlsParameters, 225 | const PeerConnection::Options* peerConnectionOptions, 226 | const json& appData) const 227 | { 228 | MSC_TRACE(); 229 | 230 | return Device::CreateRecvTransport( 231 | listener, id, iceParameters, iceCandidates, dtlsParameters, nullptr, peerConnectionOptions, appData); 232 | } 233 | } // namespace mediasoupclient 234 | -------------------------------------------------------------------------------- /test/src/Handler.test.cpp: -------------------------------------------------------------------------------- 1 | #include "Handler.hpp" 2 | #include "MediaSoupClientErrors.hpp" 3 | #include "MediaStreamTrackFactory.hpp" 4 | #include "fakeParameters.hpp" 5 | #include "ortc.hpp" 6 | #include 7 | #include 8 | #include 9 | 10 | static const json TransportRemoteParameters = generateTransportRemoteParameters(); 11 | static const json RtpParametersByKind = generateRtpParametersByKind(); 12 | static const json RouterRtpCapabilities = generateRouterRtpCapabilities(); 13 | static const auto getSendCapabilities = [](json& currentLocalRtpCapabilities) { 14 | auto routerRtpCapabilitiesCopy = RouterRtpCapabilities; 15 | return mediasoupclient::ortc::getExtendedRtpCapabilities(currentLocalRtpCapabilities, routerRtpCapabilitiesCopy); 16 | }; 17 | 18 | class FakeHandlerListener : public mediasoupclient::Handler::PrivateListener 19 | { 20 | public: 21 | void OnConnect(json& /*transportLocalParameters*/) override{}; 22 | 23 | void OnConnectionStateChange( 24 | webrtc::PeerConnectionInterface::IceConnectionState /*connectionState*/) override{}; 25 | }; 26 | 27 | TEST_CASE("Handler", "[Handler]") 28 | { 29 | MediaStreamTrackFactory& singleton = MediaStreamTrackFactory::getInstance(); 30 | SECTION("Handler::GetNativeRtpCapabilities() succeeds") 31 | { 32 | json rtpCapabilities; 33 | 34 | REQUIRE_NOTHROW( 35 | rtpCapabilities = 36 | mediasoupclient::Handler::GetNativeRtpCapabilities(&singleton.PeerConnectionOptions)); 37 | 38 | REQUIRE(rtpCapabilities["codecs"].is_array()); 39 | REQUIRE(rtpCapabilities["fecMechanisms"].is_array()); 40 | REQUIRE(rtpCapabilities["headerExtensions"].is_array()); 41 | } 42 | } 43 | 44 | TEST_CASE("SendHandler", "[Handler][SendHandler]") 45 | { 46 | static FakeHandlerListener handlerListener; 47 | MediaStreamTrackFactory& singleton = MediaStreamTrackFactory::getInstance(); 48 | 49 | static mediasoupclient::SendHandler sendHandler( 50 | &handlerListener, 51 | TransportRemoteParameters["iceParameters"], 52 | TransportRemoteParameters["iceCandidates"], 53 | TransportRemoteParameters["dtlsParameters"], 54 | TransportRemoteParameters["sctpParameters"], 55 | &singleton.PeerConnectionOptions, 56 | getSendCapabilities); 57 | 58 | static webrtc::scoped_refptr track; 59 | 60 | static std::string localId; 61 | 62 | SECTION("sendHandler.Send() fails if a null track is provided") 63 | { 64 | REQUIRE_THROWS_AS(sendHandler.Send(nullptr, nullptr, nullptr, nullptr), MediaSoupClientError); 65 | } 66 | 67 | SECTION("sendHandler.Send() succeeds if a track is provided") 68 | { 69 | track = createAudioTrack("test-track-id"); 70 | 71 | mediasoupclient::SendHandler::SendResult sendResult; 72 | 73 | REQUIRE_NOTHROW(sendResult = sendHandler.Send(track.get(), nullptr, nullptr, nullptr)); 74 | 75 | localId = sendResult.localId; 76 | 77 | REQUIRE(sendResult.rtpParameters["codecs"].size() == 1); 78 | REQUIRE(sendResult.rtpParameters["headerExtensions"].size() == 3); 79 | } 80 | 81 | SECTION("sendHandler.Send() succeeds if track is already handled") 82 | { 83 | REQUIRE_NOTHROW(sendHandler.Send(track.get(), nullptr, nullptr, nullptr)); 84 | } 85 | 86 | SECTION("sendHandler.ReplaceTrack() fails if an invalid localId is provided") 87 | { 88 | REQUIRE_THROWS_AS(sendHandler.ReplaceTrack("", nullptr), MediaSoupClientError); 89 | } 90 | 91 | SECTION("sendHandler.ReplaceTrack() succeeds if a new track is provided") 92 | { 93 | auto newTrack = createAudioTrack("test-new-track-id"); 94 | 95 | REQUIRE_NOTHROW(sendHandler.ReplaceTrack(localId, newTrack.get())); 96 | 97 | track = newTrack; 98 | } 99 | 100 | SECTION("sendHandler.SetMaxSpatialLayer() fails if invalid localId is provided") 101 | { 102 | REQUIRE_THROWS_AS(sendHandler.SetMaxSpatialLayer("", 1), MediaSoupClientError); 103 | } 104 | 105 | SECTION("sendHandler.SetMaxSpatialLayer() succeeds if track is being sent") 106 | { 107 | REQUIRE_NOTHROW(sendHandler.SetMaxSpatialLayer(localId, 1)); 108 | } 109 | 110 | SECTION("sendHandler.GetSenderStats() fails if invalid localId is provided") 111 | { 112 | REQUIRE_THROWS_AS(sendHandler.GetSenderStats(""), MediaSoupClientError); 113 | } 114 | 115 | SECTION("sendHandler.GetSenderStats() succeeds if track is being sent") 116 | { 117 | REQUIRE_NOTHROW(sendHandler.GetSenderStats(localId)); 118 | } 119 | 120 | SECTION("sendHandler.StopSending() fails if an invalid localId is provided") 121 | { 122 | REQUIRE_THROWS_AS(sendHandler.StopSending(""), MediaSoupClientError); 123 | } 124 | 125 | SECTION("sendHandler.Sends() succeeds after stopping if track if provided") 126 | { 127 | mediasoupclient::SendHandler::SendResult sendResult; 128 | 129 | REQUIRE_NOTHROW(sendResult = sendHandler.Send(track.get(), nullptr, nullptr, nullptr)); 130 | 131 | localId = sendResult.localId; 132 | } 133 | 134 | SECTION("sendHandler.StopSending() succeeds if track is being sent") 135 | { 136 | REQUIRE_NOTHROW(sendHandler.StopSending(localId)); 137 | } 138 | 139 | SECTION("sendHandler.RestartIce() succeeds") 140 | { 141 | auto iceParameters = TransportRemoteParameters["iceParameters"]; 142 | 143 | REQUIRE_NOTHROW(sendHandler.RestartIce(iceParameters)); 144 | } 145 | 146 | SECTION("sendHandler.UpdateIceServers() succeeds") 147 | { 148 | REQUIRE_NOTHROW(sendHandler.UpdateIceServers(json::array())); 149 | sendHandler.Close(); 150 | } 151 | } 152 | 153 | TEST_CASE("RecvHandler", "[Handler][RecvHandler]") 154 | { 155 | auto consumerRemoteParameters = generateConsumerRemoteParameters("audio/opus"); 156 | auto producerId = consumerRemoteParameters["producerId"].get(); 157 | auto id = consumerRemoteParameters["id"].get(); 158 | auto kind = consumerRemoteParameters["kind"].get(); 159 | auto rtpParameters = consumerRemoteParameters["rtpParameters"]; 160 | 161 | static std::string localId; 162 | 163 | static FakeHandlerListener handlerListener; 164 | MediaStreamTrackFactory& singleton = MediaStreamTrackFactory::getInstance(); 165 | 166 | static mediasoupclient::RecvHandler recvHandler( 167 | &handlerListener, 168 | TransportRemoteParameters["iceParameters"], 169 | TransportRemoteParameters["iceCandidates"], 170 | TransportRemoteParameters["dtlsParameters"], 171 | TransportRemoteParameters["sctpParameters"], 172 | &singleton.PeerConnectionOptions); 173 | 174 | SECTION("recvHander.Receive() succeeds if correct rtpParameters are provided") 175 | { 176 | mediasoupclient::RecvHandler::RecvResult recvResult; 177 | 178 | REQUIRE_NOTHROW(recvResult = recvHandler.Receive("test", "audio", &rtpParameters)); 179 | 180 | localId = recvResult.localId; 181 | } 182 | 183 | SECTION("recvHandler.GetReceiverStats() fails if unknown receiver id is provided") 184 | { 185 | REQUIRE_THROWS_AS(recvHandler.GetReceiverStats("unknown"), MediaSoupClientError); 186 | } 187 | 188 | SECTION("recvHandler.GetReceiverStats() succeeds if known receiver id is provided") 189 | { 190 | REQUIRE_NOTHROW(recvHandler.GetReceiverStats(localId)); 191 | } 192 | 193 | SECTION("recvHandler.StopReceiving() fails if unknown receiver id is provided") 194 | { 195 | REQUIRE_THROWS_AS(recvHandler.StopReceiving("unknown"), MediaSoupClientError); 196 | } 197 | 198 | SECTION("recvHandler.StopReceiving() succeeds if known receiver id is provided") 199 | { 200 | REQUIRE_NOTHROW(recvHandler.StopReceiving(localId)); 201 | } 202 | 203 | SECTION("recvHandler.RestartIce() succeeds") 204 | { 205 | auto iceParameters = TransportRemoteParameters["iceParameters"]; 206 | 207 | REQUIRE_NOTHROW(recvHandler.RestartIce(iceParameters)); 208 | } 209 | 210 | SECTION("recvHandler.UpdateIceServers() succeeds") 211 | { 212 | REQUIRE_NOTHROW(recvHandler.UpdateIceServers(json::array())); 213 | recvHandler.Close(); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /include/PeerConnection.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MSC_PEERCONNECTION_HPP 2 | #define MSC_PEERCONNECTION_HPP 3 | 4 | #include 5 | #include // webrtc::PeerConnectionInterface 6 | #include // std::promise, std::future 7 | #include // std::unique_ptr 8 | 9 | namespace mediasoupclient 10 | { 11 | class PeerConnection 12 | { 13 | public: 14 | enum class SdpType : uint8_t 15 | { 16 | OFFER = 0, 17 | PRANSWER, 18 | ANSWER 19 | }; 20 | 21 | static std::map sdpType2webRtcSdpType; 22 | static std::map 23 | iceConnectionState2String; 24 | static std::map 25 | iceGatheringState2String; 26 | static std::map signalingState2String; 27 | 28 | public: 29 | class PrivateListener : public webrtc::PeerConnectionObserver 30 | { 31 | /* Virtual methods inherited from PeerConnectionObserver. */ 32 | public: 33 | void OnSignalingChange(webrtc::PeerConnectionInterface::SignalingState newState) override; 34 | void OnAddStream(webrtc::scoped_refptr stream) override; 35 | void OnRemoveStream(webrtc::scoped_refptr stream) override; 36 | void OnDataChannel(webrtc::scoped_refptr dataChannel) override; 37 | void OnRenegotiationNeeded() override; 38 | void OnIceConnectionChange(webrtc::PeerConnectionInterface::IceConnectionState newState) override; 39 | void OnIceGatheringChange(webrtc::PeerConnectionInterface::IceGatheringState newState) override; 40 | void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override; 41 | void OnIceCandidatesRemoved(const std::vector& candidates) override; 42 | void OnIceConnectionReceivingChange(bool receiving) override; 43 | void OnAddTrack( 44 | webrtc::scoped_refptr receiver, 45 | const std::vector>& streams) override; 46 | void OnTrack(webrtc::scoped_refptr transceiver) override; 47 | void OnRemoveTrack(webrtc::scoped_refptr receiver) override; 48 | void OnInterestingUsage(int usagePattern) override; 49 | }; 50 | 51 | class SetLocalDescriptionObserver : public webrtc::SetLocalDescriptionObserverInterface 52 | { 53 | public: 54 | SetLocalDescriptionObserver() = default; 55 | ~SetLocalDescriptionObserver() override = default; 56 | 57 | std::future GetFuture(); 58 | void Reject(const std::string& error); 59 | 60 | /* Virtual methods inherited from webrtc::SetLocalDescriptionObserver. */ 61 | public: 62 | void OnSetLocalDescriptionComplete(webrtc::RTCError error) override; 63 | 64 | private: 65 | std::promise promise; 66 | }; 67 | 68 | class SetRemoteDescriptionObserver : public webrtc::SetRemoteDescriptionObserverInterface 69 | { 70 | public: 71 | SetRemoteDescriptionObserver() = default; 72 | ~SetRemoteDescriptionObserver() override = default; 73 | 74 | std::future GetFuture(); 75 | void Reject(const std::string& error); 76 | 77 | /* Virtual methods inherited from webrtc::SetRemoteDescriptionObserver. */ 78 | public: 79 | void OnSetRemoteDescriptionComplete(webrtc::RTCError error) override; 80 | 81 | private: 82 | std::promise promise; 83 | }; 84 | 85 | class SetSessionDescriptionObserver : public webrtc::SetSessionDescriptionObserver 86 | { 87 | public: 88 | SetSessionDescriptionObserver() = default; 89 | ~SetSessionDescriptionObserver() override = default; 90 | 91 | std::future GetFuture(); 92 | void Reject(const std::string& error); 93 | 94 | /* Virtual methods inherited from webrtc::SetSessionDescriptionObserver. */ 95 | public: 96 | void OnSuccess() override; 97 | void OnFailure(webrtc::RTCError error) override; 98 | 99 | private: 100 | std::promise promise; 101 | }; 102 | 103 | class CreateSessionDescriptionObserver : public webrtc::CreateSessionDescriptionObserver 104 | { 105 | public: 106 | CreateSessionDescriptionObserver() = default; 107 | ~CreateSessionDescriptionObserver() override = default; 108 | 109 | std::future GetFuture(); 110 | void Reject(const std::string& error); 111 | 112 | /* Virtual methods inherited from webrtc::CreateSessionDescriptionObserver. */ 113 | public: 114 | void OnSuccess(webrtc::SessionDescriptionInterface* desc) override; 115 | void OnFailure(webrtc::RTCError error) override; 116 | 117 | private: 118 | std::promise promise; 119 | }; 120 | 121 | class RTCStatsCollectorCallback : public webrtc::RTCStatsCollectorCallback 122 | { 123 | public: 124 | RTCStatsCollectorCallback() = default; 125 | ~RTCStatsCollectorCallback() override = default; 126 | 127 | std::future GetFuture(); 128 | 129 | /* Virtual methods inherited from webrtc::RTCStatsCollectorCallback. */ 130 | public: 131 | void OnStatsDelivered(const webrtc::scoped_refptr& report) override; 132 | 133 | private: 134 | std::promise promise; 135 | }; 136 | 137 | public: 138 | struct Options 139 | { 140 | webrtc::PeerConnectionInterface::RTCConfiguration config; 141 | webrtc::PeerConnectionFactoryInterface* factory{ nullptr }; 142 | }; 143 | 144 | public: 145 | PeerConnection(PrivateListener* privateListener, const Options* options); 146 | ~PeerConnection() = default; 147 | 148 | void Close(); 149 | webrtc::PeerConnectionInterface::RTCConfiguration GetConfiguration() const; 150 | bool SetConfiguration(const webrtc::PeerConnectionInterface::RTCConfiguration& config); 151 | std::string CreateOffer(const webrtc::PeerConnectionInterface::RTCOfferAnswerOptions& options); 152 | std::string CreateAnswer(const webrtc::PeerConnectionInterface::RTCOfferAnswerOptions& options); 153 | void SetLocalDescription(webrtc::SdpType type, const std::string& sdp); 154 | void SetRemoteDescription(webrtc::SdpType type, const std::string& sdp); 155 | const std::string GetLocalDescription(); 156 | const std::string GetRemoteDescription(); 157 | std::vector> GetTransceivers() const; 158 | webrtc::scoped_refptr AddTransceiver(webrtc::MediaType mediaType); 159 | webrtc::scoped_refptr AddTransceiver( 160 | webrtc::scoped_refptr track, 161 | webrtc::RtpTransceiverInit rtpTransceiverInit); 162 | std::vector> GetSenders(); 163 | bool RemoveTrack(webrtc::scoped_refptr sender); 164 | nlohmann::json GetStats(); 165 | nlohmann::json GetStats(webrtc::scoped_refptr selector); 166 | nlohmann::json GetStats(webrtc::scoped_refptr selector); 167 | webrtc::scoped_refptr CreateDataChannel( 168 | const std::string& label, const webrtc::DataChannelInit* config); 169 | 170 | private: 171 | // Signaling and worker threads. 172 | std::unique_ptr networkThread; 173 | std::unique_ptr signalingThread; 174 | std::unique_ptr workerThread; 175 | 176 | // PeerConnection factory. 177 | webrtc::scoped_refptr peerConnectionFactory; 178 | 179 | // PeerConnection instance. 180 | webrtc::scoped_refptr pc; 181 | }; 182 | } // namespace mediasoupclient 183 | 184 | #endif 185 | -------------------------------------------------------------------------------- /include/Transport.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MSC_TRANSPORT_HPP 2 | #define MSC_TRANSPORT_HPP 3 | 4 | #include "Consumer.hpp" 5 | #include "DataConsumer.hpp" 6 | #include "DataProducer.hpp" 7 | #include "Handler.hpp" 8 | #include "Producer.hpp" 9 | 10 | #include 11 | #include // webrtc::MediaStreamTrackInterface 12 | #include // webrtc::PeerConnectionInterface 13 | #include // webrtc::RtpEncodingParameters 14 | 15 | #include 16 | #include 17 | #include // unique_ptr 18 | #include 19 | 20 | namespace mediasoupclient 21 | { 22 | // Fast forward declarations. 23 | class Device; 24 | 25 | class Transport : public Handler::PrivateListener 26 | { 27 | public: 28 | /* Public Listener API */ 29 | class Listener 30 | { 31 | public: 32 | virtual ~Listener() = default; 33 | virtual std::future OnConnect(Transport* transport, const nlohmann::json& dtlsParameters) = 0; 34 | virtual void OnConnectionStateChange(Transport* transport, const std::string& connectionState) = 0; 35 | }; 36 | 37 | /* Only child classes will create transport intances */ 38 | protected: 39 | Transport( 40 | Listener* listener, 41 | const std::string& id, 42 | const nlohmann::json& appData); 43 | 44 | public: 45 | virtual ~Transport() = default; 46 | const std::string& GetId() const; 47 | bool IsClosed() const; 48 | const std::string& GetConnectionState() const; 49 | nlohmann::json& GetAppData(); 50 | virtual void Close(); 51 | nlohmann::json GetStats() const; 52 | void RestartIce(const nlohmann::json& iceParameters); 53 | void UpdateIceServers(const nlohmann::json& iceServers); 54 | 55 | protected: 56 | void SetHandler(Handler* handler); 57 | 58 | /* Pure virtual methods inherited from Handler::PrivateListener */ 59 | public: 60 | void OnConnect(nlohmann::json& dtlsParameters) override; 61 | void OnConnectionStateChange( 62 | webrtc::PeerConnectionInterface::IceConnectionState connectionState) override; 63 | 64 | protected: 65 | // Closed flag. 66 | bool closed{ false }; 67 | // SCTP max message size if enabled, null otherwise. 68 | size_t maxSctpMessageSize{ 0u }; 69 | // Whether the Consumer for RTP probation has been created. 70 | bool probatorConsumerCreated{ false }; 71 | // Whether this transport supports DataChannel. 72 | bool hasSctpParameters{ false }; 73 | 74 | private: 75 | // Listener. 76 | Listener* listener{ nullptr }; 77 | // Id. 78 | std::string id; 79 | // Transport (IceConneciton) connection state. 80 | webrtc::PeerConnectionInterface::IceConnectionState connectionState{ 81 | webrtc::PeerConnectionInterface::IceConnectionState::kIceConnectionNew 82 | }; 83 | // Handler. 84 | Handler* handler{ nullptr }; 85 | // App custom data. 86 | nlohmann::json appData = nlohmann::json::object(); 87 | }; 88 | 89 | class SendTransport : public Transport, 90 | public Producer::PrivateListener, 91 | public DataProducer::PrivateListener 92 | { 93 | public: 94 | /* Public Listener API */ 95 | class Listener : public Transport::Listener 96 | { 97 | public: 98 | virtual std::future OnProduce( 99 | SendTransport* transport, 100 | const std::string& kind, 101 | nlohmann::json rtpParameters, 102 | const nlohmann::json& appData) = 0; 103 | 104 | virtual std::future OnProduceData( 105 | SendTransport* transport, 106 | const nlohmann::json& sctpStreamParameters, 107 | const std::string& label, 108 | const std::string& protocol, 109 | const nlohmann::json& appData) = 0; 110 | }; 111 | 112 | private: 113 | SendTransport( 114 | Listener* listener, 115 | const std::string& id, 116 | const nlohmann::json& iceParameters, 117 | const nlohmann::json& iceCandidates, 118 | const nlohmann::json& dtlsParameters, 119 | const nlohmann::json& sctpParameters, 120 | const PeerConnection::Options* peerConnectionOptions, 121 | const std::function& getSendExtendedRtpCapabilities, 122 | const std::map* canProduceByKind, 123 | const nlohmann::json& appData); 124 | 125 | /* Device is the only one constructing Transports. */ 126 | friend Device; 127 | 128 | public: 129 | Producer* Produce( 130 | Producer::Listener* producerListener, 131 | webrtc::MediaStreamTrackInterface* track, 132 | const std::vector* encodings, 133 | const nlohmann::json* codecOptions, 134 | const nlohmann::json* codec, 135 | const nlohmann::json& appData = nlohmann::json::object()); 136 | 137 | DataProducer* ProduceData( 138 | DataProducer::Listener* listener, 139 | const std::string& label = "", 140 | const std::string& protocol = "", 141 | bool ordered = true, 142 | int maxRetransmits = 0, 143 | int maxPacketLifeTime = 0, 144 | const nlohmann::json& appData = nlohmann::json::object()); 145 | 146 | /* Virtual methods inherited from Transport. */ 147 | public: 148 | void Close() override; 149 | 150 | /* Virtual methods inherited from Producer::PrivateListener. */ 151 | public: 152 | void OnClose(Producer* producer) override; 153 | void OnClose(DataProducer* dataProducer) override; 154 | void OnReplaceTrack(const Producer* producer, webrtc::MediaStreamTrackInterface* track) override; 155 | void OnSetMaxSpatialLayer(const Producer* producer, uint8_t maxSpatialLayer) override; 156 | nlohmann::json OnGetStats(const Producer* producer) override; 157 | 158 | private: 159 | // Listener instance. 160 | Listener* listener; 161 | // Map of Producers indexed by id. 162 | std::unordered_map producers; 163 | std::unordered_map dataProducers; 164 | // Whether we can produce audio/video based on computed extended RTP 165 | // capabilities. 166 | const std::map* canProduceByKind{ nullptr }; 167 | // SendHandler instance. 168 | std::unique_ptr sendHandler; 169 | }; 170 | 171 | class RecvTransport : public Transport, 172 | public Consumer::PrivateListener, 173 | public DataConsumer::PrivateListener 174 | { 175 | public: 176 | /* Public Listener API */ 177 | class Listener : public Transport::Listener 178 | { 179 | }; 180 | 181 | private: 182 | RecvTransport( 183 | Listener* listener, 184 | const std::string& id, 185 | const nlohmann::json& iceParameters, 186 | const nlohmann::json& iceCandidates, 187 | const nlohmann::json& dtlsParameters, 188 | const nlohmann::json& sctpParameters, 189 | const PeerConnection::Options* peerConnectionOptions, 190 | const nlohmann::json* recvRtpCapabilities, 191 | const nlohmann::json& appData); 192 | 193 | /* Device is the only one constructing Transports */ 194 | friend Device; 195 | 196 | public: 197 | Consumer* Consume( 198 | Consumer::Listener* consumerListener, 199 | const std::string& id, 200 | const std::string& producerId, 201 | const std::string& kind, 202 | nlohmann::json* rtpParameters, 203 | const nlohmann::json& appData = nlohmann::json::object()); 204 | 205 | DataConsumer* ConsumeData( 206 | DataConsumer::Listener* listener, 207 | const std::string& id, 208 | const std::string& producerId, 209 | const uint16_t streamId, 210 | const std::string& label, 211 | const std::string& protocol = std::string(), 212 | const nlohmann::json& appData = nlohmann::json::object()); 213 | 214 | /* Virtual methods inherited from Transport. */ 215 | public: 216 | void Close() override; 217 | 218 | /* Virtual methods inherited from Consumer::PrivateListener. */ 219 | public: 220 | void OnClose(Consumer* consumer) override; 221 | void OnClose(DataConsumer* consumer) override; 222 | nlohmann::json OnGetStats(const Consumer* consumer) override; 223 | 224 | private: 225 | // Map of Consumers indexed by id. 226 | std::unordered_map consumers; 227 | std::unordered_map dataConsumers; 228 | // SendHandler instance. 229 | std::unique_ptr recvHandler; 230 | 231 | const nlohmann::json* recvRtpCapabilities; 232 | }; 233 | } // namespace mediasoupclient 234 | #endif 235 | -------------------------------------------------------------------------------- /test/src/ortc.test.cpp: -------------------------------------------------------------------------------- 1 | #include "ortc.hpp" 2 | #include "MediaSoupClientErrors.hpp" 3 | #include "fakeParameters.hpp" 4 | #include 5 | 6 | using namespace mediasoupclient; 7 | 8 | TEST_CASE("validateRtpCapabilities", "[ortc][validateRtpCapabilities]") 9 | { 10 | } 11 | 12 | TEST_CASE("getExtendedCapabilities", "[ortc][getExtendedCapabilities]") 13 | { 14 | SECTION("succeeds if localCaps equals remoteCaps") 15 | { 16 | json remoteCaps = generateRouterRtpCapabilities(); 17 | json localCaps = generateRouterRtpCapabilities(); 18 | 19 | auto extendedRtpCapabilities = ortc::getExtendedRtpCapabilities(localCaps, remoteCaps); 20 | 21 | REQUIRE(extendedRtpCapabilities["codecs"].size() == 3); 22 | 23 | auto codecs = extendedRtpCapabilities["codecs"]; 24 | 25 | REQUIRE(codecs[0]["mimeType"] == "audio/opus"); 26 | REQUIRE(codecs[1]["mimeType"] == "video/VP8"); 27 | REQUIRE(codecs[1]["remoteRtxPayloadType"] == 102); 28 | REQUIRE(codecs[1]["localRtxPayloadType"] == 102); 29 | REQUIRE(codecs[2]["mimeType"] == "video/H264"); 30 | REQUIRE(codecs[2]["remoteRtxPayloadType"] == 104); 31 | REQUIRE(codecs[2]["localRtxPayloadType"] == 104); 32 | 33 | REQUIRE(extendedRtpCapabilities["headerExtensions"].size() == 8); 34 | } 35 | 36 | SECTION("succeeds if localCaps is a subset of remoteCaps") 37 | { 38 | json remoteCaps = generateRouterRtpCapabilities(); 39 | json localCaps = generateRouterRtpCapabilities(); 40 | 41 | // Remove the second VP8 codec. 42 | auto it = localCaps["codecs"].begin(); 43 | 44 | it++; 45 | localCaps["codecs"].erase(it); 46 | 47 | auto extendedRtpCapabilities = ortc::getExtendedRtpCapabilities(localCaps, remoteCaps); 48 | 49 | REQUIRE(extendedRtpCapabilities["codecs"].size() == 2); 50 | 51 | auto codecs = extendedRtpCapabilities["codecs"]; 52 | 53 | REQUIRE(codecs[0]["mimeType"] == "audio/opus"); 54 | REQUIRE(codecs[1]["mimeType"] == "video/H264"); 55 | REQUIRE(codecs[1]["remoteRtxPayloadType"] == 104); 56 | REQUIRE(codecs[1]["localRtxPayloadType"] == 104); 57 | 58 | REQUIRE(extendedRtpCapabilities["headerExtensions"].size() == 8); 59 | } 60 | } 61 | 62 | TEST_CASE("getRecvRtpCapabilities", "[getRecvRtpCapabilities]") 63 | { 64 | SECTION("succeeds if localCaps equals remoteCaps") 65 | { 66 | json remoteCaps = generateRouterRtpCapabilities(); 67 | json localCaps = generateRouterRtpCapabilities(); 68 | 69 | auto extendedRtpCapabilities = ortc::getExtendedRtpCapabilities(localCaps, remoteCaps); 70 | auto recvRtpCapabilities = ortc::getRecvRtpCapabilities(extendedRtpCapabilities); 71 | 72 | REQUIRE(recvRtpCapabilities["codecs"].size() == 5); 73 | 74 | auto codecs = recvRtpCapabilities["codecs"]; 75 | 76 | REQUIRE(codecs[0]["mimeType"] == "audio/opus"); 77 | REQUIRE(codecs[1]["mimeType"] == "video/VP8"); 78 | REQUIRE(codecs[2]["mimeType"] == "video/rtx"); 79 | REQUIRE(codecs[3]["mimeType"] == "video/H264"); 80 | REQUIRE(codecs[4]["mimeType"] == "video/rtx"); 81 | } 82 | 83 | SECTION("succeeds if localCaps is a subset of remoteCaps") 84 | { 85 | json remoteCaps = generateRouterRtpCapabilities(); 86 | json localCaps = generateRouterRtpCapabilities(); 87 | 88 | // Remove the second VP8 codec VP8. 89 | auto it = localCaps["codecs"].begin(); 90 | it++; 91 | 92 | localCaps["codecs"].erase(it); 93 | 94 | auto extendedRtpCapabilities = ortc::getExtendedRtpCapabilities(localCaps, remoteCaps); 95 | auto recvRtpCapabilities = ortc::getRecvRtpCapabilities(extendedRtpCapabilities); 96 | 97 | REQUIRE(recvRtpCapabilities["codecs"].size() == 3); 98 | 99 | auto codecs = recvRtpCapabilities["codecs"]; 100 | 101 | REQUIRE(codecs[0]["mimeType"] == "audio/opus"); 102 | REQUIRE(codecs[1]["mimeType"] == "video/H264"); 103 | REQUIRE(codecs[2]["mimeType"] == "video/rtx"); 104 | } 105 | } 106 | 107 | TEST_CASE("getSendingRtpParameters", "[getSendingRtpParameters]") 108 | { 109 | SECTION("succeeds if localCaps equals remoteCaps") 110 | { 111 | json remoteCaps = generateRouterRtpCapabilities(); 112 | json localCaps = generateRouterRtpCapabilities(); 113 | 114 | auto extendedRtpCapabilities = ortc::getExtendedRtpCapabilities(localCaps, remoteCaps); 115 | auto audioSendRtpParameters = ortc::getSendingRtpParameters("audio", extendedRtpCapabilities); 116 | 117 | REQUIRE(audioSendRtpParameters["codecs"].size() == 1); 118 | REQUIRE(audioSendRtpParameters["codecs"][0]["mimeType"] == "audio/opus"); 119 | 120 | auto videoSendRtpParameters = ortc::getSendingRtpParameters("video", extendedRtpCapabilities); 121 | 122 | REQUIRE(videoSendRtpParameters["codecs"].size() == 4); 123 | REQUIRE(videoSendRtpParameters["codecs"][0]["mimeType"] == "video/VP8"); 124 | REQUIRE(videoSendRtpParameters["codecs"][1]["mimeType"] == "video/rtx"); 125 | REQUIRE(videoSendRtpParameters["codecs"][2]["mimeType"] == "video/H264"); 126 | REQUIRE(videoSendRtpParameters["codecs"][3]["mimeType"] == "video/rtx"); 127 | } 128 | } 129 | 130 | TEST_CASE("ortc::canSend", "[ortc::canSend]") 131 | { 132 | json remoteCaps = generateRouterRtpCapabilities(); 133 | json localCaps = generateRouterRtpCapabilities(); 134 | 135 | auto extendedRtpCapabilities = ortc::getExtendedRtpCapabilities(localCaps, remoteCaps); 136 | 137 | SECTION("it can send audio and video if audio and video codecs are present") 138 | { 139 | REQUIRE(ortc::canSend("audio", extendedRtpCapabilities)); 140 | REQUIRE(ortc::canSend("video", extendedRtpCapabilities)); 141 | } 142 | 143 | SECTION("it cannot send audio if no audio codec is present") 144 | { 145 | // Remove the first codec "opus" 146 | auto it = extendedRtpCapabilities["codecs"].begin(); 147 | extendedRtpCapabilities["codecs"].erase(it); 148 | 149 | REQUIRE(!ortc::canSend("audio", extendedRtpCapabilities)); 150 | REQUIRE(ortc::canSend("video", extendedRtpCapabilities)); 151 | } 152 | 153 | SECTION("it cannot send audio nor video if no codec is present") 154 | { 155 | extendedRtpCapabilities["codecs"] = json::array(); 156 | 157 | REQUIRE(!ortc::canSend("audio", extendedRtpCapabilities)); 158 | REQUIRE(!ortc::canSend("video", extendedRtpCapabilities)); 159 | } 160 | } 161 | 162 | TEST_CASE("ortc::canReceive", "[ortc::canReceive]") 163 | { 164 | json remoteCaps = generateRouterRtpCapabilities(); 165 | json localCaps = generateRouterRtpCapabilities(); 166 | auto extendedRtpCapabilities = ortc::getRecvRtpCapabilities(ortc::getExtendedRtpCapabilities(localCaps, remoteCaps)); 167 | 168 | SECTION("it can receive") 169 | { 170 | auto rtpParameters = R"( 171 | { 172 | "codecs" : 173 | [ 174 | { 175 | "mimeType" : "audio/opus", 176 | "kind" : "audio", 177 | "clockRate" : 48000, 178 | "payloadType" : 100, 179 | "channels" : 2, 180 | "rtcpFeedback" : [], 181 | "parameters" : 182 | { 183 | "useinbandfec" : 1 184 | } 185 | } 186 | ] 187 | })"_json; 188 | 189 | 190 | REQUIRE(ortc::canReceive(rtpParameters, extendedRtpCapabilities)); 191 | } 192 | 193 | SECTION("it cannot receive if empty rtpParameters") 194 | { 195 | auto rtpParameters = R"( 196 | { 197 | "codecs" : [] 198 | })"_json; 199 | 200 | REQUIRE(!ortc::canReceive(rtpParameters, extendedRtpCapabilities)); 201 | } 202 | 203 | SECTION("it cannot receive if no matching payload type") 204 | { 205 | auto rtpParameters = R"( 206 | { 207 | "codecs" : 208 | [ 209 | { 210 | "mimeType" : "audio/opus", 211 | "kind" : "audio", 212 | "clockRate" : 48000, 213 | "payloadType" : 96, 214 | "channels" : 2, 215 | "rtcpFeedback" : [], 216 | "parameters" : 217 | { 218 | "useinbandfec" : "1" 219 | } 220 | } 221 | ] 222 | })"_json; 223 | 224 | REQUIRE(!ortc::canReceive(rtpParameters, extendedRtpCapabilities)); 225 | } 226 | } 227 | 228 | TEST_CASE("ortc::reduceCodecs", "[ortc::reduceCodecs]") 229 | { 230 | json caps = generateRouterRtpCapabilities(); 231 | 232 | SECTION("it can reduce codecs") 233 | { 234 | auto capCodec = R"( 235 | { 236 | "mimeType" : "video/H264", 237 | "kind" : "video", 238 | "clockRate" : 90000, 239 | "preferredPayloadType" : 103, 240 | "rtcpFeedback" : 241 | [ 242 | { "type": "nack" }, 243 | { "type": "nack", "parameter": "pli" }, 244 | { "type": "nack", "parameter": "sli" }, 245 | { "type": "nack", "parameter": "rpsi" }, 246 | { "type": "nack", "parameter": "app" }, 247 | { "type": "ccm", "parameter": "fir" }, 248 | { "type": "goog-remb" } 249 | ], 250 | "parameters" : 251 | { 252 | "level-asymmetry-allowed" : 1, 253 | "packetization-mode" : 1, 254 | "profile-level-id" : "42e01f" 255 | } 256 | })"_json; 257 | json filteredCodecs = ortc::reduceCodecs(caps["codecs"], &capCodec); 258 | 259 | REQUIRE(filteredCodecs[0]["mimeType"] == "video/H264"); 260 | REQUIRE(filteredCodecs[1]["mimeType"] == "video/rtx"); 261 | } 262 | 263 | SECTION("it can return the first codec if no capability codec is given") 264 | { 265 | json capsCodec = json::array(); 266 | json filteredCodecs = ortc::reduceCodecs(caps["codecs"], &capsCodec); 267 | 268 | REQUIRE(filteredCodecs[0]["mimeType"] == "audio/opus"); 269 | } 270 | 271 | SECTION("it throws if codecs doesn't contain a provided codec") 272 | { 273 | auto capCodec = R"( 274 | { 275 | "mimeType" : "video/x-dummy" 276 | })"_json; 277 | 278 | REQUIRE_THROWS_AS(ortc::reduceCodecs(caps["codecs"], &capCodec), MediaSoupClientTypeError); 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /src/sdp/RemoteSdp.cpp: -------------------------------------------------------------------------------- 1 | #define MSC_CLASS "Sdp::RemoteSdp" 2 | 3 | #include "sdp/RemoteSdp.hpp" 4 | #include "Logger.hpp" 5 | #include "algorithm" // find_if. 6 | #include "sdptransform.hpp" 7 | 8 | using json = nlohmann::json; 9 | 10 | namespace mediasoupclient 11 | { 12 | /* Sdp::RemoteSdp methods */ 13 | 14 | Sdp::RemoteSdp::RemoteSdp( 15 | const json& iceParameters, 16 | const json& iceCandidates, 17 | const json& dtlsParameters, 18 | const json& sctpParameters) 19 | : iceParameters(iceParameters), iceCandidates(iceCandidates), dtlsParameters(dtlsParameters), 20 | sctpParameters(sctpParameters) 21 | { 22 | MSC_TRACE(); 23 | 24 | // clang-format off 25 | this->sdpObject = 26 | { 27 | { "version", 0 }, 28 | { "origin", 29 | { 30 | { "address", "0.0.0.0" }, 31 | { "ipVer", 4 }, 32 | { "netType", "IN" }, 33 | { "sessionId", 10000 }, 34 | { "sessionVersion", 0 }, 35 | { "username", "libmediasoupclient" } 36 | } 37 | }, 38 | { "name", "-" }, 39 | { "timing", 40 | { 41 | { "start", 0 }, 42 | { "stop", 0 } 43 | } 44 | }, 45 | { "media", json::array() } 46 | }; 47 | // clang-format on 48 | 49 | // If ICE parameters are given, add ICE-Lite indicator. 50 | if (this->iceParameters.find("iceLite") != this->iceParameters.end()) 51 | this->sdpObject["icelite"] = "ice-lite"; 52 | 53 | // clang-format off 54 | this->sdpObject["msidSemantic"] = 55 | { 56 | { "semantic", "WMS" }, 57 | { "token", "*" } 58 | }; 59 | // clang-format on 60 | 61 | // NOTE: We take the latest fingerprint. 62 | auto numFingerprints = this->dtlsParameters["fingerprints"].size(); 63 | 64 | this->sdpObject["fingerprint"] = { 65 | { "type", this->dtlsParameters.at("fingerprints")[numFingerprints - 1]["algorithm"] }, 66 | { "hash", this->dtlsParameters.at("fingerprints")[numFingerprints - 1]["value"] } 67 | }; 68 | 69 | // clang-format off 70 | this->sdpObject["groups"] = 71 | { 72 | { 73 | { "type", "BUNDLE" }, 74 | { "mids", "" } 75 | } 76 | }; 77 | // clang-format on 78 | } 79 | 80 | Sdp::RemoteSdp::~RemoteSdp() 81 | { 82 | MSC_TRACE(); 83 | 84 | for (const auto* mediaSection : this->mediaSections) 85 | { 86 | delete mediaSection; 87 | } 88 | } 89 | 90 | void Sdp::RemoteSdp::UpdateIceParameters(const json& iceParameters) 91 | { 92 | MSC_TRACE(); 93 | 94 | this->iceParameters = iceParameters; 95 | 96 | if (iceParameters.find("iceLite") != iceParameters.end()) 97 | sdpObject["icelite"] = "ice-lite"; 98 | 99 | for (auto idx{ 0u }; idx < this->mediaSections.size(); ++idx) 100 | { 101 | auto* mediaSection = this->mediaSections[idx]; 102 | 103 | mediaSection->SetIceParameters(iceParameters); 104 | 105 | // Update SDP media section. 106 | this->sdpObject["media"][idx] = mediaSection->GetObject(); 107 | } 108 | } 109 | 110 | void Sdp::RemoteSdp::UpdateDtlsRole(const std::string& role) 111 | { 112 | MSC_TRACE(); 113 | 114 | this->dtlsParameters["role"] = role; 115 | 116 | if (iceParameters.find("iceLite") != iceParameters.end()) 117 | sdpObject["icelite"] = "ice-lite"; 118 | 119 | for (auto idx{ 0u }; idx < this->mediaSections.size(); ++idx) 120 | { 121 | auto* mediaSection = this->mediaSections[idx]; 122 | 123 | mediaSection->SetDtlsRole(role); 124 | 125 | // Update SDP media section. 126 | this->sdpObject["media"][idx] = mediaSection->GetObject(); 127 | } 128 | } 129 | 130 | Sdp::RemoteSdp::MediaSectionIdx Sdp::RemoteSdp::GetNextMediaSectionIdx() 131 | { 132 | MSC_TRACE(); 133 | 134 | // If a closed media section is found, return its index. 135 | for (auto idx{ 0u }; idx < this->mediaSections.size(); ++idx) 136 | { 137 | auto* mediaSection = this->mediaSections[idx]; 138 | 139 | if (mediaSection->IsClosed()) 140 | return { idx, mediaSection->GetMid() }; 141 | } 142 | 143 | // If no closed media section is found, return next one. 144 | return { this->mediaSections.size() }; 145 | } 146 | 147 | void Sdp::RemoteSdp::Send( 148 | json& offerMediaObject, 149 | const std::string& reuseMid, 150 | json& offerRtpParameters, 151 | json& answerRtpParameters, 152 | const json* codecOptions) 153 | { 154 | MSC_TRACE(); 155 | 156 | auto* mediaSection = new AnswerMediaSection( 157 | this->iceParameters, 158 | this->iceCandidates, 159 | this->dtlsParameters, 160 | this->sctpParameters, 161 | offerMediaObject, 162 | offerRtpParameters, 163 | answerRtpParameters, 164 | codecOptions); 165 | 166 | // Closed media section replacement. 167 | if (!reuseMid.empty()) 168 | { 169 | this->ReplaceMediaSection(mediaSection, reuseMid); 170 | } 171 | else 172 | { 173 | this->AddMediaSection(mediaSection); 174 | } 175 | } 176 | 177 | void Sdp::RemoteSdp::SendSctpAssociation(json& offerMediaObject) 178 | { 179 | nlohmann::json emptyJson; 180 | auto* mediaSection = new AnswerMediaSection( 181 | this->iceParameters, 182 | this->iceCandidates, 183 | this->dtlsParameters, 184 | this->sctpParameters, 185 | offerMediaObject, 186 | emptyJson, 187 | emptyJson, 188 | nullptr); 189 | 190 | this->AddMediaSection(mediaSection); 191 | } 192 | 193 | void Sdp::RemoteSdp::RecvSctpAssociation() 194 | { 195 | nlohmann::json emptyJson; 196 | auto* mediaSection = new OfferMediaSection( 197 | this->iceParameters, 198 | this->iceCandidates, 199 | this->dtlsParameters, 200 | this->sctpParameters, 201 | "datachannel", // mid 202 | "application", // kind 203 | emptyJson, // offerRtpParameters 204 | "", // streamId 205 | "" // trackId 206 | ); 207 | this->AddMediaSection(mediaSection); 208 | } 209 | 210 | void Sdp::RemoteSdp::Receive( 211 | const std::string& mid, 212 | const std::string& kind, 213 | const json& offerRtpParameters, 214 | const std::string& streamId, 215 | const std::string& trackId) 216 | { 217 | MSC_TRACE(); 218 | 219 | auto* mediaSection = new OfferMediaSection( 220 | this->iceParameters, 221 | this->iceCandidates, 222 | this->dtlsParameters, 223 | nullptr, // sctpParameters must be null here. 224 | mid, 225 | kind, 226 | offerRtpParameters, 227 | streamId, 228 | trackId); 229 | 230 | // Let's try to recycle a closed media section (if any). 231 | // NOTE: We can recycle a closed m=audio section with a new m=video. 232 | auto mediaSectionIt = find_if( 233 | this->mediaSections.begin(), 234 | this->mediaSections.end(), 235 | [](const MediaSection* mediaSection) { return mediaSection->IsClosed(); }); 236 | 237 | if (mediaSectionIt != this->mediaSections.end()) 238 | { 239 | this->ReplaceMediaSection(mediaSection, (*mediaSectionIt)->GetMid()); 240 | } 241 | else 242 | { 243 | this->AddMediaSection(mediaSection); 244 | } 245 | } 246 | 247 | void Sdp::RemoteSdp::DisableMediaSection(const std::string& mid) 248 | { 249 | MSC_TRACE(); 250 | 251 | const auto idx = this->midToIndex[mid]; 252 | auto* mediaSection = this->mediaSections[idx]; 253 | 254 | mediaSection->Disable(); 255 | } 256 | 257 | void Sdp::RemoteSdp::CloseMediaSection(const std::string& mid) 258 | { 259 | MSC_TRACE(); 260 | 261 | const auto idx = this->midToIndex[mid]; 262 | auto* mediaSection = this->mediaSections[idx]; 263 | 264 | // NOTE: Closing the first m section is a pain since it invalidates the 265 | // bundled transport, so let's avoid it. 266 | if (mid == this->firstMid) 267 | mediaSection->Disable(); 268 | else 269 | mediaSection->Close(); 270 | 271 | // Update SDP media section. 272 | this->sdpObject["media"][idx] = mediaSection->GetObject(); 273 | 274 | // Regenerate BUNDLE mids. 275 | this->RegenerateBundleMids(); 276 | } 277 | 278 | std::string Sdp::RemoteSdp::GetSdp() 279 | { 280 | MSC_TRACE(); 281 | 282 | // Increase SDP version. 283 | auto version = this->sdpObject["origin"]["sessionVersion"].get(); 284 | 285 | this->sdpObject["origin"]["sessionVersion"] = ++version; 286 | 287 | return sdptransform::write(this->sdpObject); 288 | } 289 | 290 | void Sdp::RemoteSdp::AddMediaSection(MediaSection* newMediaSection) 291 | { 292 | MSC_TRACE(); 293 | 294 | if (this->firstMid.empty()) 295 | this->firstMid = newMediaSection->GetMid(); 296 | 297 | // Add it in the vector. 298 | this->mediaSections.push_back(newMediaSection); 299 | 300 | // Add to the map. 301 | this->midToIndex[newMediaSection->GetMid()] = this->mediaSections.size() - 1; 302 | 303 | // Add to the SDP object. 304 | this->sdpObject["media"].push_back(newMediaSection->GetObject()); 305 | 306 | this->RegenerateBundleMids(); 307 | } 308 | 309 | void Sdp::RemoteSdp::ReplaceMediaSection(MediaSection* newMediaSection, const std::string& reuseMid) 310 | { 311 | MSC_TRACE(); 312 | 313 | // Store it in the map. 314 | if (!reuseMid.empty()) 315 | { 316 | const auto idx = this->midToIndex[reuseMid]; 317 | auto* const oldMediaSection = this->mediaSections[idx]; 318 | 319 | // Replace the index in the vector with the new media section. 320 | this->mediaSections[idx] = newMediaSection; 321 | 322 | // Update the map. 323 | this->midToIndex.erase(oldMediaSection->GetMid()); 324 | this->midToIndex[newMediaSection->GetMid()] = idx; 325 | 326 | // Delete old MediaSection. 327 | delete oldMediaSection; 328 | 329 | // Update the SDP object. 330 | this->sdpObject["media"][idx] = newMediaSection->GetObject(); 331 | 332 | // Regenerate BUNDLE mids. 333 | this->RegenerateBundleMids(); 334 | } 335 | else 336 | { 337 | const auto idx = this->midToIndex[newMediaSection->GetMid()]; 338 | auto* const oldMediaSection = this->mediaSections[idx]; 339 | 340 | // Replace the index in the vector with the new media section. 341 | this->mediaSections[idx] = newMediaSection; 342 | 343 | // Delete old MediaSection. 344 | delete oldMediaSection; 345 | 346 | // Update the SDP object. 347 | this->sdpObject["media"][this->mediaSections.size() - 1] = newMediaSection->GetObject(); 348 | } 349 | } 350 | 351 | void Sdp::RemoteSdp::RegenerateBundleMids() 352 | { 353 | MSC_TRACE(); 354 | 355 | std::string mids; 356 | 357 | for (const auto* mediaSection : this->mediaSections) 358 | { 359 | if (!mediaSection->IsClosed()) 360 | { 361 | if (mids.empty()) 362 | mids = mediaSection->GetMid(); 363 | else 364 | mids.append(" ").append(mediaSection->GetMid()); 365 | } 366 | } 367 | 368 | this->sdpObject["groups"][0]["mids"] = mids; 369 | } 370 | } // namespace mediasoupclient 371 | -------------------------------------------------------------------------------- /test/deps/libwebrtc/pc/test/fake_audio_capture_module.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The WebRTC project authors. All Rights Reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license 5 | * that can be found in the LICENSE file in the root of the source 6 | * tree. An additional intellectual property rights grant can be found 7 | * in the file PATENTS. All contributing project authors may 8 | * be found in the AUTHORS file in the root of the source tree. 9 | */ 10 | 11 | // This class implements an AudioCaptureModule that can be used to detect if 12 | // audio is being received properly if it is fed by another AudioCaptureModule 13 | // in some arbitrary audio pipeline where they are connected. It does not play 14 | // out or record any audio so it does not need access to any hardware and can 15 | // therefore be used in the gtest testing framework. 16 | 17 | // Note P postfix of a function indicates that it should only be called by the 18 | // processing thread. 19 | 20 | #ifndef PC_TEST_FAKE_AUDIO_CAPTURE_MODULE_H_ 21 | #define PC_TEST_FAKE_AUDIO_CAPTURE_MODULE_H_ 22 | 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | #include "api/scoped_refptr.h" 29 | #include "api/sequence_checker.h" 30 | #include "modules/audio_device/include/audio_device.h" 31 | #include "modules/audio_device/include/audio_device_defines.h" 32 | #include "rtc_base/synchronization/mutex.h" 33 | #include "rtc_base/thread_annotations.h" 34 | 35 | namespace rtc { 36 | class Thread; 37 | } // namespace rtc 38 | 39 | class FakeAudioCaptureModule : public webrtc::AudioDeviceModule { 40 | public: 41 | typedef uint16_t Sample; 42 | 43 | // The value for the following constants have been derived by running VoE 44 | // using a real ADM. The constants correspond to 10ms of mono audio at 44kHz. 45 | static const size_t kNumberSamples = 440; 46 | static const size_t kNumberBytesPerSample = sizeof(Sample); 47 | 48 | // Creates a FakeAudioCaptureModule or returns NULL on failure. 49 | static webrtc::scoped_refptr Create(); 50 | 51 | // Returns the number of frames that have been successfully pulled by the 52 | // instance. Note that correctly detecting success can only be done if the 53 | // pulled frame was generated/pushed from a FakeAudioCaptureModule. 54 | int frames_received() const RTC_LOCKS_EXCLUDED(mutex_); 55 | 56 | int32_t ActiveAudioLayer(AudioLayer* audio_layer) const override; 57 | 58 | // Note: Calling this method from a callback may result in deadlock. 59 | int32_t RegisterAudioCallback(webrtc::AudioTransport* audio_callback) override 60 | RTC_LOCKS_EXCLUDED(mutex_); 61 | 62 | int32_t Init() override; 63 | int32_t Terminate() override; 64 | bool Initialized() const override; 65 | 66 | int16_t PlayoutDevices() override; 67 | int16_t RecordingDevices() override; 68 | int32_t PlayoutDeviceName(uint16_t index, 69 | char name[webrtc::kAdmMaxDeviceNameSize], 70 | char guid[webrtc::kAdmMaxGuidSize]) override; 71 | int32_t RecordingDeviceName(uint16_t index, 72 | char name[webrtc::kAdmMaxDeviceNameSize], 73 | char guid[webrtc::kAdmMaxGuidSize]) override; 74 | 75 | int32_t SetPlayoutDevice(uint16_t index) override; 76 | int32_t SetPlayoutDevice(WindowsDeviceType device) override; 77 | int32_t SetRecordingDevice(uint16_t index) override; 78 | int32_t SetRecordingDevice(WindowsDeviceType device) override; 79 | 80 | int32_t PlayoutIsAvailable(bool* available) override; 81 | int32_t InitPlayout() override; 82 | bool PlayoutIsInitialized() const override; 83 | int32_t RecordingIsAvailable(bool* available) override; 84 | int32_t InitRecording() override; 85 | bool RecordingIsInitialized() const override; 86 | 87 | int32_t StartPlayout() RTC_LOCKS_EXCLUDED(mutex_) override; 88 | int32_t StopPlayout() RTC_LOCKS_EXCLUDED(mutex_) override; 89 | bool Playing() const RTC_LOCKS_EXCLUDED(mutex_) override; 90 | int32_t StartRecording() RTC_LOCKS_EXCLUDED(mutex_) override; 91 | int32_t StopRecording() RTC_LOCKS_EXCLUDED(mutex_) override; 92 | bool Recording() const RTC_LOCKS_EXCLUDED(mutex_) override; 93 | 94 | int32_t InitSpeaker() override; 95 | bool SpeakerIsInitialized() const override; 96 | int32_t InitMicrophone() override; 97 | bool MicrophoneIsInitialized() const override; 98 | 99 | int32_t SpeakerVolumeIsAvailable(bool* available) override; 100 | int32_t SetSpeakerVolume(uint32_t volume) override; 101 | int32_t SpeakerVolume(uint32_t* volume) const override; 102 | int32_t MaxSpeakerVolume(uint32_t* max_volume) const override; 103 | int32_t MinSpeakerVolume(uint32_t* min_volume) const override; 104 | 105 | int32_t MicrophoneVolumeIsAvailable(bool* available) override; 106 | int32_t SetMicrophoneVolume(uint32_t volume) 107 | RTC_LOCKS_EXCLUDED(mutex_) override; 108 | int32_t MicrophoneVolume(uint32_t* volume) const 109 | RTC_LOCKS_EXCLUDED(mutex_) override; 110 | int32_t MaxMicrophoneVolume(uint32_t* max_volume) const override; 111 | 112 | int32_t MinMicrophoneVolume(uint32_t* min_volume) const override; 113 | 114 | int32_t SpeakerMuteIsAvailable(bool* available) override; 115 | int32_t SetSpeakerMute(bool enable) override; 116 | int32_t SpeakerMute(bool* enabled) const override; 117 | 118 | int32_t MicrophoneMuteIsAvailable(bool* available) override; 119 | int32_t SetMicrophoneMute(bool enable) override; 120 | int32_t MicrophoneMute(bool* enabled) const override; 121 | 122 | int32_t StereoPlayoutIsAvailable(bool* available) const override; 123 | int32_t SetStereoPlayout(bool enable) override; 124 | int32_t StereoPlayout(bool* enabled) const override; 125 | int32_t StereoRecordingIsAvailable(bool* available) const override; 126 | int32_t SetStereoRecording(bool enable) override; 127 | int32_t StereoRecording(bool* enabled) const override; 128 | 129 | int32_t PlayoutDelay(uint16_t* delay_ms) const override; 130 | 131 | bool BuiltInAECIsAvailable() const override { return false; } 132 | int32_t EnableBuiltInAEC(bool enable) override { return -1; } 133 | bool BuiltInAGCIsAvailable() const override { return false; } 134 | int32_t EnableBuiltInAGC(bool enable) override { return -1; } 135 | bool BuiltInNSIsAvailable() const override { return false; } 136 | int32_t EnableBuiltInNS(bool enable) override { return -1; } 137 | 138 | int32_t GetPlayoutUnderrunCount() const override { return -1; } 139 | 140 | absl::optional GetStats() const override { 141 | return webrtc::AudioDeviceModule::Stats(); 142 | } 143 | #if defined(WEBRTC_IOS) 144 | int GetPlayoutAudioParameters( 145 | webrtc::AudioParameters* params) const override { 146 | return -1; 147 | } 148 | int GetRecordAudioParameters(webrtc::AudioParameters* params) const override { 149 | return -1; 150 | } 151 | #endif // WEBRTC_IOS 152 | 153 | // End of functions inherited from webrtc::AudioDeviceModule. 154 | 155 | protected: 156 | // The constructor is protected because the class needs to be created as a 157 | // reference counted object (for memory managment reasons). It could be 158 | // exposed in which case the burden of proper instantiation would be put on 159 | // the creator of a FakeAudioCaptureModule instance. To create an instance of 160 | // this class use the Create(..) API. 161 | FakeAudioCaptureModule(); 162 | // The destructor is protected because it is reference counted and should not 163 | // be deleted directly. 164 | virtual ~FakeAudioCaptureModule(); 165 | 166 | private: 167 | // Initializes the state of the FakeAudioCaptureModule. This API is called on 168 | // creation by the Create() API. 169 | bool Initialize(); 170 | // SetBuffer() sets all samples in send_buffer_ to `value`. 171 | void SetSendBuffer(int value); 172 | // Resets rec_buffer_. I.e., sets all rec_buffer_ samples to 0. 173 | void ResetRecBuffer(); 174 | // Returns true if rec_buffer_ contains one or more sample greater than or 175 | // equal to `value`. 176 | bool CheckRecBuffer(int value); 177 | 178 | // Returns true/false depending on if recording or playback has been 179 | // enabled/started. 180 | bool ShouldStartProcessing() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); 181 | 182 | // Starts or stops the pushing and pulling of audio frames. 183 | void UpdateProcessing(bool start) RTC_LOCKS_EXCLUDED(mutex_); 184 | 185 | // Starts the periodic calling of ProcessFrame() in a thread safe way. 186 | void StartProcessP(); 187 | // Periodcally called function that ensures that frames are pulled and pushed 188 | // periodically if enabled/started. 189 | void ProcessFrameP() RTC_LOCKS_EXCLUDED(mutex_); 190 | // Pulls frames from the registered webrtc::AudioTransport. 191 | void ReceiveFrameP() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); 192 | // Pushes frames to the registered webrtc::AudioTransport. 193 | void SendFrameP() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); 194 | 195 | // Callback for playout and recording. 196 | webrtc::AudioTransport* audio_callback_ RTC_GUARDED_BY(mutex_); 197 | 198 | bool recording_ RTC_GUARDED_BY( 199 | mutex_); // True when audio is being pushed from the instance. 200 | bool playing_ RTC_GUARDED_BY( 201 | mutex_); // True when audio is being pulled by the instance. 202 | 203 | bool play_is_initialized_; // True when the instance is ready to pull audio. 204 | bool rec_is_initialized_; // True when the instance is ready to push audio. 205 | 206 | // Input to and output from RecordedDataIsAvailable(..) makes it possible to 207 | // modify the current mic level. The implementation does not care about the 208 | // mic level so it just feeds back what it receives. 209 | uint32_t current_mic_level_ RTC_GUARDED_BY(mutex_); 210 | 211 | // next_frame_time_ is updated in a non-drifting manner to indicate the next 212 | // wall clock time the next frame should be generated and received. started_ 213 | // ensures that next_frame_time_ can be initialized properly on first call. 214 | bool started_ RTC_GUARDED_BY(mutex_); 215 | int64_t next_frame_time_ RTC_GUARDED_BY(process_thread_checker_); 216 | 217 | std::unique_ptr process_thread_; 218 | 219 | // Buffer for storing samples received from the webrtc::AudioTransport. 220 | char rec_buffer_[kNumberSamples * kNumberBytesPerSample]; 221 | // Buffer for samples to send to the webrtc::AudioTransport. 222 | char send_buffer_[kNumberSamples * kNumberBytesPerSample]; 223 | 224 | // Counter of frames received that have samples of high enough amplitude to 225 | // indicate that the frames are not faked somewhere in the audio pipeline 226 | // (e.g. by a jitter buffer). 227 | int frames_received_; 228 | 229 | // Protects variables that are accessed from process_thread_ and 230 | // the main thread. 231 | mutable webrtc::Mutex mutex_; 232 | webrtc::SequenceChecker process_thread_checker_{ 233 | webrtc::SequenceChecker::kDetached}; 234 | }; 235 | 236 | #endif // PC_TEST_FAKE_AUDIO_CAPTURE_MODULE_H_ 237 | -------------------------------------------------------------------------------- /scripts/clang-tidy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | #===- run-clang-tidy.py - Parallel clang-tidy runner ---------*- python -*--===# 4 | # 5 | # The LLVM Compiler Infrastructure 6 | # 7 | # This file is distributed under the University of Illinois Open Source 8 | # License. See LICENSE.TXT for details. 9 | # 10 | #===------------------------------------------------------------------------===# 11 | # FIXME: Integrate with clang-tidy-diff.py 12 | 13 | """ 14 | Parallel clang-tidy runner 15 | ========================== 16 | 17 | Runs clang-tidy over all files in a compilation database. Requires clang-tidy 18 | and clang-apply-replacements in $PATH. 19 | 20 | Example invocations. 21 | - Run clang-tidy on all files in the current working directory with a default 22 | set of checks and show warnings in the cpp files and all project headers. 23 | run-clang-tidy.py $PWD 24 | 25 | - Fix all header guards. 26 | run-clang-tidy.py -fix -checks=-*,llvm-header-guard 27 | 28 | - Fix all header guards included from clang-tidy and header guards 29 | for clang-tidy headers. 30 | run-clang-tidy.py -fix -checks=-*,llvm-header-guard extra/clang-tidy \ 31 | -header-filter=extra/clang-tidy 32 | 33 | Compilation database setup: 34 | http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html 35 | """ 36 | 37 | from __future__ import print_function 38 | 39 | import argparse 40 | import glob 41 | import json 42 | import multiprocessing 43 | import os 44 | import re 45 | import shutil 46 | import subprocess 47 | import sys 48 | import tempfile 49 | import threading 50 | import traceback 51 | import yaml 52 | 53 | is_py2 = sys.version[0] == '2' 54 | 55 | if is_py2: 56 | import Queue as queue 57 | else: 58 | import queue as queue 59 | 60 | def find_compilation_database(path): 61 | """Adjusts the directory until a compilation database is found.""" 62 | result = './' 63 | while not os.path.isfile(os.path.join(result, path)): 64 | if os.path.realpath(result) == '/': 65 | print('Error: could not find compilation database.') 66 | sys.exit(1) 67 | result += '../' 68 | return os.path.realpath(result) 69 | 70 | 71 | def make_absolute(f, directory): 72 | if os.path.isabs(f): 73 | return f 74 | return os.path.normpath(os.path.join(directory, f)) 75 | 76 | 77 | def get_tidy_invocation(f, clang_tidy_binary, checks, tmpdir, build_path, 78 | header_filter, line_filter, extra_arg, extra_arg_before, 79 | quiet, config): 80 | """Gets a command line for clang-tidy.""" 81 | start = [clang_tidy_binary] 82 | if header_filter is not None: 83 | start.append('-header-filter=' + header_filter) 84 | else: 85 | # Show warnings in all in-project headers by default. 86 | start.append('-header-filter=^' + build_path + '/.*') 87 | if line_filter is not None: 88 | start.append('-line-filter=' + line_filter) 89 | if checks: 90 | start.append('-checks=' + checks) 91 | if tmpdir is not None: 92 | start.append('-export-fixes') 93 | # Get a temporary file. We immediately close the handle so clang-tidy can 94 | # overwrite it. 95 | (handle, name) = tempfile.mkstemp(suffix='.yaml', dir=tmpdir) 96 | os.close(handle) 97 | start.append(name) 98 | for arg in extra_arg: 99 | start.append('-extra-arg=%s' % arg) 100 | for arg in extra_arg_before: 101 | start.append('-extra-arg-before=%s' % arg) 102 | start.append('-p=' + build_path) 103 | if quiet: 104 | start.append('-quiet') 105 | if config: 106 | start.append('-config=' + config) 107 | start.append(f) 108 | return start 109 | 110 | 111 | def merge_replacement_files(tmpdir, mergefile): 112 | """Merge all replacement files in a directory into a single file""" 113 | # The fixes suggested by clang-tidy >= 4.0.0 are given under 114 | # the top level key 'Diagnostics' in the output yaml files 115 | mergekey="Diagnostics" 116 | merged=[] 117 | for replacefile in glob.iglob(os.path.join(tmpdir, '*.yaml')): 118 | content = yaml.safe_load(open(replacefile, 'r')) 119 | if not content: 120 | continue # Skip empty files. 121 | merged.extend(content.get(mergekey, [])) 122 | 123 | if merged: 124 | # MainSourceFile: The key is required by the definition inside 125 | # include/clang/Tooling/ReplacementsYaml.h, but the value 126 | # is actually never used inside clang-apply-replacements, 127 | # so we set it to '' here. 128 | output = { 'MainSourceFile': '', mergekey: merged } 129 | with open(mergefile, 'w') as out: 130 | yaml.safe_dump(output, out) 131 | else: 132 | # Empty the file: 133 | open(mergefile, 'w').close() 134 | 135 | 136 | def check_clang_apply_replacements_binary(args): 137 | """Checks if invoking supplied clang-apply-replacements binary works.""" 138 | try: 139 | subprocess.check_call([args.clang_apply_replacements_binary, '--version']) 140 | except: 141 | print('Unable to run clang-apply-replacements. Is clang-apply-replacements ' 142 | 'binary correctly specified?', file=sys.stderr) 143 | traceback.print_exc() 144 | sys.exit(1) 145 | 146 | 147 | def apply_fixes(args, tmpdir): 148 | """Calls clang-apply-fixes on a given directory.""" 149 | invocation = [args.clang_apply_replacements_binary] 150 | if args.format: 151 | invocation.append('-format') 152 | if args.style: 153 | invocation.append('-style=' + args.style) 154 | invocation.append(tmpdir) 155 | subprocess.call(invocation) 156 | 157 | 158 | def run_tidy(args, tmpdir, build_path, queue, lock, failed_files): 159 | """Takes filenames out of queue and runs clang-tidy on them.""" 160 | while True: 161 | name = queue.get() 162 | invocation = get_tidy_invocation(name, args.clang_tidy_binary, args.checks, 163 | tmpdir, build_path, args.header_filter, 164 | args.line_filter, 165 | args.extra_arg, args.extra_arg_before, 166 | args.quiet, args.config) 167 | 168 | proc = subprocess.Popen(invocation, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 169 | output, err = proc.communicate() 170 | if proc.returncode != 0: 171 | failed_files.append(name) 172 | with lock: 173 | sys.stdout.write(' '.join(invocation) + '\n' + output.decode('utf-8') + '\n') 174 | if len(err) > 0: 175 | sys.stderr.write(err.decode('utf-8') + '\n') 176 | queue.task_done() 177 | 178 | 179 | def main(): 180 | parser = argparse.ArgumentParser(description='Runs clang-tidy over all files ' 181 | 'in a compilation database. Requires ' 182 | 'clang-tidy and clang-apply-replacements in ' 183 | '$PATH.') 184 | parser.add_argument('-clang-tidy-binary', metavar='PATH', 185 | default='clang-tidy', 186 | help='path to clang-tidy binary') 187 | parser.add_argument('-clang-apply-replacements-binary', metavar='PATH', 188 | default='clang-apply-replacements', 189 | help='path to clang-apply-replacements binary') 190 | parser.add_argument('-checks', default=None, 191 | help='checks filter, when not specified, use clang-tidy ' 192 | 'default') 193 | parser.add_argument('-config', default=None, 194 | help='Specifies a configuration in YAML/JSON format: ' 195 | ' -config="{Checks: \'*\', ' 196 | ' CheckOptions: [{key: x, ' 197 | ' value: y}]}" ' 198 | 'When the value is empty, clang-tidy will ' 199 | 'attempt to find a file named .clang-tidy for ' 200 | 'each source file in its parent directories.') 201 | parser.add_argument('-header-filter', default=None, 202 | help='regular expression matching the names of the ' 203 | 'headers to output diagnostics from. Diagnostics from ' 204 | 'the main file of each translation unit are always ' 205 | 'displayed.') 206 | parser.add_argument('-line-filter', default=None, 207 | help='List of files with line ranges to filter the ' 208 | 'warnings. Can be used together with ' 209 | '-header-filter. The format of the list is a ' 210 | 'JSON array of objects.') 211 | parser.add_argument('-export-fixes', metavar='filename', dest='export_fixes', 212 | help='Create a yaml file to store suggested fixes in, ' 213 | 'which can be applied with clang-apply-replacements.') 214 | parser.add_argument('-j', type=int, default=0, 215 | help='number of tidy instances to be run in parallel.') 216 | parser.add_argument('files', nargs='*', default=['.*'], 217 | help='files to be processed (regex on path)') 218 | parser.add_argument('-fix', action='store_true', help='apply fix-its') 219 | parser.add_argument('-format', action='store_true', help='Reformat code ' 220 | 'after applying fixes') 221 | parser.add_argument('-style', default='file', help='The style of reformat ' 222 | 'code after applying fixes') 223 | parser.add_argument('-p', dest='build_path', 224 | help='Path used to read a compile command database.') 225 | parser.add_argument('-extra-arg', dest='extra_arg', 226 | action='append', default=[], 227 | help='Additional argument to append to the compiler ' 228 | 'command line.') 229 | parser.add_argument('-extra-arg-before', dest='extra_arg_before', 230 | action='append', default=[], 231 | help='Additional argument to prepend to the compiler ' 232 | 'command line.') 233 | parser.add_argument('-quiet', action='store_true', 234 | help='Run clang-tidy in quiet mode') 235 | args = parser.parse_args() 236 | 237 | db_path = 'compile_commands.json' 238 | 239 | if args.build_path is not None: 240 | build_path = args.build_path 241 | else: 242 | # Find our database 243 | build_path = find_compilation_database(db_path) 244 | 245 | try: 246 | invocation = [args.clang_tidy_binary, '-list-checks'] 247 | invocation.append('-p=' + build_path) 248 | if args.checks: 249 | invocation.append('-checks=' + args.checks) 250 | invocation.append('-') 251 | subprocess.check_call(invocation) 252 | except: 253 | print("Unable to run clang-tidy.", file=sys.stderr) 254 | sys.exit(1) 255 | 256 | # Load the database and extract all files. 257 | database = json.load(open(os.path.join(build_path, db_path))) 258 | files = [make_absolute(entry['file'], entry['directory']) 259 | for entry in database] 260 | 261 | max_task = args.j 262 | if max_task == 0: 263 | max_task = multiprocessing.cpu_count() 264 | 265 | tmpdir = None 266 | if args.fix or args.export_fixes: 267 | check_clang_apply_replacements_binary(args) 268 | tmpdir = tempfile.mkdtemp() 269 | 270 | # Build up a big regexy filter from all command line arguments. 271 | file_name_re = re.compile('|'.join(args.files)) 272 | 273 | return_code = 0 274 | try: 275 | # Spin up a bunch of tidy-launching threads. 276 | task_queue = queue.Queue(max_task) 277 | # List of files with a non-zero return code. 278 | failed_files = [] 279 | lock = threading.Lock() 280 | for _ in range(max_task): 281 | t = threading.Thread(target=run_tidy, 282 | args=(args, tmpdir, build_path, task_queue, lock, failed_files)) 283 | t.daemon = True 284 | t.start() 285 | 286 | # Fill the queue with files. 287 | for name in files: 288 | if file_name_re.search(name): 289 | task_queue.put(name) 290 | 291 | # Wait for all threads to be done. 292 | task_queue.join() 293 | if len(failed_files): 294 | return_code = 1 295 | 296 | except KeyboardInterrupt: 297 | # This is a sad hack. Unfortunately subprocess goes 298 | # bonkers with ctrl-c and we start forking merrily. 299 | print('\nCtrl-C detected, goodbye.') 300 | if tmpdir: 301 | shutil.rmtree(tmpdir) 302 | os.kill(0, 9) 303 | 304 | if args.export_fixes: 305 | print('Writing fixes to ' + args.export_fixes + ' ...') 306 | try: 307 | merge_replacement_files(tmpdir, args.export_fixes) 308 | except: 309 | print('Error exporting fixes.\n', file=sys.stderr) 310 | traceback.print_exc() 311 | return_code=1 312 | 313 | if args.fix: 314 | print('Applying fixes ...') 315 | try: 316 | apply_fixes(args, tmpdir) 317 | except: 318 | print('Error applying fixes.\n', file=sys.stderr) 319 | traceback.print_exc() 320 | return_code=1 321 | 322 | if tmpdir: 323 | shutil.rmtree(tmpdir) 324 | sys.exit(return_code) 325 | 326 | if __name__ == '__main__': 327 | main() 328 | -------------------------------------------------------------------------------- /test/src/fakeParameters.cpp: -------------------------------------------------------------------------------- 1 | #include "fakeParameters.hpp" 2 | #include "Utils.hpp" 3 | #include 4 | #include 5 | 6 | json generateRouterRtpCapabilities() 7 | { 8 | auto codecs = json::array(); 9 | auto headerExtensions = json::array(); 10 | auto fecMechanisms = json::array(); 11 | 12 | codecs = R"( 13 | [ 14 | { 15 | "mimeType" : "audio/opus", 16 | "kind" : "audio", 17 | "clockRate" : 48000, 18 | "preferredPayloadType" : 100, 19 | "channels" : 2, 20 | "rtcpFeedback" : [], 21 | "parameters" : 22 | { 23 | "useinbandfec" : 1 24 | } 25 | }, 26 | { 27 | "mimeType" : "video/VP8", 28 | "kind" : "video", 29 | "clockRate" : 90000, 30 | "preferredPayloadType" : 101, 31 | "rtcpFeedback" : 32 | [ 33 | { "type": "nack" }, 34 | { "type": "nack", "parameter": "pli" }, 35 | { "type": "nack", "parameter": "sli" }, 36 | { "type": "nack", "parameter": "rpsi" }, 37 | { "type": "nack", "parameter": "app" }, 38 | { "type": "ccm", "parameter": "fir" }, 39 | { "type": "goog-remb" } 40 | ], 41 | "parameters" : 42 | { 43 | "x-google-start-bitrate" : "1500" 44 | } 45 | }, 46 | { 47 | "mimeType" : "video/rtx", 48 | "kind" : "video", 49 | "clockRate" : 90000, 50 | "preferredPayloadType" : 102, 51 | "rtcpFeedback" : [], 52 | "parameters" : 53 | { 54 | "apt" : 101 55 | } 56 | }, 57 | { 58 | "mimeType" : "video/H264", 59 | "kind" : "video", 60 | "clockRate" : 90000, 61 | "preferredPayloadType" : 103, 62 | "rtcpFeedback" : 63 | [ 64 | { "type": "nack" }, 65 | { "type": "nack", "parameter": "pli" }, 66 | { "type": "nack", "parameter": "sli" }, 67 | { "type": "nack", "parameter": "rpsi" }, 68 | { "type": "nack", "parameter": "app" }, 69 | { "type": "ccm", "parameter": "fir" }, 70 | { "type": "goog-remb" } 71 | ], 72 | "parameters" : 73 | { 74 | "level-asymmetry-allowed" : 1, 75 | "packetization-mode" : 1, 76 | "profile-level-id" : "42e01f" 77 | } 78 | }, 79 | { 80 | "mimeType" : "video/rtx", 81 | "kind" : "video", 82 | "clockRate" : 90000, 83 | "preferredPayloadType" : 104, 84 | "rtcpFeedback" : [], 85 | "parameters" : 86 | { 87 | "apt" : 103 88 | } 89 | } 90 | ])"_json; 91 | 92 | headerExtensions = R"( 93 | [ 94 | { 95 | "kind" : "audio", 96 | "uri" : "urn:ietf:params:rtp-hdrext:ssrc-audio-level", 97 | "preferredId" : 1, 98 | "preferredEncrypt" : false 99 | }, 100 | { 101 | "kind" : "video", 102 | "uri" : "urn:ietf:params:rtp-hdrext:toffset", 103 | "preferredId" : 2, 104 | "preferredEncrypt" : false 105 | }, 106 | { 107 | "kind" : "audio", 108 | "uri" : "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time", 109 | "preferredId" : 3, 110 | "preferredEncrypt" : false 111 | }, 112 | { 113 | "kind" : "video", 114 | "uri" : "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time", 115 | "preferredId" : 3, 116 | "preferredEncrypt" : false 117 | }, 118 | { 119 | "kind" : "video", 120 | "uri" : "urn:3gpp:video-orientation", 121 | "preferredId" : 4, 122 | "preferredEncrypt" : false 123 | }, 124 | { 125 | "kind" : "audio", 126 | "uri" : "urn:ietf:params:rtp-hdrext:sdes:mid", 127 | "preferredId" : 5, 128 | "preferredEncrypt" : false 129 | }, 130 | { 131 | "kind" : "video", 132 | "uri" : "urn:ietf:params:rtp-hdrext:sdes:mid", 133 | "preferredId" : 5, 134 | "preferredEncrypt" : false 135 | }, 136 | { 137 | "kind" : "video", 138 | "uri" : "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id", 139 | "preferredId" : 6, 140 | "preferredEncrypt" : false 141 | } 142 | ])"_json; 143 | 144 | json capabilities = { { "codecs", codecs }, 145 | { "headerExtensions", headerExtensions }, 146 | { "fecMechanisms", fecMechanisms } }; 147 | 148 | return capabilities; 149 | }; 150 | 151 | json generateRtpParametersByKind() 152 | { 153 | /* clang-format off */ 154 | json rtpParametersByKind = 155 | { 156 | { 157 | "audio", 158 | { 159 | { "codecs", json::array() }, 160 | { "headerExtensions", json::array() } 161 | } 162 | }, 163 | { 164 | "video", 165 | { 166 | { "codecs", json::array() }, 167 | { "headerExtensions", json::array() } 168 | } 169 | } 170 | }; 171 | /* clang-format on */ 172 | 173 | auto codecs = generateRouterRtpCapabilities()["codecs"]; 174 | 175 | for (auto& codec : codecs) 176 | { 177 | codec["payloadType"] = codec["preferredPayloadType"]; 178 | codec.erase("preferredPayloadType"); 179 | 180 | auto kind = codec["kind"].get(); 181 | 182 | if (kind == "audio") 183 | rtpParametersByKind["audio"]["codecs"].push_back(codec); 184 | else if (kind == "video") 185 | rtpParametersByKind["video"]["codecs"].push_back(codec); 186 | } 187 | 188 | auto headerExtensions = generateRouterRtpCapabilities()["headerExtensions"]; 189 | for (auto& ext : headerExtensions) 190 | { 191 | ext["id"] = ext["preferredId"]; 192 | ext.erase("preferredId"); 193 | 194 | auto kind = ext["kind"].get(); 195 | 196 | if (kind == "audio") 197 | rtpParametersByKind["audio"]["headerExtensions"].push_back(ext); 198 | else if (kind == "video") 199 | rtpParametersByKind["video"]["headerExtensions"].push_back(ext); 200 | } 201 | 202 | return rtpParametersByKind; 203 | } 204 | 205 | json generateLocalDtlsParameters() 206 | { 207 | return R"( 208 | { 209 | "fingerprints" : 210 | [ 211 | { 212 | "algorithm" : "sha-256", 213 | "value" : "82:5A:68:3D:36:C3:0A:DE:AF:E7:32:43:D2:88:83:57:AC:2D:65:E5:80:C4:B6:FB:AF:1A:A0:21:9F:6D:0C:AD" 214 | } 215 | ], 216 | "role" : "auto" 217 | })"_json; 218 | }; 219 | 220 | json generateTransportRemoteParameters() 221 | { 222 | auto json = R"( 223 | { 224 | "id" : "", 225 | "iceParameters" : 226 | { 227 | "iceLite" : true, 228 | "password" : "yku5ej8nvfaor28lvtrabcx0wkrpkztz", 229 | "usernameFragment" : "h3hk1iz6qqlnqlne" 230 | }, 231 | "iceCandidates" : 232 | [ 233 | { 234 | "family" : "ipv4", 235 | "foundation" : "udpcandidate", 236 | "ip" : "9.9.9.9", 237 | "port" : 40533, 238 | "priority" : 1078862079, 239 | "protocol" : "udp", 240 | "type" : "host" 241 | }, 242 | { 243 | "family" : "ipv6", 244 | "foundation" : "udpcandidate", 245 | "ip" : "9:9:9:9:9:9", 246 | "port" : 41333, 247 | "priority" : 1078862089, 248 | "protocol" : "udp", 249 | "type" : "host" 250 | } 251 | ], 252 | "dtlsParameters" : 253 | { 254 | "fingerprints" : 255 | [ 256 | { 257 | "algorithm" : "sha-256", 258 | "value" : "A9:F4:E0:D2:74:D3:0F:D9:CA:A5:2F:9F:7F:47:FA:F0:C4:72:DD:73:49:D0:3B:14:90:20:51:30:1B:90:8E:71" 259 | }, 260 | { 261 | "algorithm" : "sha-384", 262 | "value" : "03:D9:0B:87:13:98:F6:6D:BC:FC:92:2E:39:D4:E1:97:32:61:30:56:84:70:81:6E:D1:82:97:EA:D9:C1:21:0F:6B:C5:E7:7F:E1:97:0C:17:97:6E:CF:B3:EF:2E:74:B0" 263 | }, 264 | { 265 | "algorithm" : "sha-512", 266 | "value" : "84:27:A4:28:A4:73:AF:43:02:2A:44:68:FF:2F:29:5C:3B:11:9A:60:F4:A8:F0:F5:AC:A0:E3:49:3E:B1:34:53:A9:85:CE:51:9B:ED:87:5E:B8:F4:8E:3D:FA:20:51:B8:96:EE:DA:56:DC:2F:5C:62:79:15:23:E0:21:82:2B:2C" 267 | } 268 | ], 269 | "role" : "auto" 270 | }, 271 | "sctpParameters" : 272 | { 273 | "port" : 5000, 274 | "OS" : 1024, 275 | "MIS" : 1024, 276 | "numStreams" : 2048, 277 | "maxMessageSize" : 2000000 278 | } 279 | })"_json; 280 | 281 | json["id"] = mediasoupclient::Utils::getRandomString(12); 282 | 283 | return json; 284 | }; 285 | 286 | std::string generateProducerRemoteId() 287 | { 288 | return mediasoupclient::Utils::getRandomString(12); 289 | }; 290 | 291 | json generateConsumerRemoteParameters(const std::string& codecMimeType) 292 | { 293 | if (codecMimeType == "audio/opus") 294 | { 295 | auto json = R"( 296 | { 297 | "producerId" : "", 298 | "id" : "", 299 | "kind" : "audio", 300 | "rtpParameters" : 301 | { 302 | "codecs" : 303 | [ 304 | { 305 | "mimeType" : "audio/opus", 306 | "clockRate" : 48000, 307 | "payloadType" : 100, 308 | "channels" : 2, 309 | "rtcpFeedback" : [], 310 | "parameters" : 311 | { 312 | "useinbandfec" : "1" 313 | } 314 | } 315 | ], 316 | "encodings" : 317 | [ 318 | { 319 | "ssrc" : 0 320 | } 321 | ], 322 | "headerExtensions" : 323 | [ 324 | { 325 | "uri" : "urn:ietf:params:rtp-hdrext:ssrc-audio-level", 326 | "id" : 1 327 | } 328 | ], 329 | "rtcp" : 330 | { 331 | "cname" : "", 332 | "reducedSize" : true, 333 | "mux" : true 334 | } 335 | } 336 | })"_json; 337 | 338 | json["producerId"] = mediasoupclient::Utils::getRandomString(12); 339 | json["id"] = mediasoupclient::Utils::getRandomString(12); 340 | json["rtpParameters"]["encodings"][0]["ssrc"] = 341 | mediasoupclient::Utils::getRandomInteger(1000000, 1999999); 342 | json["rtpParameters"]["rtcp"]["cname"] = mediasoupclient::Utils::getRandomString(16); 343 | 344 | return json; 345 | } 346 | else if (codecMimeType == "audio/ISAC") 347 | { 348 | auto json = R"( 349 | { 350 | "producerId" : "", 351 | "id" : "", 352 | "kind" : "audio", 353 | "rtpParameters" : 354 | { 355 | "codecs" : 356 | [ 357 | { 358 | "mimeType" : "audio/ISAC", 359 | "clockRate" : 16000, 360 | "payloadType" : 111, 361 | "channels" : 1, 362 | "rtcpFeedback" : [], 363 | "parameters" : {} 364 | } 365 | ], 366 | "encodings" : 367 | [ 368 | { 369 | "ssrc" : 0 370 | } 371 | ], 372 | "headerExtensions" : 373 | [ 374 | { 375 | "uri" : "urn:ietf:params:rtp-hdrext:ssrc-audio-level", 376 | "id" : 1 377 | } 378 | ], 379 | "rtcp" : 380 | { 381 | "cname" : "", 382 | "reducedSize" : true, 383 | "mux" : true 384 | } 385 | } 386 | })"_json; 387 | 388 | json["producerId"] = mediasoupclient::Utils::getRandomString(12); 389 | json["id"] = mediasoupclient::Utils::getRandomString(12); 390 | json["rtpParameters"]["encodings"][0]["ssrc"] = 391 | mediasoupclient::Utils::getRandomInteger(1000000, 1999999); 392 | json["rtpParameters"]["rtcp"]["cname"] = mediasoupclient::Utils::getRandomString(16); 393 | 394 | return json; 395 | } 396 | else if (codecMimeType == "video/VP8") 397 | { 398 | auto json = R"( 399 | { 400 | "producerId" : "", 401 | "id" : "", 402 | "kind" : "video", 403 | "rtpParameters" : 404 | { 405 | "codecs" : 406 | [ 407 | { 408 | "mimeType" : "video/VP8", 409 | "clockRate" : 90000, 410 | "payloadType" : 101, 411 | "rtcpFeedback" : 412 | [ 413 | { "type": "nack" }, 414 | { "type": "nack", "parameter": "pli" }, 415 | { "type": "nack", "parameter": "sli" }, 416 | { "type": "nack", "parameter": "rpsi" }, 417 | { "type": "nack", "parameter": "app" }, 418 | { "type": "ccm", "parameter": "fir" }, 419 | { "type": "goog-remb" } 420 | ], 421 | "parameters" : 422 | { 423 | "x-google-start-bitrate" : "1500" 424 | } 425 | }, 426 | { 427 | "mimeType" : "video/rtx", 428 | "clockRate" : 90000, 429 | "payloadType" : 102, 430 | "rtcpFeedback" : [], 431 | "parameters" : 432 | { 433 | "apt" : 101 434 | } 435 | } 436 | ], 437 | "encodings" : 438 | [ 439 | { 440 | "ssrc" : 0, 441 | "rtx" : 442 | { 443 | "ssrc" : 0 444 | } 445 | } 446 | ], 447 | "headerExtensions" : 448 | [ 449 | { 450 | "uri" : "urn:ietf:params:rtp-hdrext:toffset", 451 | "id" : 2 452 | }, 453 | { 454 | "uri" : "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time", 455 | "id" : 3 456 | } 457 | ], 458 | "rtcp" : 459 | { 460 | "cname" : "", 461 | "reducedSize" : true, 462 | "mux" : true 463 | } 464 | } 465 | })"_json; 466 | 467 | json["producerId"] = mediasoupclient::Utils::getRandomString(12); 468 | json["id"] = mediasoupclient::Utils::getRandomString(12); 469 | json["rtpParameters"]["encodings"][0]["ssrc"] = 470 | mediasoupclient::Utils::getRandomInteger(2000000, 2999999); 471 | json["rtpParameters"]["encodings"][0]["rtx"]["ssrc"] = 472 | mediasoupclient::Utils::getRandomInteger(3000000, 3999999); 473 | json["rtpParameters"]["rtcp"]["cname"] = mediasoupclient::Utils::getRandomString(16); 474 | 475 | return json; 476 | } 477 | 478 | return json::object(); 479 | }; 480 | -------------------------------------------------------------------------------- /test/src/RemoteSdp.test.cpp: -------------------------------------------------------------------------------- 1 | #include "sdp/RemoteSdp.hpp" 2 | #include "helpers.hpp" 3 | #include "sdptransform.hpp" 4 | #include 5 | 6 | TEST_CASE("SendRemoteSdp", "[SendRemoteSdp]") 7 | { 8 | SECTION("audio only", "[CreateAnswerSdp]") 9 | { 10 | auto iceParameters = R"( 11 | { 12 | "usernameFragment" : "5I2uVefP13X1wzOY", 13 | "password" : "e46UjXntt0K/xTncQcDBQePn" 14 | })"_json; 15 | 16 | auto iceCandidates = R"( 17 | [ 18 | { 19 | "foundation" : "1162875081", 20 | "component" : 1, 21 | "protocol" : "udp", 22 | "priority" : 2113937151, 23 | "ip" : "192.168.34.75", 24 | "port" : 60017, 25 | "type" : "host", 26 | "generation" : 0 27 | } 28 | ])"_json; 29 | 30 | auto dtlsParameters = R"( 31 | { 32 | "role" : "client", 33 | "fingerprints" : 34 | [ 35 | { 36 | "algorithm" : "sha-256", 37 | "value" : "79:14:AB:AB:93:7F:07:E8:91:1A:11:16:36:D0:11:66:C4:4F:31:A0:74:46:65:58:70:E5:09:95:48:F4:4B:D9" 38 | } 39 | ] 40 | })"_json; 41 | 42 | auto sctpParameters = R"( 43 | { 44 | "port" : 5000, 45 | "OS" : 1024, 46 | "MIS" : 1024, 47 | "maxMessageSize" : 65536 48 | })"_json; 49 | 50 | auto audioCodecs = R"( 51 | [ 52 | { 53 | "mimeType" : "audio/PCMU", 54 | "kind" : "audio", 55 | "clockRate" : 8000, 56 | "payloadType" : 0, 57 | "rtcpFeedback" : [], 58 | "parameters" : {} 59 | }, 60 | { 61 | "mimeType" : "audio/opus", 62 | "kind" : "audio", 63 | "clockRate" : 48000, 64 | "payloadType" : 96, 65 | "rtcpFeedback" : [], 66 | "parameters" : 67 | { 68 | "usedtx" : "1", 69 | "useinbandfec" : "1" 70 | } 71 | } 72 | ])"_json; 73 | 74 | auto headerExtensions = R"( 75 | [ 76 | { 77 | "value" : 1, 78 | "uri" : "URI-toffset" 79 | } 80 | ])"_json; 81 | 82 | /* clang-format off */ 83 | json sendingRtpParametersByKind = 84 | { 85 | { 86 | "audio", 87 | { 88 | { "codecs", audioCodecs }, 89 | { "headerExtensions", headerExtensions } 90 | } 91 | } 92 | }; 93 | 94 | /* clang-format on */ 95 | 96 | auto* remoteSdp = new mediasoupclient::Sdp::RemoteSdp( 97 | iceParameters, iceCandidates, dtlsParameters, sctpParameters); 98 | 99 | auto sdp = helpers::readFile("test/data/jssip.sdp"); 100 | auto localSdpObj = sdptransform::parse(sdp); 101 | auto sdpAnswer = remoteSdp->GetSdp(); 102 | auto parsed = sdptransform::parse(sdpAnswer); 103 | 104 | REQUIRE(parsed.find("fingerprint") != parsed.end()); 105 | REQUIRE( 106 | parsed["fingerprint"]["hash"] == 107 | "79:14:AB:AB:93:7F:07:E8:91:1A:11:16:36:D0:11:66:C4:4F:31:A0:74:46:65:58:70:E5:09:95:48:F4:4B:D9"); 108 | REQUIRE(parsed["fingerprint"]["type"] == "sha-256"); 109 | 110 | REQUIRE(parsed.find("groups") != parsed.end()); 111 | REQUIRE(parsed["groups"].size() == 1); 112 | // REQUIRE(parsed["groups"][0]["mids"] == "audio"); 113 | REQUIRE(parsed["groups"][0]["type"] == "BUNDLE"); 114 | 115 | // REQUIRE(parsed.find("media") != parsed.end()); 116 | // REQUIRE(parsed["media"].size() == 1); 117 | 118 | // REQUIRE(parsed["media"][0].find("candidates") != parsed["media"][0].end()); 119 | // REQUIRE(parsed["media"][0]["candidates"].size() == 1); 120 | // REQUIRE(parsed["media"][0]["candidates"][0]["component"] == 1); 121 | // REQUIRE(parsed["media"][0]["candidates"][0]["foundation"] == "1162875081"); 122 | // REQUIRE(parsed["media"][0]["candidates"][0]["ip"] == "192.168.34.75"); 123 | // REQUIRE(parsed["media"][0]["candidates"][0]["port"] == 60017); 124 | // REQUIRE(parsed["media"][0]["candidates"][0]["priority"] == 2113937151); 125 | // REQUIRE(parsed["media"][0]["candidates"][0]["transport"] == "udp"); 126 | // REQUIRE(parsed["media"][0]["candidates"][0]["type"] == "host"); 127 | 128 | // REQUIRE(parsed["media"][0].find("connection") != parsed["media"][0].end()); 129 | // REQUIRE(parsed["media"][0]["connection"]["ip"] == "127.0.0.1"); 130 | // REQUIRE(parsed["media"][0]["connection"]["version"] == 4); 131 | 132 | // REQUIRE(parsed["media"][0]["direction"] == "recvonly"); 133 | // REQUIRE(parsed["media"][0]["endOfCandidates"] == "end-of-candidates"); 134 | 135 | // REQUIRE(parsed["media"][0].find("fmtp") != parsed["media"][0].end()); 136 | // REQUIRE(parsed["media"][0]["fmtp"].size() == 1); 137 | // REQUIRE(parsed["media"][0]["fmtp"][0]["config"] == "usedtx=1;useinbandfec=1"); 138 | // REQUIRE(parsed["media"][0]["fmtp"][0]["payload"] == 96); 139 | 140 | // REQUIRE(parsed["media"][0]["iceOptions"] == "renomination"); 141 | // REQUIRE(parsed["media"][0]["icePwd"] == "e46UjXntt0K/xTncQcDBQePn"); 142 | // REQUIRE(parsed["media"][0]["iceUfrag"] == "5I2uVefP13X1wzOY"); 143 | 144 | // REQUIRE(parsed["media"][0]["mid"] == "audio"); 145 | // REQUIRE(parsed["media"][0]["payloads"] == "0 96"); 146 | // REQUIRE(parsed["media"][0]["port"] == 7); 147 | // REQUIRE(parsed["media"][0]["protocol"] == "RTP/SAVPF"); 148 | // REQUIRE(parsed["media"][0]["rtcpMux"] == "rtcp-mux"); 149 | // REQUIRE(parsed["media"][0]["rtcpRsize"] == "rtcp-rsize"); 150 | // REQUIRE(parsed["media"][0]["setup"] == "active"); 151 | // REQUIRE(parsed["media"][0]["type"] == "audio"); 152 | 153 | // REQUIRE(parsed["media"][0].find("rtp") != parsed["media"][0].end()); 154 | // REQUIRE(parsed["media"][0]["rtp"].size() == 2); 155 | // REQUIRE(parsed["media"][0]["rtp"][0]["codec"] == "PCMU"); 156 | // REQUIRE(parsed["media"][0]["rtp"][0]["payload"] == 0); 157 | // REQUIRE(parsed["media"][0]["rtp"][0]["rate"] == 8000); 158 | // REQUIRE(parsed["media"][0]["rtp"][1]["codec"] == "opus"); 159 | // REQUIRE(parsed["media"][0]["rtp"][1]["payload"] == 96); 160 | // REQUIRE(parsed["media"][0]["rtp"][1]["rate"] == 48000); 161 | 162 | REQUIRE(parsed.find("msidSemantic") != parsed.end()); 163 | REQUIRE(parsed["msidSemantic"]["semantic"] == "WMS"); 164 | REQUIRE(parsed["msidSemantic"]["token"] == "*"); 165 | 166 | REQUIRE(parsed["name"] == "-"); 167 | 168 | REQUIRE(parsed.find("origin") != parsed.end()); 169 | REQUIRE(parsed["origin"]["address"] == "0.0.0.0"); 170 | REQUIRE(parsed["origin"]["ipVer"] == 4); 171 | REQUIRE(parsed["origin"]["netType"] == "IN"); 172 | REQUIRE(parsed["origin"]["sessionVersion"] == 1); 173 | REQUIRE(parsed["origin"]["username"] == "libmediasoupclient"); 174 | 175 | REQUIRE(parsed.find("timing") != parsed.end()); 176 | REQUIRE(parsed["timing"]["start"] == 0); 177 | REQUIRE(parsed["timing"]["stop"] == 0); 178 | 179 | REQUIRE(parsed["version"] == 0); 180 | 181 | delete remoteSdp; 182 | } 183 | 184 | SECTION("audio and video", "[CreateAnswerSdp]") 185 | { 186 | auto iceParameters = R"( 187 | { 188 | "usernameFragment" : "5I2uVefP13X1wzOY", 189 | "password" : "e46UjXntt0K/xTncQcDBQePn" 190 | })"_json; 191 | 192 | auto iceCandidates = R"( 193 | [ 194 | { 195 | "foundation" : "1162875081", 196 | "component" : 1, 197 | "protocol" : "udp", 198 | "priority" : 2113937151, 199 | "ip" : "192.168.34.75", 200 | "port" : 60017, 201 | "type" : "host", 202 | "generation" : 0 203 | } 204 | ])"_json; 205 | 206 | auto dtlsParameters = R"( 207 | { 208 | "role" : "client", 209 | "fingerprints" : 210 | [ 211 | { 212 | "algorithm" : "sha-256", 213 | "value" : "79:14:AB:AB:93:7F:07:E8:91:1A:11:16:36:D0:11:66:C4:4F:31:A0:74:46:65:58:70:E5:09:95:48:F4:4B:D9" 214 | } 215 | ] 216 | })"_json; 217 | 218 | auto sctpParameters = R"( 219 | { 220 | "port" : 5000, 221 | "OS" : 1024, 222 | "MIS" : 1024, 223 | "maxMessageSize" : 65536 224 | })"_json; 225 | 226 | auto audioCodecs = R"( 227 | [ 228 | { 229 | "mimeType" : "audio/PCMU", 230 | "kind" : "audio", 231 | "clockRate" : 8000, 232 | "payloadType" : 0, 233 | "rtcpFeedback" : [], 234 | "parameters" : {} 235 | }, 236 | { 237 | "mimeType" : "audio/opus", 238 | "kind" : "audio", 239 | "clockRate" : 48000, 240 | "payloadType" : 96, 241 | "rtcpFeedback" : [], 242 | "parameters" : 243 | { 244 | "usedtx" : "1", 245 | "useinbandfec" : "1" 246 | } 247 | } 248 | ])"_json; 249 | 250 | auto headerExtensions = R"( 251 | [ 252 | { 253 | "value" : 1, 254 | "uri" : "URI-toffset" 255 | } 256 | ])"_json; 257 | 258 | /* clang-format off */ 259 | json sendingRtpParametersByKind = 260 | { 261 | { 262 | "audio", 263 | { 264 | { "codecs", audioCodecs }, 265 | { "headerExtensions", headerExtensions } 266 | } 267 | } 268 | }; 269 | 270 | json transportRemoteParameters = 271 | { 272 | { "iceParameters", iceParameters }, 273 | { "iceCandidates", iceCandidates }, 274 | { "dtlsParameters", dtlsParameters } 275 | }; 276 | /* clang-format on */ 277 | 278 | auto* remoteSdp = new mediasoupclient::Sdp::RemoteSdp( 279 | iceParameters, iceCandidates, dtlsParameters, sctpParameters); 280 | 281 | auto sdp = helpers::readFile("test/data/audio_video.sdp"); 282 | auto localSdpObj = sdptransform::parse(sdp); 283 | 284 | auto sdpAnswer = remoteSdp->GetSdp(); 285 | auto parsed = sdptransform::parse(sdpAnswer); 286 | 287 | REQUIRE(parsed.find("fingerprint") != parsed.end()); 288 | REQUIRE( 289 | parsed["fingerprint"]["hash"] == 290 | "79:14:AB:AB:93:7F:07:E8:91:1A:11:16:36:D0:11:66:C4:4F:31:A0:74:46:65:58:70:E5:09:95:48:F4:4B:D9"); 291 | REQUIRE(parsed["fingerprint"]["type"] == "sha-256"); 292 | 293 | REQUIRE(parsed.find("groups") != parsed.end()); 294 | REQUIRE(parsed["groups"].size() == 1); 295 | // REQUIRE(parsed["groups"][0]["mids"] == "audio video"); 296 | REQUIRE(parsed["groups"][0]["type"] == "BUNDLE"); 297 | 298 | // REQUIRE(parsed.find("media") != parsed.end()); 299 | // REQUIRE(parsed["media"].size() == 2); 300 | 301 | // REQUIRE(parsed["media"][0].find("candidates") != parsed["media"][0].end()); 302 | // REQUIRE(parsed["media"][0]["candidates"].size() == 1); 303 | // REQUIRE(parsed["media"][0]["candidates"][0]["component"] == 1); 304 | // REQUIRE(parsed["media"][0]["candidates"][0]["foundation"] == "1162875081"); 305 | // REQUIRE(parsed["media"][0]["candidates"][0]["ip"] == "192.168.34.75"); 306 | // REQUIRE(parsed["media"][0]["candidates"][0]["port"] == 60017); 307 | // REQUIRE(parsed["media"][0]["candidates"][0]["priority"] == 2113937151); 308 | // REQUIRE(parsed["media"][0]["candidates"][0]["transport"] == "udp"); 309 | // REQUIRE(parsed["media"][0]["candidates"][0]["type"] == "host"); 310 | 311 | // REQUIRE(parsed["media"][0].find("connection") != parsed["media"][0].end()); 312 | // REQUIRE(parsed["media"][0]["connection"]["ip"] == "127.0.0.1"); 313 | // REQUIRE(parsed["media"][0]["connection"]["version"] == 4); 314 | 315 | // REQUIRE(parsed["media"][0]["direction"] == "recvonly"); 316 | // REQUIRE(parsed["media"][0]["endOfCandidates"] == "end-of-candidates"); 317 | 318 | // REQUIRE(parsed["media"][0].find("fmtp") != parsed["media"][0].end()); 319 | // REQUIRE(parsed["media"][0]["fmtp"].size() == 1); 320 | // REQUIRE(parsed["media"][0]["fmtp"][0]["config"] == "usedtx=1;useinbandfec=1"); 321 | // REQUIRE(parsed["media"][0]["fmtp"][0]["payload"] == 96); 322 | 323 | // REQUIRE(parsed["media"][0]["iceOptions"] == "renomination"); 324 | // REQUIRE(parsed["media"][0]["icePwd"] == "e46UjXntt0K/xTncQcDBQePn"); 325 | // REQUIRE(parsed["media"][0]["iceUfrag"] == "5I2uVefP13X1wzOY"); 326 | 327 | // REQUIRE(parsed["media"][0]["mid"] == "audio"); 328 | // REQUIRE(parsed["media"][0]["payloads"] == "0 96"); 329 | // REQUIRE(parsed["media"][0]["port"] == 7); 330 | // REQUIRE(parsed["media"][0]["protocol"] == "RTP/SAVPF"); 331 | // REQUIRE(parsed["media"][0]["rtcpMux"] == "rtcp-mux"); 332 | // REQUIRE(parsed["media"][0]["rtcpRsize"] == "rtcp-rsize"); 333 | // REQUIRE(parsed["media"][0]["setup"] == "active"); 334 | // REQUIRE(parsed["media"][0]["type"] == "audio"); 335 | 336 | // REQUIRE(parsed["media"][0].find("rtp") != parsed["media"][0].end()); 337 | // REQUIRE(parsed["media"][0]["rtp"].size() == 2); 338 | // REQUIRE(parsed["media"][0]["rtp"][0]["codec"] == "PCMU"); 339 | // REQUIRE(parsed["media"][0]["rtp"][0]["payload"] == 0); 340 | // REQUIRE(parsed["media"][0]["rtp"][0]["rate"] == 8000); 341 | // REQUIRE(parsed["media"][0]["rtp"][1]["codec"] == "opus"); 342 | // REQUIRE(parsed["media"][0]["rtp"][1]["payload"] == 96); 343 | // REQUIRE(parsed["media"][0]["rtp"][1]["rate"] == 48000); 344 | 345 | REQUIRE(parsed.find("msidSemantic") != parsed.end()); 346 | REQUIRE(parsed["msidSemantic"]["semantic"] == "WMS"); 347 | REQUIRE(parsed["msidSemantic"]["token"] == "*"); 348 | 349 | REQUIRE(parsed["name"] == "-"); 350 | 351 | REQUIRE(parsed.find("origin") != parsed.end()); 352 | REQUIRE(parsed["origin"]["address"] == "0.0.0.0"); 353 | REQUIRE(parsed["origin"]["ipVer"] == 4); 354 | REQUIRE(parsed["origin"]["netType"] == "IN"); 355 | REQUIRE(parsed["origin"]["sessionVersion"] == 1); 356 | REQUIRE(parsed["origin"]["username"] == "libmediasoupclient"); 357 | 358 | REQUIRE(parsed.find("timing") != parsed.end()); 359 | REQUIRE(parsed["timing"]["start"] == 0); 360 | REQUIRE(parsed["timing"]["stop"] == 0); 361 | 362 | REQUIRE(parsed["version"] == 0); 363 | 364 | delete remoteSdp; 365 | } 366 | } 367 | --------------------------------------------------------------------------------