From 3fb43c5fefbb5bd1178c909e30e09ff277a2b5e8 Mon Sep 17 00:00:00 2001 From: baigao-X <1007668733@qq.com> Date: Sat, 20 Sep 2025 16:23:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0webrtc=E4=BB=A3?= =?UTF-8?q?=E7=90=86=E6=8B=89=E6=B5=81=20(#4389)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 增加客户端模式,支持主动拉流、推流: - addStreamProxy接口新增支持whep主动拉流,拉流地址目前只兼容zlm的whep url。 - addStreamPusherProxy接口新增支持whip主动推流,推流地址目前只兼容zlm的whip url。 - 以上推流url格式为webrtc[s]://server_host:server_port/app/stream_id?key=value, 内部会自动转换为http[s]://server_host:server_port/index/api/[whip/whep]?app=app&stream=stream_id&key=value。 - 增加WebRtc p2p 模式: - 增加 ICE FULL模式。 - 增加STUN/TURN 服务器。 - 增加websocket 信令。 - 增加P2P代理拉流。 --------- Co-authored-by: xia-chu <771730766@qq.com> Co-authored-by: mtdxc Co-authored-by: cqm --- .github/workflows/macos.yml | 9 +- 3rdpart/jsoncpp | 2 +- CMakeLists.txt | 11 + README.md | 4 +- README_en.md | 4 +- conf/config.ini | 21 +- postman/ZLMediaKit.postman_collection.json | 148 + server/WebApi.cpp | 212 +- server/WebApi.h | 592 +-- server/main.cpp | 17 + src/Common/macros.cpp | 2 +- src/Common/macros.h | 1 + src/Http/HttpBody.cpp | 2 +- src/Player/MediaPlayer.h | 1 + src/Player/PlayerBase.cpp | 9 +- src/Player/PlayerProxy.cpp | 4 + src/Player/PlayerProxy.h | 1 + src/Pusher/PusherBase.cpp | 11 +- src/Pusher/PusherBase.h | 6 +- src/Record/HlsMaker.cpp | 2 +- src/Record/Recorder.h | 5 + src/Rtmp/RtmpPusher.h | 2 +- src/Srt/SrtCaller.cpp | 4 +- srt/Crypto.cpp | 146 +- srt/Packet.cpp | 2 +- srt/SrtSession.hpp | 58 +- srt/SrtTransport.cpp | 2 +- srt/SrtTransport.hpp | 4 +- webrtc/DtlsTransport.cpp | 2887 +++++++------- webrtc/DtlsTransport.hpp | 508 +-- webrtc/IceServer.cpp | 528 --- webrtc/IceServer.hpp | 138 - webrtc/IceSession.cpp | 139 + webrtc/IceSession.hpp | 70 + webrtc/IceTransport.cpp | 2026 ++++++++++ webrtc/IceTransport.hpp | 754 ++++ webrtc/Nack.h | 180 +- webrtc/RtpExt.cpp | 1318 +++---- webrtc/RtpMap.h | 210 ++ webrtc/SctpAssociation.hpp | 6 +- webrtc/Sdp.cpp | 3983 ++++++++++---------- webrtc/Sdp.h | 1555 ++++---- webrtc/SrtpSession.hpp | 129 +- webrtc/StunPacket.cpp | 1631 ++++---- webrtc/StunPacket.hpp | 882 ++++- webrtc/USAGE.md | 256 ++ webrtc/Utils.hpp | 118 - webrtc/WebRtcClient.cpp | 303 ++ webrtc/WebRtcClient.h | 105 + webrtc/WebRtcEchoTest.cpp | 2 + webrtc/WebRtcEchoTest.h | 4 +- webrtc/WebRtcPlayer.cpp | 666 ++-- webrtc/WebRtcPlayer.h | 329 +- webrtc/WebRtcProxyPlayer.cpp | 126 + webrtc/WebRtcProxyPlayer.h | 56 + webrtc/WebRtcProxyPlayerImp.h | 43 + webrtc/WebRtcProxyPusher.cpp | 92 + webrtc/WebRtcProxyPusher.h | 51 + webrtc/WebRtcPusher.cpp | 401 +- webrtc/WebRtcPusher.h | 200 +- webrtc/WebRtcSession.cpp | 329 +- webrtc/WebRtcSession.h | 16 +- webrtc/WebRtcSignalingMsg.cpp | 49 + webrtc/WebRtcSignalingMsg.h | 58 + webrtc/WebRtcSignalingPeer.cpp | 687 ++++ webrtc/WebRtcSignalingPeer.h | 141 + webrtc/WebRtcSignalingSession.cpp | 488 +++ webrtc/WebRtcSignalingSession.h | 85 + webrtc/WebRtcTransport.cpp | 3412 +++++++++-------- webrtc/WebRtcTransport.h | 849 +++-- webrtc/readme.md | 7 - webrtc/webrtcSignal.txt | 132 + 72 files changed, 16912 insertions(+), 10319 deletions(-) delete mode 100644 webrtc/IceServer.cpp delete mode 100644 webrtc/IceServer.hpp create mode 100644 webrtc/IceSession.cpp create mode 100644 webrtc/IceSession.hpp create mode 100644 webrtc/IceTransport.cpp create mode 100644 webrtc/IceTransport.hpp create mode 100644 webrtc/RtpMap.h create mode 100644 webrtc/USAGE.md delete mode 100644 webrtc/Utils.hpp create mode 100755 webrtc/WebRtcClient.cpp create mode 100755 webrtc/WebRtcClient.h create mode 100755 webrtc/WebRtcProxyPlayer.cpp create mode 100755 webrtc/WebRtcProxyPlayer.h create mode 100755 webrtc/WebRtcProxyPlayerImp.h create mode 100755 webrtc/WebRtcProxyPusher.cpp create mode 100755 webrtc/WebRtcProxyPusher.h create mode 100644 webrtc/WebRtcSignalingMsg.cpp create mode 100644 webrtc/WebRtcSignalingMsg.h create mode 100644 webrtc/WebRtcSignalingPeer.cpp create mode 100644 webrtc/WebRtcSignalingPeer.h create mode 100644 webrtc/WebRtcSignalingSession.cpp create mode 100644 webrtc/WebRtcSignalingSession.h create mode 100644 webrtc/webrtcSignal.txt diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index f12ac283..2cd38dd3 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -18,10 +18,15 @@ jobs: with: vcpkgDirectory: '${{github.workspace}}/vcpkg' vcpkgTriplet: arm64-osx - # 2024.06.01 - vcpkgGitCommitId: '47364fbc300756f64f7876b549d9422d5f3ec0d3' + # 2025.07.11 + vcpkgGitCommitId: 'efcfaaf60d7ec57a159fc3110403d939bfb69729' vcpkgArguments: 'openssl libsrtp[openssl] usrsctp' + - name: 安装指定 CMake + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: '3.30.5' + - name: 编译 uses: lukka/run-cmake@v3 with: diff --git a/3rdpart/jsoncpp b/3rdpart/jsoncpp index 69098a18..ca98c984 160000 --- a/3rdpart/jsoncpp +++ b/3rdpart/jsoncpp @@ -1 +1 @@ -Subproject commit 69098a18b9af0c47549d9a271c054d13ca92b006 +Subproject commit ca98c98457b1163cca1f7d8db62827c115fec6d1 diff --git a/CMakeLists.txt b/CMakeLists.txt index 8190bd83..1416357e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -472,6 +472,17 @@ if(ENABLE_SRT) update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_SRT) endif() +if(ENABLE_WEBRTC) + # 查找 srtp 是否安装 + find_package(SRTP QUIET) + if(SRTP_FOUND AND ENABLE_OPENSSL) + message(STATUS "found library: ${SRTP_LIBRARIES}, ENABLE_WEBRTC defined") + update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_WEBRTC) + else() + set(ENABLE_WEBRTC OFF) + message(WARNING "srtp 未找到, WebRTC 相关功能打开失败") + endif() +endif() # ---------------------------------------------------------------------------- # Solution folders: # ---------------------------------------------------------------------------- diff --git a/README.md b/README.md index 96957c50..d11a54af 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ ## 功能清单 ### 功能一览 -image +功能预览 - RTSP[S] - RTSP[S] 服务器,支持RTMP/MP4/HLS转RTSP[S],支持亚马逊echo show这样的设备 @@ -131,6 +131,8 @@ - 支持webrtc over tcp模式 - 优秀的nack、jitter buffer算法, 抗丢包能力卓越 - 支持whip/whep协议 + - [支持ice-full,支持作为webrtc客户端拉流、推流以及p2p模式](./webrtc/USAGE.md) + - [SRT支持](./srt/srt.md) - 其他 - 支持丰富的restful api以及web hook事件 diff --git a/README_en.md b/README_en.md index 15a2448d..d9b68db4 100644 --- a/README_en.md +++ b/README_en.md @@ -45,7 +45,7 @@ ## Feature List ### Overview of Features -Overview of Features +Overview of Features - RTSP[S] - RTSP[S] server, supports RTMP/MP4/HLS to RTSP[S] conversion, supports devices such as Amazon Echo Show @@ -124,6 +124,8 @@ - Supports WebRTC over TCP mode - Excellent NACK and jitter buffer algorithms with outstanding packet loss resistance - Supports WHIP/WHEP protocols + - [Supports ice-full, works as a WebRTC client for pulling streams, pushing streams, and P2P mode](./webrtc/USAGE.md) + - [SRT support](./srt/srt.md) - Others - Supports rich RESTful APIs and webhook events diff --git a/conf/config.ini b/conf/config.ini index 80455f4e..6823b14f 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -346,11 +346,30 @@ udp_recv_socket_buffer=4194304 merge_frame=1 [rtc] +#webrtc 信令服务器端口 +signalingPort=3000 +signalingSslPort=3001 +#STUN/TURN服务器端口 +icePort=3478 +iceTcpPort=3478 +#STUN/TURN端口是否使能TURN服务 +enableTurn=1 +#ICE传输策略:0=不限制(默认),1=仅支持Relay转发,2=仅支持P2P直连 +iceTransportPolicy=0 +#STUN/TURN 服务Ice密码 +iceUfrag=ZLMediaKit +icePwd=ZLMediaKit +#TURN服务分配端口池 +portRange=50000-65000 #rtc播放推流、播放超时时间 timeoutSec=15 #本机对rtc客户端的可见ip,作为服务器时一般为公网ip,可有多个,用','分开,当置空时,会自动获取网卡ip #同时支持环境变量,以$开头,如"$EXTERN_IP"; 请参考:https://github.com/ZLMediaKit/ZLMediaKit/pull/1786 externIP= +#当指定了interfaces,ICE服务器会使用指定网卡bind socket +#以解决公网IP使用弹性公网IP配置实现(部署机器无法bind该公网ip的问题) +#支持环境变量,以$开头,如"$PRIVATE_IP" +interfaces= #rtc udp服务器监听端口号,所有rtc客户端将通过该端口传输stun/dtls/srtp/srtcp数据, #该端口是多线程的,同时支持客户端网络切换导致的连接迁移 #需要注意的是,如果服务器在nat内,需要做端口映射时,必须确保外网映射端口跟该端口一致 @@ -358,7 +377,7 @@ port=8000 #rtc tcp服务器监听端口号,在udp 不通的情况下,会使用tcp传输数据 #该端口是多线程的,同时支持客户端网络切换导致的连接迁移 #需要注意的是,如果服务器在nat内,需要做端口映射时,必须确保外网映射端口跟该端口一致 -tcpPort = 8000 +tcpPort=8000 #设置remb比特率,非0时关闭twcc并开启remb。该设置在rtc推流时有效,可以控制推流画质 #目前已经实现twcc自动调整码率,关闭remb根据真实网络状况调整码率 rembBitRate=0 diff --git a/postman/ZLMediaKit.postman_collection.json b/postman/ZLMediaKit.postman_collection.json index 5a6a61ae..953c81b4 100644 --- a/postman/ZLMediaKit.postman_collection.json +++ b/postman/ZLMediaKit.postman_collection.json @@ -2732,6 +2732,154 @@ } }, "response": [] + }, + { + "name": "WebRTC-注册到信令服务器(addWebrtcRoomKeeper)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{ZLMediaKit_URL}}/index/api/addWebrtcRoomKeeper?secret={{ZLMediaKit_secret}}&server_host=127.0.0.1&server_port=3000&room_id=peer_1", + "host": [ + "{{ZLMediaKit_URL}}" + ], + "path": [ + "index", + "api", + "addWebrtcRoomKeeper" + ], + "query": [ + { + "key": "secret", + "value": "{{ZLMediaKit_secret}}" + }, + { + "key": "server_host", + "value": "127.0.0.1", + "description": "要注册到的信令服务器地址" + }, + { + "key": "server_port", + "value": "3000", + "description": "要注册到的信令服务器端口" + }, + { + "key": "room_id", + "value": "peer_1", + "description": "要注册到的roomid" + } + ] + } + }, + "response": [] + }, + { + "name": "WebRTC-从信令服务器注销(delWebrtcRoomKeeper)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{ZLMediaKit_URL}}/index/api/delWebrtcRoomKeeper?secret={{ZLMediaKit_secret}}&room_key=", + "host": [ + "{{ZLMediaKit_URL}}" + ], + "path": [ + "index", + "api", + "delWebrtcRoomKeeper" + ], + "query": [ + { + "key": "secret", + "value": "{{ZLMediaKit_secret}}" + }, + { + "key": "room_key", + "value": "" + } + ] + } + }, + "response": [] + }, + { + "name": "WebRTC-Peer查看注册信息(listWebrtcRoomKeepers)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{ZLMediaKit_URL}}/index/api/listWebrtcRoomKeepers?secret={{ZLMediaKit_secret}}", + "host": [ + "{{ZLMediaKit_URL}}" + ], + "path": [ + "index", + "api", + "listWebrtcRoomKeepers" + ], + "query": [ + { + "key": "secret", + "value": "{{ZLMediaKit_secret}}" + } + ] + } + }, + "response": [] + }, + { + "name": "WebRTC-信令服务器查看注册信息(listWebrtcRooms)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{ZLMediaKit_URL}}/index/api/listWebrtcRooms?secret={{ZLMediaKit_secret}}", + "host": [ + "{{ZLMediaKit_URL}}" + ], + "path": [ + "index", + "api", + "listWebrtcRooms" + ], + "query": [ + { + "key": "secret", + "value": "{{ZLMediaKit_secret}}" + } + ] + } + }, + "response": [] + }, + { + "name": "WebRTC-查看WebRTCProxyPlayer连接信息(getWebrtcProxyPlayerInfo)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{ZLMediaKit_URL}}/index/api/getWebrtcProxyPlayerInfo?secret={{ZLMediaKit_secret}}&key=__defaultVhost__/live/test", + "host": [ + "{{ZLMediaKit_URL}}" + ], + "path": [ + "index", + "api", + "getWebrtcProxyPlayerInfo" + ], + "query": [ + { + "key": "secret", + "value": "{{ZLMediaKit_secret}}" + }, + { + "key": "key", + "value": "__defaultVhost__/live/test" + } + ] + } + }, + "response": [] } ], "event": [ diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 6ff38fad..7d0dda9a 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -57,6 +57,10 @@ #include "../webrtc/WebRtcPlayer.h" #include "../webrtc/WebRtcPusher.h" #include "../webrtc/WebRtcEchoTest.h" +#include "../webrtc/WebRtcSignalingPeer.h" +#include "../webrtc/WebRtcSignalingSession.h" +#include "../webrtc/WebRtcProxyPlayer.h" +#include "../webrtc/WebRtcProxyPlayerImp.h" #endif #if defined(ENABLE_VERSION) @@ -314,84 +318,6 @@ static inline void addHttpListener(){ }); } -template -class ServiceController { -public: - using Pointer = std::shared_ptr; - - void clear() { - decltype(_map) copy; - { - std::lock_guard lck(_mtx); - copy.swap(_map); - } - } - - size_t erase(const std::string &key) { - Pointer erase_ptr; - { - std::lock_guard lck(_mtx); - auto itr = _map.find(key); - if (itr != _map.end()) { - erase_ptr = itr->second; - _map.erase(itr); - return 1; - } - } - return 0; - } - - size_t size() { - std::lock_guard lck(_mtx); - return _map.size(); - } - - Pointer find(const std::string &key) const { - std::lock_guard lck(_mtx); - auto it = _map.find(key); - if (it == _map.end()) { - return nullptr; - } - return it->second; - } - - void for_each(const std::function& cb) { - std::lock_guard lck(_mtx); - auto it = _map.begin(); - while (it != _map.end()) { - cb(it->first, it->second); - it++; - } - } - - template - Pointer make(const std::string &key, _Args&& ...__args) { - // assert(!find(key)); - - auto server = std::make_shared(std::forward<_Args>(__args)...); - std::lock_guard lck(_mtx); - auto it = _map.emplace(key, server); - assert(it.second); - return server; - } - - template - Pointer makeWithAction(const std::string &key, function action, _Args&& ...__args) { - // assert(!find(key)); - - auto server = std::make_shared(std::forward<_Args>(__args)...); - action(server); - std::lock_guard lck(_mtx); - auto it = _map.emplace(key, server); - assert(it.second); - return server; - } - -private: - std::unordered_map _map; - mutable std::recursive_mutex _mtx; -}; - // 拉流代理器列表 [AUTO-TRANSLATED:6dcfb11f] // Pull stream proxy list static ServiceController s_player_proxy; @@ -2127,35 +2053,6 @@ void installWebApi() { }); #ifdef ENABLE_WEBRTC - class WebRtcArgsImp : public WebRtcArgs { - public: - WebRtcArgsImp(const ArgsString &args, std::string session_id) - : _args(args) - , _session_id(std::move(session_id)) {} - ~WebRtcArgsImp() override = default; - - toolkit::variant operator[](const string &key) const override { - if (key == "url") { - return getUrl(); - } - return _args[key]; - } - - private: - string getUrl() const{ - auto &allArgs = _args; - CHECK_ARGS("app", "stream"); - - string auth = _args["Authorization"]; // Authorization Bearer - return StrPrinter << "rtc://" << _args["Host"] << "/" << _args["app"] << "/" << _args["stream"] << "?" - << _args.parser.params() + "&session=" + _session_id + (auth.empty() ? "" : ("&Authorization=" + auth)); - } - - private: - ArgsString _args; - std::string _session_id; - }; - api_regist("/index/api/webrtc",[](API_ARGS_STRING_ASYNC){ CHECK_ARGS("type"); auto type = allArgs["type"]; @@ -2163,7 +2060,7 @@ void installWebApi() { CHECK(!offer.empty(), "http body(webrtc offer sdp) is empty"); auto &session = static_cast(sender); - auto args = std::make_shared(allArgs, sender.getIdentifier()); + auto args = std::make_shared>(allArgs, sender.getIdentifier()); WebRtcPluginManager::Instance().negotiateSdp(session, type, *args, [invoker, val, offer, headerOut](const WebRtcInterface &exchanger) mutable { auto &handler = const_cast(exchanger); try { @@ -2186,7 +2083,7 @@ void installWebApi() { auto &session = static_cast(sender); auto location = std::string(session.overSsl() ? "https://" : "http://") + allArgs["host"] + delete_webrtc_url; - auto args = std::make_shared(allArgs, sender.getIdentifier()); + auto args = std::make_shared>(allArgs, sender.getIdentifier()); WebRtcPluginManager::Instance().negotiateSdp(session, type, *args, [invoker, offer, headerOut, location](const WebRtcInterface &exchanger) mutable { auto &handler = const_cast(exchanger); try { @@ -2220,6 +2117,103 @@ void installWebApi() { obj->safeShutdown(SockException(Err_shutdown, "deleted by http api")); invoker(200, headerOut, ""); }); + + // 获取WebRTCProxyPlayer 连接信息 + api_regist("/index/api/getWebrtcProxyPlayerInfo", [](API_ARGS_MAP_ASYNC) { + CHECK_SECRET(); + CHECK_ARGS("key"); + + auto player_proxy = s_player_proxy.find(allArgs["key"]); + if (!player_proxy) { + throw ApiRetException("Stream proxy not found", API::NotFound); + } + + auto media_player = player_proxy->getDelegate(); + if (!media_player) { + throw ApiRetException("Media player not found", API::OtherFailed); + } + + auto webrtc_player_imp = std::dynamic_pointer_cast(media_player); + if (!webrtc_player_imp) { + throw ApiRetException("Stream proxy is not WebRTC type", API::OtherFailed); + } + + auto webrtc_transport = webrtc_player_imp->getWebRtcTransport(); + if (!webrtc_transport) { + throw ApiRetException("WebRTC transport not available", API::OtherFailed); + } + + std::string stream_key = allArgs["key"]; + webrtc_transport->getTransportInfo([val, headerOut, invoker, stream_key](Json::Value transport_info) mutable { + transport_info["stream_key"] = stream_key; + + if (transport_info.isMember("error")) { + Json::Value error_val; + error_val["code"] = API::OtherFailed; + error_val["msg"] = transport_info["error"].asString(); + invoker(200, headerOut, error_val.toStyledString()); + return; + } + + // 成功返回结果 + Json::Value success_val; + success_val["code"] = API::Success; + success_val["msg"] = "success"; + success_val["data"] = transport_info; + invoker(200, headerOut, success_val.toStyledString()); + }); + }); + + api_regist("/index/api/addWebrtcRoomKeeper",[](API_ARGS_MAP_ASYNC){ + CHECK_SECRET(); + CHECK_ARGS("server_host", "server_port", "room_id", "ssl"); + //server_host: 信令服务器host + //server_post: 信令服务器host + //room_id: 注册的id,信令服务器会对该id进行唯一性检查 + addWebrtcRoomKeeper(allArgs["server_host"], allArgs["server_port"], allArgs["room_id"], allArgs["ssl"], + [val, headerOut, invoker](const SockException &ex, const string &key) mutable { + if (ex) { + val["code"] = API::OtherFailed; + val["msg"] = ex.what(); + } else { + val["msg"] = "success"; + val["data"]["room_key"] = key; + } + invoker(200, headerOut, val.toStyledString()); + }); + }); + + api_regist("/index/api/delWebrtcRoomKeeper",[](API_ARGS_MAP_ASYNC){ + CHECK_SECRET(); + CHECK_ARGS("room_key"); + + delWebrtcRoomKeeper(allArgs["room_key"], + [val, headerOut, invoker](const SockException &ex) mutable { + if (ex) { + val["code"] = API::OtherFailed; + val["msg"] = ex.what(); + } + invoker(200, headerOut, val.toStyledString()); + }); + }); + + api_regist("/index/api/listWebrtcRoomKeepers", [](API_ARGS_MAP) { + CHECK_SECRET(); + listWebrtcRoomKeepers([&val](const std::string& key, const WebRtcSignalingPeer::Ptr& p) { + Json::Value item = ToJson(p); + item["room_key"] = key; + val["data"].append(item); + }); + }); + + api_regist("/index/api/listWebrtcRooms", [](API_ARGS_MAP) { + CHECK_SECRET(); + listWebrtcRooms([&val](const std::string& key, const WebRtcSignalingSession::Ptr& p) { + Json::Value item = ToJson(p); + item["room_id"] = key; + val["data"].append(item); + }); + }); #endif #if defined(ENABLE_VERSION) diff --git a/server/WebApi.h b/server/WebApi.h index 9830dfd4..19da8b99 100755 --- a/server/WebApi.h +++ b/server/WebApi.h @@ -1,228 +1,364 @@ -/* - * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. - * - * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). - * - * Use of this source code is governed by MIT-like license that can be found in the - * LICENSE file in the root of the source tree. All contributing project authors - * may be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef ZLMEDIAKIT_WEBAPI_H -#define ZLMEDIAKIT_WEBAPI_H - -#include -#include -#include "json/json.h" -#include "Common/Parser.h" -#include "Network/Socket.h" -#include "Http/HttpSession.h" -#include "Common/MultiMediaSourceMuxer.h" - -// 配置文件路径 [AUTO-TRANSLATED:8a373c2f] -// Configuration file path -extern std::string g_ini_file; - -namespace mediakit { -// //////////RTSP服务器配置/////////// [AUTO-TRANSLATED:950e1981] -// //////////RTSP server configuration/////////// -namespace Rtsp { -extern const std::string kPort; -} //namespace Rtsp - -// //////////RTMP服务器配置/////////// [AUTO-TRANSLATED:8de6f41f] -// //////////RTMP server configuration/////////// -namespace Rtmp { -extern const std::string kPort; -} //namespace RTMP -} // namespace mediakit - -namespace API { -typedef enum { - NotFound = -500,//未找到 - Exception = -400,//代码抛异常 - InvalidArgs = -300,//参数不合法 - SqlFailed = -200,//sql执行失败 - AuthFailed = -100,//鉴权失败 - OtherFailed = -1,//业务代码执行失败, - Success = 0//执行成功 -} ApiErr; - -extern const std::string kSecret; -}//namespace API - -class ApiRetException: public std::runtime_error { -public: - ApiRetException(const char *str = "success" ,int code = API::Success):runtime_error(str){ - _code = code; - } - int code(){ return _code; } -private: - int _code; -}; - -class AuthException : public ApiRetException { -public: - AuthException(const char *str):ApiRetException(str,API::AuthFailed){} -}; - -class InvalidArgsException: public ApiRetException { -public: - InvalidArgsException(const char *str):ApiRetException(str,API::InvalidArgs){} -}; - -class SuccessException: public ApiRetException { -public: - SuccessException():ApiRetException("success",API::Success){} -}; - -using ApiArgsType = std::map; - -template -std::string getValue(Args &args, const Key &key) { - auto it = args.find(key); - if (it == args.end()) { - return ""; - } - return it->second; -} - -template -std::string getValue(Json::Value &args, const Key &key) { - auto value = args.find(key); - if (value == nullptr) { - return ""; - } - return value->asString(); -} - -template -std::string getValue(std::string &args, const Key &key) { - return ""; -} - -template -std::string getValue(const mediakit::Parser &parser, const Key &key) { - auto ret = getValue(parser.getUrlArgs(), key); - if (!ret.empty()) { - return ret; - } - return getValue(parser.getHeader(), key); -} - -template -std::string getValue(const mediakit::Parser &parser, Args &args, const Key &key) { - auto ret = getValue(args, key); - if (!ret.empty()) { - return ret; - } - return getValue(parser, key); -} - -template -class HttpAllArgs { - mediakit::Parser* _parser = nullptr; - Args* _args = nullptr; -public: - const mediakit::Parser& parser; - Args& args; - - HttpAllArgs(const mediakit::Parser &p, Args &a): parser(p), args(a) {} - - HttpAllArgs(const HttpAllArgs &that): _parser(new mediakit::Parser(that.parser)), - _args(new Args(that.args)), - parser(*_parser), args(*_args) {} - ~HttpAllArgs() { - if (_parser) { - delete _parser; - } - if (_args) { - delete _args; - } - } - - template - toolkit::variant operator[](const Key &key) const { - return (toolkit::variant)getValue(parser, args, key); - } -}; - -using ArgsMap = HttpAllArgs; -using ArgsJson = HttpAllArgs; -using ArgsString = HttpAllArgs; - -#define API_ARGS_MAP toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const ArgsMap &allArgs, Json::Value &val -#define API_ARGS_MAP_ASYNC API_ARGS_MAP, const mediakit::HttpSession::HttpResponseInvoker &invoker -#define API_ARGS_JSON toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const ArgsJson &allArgs, Json::Value &val -#define API_ARGS_JSON_ASYNC API_ARGS_JSON, const mediakit::HttpSession::HttpResponseInvoker &invoker -#define API_ARGS_STRING toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const ArgsString &allArgs, Json::Value &val -#define API_ARGS_STRING_ASYNC API_ARGS_STRING, const mediakit::HttpSession::HttpResponseInvoker &invoker -#define API_ARGS_VALUE sender, headerOut, allArgs, val - -// 注册http请求参数是map类型的http api [AUTO-TRANSLATED:8a273897] -// Register http request parameters as map type http api -void api_regist(const std::string &api_path, const std::function &func); -// 注册http请求参数是map类型,但是可以异步回复的的http api [AUTO-TRANSLATED:9da5d5f5] -// Register http request parameters as map type, but can be replied asynchronously http api -void api_regist(const std::string &api_path, const std::function &func); - -// 注册http请求参数是Json::Value类型的http api(可以支持多级嵌套的json参数对象) [AUTO-TRANSLATED:c4794456] -// Register http request parameters as Json::Value type http api (can support multi-level nested json parameter objects) -void api_regist(const std::string &api_path, const std::function &func); -// 注册http请求参数是Json::Value类型,但是可以异步回复的的http api [AUTO-TRANSLATED:742e57fd] -// Register http request parameters as Json::Value type, but can be replied asynchronously http api -void api_regist(const std::string &api_path, const std::function &func); - -// 注册http请求参数是http原始请求信息的http api [AUTO-TRANSLATED:72d3fe93] -// Register http request parameters as http original request information http api -void api_regist(const std::string &api_path, const std::function &func); -// 注册http请求参数是http原始请求信息的异步回复的http api [AUTO-TRANSLATED:49feefa8] -// Register http request parameters as http original request information asynchronous reply http api -void api_regist(const std::string &api_path, const std::function &func); - -template -bool checkArgs(Args &args, const Key &key) { - return !args[key].empty(); -} - -template -bool checkArgs(Args &args, const Key &key, const KeyTypes &...keys) { - return checkArgs(args, key) && checkArgs(args, keys...); -} - -// 检查http url中或body中或http header参数是否为空的宏 [AUTO-TRANSLATED:9de001a4] -// Check whether the http url, body or http header parameters are empty -#define CHECK_ARGS(...) \ - if(!checkArgs(allArgs,##__VA_ARGS__)){ \ - throw InvalidArgsException("Required parameter missed: " #__VA_ARGS__); \ - } - -// 检查http参数中是否附带secret密钥的宏,127.0.0.1的ip不检查密钥 [AUTO-TRANSLATED:7546956c] -// Check whether the http parameters contain the secret key, the ip of 127.0.0.1 does not check the key -// 同时检测是否在ip白名单内 [AUTO-TRANSLATED:d12f963d] -// Check whether it is in the ip whitelist at the same time -#define CHECK_SECRET() \ - do { \ - auto ip = sender.get_peer_ip(); \ - if (!HttpFileManager::isIPAllowed(ip)) { \ - throw AuthException("Your ip is not allowed to access the service."); \ - } \ - CHECK_ARGS("secret"); \ - if (api_secret != allArgs["secret"]) { \ - throw AuthException("Incorrect secret"); \ - } \ - } while(false); - -void installWebApi(); -void unInstallWebApi(); - -#if defined(ENABLE_RTPPROXY) -uint16_t openRtpServer(uint16_t local_port, const mediakit::MediaTuple &tuple, int tcp_mode, const std::string &local_ip, bool re_use_port, uint32_t ssrc, int only_track, bool multiplex=false); -#endif - -Json::Value makeMediaSourceJson(mediakit::MediaSource &media); -void getStatisticJson(const std::function &cb); -void addStreamProxy(const mediakit::MediaTuple &tuple, const std::string &url, int retry_count, - const mediakit::ProtocolOption &option, int rtp_type, float timeout_sec, const toolkit::mINI &args, - const std::function &cb); -#endif //ZLMEDIAKIT_WEBAPI_H +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLMEDIAKIT_WEBAPI_H +#define ZLMEDIAKIT_WEBAPI_H + +#include +#include +#include "json/json.h" +#include "Common/Parser.h" +#include "Network/Socket.h" +#include "Http/HttpSession.h" +#include "Common/MultiMediaSourceMuxer.h" + +#if defined(ENABLE_WEBRTC) +#include "webrtc/WebRtcTransport.h" +#endif + +// 配置文件路径 [AUTO-TRANSLATED:8a373c2f] +// Configuration file path +extern std::string g_ini_file; + +namespace mediakit { +// //////////RTSP服务器配置/////////// [AUTO-TRANSLATED:950e1981] +// //////////RTSP server configuration/////////// +namespace Rtsp { +extern const std::string kPort; +} //namespace Rtsp + +// //////////RTMP服务器配置/////////// [AUTO-TRANSLATED:8de6f41f] +// //////////RTMP server configuration/////////// +namespace Rtmp { +extern const std::string kPort; +} //namespace RTMP +} // namespace mediakit + +namespace API { +typedef enum { + NotFound = -500,//未找到 + Exception = -400,//代码抛异常 + InvalidArgs = -300,//参数不合法 + SqlFailed = -200,//sql执行失败 + AuthFailed = -100,//鉴权失败 + OtherFailed = -1,//业务代码执行失败, + Success = 0//执行成功 +} ApiErr; + +extern const std::string kSecret; +}//namespace API + +class ApiRetException: public std::runtime_error { +public: + ApiRetException(const char *str = "success" ,int code = API::Success):runtime_error(str){ + _code = code; + } + int code(){ return _code; } +private: + int _code; +}; + +class AuthException : public ApiRetException { +public: + AuthException(const char *str):ApiRetException(str,API::AuthFailed){} +}; + +class InvalidArgsException: public ApiRetException { +public: + InvalidArgsException(const char *str):ApiRetException(str,API::InvalidArgs){} +}; + +class SuccessException: public ApiRetException { +public: + SuccessException():ApiRetException("success",API::Success){} +}; + +using ApiArgsType = std::map; + +template +std::string getValue(Args &args, const Key &key) { + auto it = args.find(key); + if (it == args.end()) { + return ""; + } + return it->second; +} + +template +std::string getValue(Json::Value &args, const Key &key) { + auto value = args.find(key); + if (value == nullptr) { + return ""; + } + return value->asString(); +} + +template +std::string getValue(std::string &args, const Key &key) { + return ""; +} + +template +std::string getValue(const mediakit::Parser &parser, const Key &key) { + auto ret = getValue(parser.getUrlArgs(), key); + if (!ret.empty()) { + return ret; + } + return getValue(parser.getHeader(), key); +} + +template +std::string getValue(mediakit::Parser &parser, const Key &key) { + return getValue((const mediakit::Parser &) parser, key); +} + +template +std::string getValue(const mediakit::Parser &parser, Args &args, const Key &key) { + auto ret = getValue(args, key); + if (!ret.empty()) { + return ret; + } + return getValue(parser, key); +} + +template +class HttpAllArgs { + mediakit::Parser* _parser = nullptr; + Args* _args = nullptr; +public: + const mediakit::Parser& parser; + Args& args; + + HttpAllArgs(const mediakit::Parser &p, Args &a): parser(p), args(a) {} + + HttpAllArgs(const HttpAllArgs &that): _parser(new mediakit::Parser(that.parser)), + _args(new Args(that.args)), + parser(*_parser), args(*_args) {} + ~HttpAllArgs() { + if (_parser) { + delete _parser; + } + if (_args) { + delete _args; + } + } + + template + toolkit::variant operator[](const Key &key) const { + return (toolkit::variant)getValue(parser, args, key); + } + + const Args& getArgs() const { + return args; + } + + const mediakit::Parser &getParser() const { + return parser; + } +}; + +using ArgsMap = HttpAllArgs; +using ArgsJson = HttpAllArgs; +using ArgsString = HttpAllArgs; + +#define API_ARGS_MAP toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const ArgsMap &allArgs, Json::Value &val +#define API_ARGS_MAP_ASYNC API_ARGS_MAP, const mediakit::HttpSession::HttpResponseInvoker &invoker +#define API_ARGS_JSON toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const ArgsJson &allArgs, Json::Value &val +#define API_ARGS_JSON_ASYNC API_ARGS_JSON, const mediakit::HttpSession::HttpResponseInvoker &invoker +#define API_ARGS_STRING toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const ArgsString &allArgs, Json::Value &val +#define API_ARGS_STRING_ASYNC API_ARGS_STRING, const mediakit::HttpSession::HttpResponseInvoker &invoker +#define API_ARGS_VALUE sender, headerOut, allArgs, val + +// 注册http请求参数是map类型的http api [AUTO-TRANSLATED:8a273897] +// Register http request parameters as map type http api +void api_regist(const std::string &api_path, const std::function &func); +// 注册http请求参数是map类型,但是可以异步回复的的http api [AUTO-TRANSLATED:9da5d5f5] +// Register http request parameters as map type, but can be replied asynchronously http api +void api_regist(const std::string &api_path, const std::function &func); + +// 注册http请求参数是Json::Value类型的http api(可以支持多级嵌套的json参数对象) [AUTO-TRANSLATED:c4794456] +// Register http request parameters as Json::Value type http api (can support multi-level nested json parameter objects) +void api_regist(const std::string &api_path, const std::function &func); +// 注册http请求参数是Json::Value类型,但是可以异步回复的的http api [AUTO-TRANSLATED:742e57fd] +// Register http request parameters as Json::Value type, but can be replied asynchronously http api +void api_regist(const std::string &api_path, const std::function &func); + +// 注册http请求参数是http原始请求信息的http api [AUTO-TRANSLATED:72d3fe93] +// Register http request parameters as http original request information http api +void api_regist(const std::string &api_path, const std::function &func); +// 注册http请求参数是http原始请求信息的异步回复的http api [AUTO-TRANSLATED:49feefa8] +// Register http request parameters as http original request information asynchronous reply http api +void api_regist(const std::string &api_path, const std::function &func); + +template +bool checkArgs(Args &args, const Key &key) { + return !args[key].empty(); +} + +template +bool checkArgs(Args &args, const Key &key, const KeyTypes &...keys) { + return checkArgs(args, key) && checkArgs(args, keys...); +} + +// 检查http url中或body中或http header参数是否为空的宏 [AUTO-TRANSLATED:9de001a4] +// Check whether the http url, body or http header parameters are empty +#define CHECK_ARGS(...) \ + if(!checkArgs(allArgs,##__VA_ARGS__)){ \ + throw InvalidArgsException("Required parameter missed: " #__VA_ARGS__); \ + } + +// 检查http参数中是否附带secret密钥的宏,127.0.0.1的ip不检查密钥 [AUTO-TRANSLATED:7546956c] +// Check whether the http parameters contain the secret key, the ip of 127.0.0.1 does not check the key +// 同时检测是否在ip白名单内 [AUTO-TRANSLATED:d12f963d] +// Check whether it is in the ip whitelist at the same time +#define CHECK_SECRET() \ + do { \ + auto ip = sender.get_peer_ip(); \ + if (!HttpFileManager::isIPAllowed(ip)) { \ + throw AuthException("Your ip is not allowed to access the service."); \ + } \ + CHECK_ARGS("secret"); \ + if (api_secret != allArgs["secret"]) { \ + throw AuthException("Incorrect secret"); \ + } \ + } while(false); + +void installWebApi(); +void unInstallWebApi(); + +#if defined(ENABLE_RTPPROXY) +uint16_t openRtpServer(uint16_t local_port, const mediakit::MediaTuple &tuple, int tcp_mode, const std::string &local_ip, bool re_use_port, uint32_t ssrc, int only_track, bool multiplex=false); +#endif + +Json::Value makeMediaSourceJson(mediakit::MediaSource &media); +void getStatisticJson(const std::function &cb); +void addStreamProxy(const mediakit::MediaTuple &tuple, const std::string &url, int retry_count, + const mediakit::ProtocolOption &option, int rtp_type, float timeout_sec, const toolkit::mINI &args, + const std::function &cb); + +template +class ServiceController { +public: + using Pointer = std::shared_ptr; + std::unordered_map _map; + mutable std::recursive_mutex _mtx; + + void clear() { + decltype(_map) copy; + { + std::lock_guard lck(_mtx); + copy.swap(_map); + } + } + + size_t erase(const std::string &key) { + Pointer erase_ptr; + { + std::lock_guard lck(_mtx); + auto itr = _map.find(key); + if (itr != _map.end()) { + erase_ptr = std::move(itr->second); + _map.erase(itr); + return 1; + } + } + return 0; + } + + size_t size() { + std::lock_guard lck(_mtx); + return _map.size(); + } + + Pointer find(const std::string &key) const { + std::lock_guard lck(_mtx); + auto it = _map.find(key); + if (it == _map.end()) { + return nullptr; + } + return it->second; + } + + void for_each(const std::function& cb) { + std::lock_guard lck(_mtx); + auto it = _map.begin(); + while (it != _map.end()) { + cb(it->first, it->second); + it++; + } + } + + template + Pointer make(const std::string &key, _Args&& ...__args) { + // assert(!find(key)); + + auto server = std::make_shared(std::forward<_Args>(__args)...); + std::lock_guard lck(_mtx); + auto it = _map.emplace(key, server); + assert(it.second); + return server; + } + + template + Pointer makeWithAction(const std::string &key, std::function action, _Args&& ...__args) { + // assert(!find(key)); + + auto server = std::make_shared(std::forward<_Args>(__args)...); + action(server); + std::lock_guard lck(_mtx); + auto it = _map.emplace(key, server); + assert(it.second); + return server; + } + + template + Pointer emplace(const std::string &key, _Args&& ...__args) { + // assert(!find(key)); + + auto server = std::static_pointer_cast(std::forward<_Args>(__args)...); + std::lock_guard lck(_mtx); + auto it = _map.emplace(key, server); + assert(it.second); + return server; + } +}; + +#if defined(ENABLE_WEBRTC) +template +class WebRtcArgsImp : public mediakit::WebRtcArgs { +public: + WebRtcArgsImp(const HttpAllArgs &args, std::string session_id) + : _args(args) + , _session_id(std::move(session_id)) {} + ~WebRtcArgsImp() override = default; + + toolkit::variant operator[](const std::string &key) const override { + if (key == "url") { + return getUrl(); + } + return _args[key]; + } + +private: + std::string getUrl() const { + auto &allArgs = _args; + CHECK_ARGS("app", "stream"); + + return StrPrinter << RTC_SCHEMA << "://" << (_args["Host"].empty() ? DEFAULT_VHOST : _args["Host"].data()) << "/" << _args["app"] << "/" + << _args["stream"] << "?" << _args.getParser().params() + "&session=" + _session_id; + } + +private: + HttpAllArgs _args; + std::string _session_id; +}; +#endif + +#endif //ZLMEDIAKIT_WEBAPI_H diff --git a/server/main.cpp b/server/main.cpp index 014517da..3591dcff 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -30,6 +30,8 @@ #if defined(ENABLE_WEBRTC) #include "../webrtc/WebRtcTransport.h" #include "../webrtc/WebRtcSession.h" +#include "../webrtc/WebRtcSignalingSession.h" +#include "../webrtc/IceSession.hpp" #endif #if defined(ENABLE_SRT) @@ -368,8 +370,17 @@ int start_main(int argc,char *argv[]) { } return Socket::createSocket(new_poller, false); }); + + auto signaleSrv = std::make_shared(); + auto signalsSrv = std::make_shared(); + auto iceTcpSrv = std::make_shared(); + auto iceSrv = std::make_shared(); uint16_t rtcPort = mINI::Instance()[Rtc::kPort]; uint16_t rtcTcpPort = mINI::Instance()[Rtc::kTcpPort]; + uint16_t signalingPort = mINI::Instance()[Rtc::kSignalingPort]; + uint16_t signalSslPort = mINI::Instance()[Rtc::kSignalingSslPort]; + uint16_t icePort = mINI::Instance()[Rtc::kIcePort]; + uint16_t iceTcpPort = mINI::Instance()[Rtc::kIceTcpPort]; #endif//defined(ENABLE_WEBRTC) @@ -435,6 +446,12 @@ int start_main(int argc,char *argv[]) { if (rtcTcpPort) { rtcSrv_tcp->start(rtcTcpPort, listen_ip);} + //webrtc 信令服务器 + if (signalingPort) { signaleSrv->start(signalingPort);} + if (signalSslPort) { signalsSrv->start(signalSslPort);} + //STUN/TURN服务 + if (icePort) { iceSrv->start(icePort);} + if (iceTcpPort) { iceTcpSrv->start(iceTcpPort);} #endif//defined(ENABLE_WEBRTC) #if defined(ENABLE_SRT) diff --git a/src/Common/macros.cpp b/src/Common/macros.cpp index e9574dcc..51a5eda2 100644 --- a/src/Common/macros.cpp +++ b/src/Common/macros.cpp @@ -30,7 +30,7 @@ namespace mediakit { * [AUTO-TRANSLATED:f214f734] */ #if !defined(ENABLE_VERSION) -const char kServerName[] = "ZLMediaKit-8.0(build in " __DATE__ " " __TIME__ ")"; +const char kServerName[] = "ZLMediaKit-9.0(build in " __DATE__ " " __TIME__ ")"; #else const char kServerName[] = "ZLMediaKit(git hash:" COMMIT_HASH "/" COMMIT_TIME ",branch:" BRANCH_NAME ",build time:" BUILD_TIME ")"; #endif diff --git a/src/Common/macros.h b/src/Common/macros.h index e9f87826..4bca5b7e 100644 --- a/src/Common/macros.h +++ b/src/Common/macros.h @@ -62,6 +62,7 @@ } #endif // CLEAR_ARR +#define RTC_SCHEMA "rtc" #define RTSP_SCHEMA "rtsp" #define RTMP_SCHEMA "rtmp" #define TS_SCHEMA "ts" diff --git a/src/Http/HttpBody.cpp b/src/Http/HttpBody.cpp index 9c062508..f7b0d3d0 100644 --- a/src/Http/HttpBody.cpp +++ b/src/Http/HttpBody.cpp @@ -163,7 +163,7 @@ static std::shared_ptr getSharedMmap(const string &file_path, int64_t &fil if (addr_ == nullptr) { mmap_close(hfile, hmapping, addr_); - WarnL << "MapViewOfFile() " << file_path << " failed:"; + WarnL << "MapViewOfFile() " << file_path << " failed:"; return nullptr; } diff --git a/src/Player/MediaPlayer.h b/src/Player/MediaPlayer.h index ca187019..2d43b6ed 100644 --- a/src/Player/MediaPlayer.h +++ b/src/Player/MediaPlayer.h @@ -26,6 +26,7 @@ public: void play(const std::string &url) override; toolkit::EventPoller::Ptr getPoller(); void setOnCreateSocket(toolkit::Socket::onCreateSocket cb); + const PlayerBase::Ptr& getDelegate() const { return _delegate; } private: toolkit::EventPoller::Ptr _poller; diff --git a/src/Player/PlayerBase.cpp b/src/Player/PlayerBase.cpp index 57ac236a..949c555c 100644 --- a/src/Player/PlayerBase.cpp +++ b/src/Player/PlayerBase.cpp @@ -18,7 +18,9 @@ #ifdef ENABLE_SRT #include "Srt/SrtPlayerImp.h" #endif // ENABLE_SRT - +#ifdef ENABLE_WEBRTC +#include "../webrtc/WebRtcProxyPlayerImp.h" +#endif // ENABLE_WEBRTC using namespace std; using namespace toolkit; @@ -84,6 +86,11 @@ PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &in_poller, cons return PlayerBase::Ptr(new SrtPlayerImp(poller), release_func); } #endif//ENABLE_SRT +#ifdef ENABLE_WEBRTC + if ((strcasecmp("webrtc", prefix.data()) == 0 || strcasecmp("webrtcs", prefix.data()) == 0)) { + return PlayerBase::Ptr(new WebRtcProxyPlayerImp(poller), release_func); + } +#endif//ENABLE_WEBRTC throw std::invalid_argument("not supported play schema:" + url_in); } diff --git a/src/Player/PlayerProxy.cpp b/src/Player/PlayerProxy.cpp index a7cea3e7..58e4850c 100644 --- a/src/Player/PlayerProxy.cpp +++ b/src/Player/PlayerProxy.cpp @@ -286,6 +286,10 @@ float PlayerProxy::getLossRate(MediaSource &sender, TrackType type) { return getPacketLossRate(type); } +toolkit::EventPoller::Ptr PlayerProxy::getOwnerPoller(MediaSource &sender) { + return getPoller(); +} + TranslationInfo PlayerProxy::getTranslationInfo() { return _transtalion_info; } diff --git a/src/Player/PlayerProxy.h b/src/Player/PlayerProxy.h index 304d22a6..cc67ad94 100644 --- a/src/Player/PlayerProxy.h +++ b/src/Player/PlayerProxy.h @@ -151,6 +151,7 @@ private: std::string getOriginUrl(MediaSource &sender) const override; std::shared_ptr getOriginSock(MediaSource &sender) const override; float getLossRate(MediaSource &sender, TrackType type) override; + toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override; void rePlay(const std::string &strUrl, int iFailedCnt); void onPlaySuccess(); diff --git a/src/Pusher/PusherBase.cpp b/src/Pusher/PusherBase.cpp index 47e6b8d1..055f5629 100644 --- a/src/Pusher/PusherBase.cpp +++ b/src/Pusher/PusherBase.cpp @@ -15,6 +15,9 @@ #ifdef ENABLE_SRT #include "Srt/SrtPusher.h" #endif // ENABLE_SRT +#ifdef ENABLE_WEBRTC +#include "../webrtc/WebRtcProxyPusher.h" +#endif // ENABLE_WEBRTC using namespace toolkit; @@ -23,7 +26,8 @@ namespace mediakit { static bool checkMediaSourceAndUrlMatch(const MediaSource::Ptr &src, const std::string &url) { std::string prefix = findSubString(url.data(), NULL, "://"); - if (strcasecmp("rtsps", prefix.data()) == 0 || strcasecmp("rtsp", prefix.data()) == 0) { + if (strcasecmp("rtsps", prefix.data()) == 0 || strcasecmp("rtsp", prefix.data()) == 0 || + strcasecmp("webrtcs", prefix.data()) == 0 || strcasecmp("webrtc", prefix.data()) == 0 ) { auto rtsp_src = std::dynamic_pointer_cast(src); if (!rtsp_src) { return false; @@ -91,6 +95,11 @@ PusherBase::Ptr PusherBase::createPusher(const EventPoller::Ptr &in_poller, } #endif//ENABLE_SRT +#ifdef ENABLE_WEBRTC + if ((strcasecmp("webrtc", prefix.data()) == 0 || strcasecmp("webrtcs", prefix.data()) == 0)) { + return PusherBase::Ptr(new WebRtcProxyPusherImp(poller, std::dynamic_pointer_cast(src)), release_func); + } +#endif//ENABLE_WEBRTC throw std::invalid_argument("not supported push schema:" + url); } diff --git a/src/Pusher/PusherBase.h b/src/Pusher/PusherBase.h index b6d5b1f1..269a5f38 100644 --- a/src/Pusher/PusherBase.h +++ b/src/Pusher/PusherBase.h @@ -69,7 +69,7 @@ public: virtual size_t getSendSpeed() { return 0; } virtual size_t getSendTotalBytes() { return 0; } - + protected: virtual void onShutdown(const toolkit::SockException &ex) = 0; virtual void onPublishResult(const toolkit::SockException &ex) = 0; @@ -139,11 +139,11 @@ public: size_t getSendSpeed() override { return _delegate ? _delegate->getSendSpeed() : Parent::getSendSpeed(); } - + size_t getSendTotalBytes() override { return _delegate ? _delegate->getSendTotalBytes() : Parent::getSendTotalBytes(); } - + protected: void onShutdown(const toolkit::SockException &ex) override { if (_on_shutdown) { diff --git a/src/Record/HlsMaker.cpp b/src/Record/HlsMaker.cpp index 9905b4be..c5df3a9e 100644 --- a/src/Record/HlsMaker.cpp +++ b/src/Record/HlsMaker.cpp @@ -17,7 +17,7 @@ using namespace std; namespace mediakit { HlsMaker::HlsMaker(bool is_fmp4, float seg_duration, uint32_t seg_number, bool seg_keep) { - _is_fmp4 = is_fmp4; + _is_fmp4 = is_fmp4; // 最小允许设置为0,0个切片代表点播 [AUTO-TRANSLATED:19235e8e] // Minimum allowed setting is 0, 0 slices represent on-demand _seg_number = seg_number; diff --git a/src/Record/Recorder.h b/src/Record/Recorder.h index e0fde02a..e2107daf 100644 --- a/src/Record/Recorder.h +++ b/src/Record/Recorder.h @@ -27,6 +27,11 @@ struct MediaTuple { std::string shortUrl() const { return vhost + '/' + app + '/' + stream; } + + MediaTuple() = default; + MediaTuple(std::string vhost, std::string app, std::string stream, std::string params = "") + : vhost(std::move(vhost)), app(std::move(app)), stream(std::move(stream)), params(std::move(params)) { + } }; class RecordInfo: public MediaTuple { diff --git a/src/Rtmp/RtmpPusher.h b/src/Rtmp/RtmpPusher.h index d634bcd8..878fa8f4 100644 --- a/src/Rtmp/RtmpPusher.h +++ b/src/Rtmp/RtmpPusher.h @@ -29,7 +29,7 @@ public: size_t getSendSpeed() override; size_t getSendTotalBytes() override; - + protected: //for Tcpclient override void onRecv(const toolkit::Buffer::Ptr &buf) override; diff --git a/src/Srt/SrtCaller.cpp b/src/Srt/SrtCaller.cpp index 64936052..9fbed896 100644 --- a/src/Srt/SrtCaller.cpp +++ b/src/Srt/SrtCaller.cpp @@ -1012,9 +1012,9 @@ float SrtCaller::getTimeOutSec() { GET_CONFIG(uint32_t, timeout, SRT::kTimeOutSec); if (timeout <= 0) { WarnL << "config srt " << kTimeOutSec << " not vaild"; - return 5 * 1000; + return 5.0f; } - return (float)timeout * (float)1000; + return (float)timeout; }; std::string SrtCaller::generateStreamId() { diff --git a/srt/Crypto.cpp b/srt/Crypto.cpp index 40c29f8f..798809a4 100644 --- a/srt/Crypto.cpp +++ b/srt/Crypto.cpp @@ -49,11 +49,11 @@ inline const EVP_CIPHER* aes_key_len_mapping_ctr_cipher(int key_len) { static bool aes_wrap(const uint8_t* in, int in_len, uint8_t* out, int* outLen, uint8_t* key, int key_len) { #if defined(ENABLE_OPENSSL) - EVP_CIPHER_CTX* ctx = NULL; + EVP_CIPHER_CTX* ctx = NULL; *outLen = 0; - do { + do { if (!(ctx = EVP_CIPHER_CTX_new())) { WarnL << "EVP_CIPHER_CTX_new fail"; break; @@ -62,29 +62,29 @@ static bool aes_wrap(const uint8_t* in, int in_len, uint8_t* out, int* outLen, u if (1 != EVP_EncryptInit_ex(ctx, aes_key_len_mapping_wrap_cipher(key_len), NULL, key, NULL)) { WarnL << "EVP_EncryptInit_ex fail"; - break; - } + break; + } - int len1 = 0; - if (1 != EVP_EncryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) { + int len1 = 0; + if (1 != EVP_EncryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) { WarnL << "EVP_EncryptUpdate fail"; - break; - } + break; + } - int len2 = 0; - if (1 != EVP_EncryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) { + int len2 = 0; + if (1 != EVP_EncryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) { WarnL << "EVP_EncryptFinal_ex fail"; - break; - } + break; + } - *outLen = len1 + len2; - } while (0); + *outLen = len1 + len2; + } while (0); - if (ctx != NULL) { - EVP_CIPHER_CTX_free(ctx); - } + if (ctx != NULL) { + EVP_CIPHER_CTX_free(ctx); + } - return *outLen != 0; + return *outLen != 0; #else return false; #endif @@ -103,11 +103,11 @@ static bool aes_wrap(const uint8_t* in, int in_len, uint8_t* out, int* outLen, u static bool aes_unwrap(const uint8_t* in, int in_len, uint8_t* out, int* outLen, uint8_t* key, int key_len) { #if defined(ENABLE_OPENSSL) - EVP_CIPHER_CTX* ctx = NULL; + EVP_CIPHER_CTX* ctx = NULL; *outLen = 0; - do { + do { if (!(ctx = EVP_CIPHER_CTX_new())) { WarnL << "EVP_CIPHER_CTX_new fail"; @@ -117,8 +117,8 @@ static bool aes_unwrap(const uint8_t* in, int in_len, uint8_t* out, int* outLen, if (1 != EVP_DecryptInit_ex(ctx, aes_key_len_mapping_wrap_cipher(key_len), NULL, key, NULL)) { WarnL << "EVP_DecryptInit_ex fail"; - break; - } + break; + } //设置pkcs7padding if (1 != EVP_CIPHER_CTX_set_padding(ctx, 1)) { @@ -126,26 +126,26 @@ static bool aes_unwrap(const uint8_t* in, int in_len, uint8_t* out, int* outLen, break; } - int len1 = 0; - if (1 != EVP_DecryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) { + int len1 = 0; + if (1 != EVP_DecryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) { WarnL << "EVP_DecryptUpdate fail"; - break; - } + break; + } - int len2 = 0; - if (1 != EVP_DecryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) { + int len2 = 0; + if (1 != EVP_DecryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) { WarnL << "EVP_DecryptFinal_ex fail"; - break; - } + break; + } - *outLen = len1 + len2; - } while (0); + *outLen = len1 + len2; + } while (0); - if (ctx != NULL) { - EVP_CIPHER_CTX_free(ctx); - } + if (ctx != NULL) { + EVP_CIPHER_CTX_free(ctx); + } - return *outLen != 0; + return *outLen != 0; #else return false; @@ -166,11 +166,11 @@ static bool aes_unwrap(const uint8_t* in, int in_len, uint8_t* out, int* outLen, static bool aes_ctr_encrypt(const uint8_t* in, int in_len, uint8_t* out, int* outLen, uint8_t* key, int key_len, uint8_t* iv) { #if defined(ENABLE_OPENSSL) - EVP_CIPHER_CTX* ctx = NULL; + EVP_CIPHER_CTX* ctx = NULL; *outLen = 0; - do { + do { if (!(ctx = EVP_CIPHER_CTX_new())) { WarnL << "EVP_CIPHER_CTX_new fail"; break; @@ -178,29 +178,29 @@ static bool aes_ctr_encrypt(const uint8_t* in, int in_len, uint8_t* out, int* ou if (1 != EVP_EncryptInit_ex(ctx, aes_key_len_mapping_ctr_cipher(key_len), NULL, key, iv)) { WarnL << "EVP_EncryptInit_ex fail"; - break; - } + break; + } - int len1 = 0; - if (1 != EVP_EncryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) { + int len1 = 0; + if (1 != EVP_EncryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) { WarnL << "EVP_EncryptUpdate fail"; - break; - } + break; + } - int len2 = 0; - if (1 != EVP_EncryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) { + int len2 = 0; + if (1 != EVP_EncryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) { WarnL << "EVP_EncryptFinal_ex fail"; - break; - } + break; + } - *outLen = len1 + len2; - } while (0); + *outLen = len1 + len2; + } while (0); - if (ctx != NULL) { - EVP_CIPHER_CTX_free(ctx); - } + if (ctx != NULL) { + EVP_CIPHER_CTX_free(ctx); + } - return *outLen != 0; + return *outLen != 0; #else return false; #endif @@ -221,42 +221,42 @@ static bool aes_ctr_encrypt(const uint8_t* in, int in_len, uint8_t* out, int* ou static bool aes_ctr_decrypt(const uint8_t* in, int in_len, uint8_t* out, int* outLen, uint8_t* key, int key_len, uint8_t* iv) { #if defined(ENABLE_OPENSSL) - EVP_CIPHER_CTX* ctx = NULL; + EVP_CIPHER_CTX* ctx = NULL; *outLen = 0; - do { + do { if (!(ctx = EVP_CIPHER_CTX_new())) { WarnL << "EVP_CIPHER_CTX_new fail"; break; } - if (1 != EVP_DecryptInit_ex(ctx, aes_key_len_mapping_ctr_cipher(key_len), NULL, key, iv)) { + if (1 != EVP_DecryptInit_ex(ctx, aes_key_len_mapping_ctr_cipher(key_len), NULL, key, iv)) { WarnL << "EVP_DecryptInit_ex fail"; - break; - } + break; + } - int len1 = 0; - if (1 != EVP_DecryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) { + int len1 = 0; + if (1 != EVP_DecryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) { WarnL << "EVP_DecryptUpdate fail"; - break; - } + break; + } - int len2 = 0; - if (1 != EVP_DecryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) { + int len2 = 0; + if (1 != EVP_DecryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) { WarnL << "EVP_DecryptFinal_ex fail"; - break; - } + break; + } - *outLen = len1 + len2; - } while (0); + *outLen = len1 + len2; + } while (0); - if (ctx != NULL) { - EVP_CIPHER_CTX_free(ctx); - } + if (ctx != NULL) { + EVP_CIPHER_CTX_free(ctx); + } - return *outLen != 0; + return *outLen != 0; #else return false; diff --git a/srt/Packet.cpp b/srt/Packet.cpp index 1692d8fa..f3f55ca0 100644 --- a/srt/Packet.cpp +++ b/srt/Packet.cpp @@ -353,7 +353,7 @@ bool HandshakePacket::loadExtMessage(uint8_t *buf, size_t len) { case HSExt::SRT_CMD_SID: ext = std::make_shared(); break; case HSExt::SRT_CMD_KMREQ: case HSExt::SRT_CMD_KMRSP: - ext = std::make_shared(); break; + ext = std::make_shared(); break; default: WarnL << "not support ext " << type; break; } if (ext) { diff --git a/srt/SrtSession.hpp b/srt/SrtSession.hpp index 4064534f..33579585 100644 --- a/srt/SrtSession.hpp +++ b/srt/SrtSession.hpp @@ -1,29 +1,29 @@ -#ifndef ZLMEDIAKIT_SRT_SESSION_H -#define ZLMEDIAKIT_SRT_SESSION_H - -#include "Network/Session.h" -#include "SrtTransport.hpp" - -namespace SRT { - -using namespace toolkit; - -class SrtSession : public Session { -public: - SrtSession(const Socket::Ptr &sock); - - void onRecv(const Buffer::Ptr &) override; - void onError(const SockException &err) override; - void onManager() override; - void attachServer(const toolkit::Server &server) override; - static EventPoller::Ptr queryPoller(const Buffer::Ptr &buffer); - -private: - bool _find_transport = true; - Ticker _ticker; - struct sockaddr_storage _peer_addr; - SrtTransport::Ptr _transport; -}; - -} // namespace SRT -#endif // ZLMEDIAKIT_SRT_SESSION_H \ No newline at end of file +#ifndef ZLMEDIAKIT_SRT_SESSION_H +#define ZLMEDIAKIT_SRT_SESSION_H + +#include "Network/Session.h" +#include "SrtTransport.hpp" + +namespace SRT { + +using namespace toolkit; + +class SrtSession : public Session { +public: + SrtSession(const Socket::Ptr &sock); + + void onRecv(const Buffer::Ptr &) override; + void onError(const SockException &err) override; + void onManager() override; + void attachServer(const toolkit::Server &server) override; + static EventPoller::Ptr queryPoller(const Buffer::Ptr &buffer); + +private: + bool _find_transport = true; + Ticker _ticker; + struct sockaddr_storage _peer_addr; + SrtTransport::Ptr _transport; +}; + +} // namespace SRT +#endif // ZLMEDIAKIT_SRT_SESSION_H diff --git a/srt/SrtTransport.cpp b/srt/SrtTransport.cpp index a968dbcd..d81d17b7 100644 --- a/srt/SrtTransport.cpp +++ b/srt/SrtTransport.cpp @@ -400,7 +400,7 @@ void SrtTransport::sendMsgDropReq(uint32_t first, uint32_t last) { } void SrtTransport::tryAnnounceKeyMaterial() { - //TraceL; + //TraceL; if (!_crypto) { return; diff --git a/srt/SrtTransport.hpp b/srt/SrtTransport.hpp index 36edf093..c9511633 100644 --- a/srt/SrtTransport.hpp +++ b/srt/SrtTransport.hpp @@ -169,8 +169,8 @@ private: // for encryption Crypto::Ptr _crypto; - Timer::Ptr _announce_timer; - KeyMaterialPacket::Ptr _announce_req; + Timer::Ptr _announce_timer; + KeyMaterialPacket::Ptr _announce_req; }; class SrtTransportManager { diff --git a/webrtc/DtlsTransport.cpp b/webrtc/DtlsTransport.cpp index b55906be..2480ce65 100644 --- a/webrtc/DtlsTransport.cpp +++ b/webrtc/DtlsTransport.cpp @@ -1,1442 +1,1445 @@ -/** -ISC License - -Copyright © 2015, Iñaki Baz Castillo - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#define MS_CLASS "RTC::DtlsTransport" -// #define MS_LOG_DEV_LEVEL 3 - -#include "DtlsTransport.hpp" -#include "logger.h" -#include -#include -#include -#include -#include -#include // std::sprintf(), std::fopen() -#include // std::memcpy(), std::strcmp() -#include "Util/util.h" -#include "Util/SSLBox.h" -#include "Util/SSLUtil.h" - -using namespace std; - -#define LOG_OPENSSL_ERROR(desc) \ - do \ - { \ - if (ERR_peek_error() == 0) \ - MS_ERROR("OpenSSL error [desc:'%s']", desc); \ - else \ - { \ - int64_t err; \ - while ((err = ERR_get_error()) != 0) \ - { \ - MS_ERROR("OpenSSL error [desc:'%s', error:'%s']", desc, ERR_error_string(err, nullptr)); \ - } \ - ERR_clear_error(); \ - } \ - } while (false) - -/* Static methods for OpenSSL callbacks. */ - -inline static int onSslCertificateVerify(int /*preverifyOk*/, X509_STORE_CTX* /*ctx*/) -{ - MS_TRACE(); - - // Always valid since DTLS certificates are self-signed. - return 1; -} - -inline static unsigned int onSslDtlsTimer(SSL* /*ssl*/, unsigned int timerUs) -{ - if (timerUs == 0) - return 100000; - else if (timerUs >= 4000000) - return 4000000; - else - return 2 * timerUs; -} - -namespace RTC -{ - /* Static. */ - - // clang-format off - static constexpr int DtlsMtu{ 1350 }; - // AES-HMAC: http://tools.ietf.org/html/rfc3711 - static constexpr size_t SrtpMasterKeyLength{ 16 }; - static constexpr size_t SrtpMasterSaltLength{ 14 }; - static constexpr size_t SrtpMasterLength{ SrtpMasterKeyLength + SrtpMasterSaltLength }; - // AES-GCM: http://tools.ietf.org/html/rfc7714 - static constexpr size_t SrtpAesGcm256MasterKeyLength{ 32 }; - static constexpr size_t SrtpAesGcm256MasterSaltLength{ 12 }; - static constexpr size_t SrtpAesGcm256MasterLength{ SrtpAesGcm256MasterKeyLength + SrtpAesGcm256MasterSaltLength }; - static constexpr size_t SrtpAesGcm128MasterKeyLength{ 16 }; - static constexpr size_t SrtpAesGcm128MasterSaltLength{ 12 }; - static constexpr size_t SrtpAesGcm128MasterLength{ SrtpAesGcm128MasterKeyLength + SrtpAesGcm128MasterSaltLength }; - // clang-format on - - /* Class variables. */ - // clang-format off - std::map DtlsTransport::string2FingerprintAlgorithm = - { - { "sha-1", DtlsTransport::FingerprintAlgorithm::SHA1 }, - { "sha-224", DtlsTransport::FingerprintAlgorithm::SHA224 }, - { "sha-256", DtlsTransport::FingerprintAlgorithm::SHA256 }, - { "sha-384", DtlsTransport::FingerprintAlgorithm::SHA384 }, - { "sha-512", DtlsTransport::FingerprintAlgorithm::SHA512 } - }; - std::map DtlsTransport::fingerprintAlgorithm2String = - { - { DtlsTransport::FingerprintAlgorithm::SHA1, "sha-1" }, - { DtlsTransport::FingerprintAlgorithm::SHA224, "sha-224" }, - { DtlsTransport::FingerprintAlgorithm::SHA256, "sha-256" }, - { DtlsTransport::FingerprintAlgorithm::SHA384, "sha-384" }, - { DtlsTransport::FingerprintAlgorithm::SHA512, "sha-512" } - }; - std::map DtlsTransport::string2Role = - { - { "auto", DtlsTransport::Role::AUTO }, - { "client", DtlsTransport::Role::CLIENT }, - { "server", DtlsTransport::Role::SERVER } - }; - std::vector DtlsTransport::srtpCryptoSuites = - { - { RTC::SrtpSession::CryptoSuite::AEAD_AES_256_GCM, "SRTP_AEAD_AES_256_GCM" }, - { RTC::SrtpSession::CryptoSuite::AEAD_AES_128_GCM, "SRTP_AEAD_AES_128_GCM" }, - { RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80, "SRTP_AES128_CM_SHA1_80" }, - { RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32, "SRTP_AES128_CM_SHA1_32" } - }; - // clang-format on - - INSTANCE_IMP(DtlsTransport::DtlsEnvironment); - - /* Class methods. */ - - DtlsTransport::DtlsEnvironment::DtlsEnvironment() - { - MS_TRACE(); - - // Generate a X509 certificate and private key (unless PEM files are provided). - auto ssl = toolkit::SSL_Initor::Instance().getSSLCtx("", true); - if (!ssl || !ReadCertificateAndPrivateKeyFromContext(ssl.get())) { - GenerateCertificateAndPrivateKey(); - } - - // Create a global SSL_CTX. - CreateSslCtx(); - - // Generate certificate fingerprints. - GenerateFingerprints(); - } - - DtlsTransport::DtlsEnvironment::~DtlsEnvironment() - { - MS_TRACE(); - - if (privateKey) - EVP_PKEY_free(privateKey); - if (certificate) - X509_free(certificate); - if (sslCtx) - SSL_CTX_free(sslCtx); - } - - void DtlsTransport::DtlsEnvironment::GenerateCertificateAndPrivateKey() - { - MS_TRACE(); - - int ret{ 0 }; - EC_KEY* ecKey{ nullptr }; - X509_NAME* certName{ nullptr }; - std::string subject = - std::string("mediasoup") + to_string(rand() % 999999 + 100000); - - // Create key with curve. - ecKey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); - - if (!ecKey) - { - LOG_OPENSSL_ERROR("EC_KEY_new_by_curve_name() failed"); - - goto error; - } - - EC_KEY_set_asn1_flag(ecKey, OPENSSL_EC_NAMED_CURVE); - - // NOTE: This can take some time. - ret = EC_KEY_generate_key(ecKey); - - if (ret == 0) - { - LOG_OPENSSL_ERROR("EC_KEY_generate_key() failed"); - - goto error; - } - - // Create a private key object. - privateKey = EVP_PKEY_new(); - - if (!privateKey) - { - LOG_OPENSSL_ERROR("EVP_PKEY_new() failed"); - - goto error; - } - - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) - ret = EVP_PKEY_assign_EC_KEY(privateKey, ecKey); - - if (ret == 0) - { - LOG_OPENSSL_ERROR("EVP_PKEY_assign_EC_KEY() failed"); - - goto error; - } - - // The EC key now belongs to the private key, so don't clean it up separately. - ecKey = nullptr; - - // Create the X509 certificate. - certificate = X509_new(); - - if (!certificate) - { - LOG_OPENSSL_ERROR("X509_new() failed"); - - goto error; - } - - // Set version 3 (note that 0 means version 1). - X509_set_version(certificate, 2); - - // Set serial number (avoid default 0). - ASN1_INTEGER_set( - X509_get_serialNumber(certificate), - static_cast(rand() % 999999 + 100000)); - - // Set valid period. - X509_gmtime_adj(X509_get_notBefore(certificate), -315360000); // -10 years. - X509_gmtime_adj(X509_get_notAfter(certificate), 315360000); // 10 years. - - // Set the public key for the certificate using the key. - ret = X509_set_pubkey(certificate, privateKey); - - if (ret == 0) - { - LOG_OPENSSL_ERROR("X509_set_pubkey() failed"); - - goto error; - } - - // Set certificate fields. - certName = X509_get_subject_name(certificate); - - if (!certName) - { - LOG_OPENSSL_ERROR("X509_get_subject_name() failed"); - - goto error; - } - - X509_NAME_add_entry_by_txt( - certName, "O", MBSTRING_ASC, reinterpret_cast(subject.c_str()), -1, -1, 0); - X509_NAME_add_entry_by_txt( - certName, "CN", MBSTRING_ASC, reinterpret_cast(subject.c_str()), -1, -1, 0); - - // It is self-signed so set the issuer name to be the same as the subject. - ret = X509_set_issuer_name(certificate, certName); - - if (ret == 0) - { - LOG_OPENSSL_ERROR("X509_set_issuer_name() failed"); - - goto error; - } - - // Sign the certificate with its own private key. - ret = X509_sign(certificate, privateKey, EVP_sha1()); - - if (ret == 0) - { - LOG_OPENSSL_ERROR("X509_sign() failed"); - - goto error; - } - - return; - - error: - - if (ecKey) - EC_KEY_free(ecKey); - - if (privateKey) - EVP_PKEY_free(privateKey); // NOTE: This also frees the EC key. - - if (certificate) - X509_free(certificate); - - MS_THROW_ERROR("DTLS certificate and private key generation failed"); - } - - bool DtlsTransport::DtlsEnvironment::ReadCertificateAndPrivateKeyFromContext(SSL_CTX *ctx) - { - MS_TRACE(); - certificate = SSL_CTX_get0_certificate(ctx); - if (!certificate) { - return false; - } - X509_up_ref(certificate); - - privateKey = SSL_CTX_get0_privatekey(ctx); - if (!privateKey) { - return false; - } - EVP_PKEY_up_ref(privateKey); - InfoL << "Load webrtc dtls certificate: " << toolkit::SSLUtil::getServerName(certificate); - return true; - } - - void DtlsTransport::DtlsEnvironment::CreateSslCtx() - { - MS_TRACE(); - - std::string dtlsSrtpCryptoSuites; - int ret; - - /* Set the global DTLS context. */ - - // Both DTLS 1.0 and 1.2 (requires OpenSSL >= 1.1.0). - sslCtx = SSL_CTX_new(DTLS_method()); - - if (!sslCtx) - { - LOG_OPENSSL_ERROR("SSL_CTX_new() failed"); - - goto error; - } - - ret = SSL_CTX_use_certificate(sslCtx, certificate); - - if (ret == 0) - { - LOG_OPENSSL_ERROR("SSL_CTX_use_certificate() failed"); - - goto error; - } - - ret = SSL_CTX_use_PrivateKey(sslCtx, privateKey); - - if (ret == 0) - { - LOG_OPENSSL_ERROR("SSL_CTX_use_PrivateKey() failed"); - - goto error; - } - - ret = SSL_CTX_check_private_key(sslCtx); - - if (ret == 0) - { - LOG_OPENSSL_ERROR("SSL_CTX_check_private_key() failed"); - - goto error; - } - - // Set options. - SSL_CTX_set_options( - sslCtx, - SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_TICKET | SSL_OP_SINGLE_ECDH_USE | - SSL_OP_NO_QUERY_MTU); - - // Don't use sessions cache. - SSL_CTX_set_session_cache_mode(sslCtx, SSL_SESS_CACHE_OFF); - - // Read always as much into the buffer as possible. - // NOTE: This is the default for DTLS, but a bug in non latest OpenSSL - // versions makes this call required. - SSL_CTX_set_read_ahead(sslCtx, 1); - - SSL_CTX_set_verify_depth(sslCtx, 4); - - // Require certificate from peer. - SSL_CTX_set_verify( - sslCtx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, onSslCertificateVerify); - - // Set SSL info callback. - SSL_CTX_set_info_callback(sslCtx, [](const SSL* ssl, int where, int ret){ - static_cast(SSL_get_ex_data(ssl, 0))->OnSslInfo(where, ret); - }); - // Set ciphers. - ret = SSL_CTX_set_cipher_list( - sslCtx, "DEFAULT:!NULL:!aNULL:!SHA256:!SHA384:!aECDH:!AESGCM+AES256:!aPSK:!RC4"); - - if (ret == 0) - { - LOG_OPENSSL_ERROR("SSL_CTX_set_cipher_list() failed"); - - goto error; - } - - // Enable ECDH ciphers. - // DOC: http://en.wikibooks.org/wiki/OpenSSL/Diffie-Hellman_parameters - // NOTE: https://code.google.com/p/chromium/issues/detail?id=406458 - // NOTE: https://bugs.ruby-lang.org/issues/12324 - - // For OpenSSL >= 1.0.2. - SSL_CTX_set_ecdh_auto(sslCtx, 1); - - // Set the "use_srtp" DTLS extension. - for (auto it = DtlsTransport::srtpCryptoSuites.begin(); - it != DtlsTransport::srtpCryptoSuites.end(); - ++it) - { - if (it != DtlsTransport::srtpCryptoSuites.begin()) - dtlsSrtpCryptoSuites += ":"; - - SrtpCryptoSuiteMapEntry* cryptoSuiteEntry = std::addressof(*it); - dtlsSrtpCryptoSuites += cryptoSuiteEntry->name; - } - - MS_DEBUG_2TAGS(dtls, srtp, "setting SRTP cryptoSuites for DTLS: %s", dtlsSrtpCryptoSuites.c_str()); - - // NOTE: This function returns 0 on success. - ret = SSL_CTX_set_tlsext_use_srtp(sslCtx, dtlsSrtpCryptoSuites.c_str()); - - if (ret != 0) - { - MS_ERROR( - "SSL_CTX_set_tlsext_use_srtp() failed when entering '%s'", dtlsSrtpCryptoSuites.c_str()); - LOG_OPENSSL_ERROR("SSL_CTX_set_tlsext_use_srtp() failed"); - - goto error; - } - - return; - - error: - - if (sslCtx) - { - SSL_CTX_free(sslCtx); - sslCtx = nullptr; - } - - MS_THROW_ERROR("SSL context creation failed"); - } - - void DtlsTransport::DtlsEnvironment::GenerateFingerprints() - { - MS_TRACE(); - - for (auto& kv : DtlsTransport::string2FingerprintAlgorithm) - { - const std::string& algorithmString = kv.first; - FingerprintAlgorithm algorithm = kv.second; - uint8_t binaryFingerprint[EVP_MAX_MD_SIZE]; - unsigned int size{ 0 }; - char hexFingerprint[(EVP_MAX_MD_SIZE * 3) + 1]; - const EVP_MD* hashFunction; - int ret; - - switch (algorithm) - { - case FingerprintAlgorithm::SHA1: - hashFunction = EVP_sha1(); - break; - - case FingerprintAlgorithm::SHA224: - hashFunction = EVP_sha224(); - break; - - case FingerprintAlgorithm::SHA256: - hashFunction = EVP_sha256(); - break; - - case FingerprintAlgorithm::SHA384: - hashFunction = EVP_sha384(); - break; - - case FingerprintAlgorithm::SHA512: - hashFunction = EVP_sha512(); - break; - - default: - MS_THROW_ERROR("unknown algorithm"); - } - - ret = X509_digest(certificate, hashFunction, binaryFingerprint, &size); - - if (ret == 0) - { - MS_ERROR("X509_digest() failed"); - MS_THROW_ERROR("Fingerprints generation failed"); - } - - // Convert to hexadecimal format in uppercase with colons. - for (unsigned int i{ 0 }; i < size; ++i) - { - std::sprintf(hexFingerprint + (i * 3), "%.2X:", binaryFingerprint[i]); - } - hexFingerprint[(size * 3) - 1] = '\0'; - - MS_DEBUG_TAG(dtls, "%-7s fingerprint: %s", algorithmString.c_str(), hexFingerprint); - - // Store it in the vector. - DtlsTransport::Fingerprint fingerprint; - - fingerprint.algorithm = DtlsTransport::GetFingerprintAlgorithm(algorithmString); - fingerprint.value = hexFingerprint; - - localFingerprints.push_back(fingerprint); - } - } - - /* Instance methods. */ - - DtlsTransport::DtlsTransport(EventPoller::Ptr poller,Listener* listener) : poller(std::move(poller)), listener(listener) - { - MS_TRACE(); - env = DtlsEnvironment::Instance().shared_from_this(); - - /* Set SSL. */ - - this->ssl = SSL_new(env->sslCtx); - - if (!this->ssl) - { - LOG_OPENSSL_ERROR("SSL_new() failed"); - - goto error; - } - - // Set this as custom data. - SSL_set_ex_data(this->ssl, 0, static_cast(this)); - - this->sslBioFromNetwork = BIO_new(BIO_s_mem()); - - if (!this->sslBioFromNetwork) - { - LOG_OPENSSL_ERROR("BIO_new() failed"); - - SSL_free(this->ssl); - - goto error; - } - - this->sslBioToNetwork = BIO_new(BIO_s_mem()); - - if (!this->sslBioToNetwork) - { - LOG_OPENSSL_ERROR("BIO_new() failed"); - - BIO_free(this->sslBioFromNetwork); - SSL_free(this->ssl); - - goto error; - } - - SSL_set_bio(this->ssl, this->sslBioFromNetwork, this->sslBioToNetwork); - - // Set the MTU so that we don't send packets that are too large with no fragmentation. - SSL_set_mtu(this->ssl, DtlsMtu); - DTLS_set_link_mtu(this->ssl, DtlsMtu); - - // Set callback handler for setting DTLS timer interval. - DTLS_set_timer_cb(this->ssl, onSslDtlsTimer); - - return; - - error: - - // NOTE: At this point SSL_set_bio() was not called so we must free BIOs as - // well. - if (this->sslBioFromNetwork) - BIO_free(this->sslBioFromNetwork); - - if (this->sslBioToNetwork) - BIO_free(this->sslBioToNetwork); - - if (this->ssl) - SSL_free(this->ssl); - - // NOTE: If this is not catched by the caller the program will abort, but - // this should never happen. - MS_THROW_ERROR("DtlsTransport instance creation failed"); - } - - DtlsTransport::~DtlsTransport() - { - MS_TRACE(); - - if (IsRunning()) - { - // Send close alert to the peer. - SSL_shutdown(this->ssl); - SendPendingOutgoingDtlsData(); - } - - if (this->ssl) - { - SSL_free(this->ssl); - - this->ssl = nullptr; - this->sslBioFromNetwork = nullptr; - this->sslBioToNetwork = nullptr; - } - - // Close the DTLS timer. - this->timer = nullptr; - } - - void DtlsTransport::Dump() const - { - MS_TRACE(); - - std::string state{ "new" }; - std::string role{ "none " }; - - switch (this->state) - { - case DtlsState::CONNECTING: - state = "connecting"; - break; - case DtlsState::CONNECTED: - state = "connected"; - break; - case DtlsState::FAILED: - state = "failed"; - break; - case DtlsState::CLOSED: - state = "closed"; - break; - default:; - } - - switch (this->localRole) - { - case Role::AUTO: - role = "auto"; - break; - case Role::SERVER: - role = "server"; - break; - case Role::CLIENT: - role = "client"; - break; - default:; - } - - MS_DUMP(""); - MS_DUMP(" state : %s", state.c_str()); - MS_DUMP(" role : %s", role.c_str()); - MS_DUMP(" handshake done: : %s", this->handshakeDone ? "yes" : "no"); - MS_DUMP(""); - } - - void DtlsTransport::Run(Role localRole) - { - MS_TRACE(); - - MS_ASSERT( - localRole == Role::CLIENT || localRole == Role::SERVER, - "local DTLS role must be 'client' or 'server'"); - - Role previousLocalRole = this->localRole; - - if (localRole == previousLocalRole) - { - MS_ERROR("same local DTLS role provided, doing nothing"); - - return; - } - - // If the previous local DTLS role was 'client' or 'server' do reset. - if (previousLocalRole == Role::CLIENT || previousLocalRole == Role::SERVER) - { - MS_DEBUG_TAG(dtls, "resetting DTLS due to local role change"); - - Reset(); - } - - // Update local role. - this->localRole = localRole; - - // Set state and notify the listener. - this->state = DtlsState::CONNECTING; - this->listener->OnDtlsTransportConnecting(this); - - switch (this->localRole) - { - case Role::CLIENT: - { - MS_DEBUG_TAG(dtls, "running [role:client]"); - - SSL_set_connect_state(this->ssl); - SSL_do_handshake(this->ssl); - SendPendingOutgoingDtlsData(); - SetTimeout(); - - break; - } - - case Role::SERVER: - { - MS_DEBUG_TAG(dtls, "running [role:server]"); - - SSL_set_accept_state(this->ssl); - SSL_do_handshake(this->ssl); - - break; - } - - default: - { - MS_ABORT("invalid local DTLS role"); - } - } - } - - bool DtlsTransport::SetRemoteFingerprint(Fingerprint fingerprint) - { - MS_TRACE(); - - MS_ASSERT( - fingerprint.algorithm != FingerprintAlgorithm::NONE, "no fingerprint algorithm provided"); - - this->remoteFingerprint = fingerprint; - - // The remote fingerpring may have been set after DTLS handshake was done, - // so we may need to process it now. - if (this->handshakeDone && this->state != DtlsState::CONNECTED) - { - MS_DEBUG_TAG(dtls, "handshake already done, processing it right now"); - - return ProcessHandshake(); - } - - return true; - } - - void DtlsTransport::ProcessDtlsData(const uint8_t* data, size_t len) - { - MS_TRACE(); - - int written; - int read; - - if (!IsRunning()) - { - MS_WARN_TAG(nullptr,"cannot process data while not running"); - return; - } - - // Write the received DTLS data into the sslBioFromNetwork. - written = - BIO_write(this->sslBioFromNetwork, static_cast(data), static_cast(len)); - - if (written != static_cast(len)) - { - MS_WARN_TAG( - dtls, - "OpenSSL BIO_write() wrote less (%zu bytes) than given data (%zu bytes)", - static_cast(written), - len); - } - - // Must call SSL_read() to process received DTLS data. - read = SSL_read(this->ssl, static_cast(DtlsTransport::sslReadBuffer), SslReadBufferSize); - - // Send data if it's ready. - SendPendingOutgoingDtlsData(); - - // Check SSL status and return if it is bad/closed. - if (!CheckStatus(read)) - return; - - // Set/update the DTLS timeout. - if (!SetTimeout()) - return; - - // Application data received. Notify to the listener. - if (read > 0) - { - // It is allowed to receive DTLS data even before validating remote fingerprint. - if (!this->handshakeDone) - { - MS_WARN_TAG(dtls, "ignoring application data received while DTLS handshake not done"); - - return; - } - - // Notify the listener. - this->listener->OnDtlsTransportApplicationDataReceived( - this, (uint8_t*)DtlsTransport::sslReadBuffer, static_cast(read)); - } - } - - void DtlsTransport::SendApplicationData(const uint8_t* data, size_t len) - { - MS_TRACE(); - - // We cannot send data to the peer if its remote fingerprint is not validated. - if (this->state != DtlsState::CONNECTED) - { - MS_WARN_TAG(dtls, "cannot send application data while DTLS is not fully connected"); - - return; - } - - if (len == 0) - { - MS_WARN_TAG(dtls, "ignoring 0 length data"); - - return; - } - - int written; - - written = SSL_write(this->ssl, static_cast(data), static_cast(len)); - - if (written < 0) - { - LOG_OPENSSL_ERROR("SSL_write() failed"); - - if (!CheckStatus(written)) - return; - } - else if (written != static_cast(len)) - { - MS_WARN_TAG( - dtls, "OpenSSL SSL_write() wrote less (%d bytes) than given data (%zu bytes)", written, len); - } - - // Send data. - SendPendingOutgoingDtlsData(); - } - - void DtlsTransport::Reset() - { - MS_TRACE(); - - int ret; - - if (!IsRunning()) - return; - - MS_WARN_TAG(dtls, "resetting DTLS transport"); - - // Stop the DTLS timer. - this->timer = nullptr; - - // We need to reset the SSL instance so we need to "shutdown" it, but we - // don't want to send a Close Alert to the peer, so just don't call - // SendPendingOutgoingDTLSData(). - SSL_shutdown(this->ssl); - - this->localRole = Role::NONE; - this->state = DtlsState::NEW; - this->handshakeDone = false; - this->handshakeDoneNow = false; - - // Reset SSL status. - // NOTE: For this to properly work, SSL_shutdown() must be called before. - // NOTE: This may fail if not enough DTLS handshake data has been received, - // but we don't care so just clear the error queue. - ret = SSL_clear(this->ssl); - - if (ret == 0) - ERR_clear_error(); - } - - inline bool DtlsTransport::CheckStatus(int returnCode) - { - MS_TRACE(); - - int err; - bool wasHandshakeDone = this->handshakeDone; - - err = SSL_get_error(this->ssl, returnCode); - - switch (err) - { - case SSL_ERROR_NONE: - break; - - case SSL_ERROR_SSL: - LOG_OPENSSL_ERROR("SSL status: SSL_ERROR_SSL"); - break; - - case SSL_ERROR_WANT_READ: - break; - - case SSL_ERROR_WANT_WRITE: - MS_WARN_TAG(dtls, "SSL status: SSL_ERROR_WANT_WRITE"); - break; - - case SSL_ERROR_WANT_X509_LOOKUP: - MS_DEBUG_TAG(dtls, "SSL status: SSL_ERROR_WANT_X509_LOOKUP"); - break; - - case SSL_ERROR_SYSCALL: - LOG_OPENSSL_ERROR("SSL status: SSL_ERROR_SYSCALL"); - break; - - case SSL_ERROR_ZERO_RETURN: - break; - - case SSL_ERROR_WANT_CONNECT: - MS_WARN_TAG(dtls, "SSL status: SSL_ERROR_WANT_CONNECT"); - break; - - case SSL_ERROR_WANT_ACCEPT: - MS_WARN_TAG(dtls, "SSL status: SSL_ERROR_WANT_ACCEPT"); - break; - - default: - MS_WARN_TAG(dtls, "SSL status: unknown error"); - } - - // Check if the handshake (or re-handshake) has been done right now. - if (this->handshakeDoneNow) - { - this->handshakeDoneNow = false; - this->handshakeDone = true; - - // Stop the timer. - this->timer = nullptr; - - // Process the handshake just once (ignore if DTLS renegotiation). - if (!wasHandshakeDone && this->remoteFingerprint.algorithm != FingerprintAlgorithm::NONE) - return ProcessHandshake(); - - return true; - } - // Check if the peer sent close alert or a fatal error happened. - else if (((SSL_get_shutdown(this->ssl) & SSL_RECEIVED_SHUTDOWN) != 0) || err == SSL_ERROR_SSL || err == SSL_ERROR_SYSCALL) - { - if (this->state == DtlsState::CONNECTED) - { - MS_DEBUG_TAG(dtls, "disconnected"); - - Reset(); - - // Set state and notify the listener. - this->state = DtlsState::CLOSED; - this->listener->OnDtlsTransportClosed(this); - } - else - { - MS_WARN_TAG(dtls, "connection failed"); - - Reset(); - - // Set state and notify the listener. - this->state = DtlsState::FAILED; - this->listener->OnDtlsTransportFailed(this); - } - - return false; - } - else - { - return true; - } - } - - inline void DtlsTransport::SendPendingOutgoingDtlsData() - { - MS_TRACE(); - - if (BIO_eof(this->sslBioToNetwork)) - return; - - int64_t read; - char* data{ nullptr }; - - read = BIO_get_mem_data(this->sslBioToNetwork, &data); // NOLINT - - if (read <= 0) - return; - - MS_DEBUG_DEV("%" PRIu64 " bytes of DTLS data ready to sent to the peer", read); - - // Notify the listener. - this->listener->OnDtlsTransportSendData( - this, reinterpret_cast(data), static_cast(read)); - - // Clear the BIO buffer. - // NOTE: the (void) avoids the -Wunused-value warning. - (void)BIO_reset(this->sslBioToNetwork); - } - - inline bool DtlsTransport::SetTimeout() - { - MS_TRACE(); - - MS_ASSERT( - this->state == DtlsState::CONNECTING || this->state == DtlsState::CONNECTED, - "invalid DTLS state"); - - int64_t ret; - struct timeval dtlsTimeout{ 0, 0 }; - uint64_t timeoutMs; - - // NOTE: If ret == 0 then ignore the value in dtlsTimeout. - // NOTE: No DTLSv_1_2_get_timeout() or DTLS_get_timeout() in OpenSSL 1.1.0-dev. - ret = DTLSv1_get_timeout(this->ssl, static_cast(&dtlsTimeout)); // NOLINT - - if (ret == 0) - return true; - - timeoutMs = (dtlsTimeout.tv_sec * static_cast(1000)) + (dtlsTimeout.tv_usec / 1000); - - if (timeoutMs == 0) - { - return true; - } - else if (timeoutMs < 30000) - { - MS_DEBUG_DEV("DTLS timer set in %" PRIu64 "ms", timeoutMs); - - weak_ptr weak_self = shared_from_this(); - this->timer = std::make_shared(timeoutMs / 1000.0f, [weak_self](){ - auto strong_self = weak_self.lock(); - if(strong_self){ - strong_self->OnTimer(); - } - return true; - }, this->poller); - - return true; - } - // NOTE: Don't start the timer again if the timeout is greater than 30 seconds. - else - { - MS_WARN_TAG(dtls, "DTLS timeout too high (%" PRIu64 "ms), resetting DLTS", timeoutMs); - - Reset(); - - // Set state and notify the listener. - this->state = DtlsState::FAILED; - this->listener->OnDtlsTransportFailed(this); - - return false; - } - } - - inline bool DtlsTransport::ProcessHandshake() - { - MS_TRACE(); - - MS_ASSERT(this->handshakeDone, "handshake not done yet"); - MS_ASSERT( - this->remoteFingerprint.algorithm != FingerprintAlgorithm::NONE, "remote fingerprint not set"); - - // Validate the remote fingerprint. - if (!CheckRemoteFingerprint()) - { - Reset(); - - // Set state and notify the listener. - this->state = DtlsState::FAILED; - this->listener->OnDtlsTransportFailed(this); - - return false; - } - - // Get the negotiated SRTP crypto suite. - RTC::SrtpSession::CryptoSuite srtpCryptoSuite = GetNegotiatedSrtpCryptoSuite(); - - if (srtpCryptoSuite != RTC::SrtpSession::CryptoSuite::NONE) - { - // Extract the SRTP keys (will notify the listener with them). - ExtractSrtpKeys(srtpCryptoSuite); - - return true; - } - - // NOTE: We assume that "use_srtp" DTLS extension is required even if - // there is no audio/video. - MS_WARN_2TAGS(dtls, srtp, "SRTP crypto suite not negotiated"); - - Reset(); - - // Set state and notify the listener. - this->state = DtlsState::FAILED; - this->listener->OnDtlsTransportFailed(this); - - return false; - } - - inline bool DtlsTransport::CheckRemoteFingerprint() - { - MS_TRACE(); - - MS_ASSERT( - this->remoteFingerprint.algorithm != FingerprintAlgorithm::NONE, "remote fingerprint not set"); - - X509* certificate; - uint8_t binaryFingerprint[EVP_MAX_MD_SIZE]; - unsigned int size{ 0 }; - char hexFingerprint[(EVP_MAX_MD_SIZE * 3) + 1]; - const EVP_MD* hashFunction; - int ret; - - certificate = SSL_get_peer_certificate(this->ssl); - - if (!certificate) - { - MS_WARN_TAG(dtls, "no certificate was provided by the peer"); - - return false; - } - - switch (this->remoteFingerprint.algorithm) - { - case FingerprintAlgorithm::SHA1: - hashFunction = EVP_sha1(); - break; - - case FingerprintAlgorithm::SHA224: - hashFunction = EVP_sha224(); - break; - - case FingerprintAlgorithm::SHA256: - hashFunction = EVP_sha256(); - break; - - case FingerprintAlgorithm::SHA384: - hashFunction = EVP_sha384(); - break; - - case FingerprintAlgorithm::SHA512: - hashFunction = EVP_sha512(); - break; - - default: - MS_ABORT("unknown algorithm"); - } - - // Compare the remote fingerprint with the value given via signaling. - ret = X509_digest(certificate, hashFunction, binaryFingerprint, &size); - - if (ret == 0) - { - MS_ERROR("X509_digest() failed"); - - X509_free(certificate); - - return false; - } - - // Convert to hexadecimal format in uppercase with colons. - for (unsigned int i{ 0 }; i < size; ++i) - { - std::sprintf(hexFingerprint + (i * 3), "%.2X:", binaryFingerprint[i]); - } - hexFingerprint[(size * 3) - 1] = '\0'; - - if (this->remoteFingerprint.value != hexFingerprint) - { - MS_WARN_TAG( - dtls, - "fingerprint in the remote certificate (%s) does not match the announced one (%s)", - hexFingerprint, - this->remoteFingerprint.value.c_str()); - X509_free(certificate); - return false; - } - - MS_DEBUG_TAG(dtls, "valid remote fingerprint"); - - // Get the remote certificate in PEM format. - - BIO* bio = BIO_new(BIO_s_mem()); - - // Ensure the underlying BUF_MEM structure is also freed. - // NOTE: Avoid stupid "warning: value computed is not used [-Wunused-value]" since - // BIO_set_close() always returns 1. - (void)BIO_set_close(bio, BIO_CLOSE); - - ret = PEM_write_bio_X509(bio, certificate); - - if (ret != 1) - { - LOG_OPENSSL_ERROR("PEM_write_bio_X509() failed"); - - X509_free(certificate); - BIO_free(bio); - - return false; - } - - BUF_MEM* mem; - - BIO_get_mem_ptr(bio, &mem); // NOLINT[cppcoreguidelines-pro-type-cstyle-cast] - - if (!mem || !mem->data || mem->length == 0u) - { - LOG_OPENSSL_ERROR("BIO_get_mem_ptr() failed"); - - X509_free(certificate); - BIO_free(bio); - - return false; - } - - this->remoteCert = std::string(mem->data, mem->length); - - X509_free(certificate); - BIO_free(bio); - - return true; - } - - inline void DtlsTransport::ExtractSrtpKeys(RTC::SrtpSession::CryptoSuite srtpCryptoSuite) - { - MS_TRACE(); - - size_t srtpKeyLength{ 0 }; - size_t srtpSaltLength{ 0 }; - size_t srtpMasterLength{ 0 }; - - switch (srtpCryptoSuite) - { - case RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80: - case RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32: - { - srtpKeyLength = SrtpMasterKeyLength; - srtpSaltLength = SrtpMasterSaltLength; - srtpMasterLength = SrtpMasterLength; - - break; - } - - case RTC::SrtpSession::CryptoSuite::AEAD_AES_256_GCM: - { - srtpKeyLength = SrtpAesGcm256MasterKeyLength; - srtpSaltLength = SrtpAesGcm256MasterSaltLength; - srtpMasterLength = SrtpAesGcm256MasterLength; - - break; - } - - case RTC::SrtpSession::CryptoSuite::AEAD_AES_128_GCM: - { - srtpKeyLength = SrtpAesGcm128MasterKeyLength; - srtpSaltLength = SrtpAesGcm128MasterSaltLength; - srtpMasterLength = SrtpAesGcm128MasterLength; - - break; - } - - default: - { - MS_ABORT("unknown SRTP crypto suite"); - } - } - - auto* srtpMaterial = new uint8_t[srtpMasterLength * 2]; - uint8_t* srtpLocalKey{ nullptr }; - uint8_t* srtpLocalSalt{ nullptr }; - uint8_t* srtpRemoteKey{ nullptr }; - uint8_t* srtpRemoteSalt{ nullptr }; - auto* srtpLocalMasterKey = new uint8_t[srtpMasterLength]; - auto* srtpRemoteMasterKey = new uint8_t[srtpMasterLength]; - int ret; - - ret = SSL_export_keying_material( - this->ssl, srtpMaterial, srtpMasterLength * 2, "EXTRACTOR-dtls_srtp", 19, nullptr, 0, 0); - - MS_ASSERT(ret != 0, "SSL_export_keying_material() failed"); - - switch (this->localRole) - { - case Role::SERVER: - { - srtpRemoteKey = srtpMaterial; - srtpLocalKey = srtpRemoteKey + srtpKeyLength; - srtpRemoteSalt = srtpLocalKey + srtpKeyLength; - srtpLocalSalt = srtpRemoteSalt + srtpSaltLength; - - break; - } - - case Role::CLIENT: - { - srtpLocalKey = srtpMaterial; - srtpRemoteKey = srtpLocalKey + srtpKeyLength; - srtpLocalSalt = srtpRemoteKey + srtpKeyLength; - srtpRemoteSalt = srtpLocalSalt + srtpSaltLength; - - break; - } - - default: - { - MS_ABORT("no DTLS role set"); - } - } - - // Create the SRTP local master key. - std::memcpy(srtpLocalMasterKey, srtpLocalKey, srtpKeyLength); - std::memcpy(srtpLocalMasterKey + srtpKeyLength, srtpLocalSalt, srtpSaltLength); - // Create the SRTP remote master key. - std::memcpy(srtpRemoteMasterKey, srtpRemoteKey, srtpKeyLength); - std::memcpy(srtpRemoteMasterKey + srtpKeyLength, srtpRemoteSalt, srtpSaltLength); - - // Set state and notify the listener. - this->state = DtlsState::CONNECTED; - this->listener->OnDtlsTransportConnected( - this, - srtpCryptoSuite, - srtpLocalMasterKey, - srtpMasterLength, - srtpRemoteMasterKey, - srtpMasterLength, - this->remoteCert); - - delete[] srtpMaterial; - delete[] srtpLocalMasterKey; - delete[] srtpRemoteMasterKey; - } - - inline RTC::SrtpSession::CryptoSuite DtlsTransport::GetNegotiatedSrtpCryptoSuite() - { - MS_TRACE(); - - RTC::SrtpSession::CryptoSuite negotiatedSrtpCryptoSuite = RTC::SrtpSession::CryptoSuite::NONE; - - // Ensure that the SRTP crypto suite has been negotiated. - // NOTE: This is a OpenSSL type. - SRTP_PROTECTION_PROFILE* sslSrtpCryptoSuite = SSL_get_selected_srtp_profile(this->ssl); - - if (!sslSrtpCryptoSuite) - return negotiatedSrtpCryptoSuite; - - // Get the negotiated SRTP crypto suite. - for (auto& srtpCryptoSuite : DtlsTransport::srtpCryptoSuites) - { - SrtpCryptoSuiteMapEntry* cryptoSuiteEntry = std::addressof(srtpCryptoSuite); - - if (std::strcmp(sslSrtpCryptoSuite->name, cryptoSuiteEntry->name) == 0) - { - MS_DEBUG_2TAGS(dtls, srtp, "chosen SRTP crypto suite: %s", cryptoSuiteEntry->name); - - negotiatedSrtpCryptoSuite = cryptoSuiteEntry->cryptoSuite; - } - } - - MS_ASSERT( - negotiatedSrtpCryptoSuite != RTC::SrtpSession::CryptoSuite::NONE, - "chosen SRTP crypto suite is not an available one"); - - return negotiatedSrtpCryptoSuite; - } - - inline void DtlsTransport::OnSslInfo(int where, int ret) - { - MS_TRACE(); - - int w = where & -SSL_ST_MASK; - const char* role; - - if ((w & SSL_ST_CONNECT) != 0) - role = "client"; - else if ((w & SSL_ST_ACCEPT) != 0) - role = "server"; - else - role = "undefined"; - - if ((where & SSL_CB_LOOP) != 0) - { - MS_DEBUG_TAG(dtls, "[role:%s, action:'%s']", role, SSL_state_string_long(this->ssl)); - } - else if ((where & SSL_CB_ALERT) != 0) - { - const char* alertType; - - switch (*SSL_alert_type_string(ret)) - { - case 'W': - alertType = "warning"; - break; - - case 'F': - alertType = "fatal"; - break; - - default: - alertType = "undefined"; - } - - if ((where & SSL_CB_READ) != 0) - { - MS_WARN_TAG(dtls, "received DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret)); - } - else if ((where & SSL_CB_WRITE) != 0) - { - MS_DEBUG_TAG(dtls, "sending DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret)); - } - else - { - MS_DEBUG_TAG(dtls, "DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret)); - } - } - else if ((where & SSL_CB_EXIT) != 0) - { - if (ret == 0) - MS_DEBUG_TAG(dtls, "[role:%s, failed:'%s']", role, SSL_state_string_long(this->ssl)); - else if (ret < 0) - MS_DEBUG_TAG(dtls, "role: %s, waiting:'%s']", role, SSL_state_string_long(this->ssl)); - } - else if ((where & SSL_CB_HANDSHAKE_START) != 0) - { - MS_DEBUG_TAG(dtls, "DTLS handshake start"); - } - else if ((where & SSL_CB_HANDSHAKE_DONE) != 0) - { - MS_DEBUG_TAG(dtls, "DTLS handshake done"); - - this->handshakeDoneNow = true; - } - - // NOTE: checking SSL_get_shutdown(this->ssl) & SSL_RECEIVED_SHUTDOWN here upon - // receipt of a close alert does not work (the flag is set after this callback). - } - - inline void DtlsTransport::OnTimer() - { - MS_TRACE(); - - // Workaround for https://github.com/openssl/openssl/issues/7998. - if (this->handshakeDone) - { - // MS_DEBUG_DEV("handshake is done so return"); - return; - } - - DTLSv1_handle_timeout(this->ssl); - - // If required, send DTLS data. - SendPendingOutgoingDtlsData(); - - // Set the DTLS timer again. - SetTimeout(); - } -} // namespace RTC +/** +ISC License + +Copyright © 2015, Iñaki Baz Castillo + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define MS_CLASS "RTC::DtlsTransport" +// #define MS_LOG_DEV_LEVEL 3 + +#include "DtlsTransport.hpp" +#include "logger.h" +#include +#include +#include +#include +#include +#include // std::sprintf(), std::fopen() +#include // std::memcpy(), std::strcmp() +#include "Util/util.h" +#include "Util/SSLBox.h" +#include "Util/SSLUtil.h" + +using namespace std; +using namespace toolkit; + +#define LOG_OPENSSL_ERROR(desc) \ + do \ + { \ + if (ERR_peek_error() == 0) \ + MS_ERROR("OpenSSL error [desc:'%s']", desc); \ + else \ + { \ + int64_t err; \ + while ((err = ERR_get_error()) != 0) \ + { \ + MS_ERROR("OpenSSL error [desc:'%s', error:'%s']", desc, ERR_error_string(err, nullptr)); \ + } \ + ERR_clear_error(); \ + } \ + } while (false) + +/* Static methods for OpenSSL callbacks. */ + +inline static int onSslCertificateVerify(int /*preverifyOk*/, X509_STORE_CTX* /*ctx*/) +{ + MS_TRACE(); + + // Always valid since DTLS certificates are self-signed. + return 1; +} + +inline static unsigned int onSslDtlsTimer(SSL* /*ssl*/, unsigned int timerUs) +{ + if (timerUs == 0) + return 100000; + else if (timerUs >= 4000000) + return 4000000; + else + return 2 * timerUs; +} + +namespace RTC +{ + /* Static. */ + + // clang-format off + static constexpr int DtlsMtu{ 1350 }; + // AES-HMAC: http://tools.ietf.org/html/rfc3711 + static constexpr size_t SrtpMasterKeyLength{ 16 }; + static constexpr size_t SrtpMasterSaltLength{ 14 }; + static constexpr size_t SrtpMasterLength{ SrtpMasterKeyLength + SrtpMasterSaltLength }; + // AES-GCM: http://tools.ietf.org/html/rfc7714 + static constexpr size_t SrtpAesGcm256MasterKeyLength{ 32 }; + static constexpr size_t SrtpAesGcm256MasterSaltLength{ 12 }; + static constexpr size_t SrtpAesGcm256MasterLength{ SrtpAesGcm256MasterKeyLength + SrtpAesGcm256MasterSaltLength }; + static constexpr size_t SrtpAesGcm128MasterKeyLength{ 16 }; + static constexpr size_t SrtpAesGcm128MasterSaltLength{ 12 }; + static constexpr size_t SrtpAesGcm128MasterLength{ SrtpAesGcm128MasterKeyLength + SrtpAesGcm128MasterSaltLength }; + // clang-format on + + /* Class variables. */ + // clang-format off + std::map DtlsTransport::string2FingerprintAlgorithm = + { + { "sha-1", DtlsTransport::FingerprintAlgorithm::SHA1 }, + { "sha-224", DtlsTransport::FingerprintAlgorithm::SHA224 }, + { "sha-256", DtlsTransport::FingerprintAlgorithm::SHA256 }, + { "sha-384", DtlsTransport::FingerprintAlgorithm::SHA384 }, + { "sha-512", DtlsTransport::FingerprintAlgorithm::SHA512 } + }; + std::map DtlsTransport::fingerprintAlgorithm2String = + { + { DtlsTransport::FingerprintAlgorithm::SHA1, "sha-1" }, + { DtlsTransport::FingerprintAlgorithm::SHA224, "sha-224" }, + { DtlsTransport::FingerprintAlgorithm::SHA256, "sha-256" }, + { DtlsTransport::FingerprintAlgorithm::SHA384, "sha-384" }, + { DtlsTransport::FingerprintAlgorithm::SHA512, "sha-512" } + }; + std::map DtlsTransport::string2Role = + { + { "auto", DtlsTransport::Role::AUTO }, + { "client", DtlsTransport::Role::CLIENT }, + { "server", DtlsTransport::Role::SERVER } + }; + std::vector DtlsTransport::srtpCryptoSuites = + { + { RTC::SrtpSession::CryptoSuite::AEAD_AES_256_GCM, "SRTP_AEAD_AES_256_GCM" }, + { RTC::SrtpSession::CryptoSuite::AEAD_AES_128_GCM, "SRTP_AEAD_AES_128_GCM" }, + { RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80, "SRTP_AES128_CM_SHA1_80" }, + { RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32, "SRTP_AES128_CM_SHA1_32" } + }; + // clang-format on + + INSTANCE_IMP(DtlsTransport::DtlsEnvironment); + + /* Class methods. */ + + DtlsTransport::DtlsEnvironment::DtlsEnvironment() + { + MS_TRACE(); + + // Generate a X509 certificate and private key (unless PEM files are provided). + auto ssl = toolkit::SSL_Initor::Instance().getSSLCtx("", true); + if (!ssl || !ReadCertificateAndPrivateKeyFromContext(ssl.get())) { + GenerateCertificateAndPrivateKey(); + } + + // Create a global SSL_CTX. + CreateSslCtx(); + + // Generate certificate fingerprints. + GenerateFingerprints(); + } + + DtlsTransport::DtlsEnvironment::~DtlsEnvironment() + { + MS_TRACE(); + + if (privateKey) + EVP_PKEY_free(privateKey); + if (certificate) + X509_free(certificate); + if (sslCtx) + SSL_CTX_free(sslCtx); + } + + void DtlsTransport::DtlsEnvironment::GenerateCertificateAndPrivateKey() + { + MS_TRACE(); + + int ret{ 0 }; + EC_KEY* ecKey{ nullptr }; + X509_NAME* certName{ nullptr }; + std::string subject = + std::string("mediasoup") + to_string(rand() % 999999 + 100000); + + // Create key with curve. + ecKey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + + if (!ecKey) + { + LOG_OPENSSL_ERROR("EC_KEY_new_by_curve_name() failed"); + + goto error; + } + + EC_KEY_set_asn1_flag(ecKey, OPENSSL_EC_NAMED_CURVE); + + // NOTE: This can take some time. + ret = EC_KEY_generate_key(ecKey); + + if (ret == 0) + { + LOG_OPENSSL_ERROR("EC_KEY_generate_key() failed"); + + goto error; + } + + // Create a private key object. + privateKey = EVP_PKEY_new(); + + if (!privateKey) + { + LOG_OPENSSL_ERROR("EVP_PKEY_new() failed"); + + goto error; + } + + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) + ret = EVP_PKEY_assign_EC_KEY(privateKey, ecKey); + + if (ret == 0) + { + LOG_OPENSSL_ERROR("EVP_PKEY_assign_EC_KEY() failed"); + + goto error; + } + + // The EC key now belongs to the private key, so don't clean it up separately. + ecKey = nullptr; + + // Create the X509 certificate. + certificate = X509_new(); + + if (!certificate) + { + LOG_OPENSSL_ERROR("X509_new() failed"); + + goto error; + } + + // Set version 3 (note that 0 means version 1). + X509_set_version(certificate, 2); + + // Set serial number (avoid default 0). + ASN1_INTEGER_set( + X509_get_serialNumber(certificate), + static_cast(rand() % 999999 + 100000)); + + // Set valid period. + X509_gmtime_adj(X509_get_notBefore(certificate), -315360000); // -10 years. + X509_gmtime_adj(X509_get_notAfter(certificate), 315360000); // 10 years. + + // Set the public key for the certificate using the key. + ret = X509_set_pubkey(certificate, privateKey); + + if (ret == 0) + { + LOG_OPENSSL_ERROR("X509_set_pubkey() failed"); + + goto error; + } + + // Set certificate fields. + certName = X509_get_subject_name(certificate); + + if (!certName) + { + LOG_OPENSSL_ERROR("X509_get_subject_name() failed"); + + goto error; + } + + X509_NAME_add_entry_by_txt( + certName, "O", MBSTRING_ASC, reinterpret_cast(subject.c_str()), -1, -1, 0); + X509_NAME_add_entry_by_txt( + certName, "CN", MBSTRING_ASC, reinterpret_cast(subject.c_str()), -1, -1, 0); + + // It is self-signed so set the issuer name to be the same as the subject. + ret = X509_set_issuer_name(certificate, certName); + + if (ret == 0) + { + LOG_OPENSSL_ERROR("X509_set_issuer_name() failed"); + + goto error; + } + + // Sign the certificate with its own private key. + ret = X509_sign(certificate, privateKey, EVP_sha1()); + + if (ret == 0) + { + LOG_OPENSSL_ERROR("X509_sign() failed"); + + goto error; + } + + return; + + error: + + if (ecKey) + EC_KEY_free(ecKey); + + if (privateKey) + EVP_PKEY_free(privateKey); // NOTE: This also frees the EC key. + + if (certificate) + X509_free(certificate); + + MS_THROW_ERROR("DTLS certificate and private key generation failed"); + } + + bool DtlsTransport::DtlsEnvironment::ReadCertificateAndPrivateKeyFromContext(SSL_CTX *ctx) + { + MS_TRACE(); + certificate = SSL_CTX_get0_certificate(ctx); + if (!certificate) { + return false; + } + X509_up_ref(certificate); + + privateKey = SSL_CTX_get0_privatekey(ctx); + if (!privateKey) { + return false; + } + EVP_PKEY_up_ref(privateKey); + InfoL << "Load webrtc dtls certificate: " << toolkit::SSLUtil::getServerName(certificate); + return true; + } + + void DtlsTransport::DtlsEnvironment::CreateSslCtx() + { + MS_TRACE(); + + std::string dtlsSrtpCryptoSuites; + int ret; + + /* Set the global DTLS context. */ + + // Both DTLS 1.0 and 1.2 (requires OpenSSL >= 1.1.0). + sslCtx = SSL_CTX_new(DTLS_method()); + + if (!sslCtx) + { + LOG_OPENSSL_ERROR("SSL_CTX_new() failed"); + + goto error; + } + + ret = SSL_CTX_use_certificate(sslCtx, certificate); + + if (ret == 0) + { + LOG_OPENSSL_ERROR("SSL_CTX_use_certificate() failed"); + + goto error; + } + + ret = SSL_CTX_use_PrivateKey(sslCtx, privateKey); + + if (ret == 0) + { + LOG_OPENSSL_ERROR("SSL_CTX_use_PrivateKey() failed"); + + goto error; + } + + ret = SSL_CTX_check_private_key(sslCtx); + + if (ret == 0) + { + LOG_OPENSSL_ERROR("SSL_CTX_check_private_key() failed"); + + goto error; + } + + // Set options. + SSL_CTX_set_options( + sslCtx, + SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_TICKET | SSL_OP_SINGLE_ECDH_USE | + SSL_OP_NO_QUERY_MTU); + + // Don't use sessions cache. + SSL_CTX_set_session_cache_mode(sslCtx, SSL_SESS_CACHE_OFF); + + // Read always as much into the buffer as possible. + // NOTE: This is the default for DTLS, but a bug in non latest OpenSSL + // versions makes this call required. + SSL_CTX_set_read_ahead(sslCtx, 1); + + SSL_CTX_set_verify_depth(sslCtx, 4); + + // Require certificate from peer. + SSL_CTX_set_verify( + sslCtx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, onSslCertificateVerify); + + // Set SSL info callback. + SSL_CTX_set_info_callback(sslCtx, [](const SSL* ssl, int where, int ret){ + static_cast(SSL_get_ex_data(ssl, 0))->OnSslInfo(where, ret); + }); + // Set ciphers. + ret = SSL_CTX_set_cipher_list( + sslCtx, "DEFAULT:!NULL:!aNULL:!SHA256:!SHA384:!aECDH:!AESGCM+AES256:!aPSK:!RC4"); + + if (ret == 0) + { + LOG_OPENSSL_ERROR("SSL_CTX_set_cipher_list() failed"); + + goto error; + } + + // Enable ECDH ciphers. + // DOC: http://en.wikibooks.org/wiki/OpenSSL/Diffie-Hellman_parameters + // NOTE: https://code.google.com/p/chromium/issues/detail?id=406458 + // NOTE: https://bugs.ruby-lang.org/issues/12324 + + // For OpenSSL >= 1.0.2. + SSL_CTX_set_ecdh_auto(sslCtx, 1); + + // Set the "use_srtp" DTLS extension. + for (auto it = DtlsTransport::srtpCryptoSuites.begin(); + it != DtlsTransport::srtpCryptoSuites.end(); + ++it) + { + if (it != DtlsTransport::srtpCryptoSuites.begin()) + dtlsSrtpCryptoSuites += ":"; + + SrtpCryptoSuiteMapEntry* cryptoSuiteEntry = std::addressof(*it); + dtlsSrtpCryptoSuites += cryptoSuiteEntry->name; + } + + MS_DEBUG_2TAGS(dtls, srtp, "setting SRTP cryptoSuites for DTLS: %s", dtlsSrtpCryptoSuites.c_str()); + + // NOTE: This function returns 0 on success. + ret = SSL_CTX_set_tlsext_use_srtp(sslCtx, dtlsSrtpCryptoSuites.c_str()); + + if (ret != 0) + { + MS_ERROR( + "SSL_CTX_set_tlsext_use_srtp() failed when entering '%s'", dtlsSrtpCryptoSuites.c_str()); + LOG_OPENSSL_ERROR("SSL_CTX_set_tlsext_use_srtp() failed"); + + goto error; + } + + return; + + error: + + if (sslCtx) + { + SSL_CTX_free(sslCtx); + sslCtx = nullptr; + } + + MS_THROW_ERROR("SSL context creation failed"); + } + + void DtlsTransport::DtlsEnvironment::GenerateFingerprints() + { + MS_TRACE(); + + for (auto& kv : DtlsTransport::string2FingerprintAlgorithm) + { + const std::string& algorithmString = kv.first; + FingerprintAlgorithm algorithm = kv.second; + uint8_t binaryFingerprint[EVP_MAX_MD_SIZE]; + unsigned int size{ 0 }; + char hexFingerprint[(EVP_MAX_MD_SIZE * 3) + 1]; + const EVP_MD* hashFunction; + int ret; + + switch (algorithm) + { + case FingerprintAlgorithm::SHA1: + hashFunction = EVP_sha1(); + break; + + case FingerprintAlgorithm::SHA224: + hashFunction = EVP_sha224(); + break; + + case FingerprintAlgorithm::SHA256: + hashFunction = EVP_sha256(); + break; + + case FingerprintAlgorithm::SHA384: + hashFunction = EVP_sha384(); + break; + + case FingerprintAlgorithm::SHA512: + hashFunction = EVP_sha512(); + break; + + default: + MS_THROW_ERROR("unknown algorithm"); + } + + ret = X509_digest(certificate, hashFunction, binaryFingerprint, &size); + + if (ret == 0) + { + MS_ERROR("X509_digest() failed"); + MS_THROW_ERROR("Fingerprints generation failed"); + } + + // Convert to hexadecimal format in uppercase with colons. + for (unsigned int i{ 0 }; i < size; ++i) + { + std::sprintf(hexFingerprint + (i * 3), "%.2X:", binaryFingerprint[i]); + } + hexFingerprint[(size * 3) - 1] = '\0'; + + MS_DEBUG_TAG(dtls, "%-7s fingerprint: %s", algorithmString.c_str(), hexFingerprint); + + // Store it in the vector. + DtlsTransport::Fingerprint fingerprint; + + fingerprint.algorithm = DtlsTransport::GetFingerprintAlgorithm(algorithmString); + fingerprint.value = hexFingerprint; + + localFingerprints.push_back(fingerprint); + } + } + + /* Instance methods. */ + + DtlsTransport::DtlsTransport(EventPoller::Ptr poller,Listener* listener) : poller(std::move(poller)), listener(listener) + { + MS_TRACE(); + env = DtlsEnvironment::Instance().shared_from_this(); + + /* Set SSL. */ + + this->ssl = SSL_new(env->sslCtx); + + if (!this->ssl) + { + LOG_OPENSSL_ERROR("SSL_new() failed"); + + goto error; + } + + // Set this as custom data. + SSL_set_ex_data(this->ssl, 0, static_cast(this)); + + this->sslBioFromNetwork = BIO_new(BIO_s_mem()); + + if (!this->sslBioFromNetwork) + { + LOG_OPENSSL_ERROR("BIO_new() failed"); + + SSL_free(this->ssl); + + goto error; + } + + this->sslBioToNetwork = BIO_new(BIO_s_mem()); + + if (!this->sslBioToNetwork) + { + LOG_OPENSSL_ERROR("BIO_new() failed"); + + BIO_free(this->sslBioFromNetwork); + SSL_free(this->ssl); + + goto error; + } + + SSL_set_bio(this->ssl, this->sslBioFromNetwork, this->sslBioToNetwork); + + // Set the MTU so that we don't send packets that are too large with no fragmentation. + SSL_set_mtu(this->ssl, DtlsMtu); + DTLS_set_link_mtu(this->ssl, DtlsMtu); + + // Set callback handler for setting DTLS timer interval. + DTLS_set_timer_cb(this->ssl, onSslDtlsTimer); + + return; + + error: + + // NOTE: At this point SSL_set_bio() was not called so we must free BIOs as + // well. + if (this->sslBioFromNetwork) + BIO_free(this->sslBioFromNetwork); + + if (this->sslBioToNetwork) + BIO_free(this->sslBioToNetwork); + + if (this->ssl) + SSL_free(this->ssl); + + // NOTE: If this is not catched by the caller the program will abort, but + // this should never happen. + MS_THROW_ERROR("DtlsTransport instance creation failed"); + } + + DtlsTransport::~DtlsTransport() + { + MS_TRACE(); + + if (IsRunning()) + { + // Send close alert to the peer. + SSL_shutdown(this->ssl); + SendPendingOutgoingDtlsData(); + } + + if (this->ssl) + { + SSL_free(this->ssl); + + this->ssl = nullptr; + this->sslBioFromNetwork = nullptr; + this->sslBioToNetwork = nullptr; + } + + // Close the DTLS timer. + this->timer = nullptr; + } + + void DtlsTransport::Dump() const + { + MS_TRACE(); + + std::string state{ "new" }; + std::string role{ "none " }; + + switch (this->state) + { + case DtlsState::CONNECTING: + state = "connecting"; + break; + case DtlsState::CONNECTED: + state = "connected"; + break; + case DtlsState::FAILED: + state = "failed"; + break; + case DtlsState::CLOSED: + state = "closed"; + break; + default:; + } + + switch (this->localRole) + { + case Role::AUTO: + role = "auto"; + break; + case Role::SERVER: + role = "server"; + break; + case Role::CLIENT: + role = "client"; + break; + default:; + } + + MS_DUMP(""); + MS_DUMP(" state : %s", state.c_str()); + MS_DUMP(" role : %s", role.c_str()); + MS_DUMP(" handshake done: : %s", this->handshakeDone ? "yes" : "no"); + MS_DUMP(""); + } + + void DtlsTransport::Run(Role localRole) + { + DebugL << ((localRole == RTC::DtlsTransport::Role::SERVER)? "Server" : "Client"); + + MS_TRACE(); + + MS_ASSERT( + localRole == Role::CLIENT || localRole == Role::SERVER, + "local DTLS role must be 'client' or 'server'"); + + Role previousLocalRole = this->localRole; + + if (localRole == previousLocalRole) + { + MS_ERROR("same local DTLS role provided, doing nothing"); + + return; + } + + // If the previous local DTLS role was 'client' or 'server' do reset. + if (previousLocalRole == Role::CLIENT || previousLocalRole == Role::SERVER) + { + MS_DEBUG_TAG(dtls, "resetting DTLS due to local role change"); + + Reset(); + } + + // Update local role. + this->localRole = localRole; + + // Set state and notify the listener. + this->state = DtlsState::CONNECTING; + this->listener->OnDtlsTransportConnecting(this); + + switch (this->localRole) + { + case Role::CLIENT: + { + MS_DEBUG_TAG(dtls, "running [role:client]"); + + SSL_set_connect_state(this->ssl); + SSL_do_handshake(this->ssl); + SendPendingOutgoingDtlsData(); + SetTimeout(); + + break; + } + + case Role::SERVER: + { + MS_DEBUG_TAG(dtls, "running [role:server]"); + + SSL_set_accept_state(this->ssl); + SSL_do_handshake(this->ssl); + + break; + } + + default: + { + MS_ABORT("invalid local DTLS role"); + } + } + } + + bool DtlsTransport::SetRemoteFingerprint(Fingerprint fingerprint) + { + MS_TRACE(); + + MS_ASSERT( + fingerprint.algorithm != FingerprintAlgorithm::NONE, "no fingerprint algorithm provided"); + + this->remoteFingerprint = fingerprint; + + // The remote fingerpring may have been set after DTLS handshake was done, + // so we may need to process it now. + if (this->handshakeDone && this->state != DtlsState::CONNECTED) + { + MS_DEBUG_TAG(dtls, "handshake already done, processing it right now"); + + return ProcessHandshake(); + } + + return true; + } + + void DtlsTransport::ProcessDtlsData(const uint8_t* data, size_t len) + { + MS_TRACE(); + + int written; + int read; + + if (!IsRunning()) + { + MS_WARN_TAG(nullptr,"cannot process data while not running"); + return; + } + + // Write the received DTLS data into the sslBioFromNetwork. + written = + BIO_write(this->sslBioFromNetwork, static_cast(data), static_cast(len)); + + if (written != static_cast(len)) + { + MS_WARN_TAG( + dtls, + "OpenSSL BIO_write() wrote less (%zu bytes) than given data (%zu bytes)", + static_cast(written), + len); + } + + // Must call SSL_read() to process received DTLS data. + read = SSL_read(this->ssl, static_cast(DtlsTransport::sslReadBuffer), SslReadBufferSize); + + // Send data if it's ready. + SendPendingOutgoingDtlsData(); + + // Check SSL status and return if it is bad/closed. + if (!CheckStatus(read)) + return; + + // Set/update the DTLS timeout. + if (!SetTimeout()) + return; + + // Application data received. Notify to the listener. + if (read > 0) + { + // It is allowed to receive DTLS data even before validating remote fingerprint. + if (!this->handshakeDone) + { + MS_WARN_TAG(dtls, "ignoring application data received while DTLS handshake not done"); + + return; + } + + // Notify the listener. + this->listener->OnDtlsTransportApplicationDataReceived( + this, (uint8_t*)DtlsTransport::sslReadBuffer, static_cast(read)); + } + } + + void DtlsTransport::SendApplicationData(const uint8_t* data, size_t len) + { + MS_TRACE(); + + // We cannot send data to the peer if its remote fingerprint is not validated. + if (this->state != DtlsState::CONNECTED) + { + MS_WARN_TAG(dtls, "cannot send application data while DTLS is not fully connected"); + + return; + } + + if (len == 0) + { + MS_WARN_TAG(dtls, "ignoring 0 length data"); + + return; + } + + int written; + + written = SSL_write(this->ssl, static_cast(data), static_cast(len)); + + if (written < 0) + { + LOG_OPENSSL_ERROR("SSL_write() failed"); + + if (!CheckStatus(written)) + return; + } + else if (written != static_cast(len)) + { + MS_WARN_TAG( + dtls, "OpenSSL SSL_write() wrote less (%d bytes) than given data (%zu bytes)", written, len); + } + + // Send data. + SendPendingOutgoingDtlsData(); + } + + void DtlsTransport::Reset() + { + MS_TRACE(); + + int ret; + + if (!IsRunning()) + return; + + MS_WARN_TAG(dtls, "resetting DTLS transport"); + + // Stop the DTLS timer. + this->timer = nullptr; + + // We need to reset the SSL instance so we need to "shutdown" it, but we + // don't want to send a Close Alert to the peer, so just don't call + // SendPendingOutgoingDTLSData(). + SSL_shutdown(this->ssl); + + this->localRole = Role::NONE; + this->state = DtlsState::NEW; + this->handshakeDone = false; + this->handshakeDoneNow = false; + + // Reset SSL status. + // NOTE: For this to properly work, SSL_shutdown() must be called before. + // NOTE: This may fail if not enough DTLS handshake data has been received, + // but we don't care so just clear the error queue. + ret = SSL_clear(this->ssl); + + if (ret == 0) + ERR_clear_error(); + } + + inline bool DtlsTransport::CheckStatus(int returnCode) + { + MS_TRACE(); + + int err; + bool wasHandshakeDone = this->handshakeDone; + + err = SSL_get_error(this->ssl, returnCode); + + switch (err) + { + case SSL_ERROR_NONE: + break; + + case SSL_ERROR_SSL: + LOG_OPENSSL_ERROR("SSL status: SSL_ERROR_SSL"); + break; + + case SSL_ERROR_WANT_READ: + break; + + case SSL_ERROR_WANT_WRITE: + MS_WARN_TAG(dtls, "SSL status: SSL_ERROR_WANT_WRITE"); + break; + + case SSL_ERROR_WANT_X509_LOOKUP: + MS_DEBUG_TAG(dtls, "SSL status: SSL_ERROR_WANT_X509_LOOKUP"); + break; + + case SSL_ERROR_SYSCALL: + LOG_OPENSSL_ERROR("SSL status: SSL_ERROR_SYSCALL"); + break; + + case SSL_ERROR_ZERO_RETURN: + break; + + case SSL_ERROR_WANT_CONNECT: + MS_WARN_TAG(dtls, "SSL status: SSL_ERROR_WANT_CONNECT"); + break; + + case SSL_ERROR_WANT_ACCEPT: + MS_WARN_TAG(dtls, "SSL status: SSL_ERROR_WANT_ACCEPT"); + break; + + default: + MS_WARN_TAG(dtls, "SSL status: unknown error"); + } + + // Check if the handshake (or re-handshake) has been done right now. + if (this->handshakeDoneNow) + { + this->handshakeDoneNow = false; + this->handshakeDone = true; + + // Stop the timer. + this->timer = nullptr; + + // Process the handshake just once (ignore if DTLS renegotiation). + if (!wasHandshakeDone && this->remoteFingerprint.algorithm != FingerprintAlgorithm::NONE) + return ProcessHandshake(); + + return true; + } + // Check if the peer sent close alert or a fatal error happened. + else if (((SSL_get_shutdown(this->ssl) & SSL_RECEIVED_SHUTDOWN) != 0) || err == SSL_ERROR_SSL || err == SSL_ERROR_SYSCALL) + { + if (this->state == DtlsState::CONNECTED) + { + MS_DEBUG_TAG(dtls, "disconnected"); + + Reset(); + + // Set state and notify the listener. + this->state = DtlsState::CLOSED; + this->listener->OnDtlsTransportClosed(this); + } + else + { + MS_WARN_TAG(dtls, "connection failed"); + + Reset(); + + // Set state and notify the listener. + this->state = DtlsState::FAILED; + this->listener->OnDtlsTransportFailed(this); + } + + return false; + } + else + { + return true; + } + } + + inline void DtlsTransport::SendPendingOutgoingDtlsData() + { + MS_TRACE(); + + if (BIO_eof(this->sslBioToNetwork)) + return; + + int64_t read; + char* data{ nullptr }; + + read = BIO_get_mem_data(this->sslBioToNetwork, &data); // NOLINT + + if (read <= 0) + return; + + MS_DEBUG_DEV("%" PRIu64 " bytes of DTLS data ready to sent to the peer", read); + + // Notify the listener. + this->listener->OnDtlsTransportSendData( + this, reinterpret_cast(data), static_cast(read)); + + // Clear the BIO buffer. + // NOTE: the (void) avoids the -Wunused-value warning. + (void)BIO_reset(this->sslBioToNetwork); + } + + inline bool DtlsTransport::SetTimeout() + { + MS_TRACE(); + + MS_ASSERT( + this->state == DtlsState::CONNECTING || this->state == DtlsState::CONNECTED, + "invalid DTLS state"); + + int64_t ret; + struct timeval dtlsTimeout{ 0, 0 }; + uint64_t timeoutMs; + + // NOTE: If ret == 0 then ignore the value in dtlsTimeout. + // NOTE: No DTLSv_1_2_get_timeout() or DTLS_get_timeout() in OpenSSL 1.1.0-dev. + ret = DTLSv1_get_timeout(this->ssl, static_cast(&dtlsTimeout)); // NOLINT + + if (ret == 0) + return true; + + timeoutMs = (dtlsTimeout.tv_sec * static_cast(1000)) + (dtlsTimeout.tv_usec / 1000); + + if (timeoutMs == 0) + { + return true; + } + else if (timeoutMs < 30000) + { + MS_DEBUG_DEV("DTLS timer set in %" PRIu64 "ms", timeoutMs); + + weak_ptr weak_self = shared_from_this(); + this->timer = std::make_shared(timeoutMs / 1000.0f, [weak_self](){ + auto strong_self = weak_self.lock(); + if(strong_self){ + strong_self->OnTimer(); + } + return true; + }, this->poller); + + return true; + } + // NOTE: Don't start the timer again if the timeout is greater than 30 seconds. + else + { + MS_WARN_TAG(dtls, "DTLS timeout too high (%" PRIu64 "ms), resetting DLTS", timeoutMs); + + Reset(); + + // Set state and notify the listener. + this->state = DtlsState::FAILED; + this->listener->OnDtlsTransportFailed(this); + + return false; + } + } + + inline bool DtlsTransport::ProcessHandshake() + { + MS_TRACE(); + + MS_ASSERT(this->handshakeDone, "handshake not done yet"); + MS_ASSERT( + this->remoteFingerprint.algorithm != FingerprintAlgorithm::NONE, "remote fingerprint not set"); + + // Validate the remote fingerprint. + if (!CheckRemoteFingerprint()) + { + Reset(); + + // Set state and notify the listener. + this->state = DtlsState::FAILED; + this->listener->OnDtlsTransportFailed(this); + + return false; + } + + // Get the negotiated SRTP crypto suite. + RTC::SrtpSession::CryptoSuite srtpCryptoSuite = GetNegotiatedSrtpCryptoSuite(); + + if (srtpCryptoSuite != RTC::SrtpSession::CryptoSuite::NONE) + { + // Extract the SRTP keys (will notify the listener with them). + ExtractSrtpKeys(srtpCryptoSuite); + + return true; + } + + // NOTE: We assume that "use_srtp" DTLS extension is required even if + // there is no audio/video. + MS_WARN_2TAGS(dtls, srtp, "SRTP crypto suite not negotiated"); + + Reset(); + + // Set state and notify the listener. + this->state = DtlsState::FAILED; + this->listener->OnDtlsTransportFailed(this); + + return false; + } + + inline bool DtlsTransport::CheckRemoteFingerprint() + { + MS_TRACE(); + + MS_ASSERT( + this->remoteFingerprint.algorithm != FingerprintAlgorithm::NONE, "remote fingerprint not set"); + + X509* certificate; + uint8_t binaryFingerprint[EVP_MAX_MD_SIZE]; + unsigned int size{ 0 }; + char hexFingerprint[(EVP_MAX_MD_SIZE * 3) + 1]; + const EVP_MD* hashFunction; + int ret; + + certificate = SSL_get_peer_certificate(this->ssl); + + if (!certificate) + { + MS_WARN_TAG(dtls, "no certificate was provided by the peer"); + + return false; + } + + switch (this->remoteFingerprint.algorithm) + { + case FingerprintAlgorithm::SHA1: + hashFunction = EVP_sha1(); + break; + + case FingerprintAlgorithm::SHA224: + hashFunction = EVP_sha224(); + break; + + case FingerprintAlgorithm::SHA256: + hashFunction = EVP_sha256(); + break; + + case FingerprintAlgorithm::SHA384: + hashFunction = EVP_sha384(); + break; + + case FingerprintAlgorithm::SHA512: + hashFunction = EVP_sha512(); + break; + + default: + MS_ABORT("unknown algorithm"); + } + + // Compare the remote fingerprint with the value given via signaling. + ret = X509_digest(certificate, hashFunction, binaryFingerprint, &size); + + if (ret == 0) + { + MS_ERROR("X509_digest() failed"); + + X509_free(certificate); + + return false; + } + + // Convert to hexadecimal format in uppercase with colons. + for (unsigned int i{ 0 }; i < size; ++i) + { + std::sprintf(hexFingerprint + (i * 3), "%.2X:", binaryFingerprint[i]); + } + hexFingerprint[(size * 3) - 1] = '\0'; + + if (this->remoteFingerprint.value != hexFingerprint) + { + MS_WARN_TAG( + dtls, + "fingerprint in the remote certificate (%s) does not match the announced one (%s)", + hexFingerprint, + this->remoteFingerprint.value.c_str()); + X509_free(certificate); + return false; + } + + MS_DEBUG_TAG(dtls, "valid remote fingerprint"); + + // Get the remote certificate in PEM format. + + BIO* bio = BIO_new(BIO_s_mem()); + + // Ensure the underlying BUF_MEM structure is also freed. + // NOTE: Avoid stupid "warning: value computed is not used [-Wunused-value]" since + // BIO_set_close() always returns 1. + (void)BIO_set_close(bio, BIO_CLOSE); + + ret = PEM_write_bio_X509(bio, certificate); + + if (ret != 1) + { + LOG_OPENSSL_ERROR("PEM_write_bio_X509() failed"); + + X509_free(certificate); + BIO_free(bio); + + return false; + } + + BUF_MEM* mem; + + BIO_get_mem_ptr(bio, &mem); // NOLINT[cppcoreguidelines-pro-type-cstyle-cast] + + if (!mem || !mem->data || mem->length == 0u) + { + LOG_OPENSSL_ERROR("BIO_get_mem_ptr() failed"); + + X509_free(certificate); + BIO_free(bio); + + return false; + } + + this->remoteCert = std::string(mem->data, mem->length); + + X509_free(certificate); + BIO_free(bio); + + return true; + } + + inline void DtlsTransport::ExtractSrtpKeys(RTC::SrtpSession::CryptoSuite srtpCryptoSuite) + { + MS_TRACE(); + + size_t srtpKeyLength{ 0 }; + size_t srtpSaltLength{ 0 }; + size_t srtpMasterLength{ 0 }; + + switch (srtpCryptoSuite) + { + case RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80: + case RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32: + { + srtpKeyLength = SrtpMasterKeyLength; + srtpSaltLength = SrtpMasterSaltLength; + srtpMasterLength = SrtpMasterLength; + + break; + } + + case RTC::SrtpSession::CryptoSuite::AEAD_AES_256_GCM: + { + srtpKeyLength = SrtpAesGcm256MasterKeyLength; + srtpSaltLength = SrtpAesGcm256MasterSaltLength; + srtpMasterLength = SrtpAesGcm256MasterLength; + + break; + } + + case RTC::SrtpSession::CryptoSuite::AEAD_AES_128_GCM: + { + srtpKeyLength = SrtpAesGcm128MasterKeyLength; + srtpSaltLength = SrtpAesGcm128MasterSaltLength; + srtpMasterLength = SrtpAesGcm128MasterLength; + + break; + } + + default: + { + MS_ABORT("unknown SRTP crypto suite"); + } + } + + auto* srtpMaterial = new uint8_t[srtpMasterLength * 2]; + uint8_t* srtpLocalKey{ nullptr }; + uint8_t* srtpLocalSalt{ nullptr }; + uint8_t* srtpRemoteKey{ nullptr }; + uint8_t* srtpRemoteSalt{ nullptr }; + auto* srtpLocalMasterKey = new uint8_t[srtpMasterLength]; + auto* srtpRemoteMasterKey = new uint8_t[srtpMasterLength]; + int ret; + + ret = SSL_export_keying_material( + this->ssl, srtpMaterial, srtpMasterLength * 2, "EXTRACTOR-dtls_srtp", 19, nullptr, 0, 0); + + MS_ASSERT(ret != 0, "SSL_export_keying_material() failed"); + + switch (this->localRole) + { + case Role::SERVER: + { + srtpRemoteKey = srtpMaterial; + srtpLocalKey = srtpRemoteKey + srtpKeyLength; + srtpRemoteSalt = srtpLocalKey + srtpKeyLength; + srtpLocalSalt = srtpRemoteSalt + srtpSaltLength; + + break; + } + + case Role::CLIENT: + { + srtpLocalKey = srtpMaterial; + srtpRemoteKey = srtpLocalKey + srtpKeyLength; + srtpLocalSalt = srtpRemoteKey + srtpKeyLength; + srtpRemoteSalt = srtpLocalSalt + srtpSaltLength; + + break; + } + + default: + { + MS_ABORT("no DTLS role set"); + } + } + + // Create the SRTP local master key. + std::memcpy(srtpLocalMasterKey, srtpLocalKey, srtpKeyLength); + std::memcpy(srtpLocalMasterKey + srtpKeyLength, srtpLocalSalt, srtpSaltLength); + // Create the SRTP remote master key. + std::memcpy(srtpRemoteMasterKey, srtpRemoteKey, srtpKeyLength); + std::memcpy(srtpRemoteMasterKey + srtpKeyLength, srtpRemoteSalt, srtpSaltLength); + + // Set state and notify the listener. + this->state = DtlsState::CONNECTED; + this->listener->OnDtlsTransportConnected( + this, + srtpCryptoSuite, + srtpLocalMasterKey, + srtpMasterLength, + srtpRemoteMasterKey, + srtpMasterLength, + this->remoteCert); + + delete[] srtpMaterial; + delete[] srtpLocalMasterKey; + delete[] srtpRemoteMasterKey; + } + + inline RTC::SrtpSession::CryptoSuite DtlsTransport::GetNegotiatedSrtpCryptoSuite() + { + MS_TRACE(); + + RTC::SrtpSession::CryptoSuite negotiatedSrtpCryptoSuite = RTC::SrtpSession::CryptoSuite::NONE; + + // Ensure that the SRTP crypto suite has been negotiated. + // NOTE: This is a OpenSSL type. + SRTP_PROTECTION_PROFILE* sslSrtpCryptoSuite = SSL_get_selected_srtp_profile(this->ssl); + + if (!sslSrtpCryptoSuite) + return negotiatedSrtpCryptoSuite; + + // Get the negotiated SRTP crypto suite. + for (auto& srtpCryptoSuite : DtlsTransport::srtpCryptoSuites) + { + SrtpCryptoSuiteMapEntry* cryptoSuiteEntry = std::addressof(srtpCryptoSuite); + + if (std::strcmp(sslSrtpCryptoSuite->name, cryptoSuiteEntry->name) == 0) + { + MS_DEBUG_2TAGS(dtls, srtp, "chosen SRTP crypto suite: %s", cryptoSuiteEntry->name); + + negotiatedSrtpCryptoSuite = cryptoSuiteEntry->cryptoSuite; + } + } + + MS_ASSERT( + negotiatedSrtpCryptoSuite != RTC::SrtpSession::CryptoSuite::NONE, + "chosen SRTP crypto suite is not an available one"); + + return negotiatedSrtpCryptoSuite; + } + + inline void DtlsTransport::OnSslInfo(int where, int ret) + { + MS_TRACE(); + + int w = where & -SSL_ST_MASK; + const char* role; + + if ((w & SSL_ST_CONNECT) != 0) + role = "client"; + else if ((w & SSL_ST_ACCEPT) != 0) + role = "server"; + else + role = "undefined"; + + if ((where & SSL_CB_LOOP) != 0) + { + MS_DEBUG_TAG(dtls, "[role:%s, action:'%s']", role, SSL_state_string_long(this->ssl)); + } + else if ((where & SSL_CB_ALERT) != 0) + { + const char* alertType; + + switch (*SSL_alert_type_string(ret)) + { + case 'W': + alertType = "warning"; + break; + + case 'F': + alertType = "fatal"; + break; + + default: + alertType = "undefined"; + } + + if ((where & SSL_CB_READ) != 0) + { + MS_WARN_TAG(dtls, "received DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret)); + } + else if ((where & SSL_CB_WRITE) != 0) + { + MS_DEBUG_TAG(dtls, "sending DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret)); + } + else + { + MS_DEBUG_TAG(dtls, "DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret)); + } + } + else if ((where & SSL_CB_EXIT) != 0) + { + if (ret == 0) + MS_DEBUG_TAG(dtls, "[role:%s, failed:'%s']", role, SSL_state_string_long(this->ssl)); + else if (ret < 0) + MS_DEBUG_TAG(dtls, "role: %s, waiting:'%s']", role, SSL_state_string_long(this->ssl)); + } + else if ((where & SSL_CB_HANDSHAKE_START) != 0) + { + MS_DEBUG_TAG(dtls, "DTLS handshake start"); + } + else if ((where & SSL_CB_HANDSHAKE_DONE) != 0) + { + MS_DEBUG_TAG(dtls, "DTLS handshake done"); + + this->handshakeDoneNow = true; + } + + // NOTE: checking SSL_get_shutdown(this->ssl) & SSL_RECEIVED_SHUTDOWN here upon + // receipt of a close alert does not work (the flag is set after this callback). + } + + inline void DtlsTransport::OnTimer() + { + MS_TRACE(); + + // Workaround for https://github.com/openssl/openssl/issues/7998. + if (this->handshakeDone) + { + // MS_DEBUG_DEV("handshake is done so return"); + return; + } + + DTLSv1_handle_timeout(this->ssl); + + // If required, send DTLS data. + SendPendingOutgoingDtlsData(); + + // Set the DTLS timer again. + SetTimeout(); + } +} // namespace RTC diff --git a/webrtc/DtlsTransport.hpp b/webrtc/DtlsTransport.hpp index 53a1981d..48198358 100644 --- a/webrtc/DtlsTransport.hpp +++ b/webrtc/DtlsTransport.hpp @@ -1,254 +1,254 @@ -/** -ISC License - -Copyright © 2015, Iñaki Baz Castillo - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef MS_RTC_DTLS_TRANSPORT_HPP -#define MS_RTC_DTLS_TRANSPORT_HPP - -#include "SrtpSession.hpp" -#include -#include -#include -#include -#include -#include -#include "Poller/Timer.h" -#include "Poller/EventPoller.h" -using namespace toolkit; - -namespace RTC -{ - class DtlsTransport : public std::enable_shared_from_this - { - public: - enum class DtlsState - { - NEW = 1, - CONNECTING, - CONNECTED, - FAILED, - CLOSED - }; - - public: - enum class Role - { - NONE = 0, - AUTO = 1, - CLIENT, - SERVER - }; - - public: - enum class FingerprintAlgorithm - { - NONE = 0, - SHA1 = 1, - SHA224, - SHA256, - SHA384, - SHA512 - }; - - public: - struct Fingerprint - { - FingerprintAlgorithm algorithm{ FingerprintAlgorithm::NONE }; - std::string value; - }; - - private: - struct SrtpCryptoSuiteMapEntry - { - RTC::SrtpSession::CryptoSuite cryptoSuite; - const char* name; - }; - - class DtlsEnvironment : public std::enable_shared_from_this - { - public: - using Ptr = std::shared_ptr; - ~DtlsEnvironment(); - static DtlsEnvironment& Instance(); - - private: - DtlsEnvironment(); - void GenerateCertificateAndPrivateKey(); - bool ReadCertificateAndPrivateKeyFromContext(SSL_CTX *ctx); - void CreateSslCtx(); - void GenerateFingerprints(); - - public: - X509* certificate{ nullptr }; - EVP_PKEY* privateKey{ nullptr }; - SSL_CTX* sslCtx{ nullptr }; - std::vector localFingerprints; - }; - - public: - class Listener - { - public: - // DTLS is in the process of negotiating a secure connection. Incoming - // media can flow through. - // NOTE: The caller MUST NOT call any method during this callback. - virtual void OnDtlsTransportConnecting(const RTC::DtlsTransport* dtlsTransport) = 0; - // DTLS has completed negotiation of a secure connection (including DTLS-SRTP - // and remote fingerprint verification). Outgoing media can now flow through. - // NOTE: The caller MUST NOT call any method during this callback. - virtual void OnDtlsTransportConnected( - const RTC::DtlsTransport* dtlsTransport, - RTC::SrtpSession::CryptoSuite srtpCryptoSuite, - uint8_t* srtpLocalKey, - size_t srtpLocalKeyLen, - uint8_t* srtpRemoteKey, - size_t srtpRemoteKeyLen, - std::string& remoteCert) = 0; - // The DTLS connection has been closed as the result of an error (such as a - // DTLS alert or a failure to validate the remote fingerprint). - virtual void OnDtlsTransportFailed(const RTC::DtlsTransport* dtlsTransport) = 0; - // The DTLS connection has been closed due to receipt of a close_notify alert. - virtual void OnDtlsTransportClosed(const RTC::DtlsTransport* dtlsTransport) = 0; - // Need to send DTLS data to the peer. - virtual void OnDtlsTransportSendData( - const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) = 0; - // DTLS application data received. - virtual void OnDtlsTransportApplicationDataReceived( - const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) = 0; - }; - - public: - static Role StringToRole(const std::string& role) - { - auto it = DtlsTransport::string2Role.find(role); - - if (it != DtlsTransport::string2Role.end()) - return it->second; - else - return DtlsTransport::Role::NONE; - } - static FingerprintAlgorithm GetFingerprintAlgorithm(const std::string& fingerprint) - { - auto it = DtlsTransport::string2FingerprintAlgorithm.find(fingerprint); - - if (it != DtlsTransport::string2FingerprintAlgorithm.end()) - return it->second; - else - return DtlsTransport::FingerprintAlgorithm::NONE; - } - static std::string& GetFingerprintAlgorithmString(FingerprintAlgorithm fingerprint) - { - auto it = DtlsTransport::fingerprintAlgorithm2String.find(fingerprint); - - return it->second; - } - static bool IsDtls(const uint8_t* data, size_t len) - { - // clang-format off - return ( - // Minimum DTLS record length is 13 bytes. - (len >= 13) && - // DOC: https://tools.ietf.org/html/draft-ietf-avtcore-rfc5764-mux-fixes - (data[0] > 19 && data[0] < 64) - ); - // clang-format on - } - - private: - static std::map string2Role; - static std::map string2FingerprintAlgorithm; - static std::map fingerprintAlgorithm2String; - static std::vector srtpCryptoSuites; - - public: - DtlsTransport(EventPoller::Ptr poller, Listener* listener); - ~DtlsTransport(); - - public: - void Dump() const; - void Run(Role localRole); - std::vector& GetLocalFingerprints() const - { - return env->localFingerprints; - } - bool SetRemoteFingerprint(Fingerprint fingerprint); - void ProcessDtlsData(const uint8_t* data, size_t len); - DtlsState GetState() const - { - return this->state; - } - Role GetLocalRole() const - { - return this->localRole; - } - void SendApplicationData(const uint8_t* data, size_t len); - - private: - bool IsRunning() const - { - switch (this->state) - { - case DtlsState::NEW: - return false; - case DtlsState::CONNECTING: - case DtlsState::CONNECTED: - return true; - case DtlsState::FAILED: - case DtlsState::CLOSED: - return false; - } - - // Make GCC 4.9 happy. - return false; - } - void Reset(); - bool CheckStatus(int returnCode); - void SendPendingOutgoingDtlsData(); - bool SetTimeout(); - bool ProcessHandshake(); - bool CheckRemoteFingerprint(); - void ExtractSrtpKeys(RTC::SrtpSession::CryptoSuite srtpCryptoSuite); - RTC::SrtpSession::CryptoSuite GetNegotiatedSrtpCryptoSuite(); - - private: - void OnSslInfo(int where, int ret); - void OnTimer(); - - private: - DtlsEnvironment::Ptr env; - EventPoller::Ptr poller; - // Passed by argument. - Listener* listener{ nullptr }; - // Allocated by this. - SSL* ssl{ nullptr }; - BIO* sslBioFromNetwork{ nullptr }; // The BIO from which ssl reads. - BIO* sslBioToNetwork{ nullptr }; // The BIO in which ssl writes. - Timer::Ptr timer; - // Others. - DtlsState state{ DtlsState::NEW }; - Role localRole{ Role::NONE }; - Fingerprint remoteFingerprint; - bool handshakeDone{ false }; - bool handshakeDoneNow{ false }; - std::string remoteCert; - //最大不超过mtu - static constexpr int SslReadBufferSize{ 2000 }; - uint8_t sslReadBuffer[SslReadBufferSize]; -}; -} // namespace RTC - -#endif +/** +ISC License + +Copyright © 2015, Iñaki Baz Castillo + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef MS_RTC_DTLS_TRANSPORT_HPP +#define MS_RTC_DTLS_TRANSPORT_HPP + +#include "SrtpSession.hpp" +#include +#include +#include +#include +#include +#include +#include "Poller/Timer.h" +#include "Poller/EventPoller.h" + +namespace RTC +{ + class DtlsTransport : public std::enable_shared_from_this + { + public: + using Ptr = std::shared_ptr; + enum class DtlsState + { + NEW = 1, + CONNECTING, + CONNECTED, + FAILED, + CLOSED + }; + + public: + enum class Role + { + NONE = 0, + AUTO = 1, + CLIENT, + SERVER + }; + + public: + enum class FingerprintAlgorithm + { + NONE = 0, + SHA1 = 1, + SHA224, + SHA256, + SHA384, + SHA512 + }; + + public: + struct Fingerprint + { + FingerprintAlgorithm algorithm{ FingerprintAlgorithm::NONE }; + std::string value; + }; + + private: + struct SrtpCryptoSuiteMapEntry + { + RTC::SrtpSession::CryptoSuite cryptoSuite; + const char* name; + }; + + class DtlsEnvironment : public std::enable_shared_from_this + { + public: + using Ptr = std::shared_ptr; + ~DtlsEnvironment(); + static DtlsEnvironment& Instance(); + + private: + DtlsEnvironment(); + void GenerateCertificateAndPrivateKey(); + bool ReadCertificateAndPrivateKeyFromContext(SSL_CTX *ctx); + void CreateSslCtx(); + void GenerateFingerprints(); + + public: + X509* certificate{ nullptr }; + EVP_PKEY* privateKey{ nullptr }; + SSL_CTX* sslCtx{ nullptr }; + std::vector localFingerprints; + }; + + public: + class Listener + { + public: + // DTLS is in the process of negotiating a secure connection. Incoming + // media can flow through. + // NOTE: The caller MUST NOT call any method during this callback. + virtual void OnDtlsTransportConnecting(const RTC::DtlsTransport* dtlsTransport) = 0; + // DTLS has completed negotiation of a secure connection (including DTLS-SRTP + // and remote fingerprint verification). Outgoing media can now flow through. + // NOTE: The caller MUST NOT call any method during this callback. + virtual void OnDtlsTransportConnected( + const RTC::DtlsTransport* dtlsTransport, + RTC::SrtpSession::CryptoSuite srtpCryptoSuite, + uint8_t* srtpLocalKey, + size_t srtpLocalKeyLen, + uint8_t* srtpRemoteKey, + size_t srtpRemoteKeyLen, + std::string& remoteCert) = 0; + // The DTLS connection has been closed as the result of an error (such as a + // DTLS alert or a failure to validate the remote fingerprint). + virtual void OnDtlsTransportFailed(const RTC::DtlsTransport* dtlsTransport) = 0; + // The DTLS connection has been closed due to receipt of a close_notify alert. + virtual void OnDtlsTransportClosed(const RTC::DtlsTransport* dtlsTransport) = 0; + // Need to send DTLS data to the peer. + virtual void OnDtlsTransportSendData( + const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) = 0; + // DTLS application data received. + virtual void OnDtlsTransportApplicationDataReceived( + const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) = 0; + }; + + public: + static Role StringToRole(const std::string& role) + { + auto it = DtlsTransport::string2Role.find(role); + + if (it != DtlsTransport::string2Role.end()) + return it->second; + else + return DtlsTransport::Role::NONE; + } + static FingerprintAlgorithm GetFingerprintAlgorithm(const std::string& fingerprint) + { + auto it = DtlsTransport::string2FingerprintAlgorithm.find(fingerprint); + + if (it != DtlsTransport::string2FingerprintAlgorithm.end()) + return it->second; + else + return DtlsTransport::FingerprintAlgorithm::NONE; + } + static std::string& GetFingerprintAlgorithmString(FingerprintAlgorithm fingerprint) + { + auto it = DtlsTransport::fingerprintAlgorithm2String.find(fingerprint); + + return it->second; + } + static bool IsDtls(const uint8_t* data, size_t len) + { + // clang-format off + return ( + // Minimum DTLS record length is 13 bytes. + (len >= 13) && + // DOC: https://tools.ietf.org/html/draft-ietf-avtcore-rfc5764-mux-fixes + (data[0] > 19 && data[0] < 64) + ); + // clang-format on + } + + private: + static std::map string2Role; + static std::map string2FingerprintAlgorithm; + static std::map fingerprintAlgorithm2String; + static std::vector srtpCryptoSuites; + + public: + DtlsTransport(toolkit::EventPoller::Ptr poller, Listener* listener); + ~DtlsTransport(); + + public: + void Dump() const; + void Run(Role localRole); + std::vector& GetLocalFingerprints() const + { + return env->localFingerprints; + } + bool SetRemoteFingerprint(Fingerprint fingerprint); + void ProcessDtlsData(const uint8_t* data, size_t len); + DtlsState GetState() const + { + return this->state; + } + Role GetLocalRole() const + { + return this->localRole; + } + void SendApplicationData(const uint8_t* data, size_t len); + + private: + bool IsRunning() const + { + switch (this->state) + { + case DtlsState::NEW: + return false; + case DtlsState::CONNECTING: + case DtlsState::CONNECTED: + return true; + case DtlsState::FAILED: + case DtlsState::CLOSED: + return false; + } + + // Make GCC 4.9 happy. + return false; + } + void Reset(); + bool CheckStatus(int returnCode); + void SendPendingOutgoingDtlsData(); + bool SetTimeout(); + bool ProcessHandshake(); + bool CheckRemoteFingerprint(); + void ExtractSrtpKeys(RTC::SrtpSession::CryptoSuite srtpCryptoSuite); + RTC::SrtpSession::CryptoSuite GetNegotiatedSrtpCryptoSuite(); + + private: + void OnSslInfo(int where, int ret); + void OnTimer(); + + private: + DtlsEnvironment::Ptr env; + toolkit::EventPoller::Ptr poller; + // Passed by argument. + Listener* listener{ nullptr }; + // Allocated by this. + SSL* ssl{ nullptr }; + BIO* sslBioFromNetwork{ nullptr }; // The BIO from which ssl reads. + BIO* sslBioToNetwork{ nullptr }; // The BIO in which ssl writes. + toolkit::Timer::Ptr timer; + // Others. + DtlsState state{ DtlsState::NEW }; + Role localRole{ Role::NONE }; + Fingerprint remoteFingerprint; + bool handshakeDone{ false }; + bool handshakeDoneNow{ false }; + std::string remoteCert; + //最大不超过mtu + static constexpr int SslReadBufferSize{ 2000 }; + uint8_t sslReadBuffer[SslReadBufferSize]; +}; +} // namespace RTC + +#endif diff --git a/webrtc/IceServer.cpp b/webrtc/IceServer.cpp deleted file mode 100644 index d4530d10..00000000 --- a/webrtc/IceServer.cpp +++ /dev/null @@ -1,528 +0,0 @@ -/** -ISC License - -Copyright © 2015, Iñaki Baz Castillo - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#define MS_CLASS "RTC::IceServer" -// #define MS_LOG_DEV_LEVEL 3 - -#include -#include "IceServer.hpp" - -namespace RTC -{ - /* Static. */ - /* Instance methods. */ - - IceServer::IceServer(Listener* listener, const std::string& usernameFragment, const std::string& password) - : listener(listener), usernameFragment(usernameFragment), password(password) - { - MS_TRACE(); - } - - void IceServer::ProcessStunPacket(RTC::StunPacket* packet, RTC::TransportTuple* tuple) - { - MS_TRACE(); - - // Must be a Binding method. - if (packet->GetMethod() != RTC::StunPacket::Method::BINDING) - { - if (packet->GetClass() == RTC::StunPacket::Class::REQUEST) - { - MS_WARN_TAG( - ice, - "unknown method %#.3x in STUN Request => 400", - static_cast(packet->GetMethod())); - - // Reply 400. - RTC::StunPacket* response = packet->CreateErrorResponse(400); - - response->Serialize(StunSerializeBuffer); - this->listener->OnIceServerSendStunPacket(this, response, tuple); - - delete response; - } - else - { - MS_WARN_TAG( - ice, - "ignoring STUN Indication or Response with unknown method %#.3x", - static_cast(packet->GetMethod())); - } - - return; - } - - // Must use FINGERPRINT (optional for ICE STUN indications). - if (!packet->HasFingerprint() && packet->GetClass() != RTC::StunPacket::Class::INDICATION) - { - if (packet->GetClass() == RTC::StunPacket::Class::REQUEST) - { - MS_WARN_TAG(ice, "STUN Binding Request without FINGERPRINT => 400"); - - // Reply 400. - RTC::StunPacket* response = packet->CreateErrorResponse(400); - - response->Serialize(StunSerializeBuffer); - this->listener->OnIceServerSendStunPacket(this, response, tuple); - - delete response; - } - else - { - MS_WARN_TAG(ice, "ignoring STUN Binding Response without FINGERPRINT"); - } - - return; - } - - switch (packet->GetClass()) - { - case RTC::StunPacket::Class::REQUEST: - { - // USERNAME, MESSAGE-INTEGRITY and PRIORITY are required. - if (!packet->HasMessageIntegrity() || (packet->GetPriority() == 0u) || packet->GetUsername().empty()) - { - MS_WARN_TAG(ice, "mising required attributes in STUN Binding Request => 400"); - - // Reply 400. - RTC::StunPacket* response = packet->CreateErrorResponse(400); - - response->Serialize(StunSerializeBuffer); - this->listener->OnIceServerSendStunPacket(this, response, tuple); - - delete response; - - return; - } - - // Check authentication. - switch (packet->CheckAuthentication(this->usernameFragment, this->password)) - { - case RTC::StunPacket::Authentication::OK: - { - if (!this->oldPassword.empty()) - { - MS_DEBUG_TAG(ice, "new ICE credentials applied"); - - this->oldUsernameFragment.clear(); - this->oldPassword.clear(); - } - - break; - } - - case RTC::StunPacket::Authentication::UNAUTHORIZED: - { - // We may have changed our usernameFragment and password, so check - // the old ones. - // clang-format off - if ( - !this->oldUsernameFragment.empty() && - !this->oldPassword.empty() && - packet->CheckAuthentication(this->oldUsernameFragment, this->oldPassword) == RTC::StunPacket::Authentication::OK - ) - // clang-format on - { - MS_DEBUG_TAG(ice, "using old ICE credentials"); - - break; - } - - MS_WARN_TAG(ice, "wrong authentication in STUN Binding Request => 401"); - - // Reply 401. - RTC::StunPacket* response = packet->CreateErrorResponse(401); - - response->Serialize(StunSerializeBuffer); - this->listener->OnIceServerSendStunPacket(this, response, tuple); - - delete response; - - return; - } - - case RTC::StunPacket::Authentication::BAD_REQUEST: - { - MS_WARN_TAG(ice, "cannot check authentication in STUN Binding Request => 400"); - - // Reply 400. - RTC::StunPacket* response = packet->CreateErrorResponse(400); - - response->Serialize(StunSerializeBuffer); - this->listener->OnIceServerSendStunPacket(this, response, tuple); - - delete response; - - return; - } - } - -#if 0 - // The remote peer must be ICE controlling. - if (packet->GetIceControlled()) - { - MS_WARN_TAG(ice, "peer indicates ICE-CONTROLLED in STUN Binding Request => 487"); - - // Reply 487 (Role Conflict). - RTC::StunPacket* response = packet->CreateErrorResponse(487); - - response->Serialize(StunSerializeBuffer); - this->listener->OnIceServerSendStunPacket(this, response, tuple); - - delete response; - - return; - } - -#endif - - //MS_DEBUG_DEV( - // "processing STUN Binding Request [Priority:%" PRIu32 ", UseCandidate:%s]", - // static_cast(packet->GetPriority()), - // packet->HasUseCandidate() ? "true" : "false"); - - // Create a success response. - RTC::StunPacket* response = packet->CreateSuccessResponse(); - - sockaddr_storage peerAddr; - socklen_t addr_len = sizeof(peerAddr); - getpeername(tuple->getSock()->rawFD(), (struct sockaddr *)&peerAddr, &addr_len); - - // Add XOR-MAPPED-ADDRESS. - response->SetXorMappedAddress((struct sockaddr *)&peerAddr); - - // Authenticate the response. - if (this->oldPassword.empty()) - response->Authenticate(this->password); - else - response->Authenticate(this->oldPassword); - - // Send back. - response->Serialize(StunSerializeBuffer); - this->listener->OnIceServerSendStunPacket(this, response, tuple); - - delete response; - - // Handle the tuple. - HandleTuple(tuple, packet->HasUseCandidate()); - - break; - } - - case RTC::StunPacket::Class::INDICATION: - { - MS_DEBUG_TAG(ice, "STUN Binding Indication processed"); - - break; - } - - case RTC::StunPacket::Class::SUCCESS_RESPONSE: - { - MS_DEBUG_TAG(ice, "STUN Binding Success Response processed"); - - break; - } - - case RTC::StunPacket::Class::ERROR_RESPONSE: - { - MS_DEBUG_TAG(ice, "STUN Binding Error Response processed"); - - break; - } - } - } - - bool IceServer::IsValidTuple(const RTC::TransportTuple* tuple) const - { - MS_TRACE(); - - return HasTuple(tuple) != nullptr; - } - - void IceServer::RemoveTuple(RTC::TransportTuple* tuple) - { - MS_TRACE(); - - RTC::TransportTuple* removedTuple{ nullptr }; - - // Find the removed tuple. - auto it = this->tuples.begin(); - - for (; it != this->tuples.end(); ++it) - { - RTC::TransportTuple* storedTuple = *it; - - if (storedTuple == tuple) - { - removedTuple = storedTuple; - - break; - } - } - - // If not found, ignore. - if (!removedTuple) - return; - - // Remove from the list of tuples. - this->tuples.erase(it); - - // If this is not the selected tuple, stop here. - if (removedTuple != this->selectedTuple) - return; - - // Otherwise this was the selected tuple. - this->selectedTuple = nullptr; - - // Mark the first tuple as selected tuple (if any). - if (!this->tuples.empty()) - { - SetSelectedTuple(this->tuples.front()); - } - // Or just emit 'disconnected'. - else - { - // Update state. - this->state = IceState::DISCONNECTED; - // Notify the listener. - this->listener->OnIceServerDisconnected(this); - } - } - - void IceServer::ForceSelectedTuple(const RTC::TransportTuple* tuple) - { - MS_TRACE(); - - MS_ASSERT( - this->selectedTuple, "cannot force the selected tuple if there was not a selected tuple"); - - auto* storedTuple = HasTuple(tuple); - - MS_ASSERT( - storedTuple, - "cannot force the selected tuple if the given tuple was not already a valid tuple"); - - // Mark it as selected tuple. - SetSelectedTuple(storedTuple); - } - - void IceServer::HandleTuple(RTC::TransportTuple* tuple, bool hasUseCandidate) - { - MS_TRACE(); - - switch (this->state) - { - case IceState::NEW: - { - // There should be no tuples. - MS_ASSERT( - this->tuples.empty(), "state is 'new' but there are %zu tuples", this->tuples.size()); - - // There shouldn't be a selected tuple. - MS_ASSERT(!this->selectedTuple, "state is 'new' but there is selected tuple"); - - if (!hasUseCandidate) - { - MS_DEBUG_TAG(ice, "transition from state 'new' to 'connected'"); - - // Store the tuple. - auto* storedTuple = AddTuple(tuple); - - // Mark it as selected tuple. - SetSelectedTuple(storedTuple); - // Update state. - this->state = IceState::CONNECTED; - // Notify the listener. - this->listener->OnIceServerConnected(this); - } - else - { - MS_DEBUG_TAG(ice, "transition from state 'new' to 'completed'"); - - // Store the tuple. - auto* storedTuple = AddTuple(tuple); - - // Mark it as selected tuple. - SetSelectedTuple(storedTuple); - // Update state. - this->state = IceState::COMPLETED; - // Notify the listener. - this->listener->OnIceServerCompleted(this); - } - - break; - } - - case IceState::DISCONNECTED: - { - // There should be no tuples. - MS_ASSERT( - this->tuples.empty(), - "state is 'disconnected' but there are %zu tuples", - this->tuples.size()); - - // There shouldn't be a selected tuple. - MS_ASSERT(!this->selectedTuple, "state is 'disconnected' but there is selected tuple"); - - if (!hasUseCandidate) - { - MS_DEBUG_TAG(ice, "transition from state 'disconnected' to 'connected'"); - - // Store the tuple. - auto* storedTuple = AddTuple(tuple); - - // Mark it as selected tuple. - SetSelectedTuple(storedTuple); - // Update state. - this->state = IceState::CONNECTED; - // Notify the listener. - this->listener->OnIceServerConnected(this); - } - else - { - MS_DEBUG_TAG(ice, "transition from state 'disconnected' to 'completed'"); - - // Store the tuple. - auto* storedTuple = AddTuple(tuple); - - // Mark it as selected tuple. - SetSelectedTuple(storedTuple); - // Update state. - this->state = IceState::COMPLETED; - // Notify the listener. - this->listener->OnIceServerCompleted(this); - } - - break; - } - - case IceState::CONNECTED: - { - // There should be some tuples. - MS_ASSERT(!this->tuples.empty(), "state is 'connected' but there are no tuples"); - - // There should be a selected tuple. - MS_ASSERT(this->selectedTuple, "state is 'connected' but there is not selected tuple"); - - if (!hasUseCandidate) - { - // If a new tuple store it. - if (!HasTuple(tuple)) - AddTuple(tuple); - } - else - { - MS_DEBUG_TAG(ice, "transition from state 'connected' to 'completed'"); - - auto* storedTuple = HasTuple(tuple); - - // If a new tuple store it. - if (!storedTuple) - storedTuple = AddTuple(tuple); - - // Mark it as selected tuple. - SetSelectedTuple(storedTuple); - // Update state. - this->state = IceState::COMPLETED; - // Notify the listener. - this->listener->OnIceServerCompleted(this); - } - - break; - } - - case IceState::COMPLETED: - { - // There should be some tuples. - MS_ASSERT(!this->tuples.empty(), "state is 'completed' but there are no tuples"); - - // There should be a selected tuple. - MS_ASSERT(this->selectedTuple, "state is 'completed' but there is not selected tuple"); - - if (!hasUseCandidate) - { - // If a new tuple store it. - if (!HasTuple(tuple)) - AddTuple(tuple); - } - else - { - auto* storedTuple = HasTuple(tuple); - - // If a new tuple store it. - if (!storedTuple) - storedTuple = AddTuple(tuple); - - // Mark it as selected tuple. - SetSelectedTuple(storedTuple); - } - - break; - } - } - } - - inline RTC::TransportTuple* IceServer::AddTuple(RTC::TransportTuple* tuple) - { - MS_TRACE(); - - // Add the new tuple at the beginning of the list. - this->tuples.push_front(tuple); - - // Return the address of the inserted tuple. - return tuple; - } - - inline RTC::TransportTuple* IceServer::HasTuple(const RTC::TransportTuple* tuple) const - { - MS_TRACE(); - - // If there is no selected tuple yet then we know that the tuples list - // is empty. - if (!this->selectedTuple) - return nullptr; - - // Check the current selected tuple. - if (selectedTuple == tuple) - return this->selectedTuple; - - // Otherwise check other stored tuples. - for (const auto& it : this->tuples) - { - auto& storedTuple = it; - if (storedTuple == tuple) - return storedTuple; - } - - return nullptr; - } - - inline void IceServer::SetSelectedTuple(RTC::TransportTuple* storedTuple) - { - MS_TRACE(); - - // If already the selected tuple do nothing. - if (storedTuple == this->selectedTuple) - return; - - this->selectedTuple = storedTuple; - this->lastSelectedTuple = std::static_pointer_cast(storedTuple->shared_from_this()); - - // Notify the listener. - this->listener->OnIceServerSelectedTuple(this, this->selectedTuple); - } -} // namespace RTC diff --git a/webrtc/IceServer.hpp b/webrtc/IceServer.hpp deleted file mode 100644 index 316d32af..00000000 --- a/webrtc/IceServer.hpp +++ /dev/null @@ -1,138 +0,0 @@ -/** -ISC License - -Copyright © 2015, Iñaki Baz Castillo - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef MS_RTC_ICE_SERVER_HPP -#define MS_RTC_ICE_SERVER_HPP - -#include "StunPacket.hpp" -#include "Network/Session.h" -#include "logger.h" -#include "Utils.hpp" -#include -#include -#include -#include - -namespace RTC -{ - using TransportTuple = toolkit::Session; - class IceServer - { - public: - enum class IceState - { - NEW = 1, - CONNECTED, - COMPLETED, - DISCONNECTED - }; - - public: - class Listener - { - public: - virtual ~Listener() = default; - - public: - /** - * These callbacks are guaranteed to be called before ProcessStunPacket() - * returns, so the given pointers are still usable. - */ - virtual void OnIceServerSendStunPacket( - const RTC::IceServer* iceServer, const RTC::StunPacket* packet, RTC::TransportTuple* tuple) = 0; - virtual void OnIceServerSelectedTuple( - const RTC::IceServer* iceServer, RTC::TransportTuple* tuple) = 0; - virtual void OnIceServerConnected(const RTC::IceServer* iceServer) = 0; - virtual void OnIceServerCompleted(const RTC::IceServer* iceServer) = 0; - virtual void OnIceServerDisconnected(const RTC::IceServer* iceServer) = 0; - }; - - public: - IceServer(Listener* listener, const std::string& usernameFragment, const std::string& password); - - public: - void ProcessStunPacket(RTC::StunPacket* packet, RTC::TransportTuple* tuple); - const std::string& GetUsernameFragment() const - { - return this->usernameFragment; - } - const std::string& GetPassword() const - { - return this->password; - } - IceState GetState() const - { - return this->state; - } - RTC::TransportTuple* GetSelectedTuple(bool try_last_tuple = false) const - { - return try_last_tuple ? this->lastSelectedTuple.lock().get() : this->selectedTuple; - } - void SetUsernameFragment(const std::string& usernameFragment) - { - this->oldUsernameFragment = this->usernameFragment; - this->usernameFragment = usernameFragment; - } - void SetPassword(const std::string& password) - { - this->oldPassword = this->password; - this->password = password; - } - bool IsValidTuple(const RTC::TransportTuple* tuple) const; - void RemoveTuple(RTC::TransportTuple* tuple); - // This should be just called in 'connected' or completed' state - // and the given tuple must be an already valid tuple. - void ForceSelectedTuple(const RTC::TransportTuple* tuple); - - const std::list& GetTuples() const { return tuples; } - - private: - void HandleTuple(RTC::TransportTuple* tuple, bool hasUseCandidate); - /** - * Store the given tuple and return its stored address. - */ - RTC::TransportTuple* AddTuple(RTC::TransportTuple* tuple); - /** - * If the given tuple exists return its stored address, nullptr otherwise. - */ - RTC::TransportTuple* HasTuple(const RTC::TransportTuple* tuple) const; - /** - * Set the given tuple as the selected tuple. - * NOTE: The given tuple MUST be already stored within the list. - */ - void SetSelectedTuple(RTC::TransportTuple* storedTuple); - - private: - // Passed by argument. - Listener* listener{ nullptr }; - // Others. - std::string usernameFragment; - std::string password; - std::string oldUsernameFragment; - std::string oldPassword; - IceState state{ IceState::NEW }; - std::list tuples; - RTC::TransportTuple *selectedTuple { nullptr }; - std::weak_ptr lastSelectedTuple; - //最大不超过mtu - static constexpr size_t StunSerializeBufferSize{ 1600 }; - uint8_t StunSerializeBuffer[StunSerializeBufferSize]; - }; -} // namespace RTC - -#endif diff --git a/webrtc/IceSession.cpp b/webrtc/IceSession.cpp new file mode 100644 index 00000000..207d555b --- /dev/null +++ b/webrtc/IceSession.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "IceSession.hpp" +#include "Util/util.h" +#include "Common/config.h" +#include "WebRtcTransport.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +static IceSession::Ptr queryIceTransport(uint8_t *data, size_t size) { + auto packet = RTC::StunPacket::parse((const uint8_t *)data, size); + if (!packet) { + WarnL << "parse stun error"; + return nullptr; + } + + auto username = packet->getUsername(); + return IceSessionManager::Instance().getItem(username); +} + +//////////// IceSession ////////////////////////// +IceSession::IceSession(const Socket::Ptr &sock) : Session(sock) { + TraceL << getIdentifier(); + _over_tcp = sock->sockType() == SockNum::Sock_TCP; + GET_CONFIG(string, iceUfrag, Rtc::kIceUfrag); + GET_CONFIG(string, icePwd, Rtc::kIcePwd); + _ice_transport = std::make_shared(this, iceUfrag, icePwd, getPoller()); + _ice_transport->initialize(); +} + +IceSession::~IceSession() { + TraceL << getIdentifier(); +} + +EventPoller::Ptr IceSession::queryPoller(const Buffer::Ptr &buffer) { + auto transport = queryIceTransport((uint8_t *)buffer->data(), buffer->size()); + return transport ? transport->getPoller() : nullptr; +} + +void IceSession::onRecv(const Buffer::Ptr &buffer) { + // TraceL; + if (_over_tcp) { + input(buffer->data(), buffer->size()); + } + else{ + onRecv_l(buffer->data(), buffer->size()); + } +} + +void IceSession::onRecv_l(const char* buffer, size_t size) { + if (!_session_pair) { + _session_pair = std::make_shared(shared_from_this()); + } + _ice_transport->processSocketData((const uint8_t *)buffer, size, _session_pair); +} + +void IceSession::onError(const SockException &err) { + InfoL; + // 消除循环引用 + _session_pair = nullptr; +} + +void IceSession::onManager() { +} + +ssize_t IceSession::onRecvHeader(const char *data, size_t len) { + onRecv_l(data + 2, len - 2); + return 0; +} + +const char *IceSession::onSearchPacketTail(const char *data, size_t len) { + if (len < 2) { + // Not enough data + return nullptr; + } + uint16_t length = (((uint8_t *)data)[0] << 8) | ((uint8_t *)data)[1]; + if (len < (size_t)(length + 2)) { + // Not enough data + return nullptr; + } + // Return the end of the RTP packet + return data + 2 + length; +} + +void IceSession::onIceTransportRecvData(const toolkit::Buffer::Ptr& buffer, const IceTransport::Pair::Ptr& pair) { + _ice_transport->processSocketData((const uint8_t *)buffer->data(), buffer->size(), pair); +} + +void IceSession::onIceTransportGatheringCandidate(const IceTransport::Pair::Ptr& pair, const CandidateInfo& candidate) { + DebugL << candidate.dumpString(); +} + +void IceSession::onIceTransportDisconnected() { + InfoL << getIdentifier(); +} + +void IceSession::onIceTransportCompleted() { + InfoL << getIdentifier(); +} + +//////////// IceSessionManager ////////////////////////// + +IceSessionManager &IceSessionManager::Instance() { + static IceSessionManager s_instance; + return s_instance; +} + +void IceSessionManager::addItem(const std::string& key, const IceSession::Ptr &ptr) { + std::lock_guard lck(_mtx); + _map[key] = ptr; +} + +IceSession::Ptr IceSessionManager::getItem(const std::string& key) { + assert(!key.empty()); + std::lock_guard lck(_mtx); + auto it = _map.find(key); + if (it == _map.end()) { + return nullptr; + } + return it->second.lock(); +} + +void IceSessionManager::removeItem(const std::string& key) { + std::lock_guard lck(_mtx); + _map.erase(key); +} + +}// namespace mediakit diff --git a/webrtc/IceSession.hpp b/webrtc/IceSession.hpp new file mode 100644 index 00000000..571b8af0 --- /dev/null +++ b/webrtc/IceSession.hpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + + +#ifndef ZLMEDIAKIT_WEBRTC_ICE_SESSION_H +#define ZLMEDIAKIT_WEBRTC_ICE_SESSION_H + +#include "Network/Session.h" +#include "IceTransport.hpp" +#include "Http/HttpRequestSplitter.h" + +namespace mediakit { + +class IceSession : public toolkit::Session, public RTC::IceTransport::Listener, public HttpRequestSplitter { +public: + using Ptr = std::shared_ptr; + using WeakPtr = std::weak_ptr; + IceSession(const toolkit::Socket::Ptr &sock); + ~IceSession() override; + + static toolkit::EventPoller::Ptr queryPoller(const toolkit::Buffer::Ptr &buffer); + + //// Session override//// + // void attachServer(const Server &server) override; + void onRecv(const toolkit::Buffer::Ptr &) override; + void onError(const toolkit::SockException &err) override; + void onManager() override; + + // ice related callbacks /// + void onIceTransportRecvData(const toolkit::Buffer::Ptr& buffer, const RTC::IceTransport::Pair::Ptr& pair) override; + void onIceTransportGatheringCandidate(const RTC::IceTransport::Pair::Ptr& pair, const RTC::CandidateInfo& candidate) override; + void onIceTransportDisconnected() override; + void onIceTransportCompleted() override; + + //// HttpRequestSplitter override //// + ssize_t onRecvHeader(const char *data, size_t len) override; + const char *onSearchPacketTail(const char *data, size_t len) override; + + void onRecv_l(const char *data, size_t len); +protected: + bool _over_tcp = false; + + RTC::IceTransport::Pair::Ptr _session_pair = nullptr; + RTC::IceServer::Ptr _ice_transport; +}; + +class IceSessionManager { +public: + static IceSessionManager &Instance(); + IceSession::Ptr getItem(const std::string& key); + void addItem(const std::string& key, const IceSession::Ptr &ptr); + void removeItem(const std::string& key); + +private: + IceSessionManager() = default; + +private: + std::mutex _mtx; + std::unordered_map> _map; +}; +}// namespace mediakit + +#endif //ZLMEDIAKIT_WEBRTC_ICE_SESSION_H diff --git a/webrtc/IceTransport.cpp b/webrtc/IceTransport.cpp new file mode 100644 index 00000000..84b5f01d --- /dev/null +++ b/webrtc/IceTransport.cpp @@ -0,0 +1,2026 @@ +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. +*/ + +#include +#include +#include +#include "json/json.h" +#include "Util/onceToken.h" +#include "Network/UdpClient.h" +#include "Common/Parser.h" +#include "Common/config.h" +#include "IceTransport.hpp" +#include "WebRtcTransport.h" + +using namespace std; +using namespace toolkit; +using namespace mediakit; + +namespace RTC { + +#define RTC_FIELD "rtc." +const string kPortRange = RTC_FIELD "port_range"; +const string kMaxStunRetry = RTC_FIELD "max_stun_retry"; +static onceToken token([]() { + mINI::Instance()[kPortRange] = "49152-65535"; + mINI::Instance()[kMaxStunRetry] = 7; +}); + +static uint32_t calIceCandidatePriority(CandidateInfo::AddressType type, uint32_t component_id = 1) { + uint32_t type_preference; + switch (type) { + case CandidateInfo::AddressType::HOST: type_preference = 126; break; + case CandidateInfo::AddressType::PRFLX: type_preference = 110; break; + case CandidateInfo::AddressType::SRFLX: type_preference = 100; break; + case CandidateInfo::AddressType::RELAY: type_preference = 0; break; + default: throw std::invalid_argument(StrPrinter << "not support type :" << (uint32_t)type); + } + + uint32_t local_preference = 100; + return (type_preference << 24) + (local_preference << 8) + (256 - component_id); +} + +uint64_t calCandidatePairPriority(uint32_t G, uint32_t D) { + uint32_t min_p = (G < D) ? G : D; + uint32_t max_p = (G > D) ? G : D; + return ((uint64_t)min_p << 32) | (2 * (uint64_t)max_p) | (G > D ? 1 : 0); +} + +std::string addrToStr(const sockaddr_storage& addr) { + return StrPrinter << SockUtil::inet_ntoa((const struct sockaddr*)&addr) + << ":" << SockUtil::inet_port((const struct sockaddr*)&addr); +} + +// 检查ICE传输策略是否允许该候选者对 +static bool checkIceTransportPolicy(const IceAgent::CandidatePair& pair_info, const IceTransport::Pair::Ptr& pair) { + GET_CONFIG(int, ice_transport_policy, Rtc::kIceTransportPolicy); + + // 优先使用新的统一配置参数 + switch (static_cast(ice_transport_policy)) { + case IceTransportPolicy::kRelayOnly: + // 仅支持Relay转发:要求本地或远程是中继类型 + if (pair_info._local_candidate._type != CandidateInfo::AddressType::RELAY && + pair_info._remote_candidate._type != CandidateInfo::AddressType::RELAY) { + DebugL << "relay only policy, skip pair: " << pair_info.dumpString(); + return false; + } + break; + + case IceTransportPolicy::kP2POnly: + // 仅支持P2P直连:要求本地和远程都不是中继类型 + if (pair_info._local_candidate._type == CandidateInfo::AddressType::RELAY || + pair_info._remote_candidate._type == CandidateInfo::AddressType::RELAY) { + DebugL << "p2p only policy, skip pair: " << pair_info.dumpString(); + return false; + } + break; + + case IceTransportPolicy::kAll: + default: break; + } + + return true; +} + +//////////// IceServerInfo ////////////////////////// +void IceServerInfo::parse(const std::string &url_in) { + DebugL << url_in; + + _full_url = url_in; + auto url = url_in; + + auto schema_pos = url.find(":"); + if (schema_pos == string::npos) { + throw std::runtime_error(StrPrinter << "fail to parse schema in url: " << url_in); + } + + auto schema = url.substr(0, schema_pos); + if (strcasecmp(schema.data(), "turns") == 0) { + _schema = SchemaType::TURN; + _secure = CandidateTuple::SecureType::SECURE; + } else if (strcasecmp(schema.data(), "turn") == 0) { + _schema = SchemaType::TURN; + _secure = CandidateTuple::SecureType::NOT_SECURE; + } else if (strcasecmp(schema.data(), "stuns") == 0) { + _schema = SchemaType::STUN; + _secure = CandidateTuple::SecureType::SECURE; + } else if (strcasecmp(schema.data(), "stun") == 0) { + _schema = SchemaType::STUN; + _secure = CandidateTuple::SecureType::NOT_SECURE; + } else { + throw std::runtime_error(StrPrinter << "not support schema: " << schema); + } + + // 解析了用户名密码之后再解析?参数,防止密码中的?被判为参数分隔符 + auto pos = url.find("?"); + if (pos != string::npos) { + _param_strs = url.substr(pos + 1); + url.erase(pos); + } + _addr._port = (_secure == CandidateTuple::SecureType::NOT_SECURE) ? 3478 : 5349; + auto host = url.substr(schema_pos + 1, pos); + mediakit::splitUrl(host, _addr._host, _addr._port); + + auto params = mediakit::Parser::parseArgs(_param_strs); + if (params.find("transport") != params.end()) { + auto transport = params["transport"]; + if (strcasecmp(transport.data(), "udp") == 0) { + _transport = CandidateTuple::TransportType::UDP; + } else if (strcasecmp(transport.data(), "tcp") == 0) { + _transport = CandidateTuple::TransportType::TCP; + } else { + throw std::runtime_error(StrPrinter <<"not support transport: " << transport); + } + } else { + _transport = CandidateTuple::TransportType::UDP; + } +} + +//////////// IceTransport ////////////////////////// + +IceTransport::IceTransport(Listener* listener, std::string ufrag, std::string password, EventPoller::Ptr poller) +: _poller(std::move(poller)), _listener(listener), _ufrag(std::move(ufrag)), _password(std::move(password)) { + TraceL; + _identifier = makeRandStr(32); + _request_handlers.emplace(std::make_pair(StunPacket::Class::REQUEST, StunPacket::Method::BINDING), + std::bind(&IceTransport::handleBindingRequest, this, std::placeholders::_1, std::placeholders::_2)); +} + +void IceTransport::initialize() { + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + _retry_timer = std::make_shared(0.1f, [weak_self]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return false; + } + strong_self->checkRequestTimeouts(); + return true; + }, getPoller()); +} + +void IceTransport::sendSocketData(const Buffer::Ptr& buf, const Pair::Ptr& pair, bool flush) { + return sendSocketData_l(buf, pair, flush); +} + +void IceTransport::sendSocketData_l(const Buffer::Ptr& buf, const Pair::Ptr& pair, bool flush) { + // DebugL; + if (pair == nullptr) { + throw std::invalid_argument("pair should not be nullptr"); + } + + // 一次性发送一帧的rtp数据,提高网络io性能 [AUTO-TRANSLATED:fbab421e] + // Send one frame of rtp data at a time to improve network io performance + if (pair->_socket->getSock()->sockType() == SockNum::Sock_TCP) { + // 增加tcp两字节头 + uint16_t len = htons(buf->size()); + pair->_socket->SockSender::send((char *)&len, 2); + } + +#if 0 + TraceL << pair->dumpString(1) << " send " << buf->size(); + TraceL << "data: " << hexdump(buf->data(), buf->size()); +#endif + + sockaddr_storage peer_addr; + pair->get_peer_addr(peer_addr); + auto addr_len = SockUtil::get_sock_len((const struct sockaddr*)&peer_addr); + pair->_socket->sendto(buf, (struct sockaddr*)&peer_addr, addr_len); + if (flush) { + pair->_socket->flushAll(); + } +} + +bool IceTransport::processSocketData(const uint8_t* data, size_t len, const Pair::Ptr& pair) { +#if 0 + TraceL << pair->dumpString(0) << " data len: " << len; + sockaddr_storage relay_peer_addr; + if (pair->get_relayed_addr(relay_peer_addr)) { + TraceL << "data relay from peer " << addrToStr(relay_peer_addr); + } +#endif + + auto packet = StunPacket::parse(data, len); + if (!packet) { + return processChannelData(data, len, pair); + } + processStunPacket(packet, pair); + return true; +} + +void IceTransport::processStunPacket(const StunPacket::Ptr& packet, const Pair::Ptr& pair) { +#if 0 + TraceL << "recv packet : " << packet->dumpString(true); +#endif + if ((packet->getClass() == StunPacket::Class::REQUEST) || (packet->getClass() == StunPacket::Class::INDICATION)) { + processRequest(packet, pair); + } else { + processResponse(packet, pair); + } +} + +StunPacket::Authentication IceTransport::checkRequestAuthentication(const StunPacket::Ptr& packet, const Pair::Ptr& pair) { + if (packet->getClass() == StunPacket::Class::INDICATION) { + return StunPacket::Authentication::OK; + } +#if 0 + DebugL << "_ufrag: " << _ufrag << ", _password: " << _password; +#endif + // Check authentication. + auto ret = packet->checkAuthentication(_ufrag, _password); + if (ret != StunPacket::Authentication::OK) { + sendUnauthorizedResponse(packet, pair); + } + return ret; +} + +StunPacket::Authentication IceTransport::checkResponseAuthentication(const StunPacket::Ptr& request, const StunPacket::Ptr& packet, const Pair::Ptr& pair) { + if (!packet->hasAttribute(StunAttribute::Type::FINGERPRINT)) { + sendUnauthorizedResponse(packet, pair); + return StunPacket::Authentication::UNAUTHORIZED; + } +#if 0 + DebugL << "peer_ufrag: " << request->getPeerUfrag() << ", peer_password: " << request->getPeerPassword(); +#endif + auto ret = packet->checkAuthentication(request->getPeerUfrag(), request->getPeerPassword()); + if (ret != StunPacket::Authentication::OK) { + sendUnauthorizedResponse(packet, pair); + } + return ret; +} + +void IceTransport::processResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair) { + // TraceL; + + auto it = _response_handlers.find(packet->getTransactionId().data()); + if (it == _response_handlers.end()) { + WarnL << "not support stun transaction_id ignore: " << packet->dumpString(true); + return; + } + + auto request = std::move(it->second._request); + auto handle = std::move(it->second._handler); + // 收到响应后立即清理请求信息 + _response_handlers.erase(it); + + if (packet->getClass() == StunPacket::Class::ERROR_RESPONSE) { + if (StunAttrErrorCode::Code::Unauthorized == packet->getErrorCode()) { + return processUnauthorizedResponse(packet, request, pair, std::move(handle)); + } + return; + } + + if (StunPacket::Authentication::OK != checkResponseAuthentication(request, packet, pair)) { + WarnL << "checkRequestAuthentication fail: " << packet->dumpString(); + return; + } + + handle(packet, pair); +} + +#pragma pack(push, 1) +struct ChannelDataHeader { + uint16_t channel_number; + uint16_t data_length; +}; +#pragma pack(pop) + +bool IceTransport::processChannelData(const uint8_t* data, size_t len, const Pair::Ptr& pair) { + // DebugL; + + // 检查数据长度是否足够 + if (len < 4) { + WarnL << "Received data too short to be a valid STUN or ChannelData message"; + return false; + } + ChannelDataHeader header = *(reinterpret_cast(data)); + header.channel_number = ntohs(header.channel_number); + header.data_length = ntohs(header.data_length); + + // 检查是否是ChannelData消息 + // ChannelData消息的前两个字节是通道号,范围是0x4000-0x7FFF + if (header.channel_number < 0x4000 || header.channel_number > 0x7FFF) { + // WarnL << "Invalid channel number: " << header.channel_number; + return false; + } + + // 这是一个ChannelData消息; 检查数据长度是否合法 + if (len < 4 + header.data_length) { + WarnL << "ChannelData message truncated, len: " << len << ", data_length: " << header.data_length; + return false; + } + + handleChannelData(header.channel_number, (const char *)(data + 4), header.data_length, pair); + return true; +} + +void IceTransport::processUnauthorizedResponse(const StunPacket::Ptr& response, const StunPacket::Ptr& request, const Pair::Ptr& pair, MsgHandler handler) { + // TraceL; + auto attr_nonce = response->getAttribute(); + auto attr_realm = response->getAttribute(); + if (!attr_nonce || !attr_realm) { + return; + } + + request->refreshTransactionId(); + request->addAttribute(std::move(attr_nonce)); + request->addAttribute(std::move(attr_realm)); + request->setNeedMessageIntegrity(true); + sendRequest(request, pair, std::move(handler)); +} + +void IceTransport::processRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair) { + // TraceL; + if (StunPacket::Authentication::OK != checkRequestAuthentication(packet, pair)) { + WarnL << "checkRequestAuthentication fail: " << packet->dumpString(); + return; + } + + auto it = _request_handlers.find(std::make_pair(packet->getClass(), packet->getMethod())); + if (it == _request_handlers.end()) { + WarnL << "ignore unsupport stun "<< packet->dumpString(); + return; + } + + return (it->second)(packet, pair); +} + +void IceTransport::handleBindingRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair) { + // TraceL; + + auto response = packet->createSuccessResponse(); + response->setUfrag(_ufrag); + response->setPassword(_password); + + sockaddr_storage peer_addr; + if (!pair->get_relayed_addr(peer_addr)) { + pair->get_peer_addr(peer_addr); + } + + // Add XOR-MAPPED-ADDRESS. + auto attr_xor_mapped_address = std::make_shared(response->getTransactionId()); + attr_xor_mapped_address->setAddr(peer_addr); + response->addAttribute(std::move(attr_xor_mapped_address)); + + sendPacket(response, pair); +} + +void IceTransport::sendChannelData(uint16_t channel_number, const Buffer::Ptr& buffer, const Pair::Ptr& pair) { + // TraceL; + + // ChannelData不是STUN消息,需要单独实现 + // ChannelData格式:2字节Channel Number + 2字节数据长度 + 数据内容 + auto data_len = buffer->size(); + size_t total_len = 4 + data_len; + // 分配缓冲区:头部4字节 + 数据长度 + auto channel_data = toolkit::BufferRaw::create(total_len); + auto header = reinterpret_cast(channel_data->data()); + // 设置Channel Number (前两字节,网络字节序) + header->channel_number = htons(channel_number); + // 设置数据长度 (中间两字节,网络字节序) + header->data_length = htons(data_len); + // 拷贝数据 + memcpy(channel_data->data() + 4, buffer->data(), data_len); + channel_data->setSize(total_len); + +#if 0 + TraceL << pair->dumpString(1) << " send channel " << channel_number << " data " << data_len; + TraceL << "data: " << hexdump(buffer->data(), buffer->size()); +#endif + + sendSocketData(channel_data, pair); +} + +void IceTransport::sendUnauthorizedResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair) { + // TraceL; + auto response = packet->createErrorResponse(StunAttrErrorCode::Code::Unauthorized); + sendPacket(response, pair); +} + +void IceTransport::sendErrorResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair, StunAttrErrorCode::Code errorCode) { + // TraceL; + auto response = packet->createErrorResponse(errorCode); + sendPacket(response, pair); +} + +void IceTransport::sendRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair, MsgHandler handler) { + // TraceL; + _response_handlers.emplace(packet->getTransactionId().data(), RequestInfo(packet, std::move(handler), pair)); + sendPacket(packet, pair); +} + +void IceTransport::sendPacket(const StunPacket::Ptr& packet, const Pair::Ptr& pair) { +#if 0 + TraceL << pair->dumpString(1) << " send packet: " << packet->dumpString(true); +#endif + packet->serialize(); + sendSocketData(std::static_pointer_cast(packet), pair); +} + +bool IceTransport::hasPermission(const sockaddr_storage& addr) { + auto it = _permissions.find(addr); + if (it == _permissions.end()) { + return false; + } + + // 权限有效期为5分钟 + uint64_t now = toolkit::getCurrentMillisecond(); + if (now - it->second > 5 * 60 * 1000) { + DebugL << "permissions over time, ip:" << addrToStr(addr); + _permissions.erase(it); + return false; + } + + return true; +} + +void IceTransport::addPermission(const sockaddr_storage& addr) { + _permissions[addr] = toolkit::getCurrentMillisecond(); +} + +bool IceTransport::hasChannelBind(uint16_t channel_number) { + return _channel_bindings.find(channel_number) != _channel_bindings.end(); +} + +bool IceTransport::hasChannelBind(const sockaddr_storage& addr, uint16_t& channel_number) { + for (const auto& binding : _channel_bindings) { + if (SockUtil::is_same_addr(reinterpret_cast(&binding.second), + reinterpret_cast(&addr))) { + channel_number = binding.first; + return true; + } + } + return false; +} + +void IceTransport::addChannelBind(uint16_t channel_number, const sockaddr_storage& addr) { + _channel_bindings[channel_number] = addr; + _channel_binding_times[channel_number] = toolkit::getCurrentMillisecond(); +} + +SocketHelper::Ptr IceTransport::createSocket(CandidateTuple::TransportType type, const std::string &peer_host, uint16_t peer_port, const std::string &local_ip, uint16_t local_port) { + if (type != CandidateTuple::TransportType::UDP) { + throw std::invalid_argument("not support transport type: TCP"); + } + return createUdpSocket(peer_host, peer_port, local_ip, local_port); +} + +SocketHelper::Ptr IceTransport::createUdpSocket(const std::string &peer_host, uint16_t peer_port, const std::string &local_ip, uint16_t local_port) { + auto socket = std::make_shared(getPoller()); + + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + auto ptr = socket.get(); + socket->setOnRecvFrom([weak_self, ptr](const Buffer::Ptr &buffer, struct sockaddr *addr, int addr_len) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + auto peer_host = SockUtil::inet_ntoa(addr); + auto peer_port = SockUtil::inet_port(addr); + auto pair = std::make_shared(ptr->shared_from_this(), std::move(peer_host), peer_port); + strong_self->_listener->onIceTransportRecvData(buffer, pair); + }); + + socket->setOnError([weak_self](const SockException &err) { + WarnL << err; + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + }); + + socket->setNetAdapter(local_ip); + socket->startConnect(peer_host, peer_port, local_port); + + return socket; +} + + +//////////// IceServer ////////////////////////// + +#ifndef UNUSED +#define UNUSED(x) ((void)(x)) +#endif +template +class PortManager : public std::enable_shared_from_this> { +public: + PortManager() = default; + + static PortManager &Instance() { + static auto instance = std::make_shared(); + return *instance; + } + + void addListenConfigReload(){ + weak_ptr weak_self = this->shared_from_this(); + static auto func = [weak_self](const string &str, int index) { + uint16_t port[] = { 49152, 65535 }; + auto strong_self = weak_self.lock(); + if (!strong_self) { + return port[index]; + } + sscanf(str.data(), "%" SCNu16 "-%" SCNu16, port, port + 1); + strong_self->setRange(port[0], port[1]); + return port[index]; + }; + + GET_CONFIG_FUNC(uint16_t, dummy_min_port, kPortRange, [](const string &str) { return func(str, 0); }); + GET_CONFIG_FUNC(uint16_t, dummy_max_port, kPortRange, [](const string &str) { return func(str, 1); }); + UNUSED(dummy_min_port); + UNUSED(dummy_max_port); + } + + std::shared_ptr getSinglePort() { + lock_guard lck(_pool_mtx); + if (_port_pool.empty()) { + return nullptr; + } + + auto pos = _port_pool.front(); + _port_pool.pop_front(); + InfoL << "got port from pool:" << pos; + + weak_ptr weak_self = this->shared_from_this(); + std::shared_ptr ret(new uint16_t(pos), [weak_self, pos](uint16_t *ptr) { + delete ptr; + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + + lock_guard lck(strong_self->_pool_mtx); + if (pos >= strong_self->_min_port && pos < strong_self->_max_port) { + InfoL << "return port:" << pos << " to pool"; + // 回收端口号 + strong_self->_port_pool.emplace_back(pos); + } else { + InfoL << "release port:" << pos << "[" << strong_self->_min_port << "-" << strong_self->_max_port << "]"; + // 端口范围修改过,该端口不在范围内了,不回收端口号 + } + }); + + return ret; + } + +private: + void setRange(uint16_t min_port, uint16_t max_port) { + assert(max_port >= min_port + 36 - 1); + lock_guard lck(_pool_mtx); + //端口范围未改变,不用处理 + if (min_port == _min_port && max_port == _max_port) { + return; + } + + InfoL << "setPortRange from [" << _min_port << "-" << _max_port << "] to [" << min_port << "-" << max_port << "]"; + + // 修改:直接使用端口值,不再除以2 + uint16_t start_pos = min_port; + uint16_t end_pos = max_port; + std::mt19937 rng(std::random_device {}()); + + //新指定的端口范围和原端口范围不交集,直接清除并重新增加 + if (max_port <= _min_port || min_port >= _max_port) { + _port_pool.clear(); + } else { + + //存在交集,先把交集范围内还未被分配的端口保留 + deque port_pool; + for (; !_port_pool.empty(); _port_pool.pop_front()) { + auto pos = _port_pool.front(); + + if (pos >= start_pos && pos < end_pos) { + port_pool.emplace_back(pos); + } + } + _port_pool.swap(port_pool); + + if (_min_port <= min_port && _max_port < max_port) { + // <_min_port|--------|********************|_max_port> + // = max_port && _min_port > min_port) { + // + // <_min_port|********************|--------|_max_port| + + start_pos = min_port; + end_pos = _min_port; + } else if (min_port < _min_port && max_port > _max_port) { + // _port_pool; +}; + +//注册端口管理监听配置重载 +onceToken PortManager_token([](){ + PortManager<0>::Instance().addListenConfigReload(); + PortManager<1>::Instance().addListenConfigReload(); +}); + +std::unordered_map _relayed_session; + +IceServer::IceServer(Listener* listener, std::string ufrag, std::string password, toolkit::EventPoller::Ptr poller) + : IceTransport(listener, std::move(ufrag), std::move(password), std::move(poller)) { + DebugL; + + GET_CONFIG(bool, enable_turn, Rtc::kEnableTurn); + if (enable_turn) { + _request_handlers.emplace(std::make_pair(StunPacket::Class::REQUEST, StunPacket::Method::ALLOCATE), + std::bind(&IceServer::handleAllocateRequest, this, placeholders::_1, placeholders::_2)); + _request_handlers.emplace(std::make_pair(StunPacket::Class::REQUEST, StunPacket::Method::REFRESH), + std::bind(&IceServer::handleRefreshRequest, this, placeholders::_1, placeholders::_2)); + _request_handlers.emplace(std::make_pair(StunPacket::Class::REQUEST, StunPacket::Method::CREATEPERMISSION), + std::bind(&IceServer::handleCreatePermissionRequest, this, placeholders::_1, placeholders::_2)); + _request_handlers.emplace(std::make_pair(StunPacket::Class::REQUEST, StunPacket::Method::CHANNELBIND), + std::bind(&IceServer::handleChannelBindRequest, this, placeholders::_1, placeholders::_2)); + _request_handlers.emplace(std::make_pair(StunPacket::Class::INDICATION, StunPacket::Method::SEND), + std::bind(&IceServer::handleSendIndication, this, placeholders::_1, placeholders::_2)); + } + +} + +bool IceServer::processSocketData(const uint8_t* data, size_t len, const Pair::Ptr& pair) { + if (!_session_pair) { + _session_pair = pair; + } + return IceTransport::processSocketData(data, len, pair); +} + +void IceServer::processRelayPacket(const Buffer::Ptr &buffer, const Pair::Ptr& pair) { + // TraceL << pair->dumpString(0); + + sockaddr_storage peer_addr; + pair->get_peer_addr(peer_addr); + + if (!hasPermission(peer_addr)) { + WarnL << "No permission exists for peer: " << pair->get_peer_ip() << ":" << pair->get_peer_port(); + return; + } + + auto forward_pair = std::make_shared(_session_pair->_socket, pair->_socket->get_peer_ip(), pair->_socket->get_peer_port()); + uint16_t channel_number; + if (hasChannelBind(peer_addr, channel_number)) { + sendChannelData(channel_number, buffer, forward_pair); + } else { + sendDataIndication(peer_addr, buffer, forward_pair); + } +} + +void IceServer::handleAllocateRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair) { + // TraceL; + auto response = packet->createSuccessResponse(); + response->setUfrag(_ufrag); + response->setPassword(_password); + + // Add XOR-MAPPED-ADDRESS. + sockaddr_storage peer_addr; + pair->get_peer_addr(peer_addr); + + auto attr_xor_mapped_address = std::make_shared(response->getTransactionId()); + attr_xor_mapped_address->setAddr(peer_addr); + response->addAttribute(std::move(attr_xor_mapped_address)); + + // Add XOR-RELAYED-ADDRESS. + auto socket = allocateRelayed(pair); + sockaddr_storage relayed_addr = SockUtil::make_sockaddr(socket->get_local_ip().data(), socket->get_local_port()); + auto attr_xor_relayed_address = std::make_shared(response->getTransactionId()); + attr_xor_relayed_address->setAddr(relayed_addr); + response->addAttribute(std::move(attr_xor_relayed_address)); + + auto attr_lifetime = std::make_shared(); + attr_lifetime->setLifetime(600); + response->addAttribute(std::move(attr_lifetime)); + + sendPacket(response, pair); +} + +void IceServer::handleRefreshRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair) { + // TraceL +} + +void IceServer::handleCreatePermissionRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair) { + // TraceL + + // 检查XOR-PEER-ADDRESS属性是否存在 + auto peer_addr = packet->getAttribute(); + if (!peer_addr) { + WarnL << "CreatePermission request missing XOR-PEER-ADDRESS attribute"; + sendErrorResponse(packet, pair, StunAttrErrorCode::Code::BadRequest); + return; + } + + addPermission(peer_addr->getAddr()); + + auto response = packet->createSuccessResponse(); + response->setUfrag(_ufrag); + response->setPassword(_password); + sendPacket(response, pair); +} + +void IceServer::handleChannelBindRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair) { + // TraceL + + // 检查必要的属性 + auto channel_number = packet->getAttribute(); + auto peer_addr = packet->getAttribute(); + if (!channel_number || !peer_addr) { + WarnL << "ChannelBind request missing required attributes"; + sendErrorResponse(packet, pair, StunAttrErrorCode::Code::BadRequest); + return; + } + + // 验证通道号是否在有效范围内 (0x4000-0x7FFF) + uint16_t number = channel_number->getChannelNumber(); + if (number < 0x4000 || number > 0x7FFF) { + WarnL << "Invalid channel number: " << number; + sendErrorResponse(packet, pair, StunAttrErrorCode::Code::BadRequest); + return; + } + + // 检查是否有对应peer地址的权限 + auto addr = peer_addr->getAddr(); + if (!hasPermission(addr)) { + WarnL << "No permission exists for peer address"; + sendErrorResponse(packet, pair, StunAttrErrorCode::Code::Forbidden); + return; + } + + // 添加或更新通道绑定 + addChannelBind(number, addr); + + auto response = packet->createSuccessResponse(); + response->setUfrag(_ufrag); + response->setPassword(_password); + sendPacket(response, pair); +} + +void IceServer::handleSendIndication(const StunPacket::Ptr& packet, const Pair::Ptr& pair) { + // TraceL + + // 检查必要的属性 + auto peer_addr = packet->getAttribute(); + auto data = packet->getAttribute(); + + if (!peer_addr || !data) { + WarnL << "Send indication missing required attributes"; + return; + } + + // 检查是否有对应peer地址的权限 + auto addr = peer_addr->getAddr(); + if (!hasPermission(addr)) { + WarnL << "No permission exists for peer address"; + return; + } + + auto buffer = data->getData(); + auto send_buffer = BufferRaw::create(buffer.size()); + send_buffer->assign(buffer.data(), buffer.size()); + return relayBackingData(send_buffer, pair, addr); +} + +void IceServer::handleChannelData(uint16_t channel_number, const char* data, size_t len, const Pair::Ptr& pair) { + // TraceL << "Received ChannelData message, channel number: " << channel_number; + + // 查找该通道号对应的目标地址 + auto it = _channel_bindings.find(channel_number); + if (it == _channel_bindings.end()) { + WarnL << "No binding found for channel number: " << channel_number; + return; + } + + // 获取目标地址 + sockaddr_storage peer_addr = it->second; + + // 创建一个新的缓冲区用于转发 + auto buffer = BufferRaw::create(len); + buffer->assign(data, len); + + // 转发数据到目标地址 + relayBackingData(buffer, pair, peer_addr); +} + +void IceServer::sendUnauthorizedResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair) { + // TraceL + + if (packet->getMethod() == StunPacket::Method::ALLOCATE) { + auto response = packet->createErrorResponse(StunAttrErrorCode::Code::Unauthorized); + auto attr_nonce = std::make_shared(); + auto nonce = makeRandStr(80); + _nonce_list.push_back(nonce); + attr_nonce->setNonce(nonce); + response->addAttribute(std::move(attr_nonce)); + + auto attr_realm = std::make_shared(); + attr_realm->setRealm("ZLM"); //TODO: use config.ini + response->addAttribute(std::move(attr_realm)); + sendPacket(response, pair); + return; + } + + IceTransport::sendUnauthorizedResponse(packet, pair); +} + +StunPacket::Authentication IceServer::checkRequestAuthentication(const StunPacket::Ptr& packet, const Pair::Ptr& pair) { + // TraceL + + //ICE SERVER 不对BINDGING请求校验 + if (packet->getMethod() == StunPacket::Method::BINDING) { + return StunPacket::Authentication::OK; + } + + return IceTransport::checkRequestAuthentication(packet, pair); +} + +void IceServer::sendDataIndication(const sockaddr_storage& peer_addr, const Buffer::Ptr& buffer, const Pair::Ptr& pair) { + // TraceL + + auto packet = std::make_shared(); + + auto attr_peer_address = std::make_shared(packet->getTransactionId()); + attr_peer_address->setAddr(peer_addr); + packet->addAttribute(std::move(attr_peer_address)); + + auto attr_data = std::make_shared(); + attr_data->setData((const char *)buffer->data(), buffer->size()); + packet->addAttribute(std::move(attr_data)); + + sendPacket(packet, pair); +#if 0 + TraceL << pair->dumpString(1) << " Forward UDP data as DataIndication, size: " << buffer->size(); +#endif +} + +SocketHelper::Ptr IceServer::allocateRelayed(const Pair::Ptr& pair) { + // DebugL; + + // only support udp + auto port = PortManager<0>::Instance().getSinglePort(); + std::string local_ip; + + GET_CONFIG_FUNC(std::vector, interfaces, Rtc::kInterfaces, [](string str) { + std::vector ret; + if (str.length()) { + ret = split(str, ","); + } + translateIPFromEnv(ret); + return ret; + }); + + //如果指定了对外的网卡,使用第一个对外网卡的ip + if (!interfaces.empty()) { + auto machine_interfaces = SockUtil::getInterfaceList(); + for (auto& obj : machine_interfaces) { + std::string& interface_ip = obj["ip"]; + if (toolkit::start_with(obj["name"], "lo") || interface_ip == "0.0.0.0" || interface_ip == "::") { + DebugL << "skip interace: " << obj["name"] << " " << interface_ip; + continue; + } + if (obj["name"] == interfaces.front()) { + DebugL << "use interace: " << obj["name"] << " " << interface_ip; + local_ip = interface_ip; + break; + } + } + } + + if (interfaces.empty() || local_ip.empty()){ + GET_CONFIG_FUNC(std::vector, extern_ips, Rtc::kExternIP, [](string str) { + std::vector ret; + if (str.length()) { + ret = split(str, ","); + } + translateIPFromEnv(ret); + return ret; + }); + + if (!extern_ips.empty()) { + local_ip = extern_ips.front(); + } else { + local_ip = SockUtil::get_local_ip(); + } + } + + auto socket = createRelayedUdpSocket(pair->get_peer_ip(), pair->get_peer_port(), local_ip, *port); + auto relayed_pair = std::make_shared(socket); + auto peer_addr = SockUtil::make_sockaddr(pair->get_peer_ip().data(), pair->get_peer_port()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + _relayed_pairs.emplace(peer_addr, std::make_pair(port, relayed_pair)); + _relayed_session.emplace(peer_addr, weak_self); + + InfoL << "Alloc relayed pair: " << relayed_pair->get_local_ip() << ":" << relayed_pair->get_local_port() + << " for peer pair: " << pair->get_peer_ip() << ":" << pair->get_peer_port(); + return socket; +} + +void IceServer::relayForwordingData(const toolkit::Buffer::Ptr& buffer, const sockaddr_storage& peer_addr) { + TraceL; + getPoller()->async([=]() { + auto it = _relayed_pairs.find(peer_addr); + if (it == _relayed_pairs.end()) { +#if 0 + //不是当前对象的转发,交给其他对象转发 + auto forword_it = _relayed_session.find(peer_addr); + if (forword_it == _relayed_session.end()) { + WarnL << "not relayed addr for peer addr: " << addrToStr(peer_addr); + } + + auto forword_session = forword_it->second.lock(); + if (!forword_session) { + WarnL << "forword session for peer addr " << addrToStr(peer_addr) << " is release"; + return; + } + + if (getIdentifier() == forword_session->getIdentifier()) { + //找到的会话就是当前会话,忽略 + return; + } + forword_session->relayForwordingData(buffer, peer_addr); + return; +#else + WarnL << "not relayed addr for peer addr: " << addrToStr(peer_addr); +#endif + } + + sendSocketData(buffer, it->second.second); +#if 0 + TraceL << "Forwarded ChannelData to peer: " << addrToStr(peer_addr); +#endif + }); +} + +void IceServer::relayBackingData(const toolkit::Buffer::Ptr& buffer, const Pair::Ptr& pair, const sockaddr_storage& peer_addr) { + // TraceL; + + sockaddr_storage addr; + pair->get_peer_addr(addr); + + auto it = _relayed_pairs.find(addr); + if (it == _relayed_pairs.end()) { + WarnL << "not relayed addr for peer addr: " << addrToStr(addr); + return; + } + + auto forward_pair = std::make_shared(it->second.second->_socket, + SockUtil::inet_ntoa((const struct sockaddr *)&peer_addr), SockUtil::inet_port((const struct sockaddr *)&peer_addr)); + + sendSocketData(buffer, forward_pair); +#if 0 + DebugL << "relay backing " << forward_pair->dumpString(1); +#endif +} + +SocketHelper::Ptr IceServer::createRelayedUdpSocket(const std::string &peer_host, uint16_t peer_port, const std::string &local_ip, uint16_t local_port) { + auto socket = std::make_shared(getPoller()); + + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + auto ptr = socket.get(); + socket->setOnRecvFrom([weak_self, ptr](const Buffer::Ptr &buffer, struct sockaddr *addr, int addr_len) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + auto peer_host = SockUtil::inet_ntoa(addr); + auto peer_port = SockUtil::inet_port(addr); + auto pair = std::make_shared(ptr->shared_from_this(), std::move(peer_host), peer_port); + strong_self->processRelayPacket(buffer, pair); + }); + + socket->setOnError([weak_self](const SockException &err) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + }); + + socket->setNetAdapter(local_ip); + socket->startConnect(peer_host, peer_port, local_port); + + return socket; +} + +//////////// IceAgent ////////////////////////// + +IceAgent::IceAgent(Listener* listener, Implementation implementation, Role role, std::string ufrag, std::string password, toolkit::EventPoller::Ptr poller) +: IceTransport(listener, std::move(ufrag), std::move(password), std::move(poller)), _implementation(implementation) ,_role(role) { + DebugL; + _tiebreaker = makeRandNum(); + // 创建定时器,每分钟检查一次权限和通道绑定是否需要刷新 + _refresh_timer = std::make_shared(60.0f, [this]() { + refreshPermissions(); + refreshChannelBindings(); + return true; + }, getPoller()); +} + +void IceAgent::gatheringCandidate(const CandidateTuple::Ptr& candidate_tuple, bool gathering_rflx, bool gathering_realy) { + // TraceL; + + auto interfaces = SockUtil::getInterfaceList(); + for (auto obj : interfaces) { + std::string local_ip = obj["ip"]; + if (toolkit::start_with(obj["name"], "lo") || local_ip == "0.0.0.0" || local_ip == "::") { + DebugL << "skip interace: " << obj["name"] << " " << local_ip; + continue; + } + + try { + CandidateInfo candidate; + candidate._type = CandidateInfo::AddressType::HOST; + candidate._ufrag = getUfrag(); + candidate._pwd = getPassword(); + candidate._transport = candidate_tuple->_transport; + + auto socket = createSocket(candidate_tuple->_transport, candidate_tuple->_addr._host, candidate_tuple->_addr._port, local_ip); + _socket_candidate_manager.addHostSocket(socket); + candidate._addr._host = candidate._base_addr._host = local_ip; + candidate._addr._port = candidate._base_addr._port = socket->get_local_port(); + + TraceL << "gathering local candidate " << candidate.dumpString() << " from stun server " << candidate_tuple->_addr.dumpString(); + + auto pair = std::make_shared(std::move(socket)); + onGatheringCandidate(pair, candidate); + if (gathering_rflx) { + gatheringSrflxCandidate(pair); + } + + if (gathering_realy) { + //TODO: 代优化relay_socket 复用host socket当前SocketCandidateManager数据结构不支持 + auto relay_socket = createSocket(candidate_tuple->_transport, candidate_tuple->_addr._host, candidate_tuple->_addr._port, local_ip); + _socket_candidate_manager.addRelaySocket(relay_socket); + gatheringRealyCandidate(std::make_shared(std::move(relay_socket))); + } + } catch (std::exception &ex) { + WarnL << ex.what(); + } + } +} + +void IceAgent::connectivityCheck(CandidateInfo& candidate) { + TraceL << candidate.dumpString(); + setState(IceAgent::State::Running); + auto ret = _remote_candidates.emplace(candidate); + if (ret.second) { + bool udp = candidate._transport == CandidateTuple::TransportType::UDP; + for (auto& socket : _socket_candidate_manager._host_sockets) { + if (udp != (socket->getSock()->sockType() == SockNum::Sock_UDP)) { + continue; + } + auto pair = std::make_shared(socket, candidate._addr._host, candidate._addr._port); + addToChecklist(pair, candidate); + } + + if (_socket_candidate_manager._has_relayed_candidate) { + localRelayedConnectivityCheck(candidate); + } + } + +} + +void IceAgent::localRelayedConnectivityCheck(CandidateInfo& candidate) { + TraceL << candidate.dumpString(); + for (auto &socket: _socket_candidate_manager._relay_sockets) { + auto local_relay_pair = std::make_shared(socket, _ice_server->_addr._host, _ice_server->_addr._port); + auto peer_addr = SockUtil::make_sockaddr(candidate._addr._host.data(), candidate._addr._port); + sendCreatePermissionRequest(local_relay_pair, peer_addr); + + local_relay_pair->_relayed_addr = std::make_shared(peer_addr); + addToChecklist(local_relay_pair, candidate); + } +} + +void IceAgent::nominated(const Pair::Ptr& pair, CandidateTuple& candidate) { + // TraceL; + auto handler = std::bind(&IceAgent::handleNominatedResponse, this, placeholders::_1, placeholders::_2, candidate); + sendBindRequest(pair, candidate, true, std::move(handler)); +} + +void IceAgent::sendSendIndication(const sockaddr_storage& peer_addr, const Buffer::Ptr& buffer, const Pair::Ptr& pair) { + // TraceL; + auto packet = std::make_shared(); + + auto attr_peer_address = std::make_shared(packet->getTransactionId()); + attr_peer_address->setAddr(peer_addr); + packet->addAttribute(std::move(attr_peer_address)); + + auto attr_data = std::make_shared(); + attr_data->setData(buffer->data(), buffer->size()); + packet->addAttribute(std::move(attr_data)); + + sendPacket(packet, pair); +} + +void IceAgent::gatheringSrflxCandidate(const Pair::Ptr& pair) { + // TraceL; + auto handle = std::bind(&IceAgent::handleGatheringCandidateResponse, this, placeholders::_1, placeholders::_2); + sendBindRequest(pair, std::move(handle)); +} + +void IceAgent::gatheringRealyCandidate(const Pair::Ptr &pair) { + // TraceL; + sendAllocateRequest(pair); +} + +void IceAgent::connectivityCheck(const Pair::Ptr &pair, CandidateTuple& candidate) { + // TraceL; + auto handler = std::bind(&IceAgent::handleConnectivityCheckResponse, this, placeholders::_1, placeholders::_2, candidate); + sendBindRequest(pair, candidate, false, std::move(handler)); +} + +void IceAgent::tryTriggerredCheck(const Pair::Ptr& pair) { + DebugL; + //FIXME 暂不实现,因为当前实现基本收到candidate就会发起check +} + +void IceAgent::sendBindRequest(const Pair::Ptr& pair, MsgHandler handler) { + // TraceL; + auto packet = std::make_shared(); + packet->setUfrag(_ufrag); + packet->setPassword(_password); + packet->setPeerUfrag(_ice_server->_ufrag); + packet->setPeerPassword(_ice_server->_pwd); + + packet->setNeedFingerprint(false); + packet->setNeedMessageIntegrity(false); + sendRequest(packet, pair, std::move(handler)); +} + +void IceAgent::sendBindRequest(const Pair::Ptr& pair, CandidateTuple& candidate, bool use_candidate, MsgHandler handler) { + // TraceL; + auto packet = std::make_shared(); + packet->setUfrag(_ufrag); + packet->setPassword(_password); + packet->setPeerUfrag(candidate._ufrag); + packet->setPeerPassword(candidate._pwd); + + auto attr_username = std::make_shared(); + attr_username->setUsername(candidate._ufrag + ":" + _ufrag); + packet->addAttribute(std::move(attr_username)); + + if (getRole() == Role::Controlling) { + auto attr_icecontrolling = std::make_shared(); + attr_icecontrolling->setTiebreaker(_tiebreaker); + packet->addAttribute(std::move(attr_icecontrolling)); + } else { + auto attr_icecontrolled = std::make_shared(); + attr_icecontrolled->setTiebreaker(_tiebreaker); + packet->addAttribute(std::move(attr_icecontrolled)); + } + + if (use_candidate) { + auto attr_use_candidate = std::make_shared(); + packet->addAttribute(std::move(attr_use_candidate)); + } + + if (candidate._priority != 0) { + auto attr_priority = std::make_shared(); + attr_priority->setPriority(candidate._priority); + packet->addAttribute(std::move(attr_priority)); + } + + sendRequest(packet, pair, std::move(handler)); +} + +void IceAgent::sendAllocateRequest(const Pair::Ptr& pair) { + // TraceL; + auto packet = std::make_shared(); + packet->setNeedMessageIntegrity(false); + packet->setUfrag(_ufrag); + packet->setPassword(_password); + packet->setPeerUfrag(_ice_server->_ufrag); + packet->setPeerPassword(_ice_server->_pwd); + + auto attr_username = std::make_shared(); + attr_username->setUsername(_ice_server->_ufrag); + packet->addAttribute(std::move(attr_username)); + + auto attr_requested_transport = std::make_shared(); + attr_requested_transport->setProtocol(StunAttrRequestedTransport::Protocol::UDP); + packet->addAttribute(std::move(attr_requested_transport)); + + auto handler = std::bind(&IceAgent::handleAllocateResponse, this, placeholders::_1, placeholders::_2); + sendRequest(packet, pair, std::move(handler)); +} + +void IceAgent::sendCreatePermissionRequest(const Pair::Ptr& pair, const sockaddr_storage& peer_addr) { + // TraceL; + + addPermission(peer_addr); + + auto packet = std::make_shared(); + packet->setUfrag(_ufrag); + packet->setPassword(_password); + packet->setPeerUfrag(_ice_server->_ufrag); + packet->setPeerPassword(_ice_server->_pwd); + + auto attr_username = std::make_shared(); + attr_username->setUsername(_ice_server->_ufrag); + packet->addAttribute(std::move(attr_username)); + auto attr_peer_address = std::make_shared(packet->getTransactionId()); + attr_peer_address->setAddr(peer_addr); + packet->addAttribute(std::move(attr_peer_address)); + + auto handler = std::bind(&IceAgent::handleCreatePermissionResponse, this, placeholders::_1, placeholders::_2, peer_addr); + sendRequest(packet, pair, std::move(handler)); +} + +void IceAgent::sendChannelBindRequest(const Pair::Ptr& pair, uint16_t channel_number, const sockaddr_storage& peer_addr) { + // TraceL; + auto packet = std::make_shared(); + packet->setUfrag(_ufrag); + packet->setPassword(_password); + packet->setPeerUfrag(_ice_server->_ufrag); + packet->setPeerPassword(_ice_server->_pwd); + + auto attr_username = std::make_shared(); + attr_username->setUsername(_ice_server->_ufrag); + packet->addAttribute(std::move(attr_username)); + + auto attr_channel_number = std::make_shared(); + attr_channel_number->setChannelNumber(channel_number); + packet->addAttribute(std::move(attr_channel_number)); + + auto attr_peer_address = std::make_shared(packet->getTransactionId()); + attr_peer_address->setAddr(peer_addr); + packet->addAttribute(std::move(attr_peer_address)); + + auto handler = std::bind(&IceAgent::handleChannelBindResponse, this, placeholders::_1, placeholders::_2, channel_number, peer_addr); + sendRequest(packet, pair, std::move(handler)); +} + +void IceAgent::processRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair) { + static toolkit::onceToken token([this]() { + _request_handlers.emplace(std::make_pair(StunPacket::Class::INDICATION, StunPacket::Method::DATA), std::bind(&IceAgent::handleDataIndication, this, placeholders::_1, placeholders::_2)); + }); + return IceTransport::processRequest(packet, pair); +} + +void IceAgent::handleBindingRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair) { + // TraceL; + auto controlling = packet->getAttribute(); + auto controlled = packet->getAttribute(); + auto priority = packet->getAttribute(); + + //角色冲突 + if (controlling && getRole() == Role::Controlling) { + if (controlling->getTiebreaker() > _tiebreaker) { + setRole(Role::Controlled); + InfoL << "role conflict, election fail, change role to controlled"; + } else { + InfoL << "role conflict, election success in controlling, send error"; + auto response = packet->createErrorResponse(StunAttrErrorCode::Code::RoleConflict); + response->setUfrag(_ufrag); + response->setPassword(_password); + auto attr_icecontrolling = std::make_shared(); + attr_icecontrolling->setTiebreaker(_tiebreaker); + response->addAttribute(std::move(attr_icecontrolling)); + return sendPacket(response, pair); + } + } else if (controlled && getRole() == Role::Controlled) { + if (controlled->getTiebreaker() > _tiebreaker) { + setRole(Role::Controlling); + InfoL << "rule conflict, election fail, change role to controlling"; + } else { + InfoL << "rule conflict, election success in controlled, send error"; + auto response = packet->createErrorResponse(StunAttrErrorCode::Code::RoleConflict); + response->setUfrag(_ufrag); + response->setPassword(_password); + auto attr_icecontrolled = std::make_shared(); + attr_icecontrolled->setTiebreaker(_tiebreaker); + response->addAttribute(std::move(attr_icecontrolled)); + return sendPacket(response, pair); + } + } + + auto response = packet->createSuccessResponse(); + response->setUfrag(_ufrag); + response->setPassword(_password); + + sockaddr_storage peer_addr; + if (!pair->get_relayed_addr(peer_addr)) { + pair->get_peer_addr(peer_addr); + } + + // Add XOR-MAPPED-ADDRESS. + auto attr_xor_mapped_address = std::make_shared(response->getTransactionId()); + attr_xor_mapped_address->setAddr(peer_addr); + response->addAttribute(std::move(attr_xor_mapped_address)); + + if (packet->hasAttribute(StunAttribute::Type::USE_CANDIDATE)) { + if (getRole() == Role::Controlled) { + _nominated_response = response; + onCompleted(pair); + } + } else { + sendPacket(response, pair); + tryTriggerredCheck(pair); + } + +} + +void IceAgent::handleGatheringCandidateResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair) { + // TraceL; + + if (StunPacket::Class::SUCCESS_RESPONSE != packet->getClass()) { + WarnL << "fail, get response: " << packet->dumpString(); + return; + } + + auto srflx = packet->getAttribute(); + if (!srflx) { + WarnL << "Binding request missing XOR_MAPPED_ADDRESS attribute"; + sendErrorResponse(packet, pair, StunAttrErrorCode::Code::BadRequest); + } + + CandidateInfo candidate; + candidate._type = CandidateInfo::AddressType::SRFLX; + candidate._addr._host = srflx->getIp(); + candidate._addr._port = srflx->getPort(); + candidate._base_addr._host = pair->get_local_ip(); + candidate._base_addr._port = pair->get_local_port(); + candidate._ufrag = getUfrag(); + candidate._pwd = getPassword(); + onGatheringCandidate(pair, candidate); +} + +void IceAgent::handleConnectivityCheckResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair, CandidateTuple& candidate) { + // TraceL; + + if (StunPacket::Class::SUCCESS_RESPONSE != packet->getClass()) { + WarnL << "fail, get response: " << packet->dumpString(); + if (packet->getErrorCode() == StunAttrErrorCode::Code::RoleConflict) { + InfoL << "process Role Conflict"; + + auto controlling = packet->getAttribute(); + auto controlled = packet->getAttribute(); + //角色冲突 + if (controlling && getRole() == Role::Controlling) { + if (controlling->getTiebreaker() > _tiebreaker) { + InfoL << "rule conflict, election fail, change role to controlled"; + setRole(Role::Controlled); + } else { + InfoL << "rule conflict, election success in controlling, skip"; + return; + } + } else if (controlled && getRole() == Role::Controlled) { + if (controlled->getTiebreaker() > _tiebreaker) { + InfoL << "rule conflict, election fail, change role to controlling"; + setRole(Role::Controlling); + } else { + InfoL << "rule conflict, election success in controlled, skip"; + return; + } + } + connectivityCheck(pair, candidate); + } + return; + } + + auto srflx = packet->getAttribute(); + if (!srflx) { + WarnL << "Binding request missing XOR_MAPPED_ADDRESS attribute"; + sendErrorResponse(packet, pair, StunAttrErrorCode::Code::BadRequest); + } + + if (!pair->_relayed_addr) { + //relay的消息不添加PRFLX candidaite + CandidateInfo preflx_candidate; + preflx_candidate._type = CandidateInfo::AddressType::PRFLX; + preflx_candidate._addr._host = srflx->getIp(); + preflx_candidate._addr._port = srflx->getPort(); + preflx_candidate._base_addr._host = pair->get_local_ip(); + preflx_candidate._base_addr._port = pair->get_local_port(); + preflx_candidate._ufrag = getUfrag(); + preflx_candidate._pwd = getPassword(); + onGatheringCandidate(pair, preflx_candidate); + } + + DebugL << "get candidate type preflx: " << srflx->getIp() << ":" << srflx->getPort(); + onConnected(pair); +} + +void IceAgent::handleNominatedResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair, CandidateTuple& candidate) { + // TraceL; + + if (StunPacket::Class::SUCCESS_RESPONSE != packet->getClass()) { + WarnL << "fail, get response: " << packet->dumpString(); + if (packet->getErrorCode() == StunAttrErrorCode::Code::RoleConflict) { + //角色冲突 + InfoL << "process Role Conflict"; + auto controlling = packet->getAttribute(); + auto controlled = packet->getAttribute(); + if (controlling && getRole() == Role::Controlling) { + if (controlling->getTiebreaker() > _tiebreaker) { + InfoL << "rule conflict, election fail, change role to controlled"; + setRole(Role::Controlled); + return; + } else { + InfoL << "rule conflict, election success in controlling, skip"; + return; + } + } else if (controlled && getRole() == Role::Controlled) { + if (controlled->getTiebreaker() > _tiebreaker) { + InfoL << "rule conflict, election fail, change role to controlling"; + setRole(Role::Controlling); + } else { + InfoL << "rule conflict, election success in controlled, skip"; + return; + } + } + nominated(pair, candidate); + return; + } + } + + auto srflx = packet->getAttribute(); + if (!srflx) { + WarnL << "Binding request missing XOR_MAPPED_ADDRESS attribute"; + sendErrorResponse(packet, pair, StunAttrErrorCode::Code::BadRequest); + } + + onCompleted(pair); +} + +void IceAgent::handleAllocateResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair) { + // TraceL; + + if (StunPacket::Class::SUCCESS_RESPONSE != packet->getClass()) { + WarnL << "fail, get response: " << packet->dumpString() << ", errorCode: " << (uint16_t)packet->getErrorCode(); + if (packet->getErrorCode() == StunAttrErrorCode::Code::AllocationQuotaReached) { + InfoL << "use stun retry"; + } + return; + } + + auto srflx = packet->getAttribute(); + if (!srflx) { + WarnL << "Binding request missing XOR_MAPPED_ADDRESS attribute"; + sendErrorResponse(packet, pair, StunAttrErrorCode::Code::BadRequest); + } + +#if 0 + CandidateInfo candidate; + candidate._type = CandidateInfo::AddressType::SRFLX; + candidate._addr._host = srflx->getAddrString(); + candidate._addr._port = srflx->getPort(); + candidate._base_addr._host = pair->get_local_ip(); + candidate._base_addr._port = pair->get_local_port(); + candidate._ufrag = getUfrag(); + candidate._pwd = getPassword(); + onGatheringCandidate(pair, candidate); +#endif + + auto relay = packet->getAttribute(); + if (!relay) { + WarnL << "Binding request missing XOR_RELAYED_ADDRESS attribute"; + sendErrorResponse(packet, pair, StunAttrErrorCode::Code::BadRequest); + } + + CandidateInfo candidate; + candidate._type = CandidateInfo::AddressType::RELAY; + candidate._addr._host = relay->getIp(); + candidate._addr._port = relay->getPort(); + candidate._base_addr._host = relay->getIp(); + candidate._base_addr._port = relay->getPort(); + candidate._ufrag = getUfrag(); + candidate._pwd = getPassword(); + + TraceL << "get local candidate type " << candidate.dumpString() + << ", by srflx addr " << srflx->getIp() << " : " << srflx->getPort() + << ", by host addr " << pair->get_local_ip() << " : " << pair->get_local_port(); + onGatheringCandidate(pair, candidate); +} + +void IceAgent::handleCreatePermissionResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair, const sockaddr_storage& peer_addr) { + // TraceL; + + if (StunPacket::Class::SUCCESS_RESPONSE != packet->getClass()) { + WarnL << "CreatePermission failed, response: " << packet->dumpString(); + return; + } + + // TraceL << "CreatePermission successfully"; + + static uint16_t next_channel = 0x4000; // 有效范围是 0x4000-0x7FFF + uint16_t channel_number = next_channel++; + if (next_channel > 0x7FFF) { + next_channel = 0x4000; // 循环使用通道号 + } + + sendChannelBindRequest(pair, channel_number, peer_addr); +} + +void IceAgent::handleChannelBindResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair, uint16_t channel_number, const sockaddr_storage& peer_addr) { + // TraceL; + + if (StunPacket::Class::SUCCESS_RESPONSE != packet->getClass()) { + WarnL << "ChannelBind failed, response: " << packet->dumpString(); + return; + } + + InfoL << "ChannelBind success, channel_number=" << channel_number + << ", peer_addr=" << addrToStr(peer_addr) + << ", pair: " << pair->dumpString(2); + + addChannelBind(channel_number, peer_addr); +} + +void IceAgent::handleDataIndication(const StunPacket::Ptr& packet, const Pair::Ptr& pair) { + // TraceL; + + // 检查必要的属性 + auto peer_addr = packet->getAttribute(); + auto data = packet->getAttribute(); + + if (!peer_addr || !data) { + WarnL << "Data indication missing required attributes"; + return; + } + + // 获取对端地址 + auto addr = peer_addr->getAddr(); + + // 检查是否有对应peer地址的权限 + if (!hasPermission(addr)) { + WarnL << "No permission exists for peer address"; + return; + } + + // 获取数据 + auto buffer = data->getData(); + + // 创建一个新的缓冲区 + auto recv_buffer = BufferRaw::create(buffer.size()); + recv_buffer->assign(buffer.data(), buffer.size()); + + DebugL << "Received Data indication from peer: " << addrToStr(addr) << ", size: " << buffer.size(); + + // 通知上层收到数据 + pair->_relayed_addr = std::make_shared(); + memcpy(pair->_relayed_addr.get(), &addr, sizeof(addr)); + _listener->onIceTransportRecvData(recv_buffer, pair); +} + +void IceAgent::handleChannelData(uint16_t channel_number, const char* data, size_t len, const Pair::Ptr& pair) { + // TraceL << "Received ChannelData message, channel number: " << channel_number; + + // 查找该通道号对应的目标地址 + auto it = _channel_bindings.find(channel_number); + if (it == _channel_bindings.end()) { + WarnL << "No binding found for channel number: " << channel_number; + return; + } + + // 获取目标地址 + sockaddr_storage addr = it->second; + + // 创建一个新的缓冲区用于转发 + auto buffer = BufferRaw::create(len); + buffer->assign(data, len); + + auto channel_pair = std::make_shared(*pair); + channel_pair->_relayed_addr = std::make_shared(); + memcpy(channel_pair->_relayed_addr.get(), &addr, sizeof(addr)); + _listener->onIceTransportRecvData(buffer, channel_pair); +} + +void IceAgent::onGatheringCandidate(const Pair::Ptr& pair, CandidateInfo& candidate) { + candidate._priority = calIceCandidatePriority(candidate._type); + InfoL << "got candidate " << candidate.dumpString(); + + // 使用_socket_candidate_manager替代_local_candidates进行5元组重复检查 + if (!_socket_candidate_manager.addMapping(pair->_socket, candidate)) { + InfoL << "has same 5 tuple, skip"; + return; + } + + _listener->onIceTransportGatheringCandidate(pair, candidate); + + //如果是REALY,当前的所有PEER Candidate进行CreatePermission + if (candidate._type == CandidateInfo::AddressType::RELAY) { + _socket_candidate_manager._has_relayed_candidate = true; + for (auto remote_candidate : _remote_candidates) { + localRelayedConnectivityCheck(remote_candidate); + } + } +} + +void IceAgent::onConnected(const IceTransport::Pair::Ptr& pair) { + DebugL << "get connectivity check pair: " << pair->dumpString(2); + + if (getState() != State::Running) { + InfoL << "ice state: "<< stateToString(getState()) << " is not running, skip"; + return; + } + + TraceL << "checklist size: " << _check_list.size(); + //判断ConnectivityCheck的pair是否在checklist中,存在的话加入到validlist + for (auto &candidate_pair : _check_list) { + auto &pair_it = candidate_pair->_local_pair; + auto &remote_candidate = candidate_pair->_remote_candidate; + auto &state = candidate_pair->_state; + + TraceL << "check pair " << candidate_pair->dumpString() << ", pair info: " << pair_it->dumpString(2); + + //即使是新的Peer 反射地址,也已经添加到_checklist中了 + //所以肯定会在_checklist中找到匹配项 + if (!Pair::is_same(pair_it.get(), pair.get())) { + continue; + } + + if (state == CandidateInfo::State::Frozen || state == CandidateInfo::State::Waiting) { + continue; + } + + state = CandidateInfo::State::Succeeded; + + // 检查ICE传输策略 + if (!checkIceTransportPolicy(*candidate_pair, pair)) { + return; + } + + InfoL << "push " << candidate_pair->dumpString() << " to valid_list"; + + // 直接将候选者对添加到valid_list + _valid_list.push_back(candidate_pair); + + if (getRole() == Role::Controlling) { + if (getState() != IceAgent::State::Nominated && getState() != IceAgent::State::Completed) { + //TODO:need process priority + setState(IceAgent::State::Nominated); + nominated(pair, remote_candidate); + } + } + } + + if (getRole() == Role::Controlled && _nominated_pair) { + onCompleted(_nominated_pair); + } +} + +void IceAgent::onCompleted(const IceTransport::Pair::Ptr& pair) { + // TraceL; + bool found_in_valid_list = false; + if (getImplementation() == Implementation::Full) { + for (auto &candidate_pair : _valid_list) { + auto &pair_it = candidate_pair->_local_pair; + if (Pair::is_same(pair_it.get(), pair.get())) { + candidate_pair->_nominated = true; + _select_candidate_pair = candidate_pair; + InfoL << "select pair: " << candidate_pair->dumpString(); + found_in_valid_list = true; + break; + } + } + + if (!found_in_valid_list) { + InfoL << "not found peer pair: ip: " << pair->get_peer_ip() << ", port: " << pair->get_peer_port() << "in valid_list, record first"; + //提名的candidate 未在_valid_list 中找到.先记录 + _nominated_pair = pair; + } + } else { + //Lite 模式,不做candidate校验逻辑 + found_in_valid_list = true; + } + + if (found_in_valid_list) { + + if (setSelectedPair(pair)) { + + if (getState() != IceAgent::State::Completed) { + setState(IceAgent::State::Completed); + } + + _listener->onIceTransportCompleted(); + _nominated_pair = nullptr; + } + + if (_nominated_response) { + sendPacket(_nominated_response, pair); + _nominated_response = nullptr; + } + } +} + +void IceAgent::refreshPermissions() { + if (!_ice_server || _ice_server->_schema != IceServerInfo::SchemaType::TURN) { + return; + } + + uint64_t now = toolkit::getCurrentMillisecond(); + + // 遍历所有权限,删除过期的权限 + for (auto it = _permissions.begin(); it != _permissions.end();) { + if (now - it->second > 5 * 60 * 1000) { + it = _permissions.erase(it); + } else { + ++it; + } + } + + // 对于 + for (auto& permission : _permissions) { + if (now - permission.second > 4 * 60 * 1000) { + // 创建一个新的权限请求 + sockaddr_storage addr = permission.first; + for (auto& socket : _socket_candidate_manager._relay_sockets) { + auto pair = std::make_shared(socket); + sendCreatePermissionRequest(pair, addr); + break; // 只需要使用一个本地候选项发送请求 + } + } + } +} + +void IceAgent::refreshChannelBindings() { + if (!_ice_server || _ice_server->_schema != IceServerInfo::SchemaType::TURN) { + return; + } + uint64_t now = toolkit::getCurrentMillisecond(); + + // 遍历所有通道绑定,删除过期的绑定 + for (auto it = _channel_binding_times.begin(); it != _channel_binding_times.end();) { + if (now - it->second > 10 * 60 * 1000) { // 通道绑定有效期为10分钟 + _channel_bindings.erase(it->first); + it = _channel_binding_times.erase(it); + } else { + ++it; + } + } + + // 对于即将过期的通道绑定(例如还有2分钟过期),刷新它们 + for (auto& binding_time : _channel_binding_times) { + if (now - binding_time.second > 8 * 60 * 1000) { + uint16_t channel_number = binding_time.first; + auto it = _channel_bindings.find(channel_number); + if (it != _channel_bindings.end()) { + sockaddr_storage addr = it->second; + for (auto& socket : _socket_candidate_manager._relay_sockets) { + auto pair = std::make_shared(socket); + sendChannelBindRequest(pair, channel_number, addr); + break; // 只需要使用一个本地候选项发送请求 + } + } + } + } +} + +bool IceAgent::setSelectedPair(const Pair::Ptr& pair) { + if (_selected_pair && Pair::is_same(pair.get(), _selected_pair.get())){ + return false; + } + + if (_selected_pair) { + InfoL << "Previous selected_pair: " << _selected_pair->dumpString(2); + InfoL << "New selected_pair: " << pair->dumpString(2); + } else { + InfoL << "Initial selected_pair: " << pair->dumpString(2); + } + + _last_selected_pair = std::static_pointer_cast(_selected_pair); + _selected_pair = pair; + return true; +} + +void IceAgent::removePair(const toolkit::SocketHelper *socket) { + // TODO +} + +std::vector IceAgent::getPairs() const { + // TODO + if (_selected_pair) { + return { _selected_pair }; + } + return {}; +} + +void IceAgent::sendSocketData(const Buffer::Ptr& buf, const Pair::Ptr& pair, bool flush) { + auto use_pair = pair? pair : getSelectedPair(); + + if (use_pair == nullptr) { + WarnL << "pair should not be nullptr"; + return; + } + + if (use_pair->_relayed_addr) { + return sendRealyPacket(buf, use_pair, flush); + } + return sendSocketData_l(buf, use_pair, flush); +} + +void IceAgent::sendRealyPacket(const Buffer::Ptr& buffer, const Pair::Ptr& pair, bool flush) { + // TraceL; + auto forward_pair = std::make_shared(*pair); + auto peer_addr = std::move(forward_pair->_relayed_addr); + forward_pair->_relayed_addr = nullptr; + + if (!hasPermission(*peer_addr)) { + WarnL << "No permission exists for peer: " << addrToStr(*peer_addr); + return; + } + + uint16_t channel_number; + if (hasChannelBind(*peer_addr, channel_number)) { + sendChannelData(channel_number, buffer, forward_pair); + } else { + sendSendIndication(*peer_addr, buffer, forward_pair); + } +} + +CandidateInfo IceAgent::getLocalCandidateInfo(const Pair::Ptr& pair) { + // 从socket_candidate_manager中查找对应的本地候选者信息 + for (const auto& socket_candidates : _socket_candidate_manager.socket_to_candidates) { + if (socket_candidates.first == pair->_socket) { + // 找到对应socket的候选者列表,选择第一个(host类型)作为默认 + if (!socket_candidates.second.empty()) { + return socket_candidates.second[0]; + } + } + } + + throw std::invalid_argument("No candidate found for the specified socket pair"); +} + +void IceAgent::addToChecklist(const Pair::Ptr& pair, CandidateInfo& remote_candidate) { + try { + //TODO: 优化checklist + CandidateInfo local_candidate = getLocalCandidateInfo(pair); + auto candidate_pair = std::make_shared(std::make_shared(*pair), remote_candidate, local_candidate); + candidate_pair->_state = CandidateInfo::State::InProgress; + _check_list.push_back(candidate_pair); + + std::sort(_check_list.begin(), _check_list.end(), [] ( + const std::shared_ptr& a, const std::shared_ptr& b) { + return *a < *b; + }); + + InfoL << "connectivity check candidate pair " << candidate_pair->dumpString() << ", pair info: " << pair->dumpString(2); + + connectivityCheck(std::make_shared(*pair), remote_candidate); + } catch (std::exception &ex) { + WarnL << ex.what(); + } +} + +void IceTransport::checkRequestTimeouts() { + uint64_t now = toolkit::getCurrentMillisecond(); + GET_CONFIG(uint32_t, max_retry, kMaxStunRetry); + for (auto it = _response_handlers.begin(); it != _response_handlers.end();) { + auto& transaction_id = it->first; + auto& req_info = it->second; + + // 检查是否超时 + if (now >= req_info._next_timeout) { + if (req_info._retry_count >= max_retry) { + // 超过最大重传次数,放弃请求并清理 + WarnL << "STUN request timeout after " << max_retry + << " retries, transaction_id: " << hexdump(transaction_id.data(), transaction_id.size()); + it = _response_handlers.erase(it); + continue; + } else { + // 执行重传 + retransmitRequest(transaction_id, req_info); + } + } + ++it; + } +} + +void IceTransport::retransmitRequest(const std::string& transaction_id, RequestInfo& req_info) { + // 增加重传次数 + req_info._retry_count++; + + // RTO翻倍(指数退避) + req_info._rto *= 2; + + // 计算下次超时时间 + uint64_t now = toolkit::getCurrentMillisecond(); + req_info._next_timeout = now + req_info._rto; + +#if 0 + TraceL << "Retransmitting STUN request (attempt " << req_info._retry_count + << "/" << RequestInfo::MAX_RETRIES << "), RTO: " << req_info._rto + << "ms, transaction_id: " << hexdump(transaction_id.data(), transaction_id.size()); +#endif + + // 重新发送请求包 + sendPacket(req_info._request, req_info._pair); +} + +Json::Value IceAgent::getChecklistInfo() const { + Json::Value result; + + Json::Value local_candidates_array(Json::arrayValue); + auto all_local_candidates = _socket_candidate_manager.getAllCandidates(); + for (const auto& local_candidate : all_local_candidates) { + Json::Value candidate_info; + candidate_info["type"] = CandidateInfo::getAddressTypeStr(local_candidate._type); + candidate_info["host"] = local_candidate._addr._host; + candidate_info["port"] = local_candidate._addr._port; + candidate_info["priority"] = local_candidate._priority; + if (!local_candidate._base_addr._host.empty()) { + candidate_info["base_host"] = local_candidate._base_addr._host; + candidate_info["base_port"] = local_candidate._base_addr._port; + } + local_candidates_array.append(candidate_info); + } + result["local_candidates"] = local_candidates_array; + result["local_candidates_count"] = static_cast(all_local_candidates.size()); + + Json::Value remote_candidates_array(Json::arrayValue); + for (const auto& remote_candidate : _remote_candidates) { + Json::Value candidate_info; + candidate_info["type"] = CandidateInfo::getAddressTypeStr(remote_candidate._type); + candidate_info["host"] = remote_candidate._addr._host; + candidate_info["port"] = remote_candidate._addr._port; + candidate_info["priority"] = remote_candidate._priority; + if (!remote_candidate._base_addr._host.empty()) { + candidate_info["base_host"] = remote_candidate._base_addr._host; + candidate_info["base_port"] = remote_candidate._base_addr._port; + } + remote_candidates_array.append(candidate_info); + } + result["remote_candidates"] = remote_candidates_array; + result["remote_candidates_count"] = static_cast(_remote_candidates.size()); + + Json::Value checklist_array(Json::arrayValue); + for (const auto& candidate_pair : _check_list) { + Json::Value entry; + entry["candidate_pair"] = candidate_pair->_local_candidate.dumpString() + " <-> " + candidate_pair->_remote_candidate.dumpString(); + entry["state"] = CandidateInfo::getStateStr(candidate_pair->_state); + entry["priority"] = (Json::UInt64)candidate_pair->_priority; + entry["nominated"] = candidate_pair->_nominated; + checklist_array.append(entry); + } + + result["checklists"] = checklist_array; + result["checklists_count"] = (int)_check_list.size(); + result["ice_state"] = stateToString(_state); + + if (_selected_pair) { + Json::Value selected_pair; + selected_pair["local_addr"] = _selected_pair->get_local_ip() + ":" + std::to_string(_selected_pair->get_local_port()); + selected_pair["remote_addr"] = _selected_pair->get_peer_ip() + ":" + std::to_string(_selected_pair->get_peer_port()); + if (!_selected_pair->get_relayed_ip().empty()) { + selected_pair["relayed_addr"] = _selected_pair->get_relayed_ip() + ":" + std::to_string(_selected_pair->get_relayed_port()); + } + + if (_select_candidate_pair) { + selected_pair["candidate_pair"] = _select_candidate_pair->_local_candidate.dumpString() + " <-> " + _select_candidate_pair->_remote_candidate.dumpString(); + } + + result["selected_pair"] = selected_pair; + } else { + result["selected_pair"] = Json::nullValue; + } + return result; +} + +} // namespace RTC diff --git a/webrtc/IceTransport.hpp b/webrtc/IceTransport.hpp new file mode 100644 index 00000000..6e794abe --- /dev/null +++ b/webrtc/IceTransport.hpp @@ -0,0 +1,754 @@ +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. +*/ + +#ifndef ZLMEDIAKIT_WEBRTC_ICE_TRANSPORT_HPP +#define ZLMEDIAKIT_WEBRTC_ICE_TRANSPORT_HPP + +#include +#include +#include +#include +#include +#include +#include +#include "json/json.h" +#include "Util/Byte.hpp" +#include "Poller/Timer.h" +#include "Poller/EventPoller.h" +#include "Network/Socket.h" +#include "Network/UdpClient.h" +#include "Network/Session.h" +#include "logger.h" +#include "StunPacket.hpp" + +namespace RTC { + +uint64_t calCandidatePairPriority(uint32_t G, uint32_t D); + +class CandidateAddr { +public: + + bool operator==(const CandidateAddr& rhs) const { + return ((_host == rhs._host) && (_port == rhs._port)); + } + + bool operator!=(const CandidateAddr& rhs) const { + return !(*this == rhs); + } + + std::string dumpString() const { + return _host + ":" + std::to_string(_port); + } + +public: + std::string _host; + uint16_t _port = 0; +}; + + +class CandidateTuple { +public: + using Ptr = std::shared_ptr; + CandidateTuple() = default; + virtual ~CandidateTuple() = default; + + enum class AddressType { + HOST = 1, + SRFLX, //server reflexive + PRFLX, //peer reflexive + RELAY, + }; + + enum class SecureType { + NOT_SECURE = 1, + SECURE, + }; + + enum class TransportType { + UDP = 1, + TCP, + }; + + bool operator<(const CandidateTuple& rhs) const { + return (_priority < rhs._priority); + } + + bool operator==(const CandidateTuple& rhs) const { + return ((_addr == rhs._addr) + && (_priority == rhs._priority) + && (_transport == rhs._transport) && (_secure == rhs._secure)); + } + + struct ClassHash { + std::size_t operator()(const CandidateTuple& t) const { + std::string str = t._addr._host + std::to_string(t._addr._port) + + std::to_string((uint32_t)t._transport) + std::to_string((uint32_t)t._secure); + return std::hash()(str); + } + }; + + struct ClassEqual { + bool operator()(const CandidateTuple& a, const CandidateTuple& b) const { + return a == b; + } + }; + +public: + CandidateAddr _addr; + uint32_t _priority = 0; + TransportType _transport = TransportType::UDP; + SecureType _secure = SecureType::NOT_SECURE; + std::string _ufrag; + std::string _pwd; +}; + +class CandidateInfo : public CandidateTuple { +public: + using Ptr = std::shared_ptr; + CandidateInfo() = default; + virtual ~CandidateInfo() = default; + + enum class AddressType { + INVALID = 0, + HOST = 1, + SRFLX, // server reflx + PRFLX, // peer reflx + RELAY, + }; + + enum class State { + Frozen = 1, //尚未check,并还不需要check + Waiting, //尚未发送check,但也不是Frozen + InProgress, //已经发起check,但是仍在进行中 + Succeeded, //check success + Failed, //check failed + }; + + bool operator==(const CandidateInfo& rhs) const { + return CandidateTuple::operator==(rhs) && (_type == rhs._type); + } + + std::string getAddressTypeStr() const { + return getAddressTypeStr(_type); + } + + // 获取候选者地址类型字符串的静态函数 + static std::string getAddressTypeStr(CandidateInfo::AddressType type) { + switch (type) { + case CandidateInfo::AddressType::HOST: return "host"; + case CandidateInfo::AddressType::SRFLX: return "srflx"; + case CandidateInfo::AddressType::PRFLX: return "reflx"; + case CandidateInfo::AddressType::RELAY: return "relay"; + default: return "invalid"; + } + } + + static std::string getStateStr(State state) { + switch (state) { + case State::Frozen: return "frozen"; + case State::Waiting: return "waiting"; + case State::InProgress: return "in_progress"; + case State::Succeeded: return "succeeded"; + case State::Failed: return "failed"; + default: break; + } + return "unknown"; + } + + std::string dumpString() const { + return getAddressTypeStr() + " " + _addr.dumpString(); + } + +public: + AddressType _type = AddressType::HOST; + CandidateAddr _base_addr; +}; + +// ice stun/turn服务器配置 +// 格式为: (stun/turn)[s]:host:port[?transport=(tcp/udp)], 默认udp模式 +// 例如: +// stun:stun.l.google.com:19302 → 谷歌的 STUN 服务器(UDP)。 +// turn:turn.example.com:3478?transport=tcp → 使用 TCP 的 TURN 服务器。 +// turns:turn.example.com:5349 → 使用 TLS 的 TURN 服务器。 +class IceServerInfo : public CandidateTuple { +public: + using Ptr = std::shared_ptr; + IceServerInfo() = default; + virtual ~IceServerInfo() = default; + IceServerInfo(const std::string &url) { parse(url); } + void parse(const std::string &url); + + enum class SchemaType { + TURN = 1, + STUN, + }; + +public: + std::string _full_url; + std::string _param_strs; + SchemaType _schema = SchemaType::TURN; +}; + +class IceTransport : public std::enable_shared_from_this { +public: + using Ptr = std::shared_ptr; + + class Pair { + public: + using Ptr = std::shared_ptr; + + Pair() = default; + Pair(toolkit::SocketHelper::Ptr socket) : _socket(std::move(socket)) {} + Pair(toolkit::SocketHelper::Ptr socket, std::string peer_host, uint16_t peer_port, + std::shared_ptr relayed_addr = nullptr) : + _socket(std::move(socket)), _peer_host(std::move(peer_host)), _peer_port(peer_port), _relayed_addr(std::move(relayed_addr)) { + } + + Pair(Pair &that) { + _socket = that._socket; + _peer_host = that._peer_host; + _peer_port = that._peer_port; + _relayed_addr = nullptr; + if (that._relayed_addr) { + _relayed_addr = std::make_shared(); + memcpy(_relayed_addr.get(), that._relayed_addr.get(), sizeof(sockaddr_storage)); + } + } + virtual ~Pair() = default; + + void get_peer_addr(sockaddr_storage &peer_addr) const { + if (!_peer_host.empty()) { + peer_addr = toolkit::SockUtil::make_sockaddr(_peer_host.data(), _peer_port); + } else { + auto addr = _socket->get_peer_addr(); + if (addr->sa_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)addr)->sin6_addr)) { + memset(&peer_addr, 0, sizeof(peer_addr)); + // 转换IPv6 v4mapped地址为IPv4地址 + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr; + struct sockaddr_in *addr4 = (struct sockaddr_in *)&peer_addr; + addr4->sin_family = AF_INET; + addr4->sin_port = addr6->sin6_port; + memcpy(&addr4->sin_addr, &addr6->sin6_addr.s6_addr[12], 4); + } else { + memcpy(&peer_addr, addr, toolkit::SockUtil::get_sock_len(addr)); + } + } + } + + bool get_relayed_addr(sockaddr_storage &peerAddr) const { + if (!_relayed_addr) { + return false; + } + + memcpy(&peerAddr, _relayed_addr.get(), sizeof(peerAddr)); + return true; + } + + std::string get_local_ip() const { return _socket->get_local_ip(); } + + uint16_t get_local_port() const { return _socket->get_local_port(); } + + std::string get_peer_ip() const { return !_peer_host.empty() ? _peer_host : _socket->get_peer_ip(); } + + uint16_t get_peer_port() const { return !_peer_host.empty() ? _peer_port : _socket->get_peer_port(); } + + + std::string get_relayed_ip() const { return _relayed_addr ? toolkit::SockUtil::inet_ntoa((const struct sockaddr *)_relayed_addr.get()) : ""; } + + uint16_t get_relayed_port() const { return _relayed_addr ? toolkit::SockUtil::inet_port((const struct sockaddr *)_relayed_addr.get()) : 0; } + + static bool is_same_relayed_addr(Pair *a, Pair *b) { + if (a->_relayed_addr && b->_relayed_addr) { + return toolkit::SockUtil::is_same_addr( + reinterpret_cast(a->_relayed_addr.get()), reinterpret_cast(b->_relayed_addr.get())); + } + return (a->_relayed_addr == b->_relayed_addr); + } + + static bool is_same(Pair* a, Pair* b) { + // FIXME: a->_socket == b->_socket条件成立后,后面get_peer_ip和get_peer_port一定相同 + if ((a->_socket == b->_socket) + && (a->get_peer_ip() == b->get_peer_ip()) + && (a->get_peer_port() == b->get_peer_port()) + && (is_same_relayed_addr(a, b))) { + return true; + } + return false; + } + + std::string dumpString(uint8_t flag) const { + toolkit::_StrPrinter sp; + static const char* fStr[] = { "<-", "->", "<->" }; + sp << (_socket ? (_socket->getSock()->sockType() == toolkit::SockNum::Sock_TCP ? "tcp " : "udp ") : "") + << get_local_ip() << ":" << get_local_port() << fStr[flag] << get_peer_ip() << ":" << get_peer_port(); + if (_relayed_addr && flag == 2) { + sp << " relay " << get_relayed_ip() << ":" << get_relayed_port(); + } + return sp; + } + public: + toolkit::SocketHelper::Ptr _socket; + //对端host:port 地址,因为多个pair会复用一个socket对象,因此可能会和_socket的创建bind信息不一致 + std::string _peer_host; + uint16_t _peer_port; + + //中继后地址,用于实现TURN转发地址,当该地址不为空时,该地址为真正的peer地址,_peer_host和_peer_port表示中继地址 + std::shared_ptr _relayed_addr; + }; + + class Listener { + public: + virtual ~Listener() = default; + + public: + virtual void onIceTransportRecvData(const toolkit::Buffer::Ptr& buffer, const Pair::Ptr& pair) = 0; + virtual void onIceTransportGatheringCandidate(const Pair::Ptr&, const CandidateInfo&) = 0; + virtual void onIceTransportDisconnected() = 0; + virtual void onIceTransportCompleted() = 0; + }; + +public: + using MsgHandler = std::function; + + struct RequestInfo { + StunPacket::Ptr _request; // 原始请求包 + MsgHandler _handler; // 响应处理函数 + Pair::Ptr _pair; // 发送对 + uint64_t _send_time; // 首次发送时间(毫秒) + uint64_t _next_timeout; // 下次超时时间(毫秒) + uint32_t _retry_count; // 当前重传次数 + uint32_t _rto = 500; // 当前RTO值(毫秒) 初始RTO 500ms + + RequestInfo(StunPacket::Ptr req, MsgHandler h, Pair::Ptr p) + : _request(std::move(req)) + , _handler(std::move(h)) + , _pair(std::move(p)) + , _retry_count(0) { + _send_time = toolkit::getCurrentMillisecond(); + _next_timeout = _send_time + _rto; + } + }; + + IceTransport(Listener* listener, std::string ufrag, std::string password, toolkit::EventPoller::Ptr poller); + virtual ~IceTransport() {} + + virtual void initialize(); + + const toolkit::EventPoller::Ptr& getPoller() const { return _poller; } + const std::string& getIdentifier() const { return _identifier; } + + const std::string& getUfrag() const { return _ufrag; } + const std::string& getPassword() const { return _password; } + void setUfrag(std::string ufrag) { _ufrag = std::move(ufrag); } + void setPassword(std::string password) { _password = std::move(password); } + + virtual bool processSocketData(const uint8_t* data, size_t len, const Pair::Ptr& pair); + virtual void sendSocketData(const toolkit::Buffer::Ptr& buf, const Pair::Ptr& pair, bool flush = true); + void sendSocketData_l(const toolkit::Buffer::Ptr& buf, const Pair::Ptr& pair, bool flush = true); + +protected: + virtual void processStunPacket(const StunPacket::Ptr& packet, const Pair::Ptr& pair); + virtual void processRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair); + virtual void processResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair); + virtual bool processChannelData(const uint8_t* data, size_t len, const Pair::Ptr& pair); + virtual StunPacket::Authentication checkRequestAuthentication(const StunPacket::Ptr& packet, const Pair::Ptr& pair); + StunPacket::Authentication checkResponseAuthentication(const StunPacket::Ptr& request, const StunPacket::Ptr& packet, const Pair::Ptr& pair); + void processUnauthorizedResponse(const StunPacket::Ptr& response, const StunPacket::Ptr& request, const Pair::Ptr& pair, MsgHandler handler); + virtual void handleBindingRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair); + virtual void handleChannelData(uint16_t channel_number, const char* data, size_t len, const Pair::Ptr& pair) {}; + + void sendChannelData(uint16_t channel_number, const toolkit::Buffer::Ptr &buffer, const Pair::Ptr& pair); + virtual void sendUnauthorizedResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair); + void sendErrorResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair, StunAttrErrorCode::Code errorCode); + void sendRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair, MsgHandler handler); + void sendPacket(const StunPacket::Ptr& packet, const Pair::Ptr& pair); + + // For permissions + bool hasPermission(const sockaddr_storage& addr); + void addPermission(const sockaddr_storage& addr); + + // For Channel Bind + bool hasChannelBind(uint16_t channel_number); + bool hasChannelBind(const sockaddr_storage& addr, uint16_t& channel_number); + void addChannelBind(uint16_t channel_number, const sockaddr_storage& addr); + + toolkit::SocketHelper::Ptr createSocket(CandidateTuple::TransportType type, const std::string &peer_host, uint16_t peer_port, const std::string &local_ip, uint16_t local_port = 0); + toolkit::SocketHelper::Ptr createUdpSocket(const std::string &target_host, uint16_t peer_port, const std::string &local_ip, uint16_t local_port); + + void checkRequestTimeouts(); + void retransmitRequest(const std::string& transaction_id, RequestInfo& req_info); + +protected: + std::string _identifier; + toolkit::EventPoller::Ptr _poller; + Listener* _listener = nullptr; + std::unordered_map _response_handlers; + std::unordered_map, MsgHandler, StunPacket::ClassMethodHash> _request_handlers; + + // for local + std::string _ufrag; + std::string _password; + + // For permissions + std::unordered_map _permissions; + + // For Channel Bind + std::unordered_map _channel_bindings; + std::unordered_map _channel_binding_times; + + // For STUN request retry + std::shared_ptr _retry_timer; +}; + +class IceServer : public IceTransport { +public: + using Ptr = std::shared_ptr; + using WeakPtr = std::weak_ptr; + IceServer(Listener* listener, std::string ufrag, std::string password, toolkit::EventPoller::Ptr poller); + virtual ~IceServer() {} + + bool processSocketData(const uint8_t* data, size_t len, const Pair::Ptr& pair) override; + void relayForwordingData(const toolkit::Buffer::Ptr& buffer, const sockaddr_storage& peer_addr); + void relayBackingData(const toolkit::Buffer::Ptr& buffer, const Pair::Ptr& pair, const sockaddr_storage& peer_addr); + +protected: + void processRelayPacket(const toolkit::Buffer::Ptr &buffer, const Pair::Ptr& pair); + void handleAllocateRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair); + void handleRefreshRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair); + void handleCreatePermissionRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair); + void handleChannelBindRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair); + void handleSendIndication(const StunPacket::Ptr& packet, const Pair::Ptr& pair); + void handleChannelData(uint16_t channel_number, const char* data, size_t len, const Pair::Ptr& pair) override; + + StunPacket::Authentication checkRequestAuthentication(const StunPacket::Ptr& packet, const Pair::Ptr& pair) override; + + void sendDataIndication(const sockaddr_storage& peer_addr, const toolkit::Buffer::Ptr &buffer, const Pair::Ptr& pair); + void sendUnauthorizedResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair) override; + + toolkit::SocketHelper::Ptr allocateRelayed(const Pair::Ptr& pair); + toolkit::SocketHelper::Ptr createRelayedUdpSocket(const std::string &peer_host, uint16_t peer_port, const std::string &local_ip, uint16_t local_port); + +protected: + std::vector _nonce_list; + + std::unordered_map /* port */, Pair::Ptr /*relayed_pairs*/>, + toolkit::SockUtil::SockAddrHash, toolkit::SockUtil::SockAddrEqual> _relayed_pairs; + Pair::Ptr _session_pair; +}; + +class IceAgent : public IceTransport { + +public: + using Ptr = std::shared_ptr; + + // 候选者对信息结构 + struct CandidatePair { + Pair::Ptr _local_pair; // 本地候选者对 + CandidateInfo _remote_candidate; // 远程候选者信息 + CandidateInfo _local_candidate; // 本地候选者信息 + uint64_t _priority; // 候选者对优先级(64位,符合RFC 8445) + CandidateInfo::State _state; // 连通性检查状态 + bool _nominated = false; + + CandidatePair(Pair::Ptr local_pair, CandidateInfo remote, CandidateInfo local) + : _local_pair(std::move(local_pair)) + , _remote_candidate(std::move(remote)) + , _local_candidate(std::move(local)) + , _state(CandidateInfo::State::Frozen) { + _priority = calCandidatePairPriority(local._priority, remote._priority); + } + std::string dumpString() const { + return "local " + _local_candidate.dumpString() + " <-> remote " + _remote_candidate.dumpString(); + } + // 比较操作符,用于优先级排序(高优先级在前) + bool operator<(const CandidatePair& other) const { + return _priority > other._priority; + } + }; + + enum class State { + //checklist state and ice session state + Running = 1, //正在进行候选地址的连通性检测 + Nominated, //发起提名,等待应答 + Completed, //所有候选地址完成验证,且至少有一路连接检测成功 + Failed, //所有候选地址检测失败,连接不可用 + }; + + static const char* stateToString(State state) { + switch (state) { + case State::Running: return "Running"; + case State::Completed: return "Completed"; + case State::Failed: return "Failed"; + default: return "Unknown"; + } + } + + enum class Role { + Controlling = 1, + Controlled, + }; + + enum class Implementation { + Lite = 1, + Full, + }; + + IceAgent(Listener* listener, Implementation implementation, Role role, + std::string ufrag, std::string password, toolkit::EventPoller::Ptr poller); + virtual ~IceAgent() {} + + void setIceServer(IceServerInfo::Ptr ice_server) { + _ice_server = std::move(ice_server); + } + + void gatheringCandidate(const CandidateTuple::Ptr& candidate_tuple, bool gathering_rflx, bool gathering_realy); + void connectivityCheck(CandidateInfo& candidate); + void nominated(const Pair::Ptr& pair, CandidateTuple& candidate); + + void sendSocketData(const toolkit::Buffer::Ptr& buf, const Pair::Ptr& pair, bool flush = true) override; + + IceAgent::Implementation getImplementation() const { + return _implementation; + } + + void setgetImplementation(IceAgent::Implementation implementation) { + InfoL << (uint32_t)implementation; + _implementation = implementation; + } + + IceAgent::Role getRole() const { + return _role; + } + + void setRole(IceAgent::Role role) { + InfoL << (uint32_t)role; + _role = role; + } + + IceAgent::State getState() const { + return _state; + } + + void setState(IceAgent::State state) { + InfoL << stateToString(state); + _state = state; + } + + Pair::Ptr getSelectedPair(bool try_last = false) const { + return try_last ? _last_selected_pair.lock() : _selected_pair; + } + bool setSelectedPair(const Pair::Ptr& pair); + + void removePair(const toolkit::SocketHelper *socket); + + std::vector getPairs() const; + + // 获取checklist信息,用于API查询 + Json::Value getChecklistInfo() const; + +protected: + void gatheringSrflxCandidate(const Pair::Ptr& pair); + void gatheringRealyCandidate(const Pair::Ptr& pair); + void localRelayedConnectivityCheck(CandidateInfo& candidate); + void connectivityCheck(const Pair::Ptr& pair, CandidateTuple& candidate); + void tryTriggerredCheck(const Pair::Ptr& pair); + + void sendBindRequest(const Pair::Ptr& pair, MsgHandler handler); + void sendBindRequest(const Pair::Ptr& pair, CandidateTuple& candidate, bool use_candidate, MsgHandler handler); + void sendAllocateRequest(const Pair::Ptr& pair); + void sendCreatePermissionRequest(const Pair::Ptr& pair, const sockaddr_storage& peer_addr); + void sendChannelBindRequest(const Pair::Ptr& pair, uint16_t channel_number, const sockaddr_storage& peer_addr); + + void processRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair) override; + + void handleBindingRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair) override; + void handleGatheringCandidateResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair); + void handleConnectivityCheckResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair, CandidateTuple& candidate); + void handleNominatedResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair, CandidateTuple& candidate); + void handleAllocateResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair); + void handleCreatePermissionResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair, const sockaddr_storage& peer_addr); + void handleChannelBindResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair, uint16_t channel_number, const sockaddr_storage& peer_addr); + void handleDataIndication(const StunPacket::Ptr& packet, const Pair::Ptr& pair); + void handleChannelData(uint16_t channel_number, const char* data, size_t len, const Pair::Ptr& pair) override; + + void onGatheringCandidate(const Pair::Ptr& pair, CandidateInfo& candidate); + void onConnected(const Pair::Ptr& pair); + void onCompleted(const Pair::Ptr& pair); + + void refreshPermissions(); + void refreshChannelBindings(); + + void sendSendIndication(const sockaddr_storage& peer_addr, const toolkit::Buffer::Ptr& buffer, const Pair::Ptr& pair); + void sendRealyPacket(const toolkit::Buffer::Ptr& buffer, const Pair::Ptr& pair, bool flush); + +private: + + CandidateInfo getLocalCandidateInfo(const Pair::Ptr& local_pair); + void addToChecklist(const Pair::Ptr& local_pair, CandidateInfo& remote_candidate); + +protected: + IceServerInfo::Ptr _ice_server; + + std::shared_ptr _refresh_timer; + + // for candidate + + Implementation _implementation = Implementation::Full; + Role _role = Role::Controlling; //ice role + uint64_t _tiebreaker = 0; // 8 bytes unsigned integer. + State _state = IceAgent::State::Running; //ice session state + + Pair::Ptr _selected_pair; + Pair::Ptr _nominated_pair; + StunPacket::Ptr _nominated_response; + std::weak_ptr _last_selected_pair; + + // 双向索引的候选地址管理结构 + struct SocketCandidateManager { + // socket -> candidates 的一对多映射 + std::unordered_map> socket_to_candidates; + + // candidate -> socket 的映射(用于快速查找) + std::unordered_map candidate_to_socket; + + // 按类型分组的socket列表,方便遍历 + std::vector _host_sockets; // HOST类型socket + std::vector _relay_sockets; // RELAY类型socket + + bool _has_relayed_candidate = false; + + // 添加映射关系,带5元组重复检查 + bool addMapping(toolkit::SocketHelper::Ptr socket, const CandidateInfo& candidate) { + // 检查5元组是否已存在 + if (candidate_to_socket.find(candidate) != candidate_to_socket.end()) { + return false; // 已存在相同的5元组 + } + + socket_to_candidates[socket].push_back(candidate); + candidate_to_socket[candidate] = socket; + + // 按类型分组 + if (candidate._type != CandidateInfo::AddressType::RELAY) { + addHostSocket(std::move(socket)); + } else if (candidate._type == CandidateInfo::AddressType::RELAY) { + addRelaySocket(std::move(socket)); + } + + return true; + } + + // 获取socket对应的所有candidates + std::vector getCandidates(const toolkit::SocketHelper::Ptr& socket) const { + auto it = socket_to_candidates.find(socket); + return (it != socket_to_candidates.end()) ? it->second : std::vector(); + } + + // 获取candidate对应的socket + toolkit::SocketHelper::Ptr getSocket(const CandidateInfo& candidate) const { + auto it = candidate_to_socket.find(candidate); + return (it != candidate_to_socket.end()) ? it->second : nullptr; + } + + // 获取所有socket(便于遍历) + std::vector getAllSockets() const { + std::vector result; + result.reserve(_host_sockets.size() + _relay_sockets.size()); + result.insert(result.end(), _host_sockets.begin(), _host_sockets.end()); + result.insert(result.end(), _relay_sockets.begin(), _relay_sockets.end()); + return result; + } + + // 获取所有candidates(便于遍历) + std::vector getAllCandidates() const { + std::vector result; + for (auto& pair : candidate_to_socket) { + result.push_back(pair.first); + } + return result; + } + + // 直接添加host socket + void addHostSocket(toolkit::SocketHelper::Ptr socket) { + if (std::find(_host_sockets.begin(), _host_sockets.end(), socket) == _host_sockets.end()) { + _host_sockets.emplace_back(std::move(socket)); + } + } + + // 直接添加relay socket + void addRelaySocket(toolkit::SocketHelper::Ptr socket) { + if (std::find(_relay_sockets.begin(), _relay_sockets.end(), socket) == _relay_sockets.end()) { + _relay_sockets.emplace_back(std::move(socket)); + } + } + + // 获取host sockets + const std::vector& getHostSockets() const { + return _host_sockets; + } + + // 获取relay sockets + const std::vector& getRelaySockets() const { + return _relay_sockets; + } + + // 移除host socket + void removeHostSocket(const toolkit::SocketHelper::Ptr& socket) { + auto it = std::find(_host_sockets.begin(), _host_sockets.end(), socket); + if (it != _host_sockets.end()) { + _host_sockets.erase(it); + } + } + + // 移除relay socket + void removeRelaySocket(const toolkit::SocketHelper::Ptr& socket) { + auto it = std::find(_relay_sockets.begin(), _relay_sockets.end(), socket); + if (it != _relay_sockets.end()) { + _relay_sockets.erase(it); + } + } + + // 清空host sockets + void clearHostSockets() { + _host_sockets.clear(); + } + + // 清空relay sockets + void clearRelaySockets() { + _relay_sockets.clear(); + } + + // 获取host socket数量 + size_t getHostSocketCount() const { + return _host_sockets.size(); + } + + // 获取relay socket数量 + size_t getRelaySocketCount() const { + return _relay_sockets.size(); + } + }; + + //for GATHERING_CANDIDATE + SocketCandidateManager _socket_candidate_manager; //local candidates + + //for CONNECTIVITY_CHECK + using CandidateSet = std::unordered_set; + CandidateSet _remote_candidates; + + //TODO:当前仅支持多数据流复用一个checklist + std::vector> _check_list; + std::vector> _valid_list; + std::shared_ptr _select_candidate_pair; + +}; + +} // namespace RTC +#endif //ZLMEDIAKIT_WEBRTC_ICE_TRANSPORT_HPP diff --git a/webrtc/Nack.h b/webrtc/Nack.h index 1ebfda71..49a768af 100644 --- a/webrtc/Nack.h +++ b/webrtc/Nack.h @@ -1,90 +1,90 @@ -/* - * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. - * - * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). - * - * Use of this source code is governed by MIT-like license that can be found in the - * LICENSE file in the root of the source tree. All contributing project authors - * may be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef ZLMEDIAKIT_NACK_H -#define ZLMEDIAKIT_NACK_H - -#include -#include -#include -#include -#include "Rtsp/Rtsp.h" -#include "Rtcp/RtcpFCI.h" - -namespace mediakit { - -// RTC配置项目 [AUTO-TRANSLATED:19940011] -// RTC configuration project -namespace Rtc { -// ~ nack发送端,rtp接收端 [AUTO-TRANSLATED:bb169205] -// ~ nack sender, rtp receiver -// 最大保留的rtp丢包状态个数 [AUTO-TRANSLATED:70eee442] -// Maximum number of retained rtp packet loss states -extern const std::string kNackMaxSize; -// rtp丢包状态最长保留时间 [AUTO-TRANSLATED:f9306375] -// Maximum retention time for rtp packet loss states -extern const std::string kNackMaxMS; -} // namespace Rtc - -class NackList { -public: - void pushBack(RtpPacket::Ptr rtp); - void forEach(const FCI_NACK &nack, const std::function &cb); - -private: - void popFront(); - uint32_t getCacheMS(); - int64_t getNtpStamp(uint16_t seq); - RtpPacket::Ptr *getRtp(uint16_t seq); - -private: - uint32_t _cache_ms_check = 0; - std::deque _nack_cache_seq; - std::unordered_map _nack_cache_pkt; -}; - -class NackContext { -public: - using Ptr = std::shared_ptr; - using onNack = std::function; - - NackContext(); - - void received(uint16_t seq, bool is_rtx = false); - void setOnNack(onNack cb); - uint64_t reSendNack(); - -private: - void eraseFrontSeq(); - void doNack(const FCI_NACK &nack, bool record_nack); - void recordNack(const FCI_NACK &nack); - void clearNackStatus(uint16_t seq); - void makeNack(uint16_t max, bool flush = false); - -private: - bool _started = false; - int _rtt = 50; - onNack _cb; - std::set _seq; - // 最新nack包中的rtp seq值 [AUTO-TRANSLATED:6984d95a] - // RTP seq value in the latest nack packet - uint16_t _nack_seq = 0; - - struct NackStatus { - uint64_t first_stamp; - uint64_t update_stamp; - uint32_t nack_count = 0; - }; - std::map _nack_send_status; -}; - -} // namespace mediakit - -#endif //ZLMEDIAKIT_NACK_H +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLMEDIAKIT_NACK_H +#define ZLMEDIAKIT_NACK_H + +#include +#include +#include +#include +#include "Rtsp/Rtsp.h" +#include "Rtcp/RtcpFCI.h" + +namespace mediakit { + +// RTC配置项目 [AUTO-TRANSLATED:19940011] +// RTC configuration project +namespace Rtc { +// ~ nack发送端,rtp接收端 [AUTO-TRANSLATED:bb169205] +// ~ nack sender, rtp receiver +// 最大保留的rtp丢包状态个数 [AUTO-TRANSLATED:70eee442] +// Maximum number of retained rtp packet loss states +extern const std::string kNackMaxSize; +// rtp丢包状态最长保留时间 [AUTO-TRANSLATED:f9306375] +// Maximum retention time for rtp packet loss states +extern const std::string kNackMaxMS; +} // namespace Rtc + +class NackList { +public: + void pushBack(RtpPacket::Ptr rtp); + void forEach(const FCI_NACK &nack, const std::function &cb); + +private: + void popFront(); + uint32_t getCacheMS(); + int64_t getNtpStamp(uint16_t seq); + RtpPacket::Ptr *getRtp(uint16_t seq); + +private: + uint32_t _cache_ms_check = 0; + std::deque _nack_cache_seq; + std::unordered_map _nack_cache_pkt; +}; + +class NackContext { +public: + using Ptr = std::shared_ptr; + using onNack = std::function; + + NackContext(); + + void received(uint16_t seq, bool is_rtx = false); + void setOnNack(onNack cb); + uint64_t reSendNack(); + +private: + void eraseFrontSeq(); + void doNack(const FCI_NACK &nack, bool record_nack); + void recordNack(const FCI_NACK &nack); + void clearNackStatus(uint16_t seq); + void makeNack(uint16_t max, bool flush = false); + +private: + bool _started = false; + int _rtt = 50; + onNack _cb; + std::set _seq; + // 最新nack包中的rtp seq值 [AUTO-TRANSLATED:6984d95a] + // RTP seq value in the latest nack packet + uint16_t _nack_seq = 0; + + struct NackStatus { + uint64_t first_stamp; + uint64_t update_stamp; + uint32_t nack_count = 0; + }; + std::map _nack_send_status; +}; + +} // namespace mediakit + +#endif //ZLMEDIAKIT_NACK_H diff --git a/webrtc/RtpExt.cpp b/webrtc/RtpExt.cpp index 4c0deb29..b9a405d6 100644 --- a/webrtc/RtpExt.cpp +++ b/webrtc/RtpExt.cpp @@ -1,659 +1,659 @@ -/* - * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. - * - * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). - * - * Use of this source code is governed by MIT-like license that can be found in the - * LICENSE file in the root of the source tree. All contributing project authors - * may be found in the AUTHORS file in the root of the source tree. - */ - -#include "RtpExt.h" -#include "Sdp.h" - -#pragma pack(push, 1) - -using namespace std; -using namespace toolkit; - -namespace mediakit { - -//https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01 -//https://tools.ietf.org/html/rfc5285 - -// 0 1 2 3 -// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | 0xBE | 0xDE | length=3 | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | ID | L=0 | data | ID | L=1 | data... -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// ...data | 0 (pad) | 0 (pad) | ID | L=3 | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | data | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -class RtpExtOneByte { -public: - static constexpr uint16_t kMinSize = 1; - size_t getSize() const; - uint8_t getId() const; - void setId(uint8_t id); - uint8_t* getData(); - -private: -#if __BYTE_ORDER == __BIG_ENDIAN - uint8_t id: 4; - uint8_t len: 4; -#else - uint8_t len: 4; - uint8_t id: 4; -#endif - uint8_t data[1]; -}; - -//0 1 2 3 -// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | 0x100 |appbits| length=3 | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | ID | L=0 | ID | L=1 | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | data | 0 (pad) | ID | L=4 | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | data | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -class RtpExtTwoByte { -public: - static constexpr uint16_t kMinSize = 2; - - size_t getSize() const; - uint8_t getId() const; - void setId(uint8_t id); - uint8_t* getData(); - -private: - uint8_t id; - uint8_t len; - uint8_t data[1]; -}; - -#pragma pack(pop) - -////////////////////////////////////////////////////////////////// - -size_t RtpExtOneByte::getSize() const { - return len + 1; -} - -uint8_t RtpExtOneByte::getId() const { - return id; -} - -void RtpExtOneByte::setId(uint8_t in) { - CHECK(in < (int)RtpExtType::reserved); - id = in & 0x0F; -} - -uint8_t *RtpExtOneByte::getData() { - return data; -} - -////////////////////////////////////////////////////////////////// - -size_t RtpExtTwoByte::getSize() const { - return len; -} - -uint8_t RtpExtTwoByte::getId() const { - return id; -} - -void RtpExtTwoByte::setId(uint8_t in) { - id = in; -} - -uint8_t *RtpExtTwoByte::getData() { - return data; -} - -////////////////////////////////////////////////////////////////// - -static constexpr uint16_t kOneByteHeader = 0xBEDE; -static constexpr uint16_t kTwoByteHeader = 0x1000; - -template -static bool isOneByteExt(){ - return false; -} - -template<> -bool isOneByteExt(){ - return true; -} - -template -void appendExt(map &ret, uint8_t *ptr, const uint8_t *end) { - while (ptr < end) { - auto ext = reinterpret_cast(ptr); - if (ext->getId() == (uint8_t) RtpExtType::padding) { - // padding,忽略 [AUTO-TRANSLATED:a7fda608] - // padding, ignore - ++ptr; - continue; - } - CHECK(reinterpret_cast(ext) + Type::kMinSize <= end); - CHECK(ext->getData() + ext->getSize() <= end); - ret.emplace(ext->getId(), RtpExt(ext, isOneByteExt(), reinterpret_cast(ext->getData()), ext->getSize())); - ptr += Type::kMinSize + ext->getSize(); - } -} - -RtpExt::RtpExt(void *ext, bool one_byte_ext, const char *str, size_t size) { - _ext = ext; - _one_byte_ext = one_byte_ext; - _data = str; - _size = size; -} - -const char *RtpExt::data() const { - return _data; -} - -size_t RtpExt::size() const { - return _size; -} - -const uint8_t& RtpExt::operator[](size_t pos) const{ - CHECK(pos < _size); - return ((uint8_t*)_data)[pos]; -} - -RtpExt::operator std::string() const{ - return string(_data, _size); -} - -map RtpExt::getExtValue(const RtpHeader *header) { - map ret; - assert(header); - auto ext_size = header->getExtSize(); - if (!ext_size) { - return ret; - } - auto reserved = header->getExtReserved(); - auto ptr = const_cast(header)->getExtData(); - auto end = ptr + ext_size; - if (reserved == kOneByteHeader) { - appendExt(ret, ptr, end); - return ret; - } - if ((reserved & 0xFFF0) == kTwoByteHeader) { - appendExt(ret, ptr, end); - return ret; - } - return ret; -} - -#define XX(type, url) {RtpExtType::type , url}, -static map s_type_to_url = {RTP_EXT_MAP(XX)}; -#undef XX - - -#define XX(type, url) {url, RtpExtType::type}, -static unordered_map s_url_to_type = {RTP_EXT_MAP(XX)}; -#undef XX - -RtpExtType RtpExt::getExtType(const string &url) { - auto it = s_url_to_type.find(url); - if (it == s_url_to_type.end()) { - WarnL << "unknown rtp ext url type: " << url; - return RtpExtType::padding; - } - return it->second; -} - -const string &RtpExt::getExtUrl(RtpExtType type) { - auto it = s_type_to_url.find(type); - if (it == s_type_to_url.end()) { - throw std::invalid_argument(string("未识别的rtp ext类型:") + to_string((int) type)); - } - return it->second; -} - -const char *RtpExt::getExtName(RtpExtType type) { -#define XX(type, url) case RtpExtType::type: return #type; - switch (type) { - RTP_EXT_MAP(XX) - default: return "unknown ext type"; - } -#undef XX -} - -string RtpExt::dumpString() const { - _StrPrinter printer; - switch (_type) { - case RtpExtType::ssrc_audio_level : { - bool vad; - printer << "audio level:" << (int) getAudioLevel(&vad) << ", vad:" << vad; - break; - } - case RtpExtType::abs_send_time : { - printer << "abs send time:" << getAbsSendTime(); - break; - } - case RtpExtType::transport_cc : { - printer << "twcc ext seq:" << getTransportCCSeq(); - break; - } - case RtpExtType::sdes_mid : { - printer << "sdes mid:" << getSdesMid(); - break; - } - case RtpExtType::sdes_rtp_stream_id : { - printer << "rtp stream id:" << getRtpStreamId(); - break; - } - case RtpExtType::sdes_repaired_rtp_stream_id : { - printer << "rtp repaired stream id:" << getRepairedRtpStreamId(); - break; - } - case RtpExtType::video_timing : { - uint8_t flags; - uint16_t encode_start, encode_finish, packetization_complete, last_pkt_left_pacer, reserved_net0, reserved_net1; - getVideoTiming(flags, encode_start, encode_finish, packetization_complete, last_pkt_left_pacer, - reserved_net0, reserved_net1); - printer << "video timing, flags:" << (int) flags - << ",encode:" << encode_start << "-" << encode_finish - << ",packetization_complete:" << packetization_complete - << ",last_pkt_left_pacer:" << last_pkt_left_pacer - << ",reserved_net0:" << reserved_net0 - << ",reserved_net1:" << reserved_net1; - break; - } - case RtpExtType::video_content_type : { - printer << "video content type:" << (int)getVideoContentType(); - break; - } - case RtpExtType::video_orientation : { - bool camera_bit, flip_bit, first_rotation, second_rotation; - getVideoOrientation(camera_bit, flip_bit, first_rotation, second_rotation); - printer << "video orientation:" << camera_bit << "-" << flip_bit << "-" << first_rotation << "-" << second_rotation; - break; - } - case RtpExtType::playout_delay : { - uint16_t min_delay, max_delay; - getPlayoutDelay(min_delay, max_delay); - printer << "playout delay:" << min_delay << "-" << max_delay; - break; - } - case RtpExtType::toffset : { - printer << "toffset:" << getTransmissionOffset(); - break; - } - case RtpExtType::framemarking : { - printer << "framemarking tid:" << (int)getFramemarkingTID(); - break; - } - default: { - printer << getExtName(_type) << ", hex:" << hexdump(data(), size()); - break; - } - } - return printer; -} - -//https://tools.ietf.org/html/rfc6464 -// 0 1 -// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | ID | len=0 |V| level | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// -// Figure 1: Sample Audio Level Encoding Using the -// One-Byte Header Format -// -// -// 0 1 2 3 -// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | ID | len=1 |V| level | 0 (pad) | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// -// Figure 2: Sample Audio Level Encoding Using the -// Two-Byte Header Format -uint8_t RtpExt::getAudioLevel(bool *vad) const{ - CHECK(_type == RtpExtType::ssrc_audio_level && size() >= 1); - auto &byte = (*this)[0]; - if (vad) { - *vad = byte & 0x80; - } - return byte & 0x7F; -} - -//http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time -// Wire format: 1-byte extension, 3 bytes of data. total 4 bytes extra per packet (plus shared 4 bytes for all extensions present: 2 byte magic word 0xBEDE, 2 byte # of extensions). Will in practice replace the “toffset” extension so we should see no long term increase in traffic as a result. [AUTO-TRANSLATED:178290be] -// Wire format: 1-byte extension, 3 bytes of data. total 4 bytes extra per packet (plus shared 4 bytes for all extensions present: 2 byte magic word 0xBEDE, 2 byte # of extensions). Will in practice replace the “toffset” extension so we should see no long term increase in traffic as a result. -// -//Encoding: Timestamp is in seconds, 24 bit 6.18 fixed point, yielding 64s wraparound and 3.8us resolution (one increment for each 477 bytes going out on a 1Gbps interface). -// -//Relation to NTP timestamps: abs_send_time_24 = (ntp_timestamp_64 >> 14) & 0x00ffffff ; NTP timestamp is 32 bits for whole seconds, 32 bits fraction of second. -// -//Notes: Packets are time stamped when going out, preferably close to metal. Intermediate RTP relays (entities possibly altering the stream) should remove the extension or set its own timestamp. -uint32_t RtpExt::getAbsSendTime() const { - CHECK(_type == RtpExtType::abs_send_time && size() >= 3); - uint32_t ret = 0; - ret |= (*this)[0] << 16; - ret |= (*this)[1] << 8; - ret |= (*this)[2]; - return ret; -} - -//https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01 -// 0 1 2 3 -// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | 0xBE | 0xDE | length=1 | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | ID | L=1 |transport-wide sequence number | zero padding | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -uint16_t RtpExt::getTransportCCSeq() const { - CHECK(_type == RtpExtType::transport_cc && size() >= 2); - uint16_t ret; - ret = (*this)[0] << 8; - ret |= (*this)[1]; - return ret; -} - -//https://tools.ietf.org/html/draft-ietf-avtext-sdes-hdr-ext-07 -// 0 1 2 3 -// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | ID | len | SDES Item text value ... | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -string RtpExt::getSdesMid() const { - CHECK(_type == RtpExtType::sdes_mid && size() >= 1); - return *this; -} - - -//https://tools.ietf.org/html/draft-ietf-avtext-rid-06 -// 用于simulcast [AUTO-TRANSLATED:59b2682f] -// Used for simulcast -//3.1. RTCP 'RtpStreamId' SDES Extension -// -// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// |RtpStreamId=TBD| length | RtpStreamId ... -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// -// -// The RtpStreamId payload is UTF-8 encoded and is not null-terminated. -// -// RFC EDITOR NOTE: Please replace TBD with the assigned SDES -// identifier value. - -//3.2. RTCP 'RepairedRtpStreamId' SDES Extension -// -// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// |Repaired...=TBD| length | RepairRtpStreamId ... -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// -// -// The RepairedRtpStreamId payload is UTF-8 encoded and is not null- -// terminated. -// -// RFC EDITOR NOTE: Please replace TBD with the assigned SDES -// identifier value. - -string RtpExt::getRtpStreamId() const { - CHECK(_type == RtpExtType::sdes_rtp_stream_id && size() >= 1); - return *this; -} - -string RtpExt::getRepairedRtpStreamId() const { - CHECK(_type == RtpExtType::sdes_repaired_rtp_stream_id && size() >= 1); - return *this; -} - - -//http://www.webrtc.org/experiments/rtp-hdrext/video-timing -//Wire format: 1-byte extension, 13 bytes of data. Total 14 bytes extra per packet (plus 1-3 padding byte in some cases, plus shared 4 bytes for all extensions present: 2 byte magic word 0xBEDE, 2 byte # of extensions). -// -//First byte is a flags field. Defined flags: -// -//0x01 - extension is set due to timer. -//0x02 - extension is set because the frame is larger than usual. -//Both flags may be set at the same time. All remaining 6 bits are reserved and should be ignored. -// -//Next, 6 timestamps are stored as 16-bit values in big-endian order, representing delta from the capture time of a packet in ms. Timestamps are, in order: -// -//Encode start. -//Encode finish. -//Packetization complete. -//Last packet left the pacer. -//Reserved for network. -//Reserved for network (2). - -void RtpExt::getVideoTiming(uint8_t &flags, - uint16_t &encode_start, - uint16_t &encode_finish, - uint16_t &packetization_complete, - uint16_t &last_pkt_left_pacer, - uint16_t &reserved_net0, - uint16_t &reserved_net1) const { - CHECK(_type == RtpExtType::video_timing && size() >= 13); - flags = (*this)[0]; - encode_start = (*this)[1] << 8 | (*this)[2]; - encode_finish = (*this)[3] << 8 | (*this)[4]; - packetization_complete = (*this)[5] << 8 | (*this)[6]; - last_pkt_left_pacer = (*this)[7] << 8 | (*this)[8]; - reserved_net0 = (*this)[9] << 8 | (*this)[10]; - reserved_net1 = (*this)[11] << 8 | (*this)[12]; -} - - -//http://www.webrtc.org/experiments/rtp-hdrext/color-space -// 0 1 2 3 -// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | ID | L = 3 | primaries | transfer | matrix | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// |range+chr.sit. | -// +-+-+-+-+-+-+-+-+ - - -//http://www.webrtc.org/experiments/rtp-hdrext/video-content-type -//Values: -//0x00: Unspecified. Default value. Treated the same as an absence of an extension. -//0x01: Screenshare. Video stream is of a screenshare type. -// 0x02: 摄像头? [AUTO-TRANSLATED:ce2acbbb] -// 0x02: Camera? -//Notes: Extension shoud be present only in the last packet of key-frames. -// If attached to other packets it should be ignored. -// If extension is absent, Unspecified value is assumed. -uint8_t RtpExt::getVideoContentType() const { - CHECK(_type == RtpExtType::video_content_type && size() >= 1); - return (*this)[0]; -} - -//http://www.3gpp.org/ftp/Specs/html-info/26114.htm -void RtpExt::getVideoOrientation(bool &camera_bit, bool &flip_bit, bool &first_rotation, bool &second_rotation) const { - CHECK(_type == RtpExtType::video_orientation && size() >= 1); - uint8_t byte = (*this)[0]; - camera_bit = (byte & 0x08) >> 3; - flip_bit = (byte & 0x04) >> 2; - first_rotation = (byte & 0x02) >> 1; - second_rotation = byte & 0x01; -} - -//http://www.webrtc.org/experiments/rtp-hdrext/playout-delay -// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -//+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -//| ID | len=2 | MIN delay | MAX delay | -//+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -void RtpExt::getPlayoutDelay(uint16_t &min_delay, uint16_t &max_delay) const { - CHECK(_type == RtpExtType::playout_delay && size() >= 3); - uint32_t bytes = (*this)[0] << 16 | (*this)[1] << 8 | (*this)[2]; - min_delay = (bytes & 0x00FFF000) >> 12; - max_delay = bytes & 0x00000FFF; -} - -//urn:ietf:params:rtp-hdrext:toffset -//https://tools.ietf.org/html/rfc5450 -// 0 1 2 3 -// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | ID | len=2 | transmission offset | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -uint32_t RtpExt::getTransmissionOffset() const { - CHECK(_type == RtpExtType::toffset && size() >= 3); - return (*this)[0] << 16 | (*this)[1] << 8 | (*this)[2]; -} - -//http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07 -// 0 1 2 3 -// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | ID=? | L=2 |S|E|I|D|B| TID | LID | TL0PICIDX | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -uint8_t RtpExt::getFramemarkingTID() const { - CHECK(_type == RtpExtType::framemarking && size() >= 3); - return (*this)[0] & 0x07; -} - -void RtpExt::setExtId(uint8_t ext_id) { - assert(ext_id > (int) RtpExtType::padding && _ext); - if (_one_byte_ext) { - if (ext_id >= (int)RtpExtType::reserved) { - WarnL << "One byte rtp ext can not store id " << (int)ext_id << "(" << getExtName((RtpExtType)ext_id) << ") big than 14"; - clearExt(); - return; - } - auto ptr = reinterpret_cast(_ext); - ptr->setId(ext_id); - } else { - auto ptr = reinterpret_cast(_ext); - ptr->setId(ext_id); - } -} - -void RtpExt::clearExt(){ - assert(_ext); - if (_one_byte_ext) { - auto ptr = reinterpret_cast(_ext); - memset(ptr, (int) RtpExtType::padding, RtpExtOneByte::kMinSize + ptr->getSize()); - } else { - auto ptr = reinterpret_cast(_ext); - memset(ptr, (int) RtpExtType::padding, RtpExtTwoByte::kMinSize + ptr->getSize()); - } -} - -void RtpExt::setType(RtpExtType type) { - _type = type; -} - -RtpExtType RtpExt::getType() const { - return _type; -} - -RtpExt::operator bool() const { - return _ext != nullptr; -} - -RtpExtContext::RtpExtContext(const RtcMedia &m){ - for (auto &ext : m.extmap) { - auto ext_type = RtpExt::getExtType(ext.ext); - _rtp_ext_id_to_type.emplace(ext.id, ext_type); - _rtp_ext_type_to_id.emplace(ext_type, ext.id); - } -} - -string RtpExtContext::getRid(uint32_t ssrc) const{ - auto it = _ssrc_to_rid.find(ssrc); - if (it == _ssrc_to_rid.end()) { - return ""; - } - return it->second; -} - -void RtpExtContext::setRid(uint32_t ssrc, const string &rid) { - _ssrc_to_rid[ssrc] = rid; -} - -RtpExt RtpExtContext::changeRtpExtId(const RtpHeader *header, bool is_recv, string *rid_ptr, RtpExtType type) { - string rid, repaired_rid; - RtpExt ret; - auto ext_map = RtpExt::getExtValue(header); - for (auto &pr : ext_map) { - if (is_recv) { - auto it = _rtp_ext_id_to_type.find(pr.first); - if (it == _rtp_ext_id_to_type.end()) { - // TraceL << "接收rtp时,忽略不识别的rtp ext, id=" << (int) pr.first; [AUTO-TRANSLATED:284d8a38] - // TraceL << "Receiving rtp, ignoring unrecognized rtp ext, id=" << (int) pr.first; - pr.second.clearExt(); - continue; - } - pr.second.setType(it->second); - // 重新赋值ext id为 ext type,作为后面处理ext的统一中间类型 [AUTO-TRANSLATED:ab825878] - // Reassign ext id to ext type, as a unified intermediate type for processing ext later - pr.second.setExtId((uint8_t) it->second); - switch (it->second) { - case RtpExtType::sdes_rtp_stream_id : rid = pr.second.getRtpStreamId(); break; - case RtpExtType::sdes_repaired_rtp_stream_id : repaired_rid = pr.second.getRepairedRtpStreamId(); break; - default : break; - } - } else { - pr.second.setType((RtpExtType) pr.first); - auto it = _rtp_ext_type_to_id.find((RtpExtType) pr.first); - if (it == _rtp_ext_type_to_id.end()) { - // TraceL << "发送rtp时, 忽略不被客户端支持rtp ext:" << pr.second.dumpString(); [AUTO-TRANSLATED:5d9fd8cc] - // TraceL << "Sending rtp, ignoring rtp ext not supported by client:" << pr.second.dumpString(); - pr.second.clearExt(); - continue; - } - // 重新赋值ext id为客户端sdp声明的类型 [AUTO-TRANSLATED:06d60796] - // Reassign ext id to the type declared in client sdp - pr.second.setExtId(it->second); - } - if (pr.second.getType() == type) { - ret = pr.second; - } - } - - if (!is_recv) { - return ret; - } - if (rid.empty()) { - rid = repaired_rid; - } - auto ssrc = ntohl(header->ssrc); - if (rid.empty()) { - // 获取rid [AUTO-TRANSLATED:8ae4dffa] - // Get rid - rid = _ssrc_to_rid[ssrc]; - } else { - // 设置rid [AUTO-TRANSLATED:5e34819b] - // Set rid - auto it = _ssrc_to_rid.find(ssrc); - if (it == _ssrc_to_rid.end() || it->second != rid) { - _ssrc_to_rid[ssrc] = rid; - onGetRtp(header->pt, ssrc, rid); - } - } - if (rid_ptr) { - *rid_ptr = rid; - } - return ret; -} - -void RtpExtContext::setOnGetRtp(OnGetRtp cb) { - _cb = std::move(cb); -} - -void RtpExtContext::onGetRtp(uint8_t pt, uint32_t ssrc, const string &rid){ - if (_cb) { - _cb(pt, ssrc, rid); - } -} - -}// namespace mediakit \ No newline at end of file +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "RtpExt.h" +#include "Sdp.h" + +#pragma pack(push, 1) + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +//https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01 +//https://tools.ietf.org/html/rfc5285 + +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | 0xBE | 0xDE | length=3 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | L=0 | data | ID | L=1 | data... +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// ...data | 0 (pad) | 0 (pad) | ID | L=3 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | data | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +class RtpExtOneByte { +public: + static constexpr uint16_t kMinSize = 1; + size_t getSize() const; + uint8_t getId() const; + void setId(uint8_t id); + uint8_t* getData(); + +private: +#if __BYTE_ORDER == __BIG_ENDIAN + uint8_t id: 4; + uint8_t len: 4; +#else + uint8_t len: 4; + uint8_t id: 4; +#endif + uint8_t data[1]; +}; + +//0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | 0x100 |appbits| length=3 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | L=0 | ID | L=1 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | data | 0 (pad) | ID | L=4 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | data | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +class RtpExtTwoByte { +public: + static constexpr uint16_t kMinSize = 2; + + size_t getSize() const; + uint8_t getId() const; + void setId(uint8_t id); + uint8_t* getData(); + +private: + uint8_t id; + uint8_t len; + uint8_t data[1]; +}; + +#pragma pack(pop) + +////////////////////////////////////////////////////////////////// + +size_t RtpExtOneByte::getSize() const { + return len + 1; +} + +uint8_t RtpExtOneByte::getId() const { + return id; +} + +void RtpExtOneByte::setId(uint8_t in) { + CHECK(in < (int)RtpExtType::reserved); + id = in & 0x0F; +} + +uint8_t *RtpExtOneByte::getData() { + return data; +} + +////////////////////////////////////////////////////////////////// + +size_t RtpExtTwoByte::getSize() const { + return len; +} + +uint8_t RtpExtTwoByte::getId() const { + return id; +} + +void RtpExtTwoByte::setId(uint8_t in) { + id = in; +} + +uint8_t *RtpExtTwoByte::getData() { + return data; +} + +////////////////////////////////////////////////////////////////// + +static constexpr uint16_t kOneByteHeader = 0xBEDE; +static constexpr uint16_t kTwoByteHeader = 0x1000; + +template +static bool isOneByteExt(){ + return false; +} + +template<> +bool isOneByteExt(){ + return true; +} + +template +void appendExt(map &ret, uint8_t *ptr, const uint8_t *end) { + while (ptr < end) { + auto ext = reinterpret_cast(ptr); + if (ext->getId() == (uint8_t) RtpExtType::padding) { + // padding,忽略 [AUTO-TRANSLATED:a7fda608] + // padding, ignore + ++ptr; + continue; + } + CHECK(reinterpret_cast(ext) + Type::kMinSize <= end); + CHECK(ext->getData() + ext->getSize() <= end); + ret.emplace(ext->getId(), RtpExt(ext, isOneByteExt(), reinterpret_cast(ext->getData()), ext->getSize())); + ptr += Type::kMinSize + ext->getSize(); + } +} + +RtpExt::RtpExt(void *ext, bool one_byte_ext, const char *str, size_t size) { + _ext = ext; + _one_byte_ext = one_byte_ext; + _data = str; + _size = size; +} + +const char *RtpExt::data() const { + return _data; +} + +size_t RtpExt::size() const { + return _size; +} + +const uint8_t& RtpExt::operator[](size_t pos) const{ + CHECK(pos < _size); + return ((uint8_t*)_data)[pos]; +} + +RtpExt::operator std::string() const{ + return string(_data, _size); +} + +map RtpExt::getExtValue(const RtpHeader *header) { + map ret; + assert(header); + auto ext_size = header->getExtSize(); + if (!ext_size) { + return ret; + } + auto reserved = header->getExtReserved(); + auto ptr = const_cast(header)->getExtData(); + auto end = ptr + ext_size; + if (reserved == kOneByteHeader) { + appendExt(ret, ptr, end); + return ret; + } + if ((reserved & 0xFFF0) == kTwoByteHeader) { + appendExt(ret, ptr, end); + return ret; + } + return ret; +} + +#define XX(type, url) {RtpExtType::type , url}, +static map s_type_to_url = {RTP_EXT_MAP(XX)}; +#undef XX + + +#define XX(type, url) {url, RtpExtType::type}, +static unordered_map s_url_to_type = {RTP_EXT_MAP(XX)}; +#undef XX + +RtpExtType RtpExt::getExtType(const string &url) { + auto it = s_url_to_type.find(url); + if (it == s_url_to_type.end()) { + WarnL << "unknown rtp ext url type: " << url; + return RtpExtType::padding; + } + return it->second; +} + +const string &RtpExt::getExtUrl(RtpExtType type) { + auto it = s_type_to_url.find(type); + if (it == s_type_to_url.end()) { + throw std::invalid_argument(string("未识别的rtp ext类型:") + to_string((int) type)); + } + return it->second; +} + +const char *RtpExt::getExtName(RtpExtType type) { +#define XX(type, url) case RtpExtType::type: return #type; + switch (type) { + RTP_EXT_MAP(XX) + default: return "unknown ext type"; + } +#undef XX +} + +string RtpExt::dumpString() const { + _StrPrinter printer; + switch (_type) { + case RtpExtType::ssrc_audio_level : { + bool vad; + printer << "audio level:" << (int) getAudioLevel(&vad) << ", vad:" << vad; + break; + } + case RtpExtType::abs_send_time : { + printer << "abs send time:" << getAbsSendTime(); + break; + } + case RtpExtType::transport_cc : { + printer << "twcc ext seq:" << getTransportCCSeq(); + break; + } + case RtpExtType::sdes_mid : { + printer << "sdes mid:" << getSdesMid(); + break; + } + case RtpExtType::sdes_rtp_stream_id : { + printer << "rtp stream id:" << getRtpStreamId(); + break; + } + case RtpExtType::sdes_repaired_rtp_stream_id : { + printer << "rtp repaired stream id:" << getRepairedRtpStreamId(); + break; + } + case RtpExtType::video_timing : { + uint8_t flags; + uint16_t encode_start, encode_finish, packetization_complete, last_pkt_left_pacer, reserved_net0, reserved_net1; + getVideoTiming(flags, encode_start, encode_finish, packetization_complete, last_pkt_left_pacer, + reserved_net0, reserved_net1); + printer << "video timing, flags:" << (int) flags + << ",encode:" << encode_start << "-" << encode_finish + << ",packetization_complete:" << packetization_complete + << ",last_pkt_left_pacer:" << last_pkt_left_pacer + << ",reserved_net0:" << reserved_net0 + << ",reserved_net1:" << reserved_net1; + break; + } + case RtpExtType::video_content_type : { + printer << "video content type:" << (int)getVideoContentType(); + break; + } + case RtpExtType::video_orientation : { + bool camera_bit, flip_bit, first_rotation, second_rotation; + getVideoOrientation(camera_bit, flip_bit, first_rotation, second_rotation); + printer << "video orientation:" << camera_bit << "-" << flip_bit << "-" << first_rotation << "-" << second_rotation; + break; + } + case RtpExtType::playout_delay : { + uint16_t min_delay, max_delay; + getPlayoutDelay(min_delay, max_delay); + printer << "playout delay:" << min_delay << "-" << max_delay; + break; + } + case RtpExtType::toffset : { + printer << "toffset:" << getTransmissionOffset(); + break; + } + case RtpExtType::framemarking : { + printer << "framemarking tid:" << (int)getFramemarkingTID(); + break; + } + default: { + printer << getExtName(_type) << ", hex:" << hexdump(data(), size()); + break; + } + } + return printer; +} + +//https://tools.ietf.org/html/rfc6464 +// 0 1 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=0 |V| level | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// Figure 1: Sample Audio Level Encoding Using the +// One-Byte Header Format +// +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=1 |V| level | 0 (pad) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// Figure 2: Sample Audio Level Encoding Using the +// Two-Byte Header Format +uint8_t RtpExt::getAudioLevel(bool *vad) const{ + CHECK(_type == RtpExtType::ssrc_audio_level && size() >= 1); + auto &byte = (*this)[0]; + if (vad) { + *vad = byte & 0x80; + } + return byte & 0x7F; +} + +//http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time +// Wire format: 1-byte extension, 3 bytes of data. total 4 bytes extra per packet (plus shared 4 bytes for all extensions present: 2 byte magic word 0xBEDE, 2 byte # of extensions). Will in practice replace the “toffset” extension so we should see no long term increase in traffic as a result. [AUTO-TRANSLATED:178290be] +// Wire format: 1-byte extension, 3 bytes of data. total 4 bytes extra per packet (plus shared 4 bytes for all extensions present: 2 byte magic word 0xBEDE, 2 byte # of extensions). Will in practice replace the “toffset” extension so we should see no long term increase in traffic as a result. +// +//Encoding: Timestamp is in seconds, 24 bit 6.18 fixed point, yielding 64s wraparound and 3.8us resolution (one increment for each 477 bytes going out on a 1Gbps interface). +// +//Relation to NTP timestamps: abs_send_time_24 = (ntp_timestamp_64 >> 14) & 0x00ffffff ; NTP timestamp is 32 bits for whole seconds, 32 bits fraction of second. +// +//Notes: Packets are time stamped when going out, preferably close to metal. Intermediate RTP relays (entities possibly altering the stream) should remove the extension or set its own timestamp. +uint32_t RtpExt::getAbsSendTime() const { + CHECK(_type == RtpExtType::abs_send_time && size() >= 3); + uint32_t ret = 0; + ret |= (*this)[0] << 16; + ret |= (*this)[1] << 8; + ret |= (*this)[2]; + return ret; +} + +//https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01 +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | 0xBE | 0xDE | length=1 | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | L=1 |transport-wide sequence number | zero padding | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +uint16_t RtpExt::getTransportCCSeq() const { + CHECK(_type == RtpExtType::transport_cc && size() >= 2); + uint16_t ret; + ret = (*this)[0] << 8; + ret |= (*this)[1]; + return ret; +} + +//https://tools.ietf.org/html/draft-ietf-avtext-sdes-hdr-ext-07 +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len | SDES Item text value ... | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +string RtpExt::getSdesMid() const { + CHECK(_type == RtpExtType::sdes_mid && size() >= 1); + return *this; +} + + +//https://tools.ietf.org/html/draft-ietf-avtext-rid-06 +// 用于simulcast [AUTO-TRANSLATED:59b2682f] +// Used for simulcast +//3.1. RTCP 'RtpStreamId' SDES Extension +// +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |RtpStreamId=TBD| length | RtpStreamId ... +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// +// The RtpStreamId payload is UTF-8 encoded and is not null-terminated. +// +// RFC EDITOR NOTE: Please replace TBD with the assigned SDES +// identifier value. + +//3.2. RTCP 'RepairedRtpStreamId' SDES Extension +// +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |Repaired...=TBD| length | RepairRtpStreamId ... +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// +// The RepairedRtpStreamId payload is UTF-8 encoded and is not null- +// terminated. +// +// RFC EDITOR NOTE: Please replace TBD with the assigned SDES +// identifier value. + +string RtpExt::getRtpStreamId() const { + CHECK(_type == RtpExtType::sdes_rtp_stream_id && size() >= 1); + return *this; +} + +string RtpExt::getRepairedRtpStreamId() const { + CHECK(_type == RtpExtType::sdes_repaired_rtp_stream_id && size() >= 1); + return *this; +} + + +//http://www.webrtc.org/experiments/rtp-hdrext/video-timing +//Wire format: 1-byte extension, 13 bytes of data. Total 14 bytes extra per packet (plus 1-3 padding byte in some cases, plus shared 4 bytes for all extensions present: 2 byte magic word 0xBEDE, 2 byte # of extensions). +// +//First byte is a flags field. Defined flags: +// +//0x01 - extension is set due to timer. +//0x02 - extension is set because the frame is larger than usual. +//Both flags may be set at the same time. All remaining 6 bits are reserved and should be ignored. +// +//Next, 6 timestamps are stored as 16-bit values in big-endian order, representing delta from the capture time of a packet in ms. Timestamps are, in order: +// +//Encode start. +//Encode finish. +//Packetization complete. +//Last packet left the pacer. +//Reserved for network. +//Reserved for network (2). + +void RtpExt::getVideoTiming(uint8_t &flags, + uint16_t &encode_start, + uint16_t &encode_finish, + uint16_t &packetization_complete, + uint16_t &last_pkt_left_pacer, + uint16_t &reserved_net0, + uint16_t &reserved_net1) const { + CHECK(_type == RtpExtType::video_timing && size() >= 13); + flags = (*this)[0]; + encode_start = (*this)[1] << 8 | (*this)[2]; + encode_finish = (*this)[3] << 8 | (*this)[4]; + packetization_complete = (*this)[5] << 8 | (*this)[6]; + last_pkt_left_pacer = (*this)[7] << 8 | (*this)[8]; + reserved_net0 = (*this)[9] << 8 | (*this)[10]; + reserved_net1 = (*this)[11] << 8 | (*this)[12]; +} + + +//http://www.webrtc.org/experiments/rtp-hdrext/color-space +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | L = 3 | primaries | transfer | matrix | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |range+chr.sit. | +// +-+-+-+-+-+-+-+-+ + + +//http://www.webrtc.org/experiments/rtp-hdrext/video-content-type +//Values: +//0x00: Unspecified. Default value. Treated the same as an absence of an extension. +//0x01: Screenshare. Video stream is of a screenshare type. +// 0x02: 摄像头? [AUTO-TRANSLATED:ce2acbbb] +// 0x02: Camera? +//Notes: Extension shoud be present only in the last packet of key-frames. +// If attached to other packets it should be ignored. +// If extension is absent, Unspecified value is assumed. +uint8_t RtpExt::getVideoContentType() const { + CHECK(_type == RtpExtType::video_content_type && size() >= 1); + return (*this)[0]; +} + +//http://www.3gpp.org/ftp/Specs/html-info/26114.htm +void RtpExt::getVideoOrientation(bool &camera_bit, bool &flip_bit, bool &first_rotation, bool &second_rotation) const { + CHECK(_type == RtpExtType::video_orientation && size() >= 1); + uint8_t byte = (*this)[0]; + camera_bit = (byte & 0x08) >> 3; + flip_bit = (byte & 0x04) >> 2; + first_rotation = (byte & 0x02) >> 1; + second_rotation = byte & 0x01; +} + +//http://www.webrtc.org/experiments/rtp-hdrext/playout-delay +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +//+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +//| ID | len=2 | MIN delay | MAX delay | +//+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +void RtpExt::getPlayoutDelay(uint16_t &min_delay, uint16_t &max_delay) const { + CHECK(_type == RtpExtType::playout_delay && size() >= 3); + uint32_t bytes = (*this)[0] << 16 | (*this)[1] << 8 | (*this)[2]; + min_delay = (bytes & 0x00FFF000) >> 12; + max_delay = bytes & 0x00000FFF; +} + +//urn:ietf:params:rtp-hdrext:toffset +//https://tools.ietf.org/html/rfc5450 +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=2 | transmission offset | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +uint32_t RtpExt::getTransmissionOffset() const { + CHECK(_type == RtpExtType::toffset && size() >= 3); + return (*this)[0] << 16 | (*this)[1] << 8 | (*this)[2]; +} + +//http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07 +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID=? | L=2 |S|E|I|D|B| TID | LID | TL0PICIDX | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +uint8_t RtpExt::getFramemarkingTID() const { + CHECK(_type == RtpExtType::framemarking && size() >= 3); + return (*this)[0] & 0x07; +} + +void RtpExt::setExtId(uint8_t ext_id) { + assert(ext_id > (int) RtpExtType::padding && _ext); + if (_one_byte_ext) { + if (ext_id >= (int)RtpExtType::reserved) { + WarnL << "One byte rtp ext can not store id " << (int)ext_id << "(" << getExtName((RtpExtType)ext_id) << ") big than 14"; + clearExt(); + return; + } + auto ptr = reinterpret_cast(_ext); + ptr->setId(ext_id); + } else { + auto ptr = reinterpret_cast(_ext); + ptr->setId(ext_id); + } +} + +void RtpExt::clearExt(){ + assert(_ext); + if (_one_byte_ext) { + auto ptr = reinterpret_cast(_ext); + memset(ptr, (int) RtpExtType::padding, RtpExtOneByte::kMinSize + ptr->getSize()); + } else { + auto ptr = reinterpret_cast(_ext); + memset(ptr, (int) RtpExtType::padding, RtpExtTwoByte::kMinSize + ptr->getSize()); + } +} + +void RtpExt::setType(RtpExtType type) { + _type = type; +} + +RtpExtType RtpExt::getType() const { + return _type; +} + +RtpExt::operator bool() const { + return _ext != nullptr; +} + +RtpExtContext::RtpExtContext(const RtcMedia &m){ + for (auto &ext : m.extmap) { + auto ext_type = RtpExt::getExtType(ext.ext); + _rtp_ext_id_to_type.emplace(ext.id, ext_type); + _rtp_ext_type_to_id.emplace(ext_type, ext.id); + } +} + +string RtpExtContext::getRid(uint32_t ssrc) const{ + auto it = _ssrc_to_rid.find(ssrc); + if (it == _ssrc_to_rid.end()) { + return ""; + } + return it->second; +} + +void RtpExtContext::setRid(uint32_t ssrc, const string &rid) { + _ssrc_to_rid[ssrc] = rid; +} + +RtpExt RtpExtContext::changeRtpExtId(const RtpHeader *header, bool is_recv, string *rid_ptr, RtpExtType type) { + string rid, repaired_rid; + RtpExt ret; + auto ext_map = RtpExt::getExtValue(header); + for (auto &pr : ext_map) { + if (is_recv) { + auto it = _rtp_ext_id_to_type.find(pr.first); + if (it == _rtp_ext_id_to_type.end()) { + // TraceL << "接收rtp时,忽略不识别的rtp ext, id=" << (int) pr.first; [AUTO-TRANSLATED:284d8a38] + // TraceL << "Receiving rtp, ignoring unrecognized rtp ext, id=" << (int) pr.first; + pr.second.clearExt(); + continue; + } + pr.second.setType(it->second); + // 重新赋值ext id为 ext type,作为后面处理ext的统一中间类型 [AUTO-TRANSLATED:ab825878] + // Reassign ext id to ext type, as a unified intermediate type for processing ext later + pr.second.setExtId((uint8_t) it->second); + switch (it->second) { + case RtpExtType::sdes_rtp_stream_id : rid = pr.second.getRtpStreamId(); break; + case RtpExtType::sdes_repaired_rtp_stream_id : repaired_rid = pr.second.getRepairedRtpStreamId(); break; + default : break; + } + } else { + pr.second.setType((RtpExtType) pr.first); + auto it = _rtp_ext_type_to_id.find((RtpExtType) pr.first); + if (it == _rtp_ext_type_to_id.end()) { + // TraceL << "发送rtp时, 忽略不被客户端支持rtp ext:" << pr.second.dumpString(); [AUTO-TRANSLATED:5d9fd8cc] + // TraceL << "Sending rtp, ignoring rtp ext not supported by client:" << pr.second.dumpString(); + pr.second.clearExt(); + continue; + } + // 重新赋值ext id为客户端sdp声明的类型 [AUTO-TRANSLATED:06d60796] + // Reassign ext id to the type declared in client sdp + pr.second.setExtId(it->second); + } + if (pr.second.getType() == type) { + ret = pr.second; + } + } + + if (!is_recv) { + return ret; + } + if (rid.empty()) { + rid = repaired_rid; + } + auto ssrc = ntohl(header->ssrc); + if (rid.empty()) { + // 获取rid [AUTO-TRANSLATED:8ae4dffa] + // Get rid + rid = _ssrc_to_rid[ssrc]; + } else { + // 设置rid [AUTO-TRANSLATED:5e34819b] + // Set rid + auto it = _ssrc_to_rid.find(ssrc); + if (it == _ssrc_to_rid.end() || it->second != rid) { + _ssrc_to_rid[ssrc] = rid; + onGetRtp(header->pt, ssrc, rid); + } + } + if (rid_ptr) { + *rid_ptr = rid; + } + return ret; +} + +void RtpExtContext::setOnGetRtp(OnGetRtp cb) { + _cb = std::move(cb); +} + +void RtpExtContext::onGetRtp(uint8_t pt, uint32_t ssrc, const string &rid){ + if (_cb) { + _cb(pt, ssrc, rid); + } +} + +}// namespace mediakit diff --git a/webrtc/RtpMap.h b/webrtc/RtpMap.h new file mode 100644 index 00000000..ea9ebbbf --- /dev/null +++ b/webrtc/RtpMap.h @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLMEDIAKIT_RTPMAP_H +#define ZLMEDIAKIT_RTPMAP_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include "Extension/Frame.h" + +namespace mediakit { + +class RtpMap { +public: + using Ptr = std::shared_ptr; + RtpMap(std::string code_name, uint8_t payload, uint32_t clock_rate) + : _code_name(std::move(code_name)) + , _payload(payload) + , _clock_rate(clock_rate) {} + virtual ~RtpMap() = default; + + virtual TrackType getType() = 0; + + const std::map &getFmtp() const { return _fmtp; } + + const std::string &getCodeName() const { return _code_name; } + uint8_t getPayload() const { return _payload; } + uint32_t getClockRate() const { return _clock_rate; } + +protected: + std::map _fmtp; + std::string _code_name; + uint8_t _payload; + uint32_t _clock_rate; +}; + +class VideoRtpMap : public RtpMap { +public: + VideoRtpMap(std::string code_name, uint8_t payload, uint32_t clock_rate) + : RtpMap(std::move(code_name), payload, clock_rate) {}; + + TrackType getType() override { return TrackVideo; } +}; + +class AudioRtpMap : public RtpMap { +public: + AudioRtpMap( std::string code_name, uint8_t payload, uint32_t clock_rate) + : RtpMap(std::move(code_name), payload, clock_rate) {}; + + TrackType getType() override { return TrackAudio; }; +}; + +#define H264_PROFILE_IDC_MAP(XX) \ + XX(PROFILE_H264_BASELINE, 66, "baseline") \ + XX(PROFILE_H264_MAIN, 77, "main") \ + XX(PROFILE_H264_HIGH, 100, "high") \ + XX(PROFILE_H264_HIGH10, 110, "high10") \ + XX(PROFILE_H264_HIGH422, 122, "high422") \ + XX(PROFILE_H264_HIGH444, 244, "high444") \ + +typedef enum { + H264ProfileIdcInvalid = -1, +#define XX(name, value, str) name = value, + H264_PROFILE_IDC_MAP(XX) +#undef XX + H264ProfileIdcMax +} H264ProfileIdc; + +#define H264_PROFILE_LEVEL_MAP(XX) \ + XX(10) \ + XX(20) \ + XX(30) \ + XX(31) \ + XX(40) \ + XX(41) \ + XX(50) \ + XX(51) + +typedef enum { + H264ProfileLevelInvalid = -1, +#define XX(value) H264_PROFILE_LEVEL_##value = value, + H264_PROFILE_LEVEL_MAP(XX) +#undef XX + H264ProfileLevelMax +} H264ProfileLevel; + +class H264RtpMap : public VideoRtpMap { +public: + H264RtpMap(uint8_t payload, uint32_t clock_rate, H264ProfileIdc profile_idc) + : VideoRtpMap("H264", payload, clock_rate) + , _profile_idc(profile_idc) { + _fmtp.emplace("level-asymmetry-allowed", "1"); + _fmtp.emplace("packetization-mode", "1"); + + toolkit::_StrPrinter printer; + printer << std::setw(2) << std::setfill('0') << std::hex << _profile_idc; + printer << std::setw(2) << std::setfill('0') << std::hex << _profile_iop; + printer << std::setw(2) << std::setfill('0') << std::hex << _profile_level; + _fmtp.emplace("profile-level-id", printer); + }; + +private: + H264ProfileIdc _profile_idc; + int _profile_iop = 0; + H264ProfileLevel _profile_level = H264_PROFILE_LEVEL_31; +}; + +#define H265_PROFILE_IDC_MAP(XX) \ + XX(PROFILE_H265_MAIN, 1, "main") \ + XX(PROFILE_H265_MAIN10, 2, "main10") \ + XX(PROFILE_H265_MAINSTILL, 3, "mainstill") \ + XX(PROFILE_H265_RANGE_EXTS, 4, "RangeExtensions") \ + XX(PROFILE_H265_HIGH_THROUGHPUT, 5, "HighThroughput") \ + XX(PROFILE_H265_MULTIVIEW, 6, "MultiviewMain") \ + XX(PROFILE_H265_SCALABLE_MAIN, 7, "ScalableMain") \ + XX(PROFILE_H265_3DMAIN, 8, "3dMain") \ + XX(PROFILE_H265_SCREEN, 9, "ScreenContentCoding") \ + XX(PROFILE_H265_SCALABLE_RANGE_EXTENSIONS, 10, "ScalableRangeExtensions") \ + XX(PROFILE_H265_HIGH_SCREEN, 11, "HighThroughputScreenContentCoding") + +typedef enum { + H265ProfileIdcInvalid = -1, +#define XX(name, value, str) name = value, + H265_PROFILE_IDC_MAP(XX) +#undef XX + H265ProfileIdcMax +} H265ProfileIdc; + +#define H265_PROFILE_LEVEL_MAP(XX) \ + XX(30) \ + XX(60) \ + XX(63) \ + XX(90) \ + XX(93) \ + XX(120) \ + XX(123) \ + XX(150) \ + XX(153) \ + XX(156) \ + XX(180) \ + XX(183) \ + XX(186) + +typedef enum { + H265ProfileLevelInvalid = -1, +#define XX(value) H265_PROFILE_LEVEL_##value = value, + H265_PROFILE_LEVEL_MAP(XX) +#undef XX + H265ProfileLevelMax +} H265ProfileLevel; + +class H265RtpMap : public VideoRtpMap { +public: + H265RtpMap(uint8_t payload, uint32_t clock_rate, H265ProfileIdc profile_idc) + : VideoRtpMap("H265", payload, clock_rate) + , _profile_idc(profile_idc) { + _fmtp.emplace("level-asymmetry-allowed", "1"); + _fmtp.emplace("packetization-mode", "1"); + + _fmtp.emplace("profile-id", std::to_string(_profile_idc)); + _fmtp.emplace("tier-flag", std::to_string(_tier_flag)); + _fmtp.emplace("level-id", std::to_string(_profile_level)); + } + +private: + H265ProfileIdc _profile_idc; + int _tier_flag = 0; // 0: main tier; 1: high tier + H265ProfileLevel _profile_level = H265_PROFILE_LEVEL_30; +}; + +class VP9RtpMap : public VideoRtpMap { +public: + VP9RtpMap(uint8_t payload, uint32_t clock_rate, int profile_id) + : VideoRtpMap("VP9", payload, clock_rate) + , _profile_id(profile_id) { + _fmtp.emplace("profile-id", std::to_string(_profile_id)); + }; + +private: + int _profile_id = 1; // 0-3 +}; + +class AV1RtpMap : public VideoRtpMap { +public: + AV1RtpMap(uint8_t payload, uint32_t clock_rate, int profile_id) + : VideoRtpMap("AV1", payload, clock_rate) + , _profile_id(profile_id) { + // a=fmtp:45 level-idx=5;profile=0;tier=0 + _fmtp.emplace("profile-id", std::to_string(_profile_id)); + }; + +private: + int _profile_id = 0; // 0-2 +}; +} // namespace mediakit + +#endif // ZLMEDIAKIT_RTPMAP_H diff --git a/webrtc/SctpAssociation.hpp b/webrtc/SctpAssociation.hpp index 9c46d275..715c68a9 100644 --- a/webrtc/SctpAssociation.hpp +++ b/webrtc/SctpAssociation.hpp @@ -3,7 +3,7 @@ #ifdef ENABLE_SCTP #include -#include "Utils.hpp" +#include "Util/Byte.hpp" #include "Poller/EventPoller.h" namespace RTC @@ -62,8 +62,8 @@ namespace RTC return ( (len >= 12) && // Must have Source Port Number and Destination Port Number set to 5000 (hack). - (Utils::Byte::Get2Bytes(data, 0) == 5000) && - (Utils::Byte::Get2Bytes(data, 2) == 5000) + (toolkit::Byte::Get2Bytes(data, 0) == 5000) && + (toolkit::Byte::Get2Bytes(data, 2) == 5000) ); // clang-format on } diff --git a/webrtc/Sdp.cpp b/webrtc/Sdp.cpp index 358b5aa6..a6412e0b 100644 --- a/webrtc/Sdp.cpp +++ b/webrtc/Sdp.cpp @@ -1,1904 +1,2079 @@ -/* - * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. - * - * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). - * - * Use of this source code is governed by MIT-like license that can be found in the - * LICENSE file in the root of the source tree. All contributing project authors - * may be found in the AUTHORS file in the root of the source tree. - */ - -#include "Sdp.h" -#include "Rtsp/Rtsp.h" -#include "Common/config.h" -#include - -using namespace std; -using namespace toolkit; - -namespace mediakit { - -namespace Rtc { -#define RTC_FIELD "rtc." -const string kPreferredCodecA = RTC_FIELD "preferredCodecA"; -const string kPreferredCodecV = RTC_FIELD "preferredCodecV"; -static onceToken token([]() { - mINI::Instance()[kPreferredCodecA] = "PCMA,PCMU,opus,mpeg4-generic"; - mINI::Instance()[kPreferredCodecV] = "H264,H265,AV1,VP9,VP8"; -}); -} // namespace Rtc - -using onCreateSdpItem = function; -static map sdpItemCreator; - -template -void registerSdpItem() { - onCreateSdpItem func = [](const string &key, const string &value) { - auto ret = std::make_shared(); - ret->parse(value); - return ret; - }; - Item item; - sdpItemCreator.emplace(item.getKey(), std::move(func)); -} - -class DirectionInterface { -public: - virtual RtpDirection getDirection() const = 0; -}; - -class SdpDirectionSendonly : public SdpItem, public DirectionInterface { -public: - const char *getKey() const override { return getRtpDirectionString(getDirection()); } - RtpDirection getDirection() const override { return RtpDirection::sendonly; } -}; - -class SdpDirectionRecvonly : public SdpItem, public DirectionInterface { -public: - const char *getKey() const override { return getRtpDirectionString(getDirection()); } - RtpDirection getDirection() const override { return RtpDirection::recvonly; } -}; - -class SdpDirectionSendrecv : public SdpItem, public DirectionInterface { -public: - const char *getKey() const override { return getRtpDirectionString(getDirection()); } - RtpDirection getDirection() const override { return RtpDirection::sendrecv; } -}; - -class SdpDirectionInactive : public SdpItem, public DirectionInterface { -public: - const char *getKey() const override { return getRtpDirectionString(getDirection()); } - RtpDirection getDirection() const override { return RtpDirection::inactive; } -}; - -class DirectionInterfaceImp : public SdpItem, public DirectionInterface { -public: - DirectionInterfaceImp(RtpDirection direct) { direction = direct; } - const char *getKey() const override { return getRtpDirectionString(getDirection()); } - RtpDirection getDirection() const override { return direction; } - -private: - RtpDirection direction; -}; - -static bool registerAllItem() { - registerSdpItem>(); - registerSdpItem>(); - registerSdpItem>(); - registerSdpItem>(); - registerSdpItem>(); - registerSdpItem>(); - registerSdpItem>(); - registerSdpItem>(); - registerSdpItem>(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - registerSdpItem(); - return true; -} - -static map dtls_role_map = { - {"active", DtlsRole::active}, - {"passive", DtlsRole::passive}, - {"actpass", DtlsRole::actpass} -}; - -DtlsRole getDtlsRole(const string &str) { - auto it = dtls_role_map.find(str); - return it == dtls_role_map.end() ? DtlsRole::invalid : it->second; -} - -const char *getDtlsRoleString(DtlsRole role) { - switch (role) { - case DtlsRole::active: return "active"; - case DtlsRole::passive: return "passive"; - case DtlsRole::actpass: return "actpass"; - default: return "invalid"; - } -} - -static map direction_map = { - {"sendonly", RtpDirection::sendonly}, - {"recvonly", RtpDirection::recvonly}, - {"sendrecv", RtpDirection::sendrecv}, - {"inactive", RtpDirection::inactive} -}; - -RtpDirection getRtpDirection(const string &str) { - auto it = direction_map.find(str); - return it == direction_map.end() ? RtpDirection::invalid : it->second; -} - -const char *getRtpDirectionString(RtpDirection val) { - switch (val) { - case RtpDirection::sendonly: return "sendonly"; - case RtpDirection::recvonly: return "recvonly"; - case RtpDirection::sendrecv: return "sendrecv"; - case RtpDirection::inactive: return "inactive"; - default: return "invalid"; - } -} - -////////////////////////////////////////////////////////////////////////////////////////// - -string RtcSdpBase::toString() const { - _StrPrinter printer; - for (auto &item : items) { - printer << item->getKey() << "=" << item->toString() << "\r\n"; - } - return printer; -} - -RtpDirection RtcSdpBase::getDirection() const { - for (auto &item : items) { - auto attr = dynamic_pointer_cast(item); - if (attr) { - auto dir = dynamic_pointer_cast(attr->detail); - if (dir) { - return dir->getDirection(); - } - } - } - return RtpDirection::invalid; -} - -SdpItem::Ptr RtcSdpBase::getItem(char key_c, const char *attr_key) const { - std::string key(1, key_c); - for (auto item : items) { - if (strcasecmp(item->getKey(), key.data()) == 0) { - if (!attr_key) { - return item; - } - auto attr = dynamic_pointer_cast(item); - if (attr && !strcasecmp(attr->detail->getKey(), attr_key)) { - return attr->detail; - } - } - } - return SdpItem::Ptr(); -} - -////////////////////////////////////////////////////////////////////////// -int RtcSessionSdp::getVersion() const { - return atoi(getStringItem('v').data()); -} - -SdpOrigin RtcSessionSdp::getOrigin() const { - return getItemClass('o'); -} - -string RtcSessionSdp::getSessionName() const { - return getStringItem('s'); -} - -string RtcSessionSdp::getSessionInfo() const { - return getStringItem('i'); -} - -SdpTime RtcSessionSdp::getSessionTime() const { - return getItemClass('t'); -} - -SdpConnection RtcSessionSdp::getConnection() const { - return getItemClass('c'); -} - -SdpBandwidth RtcSessionSdp::getBandwidth() const { - return getItemClass('b'); -} - -string RtcSessionSdp::getUri() const { - return getStringItem('u'); -} - -string RtcSessionSdp::getEmail() const { - return getStringItem('e'); -} - -string RtcSessionSdp::getPhone() const { - return getStringItem('p'); -} - -string RtcSessionSdp::getTimeZone() const { - return getStringItem('z'); -} - -string RtcSessionSdp::getEncryptKey() const { - return getStringItem('k'); -} - -string RtcSessionSdp::getRepeatTimes() const { - return getStringItem('r'); -} - -////////////////////////////////////////////////////////////////////// - -void RtcSessionSdp::parse(const string &str) { - static auto flag = registerAllItem(); - RtcSdpBase *media = nullptr; - auto lines = split(str, "\n"); - std::set line_set; - for (auto &line : lines) { - trim(line); - if (line.size() < 3 || line[1] != '=') { - continue; - } - - if (!line_set.emplace(line).second) { - continue; - } - - auto key = line.substr(0, 1); - auto value = line.substr(2); - if (!strcasecmp(key.data(), "m")) { - medias.emplace_back(RtcSdpBase()); - media = &medias.back(); - line_set.clear(); - } - - SdpItem::Ptr item; - auto it = sdpItemCreator.find(key); - if (it != sdpItemCreator.end()) { - item = it->second(key, value); - } else { - item = std::make_shared(key); - item->parse(value); - } - if (media) { - media->addItem(std::move(item)); - } else { - addItem(std::move(item)); - } - } -} - -string RtcSessionSdp::toString() const { - _StrPrinter printer; - printer << RtcSdpBase::toString(); - for (auto &media : medias) { - printer << media.toString(); - } - - return printer; -} - -////////////////////////////////////////////////////////////////////////////////////////// - -#define CHECK_SDP(exp) CHECK(exp, "解析sdp ", getKey(), " 字段失败:", str) - -void SdpTime::parse(const string &str) { - CHECK_SDP(sscanf(str.data(), "%" SCNu64 " %" SCNu64, &start, &stop) == 2); -} - -string SdpTime::toString() const { - if (value.empty()) { - value = to_string(start) + " " + to_string(stop); - } - return SdpItem::toString(); -} - -void SdpOrigin::parse(const string &str) { - auto vec = split(str, " "); - CHECK_SDP(vec.size() == 6); - username = vec[0]; - session_id = vec[1]; - session_version = vec[2]; - nettype = vec[3]; - addrtype = vec[4]; - address = vec[5]; -} - -string SdpOrigin::toString() const { - if (value.empty()) { - value = username + " " + session_id + " " + session_version + " " + nettype + " " + addrtype + " " + address; - } - return SdpItem::toString(); -} - -void SdpConnection::parse(const string &str) { - auto vec = split(str, " "); - CHECK_SDP(vec.size() == 3); - nettype = vec[0]; - addrtype = vec[1]; - address = vec[2]; -} - -string SdpConnection::toString() const { - if (value.empty()) { - value = nettype + " " + addrtype + " " + address; - } - return SdpItem::toString(); -} - -void SdpBandwidth::parse(const string &str) { - auto vec = split(str, ":"); - CHECK_SDP(vec.size() == 2); - bwtype = vec[0]; - bandwidth = atoi(vec[1].data()); -} - -string SdpBandwidth::toString() const { - if (value.empty()) { - value = bwtype + ":" + to_string(bandwidth); - } - return SdpItem::toString(); -} - -void SdpMedia::parse(const string &str) { - auto vec = split(str, " "); - CHECK_SDP(vec.size() >= 4); - type = getTrackType(vec[0]); - CHECK_SDP(type != TrackInvalid); - port = atoi(vec[1].data()); - proto = vec[2]; - for (size_t i = 3; i < vec.size(); ++i) { - fmts.emplace_back(vec[i]); - } -} - -string SdpMedia::toString() const { - if (value.empty()) { - value = string(getTrackString(type)) + " " + to_string(port) + " " + proto; - for (auto fmt : fmts) { - value += ' '; - value += fmt; - } - } - return SdpItem::toString(); -} - -void SdpAttr::parse(const string &str) { - auto pos = str.find(':'); - auto key = pos == string::npos ? str : str.substr(0, pos); - auto value = pos == string::npos ? string() : str.substr(pos + 1); - auto it = sdpItemCreator.find(key); - if (it != sdpItemCreator.end()) { - detail = it->second(key, value); - } else { - detail = std::make_shared(key); - detail->parse(value); - } -} - -string SdpAttr::toString() const { - if (value.empty()) { - auto detail_value = detail->toString(); - if (detail_value.empty()) { - value = detail->getKey(); - } else { - value = string(detail->getKey()) + ":" + detail_value; - } - } - return SdpItem::toString(); -} - -void SdpAttrGroup::parse(const string &str) { - auto vec = split(str, " "); - CHECK_SDP(vec.size() >= 2); - type = vec[0]; - vec.erase(vec.begin()); - mids = std::move(vec); -} - -string SdpAttrGroup::toString() const { - if (value.empty()) { - value = type; - for (auto mid : mids) { - value += ' '; - value += mid; - } - } - return SdpItem::toString(); -} - -void SdpAttrMsidSemantic::parse(const string &str) { - auto vec = split(str, " "); - CHECK_SDP(vec.size() >= 1); - msid = vec[0]; - token = vec.size() > 1 ? vec[1] : ""; -} - -string SdpAttrMsidSemantic::toString() const { - if (value.empty()) { - if (token.empty()) { - value = string(" ") + msid; - } else { - value = string(" ") + msid + " " + token; - } - } - return SdpItem::toString(); -} - -void SdpAttrRtcp::parse(const string &str) { - auto vec = split(str, " "); - CHECK_SDP(vec.size() == 4); - port = atoi(vec[0].data()); - nettype = vec[1]; - addrtype = vec[2]; - address = vec[3]; -} - -string SdpAttrRtcp::toString() const { - if (value.empty()) { - value = to_string(port) + " " + nettype + " " + addrtype + " " + address; - } - return SdpItem::toString(); -} - -void SdpAttrIceOption::parse(const string &str) { - auto vec = split(str, " "); - for (auto &v : vec) { - if (!strcasecmp(v.data(), "trickle")) { - trickle = true; - continue; - } - if (!strcasecmp(v.data(), "renomination")) { - renomination = true; - continue; - } - } -} - -string SdpAttrIceOption::toString() const { - if (value.empty()) { - if (trickle && renomination) { - value = "trickle renomination"; - } else if (trickle) { - value = "trickle"; - } else if (renomination) { - value = "renomination"; - } - } - return value; -} - -void SdpAttrFingerprint::parse(const string &str) { - auto vec = split(str, " "); - CHECK_SDP(vec.size() == 2); - algorithm = vec[0]; - hash = vec[1]; -} - -string SdpAttrFingerprint::toString() const { - if (value.empty()) { - value = algorithm + " " + hash; - } - return SdpItem::toString(); -} - -void SdpAttrSetup::parse(const string &str) { - role = getDtlsRole(str); - CHECK_SDP(role != DtlsRole::invalid); -} - -string SdpAttrSetup::toString() const { - if (value.empty()) { - value = getDtlsRoleString(role); - } - return SdpItem::toString(); -} - -void SdpAttrExtmap::parse(const string &str) { - char buf[128] = { 0 }; - char direction_buf[32] = { 0 }; - if (sscanf(str.data(), "%" SCNd8 "/%31[^ ] %127s", &id, direction_buf, buf) != 3) { - CHECK_SDP(sscanf(str.data(), "%" SCNd8 " %127s", &id, buf) == 2); - direction = RtpDirection::sendrecv; - } else { - direction = getRtpDirection(direction_buf); - } - ext = buf; -} - -string SdpAttrExtmap::toString() const { - if (value.empty()) { - if (direction == RtpDirection::invalid || direction == RtpDirection::sendrecv) { - value = to_string((int)id) + " " + ext; - } else { - value = to_string((int)id) + "/" + getRtpDirectionString(direction) + " " + ext; - } - } - return SdpItem::toString(); -} - -void SdpAttrRtpMap::parse(const string &str) { - char buf[32] = { 0 }; - if (sscanf(str.data(), "%" SCNu8 " %31[^/]/%" SCNd32 "/%" SCNd32, &pt, buf, &sample_rate, &channel) != 4) { - CHECK_SDP(sscanf(str.data(), "%" SCNu8 " %31[^/]/%" SCNd32, &pt, buf, &sample_rate) == 3); - if (getTrackType(getCodecId(buf)) == TrackAudio) { - // 未指定通道数时,且为音频时,那么通道数默认为1 [AUTO-TRANSLATED:bd128fbd] - // If the number of channels is not specified and it is audio, the number of channels defaults to 1 - channel = 1; - } - } - codec = buf; -} - -string SdpAttrRtpMap::toString() const { - if (value.empty()) { - value = to_string((int)pt) + " " + codec + "/" + to_string(sample_rate); - if (channel) { - value += '/'; - value += to_string(channel); - } - } - return SdpItem::toString(); -} - -void SdpAttrRtcpFb::parse(const string &str_in) { - auto str = str_in + "\n"; - char rtcp_type_buf[32] = { 0 }; - CHECK_SDP(sscanf(str.data(), "%" SCNu8 " %31[^\n]", &pt, rtcp_type_buf) == 2); - rtcp_type = rtcp_type_buf; -} - -string SdpAttrRtcpFb::toString() const { - if (value.empty()) { - value = to_string((int)pt) + " " + rtcp_type; - } - return SdpItem::toString(); -} - -void SdpAttrFmtp::parse(const string &str) { - auto pos = str.find(' '); - CHECK_SDP(pos != string::npos); - pt = atoi(str.substr(0, pos).data()); - auto vec = split(str.substr(pos + 1), ";"); - for (auto &item : vec) { - trim(item); - auto pos = item.find('='); - if (pos == string::npos) { - fmtp.emplace(std::make_pair(item, "")); - } else { - fmtp.emplace(std::make_pair(item.substr(0, pos), item.substr(pos + 1))); - } - } - CHECK_SDP(!fmtp.empty()); -} - -string SdpAttrFmtp::toString() const { - if (value.empty()) { - value = to_string((int)pt); - int i = 0; - for (auto &pr : fmtp) { - value += (i++ ? ';' : ' '); - value += pr.first + "=" + pr.second; - } - } - return SdpItem::toString(); -} - -void SdpAttrSSRC::parse(const string &str_in) { - auto str = str_in + '\n'; - char attr_buf[32] = { 0 }; - char attr_val_buf[128] = { 0 }; - if (3 == sscanf(str.data(), "%" SCNu32 " %31[^:]:%127[^\n]", &ssrc, attr_buf, attr_val_buf)) { - attribute = attr_buf; - attribute_value = attr_val_buf; - } else if (2 == sscanf(str.data(), "%" SCNu32 " %31s[^\n]", &ssrc, attr_buf)) { - attribute = attr_buf; - } else { - CHECK_SDP(0); - } -} - -string SdpAttrSSRC::toString() const { - if (value.empty()) { - value = to_string(ssrc) + ' '; - value += attribute; - if (!attribute_value.empty()) { - value += ':'; - value += attribute_value; - } - } - return SdpItem::toString(); -} - -void SdpAttrSSRCGroup::parse(const string &str) { - auto vec = split(str, " "); - CHECK_SDP(vec.size() >= 3); - type = std::move(vec[0]); - CHECK(isFID() || isSIM()); - vec.erase(vec.begin()); - for (auto ssrc : vec) { - ssrcs.emplace_back((uint32_t)atoll(ssrc.data())); - } -} - -string SdpAttrSSRCGroup::toString() const { - if (value.empty()) { - value = type; - // 最少要求2个ssrc [AUTO-TRANSLATED:968acb83] - // At least 2 SSRCs are required - CHECK(ssrcs.size() >= 2); - for (auto &ssrc : ssrcs) { - value += ' '; - value += to_string(ssrc); - } - } - return SdpItem::toString(); -} - -void SdpAttrSctpMap::parse(const string &str) { - char subtypes_buf[64] = { 0 }; - CHECK_SDP(3 == sscanf(str.data(), "%" SCNu16 " %63[^ ] %" SCNd32, &port, subtypes_buf, &streams)); - subtypes = subtypes_buf; -} - -string SdpAttrSctpMap::toString() const { - if (value.empty()) { - value = to_string(port); - value += ' '; - value += subtypes; - value += ' '; - value += to_string(streams); - } - return SdpItem::toString(); -} - -void SdpAttrCandidate::parse(const string &str) { - char foundation_buf[40] = { 0 }; - char transport_buf[16] = { 0 }; - char address_buf[64] = { 0 }; - char type_buf[16] = { 0 }; - - // https://datatracker.ietf.org/doc/html/rfc5245#section-15.1 - CHECK_SDP(sscanf(str.data(), "%32[^ ] %" SCNu32 " %15[^ ] %" SCNu32 " %63[^ ] %" SCNu16 " typ %15[^ ]", - foundation_buf, &component, transport_buf, &priority, address_buf, &port, type_buf) == 7); - foundation = foundation_buf; - transport = transport_buf; - address = address_buf; - type = type_buf; - auto pos = str.find(type); - if (pos != string::npos) { - auto remain = str.substr(pos + type.size()); - trim(remain); - if (!remain.empty()) { - auto vec = split(remain, " "); - string key; - for (auto &item : vec) { - if (key.empty()) { - key = item; - } else { - arr.emplace_back(std::make_pair(std::move(key), std::move(item))); - } - } - } - } -} - -string SdpAttrCandidate::toString() const { - if (value.empty()) { - value = foundation + " " + to_string(component) + " " + transport + " " + to_string(priority) + " " + address + " " + to_string(port) + " typ " + type; - for (auto &pr : arr) { - value += ' '; - value += pr.first; - value += ' '; - value += pr.second; - } - } - return SdpItem::toString(); -} - -void SdpAttrSimulcast::parse(const string &str) { - // https://www.meetecho.com/blog/simulcast-janus-ssrc/ - // a=simulcast:send/recv q;h;f - // a=simulcast:send/recv [rid=]q;h;f - // a=simulcast: recv h;m;l - // - auto vec = split(str, " "); - CHECK_SDP(vec.size() == 2); - direction = vec[0]; - rids = split(vec[1], ";"); -} - -string SdpAttrSimulcast::toString() const { - if (value.empty()) { - value = direction + " "; - bool first = true; - for (auto &rid : rids) { - if (first) { - first = false; - } else { - value += ';'; - } - value += rid; - } - } - return SdpItem::toString(); -} - -void SdpAttrRid::parse(const string &str) { - auto vec = split(str, " "); - CHECK(vec.size() >= 2); - rid = vec[0]; - direction = vec[1]; -} - -string SdpAttrRid::toString() const { - if (value.empty()) { - value = rid + " " + direction; - } - return SdpItem::toString(); -} - -void RtcSession::loadFrom(const string &str) { - RtcSessionSdp sdp; - sdp.parse(str); - - version = sdp.getVersion(); - origin = sdp.getOrigin(); - session_name = sdp.getSessionName(); - session_info = sdp.getSessionInfo(); - connection = sdp.getConnection(); - time = sdp.getSessionTime(); - msid_semantic = sdp.getItemClass('a', "msid-semantic"); - for (auto &media : sdp.medias) { - auto mline = media.getItemClass('m'); - this->media.emplace_back(); - auto &rtc_media = this->media.back(); - rtc_media.mid = media.getStringItem('a', "mid"); - rtc_media.proto = mline.proto; - rtc_media.type = mline.type; - rtc_media.port = mline.port; - rtc_media.addr = media.getItemClass('c'); - rtc_media.bandwidth = media.getItemClass('b'); - rtc_media.ice_ufrag = media.getStringItem('a', "ice-ufrag"); - rtc_media.ice_pwd = media.getStringItem('a', "ice-pwd"); - rtc_media.role = media.getItemClass('a', "setup").role; - rtc_media.fingerprint = media.getItemClass('a', "fingerprint"); - if (rtc_media.fingerprint.empty()) { - rtc_media.fingerprint = sdp.getItemClass('a', "fingerprint"); - } - rtc_media.ice_lite = media.getItem('a', "ice-lite").operator bool(); - auto ice_options = media.getItemClass('a', "ice-options"); - rtc_media.ice_trickle = ice_options.trickle; - rtc_media.ice_renomination = ice_options.renomination; - rtc_media.candidate = media.getAllItem('a', "candidate"); - - if (mline.type == TrackType::TrackApplication) { - rtc_media.sctp_port = atoi(media.getStringItem('a', "sctp-port").data()); - rtc_media.sctpmap = media.getItemClass('a', "sctpmap"); - continue; - } - rtc_media.rtcp_addr = media.getItemClass('a', "rtcp"); - rtc_media.direction = media.getDirection(); - rtc_media.extmap = media.getAllItem('a', "extmap"); - rtc_media.rtcp_mux = media.getItem('a', "rtcp-mux").operator bool(); - rtc_media.rtcp_rsize = media.getItem('a', "rtcp-rsize").operator bool(); - - map rtc_ssrc_map; - auto ssrc_attr = media.getAllItem('a', "ssrc"); - for (auto &ssrc : ssrc_attr) { - auto &rtc_ssrc = rtc_ssrc_map[ssrc.ssrc]; - rtc_ssrc.ssrc = ssrc.ssrc; - if (!strcasecmp(ssrc.attribute.data(), "cname")) { - rtc_ssrc.cname = ssrc.attribute_value; - continue; - } - if (!strcasecmp(ssrc.attribute.data(), "msid")) { - rtc_ssrc.msid = ssrc.attribute_value; - continue; - } - if (!strcasecmp(ssrc.attribute.data(), "mslabel")) { - rtc_ssrc.mslabel = ssrc.attribute_value; - continue; - } - if (!strcasecmp(ssrc.attribute.data(), "label")) { - rtc_ssrc.label = ssrc.attribute_value; - continue; - } - } - - auto ssrc_groups = media.getAllItem('a', "ssrc-group"); - bool have_rtx_ssrc = false; - SdpAttrSSRCGroup *ssrc_group_sim = nullptr; - for (auto &group : ssrc_groups) { - if (group.isFID()) { - have_rtx_ssrc = true; - // ssrc-group:FID字段必须包含rtp/rtx的ssrc [AUTO-TRANSLATED:3da97d7d] - // The ssrc-group:FID field must contain the SSRCs of rtp/rtx - CHECK(group.ssrcs.size() == 2); - // 根据rtp ssrc找到对象 [AUTO-TRANSLATED:c0a56b42] - // Find the object based on the RTP SSRC - auto it = rtc_ssrc_map.find(group.ssrcs[0]); - CHECK(it != rtc_ssrc_map.end()); - // 设置rtx ssrc [AUTO-TRANSLATED:422e2a55] - // Set the RTX SSRC - it->second.rtx_ssrc = group.ssrcs[1]; - rtc_media.rtp_rtx_ssrc.emplace_back(it->second); - } else if (group.isSIM()) { - CHECK(!ssrc_group_sim); - ssrc_group_sim = &group; - } - } - - if (!have_rtx_ssrc) { - // 按照sdp顺序依次添加ssrc [AUTO-TRANSLATED:0996ba7e] - // Add SSRCs in the order of SDP - for (auto &attr : ssrc_attr) { - if (attr.attribute == "cname") { - rtc_media.rtp_rtx_ssrc.emplace_back(rtc_ssrc_map[attr.ssrc]); - } - } - } - - auto simulcast = media.getItemClass('a', "simulcast"); - if (!simulcast.empty()) { - // a=rid:h send - // a=rid:m send - // a=rid:l send - // a=simulcast:send h;m;l - // 风格的simulcast [AUTO-TRANSLATED:94ac2d55] - // Style of simulcast - unordered_set rid_map; - for (auto &rid : simulcast.rids) { - rid_map.emplace(rid); - } - for (auto &rid : media.getAllItem('a', "rid")) { - CHECK(rid.direction == simulcast.direction); - CHECK(rid_map.find(rid.rid) != rid_map.end()); - } - // simulcast最少要求2种方案 [AUTO-TRANSLATED:31732a7a] - // Simulcast requires at least 2 schemes - CHECK(simulcast.rids.size() >= 2); - rtc_media.rtp_rids = simulcast.rids; - } - - if (ssrc_group_sim) { - // 指定了a=ssrc-group:SIM [AUTO-TRANSLATED:5732661e] - // a=ssrc-group:SIM is specified - for (auto ssrc : ssrc_group_sim->ssrcs) { - auto it = rtc_ssrc_map.find(ssrc); - CHECK(it != rtc_ssrc_map.end()); - rtc_media.rtp_ssrc_sim.emplace_back(it->second); - } - } else if (!rtc_media.rtp_rids.empty()) { - // 未指定a=ssrc-group:SIM, 但是指定了a=simulcast, 那么只能根据ssrc顺序来对应rid顺序 [AUTO-TRANSLATED:b198a817] - // a=ssrc-group:SIM is not specified, but a=simulcast is specified, so the RID order can only be matched according to the SSRC order - rtc_media.rtp_ssrc_sim = rtc_media.rtp_rtx_ssrc; - } - - if (!rtc_media.supportSimulcast()) { - // 不支持simulcast的情况下,最多一组ssrc [AUTO-TRANSLATED:3ea8ed65] - // In the case of not supporting simulcast, there is at most one group of SSRCs - CHECK(rtc_media.rtp_rtx_ssrc.size() <= 1); - } else { - // simulcast的情况下,要么没有指定ssrc,要么指定的ssrc个数与rid个数一致 [AUTO-TRANSLATED:1d45ce03] - // In the case of simulcast, either no SSRC is specified or the number of specified SSRCs is consistent with the number of RIDs - // CHECK(rtc_media.rtp_ssrc_sim.empty() || rtc_media.rtp_ssrc_sim.size() == rtc_media.rtp_rids.size()); - } - - auto rtpmap_arr = media.getAllItem('a', "rtpmap"); - auto rtcpfb_arr = media.getAllItem('a', "rtcp-fb"); - auto fmtp_aar = media.getAllItem('a', "fmtp"); - // 方便根据pt查找rtpmap,一个pt必有一条 [AUTO-TRANSLATED:c3673faa] - // Convenient to find rtpmap based on pt, one pt must have one - map rtpmap_map; - // 方便根据pt查找rtcp-fb,一个pt可能有多条或0条 [AUTO-TRANSLATED:38361f68] - // Convenient to find rtcp-fb based on pt, one pt may have multiple or 0 - multimap rtcpfb_map; - // 方便根据pt查找fmtp,一个pt最多一条 [AUTO-TRANSLATED:be5d652d] - // Convenient to find fmtp based on pt, one pt has at most one - map fmtp_map; - - for (auto &rtpmap : rtpmap_arr) { - // 添加失败,有多条 [AUTO-TRANSLATED:717782c0] - // Add failed, there are multiple - CHECK(rtpmap_map.emplace(rtpmap.pt, rtpmap).second, "该pt存在多条a=rtpmap:", (int)rtpmap.pt); - } - for (auto &rtpfb : rtcpfb_arr) { - rtcpfb_map.emplace(rtpfb.pt, rtpfb); - } - for (auto &fmtp : fmtp_aar) { - // 添加失败,有多条 [AUTO-TRANSLATED:717782c0] - // Add failed, there are multiple - CHECK(fmtp_map.emplace(fmtp.pt, fmtp).second, "该pt存在多条a=fmtp:", (int)fmtp.pt); - } - for (auto &item : mline.fmts) { - auto pt = atoi(item.c_str()); - CHECK(pt < 0xFF, "invalid payload type: ", item); - // 遍历所有编码方案的pt [AUTO-TRANSLATED:40f2db36] - // Traverse the pt of all encoding schemes - rtc_media.plan.emplace_back(); - auto &plan = rtc_media.plan.back(); - auto rtpmap_it = rtpmap_map.find(pt); - if (rtpmap_it == rtpmap_map.end()) { - plan.pt = pt; - plan.codec = RtpPayload::getName(pt); - plan.sample_rate = RtpPayload::getClockRate(pt); - plan.channel = RtpPayload::getAudioChannel(pt); - } else { - plan.pt = rtpmap_it->second.pt; - plan.codec = rtpmap_it->second.codec; - plan.sample_rate = rtpmap_it->second.sample_rate; - plan.channel = rtpmap_it->second.channel; - } - - auto fmtp_it = fmtp_map.find(pt); - if (fmtp_it != fmtp_map.end()) { - plan.fmtp = fmtp_it->second.fmtp; - } - for (auto rtpfb_it = rtcpfb_map.find(pt); rtpfb_it != rtcpfb_map.end() && rtpfb_it->second.pt == pt; ++rtpfb_it) { - plan.rtcp_fb.emplace(rtpfb_it->second.rtcp_type); - } - } - } - - group = sdp.getItemClass('a', "group"); -} - -void RtcSdpBase::toRtsp() { - for (auto it = items.begin(); it != items.end();) { - switch ((*it)->getKey()[0]) { - case 'v': - case 'o': - case 's': - case 'i': - case 't': - case 'c': - case 'b': { - ++it; - break; - } - - case 'm': { - auto m = dynamic_pointer_cast(*it); - CHECK(m); - m->proto = "RTP/AVP"; - ++it; - break; - } - case 'a': { - auto attr = dynamic_pointer_cast(*it); - CHECK(attr); - if (!strcasecmp(attr->detail->getKey(), "rtpmap") || !strcasecmp(attr->detail->getKey(), "fmtp")) { - ++it; - break; - } - } - default: { - it = items.erase(it); - break; - } - } - } -} - -string RtcSession::toRtspSdp() const { - RtcSession copy = *this; - copy.media.clear(); - for (auto &m : media) { - switch (m.type) { - case TrackAudio: - case TrackVideo: { - if (m.direction != RtpDirection::inactive) { - copy.media.emplace_back(m); - copy.media.back().plan.resize(1); - } - break; - } - default: continue; - } - } - - CHECK(!copy.media.empty()); - auto sdp = copy.toRtcSessionSdp(); - sdp->toRtsp(); - int i = 0; - for (auto &m : sdp->medias) { - m.toRtsp(); - m.addAttr(std::make_shared("control", string("trackID=") + to_string(i++))); - } - return sdp->toString(); -} - -void addSdpAttrSSRC(const RtcSSRC &rtp_ssrc, RtcSdpBase &media, uint32_t ssrc_num) { - assert(ssrc_num); - SdpAttrSSRC ssrc; - ssrc.ssrc = ssrc_num; - - ssrc.attribute = "cname"; - ssrc.attribute_value = rtp_ssrc.cname; - media.addAttr(std::make_shared(ssrc)); - - if (!rtp_ssrc.msid.empty()) { - ssrc.attribute = "msid"; - ssrc.attribute_value = rtp_ssrc.msid; - media.addAttr(std::make_shared(ssrc)); - } - - if (!rtp_ssrc.mslabel.empty()) { - ssrc.attribute = "mslabel"; - ssrc.attribute_value = rtp_ssrc.mslabel; - media.addAttr(std::make_shared(ssrc)); - } - - if (!rtp_ssrc.label.empty()) { - ssrc.attribute = "label"; - ssrc.attribute_value = rtp_ssrc.label; - media.addAttr(std::make_shared(ssrc)); - } -} - -RtcSessionSdp::Ptr RtcSession::toRtcSessionSdp() const { - RtcSessionSdp::Ptr ret = std::make_shared(); - auto &sdp = *ret; - sdp.addItem(std::make_shared>(to_string(version))); - sdp.addItem(std::make_shared(origin)); - sdp.addItem(std::make_shared>(session_name)); - if (!session_info.empty()) { - sdp.addItem(std::make_shared>(session_info)); - } - sdp.addItem(std::make_shared(time)); - if (connection.empty()) { - sdp.addItem(std::make_shared(connection)); - } - sdp.addAttr(std::make_shared(group)); - sdp.addAttr(std::make_shared(msid_semantic)); - - bool ice_lite = false; - - for (auto &m : media) { - sdp.medias.emplace_back(); - auto &sdp_media = sdp.medias.back(); - auto mline = std::make_shared(); - mline->type = m.type; - mline->port = m.port; - mline->proto = m.proto; - for (auto &p : m.plan) { - mline->fmts.emplace_back(to_string((int)p.pt)); - } - if (m.type == TrackApplication) { - mline->fmts.emplace_back("webrtc-datachannel"); - } - sdp_media.addItem(std::move(mline)); - sdp_media.addItem(std::make_shared(m.addr)); - if (!m.bandwidth.empty() && m.type != TrackAudio) { - sdp_media.addItem(std::make_shared(m.bandwidth)); - } - if (!m.rtcp_addr.empty()) { - sdp_media.addAttr(std::make_shared(m.rtcp_addr)); - } - - sdp_media.addAttr(std::make_shared(m.ice_ufrag)); - sdp_media.addAttr(std::make_shared(m.ice_pwd)); - if (m.ice_trickle || m.ice_renomination) { - auto attr = std::make_shared(); - attr->trickle = m.ice_trickle; - attr->renomination = m.ice_renomination; - sdp_media.addAttr(attr); - } - sdp_media.addAttr(std::make_shared(m.fingerprint)); - sdp_media.addAttr(std::make_shared(m.role)); - sdp_media.addAttr(std::make_shared(m.mid)); - if (m.ice_lite) { - sdp_media.addAttr(std::make_shared("ice-lite")); - ice_lite = true; - } - for (auto &ext : m.extmap) { - sdp_media.addAttr(std::make_shared(ext)); - } - if (m.direction != RtpDirection::invalid) { - sdp_media.addAttr(std::make_shared(m.direction)); - } - if (m.rtcp_mux) { - sdp_media.addAttr(std::make_shared("rtcp-mux")); - } - if (m.rtcp_rsize) { - sdp_media.addAttr(std::make_shared("rtcp-rsize")); - } - - if (m.type != TrackApplication) { - for (auto &p : m.plan) { - auto rtp_map = std::make_shared(); - rtp_map->pt = p.pt; - rtp_map->codec = p.codec; - rtp_map->sample_rate = p.sample_rate; - rtp_map->channel = p.channel; - // 添加a=rtpmap [AUTO-TRANSLATED:8bef5d64] - // Add a=rtpmap - sdp_media.addAttr(std::move(rtp_map)); - - for (auto &fb : p.rtcp_fb) { - auto rtcp_fb = std::make_shared(); - rtcp_fb->pt = p.pt; - rtcp_fb->rtcp_type = fb; - // 添加a=rtcp-fb [AUTO-TRANSLATED:11754b43] - // Add a=rtcp-fb - sdp_media.addAttr(std::move(rtcp_fb)); - } - - if (!p.fmtp.empty()) { - auto fmtp = std::make_shared(); - fmtp->pt = p.pt; - fmtp->fmtp = p.fmtp; - // 添加a=fmtp [AUTO-TRANSLATED:594a4425] - // Add a=fmtp - sdp_media.addAttr(std::move(fmtp)); - } - } - - { - // 添加a=msid字段 [AUTO-TRANSLATED:cf2c1471] - // Add a=msid field - if (!m.rtp_rtx_ssrc.empty()) { - if (!m.rtp_rtx_ssrc[0].msid.empty()) { - auto msid = std::make_shared(); - msid->parse(m.rtp_rtx_ssrc[0].msid); - sdp_media.addAttr(std::move(msid)); - } - } - } - - { - for (auto &ssrc : m.rtp_rtx_ssrc) { - // 添加a=ssrc字段 [AUTO-TRANSLATED:75ca5225] - // Add a=ssrc field - CHECK(!ssrc.empty()); - addSdpAttrSSRC(ssrc, sdp_media, ssrc.ssrc); - if (ssrc.rtx_ssrc) { - addSdpAttrSSRC(ssrc, sdp_media, ssrc.rtx_ssrc); - - // 生成a=ssrc-group:FID字段 [AUTO-TRANSLATED:22b1f966] - // Generate a=ssrc-group:FID field - // 有rtx ssrc [AUTO-TRANSLATED:fece8076] - // There is rtx ssrc - auto group = std::make_shared(); - group->type = "FID"; - group->ssrcs.emplace_back(ssrc.ssrc); - group->ssrcs.emplace_back(ssrc.rtx_ssrc); - sdp_media.addAttr(std::move(group)); - } - } - } - - { - if (m.rtp_ssrc_sim.size() >= 2) { - // simulcast 要求 2~3路 [AUTO-TRANSLATED:3237ffca] - // Simulcast requires 2~3 channels - auto group = std::make_shared(); - for (auto &ssrc : m.rtp_ssrc_sim) { - group->ssrcs.emplace_back(ssrc.ssrc); - } - // 添加a=ssrc-group:SIM字段 [AUTO-TRANSLATED:46b04aae] - // Add a=ssrc-group:SIM field - group->type = "SIM"; - sdp_media.addAttr(std::move(group)); - } - - if (m.rtp_rids.size() >= 2) { - auto simulcast = std::make_shared(); - simulcast->direction = "recv"; - simulcast->rids = m.rtp_rids; - sdp_media.addAttr(std::move(simulcast)); - - for (auto &rid : m.rtp_rids) { - auto attr_rid = std::make_shared(); - attr_rid->rid = rid; - attr_rid->direction = "recv"; - sdp_media.addAttr(std::move(attr_rid)); - } - } - } - - } else { - if (!m.sctpmap.empty()) { - sdp_media.addAttr(std::make_shared(m.sctpmap)); - } - sdp_media.addAttr(std::make_shared("sctp-port", to_string(m.sctp_port))); - } - - for (auto &cand : m.candidate) { - if (cand.port) { - sdp_media.addAttr(std::make_shared(cand)); - } - } - } - if (ice_lite) { - sdp.addAttr(std::make_shared("ice-lite")); - } - return ret; -} - -string RtcSession::toString() const { - return toRtcSessionSdp()->toString(); -} - -string RtcCodecPlan::getFmtp(const char *key) const { - for (auto &item : fmtp) { - if (strcasecmp(item.first.data(), key) == 0) { - return item.second; - } - } - return ""; -} - -const RtcCodecPlan *RtcMedia::getPlan(uint8_t pt) const { - for (auto &item : plan) { - if (item.pt == pt) { - return &item; - } - } - return nullptr; -} - -const RtcCodecPlan *RtcMedia::getPlan(const char *codec) const { - for (auto &item : plan) { - if (strcasecmp(item.codec.data(), codec) == 0) { - return &item; - } - } - return nullptr; -} - -const RtcCodecPlan *RtcMedia::getRelatedRtxPlan(uint8_t pt) const { - for (auto &item : plan) { - if (strcasecmp(item.codec.data(), "rtx") == 0) { - auto apt = atoi(item.getFmtp("apt").data()); - if (pt == apt) { - return &item; - } - } - } - return nullptr; -} - -uint32_t RtcMedia::getRtpSSRC() const { - if (rtp_rtx_ssrc.size()) { - return rtp_rtx_ssrc[0].ssrc; - } - return 0; -} - -uint32_t RtcMedia::getRtxSSRC() const { - if (rtp_rtx_ssrc.size()) { - return rtp_rtx_ssrc[0].rtx_ssrc; - } - return 0; -} - -bool RtcMedia::supportSimulcast() const { - if (!rtp_rids.empty()) { - return true; - } - if (!rtp_ssrc_sim.empty()) { - return true; - } - return false; -} - -void RtcMedia::checkValid() const { - CHECK(type != TrackInvalid); - CHECK(!mid.empty()); - CHECK(!proto.empty()); - CHECK(direction != RtpDirection::invalid || type == TrackApplication); - CHECK(!plan.empty() || type == TrackApplication); - CHECK(type == TrackApplication || rtcp_mux, "只支持rtcp-mux模式"); - - bool send_rtp = (direction == RtpDirection::sendonly || direction == RtpDirection::sendrecv); - if (!supportSimulcast()) { - // 非simulcast时,检查有没有指定rtp ssrc [AUTO-TRANSLATED:e2d53f8a] - // When not simulcast, check if the RTP SSRC is specified - CHECK(!rtp_rtx_ssrc.empty() || !send_rtp); - } - -#if 0 - // todo 发现Firefox(88.0)在mac平台下,开启rtx后没有指定ssrc [AUTO-TRANSLATED:9112d91a] - // todo Found that Firefox (88.0) on the mac platform does not specify ssrc when rtx is enabled - auto rtx_plan = getPlan("rtx"); - if (rtx_plan) { - // 开启rtx后必须指定rtx_ssrc [AUTO-TRANSLATED:c527f68d] - // RTX must be specified after rtx_ssrc is enabled - CHECK(rtp_rtx_ssrc.size() >= 2 || !send_rtp); - } -#endif -} - -void RtcSession::checkValid() const { - CHECK(version == 0); - CHECK(!origin.empty()); - CHECK(!session_name.empty()); - CHECK(!msid_semantic.empty()); - CHECK(!media.empty()); - CHECK(!group.mids.empty() && group.mids.size() <= media.size(), "只支持group BUNDLE模式"); - - bool have_active_media = false; - for (auto &item : media) { - item.checkValid(); - - if (TrackApplication == item.type) { - have_active_media = true; - } - switch (item.direction) { - case RtpDirection::sendrecv: - case RtpDirection::sendonly: - case RtpDirection::recvonly: have_active_media = true; break; - default: break; - } - } - CHECK(have_active_media, "必须确保最少有一个活跃的track"); -} - -const RtcMedia *RtcSession::getMedia(TrackType type) const { - for (auto &m : media) { - if (m.type == type) { - return &m; - } - } - return nullptr; -} - -bool RtcSession::supportRtcpFb(const string &name, TrackType type) const { - auto media = getMedia(type); - if (!media) { - return false; - } - auto &ref = media->plan[0].rtcp_fb; - return ref.find(name) != ref.end(); -} - -bool RtcSession::supportSimulcast() const { - for (auto &m : media) { - if (m.supportSimulcast()) { - return true; - } - } - return false; -} - -bool RtcSession::isOnlyDatachannel() const { - return 1 == media.size() && TrackApplication == media[0].type; -} - -string const SdpConst::kTWCCRtcpFb = "transport-cc"; -string const SdpConst::kRembRtcpFb = "goog-remb"; - -void RtcConfigure::RtcTrackConfigure::enableTWCC(bool enable) { - if (!enable) { - rtcp_fb.erase(SdpConst::kTWCCRtcpFb); - extmap.erase(RtpExtType::transport_cc); - } else { - rtcp_fb.emplace(SdpConst::kTWCCRtcpFb); - extmap.emplace(RtpExtType::transport_cc, RtpDirection::sendrecv); - } -} - -void RtcConfigure::RtcTrackConfigure::enableREMB(bool enable) { - if (!enable) { - rtcp_fb.erase(SdpConst::kRembRtcpFb); - extmap.erase(RtpExtType::abs_send_time); - } else { - rtcp_fb.emplace(SdpConst::kRembRtcpFb); - extmap.emplace(RtpExtType::abs_send_time, RtpDirection::sendrecv); - } -} - -static vector toCodecArray(const string &str) { - vector ret; - auto vec = split(str, ","); - for (auto &s : vec) { - auto codec = getCodecId(trim(s)); - if (codec != CodecInvalid) { - ret.emplace_back(codec); - } - } - return ret; -} - -void RtcConfigure::RtcTrackConfigure::setDefaultSetting(TrackType type) { - rtcp_mux = true; - rtcp_rsize = false; - group_bundle = true; - support_rtx = true; - support_red = false; - support_ulpfec = false; - ice_lite = true; - ice_trickle = true; - ice_renomination = false; - switch (type) { - case TrackAudio: { - // 此处调整偏好的编码格式优先级 [AUTO-TRANSLATED:b8719e66] - // Adjust the priority of preferred encoding formats here - GET_CONFIG_FUNC(vector, s_preferred_codec, Rtc::kPreferredCodecA, toCodecArray); - CHECK(!s_preferred_codec.empty(), "rtc音频偏好codec不能为空"); - preferred_codec = s_preferred_codec; - - rtcp_fb = { SdpConst::kTWCCRtcpFb, SdpConst::kRembRtcpFb }; - extmap = { { RtpExtType::ssrc_audio_level, RtpDirection::sendrecv }, - { RtpExtType::csrc_audio_level, RtpDirection::sendrecv }, - { RtpExtType::abs_send_time, RtpDirection::sendrecv }, - { RtpExtType::transport_cc, RtpDirection::sendrecv }, - // rtx重传rtp时,忽略sdes_mid类型的rtp ext,实测发现Firefox在接收rtx时,如果存在sdes_mid的ext,将导致无法播放 [AUTO-TRANSLATED:221df025] - // When rtx retransmits rtp, ignore the rtp ext of sdes_mid type. It is found that Firefox cannot play when receiving rtx if there is an ext of sdes_mid - //{RtpExtType::sdes_mid,RtpDirection::sendrecv}, - { RtpExtType::sdes_rtp_stream_id, RtpDirection::sendrecv }, - { RtpExtType::sdes_repaired_rtp_stream_id, RtpDirection::sendrecv } }; - break; - } - case TrackVideo: { - // 此处调整偏好的编码格式优先级 [AUTO-TRANSLATED:b8719e66] - // Adjust the priority of preferred encoding formats here - GET_CONFIG_FUNC(vector, s_preferred_codec, Rtc::kPreferredCodecV, toCodecArray); - CHECK(!s_preferred_codec.empty(), "rtc视频偏好codec不能为空"); - preferred_codec = s_preferred_codec; - - rtcp_fb = { SdpConst::kTWCCRtcpFb, SdpConst::kRembRtcpFb, "nack", "ccm fir", "nack pli" }; - extmap = { { RtpExtType::abs_send_time, RtpDirection::sendrecv }, - { RtpExtType::transport_cc, RtpDirection::sendrecv }, - // rtx重传rtp时,忽略sdes_mid类型的rtp ext,实测发现Firefox在接收rtx时,如果存在sdes_mid的ext,将导致无法播放 [AUTO-TRANSLATED:221df025] - // When rtx retransmits rtp, ignore the rtp ext of sdes_mid type. It is found that Firefox cannot play when receiving rtx if there is an ext of sdes_mid - //{RtpExtType::sdes_mid,RtpDirection::sendrecv}, - { RtpExtType::sdes_rtp_stream_id, RtpDirection::sendrecv }, - { RtpExtType::sdes_repaired_rtp_stream_id, RtpDirection::sendrecv }, - { RtpExtType::video_timing, RtpDirection::sendrecv }, - { RtpExtType::color_space, RtpDirection::sendrecv }, - { RtpExtType::video_content_type, RtpDirection::sendrecv }, - { RtpExtType::playout_delay, RtpDirection::sendrecv }, - // 手机端推webrtc 会带有旋转角度,rtc协议能正常播放 其他协议拉流画面旋转 [AUTO-TRANSLATED:3f2f9e0e] - // Mobile push webrtc will have a rotation angle, rtc protocol can play normally, other protocols pull stream picture rotation - //{RtpExtType::video_orientation, RtpDirection::sendrecv}, - { RtpExtType::toffset, RtpDirection::sendrecv }, - { RtpExtType::framemarking, RtpDirection::sendrecv } }; - break; - } - case TrackApplication: { - break; - } - default: break; - } -} - -void RtcConfigure::setDefaultSetting(string ice_ufrag, string ice_pwd, RtpDirection direction, const SdpAttrFingerprint &fingerprint) { - video.setDefaultSetting(TrackVideo); - audio.setDefaultSetting(TrackAudio); - application.setDefaultSetting(TrackApplication); - - video.ice_ufrag = audio.ice_ufrag = application.ice_ufrag = std::move(ice_ufrag); - video.ice_pwd = audio.ice_pwd = application.ice_pwd = std::move(ice_pwd); - video.direction = audio.direction = application.direction = direction; - video.fingerprint = audio.fingerprint = application.fingerprint = fingerprint; -} - -void RtcConfigure::addCandidate(const SdpAttrCandidate &candidate, TrackType type) { - switch (type) { - case TrackAudio: { - audio.candidate.emplace_back(candidate); - break; - } - case TrackVideo: { - video.candidate.emplace_back(candidate); - break; - } - case TrackApplication: { - application.candidate.emplace_back(candidate); - break; - } - default: { - if (audio.group_bundle) { - audio.candidate.emplace_back(candidate); - } - if (video.group_bundle) { - video.candidate.emplace_back(candidate); - } - if (application.group_bundle) { - application.candidate.emplace_back(candidate); - } - break; - } - } -} - -void RtcConfigure::enableTWCC(bool enable, TrackType type) { - switch (type) { - case TrackAudio: { - audio.enableTWCC(enable); - break; - } - case TrackVideo: { - video.enableTWCC(enable); - break; - } - default: { - audio.enableTWCC(enable); - video.enableTWCC(enable); - break; - } - } -} - -void RtcConfigure::enableREMB(bool enable, TrackType type) { - switch (type) { - case TrackAudio: { - audio.enableREMB(enable); - break; - } - case TrackVideo: { - video.enableREMB(enable); - break; - } - default: { - audio.enableREMB(enable); - video.enableREMB(enable); - break; - } - } -} - -shared_ptr RtcConfigure::createAnswer(const RtcSession &offer) const { - shared_ptr ret = std::make_shared(); - ret->version = offer.version; - ret->origin = offer.origin; - ret->session_name = offer.session_name; - ret->msid_semantic = offer.msid_semantic; - - for (auto &m : offer.media) { - matchMedia(ret, m); - } - - // 设置音视频端口复用 [AUTO-TRANSLATED:ffe27d17] - // Set audio and video port multiplexing - if (!offer.group.mids.empty()) { - for (auto &m : ret->media) { - // The remote end has rejected (port 0) the m-section, so it should not be putting its mid in the group attribute. - if (m.port) { - ret->group.mids.emplace_back(m.mid); - } - } - } - return ret; -} - -static RtpDirection matchDirection(RtpDirection offer_direction, RtpDirection supported) { - switch (offer_direction) { - case RtpDirection::sendonly: { - if (supported != RtpDirection::recvonly && supported != RtpDirection::sendrecv) { - // 我们不支持接收 [AUTO-TRANSLATED:e4ef4034] - // We do not support receiving - return RtpDirection::inactive; - } - return RtpDirection::recvonly; - } - - case RtpDirection::recvonly: { - if (supported != RtpDirection::sendonly && supported != RtpDirection::sendrecv) { - // 我们不支持发送 [AUTO-TRANSLATED:6505a226] - // We do not support sending - return RtpDirection::inactive; - } - return RtpDirection::sendonly; - } - - // 对方支持发送接收,那么最终能力根据配置来决定 [AUTO-TRANSLATED:d234d603] - // The other party supports sending and receiving, so the final capability is determined by the configuration - case RtpDirection::sendrecv: return (supported == RtpDirection::invalid ? RtpDirection::inactive : supported); - case RtpDirection::inactive: return RtpDirection::inactive; - default: return RtpDirection::invalid; - } -} - -static DtlsRole mathDtlsRole(DtlsRole role) { - switch (role) { - case DtlsRole::actpass: - case DtlsRole::active: return DtlsRole::passive; - case DtlsRole::passive: return DtlsRole::active; - default: CHECK(0, "invalid role:", getDtlsRoleString(role)); return DtlsRole::passive; - } -} - -void RtcConfigure::matchMedia(const std::shared_ptr &ret, const RtcMedia &offer_media) const { - bool check_profile = true; - bool check_codec = true; - const RtcTrackConfigure *cfg_ptr = nullptr; - - switch (offer_media.type) { - case TrackAudio: cfg_ptr = &audio; break; - case TrackVideo: cfg_ptr = &video; break; - case TrackApplication: cfg_ptr = &application; break; - default: return; - } - auto &configure = *cfg_ptr; - -RETRY: - - if (offer_media.type == TrackApplication) { - RtcMedia answer_media = offer_media; - answer_media.role = mathDtlsRole(offer_media.role); - answer_media.ice_ufrag = configure.ice_ufrag; - answer_media.ice_pwd = configure.ice_pwd; - answer_media.fingerprint = configure.fingerprint; - answer_media.ice_lite = configure.ice_lite; -#ifdef ENABLE_SCTP - answer_media.candidate = configure.candidate; -#else - answer_media.port = 0; - WarnL << "answer sdp忽略application mline, 请安装usrsctp后再测试datachannel功能"; -#endif - ret->media.emplace_back(answer_media); - return; - } - for (auto &codec : configure.preferred_codec) { - if (offer_media.ice_lite && configure.ice_lite) { - WarnL << "answer sdp配置为ice_lite模式,与offer sdp中的ice_lite模式冲突"; - continue; - } - const RtcCodecPlan *selected_plan = nullptr; - for (auto &plan : offer_media.plan) { - // 先检查编码格式是否为偏好 [AUTO-TRANSLATED:b7fb32a0] - // First check if the encoding format is preferred - if (check_codec && getCodecId(plan.codec) != codec) { - continue; - } - // 命中偏好的编码格式,然后检查规格 [AUTO-TRANSLATED:a859c839] - // Hit the preferred encoding format, then check the specifications - if (check_profile && !onCheckCodecProfile(plan, codec)) { - continue; - } - // 找到中意的codec [AUTO-TRANSLATED:4b5eebfd] - // Find the desired codec - selected_plan = &plan; - break; - } - if (!selected_plan) { - // offer中该媒体的所有的codec都不支持 [AUTO-TRANSLATED:3b57b86f] - // All codecs for this media in the offer are not supported - continue; - } - RtcMedia answer_media; - answer_media.type = offer_media.type; - answer_media.mid = offer_media.mid; - answer_media.proto = offer_media.proto; - answer_media.port = offer_media.port; - answer_media.addr = offer_media.addr; - answer_media.bandwidth = offer_media.bandwidth; - answer_media.rtcp_addr = offer_media.rtcp_addr; - answer_media.rtcp_mux = offer_media.rtcp_mux && configure.rtcp_mux; - answer_media.rtcp_rsize = offer_media.rtcp_rsize && configure.rtcp_rsize; - answer_media.ice_trickle = offer_media.ice_trickle && configure.ice_trickle; - answer_media.ice_renomination = offer_media.ice_renomination && configure.ice_renomination; - answer_media.ice_ufrag = configure.ice_ufrag; - answer_media.ice_pwd = configure.ice_pwd; - answer_media.fingerprint = configure.fingerprint; - answer_media.ice_lite = configure.ice_lite; - answer_media.candidate = configure.candidate; - // copy simulicast setting - answer_media.rtp_rids = offer_media.rtp_rids; - answer_media.rtp_ssrc_sim = offer_media.rtp_ssrc_sim; - - answer_media.role = mathDtlsRole(offer_media.role); - - // 如果codec匹配失败,那么禁用该track [AUTO-TRANSLATED:037de9a8] - // If the codec matching fails, then disable the track - answer_media.direction = check_codec ? matchDirection(offer_media.direction, configure.direction) : RtpDirection::inactive; - if (answer_media.direction == RtpDirection::invalid) { - continue; - } - if (answer_media.direction == RtpDirection::sendrecv) { - // 如果是收发双向,那么我们拷贝offer sdp的ssrc,确保ssrc一致 [AUTO-TRANSLATED:d4a621f2] - // If it is bidirectional, then we copy the offer sdp ssrc to ensure ssrc consistency - answer_media.rtp_rtx_ssrc = offer_media.rtp_rtx_ssrc; - } - - // 添加媒体plan [AUTO-TRANSLATED:3f730050] - // Add media plan - answer_media.plan.emplace_back(*selected_plan); - onSelectPlan(answer_media.plan.back(), codec); - - set pt_selected = { selected_plan->pt }; - - // 添加rtx,red,ulpfec plan [AUTO-TRANSLATED:1abff0c1] - // Add rtx, red, ulpfec plan - if (configure.support_red || configure.support_rtx || configure.support_ulpfec) { - for (auto &plan : offer_media.plan) { - if (!strcasecmp(plan.codec.data(), "rtx")) { - if (configure.support_rtx && atoi(plan.getFmtp("apt").data()) == selected_plan->pt) { - answer_media.plan.emplace_back(plan); - pt_selected.emplace(plan.pt); - } - continue; - } - if (!strcasecmp(plan.codec.data(), "red")) { - if (configure.support_red) { - answer_media.plan.emplace_back(plan); - pt_selected.emplace(plan.pt); - } - continue; - } - if (!strcasecmp(plan.codec.data(), "ulpfec")) { - if (configure.support_ulpfec) { - answer_media.plan.emplace_back(plan); - pt_selected.emplace(plan.pt); - } - continue; - } - } - } - - // 对方和我方都支持的扩展,那么我们才支持 [AUTO-TRANSLATED:a6cd98b2] - // We only support extensions that are supported by both the other party and us - for (auto &ext : offer_media.extmap) { - auto it = configure.extmap.find(RtpExt::getExtType(ext.ext)); - if (it != configure.extmap.end()) { - auto new_dir = matchDirection(ext.direction, it->second); - switch (new_dir) { - case RtpDirection::invalid: - case RtpDirection::inactive: continue; - default: break; - } - answer_media.extmap.emplace_back(ext); - answer_media.extmap.back().direction = new_dir; - } - } - - auto &rtcp_fb_ref = answer_media.plan[0].rtcp_fb; - rtcp_fb_ref.clear(); - // 对方和我方都支持的rtcpfb,那么我们才支持 [AUTO-TRANSLATED:f10450bb] - // We only support rtcpfb that is supported by both the other party and us - for (auto &fp : selected_plan->rtcp_fb) { - if (configure.rtcp_fb.find(fp) != configure.rtcp_fb.end()) { - // 对方该rtcp被我们支持 [AUTO-TRANSLATED:3b16e666] - // The other party's rtcp is supported by us - rtcp_fb_ref.emplace(fp); - } - } - -#if 0 - // todo 此处为添加无效的plan,webrtc sdp通过调节plan pt顺序选择匹配的codec,意味着后面的codec其实放在sdp中是无意义的 [AUTO-TRANSLATED:502d0cb2] - // todo This is to add an invalid plan. WebRTC sdp selects the matching codec by adjusting the plan pt order, which means that the subsequent codecs are actually meaningless in the sdp - for (auto &plan : offer_media.plan) { - if (pt_selected.find(plan.pt) == pt_selected.end()) { - answer_media.plan.emplace_back(plan); - } - } -#endif - ret->media.emplace_back(answer_media); - return; - } - - if (check_profile) { - // 如果是由于检查profile导致匹配失败,那么重试一次,且不检查profile [AUTO-TRANSLATED:897fa4ae] - // If the matching fails due to profile check, retry once and do not check profile - check_profile = false; - goto RETRY; - } - - if (check_codec) { - // 如果是由于检查codec导致匹配失败,那么重试一次,且不检查codec [AUTO-TRANSLATED:fbd85968] - // If the matching fails due to codec check, retry once and do not check codec - check_codec = false; - goto RETRY; - } -} - -void RtcConfigure::setPlayRtspInfo(const string &sdp) { - RtcSession session; - video.direction = RtpDirection::inactive; - audio.direction = RtpDirection::inactive; - - session.loadFrom(sdp); - for (auto &m : session.media) { - switch (m.type) { - case TrackVideo: { - video.direction = RtpDirection::sendonly; - _rtsp_video_plan = std::make_shared(m.plan[0]); - video.preferred_codec.clear(); - video.preferred_codec.emplace_back(getCodecId(_rtsp_video_plan->codec)); - break; - } - case TrackAudio: { - audio.direction = RtpDirection::sendonly; - _rtsp_audio_plan = std::make_shared(m.plan[0]); - audio.preferred_codec.clear(); - audio.preferred_codec.emplace_back(getCodecId(_rtsp_audio_plan->codec)); - break; - } - default: break; - } - } -} - -static const string kH264Profile { "profile-level-id" }; -static const string kH265Profile { "profile-id" }; -static const string kMode { "packetization-mode" }; - -bool RtcConfigure::onCheckCodecProfile(const RtcCodecPlan &plan, CodecId codec) const { - if (_rtsp_audio_plan && codec == getCodecId(_rtsp_audio_plan->codec)) { - if (plan.sample_rate != _rtsp_audio_plan->sample_rate || plan.channel != _rtsp_audio_plan->channel) { - // 音频采样率和通道数必须相同 [AUTO-TRANSLATED:6e591932] - // Audio sampling rate and number of channels must be the same - return false; - } - return true; - } - if (_rtsp_video_plan && codec == CodecH264 && getCodecId(_rtsp_video_plan->codec) == CodecH264) { - // h264时,profile-level-id [AUTO-TRANSLATED:94a5f360] - // When h264, profile-level-id - if (strcasecmp(_rtsp_video_plan->fmtp[kH264Profile].data(), const_cast(plan).fmtp[kH264Profile].data())) { - // profile-level-id 不匹配 [AUTO-TRANSLATED:814ec4c4] - // profile-level-id does not match - return false; - } - return true; - } - - if (_rtsp_video_plan && codec == CodecH265 && getCodecId(_rtsp_video_plan->codec) == CodecH265) { - // h265时,profile-id - if (strcasecmp(_rtsp_video_plan->fmtp[kH265Profile].data(), const_cast(plan).fmtp[kH265Profile].data())) { - // profile-id 不匹配 - return false; - } - return true; - } - - return true; -} - -/** - Single NAI Unit Mode = 0. // Single NAI mode (Only nals from 1-23 are allowed) - Non Interleaved Mode = 1,// Non-interleaved Mode: 1-23,24 (STAP-A),28 (FU-A) are allowed - Interleaved Mode = 2, // 25 (STAP-B),26 (MTAP16),27 (MTAP24),28 (EU-A),and 29 (EU-B) are allowed. - Single NAI Unit Mode = 0. // Single NAI mode (Only nals from 1-23 are allowed) - Non Interleaved Mode = 1,// Non-interleaved Mode: 1-23,24 (STAP-A),28 (FU-A) are allowed - Interleaved Mode = 2, // 25 (STAP-B),26 (MTAP16),27 (MTAP24),28 (EU-A),and 29 (EU-B) are allowed. - * - * [AUTO-TRANSLATED:b1526114] - **/ -void RtcConfigure::onSelectPlan(RtcCodecPlan &plan, CodecId codec) const { - if (_rtsp_video_plan && codec == CodecH264 && getCodecId(_rtsp_video_plan->codec) == CodecH264) { - // h264时,设置packetization-mod为一致 [AUTO-TRANSLATED:59a00889] - // When h264, set packetization-mod to be consistent - auto mode = _rtsp_video_plan->fmtp[kMode]; - GET_CONFIG(bool, h264_stap_a, Rtp::kH264StapA); - plan.fmtp[kMode] = mode.empty() ? std::to_string(h264_stap_a) : mode; - } -} - -} // namespace mediakit \ No newline at end of file +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "Sdp.h" +#include "Rtsp/Rtsp.h" +#include "Common/config.h" +#include + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +namespace Rtc { +#define RTC_FIELD "rtc." +const string kPreferredCodecA = RTC_FIELD "preferredCodecA"; +const string kPreferredCodecV = RTC_FIELD "preferredCodecV"; +static onceToken token([]() { + mINI::Instance()[kPreferredCodecA] = "PCMA,PCMU,opus,mpeg4-generic"; + mINI::Instance()[kPreferredCodecV] = "H264,H265,AV1,VP9,VP8"; +}); +} // namespace Rtc + +using onCreateSdpItem = function; +static map sdpItemCreator; + +template +void registerSdpItem() { + onCreateSdpItem func = [](const string &key, const string &value) { + auto ret = std::make_shared(); + ret->parse(value); + return ret; + }; + Item item; + sdpItemCreator.emplace(item.getKey(), std::move(func)); +} + +class DirectionInterface { +public: + virtual RtpDirection getDirection() const = 0; +}; + +class SdpDirectionSendonly : public SdpItem, public DirectionInterface { +public: + const char *getKey() const override { return getRtpDirectionString(getDirection()); } + RtpDirection getDirection() const override { return RtpDirection::sendonly; } +}; + +class SdpDirectionRecvonly : public SdpItem, public DirectionInterface { +public: + const char *getKey() const override { return getRtpDirectionString(getDirection()); } + RtpDirection getDirection() const override { return RtpDirection::recvonly; } +}; + +class SdpDirectionSendrecv : public SdpItem, public DirectionInterface { +public: + const char *getKey() const override { return getRtpDirectionString(getDirection()); } + RtpDirection getDirection() const override { return RtpDirection::sendrecv; } +}; + +class SdpDirectionInactive : public SdpItem, public DirectionInterface { +public: + const char *getKey() const override { return getRtpDirectionString(getDirection()); } + RtpDirection getDirection() const override { return RtpDirection::inactive; } +}; + +class DirectionInterfaceImp : public SdpItem, public DirectionInterface { +public: + DirectionInterfaceImp(RtpDirection direct) { direction = direct; } + const char *getKey() const override { return getRtpDirectionString(getDirection()); } + RtpDirection getDirection() const override { return direction; } + +private: + RtpDirection direction; +}; + +static bool registerAllItem() { + registerSdpItem>(); + registerSdpItem>(); + registerSdpItem>(); + registerSdpItem>(); + registerSdpItem>(); + registerSdpItem>(); + registerSdpItem>(); + registerSdpItem>(); + registerSdpItem>(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + registerSdpItem(); + return true; +} + +static map dtls_role_map = { + {"active", DtlsRole::active}, + {"passive", DtlsRole::passive}, + {"actpass", DtlsRole::actpass} +}; + +DtlsRole getDtlsRole(const string &str) { + auto it = dtls_role_map.find(str); + return it == dtls_role_map.end() ? DtlsRole::invalid : it->second; +} + +const char *getDtlsRoleString(DtlsRole role) { + switch (role) { + case DtlsRole::active: return "active"; + case DtlsRole::passive: return "passive"; + case DtlsRole::actpass: return "actpass"; + default: return "invalid"; + } +} + +static map direction_map = { + {"sendonly", RtpDirection::sendonly}, + {"recvonly", RtpDirection::recvonly}, + {"sendrecv", RtpDirection::sendrecv}, + {"inactive", RtpDirection::inactive} +}; + +RtpDirection getRtpDirection(const string &str) { + auto it = direction_map.find(str); + return it == direction_map.end() ? RtpDirection::invalid : it->second; +} + +const char *getRtpDirectionString(RtpDirection val) { + switch (val) { + case RtpDirection::sendonly: return "sendonly"; + case RtpDirection::recvonly: return "recvonly"; + case RtpDirection::sendrecv: return "sendrecv"; + case RtpDirection::inactive: return "inactive"; + default: return "invalid"; + } +} + +////////////////////////////////////////////////////////////////////////////////////////// + +string RtcSdpBase::toString() const { + _StrPrinter printer; + for (auto &item : items) { + printer << item->getKey() << "=" << item->toString() << "\r\n"; + } + return printer; +} + +RtpDirection RtcSdpBase::getDirection() const { + for (auto &item : items) { + auto attr = dynamic_pointer_cast(item); + if (attr) { + auto dir = dynamic_pointer_cast(attr->detail); + if (dir) { + return dir->getDirection(); + } + } + } + return RtpDirection::invalid; +} + +SdpItem::Ptr RtcSdpBase::getItem(char key_c, const char *attr_key) const { + std::string key(1, key_c); + for (auto item : items) { + if (strcasecmp(item->getKey(), key.data()) == 0) { + if (!attr_key) { + return item; + } + auto attr = dynamic_pointer_cast(item); + if (attr && !strcasecmp(attr->detail->getKey(), attr_key)) { + return attr->detail; + } + } + } + return SdpItem::Ptr(); +} + +////////////////////////////////////////////////////////////////////////// +int RtcSessionSdp::getVersion() const { + return atoi(getStringItem('v').data()); +} + +SdpOrigin RtcSessionSdp::getOrigin() const { + return getItemClass('o'); +} + +string RtcSessionSdp::getSessionName() const { + return getStringItem('s'); +} + +string RtcSessionSdp::getSessionInfo() const { + return getStringItem('i'); +} + +SdpTime RtcSessionSdp::getSessionTime() const { + return getItemClass('t'); +} + +SdpConnection RtcSessionSdp::getConnection() const { + return getItemClass('c'); +} + +SdpBandwidth RtcSessionSdp::getBandwidth() const { + return getItemClass('b'); +} + +string RtcSessionSdp::getUri() const { + return getStringItem('u'); +} + +string RtcSessionSdp::getEmail() const { + return getStringItem('e'); +} + +string RtcSessionSdp::getPhone() const { + return getStringItem('p'); +} + +string RtcSessionSdp::getTimeZone() const { + return getStringItem('z'); +} + +string RtcSessionSdp::getEncryptKey() const { + return getStringItem('k'); +} + +string RtcSessionSdp::getRepeatTimes() const { + return getStringItem('r'); +} + +////////////////////////////////////////////////////////////////////// + +void RtcSessionSdp::parse(const string &str) { + static auto flag = registerAllItem(); + RtcSdpBase *media = nullptr; + auto lines = split(str, "\n"); + std::set line_set; + for (auto &line : lines) { + trim(line); + if (line.size() < 3 || line[1] != '=') { + continue; + } + + if (!line_set.emplace(line).second) { + continue; + } + + auto key = line.substr(0, 1); + auto value = line.substr(2); + if (!strcasecmp(key.data(), "m")) { + medias.emplace_back(RtcSdpBase()); + media = &medias.back(); + line_set.clear(); + } + + SdpItem::Ptr item; + auto it = sdpItemCreator.find(key); + if (it != sdpItemCreator.end()) { + item = it->second(key, value); + } else { + item = std::make_shared(key); + item->parse(value); + } + if (media) { + media->addItem(std::move(item)); + } else { + addItem(std::move(item)); + } + } +} + +string RtcSessionSdp::toString() const { + _StrPrinter printer; + printer << RtcSdpBase::toString(); + for (auto &media : medias) { + printer << media.toString(); + } + + return printer; +} + +////////////////////////////////////////////////////////////////////////////////////////// + +#define CHECK_SDP(exp) CHECK(exp, "解析sdp ", getKey(), " 字段失败:", str) + +void SdpTime::parse(const string &str) { + CHECK_SDP(sscanf(str.data(), "%" SCNu64 " %" SCNu64, &start, &stop) == 2); +} + +string SdpTime::toString() const { + if (value.empty()) { + value = to_string(start) + " " + to_string(stop); + } + return SdpItem::toString(); +} + +void SdpOrigin::parse(const string &str) { + auto vec = split(str, " "); + CHECK_SDP(vec.size() == 6); + username = vec[0]; + session_id = vec[1]; + session_version = vec[2]; + nettype = vec[3]; + addrtype = vec[4]; + address = vec[5]; +} + +string SdpOrigin::toString() const { + if (value.empty()) { + value = username + " " + session_id + " " + session_version + " " + nettype + " " + addrtype + " " + address; + } + return SdpItem::toString(); +} + +void SdpConnection::parse(const string &str) { + auto vec = split(str, " "); + CHECK_SDP(vec.size() == 3); + nettype = vec[0]; + addrtype = vec[1]; + address = vec[2]; +} + +string SdpConnection::toString() const { + if (value.empty()) { + value = nettype + " " + addrtype + " " + address; + } + return SdpItem::toString(); +} + +void SdpBandwidth::parse(const string &str) { + auto vec = split(str, ":"); + CHECK_SDP(vec.size() == 2); + bwtype = vec[0]; + bandwidth = atoi(vec[1].data()); +} + +string SdpBandwidth::toString() const { + if (value.empty()) { + value = bwtype + ":" + to_string(bandwidth); + } + return SdpItem::toString(); +} + +void SdpMedia::parse(const string &str) { + auto vec = split(str, " "); + CHECK_SDP(vec.size() >= 4); + type = getTrackType(vec[0]); + CHECK_SDP(type != TrackInvalid); + port = atoi(vec[1].data()); + proto = vec[2]; + for (size_t i = 3; i < vec.size(); ++i) { + fmts.emplace_back(vec[i]); + } +} + +string SdpMedia::toString() const { + if (value.empty()) { + value = string(getTrackString(type)) + " " + to_string(port) + " " + proto; + for (auto fmt : fmts) { + value += ' '; + value += fmt; + } + } + return SdpItem::toString(); +} + +void SdpAttr::parse(const string &str) { + auto pos = str.find(':'); + auto key = pos == string::npos ? str : str.substr(0, pos); + auto value = pos == string::npos ? string() : str.substr(pos + 1); + auto it = sdpItemCreator.find(key); + if (it != sdpItemCreator.end()) { + detail = it->second(key, value); + } else { + detail = std::make_shared(key); + detail->parse(value); + } +} + +string SdpAttr::toString() const { + if (value.empty()) { + auto detail_value = detail->toString(); + if (detail_value.empty()) { + value = detail->getKey(); + } else { + value = string(detail->getKey()) + ":" + detail_value; + } + } + return SdpItem::toString(); +} + +void SdpAttrGroup::parse(const string &str) { + auto vec = split(str, " "); + CHECK_SDP(vec.size() >= 2); + type = vec[0]; + vec.erase(vec.begin()); + mids = std::move(vec); +} + +string SdpAttrGroup::toString() const { + if (value.empty()) { + value = type; + for (auto mid : mids) { + value += ' '; + value += mid; + } + } + return SdpItem::toString(); +} + +void SdpAttrMsidSemantic::parse(const string &str) { + auto vec = split(str, " "); + CHECK_SDP(vec.size() >= 1); + msid = vec[0]; + token = vec.size() > 1 ? vec[1] : ""; +} + +string SdpAttrMsidSemantic::toString() const { + if (value.empty()) { + if (token.empty()) { + value = string(" ") + msid; + } else { + value = string(" ") + msid + " " + token; + } + } + return SdpItem::toString(); +} + +void SdpAttrRtcp::parse(const string &str) { + auto vec = split(str, " "); + CHECK_SDP(vec.size() == 4); + port = atoi(vec[0].data()); + nettype = vec[1]; + addrtype = vec[2]; + address = vec[3]; +} + +string SdpAttrRtcp::toString() const { + if (value.empty()) { + value = to_string(port) + " " + nettype + " " + addrtype + " " + address; + } + return SdpItem::toString(); +} + +void SdpAttrIceOption::parse(const string &str) { + auto vec = split(str, " "); + for (auto &v : vec) { + if (!strcasecmp(v.data(), "trickle")) { + trickle = true; + continue; + } + if (!strcasecmp(v.data(), "renomination")) { + renomination = true; + continue; + } + } +} + +string SdpAttrIceOption::toString() const { + if (value.empty()) { + if (trickle && renomination) { + value = "trickle renomination"; + } else if (trickle) { + value = "trickle"; + } else if (renomination) { + value = "renomination"; + } + } + return value; +} + +void SdpAttrFingerprint::parse(const string &str) { + auto vec = split(str, " "); + CHECK_SDP(vec.size() == 2); + algorithm = vec[0]; + hash = vec[1]; +} + +string SdpAttrFingerprint::toString() const { + if (value.empty()) { + value = algorithm + " " + hash; + } + return SdpItem::toString(); +} + +void SdpAttrSetup::parse(const string &str) { + role = getDtlsRole(str); + CHECK_SDP(role != DtlsRole::invalid); +} + +string SdpAttrSetup::toString() const { + if (value.empty()) { + value = getDtlsRoleString(role); + } + return SdpItem::toString(); +} + +void SdpAttrExtmap::parse(const string &str) { + char buf[128] = { 0 }; + char direction_buf[32] = { 0 }; + if (sscanf(str.data(), "%" SCNd8 "/%31[^ ] %127s", &id, direction_buf, buf) != 3) { + CHECK_SDP(sscanf(str.data(), "%" SCNd8 " %127s", &id, buf) == 2); + direction = RtpDirection::sendrecv; + } else { + direction = getRtpDirection(direction_buf); + } + ext = buf; +} + +string SdpAttrExtmap::toString() const { + if (value.empty()) { + if (direction == RtpDirection::invalid || direction == RtpDirection::sendrecv) { + value = to_string((int)id) + " " + ext; + } else { + value = to_string((int)id) + "/" + getRtpDirectionString(direction) + " " + ext; + } + } + return SdpItem::toString(); +} + +void SdpAttrRtpMap::parse(const string &str) { + char buf[32] = { 0 }; + if (sscanf(str.data(), "%" SCNu8 " %31[^/]/%" SCNd32 "/%" SCNd32, &pt, buf, &sample_rate, &channel) != 4) { + CHECK_SDP(sscanf(str.data(), "%" SCNu8 " %31[^/]/%" SCNd32, &pt, buf, &sample_rate) == 3); + if (getTrackType(getCodecId(buf)) == TrackAudio) { + // 未指定通道数时,且为音频时,那么通道数默认为1 [AUTO-TRANSLATED:bd128fbd] + // If the number of channels is not specified and it is audio, the number of channels defaults to 1 + channel = 1; + } + } + codec = buf; +} + +string SdpAttrRtpMap::toString() const { + if (value.empty()) { + value = to_string((int)pt) + " " + codec + "/" + to_string(sample_rate); + if (channel) { + value += '/'; + value += to_string(channel); + } + } + return SdpItem::toString(); +} + +void SdpAttrRtcpFb::parse(const string &str_in) { + auto str = str_in + "\n"; + char rtcp_type_buf[32] = { 0 }; + CHECK_SDP(sscanf(str.data(), "%" SCNu8 " %31[^\n]", &pt, rtcp_type_buf) == 2); + rtcp_type = rtcp_type_buf; +} + +string SdpAttrRtcpFb::toString() const { + if (value.empty()) { + value = to_string((int)pt) + " " + rtcp_type; + } + return SdpItem::toString(); +} + +void SdpAttrFmtp::parse(const string &str) { + auto pos = str.find(' '); + CHECK_SDP(pos != string::npos); + pt = atoi(str.substr(0, pos).data()); + auto vec = split(str.substr(pos + 1), ";"); + for (auto &item : vec) { + trim(item); + auto pos = item.find('='); + if (pos == string::npos) { + fmtp.emplace(std::make_pair(item, "")); + } else { + fmtp.emplace(std::make_pair(item.substr(0, pos), item.substr(pos + 1))); + } + } + CHECK_SDP(!fmtp.empty()); +} + +string SdpAttrFmtp::toString() const { + if (value.empty()) { + value = to_string((int)pt); + int i = 0; + for (auto &pr : fmtp) { + value += (i++ ? ';' : ' '); + value += pr.first + "=" + pr.second; + } + } + return SdpItem::toString(); +} + +void SdpAttrSSRC::parse(const string &str_in) { + auto str = str_in + '\n'; + char attr_buf[32] = { 0 }; + char attr_val_buf[128] = { 0 }; + if (3 == sscanf(str.data(), "%" SCNu32 " %31[^:]:%127[^\n]", &ssrc, attr_buf, attr_val_buf)) { + attribute = attr_buf; + attribute_value = attr_val_buf; + } else if (2 == sscanf(str.data(), "%" SCNu32 " %31s[^\n]", &ssrc, attr_buf)) { + attribute = attr_buf; + } else { + CHECK_SDP(0); + } +} + +string SdpAttrSSRC::toString() const { + if (value.empty()) { + value = to_string(ssrc) + ' '; + value += attribute; + if (!attribute_value.empty()) { + value += ':'; + value += attribute_value; + } + } + return SdpItem::toString(); +} + +void SdpAttrSSRCGroup::parse(const string &str) { + auto vec = split(str, " "); + CHECK_SDP(vec.size() >= 3); + type = std::move(vec[0]); + CHECK(isFID() || isSIM()); + vec.erase(vec.begin()); + for (auto ssrc : vec) { + ssrcs.emplace_back((uint32_t)atoll(ssrc.data())); + } +} + +string SdpAttrSSRCGroup::toString() const { + if (value.empty()) { + value = type; + // 最少要求2个ssrc [AUTO-TRANSLATED:968acb83] + // At least 2 SSRCs are required + CHECK(ssrcs.size() >= 2); + for (auto &ssrc : ssrcs) { + value += ' '; + value += to_string(ssrc); + } + } + return SdpItem::toString(); +} + +void SdpAttrSctpMap::parse(const string &str) { + char subtypes_buf[64] = { 0 }; + CHECK_SDP(3 == sscanf(str.data(), "%" SCNu16 " %63[^ ] %" SCNd32, &port, subtypes_buf, &streams)); + subtypes = subtypes_buf; +} + +string SdpAttrSctpMap::toString() const { + if (value.empty()) { + value = to_string(port); + value += ' '; + value += subtypes; + value += ' '; + value += to_string(streams); + } + return SdpItem::toString(); +} + +void SdpAttrCandidate::parse(const string &str) { + char foundation_buf[40] = { 0 }; + char transport_buf[16] = { 0 }; + char address_buf[64] = { 0 }; + char type_buf[16] = { 0 }; + + // https://datatracker.ietf.org/doc/html/rfc5245#section-15.1 + CHECK_SDP(sscanf(str.data(), "%32[^ ] %" SCNu32 " %15[^ ] %" SCNu32 " %63[^ ] %" SCNu16 " typ %15[^ ]", + foundation_buf, &component, transport_buf, &priority, address_buf, &port, type_buf) == 7); + foundation = foundation_buf; + transport = transport_buf; + address = address_buf; + type = type_buf; + auto pos = str.find(type); + if (pos != string::npos) { + auto remain = str.substr(pos + type.size()); + trim(remain); + if (!remain.empty()) { + auto vec = split(remain, " "); + string key; + for (auto &item : vec) { + if (key.empty()) { + key = item; + } else { + arr.emplace_back(std::make_pair(std::move(key), std::move(item))); + } + } + } + } +} + +string SdpAttrCandidate::toString() const { + if (value.empty()) { + value = foundation + " " + to_string(component) + " " + transport + " " + to_string(priority) + " " + address + " " + to_string(port) + " typ " + type; + for (auto &pr : arr) { + value += ' '; + value += pr.first; + value += ' '; + value += pr.second; + } + } + return SdpItem::toString(); +} + +void SdpAttrSimulcast::parse(const string &str) { + // https://www.meetecho.com/blog/simulcast-janus-ssrc/ + // a=simulcast:send/recv q;h;f + // a=simulcast:send/recv [rid=]q;h;f + // a=simulcast: recv h;m;l + // + auto vec = split(str, " "); + CHECK_SDP(vec.size() == 2); + direction = vec[0]; + rids = split(vec[1], ";"); +} + +string SdpAttrSimulcast::toString() const { + if (value.empty()) { + value = direction + " "; + bool first = true; + for (auto &rid : rids) { + if (first) { + first = false; + } else { + value += ';'; + } + value += rid; + } + } + return SdpItem::toString(); +} + +void SdpAttrRid::parse(const string &str) { + auto vec = split(str, " "); + CHECK(vec.size() >= 2); + rid = vec[0]; + direction = vec[1]; +} + +string SdpAttrRid::toString() const { + if (value.empty()) { + value = rid + " " + direction; + } + return SdpItem::toString(); +} + +void RtcSession::loadFrom(const string &str) { + RtcSessionSdp sdp; + sdp.parse(str); + + version = sdp.getVersion(); + origin = sdp.getOrigin(); + session_name = sdp.getSessionName(); + session_info = sdp.getSessionInfo(); + connection = sdp.getConnection(); + time = sdp.getSessionTime(); + msid_semantic = sdp.getItemClass('a', "msid-semantic"); + for (auto &media : sdp.medias) { + auto mline = media.getItemClass('m'); + this->media.emplace_back(); + auto &rtc_media = this->media.back(); + rtc_media.mid = media.getStringItem('a', "mid"); + rtc_media.proto = mline.proto; + rtc_media.type = mline.type; + rtc_media.port = mline.port; + rtc_media.addr = media.getItemClass('c'); + rtc_media.bandwidth = media.getItemClass('b'); + rtc_media.ice_ufrag = media.getStringItem('a', "ice-ufrag"); + rtc_media.ice_pwd = media.getStringItem('a', "ice-pwd"); + rtc_media.role = media.getItemClass('a', "setup").role; + rtc_media.fingerprint = media.getItemClass('a', "fingerprint"); + if (rtc_media.fingerprint.empty()) { + rtc_media.fingerprint = sdp.getItemClass('a', "fingerprint"); + } + rtc_media.ice_lite = media.getItem('a', "ice-lite").operator bool(); + auto ice_options = media.getItemClass('a', "ice-options"); + rtc_media.ice_trickle = ice_options.trickle; + rtc_media.ice_renomination = ice_options.renomination; + rtc_media.candidate = media.getAllItem('a', "candidate"); + + if (mline.type == TrackType::TrackApplication) { + rtc_media.sctp_port = atoi(media.getStringItem('a', "sctp-port").data()); + rtc_media.sctpmap = media.getItemClass('a', "sctpmap"); + continue; + } + rtc_media.rtcp_addr = media.getItemClass('a', "rtcp"); + rtc_media.direction = media.getDirection(); + rtc_media.extmap = media.getAllItem('a', "extmap"); + rtc_media.rtcp_mux = media.getItem('a', "rtcp-mux").operator bool(); + rtc_media.rtcp_rsize = media.getItem('a', "rtcp-rsize").operator bool(); + + map rtc_ssrc_map; + auto ssrc_attr = media.getAllItem('a', "ssrc"); + for (auto &ssrc : ssrc_attr) { + auto &rtc_ssrc = rtc_ssrc_map[ssrc.ssrc]; + rtc_ssrc.ssrc = ssrc.ssrc; + if (!strcasecmp(ssrc.attribute.data(), "cname")) { + rtc_ssrc.cname = ssrc.attribute_value; + continue; + } + if (!strcasecmp(ssrc.attribute.data(), "msid")) { + rtc_ssrc.msid = ssrc.attribute_value; + continue; + } + if (!strcasecmp(ssrc.attribute.data(), "mslabel")) { + rtc_ssrc.mslabel = ssrc.attribute_value; + continue; + } + if (!strcasecmp(ssrc.attribute.data(), "label")) { + rtc_ssrc.label = ssrc.attribute_value; + continue; + } + } + + auto ssrc_groups = media.getAllItem('a', "ssrc-group"); + bool have_rtx_ssrc = false; + SdpAttrSSRCGroup *ssrc_group_sim = nullptr; + for (auto &group : ssrc_groups) { + if (group.isFID()) { + have_rtx_ssrc = true; + // ssrc-group:FID字段必须包含rtp/rtx的ssrc [AUTO-TRANSLATED:3da97d7d] + // The ssrc-group:FID field must contain the SSRCs of rtp/rtx + CHECK(group.ssrcs.size() == 2); + // 根据rtp ssrc找到对象 [AUTO-TRANSLATED:c0a56b42] + // Find the object based on the RTP SSRC + auto it = rtc_ssrc_map.find(group.ssrcs[0]); + CHECK(it != rtc_ssrc_map.end()); + // 设置rtx ssrc [AUTO-TRANSLATED:422e2a55] + // Set the RTX SSRC + it->second.rtx_ssrc = group.ssrcs[1]; + rtc_media.rtp_rtx_ssrc.emplace_back(it->second); + } else if (group.isSIM()) { + CHECK(!ssrc_group_sim); + ssrc_group_sim = &group; + } + } + + if (!have_rtx_ssrc) { + // 按照sdp顺序依次添加ssrc [AUTO-TRANSLATED:0996ba7e] + // Add SSRCs in the order of SDP + for (auto &attr : ssrc_attr) { + if (attr.attribute == "cname") { + rtc_media.rtp_rtx_ssrc.emplace_back(rtc_ssrc_map[attr.ssrc]); + } + } + } + + auto simulcast = media.getItemClass('a', "simulcast"); + if (!simulcast.empty()) { + // a=rid:h send + // a=rid:m send + // a=rid:l send + // a=simulcast:send h;m;l + // 风格的simulcast [AUTO-TRANSLATED:94ac2d55] + // Style of simulcast + unordered_set rid_map; + for (auto &rid : simulcast.rids) { + rid_map.emplace(rid); + } + for (auto &rid : media.getAllItem('a', "rid")) { + CHECK(rid.direction == simulcast.direction); + CHECK(rid_map.find(rid.rid) != rid_map.end()); + } + // simulcast最少要求2种方案 [AUTO-TRANSLATED:31732a7a] + // Simulcast requires at least 2 schemes + CHECK(simulcast.rids.size() >= 2); + rtc_media.rtp_rids = simulcast.rids; + } + + if (ssrc_group_sim) { + // 指定了a=ssrc-group:SIM [AUTO-TRANSLATED:5732661e] + // a=ssrc-group:SIM is specified + for (auto ssrc : ssrc_group_sim->ssrcs) { + auto it = rtc_ssrc_map.find(ssrc); + CHECK(it != rtc_ssrc_map.end()); + rtc_media.rtp_ssrc_sim.emplace_back(it->second); + } + } else if (!rtc_media.rtp_rids.empty()) { + // 未指定a=ssrc-group:SIM, 但是指定了a=simulcast, 那么只能根据ssrc顺序来对应rid顺序 [AUTO-TRANSLATED:b198a817] + // a=ssrc-group:SIM is not specified, but a=simulcast is specified, so the RID order can only be matched according to the SSRC order + rtc_media.rtp_ssrc_sim = rtc_media.rtp_rtx_ssrc; + } + + if (!rtc_media.supportSimulcast()) { + // 不支持simulcast的情况下,最多一组ssrc [AUTO-TRANSLATED:3ea8ed65] + // In the case of not supporting simulcast, there is at most one group of SSRCs + CHECK(rtc_media.rtp_rtx_ssrc.size() <= 1); + } else { + // simulcast的情况下,要么没有指定ssrc,要么指定的ssrc个数与rid个数一致 [AUTO-TRANSLATED:1d45ce03] + // In the case of simulcast, either no SSRC is specified or the number of specified SSRCs is consistent with the number of RIDs + // CHECK(rtc_media.rtp_ssrc_sim.empty() || rtc_media.rtp_ssrc_sim.size() == rtc_media.rtp_rids.size()); + } + + auto rtpmap_arr = media.getAllItem('a', "rtpmap"); + auto rtcpfb_arr = media.getAllItem('a', "rtcp-fb"); + auto fmtp_aar = media.getAllItem('a', "fmtp"); + // 方便根据pt查找rtpmap,一个pt必有一条 [AUTO-TRANSLATED:c3673faa] + // Convenient to find rtpmap based on pt, one pt must have one + map rtpmap_map; + // 方便根据pt查找rtcp-fb,一个pt可能有多条或0条 [AUTO-TRANSLATED:38361f68] + // Convenient to find rtcp-fb based on pt, one pt may have multiple or 0 + multimap rtcpfb_map; + // 方便根据pt查找fmtp,一个pt最多一条 [AUTO-TRANSLATED:be5d652d] + // Convenient to find fmtp based on pt, one pt has at most one + map fmtp_map; + + for (auto &rtpmap : rtpmap_arr) { + // 添加失败,有多条 [AUTO-TRANSLATED:717782c0] + // Add failed, there are multiple + CHECK(rtpmap_map.emplace(rtpmap.pt, rtpmap).second, "该pt存在多条a=rtpmap:", (int)rtpmap.pt); + } + for (auto &rtpfb : rtcpfb_arr) { + rtcpfb_map.emplace(rtpfb.pt, rtpfb); + } + for (auto &fmtp : fmtp_aar) { + // 添加失败,有多条 [AUTO-TRANSLATED:717782c0] + // Add failed, there are multiple + CHECK(fmtp_map.emplace(fmtp.pt, fmtp).second, "该pt存在多条a=fmtp:", (int)fmtp.pt); + } + for (auto &item : mline.fmts) { + auto pt = atoi(item.c_str()); + CHECK(pt < 0xFF, "invalid payload type: ", item); + // 遍历所有编码方案的pt [AUTO-TRANSLATED:40f2db36] + // Traverse the pt of all encoding schemes + rtc_media.plan.emplace_back(); + auto &plan = rtc_media.plan.back(); + auto rtpmap_it = rtpmap_map.find(pt); + if (rtpmap_it == rtpmap_map.end()) { + plan.pt = pt; + plan.codec = RtpPayload::getName(pt); + plan.sample_rate = RtpPayload::getClockRate(pt); + plan.channel = RtpPayload::getAudioChannel(pt); + } else { + plan.pt = rtpmap_it->second.pt; + plan.codec = rtpmap_it->second.codec; + plan.sample_rate = rtpmap_it->second.sample_rate; + plan.channel = rtpmap_it->second.channel; + } + + auto fmtp_it = fmtp_map.find(pt); + if (fmtp_it != fmtp_map.end()) { + plan.fmtp = fmtp_it->second.fmtp; + } + for (auto rtpfb_it = rtcpfb_map.find(pt); rtpfb_it != rtcpfb_map.end() && rtpfb_it->second.pt == pt; ++rtpfb_it) { + plan.rtcp_fb.emplace(rtpfb_it->second.rtcp_type); + } + } + } + + group = sdp.getItemClass('a', "group"); +} + +void RtcSdpBase::toRtsp() { + for (auto it = items.begin(); it != items.end();) { + switch ((*it)->getKey()[0]) { + case 'v': + case 'o': + case 's': + case 'i': + case 't': + case 'c': + case 'b': { + ++it; + break; + } + + case 'm': { + auto m = dynamic_pointer_cast(*it); + CHECK(m); + m->proto = "RTP/AVP"; + ++it; + break; + } + case 'a': { + auto attr = dynamic_pointer_cast(*it); + CHECK(attr); + if (!strcasecmp(attr->detail->getKey(), "rtpmap") || !strcasecmp(attr->detail->getKey(), "fmtp")) { + ++it; + break; + } + } + default: { + it = items.erase(it); + break; + } + } + } +} + +string RtcSession::toRtspSdp() const { + RtcSession copy = *this; + copy.media.clear(); + for (auto &m : media) { + switch (m.type) { + case TrackAudio: + case TrackVideo: { + if (m.direction != RtpDirection::inactive) { + copy.media.emplace_back(m); + copy.media.back().plan.resize(1); + } + break; + } + default: continue; + } + } + + CHECK(!copy.media.empty()); + auto sdp = copy.toRtcSessionSdp(); + sdp->toRtsp(); + int i = 0; + for (auto &m : sdp->medias) { + m.toRtsp(); + m.addAttr(std::make_shared("control", string("trackID=") + to_string(i++))); + } + return sdp->toString(); +} + +void addSdpAttrSSRC(const RtcSSRC &rtp_ssrc, RtcSdpBase &media, uint32_t ssrc_num) { + assert(ssrc_num); + SdpAttrSSRC ssrc; + ssrc.ssrc = ssrc_num; + + ssrc.attribute = "cname"; + ssrc.attribute_value = rtp_ssrc.cname; + media.addAttr(std::make_shared(ssrc)); + + if (!rtp_ssrc.msid.empty()) { + ssrc.attribute = "msid"; + ssrc.attribute_value = rtp_ssrc.msid; + media.addAttr(std::make_shared(ssrc)); + } + + if (!rtp_ssrc.mslabel.empty()) { + ssrc.attribute = "mslabel"; + ssrc.attribute_value = rtp_ssrc.mslabel; + media.addAttr(std::make_shared(ssrc)); + } + + if (!rtp_ssrc.label.empty()) { + ssrc.attribute = "label"; + ssrc.attribute_value = rtp_ssrc.label; + media.addAttr(std::make_shared(ssrc)); + } +} + +RtcSessionSdp::Ptr RtcSession::toRtcSessionSdp() const { + RtcSessionSdp::Ptr ret = std::make_shared(); + auto &sdp = *ret; + sdp.addItem(std::make_shared>(to_string(version))); + sdp.addItem(std::make_shared(origin)); + sdp.addItem(std::make_shared>(session_name)); + if (!session_info.empty()) { + sdp.addItem(std::make_shared>(session_info)); + } + sdp.addItem(std::make_shared(time)); + if (connection.empty()) { + sdp.addItem(std::make_shared(connection)); + } + sdp.addAttr(std::make_shared(group)); + sdp.addAttr(std::make_shared()); + sdp.addAttr(std::make_shared(msid_semantic)); + + bool ice_lite = false; + + for (auto &m : media) { + sdp.medias.emplace_back(); + auto &sdp_media = sdp.medias.back(); + auto mline = std::make_shared(); + mline->type = m.type; + mline->port = m.port; + mline->proto = m.proto; + for (auto &p : m.plan) { + mline->fmts.emplace_back(to_string((int)p.pt)); + } + if (m.type == TrackApplication) { + mline->fmts.emplace_back("webrtc-datachannel"); + } + sdp_media.addItem(std::move(mline)); + sdp_media.addItem(std::make_shared(m.addr)); + if (!m.bandwidth.empty() && m.type != TrackAudio) { + sdp_media.addItem(std::make_shared(m.bandwidth)); + } + if (!m.rtcp_addr.empty()) { + sdp_media.addAttr(std::make_shared(m.rtcp_addr)); + } + + sdp_media.addAttr(std::make_shared(m.ice_ufrag)); + sdp_media.addAttr(std::make_shared(m.ice_pwd)); + if (m.ice_trickle || m.ice_renomination) { + auto attr = std::make_shared(); + attr->trickle = m.ice_trickle; + attr->renomination = m.ice_renomination; + sdp_media.addAttr(attr); + } + sdp_media.addAttr(std::make_shared(m.fingerprint)); + sdp_media.addAttr(std::make_shared(m.role)); + sdp_media.addAttr(std::make_shared(m.mid)); + if (m.ice_lite) { + sdp_media.addAttr(std::make_shared("ice-lite")); + ice_lite = true; + } + for (auto &ext : m.extmap) { + sdp_media.addAttr(std::make_shared(ext)); + } + if (m.direction != RtpDirection::invalid) { + sdp_media.addAttr(std::make_shared(m.direction)); + } + if (m.rtcp_mux) { + sdp_media.addAttr(std::make_shared("rtcp-mux")); + } + if (m.rtcp_rsize) { + sdp_media.addAttr(std::make_shared("rtcp-rsize")); + } + + if (m.type != TrackApplication) { + for (auto &p : m.plan) { + auto rtp_map = std::make_shared(); + rtp_map->pt = p.pt; + rtp_map->codec = p.codec; + rtp_map->sample_rate = p.sample_rate; + rtp_map->channel = p.channel; + // 添加a=rtpmap [AUTO-TRANSLATED:8bef5d64] + // Add a=rtpmap + sdp_media.addAttr(std::move(rtp_map)); + + for (auto &fb : p.rtcp_fb) { + auto rtcp_fb = std::make_shared(); + rtcp_fb->pt = p.pt; + rtcp_fb->rtcp_type = fb; + // 添加a=rtcp-fb [AUTO-TRANSLATED:11754b43] + // Add a=rtcp-fb + sdp_media.addAttr(std::move(rtcp_fb)); + } + + if (!p.fmtp.empty()) { + auto fmtp = std::make_shared(); + fmtp->pt = p.pt; + fmtp->fmtp = p.fmtp; + // 添加a=fmtp [AUTO-TRANSLATED:594a4425] + // Add a=fmtp + sdp_media.addAttr(std::move(fmtp)); + } + } + + { + // 添加a=msid字段 [AUTO-TRANSLATED:cf2c1471] + // Add a=msid field + if (!m.rtp_rtx_ssrc.empty()) { + if (!m.rtp_rtx_ssrc[0].msid.empty()) { + auto msid = std::make_shared(); + msid->parse(m.rtp_rtx_ssrc[0].msid); + sdp_media.addAttr(std::move(msid)); + } + } + } + + { + for (auto &ssrc : m.rtp_rtx_ssrc) { + // 添加a=ssrc字段 [AUTO-TRANSLATED:75ca5225] + // Add a=ssrc field + CHECK(!ssrc.empty()); + addSdpAttrSSRC(ssrc, sdp_media, ssrc.ssrc); + if (ssrc.rtx_ssrc) { + addSdpAttrSSRC(ssrc, sdp_media, ssrc.rtx_ssrc); + + // 生成a=ssrc-group:FID字段 [AUTO-TRANSLATED:22b1f966] + // Generate a=ssrc-group:FID field + // 有rtx ssrc [AUTO-TRANSLATED:fece8076] + // There is rtx ssrc + auto group = std::make_shared(); + group->type = "FID"; + group->ssrcs.emplace_back(ssrc.ssrc); + group->ssrcs.emplace_back(ssrc.rtx_ssrc); + sdp_media.addAttr(std::move(group)); + } + } + } + + { + if (m.rtp_ssrc_sim.size() >= 2) { + // simulcast 要求 2~3路 [AUTO-TRANSLATED:3237ffca] + // Simulcast requires 2~3 channels + auto group = std::make_shared(); + for (auto &ssrc : m.rtp_ssrc_sim) { + group->ssrcs.emplace_back(ssrc.ssrc); + } + // 添加a=ssrc-group:SIM字段 [AUTO-TRANSLATED:46b04aae] + // Add a=ssrc-group:SIM field + group->type = "SIM"; + sdp_media.addAttr(std::move(group)); + } + + if (m.rtp_rids.size() >= 2) { + auto simulcast = std::make_shared(); + simulcast->direction = "recv"; + simulcast->rids = m.rtp_rids; + sdp_media.addAttr(std::move(simulcast)); + + for (auto &rid : m.rtp_rids) { + auto attr_rid = std::make_shared(); + attr_rid->rid = rid; + attr_rid->direction = "recv"; + sdp_media.addAttr(std::move(attr_rid)); + } + } + } + + } else { + if (!m.sctpmap.empty()) { + sdp_media.addAttr(std::make_shared(m.sctpmap)); + } + sdp_media.addAttr(std::make_shared("sctp-port", to_string(m.sctp_port))); + } + + for (auto &cand : m.candidate) { + if (cand.port) { + sdp_media.addAttr(std::make_shared(cand)); + } + } + } + if (ice_lite) { + sdp.addAttr(std::make_shared("ice-lite")); + } + return ret; +} + +string RtcSession::toString() const { + return toRtcSessionSdp()->toString(); +} + +string RtcCodecPlan::getFmtp(const char *key) const { + for (auto &item : fmtp) { + if (strcasecmp(item.first.data(), key) == 0) { + return item.second; + } + } + return ""; +} + +const RtcCodecPlan *RtcMedia::getPlan(uint8_t pt) const { + for (auto &item : plan) { + if (item.pt == pt) { + return &item; + } + } + return nullptr; +} + +const RtcCodecPlan *RtcMedia::getPlan(const char *codec) const { + for (auto &item : plan) { + if (strcasecmp(item.codec.data(), codec) == 0) { + return &item; + } + } + return nullptr; +} + +const RtcCodecPlan *RtcMedia::getRelatedRtxPlan(uint8_t pt) const { + for (auto &item : plan) { + if (strcasecmp(item.codec.data(), "rtx") == 0) { + auto apt = atoi(item.getFmtp("apt").data()); + if (pt == apt) { + return &item; + } + } + } + return nullptr; +} + +uint32_t RtcMedia::getRtpSSRC() const { + if (rtp_rtx_ssrc.size()) { + return rtp_rtx_ssrc[0].ssrc; + } + return 0; +} + +uint32_t RtcMedia::getRtxSSRC() const { + if (rtp_rtx_ssrc.size()) { + return rtp_rtx_ssrc[0].rtx_ssrc; + } + return 0; +} + +bool RtcMedia::supportSimulcast() const { + if (!rtp_rids.empty()) { + return true; + } + if (!rtp_ssrc_sim.empty()) { + return true; + } + return false; +} + +void RtcMedia::checkValid() const { + CHECK(type != TrackInvalid); + CHECK(!mid.empty()); + CHECK(!proto.empty()); + CHECK(direction != RtpDirection::invalid || type == TrackApplication); + CHECK(!plan.empty() || type == TrackApplication); + CHECK(type == TrackApplication || rtcp_mux, "只支持rtcp-mux模式"); + + bool send_rtp = (direction == RtpDirection::sendonly || direction == RtpDirection::sendrecv); + if (!supportSimulcast()) { + // 非simulcast时,检查有没有指定rtp ssrc [AUTO-TRANSLATED:e2d53f8a] + // When not simulcast, check if the RTP SSRC is specified + CHECK(!rtp_rtx_ssrc.empty() || !send_rtp); + + for (auto ssrc : rtp_rtx_ssrc) { + InfoL << "ssrc:" << ssrc.cname << "," << ssrc.msid; + } + } + +#if 0 + // todo 发现Firefox(88.0)在mac平台下,开启rtx后没有指定ssrc [AUTO-TRANSLATED:9112d91a] + // todo Found that Firefox (88.0) on the mac platform does not specify ssrc when rtx is enabled + auto rtx_plan = getPlan("rtx"); + if (rtx_plan) { + // 开启rtx后必须指定rtx_ssrc [AUTO-TRANSLATED:c527f68d] + // RTX must be specified after rtx_ssrc is enabled + CHECK(rtp_rtx_ssrc.size() >= 2 || !send_rtp); + } +#endif +} + +void RtcSession::checkValid() const { + CHECK(version == 0); + CHECK(!origin.empty()); + CHECK(!session_name.empty()); + CHECK(!msid_semantic.empty()); + CHECK(!media.empty()); + CHECK(!group.mids.empty() && group.mids.size() <= media.size(), "只支持group BUNDLE模式"); + + bool have_active_media = false; + for (auto &item : media) { + item.checkValid(); + + if (TrackApplication == item.type) { + have_active_media = true; + } + switch (item.direction) { + case RtpDirection::sendrecv: + case RtpDirection::sendonly: + case RtpDirection::recvonly: have_active_media = true; break; + default: break; + } + } + CHECK(have_active_media, "必须确保最少有一个活跃的track"); +} + +const RtcMedia *RtcSession::getMedia(TrackType type) const { + for (auto &m : media) { + if (m.type == type) { + return &m; + } + } + return nullptr; +} + +bool RtcSession::supportRtcpFb(const string &name, TrackType type) const { + auto media = getMedia(type); + if (!media) { + return false; + } + auto &ref = media->plan[0].rtcp_fb; + return ref.find(name) != ref.end(); +} + +bool RtcSession::supportSimulcast() const { + for (auto &m : media) { + if (m.supportSimulcast()) { + return true; + } + } + return false; +} + +bool RtcSession::isOnlyDatachannel() const { + return 1 == media.size() && TrackApplication == media[0].type; +} + +string const SdpConst::kTWCCRtcpFb = "transport-cc"; +string const SdpConst::kRembRtcpFb = "goog-remb"; + +void RtcConfigure::RtcTrackConfigure::enableTWCC(bool enable) { + if (!enable) { + rtcp_fb.erase(SdpConst::kTWCCRtcpFb); + extmap.erase(RtpExtType::transport_cc); + } else { + rtcp_fb.emplace(SdpConst::kTWCCRtcpFb); + extmap.emplace(RtpExtType::transport_cc, RtpDirection::sendrecv); + } +} + +void RtcConfigure::RtcTrackConfigure::enableREMB(bool enable) { + if (!enable) { + rtcp_fb.erase(SdpConst::kRembRtcpFb); + extmap.erase(RtpExtType::abs_send_time); + } else { + rtcp_fb.emplace(SdpConst::kRembRtcpFb); + extmap.emplace(RtpExtType::abs_send_time, RtpDirection::sendrecv); + } +} + +static vector toCodecArray(const string &str) { + vector ret; + auto vec = split(str, ","); + for (auto &s : vec) { + auto codec = getCodecId(trim(s)); + if (codec != CodecInvalid) { + ret.emplace_back(codec); + } + } + return ret; +} + +void RtcConfigure::RtcTrackConfigure::setDefaultSetting(TrackType type) { + rtcp_mux = true; + rtcp_rsize = false; + group_bundle = true; + support_rtx = true; + support_red = false; + support_ulpfec = false; + ice_lite = true; + ice_trickle = true; + ice_renomination = false; + switch (type) { + case TrackAudio: { + // 此处调整偏好的编码格式优先级 [AUTO-TRANSLATED:b8719e66] + // Adjust the priority of preferred encoding formats here + GET_CONFIG_FUNC(vector, s_preferred_codec, Rtc::kPreferredCodecA, toCodecArray); + CHECK(!s_preferred_codec.empty(), "rtc音频偏好codec不能为空"); + preferred_codec = s_preferred_codec; + + rtcp_fb = { SdpConst::kTWCCRtcpFb, SdpConst::kRembRtcpFb }; + extmap = { { RtpExtType::ssrc_audio_level, RtpDirection::sendrecv }, + { RtpExtType::csrc_audio_level, RtpDirection::sendrecv }, + { RtpExtType::abs_send_time, RtpDirection::sendrecv }, + { RtpExtType::transport_cc, RtpDirection::sendrecv }, + // rtx重传rtp时,忽略sdes_mid类型的rtp ext,实测发现Firefox在接收rtx时,如果存在sdes_mid的ext,将导致无法播放 [AUTO-TRANSLATED:221df025] + // When rtx retransmits rtp, ignore the rtp ext of sdes_mid type. It is found that Firefox cannot play when receiving rtx if there is an ext of sdes_mid + //{RtpExtType::sdes_mid,RtpDirection::sendrecv}, + { RtpExtType::sdes_rtp_stream_id, RtpDirection::sendrecv }, + { RtpExtType::sdes_repaired_rtp_stream_id, RtpDirection::sendrecv } }; + break; + } + case TrackVideo: { + // 此处调整偏好的编码格式优先级 [AUTO-TRANSLATED:b8719e66] + // Adjust the priority of preferred encoding formats here + GET_CONFIG_FUNC(vector, s_preferred_codec, Rtc::kPreferredCodecV, toCodecArray); + CHECK(!s_preferred_codec.empty(), "rtc视频偏好codec不能为空"); + preferred_codec = s_preferred_codec; + + rtcp_fb = { SdpConst::kTWCCRtcpFb, SdpConst::kRembRtcpFb, "nack", "ccm fir", "nack pli" }; + extmap = { { RtpExtType::abs_send_time, RtpDirection::sendrecv }, + { RtpExtType::transport_cc, RtpDirection::sendrecv }, + // rtx重传rtp时,忽略sdes_mid类型的rtp ext,实测发现Firefox在接收rtx时,如果存在sdes_mid的ext,将导致无法播放 [AUTO-TRANSLATED:221df025] + // When rtx retransmits rtp, ignore the rtp ext of sdes_mid type. It is found that Firefox cannot play when receiving rtx if there is an ext of sdes_mid + //{RtpExtType::sdes_mid,RtpDirection::sendrecv}, + { RtpExtType::sdes_rtp_stream_id, RtpDirection::sendrecv }, + { RtpExtType::sdes_repaired_rtp_stream_id, RtpDirection::sendrecv }, + { RtpExtType::video_timing, RtpDirection::sendrecv }, + { RtpExtType::color_space, RtpDirection::sendrecv }, + { RtpExtType::video_content_type, RtpDirection::sendrecv }, + { RtpExtType::playout_delay, RtpDirection::sendrecv }, + // 手机端推webrtc 会带有旋转角度,rtc协议能正常播放 其他协议拉流画面旋转 [AUTO-TRANSLATED:3f2f9e0e] + // Mobile push webrtc will have a rotation angle, rtc protocol can play normally, other protocols pull stream picture rotation + //{RtpExtType::video_orientation, RtpDirection::sendrecv}, + { RtpExtType::toffset, RtpDirection::sendrecv }, + { RtpExtType::framemarking, RtpDirection::sendrecv } }; + break; + } + case TrackApplication: { + break; + } + default: break; + } +} + +void RtcConfigure::setDefaultSetting(string ice_ufrag, string ice_pwd, RtpDirection direction, const SdpAttrFingerprint &fingerprint) { + video.setDefaultSetting(TrackVideo); + audio.setDefaultSetting(TrackAudio); + application.setDefaultSetting(TrackApplication); + + video.ice_ufrag = audio.ice_ufrag = application.ice_ufrag = std::move(ice_ufrag); + video.ice_pwd = audio.ice_pwd = application.ice_pwd = std::move(ice_pwd); + video.direction = audio.direction = application.direction = direction; + video.fingerprint = audio.fingerprint = application.fingerprint = fingerprint; +} + +void RtcConfigure::addCandidate(const SdpAttrCandidate &candidate, TrackType type) { + switch (type) { + case TrackAudio: { + audio.candidate.emplace_back(candidate); + break; + } + case TrackVideo: { + video.candidate.emplace_back(candidate); + break; + } + case TrackApplication: { + application.candidate.emplace_back(candidate); + break; + } + default: { + if (audio.group_bundle) { + audio.candidate.emplace_back(candidate); + } + if (video.group_bundle) { + video.candidate.emplace_back(candidate); + } + if (application.group_bundle) { + application.candidate.emplace_back(candidate); + } + break; + } + } +} + +void RtcConfigure::enableTWCC(bool enable, TrackType type) { + switch (type) { + case TrackAudio: { + audio.enableTWCC(enable); + break; + } + case TrackVideo: { + video.enableTWCC(enable); + break; + } + default: { + audio.enableTWCC(enable); + video.enableTWCC(enable); + break; + } + } +} + +void RtcConfigure::enableREMB(bool enable, TrackType type) { + switch (type) { + case TrackAudio: { + audio.enableREMB(enable); + break; + } + case TrackVideo: { + video.enableREMB(enable); + break; + } + default: { + audio.enableREMB(enable); + video.enableREMB(enable); + break; + } + } +} + +shared_ptr RtcConfigure::createOffer() const { + shared_ptr ret = std::make_shared(); + ret->version = 0; + ret->origin.session_id = std::to_string(makeRandNum()); + ret->origin.session_version = std::to_string(1); + ret->session_name = "-"; + + createMediaOffer(ret); + // 设置音视频端口复用 [AUTO-TRANSLATED:ffe27d17] + // Set audio and video port multiplexing + for (auto &m : ret->media) { + // The remote end has rejected (port 0) the m-section, so it should not be putting its mid in the group attribute. + if (m.port) { + ret->group.mids.emplace_back(m.mid); + } + } + + return ret; +} + +shared_ptr RtcConfigure::createAnswer(const RtcSession &offer) const { + shared_ptr ret = std::make_shared(); + ret->version = offer.version; + ret->origin = offer.origin; + ret->session_name = offer.session_name; + ret->msid_semantic = offer.msid_semantic; + + for (auto &m : offer.media) { + matchMedia(ret, m); + } + + // 设置音视频端口复用 [AUTO-TRANSLATED:ffe27d17] + // Set audio and video port multiplexing + if (!offer.group.mids.empty()) { + for (auto &m : ret->media) { + // The remote end has rejected (port 0) the m-section, so it should not be putting its mid in the group attribute. + if (m.port) { + ret->group.mids.emplace_back(m.mid); + } + } + } + return ret; +} + +static RtpDirection matchDirection(RtpDirection offer_direction, RtpDirection supported) { + switch (offer_direction) { + case RtpDirection::sendonly: { + if (supported != RtpDirection::recvonly && supported != RtpDirection::sendrecv) { + // 我们不支持接收 [AUTO-TRANSLATED:e4ef4034] + // We do not support receiving + return RtpDirection::inactive; + } + return RtpDirection::recvonly; + } + + case RtpDirection::recvonly: { + if (supported != RtpDirection::sendonly && supported != RtpDirection::sendrecv) { + // 我们不支持发送 [AUTO-TRANSLATED:6505a226] + // We do not support sending + return RtpDirection::inactive; + } + return RtpDirection::sendonly; + } + + // 对方支持发送接收,那么最终能力根据配置来决定 [AUTO-TRANSLATED:d234d603] + // The other party supports sending and receiving, so the final capability is determined by the configuration + case RtpDirection::sendrecv: return (supported == RtpDirection::invalid ? RtpDirection::inactive : supported); + case RtpDirection::inactive: return RtpDirection::inactive; + default: return RtpDirection::invalid; + } +} + +static DtlsRole mathDtlsRole(DtlsRole role) { + switch (role) { + case DtlsRole::actpass: + case DtlsRole::active: return DtlsRole::passive; + case DtlsRole::passive: return DtlsRole::active; + default: CHECK(0, "invalid role:", getDtlsRoleString(role)); return DtlsRole::passive; + } +} + +void RtcConfigure::createMediaOffer(const std::shared_ptr &ret) const { + int index = 0; + if (video.direction != RtpDirection::sendonly || _rtsp_video_plan) { + createMediaOfferEach(ret, TrackVideo, index++); + } + + if (audio.direction != RtpDirection::sendonly || _rtsp_audio_plan) { + createMediaOfferEach(ret, TrackAudio, index++); + } + // createMediaOfferEach(ret, TrackApplication, index++); +} + +void RtcConfigure::createMediaOfferEach(const std::shared_ptr &ret, TrackType type, int index) const { + // rtpmap + static std::multimap audio_list_ref, video_list_ref; + static toolkit::onceToken token([]() { + audio_list_ref.emplace(CodecG711U, make_shared("PCMU", 0, 8000)); + audio_list_ref.emplace(CodecG711A, make_shared("PCMA", 8, 8000)); + audio_list_ref.emplace(CodecOpus, make_shared("opus", 111, 48000)); + audio_list_ref.emplace(CodecAAC, make_shared("mpeg4-generic", 96, 48000)); + + video_list_ref.emplace(CodecH264, make_shared(102, 90000, PROFILE_H264_BASELINE)); + video_list_ref.emplace(CodecH264, make_shared(104, 90000, PROFILE_H264_MAIN)); + video_list_ref.emplace(CodecH264, make_shared(106, 90000, PROFILE_H264_HIGH)); + video_list_ref.emplace(CodecH265, make_shared(120, 90000, PROFILE_H265_MAIN)); + video_list_ref.emplace(CodecH265, make_shared(124, 90000, PROFILE_H265_MAIN10)); + video_list_ref.emplace(CodecH265, make_shared(126, 90000, PROFILE_H265_SCREEN)); + video_list_ref.emplace(CodecAV1, make_shared(35, 90000, 0)); + video_list_ref.emplace(CodecVP8, make_shared("VP8", 96, 90000)); + video_list_ref.emplace(CodecVP9, make_shared(98, 90000, 0)); + video_list_ref.emplace(CodecVP9, make_shared(100, 90000, 2)); + }); + + bool check_profile = true; + bool check_codec = true; + const RtcTrackConfigure *cfg_ptr = nullptr; + std::multimap* rtpMap = nullptr; + switch (type) { + case TrackAudio: cfg_ptr = &audio; rtpMap = &audio_list_ref; break; + case TrackVideo: cfg_ptr = &video; rtpMap = &video_list_ref; break; + case TrackApplication: cfg_ptr = &application; break; + default: return; + } + auto &configure = *cfg_ptr; + + if (type == TrackApplication) { + RtcMedia media; + media.role = DtlsRole::active; + media.ice_ufrag = configure.ice_ufrag; + media.ice_pwd = configure.ice_pwd; + media.fingerprint = configure.fingerprint; + // media.ice_lite = configure.ice_lite; + media.ice_lite = false; +#ifdef ENABLE_SCTP + media.candidate = configure.candidate; +#else + media.port = 9; //占位符,表示后续协商分配 + WarnL << "answer sdp忽略application mline, 请安装usrsctp后再测试datachannel功能"; +#endif + ret->media.emplace_back(media); + return; + } + + RtcMedia media; + media.type = type; + media.mid = to_string(index); + media.proto = "UDP/TLS/RTP/SAVPF"; + media.port = 9;//占位符,表示后续协商分配 + // media.addr = ; + // media.bandwidth = ; + // media.rtcp_addr = ; + media.rtcp_mux = true; + media.rtcp_rsize = true; + media.ice_trickle = true; + media.ice_renomination = configure.ice_renomination; + media.ice_ufrag = configure.ice_ufrag; + media.ice_pwd = configure.ice_pwd; + media.fingerprint = configure.fingerprint; + // media.ice_lite = configure.ice_lite; + media.ice_lite = false; + // candidate offer不生成candidate,反正也是错的 + // media.candidate = configure.candidate; + // copy simulicast setting + // media.rtp_rids =; + // media.rtp_ssrc_sim = ; + + media.role = DtlsRole::active; + + // 如果codec匹配失败,那么禁用该track [AUTO-TRANSLATED:037de9a8] + // If the codec matching fails, then disable the track + media.direction = configure.direction; + + //extmap + for (auto extmap : cfg_ptr->extmap) { +#if 0 + if (extmap.second != media.direction) { + continue; + } +#endif + SdpAttrExtmap attrExtmap; + attrExtmap.direction = extmap.second; + attrExtmap.id = (uint8_t)extmap.first; + attrExtmap.ext = RtpExt::getExtUrl(extmap.first); + + media.extmap.push_back(attrExtmap); + } + + //rtpmap + for (auto codec : cfg_ptr->preferred_codec) { + if (!rtpMap) continue; + auto range = rtpMap->equal_range(codec); + for (auto it = range.first; it != range.second; ++it) { + auto rtpmap = it->second; + RtcCodecPlan plan; + plan.codec = rtpmap->getCodeName(); + plan.pt = rtpmap->getPayload(); + plan.sample_rate = rtpmap->getClockRate(); + plan.rtcp_fb = cfg_ptr->rtcp_fb; + auto fmtp = rtpmap->getFmtp(); + for (const auto& pair : fmtp) { + plan.fmtp.emplace(pair); + } + media.plan.push_back(plan); + // add video rtx plan + if (rtpmap->getType() == TrackVideo) { + // a=rtpmap:108 rtx/90000 + // a=fmtp:108 apt=107 + RtcCodecPlan rtx; + rtx.codec = "rtx"; + rtx.pt = rtpmap->getPayload() + 1; + rtx.sample_rate = rtpmap->getClockRate(); + rtx.fmtp["apt"] = std::to_string(rtpmap->getPayload()); + media.plan.push_back(rtx); + } + } + } + + //msid + if (media.direction != RtpDirection::recvonly) { + RtcSSRC ssrc; + ssrc.ssrc = (uint32_t)makeRandNum(); + ssrc.rtx_ssrc = (uint32_t)makeRandNum(); + ssrc.cname = makeRandStr(16); + ssrc.msid = makeRandStr(36) + " " + makeUuidStr(); + media.rtp_rtx_ssrc.push_back(ssrc); + } + + ret->media.emplace_back(media); +} + +void RtcConfigure::matchMedia(const std::shared_ptr &ret, const RtcMedia &offer_media) const { + bool check_profile = true; + bool check_codec = true; + const RtcTrackConfigure *cfg_ptr = nullptr; + + switch (offer_media.type) { + case TrackAudio: cfg_ptr = &audio; break; + case TrackVideo: cfg_ptr = &video; break; + case TrackApplication: cfg_ptr = &application; break; + default: return; + } + auto &configure = *cfg_ptr; + +RETRY: + + if (offer_media.type == TrackApplication) { + RtcMedia answer_media = offer_media; + answer_media.role = mathDtlsRole(offer_media.role); + answer_media.ice_ufrag = configure.ice_ufrag; + answer_media.ice_pwd = configure.ice_pwd; + answer_media.fingerprint = configure.fingerprint; + answer_media.ice_lite = configure.ice_lite; +#ifdef ENABLE_SCTP + answer_media.candidate = configure.candidate; +#else + answer_media.port = 0; + WarnL << "answer sdp忽略application mline, 请安装usrsctp后再测试datachannel功能"; +#endif + ret->media.emplace_back(answer_media); + return; + } + for (auto &codec : configure.preferred_codec) { + if (offer_media.ice_lite && configure.ice_lite) { + WarnL << "answer sdp配置为ice_lite模式,与offer sdp中的ice_lite模式冲突"; + continue; + } + const RtcCodecPlan *selected_plan = nullptr; + for (auto &plan : offer_media.plan) { + // 先检查编码格式是否为偏好 [AUTO-TRANSLATED:b7fb32a0] + // First check if the encoding format is preferred + if (check_codec && getCodecId(plan.codec) != codec) { + continue; + } + // 命中偏好的编码格式,然后检查规格 [AUTO-TRANSLATED:a859c839] + // Hit the preferred encoding format, then check the specifications + if (check_profile && !onCheckCodecProfile(plan, codec)) { + continue; + } + // 找到中意的codec [AUTO-TRANSLATED:4b5eebfd] + // Find the desired codec + selected_plan = &plan; + break; + } + if (!selected_plan) { + // offer中该媒体的所有的codec都不支持 [AUTO-TRANSLATED:3b57b86f] + // All codecs for this media in the offer are not supported + continue; + } + RtcMedia answer_media; + answer_media.type = offer_media.type; + answer_media.mid = offer_media.mid; + answer_media.proto = offer_media.proto; + answer_media.port = offer_media.port; + answer_media.addr = offer_media.addr; + answer_media.bandwidth = offer_media.bandwidth; + answer_media.rtcp_addr = offer_media.rtcp_addr; + answer_media.rtcp_mux = offer_media.rtcp_mux && configure.rtcp_mux; + answer_media.rtcp_rsize = offer_media.rtcp_rsize && configure.rtcp_rsize; + answer_media.ice_trickle = offer_media.ice_trickle && configure.ice_trickle; + answer_media.ice_renomination = offer_media.ice_renomination && configure.ice_renomination; + answer_media.ice_ufrag = configure.ice_ufrag; + answer_media.ice_pwd = configure.ice_pwd; + answer_media.fingerprint = configure.fingerprint; + answer_media.ice_lite = configure.ice_lite; + answer_media.candidate = configure.candidate; + // copy simulicast setting + answer_media.rtp_rids = offer_media.rtp_rids; + answer_media.rtp_ssrc_sim = offer_media.rtp_ssrc_sim; + + answer_media.role = mathDtlsRole(offer_media.role); + + // 如果codec匹配失败,那么禁用该track [AUTO-TRANSLATED:037de9a8] + // If the codec matching fails, then disable the track + answer_media.direction = check_codec ? matchDirection(offer_media.direction, configure.direction) : RtpDirection::inactive; + if (answer_media.direction == RtpDirection::invalid) { + continue; + } + if (answer_media.direction == RtpDirection::sendrecv) { + // 如果是收发双向,那么我们拷贝offer sdp的ssrc,确保ssrc一致 [AUTO-TRANSLATED:d4a621f2] + // If it is bidirectional, then we copy the offer sdp ssrc to ensure ssrc consistency + answer_media.rtp_rtx_ssrc = offer_media.rtp_rtx_ssrc; + } + + // 添加媒体plan [AUTO-TRANSLATED:3f730050] + // Add media plan + answer_media.plan.emplace_back(*selected_plan); + onSelectPlan(answer_media.plan.back(), codec); + + set pt_selected = { selected_plan->pt }; + + // 添加rtx,red,ulpfec plan [AUTO-TRANSLATED:1abff0c1] + // Add rtx, red, ulpfec plan + if (configure.support_red || configure.support_rtx || configure.support_ulpfec) { + for (auto &plan : offer_media.plan) { + if (!strcasecmp(plan.codec.data(), "rtx")) { + if (configure.support_rtx && atoi(plan.getFmtp("apt").data()) == selected_plan->pt) { + answer_media.plan.emplace_back(plan); + pt_selected.emplace(plan.pt); + } + continue; + } + if (!strcasecmp(plan.codec.data(), "red")) { + if (configure.support_red) { + answer_media.plan.emplace_back(plan); + pt_selected.emplace(plan.pt); + } + continue; + } + if (!strcasecmp(plan.codec.data(), "ulpfec")) { + if (configure.support_ulpfec) { + answer_media.plan.emplace_back(plan); + pt_selected.emplace(plan.pt); + } + continue; + } + } + } + + // 对方和我方都支持的扩展,那么我们才支持 [AUTO-TRANSLATED:a6cd98b2] + // We only support extensions that are supported by both the other party and us + for (auto &ext : offer_media.extmap) { + auto it = configure.extmap.find(RtpExt::getExtType(ext.ext)); + if (it != configure.extmap.end()) { + auto new_dir = matchDirection(ext.direction, it->second); + switch (new_dir) { + case RtpDirection::invalid: + case RtpDirection::inactive: continue; + default: break; + } + answer_media.extmap.emplace_back(ext); + answer_media.extmap.back().direction = new_dir; + } + } + + auto &rtcp_fb_ref = answer_media.plan[0].rtcp_fb; + rtcp_fb_ref.clear(); + // 对方和我方都支持的rtcpfb,那么我们才支持 [AUTO-TRANSLATED:f10450bb] + // We only support rtcpfb that is supported by both the other party and us + for (auto &fp : selected_plan->rtcp_fb) { + if (configure.rtcp_fb.find(fp) != configure.rtcp_fb.end()) { + // 对方该rtcp被我们支持 [AUTO-TRANSLATED:3b16e666] + // The other party's rtcp is supported by us + rtcp_fb_ref.emplace(fp); + } + } + +#if 0 + // todo 此处为添加无效的plan,webrtc sdp通过调节plan pt顺序选择匹配的codec,意味着后面的codec其实放在sdp中是无意义的 [AUTO-TRANSLATED:502d0cb2] + // todo This is to add an invalid plan. WebRTC sdp selects the matching codec by adjusting the plan pt order, which means that the subsequent codecs are actually meaningless in the sdp + for (auto &plan : offer_media.plan) { + if (pt_selected.find(plan.pt) == pt_selected.end()) { + answer_media.plan.emplace_back(plan); + } + } +#endif + ret->media.emplace_back(answer_media); + return; + } + + if (check_profile) { + // 如果是由于检查profile导致匹配失败,那么重试一次,且不检查profile [AUTO-TRANSLATED:897fa4ae] + // If the matching fails due to profile check, retry once and do not check profile + check_profile = false; + goto RETRY; + } + + if (check_codec) { + // 如果是由于检查codec导致匹配失败,那么重试一次,且不检查codec [AUTO-TRANSLATED:fbd85968] + // If the matching fails due to codec check, retry once and do not check codec + check_codec = false; + goto RETRY; + } +} + +void RtcConfigure::setPlayRtspInfo(const string &sdp) { + RtcSession session; + video.direction = RtpDirection::inactive; + audio.direction = RtpDirection::inactive; + + session.loadFrom(sdp); + for (auto &m : session.media) { + switch (m.type) { + case TrackVideo: { + video.direction = RtpDirection::sendonly; + _rtsp_video_plan = std::make_shared(m.plan[0]); + video.preferred_codec.clear(); + video.preferred_codec.emplace_back(getCodecId(_rtsp_video_plan->codec)); + break; + } + case TrackAudio: { + audio.direction = RtpDirection::sendonly; + _rtsp_audio_plan = std::make_shared(m.plan[0]); + audio.preferred_codec.clear(); + audio.preferred_codec.emplace_back(getCodecId(_rtsp_audio_plan->codec)); + break; + } + default: break; + } + } +} + +static const string kH264Profile { "profile-level-id" }; +static const string kH265Profile { "profile-id" }; +static const string kMode { "packetization-mode" }; + +bool RtcConfigure::onCheckCodecProfile(const RtcCodecPlan &plan, CodecId codec) const { + if (_rtsp_audio_plan && codec == getCodecId(_rtsp_audio_plan->codec)) { + if (plan.sample_rate != _rtsp_audio_plan->sample_rate || plan.channel != _rtsp_audio_plan->channel) { + // 音频采样率和通道数必须相同 [AUTO-TRANSLATED:6e591932] + // Audio sampling rate and number of channels must be the same + return false; + } + return true; + } + if (_rtsp_video_plan && codec == CodecH264 && getCodecId(_rtsp_video_plan->codec) == CodecH264) { + // h264时,profile-level-id [AUTO-TRANSLATED:94a5f360] + // When h264, profile-level-id + if (strcasecmp(_rtsp_video_plan->fmtp[kH264Profile].data(), const_cast(plan).fmtp[kH264Profile].data())) { + // profile-level-id 不匹配 [AUTO-TRANSLATED:814ec4c4] + // profile-level-id does not match + return false; + } + return true; + } + + if (_rtsp_video_plan && codec == CodecH265 && getCodecId(_rtsp_video_plan->codec) == CodecH265) { + // h265时,profile-id + if (strcasecmp(_rtsp_video_plan->fmtp[kH265Profile].data(), const_cast(plan).fmtp[kH265Profile].data())) { + // profile-id 不匹配 + return false; + } + return true; + } + + return true; +} + +/** + Single NAI Unit Mode = 0. // Single NAI mode (Only nals from 1-23 are allowed) + Non Interleaved Mode = 1,// Non-interleaved Mode: 1-23,24 (STAP-A),28 (FU-A) are allowed + Interleaved Mode = 2, // 25 (STAP-B),26 (MTAP16),27 (MTAP24),28 (EU-A),and 29 (EU-B) are allowed. + Single NAI Unit Mode = 0. // Single NAI mode (Only nals from 1-23 are allowed) + Non Interleaved Mode = 1,// Non-interleaved Mode: 1-23,24 (STAP-A),28 (FU-A) are allowed + Interleaved Mode = 2, // 25 (STAP-B),26 (MTAP16),27 (MTAP24),28 (EU-A),and 29 (EU-B) are allowed. + * + * [AUTO-TRANSLATED:b1526114] + **/ +void RtcConfigure::onSelectPlan(RtcCodecPlan &plan, CodecId codec) const { + if (_rtsp_video_plan && codec == CodecH264 && getCodecId(_rtsp_video_plan->codec) == CodecH264) { + // h264时,设置packetization-mod为一致 [AUTO-TRANSLATED:59a00889] + // When h264, set packetization-mod to be consistent + auto mode = _rtsp_video_plan->fmtp[kMode]; + GET_CONFIG(bool, h264_stap_a, Rtp::kH264StapA); + plan.fmtp[kMode] = mode.empty() ? std::to_string(h264_stap_a) : mode; + } +} + +} // namespace mediakit diff --git a/webrtc/Sdp.h b/webrtc/Sdp.h index b260f852..3eacfe7c 100644 --- a/webrtc/Sdp.h +++ b/webrtc/Sdp.h @@ -1,776 +1,779 @@ -/* - * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. - * - * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). - * - * Use of this source code is governed by MIT-like license that can be found in the - * LICENSE file in the root of the source tree. All contributing project authors - * may be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef ZLMEDIAKIT_SDP_H -#define ZLMEDIAKIT_SDP_H - -#include -#include -#include -#include -#include "RtpExt.h" -#include "assert.h" -#include "Extension/Frame.h" -#include "Common/Parser.h" - -namespace mediakit { - -// https://datatracker.ietf.org/doc/rfc4566/?include_text=1 -// https://blog.csdn.net/aggresss/article/details/109850434 -// https://aggresss.blog.csdn.net/article/details/106436703 -// Session description -// v= (protocol version) -// o= (originator and session identifier) -// s= (session name) -// i=* (session information) -// u=* (URI of description) -// e=* (email address) -// p=* (phone number) -// c=* (connection information -- not required if included in -// all media) -// b=* (zero or more bandwidth information lines) -// One or more time descriptions ("t=" and "r=" lines; see below) -// z=* (time zone adjustments) -// k=* (encryption key) -// a=* (zero or more session attribute lines) -// Zero or more media descriptions -// -// Time description -// t= (time the session is active) -// r=* (zero or more repeat times) -// -// Media description, if present -// m= (media name and transport address) -// i=* (media title) -// c=* (connection information -- optional if included at -// session level) -// b=* (zero or more bandwidth information lines) -// k=* (encryption key) -// a=* (zero or more media attribute lines) - -enum class RtpDirection { - invalid = -1, - // 只发送 [AUTO-TRANSLATED:d7e7fdb7] - // Send only - sendonly, - // 只接收 [AUTO-TRANSLATED:f75ca789] - // Receive only - recvonly, - // 同时发送接收 [AUTO-TRANSLATED:7f900ba1] - // Send and receive simultaneously - sendrecv, - // 禁止发送数据 [AUTO-TRANSLATED:6045b47e] - // Prohibit sending data - inactive -}; - -enum class DtlsRole { - invalid = -1, - // 客户端 [AUTO-TRANSLATED:915417a2] - // Client - active, - // 服务端 [AUTO-TRANSLATED:03a80b18] - // Server - passive, - // 既可作做客户端也可以做服务端 [AUTO-TRANSLATED:5ab1162e] - // Can be used as both client and server - actpass, -}; - -enum class SdpType { invalid = -1, offer, answer }; - -DtlsRole getDtlsRole(const std::string &str); -const char *getDtlsRoleString(DtlsRole role); -RtpDirection getRtpDirection(const std::string &str); -const char *getRtpDirectionString(RtpDirection val); - -class SdpItem { -public: - using Ptr = std::shared_ptr; - virtual ~SdpItem() = default; - virtual void parse(const std::string &str) { value = str; } - virtual std::string toString() const { return value; } - virtual const char *getKey() const = 0; - - void reset() { value.clear(); } - -protected: - mutable std::string value; -}; - -template -class SdpString : public SdpItem { -public: - SdpString() = default; - SdpString(std::string val) { value = std::move(val); } - // *=* - const char* getKey() const override { static std::string key(1, KEY); return key.data();} -}; - -class SdpCommon : public SdpItem { -public: - std::string key; - SdpCommon(std::string key) { this->key = std::move(key); } - SdpCommon(std::string key, std::string val) { - this->key = std::move(key); - this->value = std::move(val); - } - - const char *getKey() const override { return key.data(); } -}; - -class SdpTime : public SdpItem { -public: - // 5.9. Timing ("t=") - // t= - uint64_t start { 0 }; - uint64_t stop { 0 }; - void parse(const std::string &str) override; - std::string toString() const override; - const char *getKey() const override { return "t"; } -}; - -class SdpOrigin : public SdpItem { -public: - // 5.2. Origin ("o=") - // o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5 - // o= - std::string username { "-" }; - std::string session_id; - std::string session_version; - std::string nettype { "IN" }; - std::string addrtype { "IP4" }; - std::string address { "0.0.0.0" }; - void parse(const std::string &str) override; - std::string toString() const override; - const char *getKey() const override { return "o"; } - bool empty() const { - return username.empty() || session_id.empty() || session_version.empty() - || nettype.empty() || addrtype.empty() || address.empty(); - } -}; - -class SdpConnection : public SdpItem { -public: - // 5.7. Connection Data ("c=") - // c=IN IP4 224.2.17.12/127 - // c= - std::string nettype { "IN" }; - std::string addrtype { "IP4" }; - std::string address { "0.0.0.0" }; - void parse(const std::string &str) override; - std::string toString() const override; - const char *getKey() const override { return "c"; } - bool empty() const { return address.empty(); } -}; - -class SdpBandwidth : public SdpItem { -public: - // 5.8. Bandwidth ("b=") - // b=: - - // AS、CT [AUTO-TRANSLATED:65298206] - // AS, CT - std::string bwtype { "AS" }; - uint32_t bandwidth { 0 }; - - void parse(const std::string &str) override; - std::string toString() const override; - const char *getKey() const override { return "b"; } - bool empty() const { return bandwidth == 0; } -}; - -class SdpMedia : public SdpItem { -public: - // 5.14. Media Descriptions ("m=") - // m= ... - TrackType type; - uint16_t port; - // RTP/AVP:应用场景为视频/音频的 RTP 协议。参考 RFC 3551 [AUTO-TRANSLATED:7a9d7e86] - // RTP/AVP: The application scenario is the RTP protocol for video/audio. Refer to RFC 3551 - // RTP/SAVP:应用场景为视频/音频的 SRTP 协议。参考 RFC 3711 [AUTO-TRANSLATED:7989a619] - // RTP/SAVP: The application scenario is the SRTP protocol for video/audio. Refer to RFC 3711 - // RTP/AVPF: 应用场景为视频/音频的 RTP 协议,支持 RTCP-based Feedback。参考 RFC 4585 [AUTO-TRANSLATED:71241e80] - // RTP/AVPF: The application scenario is the RTP protocol for video/audio, supporting RTCP-based Feedback. Refer to RFC 4585 - // RTP/SAVPF: 应用场景为视频/音频的 SRTP 协议,支持 RTCP-based Feedback。参考 RFC 5124 [AUTO-TRANSLATED:69015267] - // RTP/SAVPF: The application scenario is the SRTP protocol for video/audio, supporting RTCP-based Feedback. Refer to RFC 5124 - std::string proto; - std::vector fmts; - - void parse(const std::string &str) override; - std::string toString() const override; - const char *getKey() const override { return "m"; } -}; - -class SdpAttr : public SdpItem { -public: - using Ptr = std::shared_ptr; - // 5.13. Attributes ("a=") - // a= - // a=: - SdpItem::Ptr detail; - void parse(const std::string &str) override; - std::string toString() const override; - const char *getKey() const override { return "a"; } -}; - -class SdpAttrGroup : public SdpItem { -public: - // a=group:BUNDLE line with all the 'mid' identifiers part of the - // BUNDLE group is included at the session-level. - // a=group:LS session level attribute MUST be included wth the 'mid' - // identifiers that are part of the same lip sync group. - std::string type { "BUNDLE" }; - std::vector mids; - void parse(const std::string &str) override; - std::string toString() const override; - const char *getKey() const override { return "group"; } -}; - -class SdpAttrMsidSemantic : public SdpItem { -public: - // https://tools.ietf.org/html/draft-alvestrand-rtcweb-msid-02#section-3 - // 3. The Msid-Semantic Attribute - // - // In order to fully reproduce the semantics of the SDP and SSRC - // grouping frameworks, a session-level attribute is defined for - // signalling the semantics associated with an msid grouping. - // - // This OPTIONAL attribute gives the message ID and its group semantic. - // a=msid-semantic: examplefoo LS - // - // - // The ABNF of msid-semantic is: - // - // msid-semantic-attr = "msid-semantic:" " " msid token - // token = - // - // The semantic field may hold values from the IANA registries - // "Semantics for the "ssrc-group" SDP Attribute" and "Semantics for the - // "group" SDP Attribute". - // a=msid-semantic: WMS 616cfbb1-33a3-4d8c-8275-a199d6005549 - std::string msid { "WMS" }; - std::string token; - void parse(const std::string &str) override; - std::string toString() const override; - const char *getKey() const override { return "msid-semantic"; } - bool empty() const { return msid.empty(); } -}; - -class SdpAttrRtcp : public SdpItem { -public: - // a=rtcp:9 IN IP4 0.0.0.0 - uint16_t port { 0 }; - std::string nettype { "IN" }; - std::string addrtype { "IP4" }; - std::string address { "0.0.0.0" }; - void parse(const std::string &str) override; - ; - std::string toString() const override; - const char *getKey() const override { return "rtcp"; } - bool empty() const { return address.empty() || !port; } -}; - -class SdpAttrIceUfrag : public SdpItem { -public: - SdpAttrIceUfrag() = default; - SdpAttrIceUfrag(std::string str) { value = std::move(str); } - // a=ice-ufrag:sXJ3 - const char *getKey() const override { return "ice-ufrag"; } -}; - -class SdpAttrIcePwd : public SdpItem { -public: - SdpAttrIcePwd() = default; - SdpAttrIcePwd(std::string str) { value = std::move(str); } - // a=ice-pwd:yEclOTrLg1gEubBFefOqtmyV - const char *getKey() const override { return "ice-pwd"; } -}; - -class SdpAttrIceOption : public SdpItem { -public: - // a=ice-options:trickle - bool trickle { false }; - bool renomination { false }; - void parse(const std::string &str) override; - std::string toString() const override; - const char *getKey() const override { return "ice-options"; } -}; - -class SdpAttrFingerprint : public SdpItem { -public: - // a=fingerprint:sha-256 22:14:B5:AF:66:12:C7:C7:8D:EF:4B:DE:40:25:ED:5D:8F:17:54:DD:88:33:C0:13:2E:FD:1A:FA:7E:7A:1B:79 - std::string algorithm; - std::string hash; - void parse(const std::string &str) override; - std::string toString() const override; - const char *getKey() const override { return "fingerprint"; } - bool empty() const { return algorithm.empty() || hash.empty(); } -}; - -class SdpAttrSetup : public SdpItem { -public: - // a=setup:actpass - SdpAttrSetup() = default; - SdpAttrSetup(DtlsRole r) { role = r; } - DtlsRole role { DtlsRole::actpass }; - void parse(const std::string &str) override; - std::string toString() const override; - const char *getKey() const override { return "setup"; } -}; - -class SdpAttrMid : public SdpItem { -public: - SdpAttrMid() = default; - SdpAttrMid(std::string val) { value = std::move(val); } - // a=mid:audio - const char *getKey() const override { return "mid"; } -}; - -class SdpAttrExtmap : public SdpItem { -public: - // https://aggresss.blog.csdn.net/article/details/106436703 - // a=extmap:1[/sendonly] urn:ietf:params:rtp-hdrext:ssrc-audio-level - uint8_t id; - RtpDirection direction { RtpDirection::invalid }; - std::string ext; - void parse(const std::string &str) override; - std::string toString() const override; - const char *getKey() const override { return "extmap"; } -}; - -class SdpAttrRtpMap : public SdpItem { -public: - // a=rtpmap:111 opus/48000/2 - uint8_t pt; - std::string codec; - uint32_t sample_rate; - uint32_t channel { 0 }; - void parse(const std::string &str) override; - std::string toString() const override; - const char *getKey() const override { return "rtpmap"; } -}; - -class SdpAttrRtcpFb : public SdpItem { -public: - // a=rtcp-fb:98 nack pli - // a=rtcp-fb:120 nack 支持 nack 重传,nack (Negative-Acknowledgment) 。 [AUTO-TRANSLATED:08d5c4e2] - // a=rtcp-fb:120 nack supports nack retransmission, nack (Negative-Acknowledgment). - // a=rtcp-fb:120 nack pli 支持 nack 关键帧重传,PLI (Picture Loss Indication) 。 [AUTO-TRANSLATED:c331c1dd] - // a=rtcp-fb:120 nack pli supports nack keyframe retransmission, PLI (Picture Loss Indication). - // a=rtcp-fb:120 ccm fir 支持编码层关键帧请求,CCM (Codec Control Message),FIR (Full Intra Request ),通常与 nack pli 有同样的效果,但是 nack pli [AUTO-TRANSLATED:7090fdc9] - // a=rtcp-fb:120 ccm fir supports keyframe requests for the coding layer, CCM (Codec Control Message), FIR (Full Intra Request), which usually has the same effect as nack pli, but nack pli - // 是用于重传时的关键帧请求。 a=rtcp-fb:120 goog-remb 支持 REMB (Receiver Estimated Maximum Bitrate) 。 a=rtcp-fb:120 transport-cc 支持 TCC (Transport [AUTO-TRANSLATED:ffac8e91] - // is used for keyframe requests during retransmission. a=rtcp-fb:120 goog-remb supports REMB (Receiver Estimated Maximum Bitrate). a=rtcp-fb:120 transport-cc supports TCC (Transport - // Congest Control) 。 [AUTO-TRANSLATED:dcf53e31] - // Congest Control). - uint8_t pt; - std::string rtcp_type; - void parse(const std::string &str) override; - std::string toString() const override; - const char *getKey() const override { return "rtcp-fb"; } -}; - -class SdpAttrFmtp : public SdpItem { -public: - // fmtp:96 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f - uint8_t pt; - std::map fmtp; - void parse(const std::string &str) override; - std::string toString() const override; - const char *getKey() const override { return "fmtp"; } -}; - -class SdpAttrSSRC : public SdpItem { -public: - // a=ssrc:3245185839 cname:Cx4i/VTR51etgjT7 - // a=ssrc:3245185839 msid:cb373bff-0fea-4edb-bc39-e49bb8e8e3b9 0cf7e597-36a2-4480-9796-69bf0955eef5 - // a=ssrc:3245185839 mslabel:cb373bff-0fea-4edb-bc39-e49bb8e8e3b9 - // a=ssrc:3245185839 label:0cf7e597-36a2-4480-9796-69bf0955eef5 - // a=ssrc: - // a=ssrc: : - // cname 是必须的,msid/mslabel/label 这三个属性都是 WebRTC 自创的,或者说 Google 自创的,可以参考 https://tools.ietf.org/html/draft-ietf-mmusic-msid-17, [AUTO-TRANSLATED:d8cb1baf] - // cname is required, msid/mslabel/label these three attributes are all created by WebRTC, or Google created, you can refer to https://tools.ietf.org/html/draft-ietf-mmusic-msid-17, - // 理解它们三者的关系需要先了解三个概念:RTP stream / MediaStreamTrack / MediaStream : [AUTO-TRANSLATED:7d385cf5] - // understanding the relationship between the three requires understanding three concepts: RTP stream / MediaStreamTrack / MediaStream: - // 一个 a=ssrc 代表一个 RTP stream ; [AUTO-TRANSLATED:ee1ecc6f] - // One a=ssrc represents one RTP stream; - // 一个 MediaStreamTrack 通常包含一个或多个 RTP stream,例如一个视频 MediaStreamTrack 中通常包含两个 RTP stream,一个用于常规传输,一个用于 nack 重传; [AUTO-TRANSLATED:e8ddf0fd] - // A MediaStreamTrack usually contains one or more RTP streams, for example, a video MediaStreamTrack usually contains two RTP streams, one for regular transmission and one for nack retransmission; - // 一个 MediaStream 通常包含一个或多个 MediaStreamTrack ,例如 simulcast 场景下,一个 MediaStream 通常会包含三个不同编码质量的 MediaStreamTrack ; [AUTO-TRANSLATED:31318d43] - // A MediaStream usually contains one or more MediaStreamTrack, for example, in a simulcast scenario, a MediaStream usually contains three MediaStreamTrack of different encoding quality; - // 这种标记方式并不被 Firefox 认可,在 Firefox 生成的 SDP 中一个 a=ssrc 通常只有一行,例如: [AUTO-TRANSLATED:8c2c424c] - // This marking method is not recognized by Firefox, in the SDP generated by Firefox, one a=ssrc usually has only one line, for example: - // a=ssrc:3245185839 cname:Cx4i/VTR51etgjT7 - - uint32_t ssrc; - std::string attribute; - std::string attribute_value; - void parse(const std::string &str) override; - std::string toString() const override; - const char *getKey() const override { return "ssrc"; } -}; - -class SdpAttrSSRCGroup : public SdpItem { -public: - // a=ssrc-group 定义参考 RFC 5576(https://tools.ietf.org/html/rfc5576) ,用于描述多个 ssrc 之间的关联,常见的有两种: [AUTO-TRANSLATED:a87cbcc6] - // a=ssrc-group definition refers to RFC 5576(https://tools.ietf.org/html/rfc5576), used to describe the association between multiple ssrcs, there are two common types: - // a=ssrc-group:FID 2430709021 3715850271 - // FID (Flow Identification) 最初用在 FEC 的关联中,WebRTC 中通常用于关联一组常规 RTP stream 和 重传 RTP stream 。 [AUTO-TRANSLATED:f2c0fcbb] - // FID (Flow Identification) was originally used in FEC association, and in WebRTC it is usually used to associate a group of regular RTP streams and retransmission RTP streams. - // a=ssrc-group:SIM 360918977 360918978 360918980 - // 在 Chrome 独有的 SDP munging 风格的 simulcast 中使用,将三组编码质量由低到高的 MediaStreamTrack 关联在一起。 [AUTO-TRANSLATED:61bf7596] - // Used in Chrome's unique SDP munging style simulcast, associating three groups of MediaStreamTrack from low to high encoding quality. - std::string type { "FID" }; - std::vector ssrcs; - - bool isFID() const { return type == "FID"; } - bool isSIM() const { return type == "SIM"; } - void parse(const std::string &str) override; - std::string toString() const override; - const char *getKey() const override { return "ssrc-group"; } -}; - -class SdpAttrSctpMap : public SdpItem { -public: - // https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-05 - // a=sctpmap:5000 webrtc-datachannel 1024 - // a=sctpmap: sctpmap-number media-subtypes [streams] - uint16_t port = 0; - std::string subtypes; - uint32_t streams = 0; - void parse(const std::string &str) override; - std::string toString() const override; - const char *getKey() const override { return "sctpmap"; } - bool empty() const { return port == 0 && subtypes.empty() && streams == 0; } -}; - -class SdpAttrCandidate : public SdpItem { -public: - using Ptr = std::shared_ptr; - // https://tools.ietf.org/html/rfc5245 - // 15.1. "candidate" Attribute - // a=candidate:4 1 udp 2 192.168.1.7 58107 typ host - // a=candidate:
typ - std::string foundation; - // 传输媒体的类型,1代表RTP;2代表 RTCP。 [AUTO-TRANSLATED:9ec924a6] - // The type of media to be transmitted, 1 represents RTP; 2 represents RTCP. - uint32_t component; - std::string transport { "udp" }; - uint32_t priority; - std::string address; - uint16_t port; - std::string type; - std::vector> arr; - - void parse(const std::string &str) override; - std::string toString() const override; - const char *getKey() const override { return "candidate"; } -}; - -class SdpAttrMsid : public SdpItem { -public: - const char *getKey() const override { return "msid"; } -}; - -class SdpAttrExtmapAllowMixed : public SdpItem { -public: - const char *getKey() const override { return "extmap-allow-mixed"; } -}; - -class SdpAttrSimulcast : public SdpItem { -public: - // https://www.meetecho.com/blog/simulcast-janus-ssrc/ - // https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-14 - const char *getKey() const override { return "simulcast"; } - void parse(const std::string &str) override; - std::string toString() const override; - bool empty() const { return rids.empty(); } - std::string direction; - std::vector rids; -}; - -class SdpAttrRid : public SdpItem { -public: - void parse(const std::string &str) override; - std::string toString() const override; - const char *getKey() const override { return "rid"; } - std::string direction; - std::string rid; -}; - -class RtcSdpBase { -public: - void addItem(SdpItem::Ptr item) { items.push_back(std::move(item)); } - void addAttr(SdpItem::Ptr attr) { - auto item = std::make_shared(); - item->detail = std::move(attr); - items.push_back(std::move(item)); - } - - virtual ~RtcSdpBase() = default; - virtual std::string toString() const; - void toRtsp(); - - RtpDirection getDirection() const; - - template - cls getItemClass(char key, const char *attr_key = nullptr) const { - auto item = std::dynamic_pointer_cast(getItem(key, attr_key)); - if (!item) { - return cls(); - } - return *item; - } - - std::string getStringItem(char key, const char *attr_key = nullptr) const { - auto item = getItem(key, attr_key); - if (!item) { - return ""; - } - return item->toString(); - } - - SdpItem::Ptr getItem(char key, const char *attr_key = nullptr) const; - - template - std::vector getAllItem(char key_c, const char *attr_key = nullptr) const { - std::vector ret; - std::string key(1, key_c); - for (auto item : items) { - if (strcasecmp(item->getKey(), key.data()) == 0) { - if (!attr_key) { - auto c = std::dynamic_pointer_cast(item); - if (c) { - ret.emplace_back(*c); - } - } else { - auto attr = std::dynamic_pointer_cast(item); - if (attr && !strcasecmp(attr->detail->getKey(), attr_key)) { - auto c = std::dynamic_pointer_cast(attr->detail); - if (c) { - ret.emplace_back(*c); - } - } - } - } - } - return ret; - } - -private: - std::vector items; -}; - -class RtcSessionSdp : public RtcSdpBase { -public: - using Ptr = std::shared_ptr; - int getVersion() const; - SdpOrigin getOrigin() const; - std::string getSessionName() const; - std::string getSessionInfo() const; - SdpTime getSessionTime() const; - SdpConnection getConnection() const; - SdpBandwidth getBandwidth() const; - - std::string getUri() const; - std::string getEmail() const; - std::string getPhone() const; - std::string getTimeZone() const; - std::string getEncryptKey() const; - std::string getRepeatTimes() const; - - std::vector medias; - void parse(const std::string &str); - std::string toString() const override; -}; - -////////////////////////////////////////////////////////////////// - -// ssrc相关信息 [AUTO-TRANSLATED:954c641d] -// ssrc related information -class RtcSSRC { -public: - uint32_t ssrc { 0 }; - uint32_t rtx_ssrc { 0 }; - std::string cname; - std::string msid; - std::string mslabel; - std::string label; - - bool empty() const { return ssrc == 0 && cname.empty(); } -}; - -// rtc传输编码方案 [AUTO-TRANSLATED:8b911508] -// rtc transmission encoding scheme -class RtcCodecPlan { -public: - using Ptr = std::shared_ptr; - uint8_t pt; - std::string codec; - uint32_t sample_rate; - // 音频时有效 [AUTO-TRANSLATED:5b230fc8] - // Valid for audio - uint32_t channel = 0; - // rtcp反馈 [AUTO-TRANSLATED:580378bd] - // RTCP feedback - std::set rtcp_fb; - std::map fmtp; - - std::string getFmtp(const char *key) const; -}; - -// rtc 媒体描述 [AUTO-TRANSLATED:b1711a11] -// RTC media description -class RtcMedia { -public: - TrackType type { TrackType::TrackInvalid }; - std::string mid; - uint16_t port { 0 }; - SdpConnection addr; - SdpBandwidth bandwidth; - std::string proto; - RtpDirection direction { RtpDirection::invalid }; - std::vector plan; - - //////// rtp //////// - std::vector rtp_rtx_ssrc; - - //////// simulcast //////// - std::vector rtp_ssrc_sim; - std::vector rtp_rids; - - //////// rtcp //////// - bool rtcp_mux { false }; - bool rtcp_rsize { false }; - SdpAttrRtcp rtcp_addr; - - //////// ice //////// - bool ice_trickle { false }; - bool ice_lite { false }; - bool ice_renomination { false }; - std::string ice_ufrag; - std::string ice_pwd; - std::vector candidate; - - //////// dtls //////// - DtlsRole role { DtlsRole::invalid }; - SdpAttrFingerprint fingerprint; - - //////// extmap //////// - std::vector extmap; - - //////// sctp //////////// - SdpAttrSctpMap sctpmap; - uint32_t sctp_port { 0 }; - - void checkValid() const; - const RtcCodecPlan *getPlan(uint8_t pt) const; - const RtcCodecPlan *getPlan(const char *codec) const; - const RtcCodecPlan *getRelatedRtxPlan(uint8_t pt) const; - uint32_t getRtpSSRC() const; - uint32_t getRtxSSRC() const; - bool supportSimulcast() const; -}; - -class RtcSession { -public: - using Ptr = std::shared_ptr; - - uint32_t version; - SdpOrigin origin; - std::string session_name; - std::string session_info; - SdpTime time; - SdpConnection connection; - SdpAttrMsidSemantic msid_semantic; - std::vector media; - SdpAttrGroup group; - - void loadFrom(const std::string &sdp); - void checkValid() const; - std::string toString() const; - std::string toRtspSdp() const; - const RtcMedia *getMedia(TrackType type) const; - bool supportRtcpFb(const std::string &name, TrackType type = TrackType::TrackVideo) const; - bool supportSimulcast() const; - bool isOnlyDatachannel() const; - -private: - RtcSessionSdp::Ptr toRtcSessionSdp() const; -}; - -class RtcConfigure { -public: - using Ptr = std::shared_ptr; - class RtcTrackConfigure { - public: - bool rtcp_mux; - bool rtcp_rsize; - bool group_bundle; - bool support_rtx; - bool support_red; - bool support_ulpfec; - bool ice_lite; - bool ice_trickle; - bool ice_renomination; - std::string ice_ufrag; - std::string ice_pwd; - - RtpDirection direction { RtpDirection::invalid }; - SdpAttrFingerprint fingerprint; - - std::set rtcp_fb; - std::map extmap; - std::vector preferred_codec; - std::vector candidate; - - void setDefaultSetting(TrackType type); - void enableTWCC(bool enable = true); - void enableREMB(bool enable = true); - }; - - RtcTrackConfigure video; - RtcTrackConfigure audio; - RtcTrackConfigure application; - - void setDefaultSetting(std::string ice_ufrag, std::string ice_pwd, RtpDirection direction, const SdpAttrFingerprint &fingerprint); - void addCandidate(const SdpAttrCandidate &candidate, TrackType type = TrackInvalid); - - std::shared_ptr createAnswer(const RtcSession &offer) const; - - void setPlayRtspInfo(const std::string &sdp); - - void enableTWCC(bool enable = true, TrackType type = TrackInvalid); - void enableREMB(bool enable = true, TrackType type = TrackInvalid); - -private: - void matchMedia(const std::shared_ptr &ret, const RtcMedia &media) const; - bool onCheckCodecProfile(const RtcCodecPlan &plan, CodecId codec) const; - void onSelectPlan(RtcCodecPlan &plan, CodecId codec) const; - -private: - RtcCodecPlan::Ptr _rtsp_video_plan; - RtcCodecPlan::Ptr _rtsp_audio_plan; -}; - -class SdpConst { -public: - static std::string const kTWCCRtcpFb; - static std::string const kRembRtcpFb; - -private: - SdpConst() = delete; - ~SdpConst() = delete; -}; - -} // namespace mediakit - -#endif // ZLMEDIAKIT_SDP_H +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLMEDIAKIT_SDP_H +#define ZLMEDIAKIT_SDP_H + +#include +#include +#include +#include +#include "RtpExt.h" +#include "RtpMap.h" +#include "Extension/Frame.h" +#include "Common/Parser.h" + +namespace mediakit { + +// https://datatracker.ietf.org/doc/rfc4566/?include_text=1 +// https://blog.csdn.net/aggresss/article/details/109850434 +// https://aggresss.blog.csdn.net/article/details/106436703 +// Session description +// v= (protocol version) +// o= (originator and session identifier) +// s= (session name) +// i=* (session information) +// u=* (URI of description) +// e=* (email address) +// p=* (phone number) +// c=* (connection information -- not required if included in +// all media) +// b=* (zero or more bandwidth information lines) +// One or more time descriptions ("t=" and "r=" lines; see below) +// z=* (time zone adjustments) +// k=* (encryption key) +// a=* (zero or more session attribute lines) +// Zero or more media descriptions +// +// Time description +// t= (time the session is active) +// r=* (zero or more repeat times) +// +// Media description, if present +// m= (media name and transport address) +// i=* (media title) +// c=* (connection information -- optional if included at +// session level) +// b=* (zero or more bandwidth information lines) +// k=* (encryption key) +// a=* (zero or more media attribute lines) + +enum class RtpDirection { + invalid = -1, + // 只发送 [AUTO-TRANSLATED:d7e7fdb7] + // Send only + sendonly, + // 只接收 [AUTO-TRANSLATED:f75ca789] + // Receive only + recvonly, + // 同时发送接收 [AUTO-TRANSLATED:7f900ba1] + // Send and receive simultaneously + sendrecv, + // 禁止发送数据 [AUTO-TRANSLATED:6045b47e] + // Prohibit sending data + inactive +}; + +enum class DtlsRole { + invalid = -1, + // 客户端 [AUTO-TRANSLATED:915417a2] + // Client + active, + // 服务端 [AUTO-TRANSLATED:03a80b18] + // Server + passive, + // 既可作做客户端也可以做服务端 [AUTO-TRANSLATED:5ab1162e] + // Can be used as both client and server + actpass, +}; + +enum class SdpType { invalid = -1, offer, answer }; + +DtlsRole getDtlsRole(const std::string &str); +const char *getDtlsRoleString(DtlsRole role); +RtpDirection getRtpDirection(const std::string &str); +const char *getRtpDirectionString(RtpDirection val); + +class SdpItem { +public: + using Ptr = std::shared_ptr; + virtual ~SdpItem() = default; + virtual void parse(const std::string &str) { value = str; } + virtual std::string toString() const { return value; } + virtual const char *getKey() const = 0; + + void reset() { value.clear(); } + +protected: + mutable std::string value; +}; + +template +class SdpString : public SdpItem { +public: + SdpString() = default; + SdpString(std::string val) { value = std::move(val); } + // *=* + const char* getKey() const override { static std::string key(1, KEY); return key.data();} +}; + +class SdpCommon : public SdpItem { +public: + std::string key; + SdpCommon(std::string key) { this->key = std::move(key); } + SdpCommon(std::string key, std::string val) { + this->key = std::move(key); + this->value = std::move(val); + } + + const char *getKey() const override { return key.data(); } +}; + +class SdpTime : public SdpItem { +public: + // 5.9. Timing ("t=") + // t= + uint64_t start { 0 }; + uint64_t stop { 0 }; + void parse(const std::string &str) override; + std::string toString() const override; + const char *getKey() const override { return "t"; } +}; + +class SdpOrigin : public SdpItem { +public: + // 5.2. Origin ("o=") + // o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5 + // o= + std::string username { "-" }; + std::string session_id; + std::string session_version; + std::string nettype { "IN" }; + std::string addrtype { "IP4" }; + std::string address { "0.0.0.0" }; + void parse(const std::string &str) override; + std::string toString() const override; + const char *getKey() const override { return "o"; } + bool empty() const { + return username.empty() || session_id.empty() || session_version.empty() + || nettype.empty() || addrtype.empty() || address.empty(); + } +}; + +class SdpConnection : public SdpItem { +public: + // 5.7. Connection Data ("c=") + // c=IN IP4 224.2.17.12/127 + // c= + std::string nettype { "IN" }; + std::string addrtype { "IP4" }; + std::string address { "0.0.0.0" }; + void parse(const std::string &str) override; + std::string toString() const override; + const char *getKey() const override { return "c"; } + bool empty() const { return address.empty(); } +}; + +class SdpBandwidth : public SdpItem { +public: + // 5.8. Bandwidth ("b=") + // b=: + + // AS、CT [AUTO-TRANSLATED:65298206] + // AS, CT + std::string bwtype { "AS" }; + uint32_t bandwidth { 0 }; + + void parse(const std::string &str) override; + std::string toString() const override; + const char *getKey() const override { return "b"; } + bool empty() const { return bandwidth == 0; } +}; + +class SdpMedia : public SdpItem { +public: + // 5.14. Media Descriptions ("m=") + // m= ... + TrackType type; + uint16_t port; + // RTP/AVP:应用场景为视频/音频的 RTP 协议。参考 RFC 3551 [AUTO-TRANSLATED:7a9d7e86] + // RTP/AVP: The application scenario is the RTP protocol for video/audio. Refer to RFC 3551 + // RTP/SAVP:应用场景为视频/音频的 SRTP 协议。参考 RFC 3711 [AUTO-TRANSLATED:7989a619] + // RTP/SAVP: The application scenario is the SRTP protocol for video/audio. Refer to RFC 3711 + // RTP/AVPF: 应用场景为视频/音频的 RTP 协议,支持 RTCP-based Feedback。参考 RFC 4585 [AUTO-TRANSLATED:71241e80] + // RTP/AVPF: The application scenario is the RTP protocol for video/audio, supporting RTCP-based Feedback. Refer to RFC 4585 + // RTP/SAVPF: 应用场景为视频/音频的 SRTP 协议,支持 RTCP-based Feedback。参考 RFC 5124 [AUTO-TRANSLATED:69015267] + // RTP/SAVPF: The application scenario is the SRTP protocol for video/audio, supporting RTCP-based Feedback. Refer to RFC 5124 + std::string proto; + std::vector fmts; + + void parse(const std::string &str) override; + std::string toString() const override; + const char *getKey() const override { return "m"; } +}; + +class SdpAttr : public SdpItem { +public: + using Ptr = std::shared_ptr; + // 5.13. Attributes ("a=") + // a= + // a=: + SdpItem::Ptr detail; + void parse(const std::string &str) override; + std::string toString() const override; + const char *getKey() const override { return "a"; } +}; + +class SdpAttrGroup : public SdpItem { +public: + // a=group:BUNDLE line with all the 'mid' identifiers part of the + // BUNDLE group is included at the session-level. + // a=group:LS session level attribute MUST be included wth the 'mid' + // identifiers that are part of the same lip sync group. + std::string type { "BUNDLE" }; + std::vector mids; + void parse(const std::string &str) override; + std::string toString() const override; + const char *getKey() const override { return "group"; } +}; + +class SdpAttrMsidSemantic : public SdpItem { +public: + // https://tools.ietf.org/html/draft-alvestrand-rtcweb-msid-02#section-3 + // 3. The Msid-Semantic Attribute + // + // In order to fully reproduce the semantics of the SDP and SSRC + // grouping frameworks, a session-level attribute is defined for + // signalling the semantics associated with an msid grouping. + // + // This OPTIONAL attribute gives the message ID and its group semantic. + // a=msid-semantic: examplefoo LS + // + // + // The ABNF of msid-semantic is: + // + // msid-semantic-attr = "msid-semantic:" " " msid token + // token = + // + // The semantic field may hold values from the IANA registries + // "Semantics for the "ssrc-group" SDP Attribute" and "Semantics for the + // "group" SDP Attribute". + // a=msid-semantic: WMS 616cfbb1-33a3-4d8c-8275-a199d6005549 + std::string msid { "WMS" }; + std::string token; + void parse(const std::string &str) override; + std::string toString() const override; + const char *getKey() const override { return "msid-semantic"; } + bool empty() const { return msid.empty(); } +}; + +class SdpAttrRtcp : public SdpItem { +public: + // a=rtcp:9 IN IP4 0.0.0.0 + uint16_t port { 0 }; + std::string nettype { "IN" }; + std::string addrtype { "IP4" }; + std::string address { "0.0.0.0" }; + void parse(const std::string &str) override; + ; + std::string toString() const override; + const char *getKey() const override { return "rtcp"; } + bool empty() const { return address.empty() || !port; } +}; + +class SdpAttrIceUfrag : public SdpItem { +public: + SdpAttrIceUfrag() = default; + SdpAttrIceUfrag(std::string str) { value = std::move(str); } + // a=ice-ufrag:sXJ3 + const char *getKey() const override { return "ice-ufrag"; } +}; + +class SdpAttrIcePwd : public SdpItem { +public: + SdpAttrIcePwd() = default; + SdpAttrIcePwd(std::string str) { value = std::move(str); } + // a=ice-pwd:yEclOTrLg1gEubBFefOqtmyV + const char *getKey() const override { return "ice-pwd"; } +}; + +class SdpAttrIceOption : public SdpItem { +public: + // a=ice-options:trickle + bool trickle { false }; + bool renomination { false }; + void parse(const std::string &str) override; + std::string toString() const override; + const char *getKey() const override { return "ice-options"; } +}; + +class SdpAttrFingerprint : public SdpItem { +public: + // a=fingerprint:sha-256 22:14:B5:AF:66:12:C7:C7:8D:EF:4B:DE:40:25:ED:5D:8F:17:54:DD:88:33:C0:13:2E:FD:1A:FA:7E:7A:1B:79 + std::string algorithm; + std::string hash; + void parse(const std::string &str) override; + std::string toString() const override; + const char *getKey() const override { return "fingerprint"; } + bool empty() const { return algorithm.empty() || hash.empty(); } +}; + +class SdpAttrSetup : public SdpItem { +public: + // a=setup:actpass + SdpAttrSetup() = default; + SdpAttrSetup(DtlsRole r) { role = r; } + DtlsRole role { DtlsRole::actpass }; + void parse(const std::string &str) override; + std::string toString() const override; + const char *getKey() const override { return "setup"; } +}; + +class SdpAttrMid : public SdpItem { +public: + SdpAttrMid() = default; + SdpAttrMid(std::string val) { value = std::move(val); } + // a=mid:audio + const char *getKey() const override { return "mid"; } +}; + +class SdpAttrExtmap : public SdpItem { +public: + // https://aggresss.blog.csdn.net/article/details/106436703 + // a=extmap:1[/sendonly] urn:ietf:params:rtp-hdrext:ssrc-audio-level + uint8_t id; + RtpDirection direction { RtpDirection::invalid }; + std::string ext; + void parse(const std::string &str) override; + std::string toString() const override; + const char *getKey() const override { return "extmap"; } +}; + +class SdpAttrRtpMap : public SdpItem { +public: + // a=rtpmap:111 opus/48000/2 + uint8_t pt; + std::string codec; + uint32_t sample_rate; + uint32_t channel { 0 }; + void parse(const std::string &str) override; + std::string toString() const override; + const char *getKey() const override { return "rtpmap"; } +}; + +class SdpAttrRtcpFb : public SdpItem { +public: + // a=rtcp-fb:98 nack pli + // a=rtcp-fb:120 nack 支持 nack 重传,nack (Negative-Acknowledgment) 。 [AUTO-TRANSLATED:08d5c4e2] + // a=rtcp-fb:120 nack supports nack retransmission, nack (Negative-Acknowledgment). + // a=rtcp-fb:120 nack pli 支持 nack 关键帧重传,PLI (Picture Loss Indication) 。 [AUTO-TRANSLATED:c331c1dd] + // a=rtcp-fb:120 nack pli supports nack keyframe retransmission, PLI (Picture Loss Indication). + // a=rtcp-fb:120 ccm fir 支持编码层关键帧请求,CCM (Codec Control Message),FIR (Full Intra Request ),通常与 nack pli 有同样的效果,但是 nack pli [AUTO-TRANSLATED:7090fdc9] + // a=rtcp-fb:120 ccm fir supports keyframe requests for the coding layer, CCM (Codec Control Message), FIR (Full Intra Request), which usually has the same effect as nack pli, but nack pli + // 是用于重传时的关键帧请求。 a=rtcp-fb:120 goog-remb 支持 REMB (Receiver Estimated Maximum Bitrate) 。 a=rtcp-fb:120 transport-cc 支持 TCC (Transport [AUTO-TRANSLATED:ffac8e91] + // is used for keyframe requests during retransmission. a=rtcp-fb:120 goog-remb supports REMB (Receiver Estimated Maximum Bitrate). a=rtcp-fb:120 transport-cc supports TCC (Transport + // Congest Control) 。 [AUTO-TRANSLATED:dcf53e31] + // Congest Control). + uint8_t pt; + std::string rtcp_type; + void parse(const std::string &str) override; + std::string toString() const override; + const char *getKey() const override { return "rtcp-fb"; } +}; + +class SdpAttrFmtp : public SdpItem { +public: + // fmtp:96 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f + uint8_t pt; + std::map fmtp; + void parse(const std::string &str) override; + std::string toString() const override; + const char *getKey() const override { return "fmtp"; } +}; + +class SdpAttrSSRC : public SdpItem { +public: + // a=ssrc:3245185839 cname:Cx4i/VTR51etgjT7 + // a=ssrc:3245185839 msid:cb373bff-0fea-4edb-bc39-e49bb8e8e3b9 0cf7e597-36a2-4480-9796-69bf0955eef5 + // a=ssrc:3245185839 mslabel:cb373bff-0fea-4edb-bc39-e49bb8e8e3b9 + // a=ssrc:3245185839 label:0cf7e597-36a2-4480-9796-69bf0955eef5 + // a=ssrc: + // a=ssrc: : + // cname 是必须的,msid/mslabel/label 这三个属性都是 WebRTC 自创的,或者说 Google 自创的,可以参考 https://tools.ietf.org/html/draft-ietf-mmusic-msid-17, [AUTO-TRANSLATED:d8cb1baf] + // cname is required, msid/mslabel/label these three attributes are all created by WebRTC, or Google created, you can refer to https://tools.ietf.org/html/draft-ietf-mmusic-msid-17, + // 理解它们三者的关系需要先了解三个概念:RTP stream / MediaStreamTrack / MediaStream : [AUTO-TRANSLATED:7d385cf5] + // understanding the relationship between the three requires understanding three concepts: RTP stream / MediaStreamTrack / MediaStream: + // 一个 a=ssrc 代表一个 RTP stream ; [AUTO-TRANSLATED:ee1ecc6f] + // One a=ssrc represents one RTP stream; + // 一个 MediaStreamTrack 通常包含一个或多个 RTP stream,例如一个视频 MediaStreamTrack 中通常包含两个 RTP stream,一个用于常规传输,一个用于 nack 重传; [AUTO-TRANSLATED:e8ddf0fd] + // A MediaStreamTrack usually contains one or more RTP streams, for example, a video MediaStreamTrack usually contains two RTP streams, one for regular transmission and one for nack retransmission; + // 一个 MediaStream 通常包含一个或多个 MediaStreamTrack ,例如 simulcast 场景下,一个 MediaStream 通常会包含三个不同编码质量的 MediaStreamTrack ; [AUTO-TRANSLATED:31318d43] + // A MediaStream usually contains one or more MediaStreamTrack, for example, in a simulcast scenario, a MediaStream usually contains three MediaStreamTrack of different encoding quality; + // 这种标记方式并不被 Firefox 认可,在 Firefox 生成的 SDP 中一个 a=ssrc 通常只有一行,例如: [AUTO-TRANSLATED:8c2c424c] + // This marking method is not recognized by Firefox, in the SDP generated by Firefox, one a=ssrc usually has only one line, for example: + // a=ssrc:3245185839 cname:Cx4i/VTR51etgjT7 + + uint32_t ssrc; + std::string attribute; + std::string attribute_value; + void parse(const std::string &str) override; + std::string toString() const override; + const char *getKey() const override { return "ssrc"; } +}; + +class SdpAttrSSRCGroup : public SdpItem { +public: + // a=ssrc-group 定义参考 RFC 5576(https://tools.ietf.org/html/rfc5576) ,用于描述多个 ssrc 之间的关联,常见的有两种: [AUTO-TRANSLATED:a87cbcc6] + // a=ssrc-group definition refers to RFC 5576(https://tools.ietf.org/html/rfc5576), used to describe the association between multiple ssrcs, there are two common types: + // a=ssrc-group:FID 2430709021 3715850271 + // FID (Flow Identification) 最初用在 FEC 的关联中,WebRTC 中通常用于关联一组常规 RTP stream 和 重传 RTP stream 。 [AUTO-TRANSLATED:f2c0fcbb] + // FID (Flow Identification) was originally used in FEC association, and in WebRTC it is usually used to associate a group of regular RTP streams and retransmission RTP streams. + // a=ssrc-group:SIM 360918977 360918978 360918980 + // 在 Chrome 独有的 SDP munging 风格的 simulcast 中使用,将三组编码质量由低到高的 MediaStreamTrack 关联在一起。 [AUTO-TRANSLATED:61bf7596] + // Used in Chrome's unique SDP munging style simulcast, associating three groups of MediaStreamTrack from low to high encoding quality. + std::string type { "FID" }; + std::vector ssrcs; + + bool isFID() const { return type == "FID"; } + bool isSIM() const { return type == "SIM"; } + void parse(const std::string &str) override; + std::string toString() const override; + const char *getKey() const override { return "ssrc-group"; } +}; + +class SdpAttrSctpMap : public SdpItem { +public: + // https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-05 + // a=sctpmap:5000 webrtc-datachannel 1024 + // a=sctpmap: sctpmap-number media-subtypes [streams] + uint16_t port = 0; + std::string subtypes; + uint32_t streams = 0; + void parse(const std::string &str) override; + std::string toString() const override; + const char *getKey() const override { return "sctpmap"; } + bool empty() const { return port == 0 && subtypes.empty() && streams == 0; } +}; + +class SdpAttrCandidate : public SdpItem { +public: + using Ptr = std::shared_ptr; + // https://tools.ietf.org/html/rfc5245 + // 15.1. "candidate" Attribute + // a=candidate:4 1 udp 2 192.168.1.7 58107 typ host + // a=candidate:
typ + std::string foundation; + // 传输媒体的类型,1代表RTP;2代表 RTCP。 [AUTO-TRANSLATED:9ec924a6] + // The type of media to be transmitted, 1 represents RTP; 2 represents RTCP. + uint32_t component; + std::string transport { "udp" }; + uint32_t priority; + std::string address; + uint16_t port; + std::string type; + std::vector> arr; + + void parse(const std::string &str) override; + std::string toString() const override; + const char *getKey() const override { return "candidate"; } +}; + +class SdpAttrMsid : public SdpItem { +public: + const char *getKey() const override { return "msid"; } +}; + +class SdpAttrExtmapAllowMixed : public SdpItem { +public: + const char *getKey() const override { return "extmap-allow-mixed"; } +}; + +class SdpAttrSimulcast : public SdpItem { +public: + // https://www.meetecho.com/blog/simulcast-janus-ssrc/ + // https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-14 + const char *getKey() const override { return "simulcast"; } + void parse(const std::string &str) override; + std::string toString() const override; + bool empty() const { return rids.empty(); } + std::string direction; + std::vector rids; +}; + +class SdpAttrRid : public SdpItem { +public: + void parse(const std::string &str) override; + std::string toString() const override; + const char *getKey() const override { return "rid"; } + std::string direction; + std::string rid; +}; + +class RtcSdpBase { +public: + void addItem(SdpItem::Ptr item) { items.push_back(std::move(item)); } + void addAttr(SdpItem::Ptr attr) { + auto item = std::make_shared(); + item->detail = std::move(attr); + items.push_back(std::move(item)); + } + + virtual ~RtcSdpBase() = default; + virtual std::string toString() const; + void toRtsp(); + + RtpDirection getDirection() const; + + template + cls getItemClass(char key, const char *attr_key = nullptr) const { + auto item = std::dynamic_pointer_cast(getItem(key, attr_key)); + if (!item) { + return cls(); + } + return *item; + } + + std::string getStringItem(char key, const char *attr_key = nullptr) const { + auto item = getItem(key, attr_key); + if (!item) { + return ""; + } + return item->toString(); + } + + SdpItem::Ptr getItem(char key, const char *attr_key = nullptr) const; + + template + std::vector getAllItem(char key_c, const char *attr_key = nullptr) const { + std::vector ret; + std::string key(1, key_c); + for (auto item : items) { + if (strcasecmp(item->getKey(), key.data()) == 0) { + if (!attr_key) { + auto c = std::dynamic_pointer_cast(item); + if (c) { + ret.emplace_back(*c); + } + } else { + auto attr = std::dynamic_pointer_cast(item); + if (attr && !strcasecmp(attr->detail->getKey(), attr_key)) { + auto c = std::dynamic_pointer_cast(attr->detail); + if (c) { + ret.emplace_back(*c); + } + } + } + } + } + return ret; + } + +private: + std::vector items; +}; + +class RtcSessionSdp : public RtcSdpBase { +public: + using Ptr = std::shared_ptr; + int getVersion() const; + SdpOrigin getOrigin() const; + std::string getSessionName() const; + std::string getSessionInfo() const; + SdpTime getSessionTime() const; + SdpConnection getConnection() const; + SdpBandwidth getBandwidth() const; + + std::string getUri() const; + std::string getEmail() const; + std::string getPhone() const; + std::string getTimeZone() const; + std::string getEncryptKey() const; + std::string getRepeatTimes() const; + + std::vector medias; + void parse(const std::string &str); + std::string toString() const override; +}; + +////////////////////////////////////////////////////////////////// + +// ssrc相关信息 [AUTO-TRANSLATED:954c641d] +// ssrc related information +class RtcSSRC { +public: + uint32_t ssrc { 0 }; + uint32_t rtx_ssrc { 0 }; + std::string cname; + std::string msid; + std::string mslabel; + std::string label; + + bool empty() const { return ssrc == 0 && cname.empty(); } +}; + +// rtc传输编码方案 [AUTO-TRANSLATED:8b911508] +// rtc transmission encoding scheme +class RtcCodecPlan { +public: + using Ptr = std::shared_ptr; + uint8_t pt; + std::string codec; + uint32_t sample_rate; + // 音频时有效 [AUTO-TRANSLATED:5b230fc8] + // Valid for audio + uint32_t channel = 0; + // rtcp反馈 [AUTO-TRANSLATED:580378bd] + // RTCP feedback + std::set rtcp_fb; + std::map fmtp; + + std::string getFmtp(const char *key) const; +}; + +// rtc 媒体描述 [AUTO-TRANSLATED:b1711a11] +// RTC media description +class RtcMedia { +public: + TrackType type { TrackType::TrackInvalid }; + std::string mid; + uint16_t port { 0 }; + SdpConnection addr; + SdpBandwidth bandwidth; + std::string proto; + RtpDirection direction { RtpDirection::invalid }; + std::vector plan; + + //////// rtp //////// + std::vector rtp_rtx_ssrc; + + //////// simulcast //////// + std::vector rtp_ssrc_sim; + std::vector rtp_rids; + + //////// rtcp //////// + bool rtcp_mux { false }; + bool rtcp_rsize { false }; + SdpAttrRtcp rtcp_addr; + + //////// ice //////// + bool ice_trickle { false }; + bool ice_lite { false }; + bool ice_renomination { false }; + std::string ice_ufrag; + std::string ice_pwd; + std::vector candidate; + + //////// dtls //////// + DtlsRole role { DtlsRole::invalid }; + SdpAttrFingerprint fingerprint; + + //////// extmap //////// + std::vector extmap; + + //////// sctp //////////// + SdpAttrSctpMap sctpmap; + uint32_t sctp_port { 0 }; + + void checkValid() const; + const RtcCodecPlan *getPlan(uint8_t pt) const; + const RtcCodecPlan *getPlan(const char *codec) const; + const RtcCodecPlan *getRelatedRtxPlan(uint8_t pt) const; + uint32_t getRtpSSRC() const; + uint32_t getRtxSSRC() const; + bool supportSimulcast() const; +}; + +class RtcSession { +public: + using Ptr = std::shared_ptr; + + uint32_t version; + SdpOrigin origin; + std::string session_name; + std::string session_info; + SdpTime time; + SdpConnection connection; + SdpAttrMsidSemantic msid_semantic; + std::vector media; + SdpAttrGroup group; + + void loadFrom(const std::string &sdp); + void checkValid() const; + std::string toString() const; + std::string toRtspSdp() const; + const RtcMedia *getMedia(TrackType type) const; + bool supportRtcpFb(const std::string &name, TrackType type = TrackType::TrackVideo) const; + bool supportSimulcast() const; + bool isOnlyDatachannel() const; + +private: + RtcSessionSdp::Ptr toRtcSessionSdp() const; +}; + +class RtcConfigure { +public: + using Ptr = std::shared_ptr; + class RtcTrackConfigure { + public: + bool rtcp_mux; + bool rtcp_rsize; + bool group_bundle; + bool support_rtx; + bool support_red; + bool support_ulpfec; + bool ice_lite; + bool ice_trickle; + bool ice_renomination; + std::string ice_ufrag; + std::string ice_pwd; + + RtpDirection direction { RtpDirection::invalid }; + SdpAttrFingerprint fingerprint; + + std::set rtcp_fb; + std::map extmap; + std::vector preferred_codec; + std::vector candidate; + + void setDefaultSetting(TrackType type); + void enableTWCC(bool enable = true); + void enableREMB(bool enable = true); + }; + + RtcTrackConfigure video; + RtcTrackConfigure audio; + RtcTrackConfigure application; + + void setDefaultSetting(std::string ice_ufrag, std::string ice_pwd, RtpDirection direction, const SdpAttrFingerprint &fingerprint); + void addCandidate(const SdpAttrCandidate &candidate, TrackType type = TrackInvalid); + + std::shared_ptr createOffer() const; + std::shared_ptr createAnswer(const RtcSession &offer) const; + + void setPlayRtspInfo(const std::string &sdp); + + void enableTWCC(bool enable = true, TrackType type = TrackInvalid); + void enableREMB(bool enable = true, TrackType type = TrackInvalid); + +private: + void createMediaOffer(const std::shared_ptr &ret) const; + void createMediaOfferEach(const std::shared_ptr &ret, TrackType type, int index) const; + void matchMedia(const std::shared_ptr &ret, const RtcMedia &media) const; + bool onCheckCodecProfile(const RtcCodecPlan &plan, CodecId codec) const; + void onSelectPlan(RtcCodecPlan &plan, CodecId codec) const; + +private: + RtcCodecPlan::Ptr _rtsp_video_plan; + RtcCodecPlan::Ptr _rtsp_audio_plan; +}; + +class SdpConst { +public: + static std::string const kTWCCRtcpFb; + static std::string const kRembRtcpFb; + +private: + SdpConst() = delete; + ~SdpConst() = delete; +}; + +} // namespace mediakit + +#endif // ZLMEDIAKIT_SDP_H diff --git a/webrtc/SrtpSession.hpp b/webrtc/SrtpSession.hpp index 5e413aea..eed09473 100644 --- a/webrtc/SrtpSession.hpp +++ b/webrtc/SrtpSession.hpp @@ -1,64 +1,65 @@ -/** -ISC License - -Copyright © 2015, Iñaki Baz Castillo - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef MS_RTC_SRTP_SESSION_HPP -#define MS_RTC_SRTP_SESSION_HPP - -#include "Utils.hpp" - -#include - -typedef struct srtp_ctx_t_ *srtp_t; - -namespace RTC { - -class DepLibSRTP; - -class SrtpSession { -public: - enum class CryptoSuite { - NONE = 0, - AES_CM_128_HMAC_SHA1_80 = 1, - AES_CM_128_HMAC_SHA1_32, - AEAD_AES_256_GCM, - AEAD_AES_128_GCM - }; - -public: - enum class Type { INBOUND = 1, OUTBOUND }; - -public: - SrtpSession(Type type, CryptoSuite cryptoSuite, uint8_t *key, size_t keyLen); - ~SrtpSession(); - -public: - bool EncryptRtp(uint8_t *data, int *len); - bool DecryptSrtp(uint8_t *data, int *len); - bool EncryptRtcp(uint8_t *data, int *len); - bool DecryptSrtcp(uint8_t *data, int *len); - void RemoveStream(uint32_t ssrc); - -private: - // Allocated by this. - srtp_t session { nullptr }; - std::shared_ptr _env; -}; - -} // namespace RTC - -#endif +/** +ISC License + +Copyright © 2015, Iñaki Baz Castillo + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef MS_RTC_SRTP_SESSION_HPP +#define MS_RTC_SRTP_SESSION_HPP + +#include "Util/Byte.hpp" + +#include + +typedef struct srtp_ctx_t_ *srtp_t; + +namespace RTC { + +class DepLibSRTP; + +class SrtpSession { +public: + using Ptr = std::shared_ptr; + enum class CryptoSuite { + NONE = 0, + AES_CM_128_HMAC_SHA1_80 = 1, + AES_CM_128_HMAC_SHA1_32, + AEAD_AES_256_GCM, + AEAD_AES_128_GCM + }; + +public: + enum class Type { INBOUND = 1, OUTBOUND }; + +public: + SrtpSession(Type type, CryptoSuite cryptoSuite, uint8_t *key, size_t keyLen); + ~SrtpSession(); + +public: + bool EncryptRtp(uint8_t *data, int *len); + bool DecryptSrtp(uint8_t *data, int *len); + bool EncryptRtcp(uint8_t *data, int *len); + bool DecryptSrtcp(uint8_t *data, int *len); + void RemoveStream(uint32_t ssrc); + +private: + // Allocated by this. + srtp_t session { nullptr }; + std::shared_ptr _env; +}; + +} // namespace RTC + +#endif diff --git a/webrtc/StunPacket.cpp b/webrtc/StunPacket.cpp index df168759..506b74b3 100644 --- a/webrtc/StunPacket.cpp +++ b/webrtc/StunPacket.cpp @@ -1,881 +1,906 @@ -/** -ISC License - -Copyright © 2015, Iñaki Baz Castillo - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#define MS_CLASS "RTC::StunPacket" -// #define MS_LOG_DEV_LEVEL 3 +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. +*/ #include "StunPacket.hpp" #include // std::snprintf() #include // std::memcmp(), std::memcpy() #include +#include "Util/logger.h" +#include "Common/macros.h" + +using namespace std; +using namespace toolkit; namespace RTC { - static const uint32_t crc32Table[] = - { - 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, - 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, - 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, - 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, - 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, - 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, - 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, - 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, - 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, - 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, - 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, - 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, - 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, - 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, - 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, - 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, - 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, - 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, - 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, - 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, - 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, - 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, - 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, - 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, - 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, - 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, - 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, - 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, - 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, - 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, - 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, - 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d - }; - inline uint32_t GetCRC32(const uint8_t *data, size_t size) { - uint32_t crc{0xFFFFFFFF}; - const uint8_t *p = data; +static const uint32_t crc32Table[] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; - while (size--) { - crc = crc32Table[(crc ^ *p++) & 0xFF] ^ (crc >> 8); - } +inline uint32_t getCRC32(const uint8_t *data, size_t size) { + uint32_t crc { 0xFFFFFFFF }; + const uint8_t *p = data; - return crc ^ ~0U; + while (size--) { + crc = crc32Table[(crc ^ *p++) & 0xFF] ^ (crc >> 8); } - static std::string openssl_HMACsha1(const void *key, size_t key_len, const void *data, size_t data_len){ - std::string str; - str.resize(20); - unsigned int out_len; + return crc ^ ~0U; +} + +static std::string openssl_HMACsha1(const void *key, size_t key_len, const void *data, size_t data_len) { + std::string str; + str.resize(20); + unsigned int out_len; #if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER > 0x10100000L) - //openssl 1.1.0新增api,老版本api作废 - HMAC_CTX *ctx = HMAC_CTX_new(); - HMAC_CTX_reset(ctx); - HMAC_Init_ex(ctx, key, (int)key_len, EVP_sha1(), NULL); - HMAC_Update(ctx, (unsigned char*)data, data_len); - HMAC_Final(ctx, (unsigned char *)str.data(), &out_len); - HMAC_CTX_reset(ctx); - HMAC_CTX_free(ctx); + // openssl 1.1.0新增api,老版本api作废 + HMAC_CTX *ctx = HMAC_CTX_new(); + HMAC_CTX_reset(ctx); + HMAC_Init_ex(ctx, key, (int)key_len, EVP_sha1(), NULL); + HMAC_Update(ctx, (unsigned char *)data, data_len); + HMAC_Final(ctx, (unsigned char *)str.data(), &out_len); + HMAC_CTX_reset(ctx); + HMAC_CTX_free(ctx); #else - HMAC_CTX ctx; - HMAC_CTX_init(&ctx); - HMAC_Init_ex(&ctx, key, key_len, EVP_sha1(), NULL); - HMAC_Update(&ctx, (unsigned char*)data, data_len); - HMAC_Final(&ctx, (unsigned char *)str.data(), &out_len); - HMAC_CTX_cleanup(&ctx); -#endif //defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER > 0x10100000L) - return str; + HMAC_CTX ctx; + HMAC_CTX_init(&ctx); + HMAC_Init_ex(&ctx, key, key_len, EVP_sha1(), NULL); + HMAC_Update(&ctx, (unsigned char *)data, data_len); + HMAC_Final(&ctx, (unsigned char *)str.data(), &out_len); + HMAC_CTX_cleanup(&ctx); +#endif // defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER > 0x10100000L) + return str; +} + +static std::string openssl_MD5(const void *data, size_t data_len) { + std::string str; + str.resize(16); + unsigned int out_len; +#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER > 0x10100000L) + // openssl 1.1.0新增api,老版本api作废 + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_md5(), NULL); + EVP_DigestUpdate(ctx, data, data_len); + unsigned int md_len; + EVP_DigestFinal_ex(ctx, (unsigned char *)str.data(), &md_len); + EVP_MD_CTX_free(ctx); +#else + MD5_CTX ctx; + MD5_Init(&ctx); + MD5_Update(&ctx, data, data_len); + MD5_Final((unsigned char *)str.data(), &ctx); +#endif // defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER > 0x10100000L) + return str; +} + +/////////////////////////////////////////////////// +// StunAttribute + +bool StunAttribute::isComprehensionRequired(const uint8_t *data, size_t len) { + return ((data[0] & 0xC0) == 0x00); +} + +const uint8_t * StunAttribute::loadHeader(const uint8_t *buf) { + _type = (Type)Byte::Get2Bytes(buf, 0); + _length = Byte::Get2Bytes(buf, 2); + return buf + ATTR_HEADER_SIZE; +} + +uint8_t * StunAttribute::storeHeader() { + _data = toolkit::BufferRaw::create(ATTR_HEADER_SIZE + Byte::PadTo4Bytes(_length)); + _data->setSize(_data->getCapacity()); + memset(_data->data(), 0, _data->size()); + uint8_t *ptr = (uint8_t *)_data->data(); + Byte::Set2Bytes(ptr, 0, (uint16_t)_type); + Byte::Set2Bytes(ptr, 2, _length); + return ptr + ATTR_HEADER_SIZE; +} + +bool StunAttrMappedAddress::loadFromData(const uint8_t *buf, size_t len) { + StunAttribute::loadHeader(buf); + return true; +} + +bool StunAttrMappedAddress::storeToData() { + return true; +} + +bool StunAttrUserName::loadFromData(const uint8_t *buf, size_t len) { + auto p = StunAttribute::loadHeader(buf); + _username.assign((const char *)p, _length); + return true; +} + +bool StunAttrUserName::storeToData() { + _length = _username.length(); + auto p = StunAttribute::storeHeader(); + memcpy(p, _username.data(), _username.length()); + return true; +} + +bool StunAttrMessageIntegrity::loadFromData(const uint8_t *buf, size_t len) { + auto p = StunAttribute::loadHeader(buf); + _hmac.assign((const char *)p, _length); + return true; +} + +bool StunAttrMessageIntegrity::storeToData() { + _length = _hmac.size(); + auto p = StunAttribute::storeHeader(); + memcpy(p, _hmac.data(), _hmac.size()); + return true; +} + +bool StunAttrErrorCode::loadFromData(const uint8_t *buf, size_t len) { + auto p = StunAttribute::loadHeader(buf); + _error_code = (Code)(p[2] * 100 + p[3]); + return true; +} + +bool StunAttrErrorCode::storeToData() { + _length = 4; + auto p = StunAttribute::storeHeader(); + Byte::Set2Bytes(p, 0, 0); // reserved + uint16_t code = (uint16_t)_error_code; + p[2] = code / 100; + p[3] = code % 100; + return true; +} + +bool StunAttrChannelNumber::loadFromData(const uint8_t *buf, size_t len) { + auto p = StunAttribute::loadHeader(buf); + _channel_number = Byte::Get2Bytes(p, 0); + return true; +} + +bool StunAttrChannelNumber::storeToData() { + _length = 4; + auto p = StunAttribute::storeHeader(); + Byte::Set2Bytes(p, 0, _channel_number); + Byte::Set2Bytes(p, 2, 0); // RFFU + return true; +} + +bool StunAttrLifeTime::loadFromData(const uint8_t *buf, size_t len) { + auto p = StunAttribute::loadHeader(buf); + _lifetime = Byte::Get4Bytes(p, 0); + return true; +} + +bool StunAttrLifeTime::storeToData() { + _length = 4; + auto p = StunAttribute::storeHeader(); + Byte::Set4Bytes(p, 0, _lifetime); + return true; +} + +bool StunAttrXorPeerAddress::loadFromData(const uint8_t *buf, size_t len) { + auto attrValue = StunAttribute::loadHeader(buf); + memset(&_addr, 0, sizeof(_addr)); + uint8_t port[2], addr[16]; + port[0] = attrValue[2] ^ StunPacket::_magicCookie[0]; + port[1] = attrValue[3] ^ StunPacket::_magicCookie[1]; + addr[0] = attrValue[4] ^ StunPacket::_magicCookie[0]; + addr[1] = attrValue[5] ^ StunPacket::_magicCookie[1]; + addr[2] = attrValue[6] ^ StunPacket::_magicCookie[2]; + addr[3] = attrValue[7] ^ StunPacket::_magicCookie[3]; + auto protocol = attrValue[1]; + if (protocol == 0x01) { + _addr.ss_family = AF_INET; + struct sockaddr_in *ipv4 = (struct sockaddr_in *)&_addr; + ipv4->sin_port = ntohs(Byte::Get2Bytes(port, 0)); + std::memcpy((void *)&(reinterpret_cast(&_addr))->sin_addr.s_addr, addr, 4); + } else { + _addr.ss_family = AF_INET6; + for (int i=0; i < 12; ++i) { + addr[i + 4] = attrValue[i + 8] ^ _transaction_id[i]; + } + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)&_addr; + ipv6->sin6_port = ntohs(Byte::Get2Bytes(port, 0)); + std::memcpy((void *)&(reinterpret_cast(&_addr))->sin6_addr.s6_addr, addr, 16); } - /* Class variables. */ + return true; +} - const uint8_t StunPacket::magicCookie[] = { 0x21, 0x12, 0xA4, 0x42 }; +bool StunAttrXorPeerAddress::storeToData() { + _length = (_addr.ss_family == AF_INET) ? 8 : 20; + auto attrValue = StunAttribute::storeHeader(); + // Set first byte to 0. + attrValue[0] = 0; + if (_addr.ss_family == AF_INET) { + // Set inet family. + attrValue[1] = 1; + // Set port and XOR it. + std::memcpy(attrValue + 2, &(reinterpret_cast(&_addr))->sin_port, 2); + attrValue[2] ^= StunPacket::_magicCookie[0]; + attrValue[3] ^= StunPacket::_magicCookie[1]; - /* Class methods. */ + // Set address and XOR it. + std::memcpy(attrValue + 4, &(reinterpret_cast(&_addr))->sin_addr.s_addr, 4); + attrValue[4] ^= StunPacket::_magicCookie[0]; + attrValue[5] ^= StunPacket::_magicCookie[1]; + attrValue[6] ^= StunPacket::_magicCookie[2]; + attrValue[7] ^= StunPacket::_magicCookie[3]; + } else if (_addr.ss_family == AF_INET6) { + // Set inet family. + attrValue[1] = 2; - StunPacket* StunPacket::Parse(const uint8_t* data, size_t len) - { - MS_TRACE(); - - if (!StunPacket::IsStun(data, len)) - return nullptr; - - /* - The message type field is decomposed further into the following - structure: - - 0 1 - 2 3 4 5 6 7 8 9 0 1 2 3 4 5 - +--+--+-+-+-+-+-+-+-+-+-+-+-+-+ - |M |M |M|M|M|C|M|M|M|C|M|M|M|M| - |11|10|9|8|7|1|6|5|4|0|3|2|1|0| - +--+--+-+-+-+-+-+-+-+-+-+-+-+-+ - - Figure 3: Format of STUN Message Type Field - - Here the bits in the message type field are shown as most significant - (M11) through least significant (M0). M11 through M0 represent a 12- - bit encoding of the method. C1 and C0 represent a 2-bit encoding of - the class. - */ - - // Get type field. - uint16_t msgType = Utils::Byte::Get2Bytes(data, 0); - - // Get length field. - uint16_t msgLength = Utils::Byte::Get2Bytes(data, 2); - - // length field must be total size minus header's 20 bytes, and must be multiple of 4 Bytes. - if ((static_cast(msgLength) != len - 20) || ((msgLength & 0x03) != 0)) - { - MS_WARN_TAG( - ice, - "length field + 20 does not match total size (or it is not multiple of 4 bytes), " - "packet discarded"); - - return nullptr; + std::memcpy(attrValue + 2, &(reinterpret_cast(&_addr))->sin6_port, 2); + attrValue[2] ^= StunPacket::_magicCookie[0]; + attrValue[3] ^= StunPacket::_magicCookie[1]; + // Set address and XOR it. + std::memcpy(attrValue + 4, &(reinterpret_cast(&_addr))->sin6_addr.s6_addr, 16); + attrValue[4] ^= StunPacket::_magicCookie[0]; + attrValue[5] ^= StunPacket::_magicCookie[1]; + attrValue[6] ^= StunPacket::_magicCookie[2]; + attrValue[7] ^= StunPacket::_magicCookie[3]; + for (int i=0; i < 12; ++i) { + attrValue[8 + i] ^= _transaction_id[i]; } - - // Get STUN method. - uint16_t msgMethod = (msgType & 0x000f) | ((msgType & 0x00e0) >> 1) | ((msgType & 0x3E00) >> 2); - - // Get STUN class. - uint16_t msgClass = ((data[0] & 0x01) << 1) | ((data[1] & 0x10) >> 4); - - // Create a new StunPacket (data + 8 points to the received TransactionID field). - auto* packet = new StunPacket( - static_cast(msgClass), static_cast(msgMethod), data + 8, data, len); - - /* - STUN Attributes - - After the STUN header are zero or more attributes. Each attribute - MUST be TLV encoded, with a 16-bit type, 16-bit length, and value. - Each STUN attribute MUST end on a 32-bit boundary. As mentioned - above, all fields in an attribute are transmitted most significant - bit first. - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Type | Length | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Value (variable) .... - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ - - // Start looking for attributes after STUN header (Byte #20). - size_t pos{ 20 }; - // Flags (positions) for special MESSAGE-INTEGRITY and FINGERPRINT attributes. - bool hasMessageIntegrity{ false }; - bool hasFingerprint{ false }; - size_t fingerprintAttrPos; // Will point to the beginning of the attribute. - uint32_t fingerprint; // Holds the value of the FINGERPRINT attribute. - - // Ensure there are at least 4 remaining bytes (attribute with 0 length). - while (pos + 4 <= len) - { - // Get the attribute type. - auto attrType = static_cast(Utils::Byte::Get2Bytes(data, pos)); - - // Get the attribute length. - uint16_t attrLength = Utils::Byte::Get2Bytes(data, pos + 2); - - // Ensure the attribute length is not greater than the remaining size. - if ((pos + 4 + attrLength) > len) - { - MS_WARN_TAG(ice, "the attribute length exceeds the remaining size, packet discarded"); - - delete packet; - return nullptr; - } - - // FINGERPRINT must be the last attribute. - if (hasFingerprint) - { - MS_WARN_TAG(ice, "attribute after FINGERPRINT is not allowed, packet discarded"); - - delete packet; - return nullptr; - } - - // After a MESSAGE-INTEGRITY attribute just FINGERPRINT is allowed. - if (hasMessageIntegrity && attrType != Attribute::FINGERPRINT) - { - MS_WARN_TAG( - ice, - "attribute after MESSAGE-INTEGRITY other than FINGERPRINT is not allowed, " - "packet discarded"); - - delete packet; - return nullptr; - } - - const uint8_t* attrValuePos = data + pos + 4; - - switch (attrType) - { - case Attribute::USERNAME: - { - packet->SetUsername( - reinterpret_cast(attrValuePos), static_cast(attrLength)); - - break; - } - - case Attribute::PRIORITY: - { - // Ensure attribute length is 4 bytes. - if (attrLength != 4) - { - MS_WARN_TAG(ice, "attribute PRIORITY must be 4 bytes length, packet discarded"); - - delete packet; - return nullptr; - } - - packet->SetPriority(Utils::Byte::Get4Bytes(attrValuePos, 0)); - - break; - } - - case Attribute::ICE_CONTROLLING: - { - // Ensure attribute length is 8 bytes. - if (attrLength != 8) - { - MS_WARN_TAG(ice, "attribute ICE-CONTROLLING must be 8 bytes length, packet discarded"); - - delete packet; - return nullptr; - } - - packet->SetIceControlling(Utils::Byte::Get8Bytes(attrValuePos, 0)); - - break; - } - - case Attribute::ICE_CONTROLLED: - { - // Ensure attribute length is 8 bytes. - if (attrLength != 8) - { - MS_WARN_TAG(ice, "attribute ICE-CONTROLLED must be 8 bytes length, packet discarded"); - - delete packet; - return nullptr; - } - - packet->SetIceControlled(Utils::Byte::Get8Bytes(attrValuePos, 0)); - - break; - } - - case Attribute::USE_CANDIDATE: - { - // Ensure attribute length is 0 bytes. - if (attrLength != 0) - { - MS_WARN_TAG(ice, "attribute USE-CANDIDATE must be 0 bytes length, packet discarded"); - - delete packet; - return nullptr; - } - - packet->SetUseCandidate(); - - break; - } - - case Attribute::MESSAGE_INTEGRITY: - { - // Ensure attribute length is 20 bytes. - if (attrLength != 20) - { - MS_WARN_TAG(ice, "attribute MESSAGE-INTEGRITY must be 20 bytes length, packet discarded"); - - delete packet; - return nullptr; - } - - hasMessageIntegrity = true; - packet->SetMessageIntegrity(attrValuePos); - - break; - } - - case Attribute::FINGERPRINT: - { - // Ensure attribute length is 4 bytes. - if (attrLength != 4) - { - MS_WARN_TAG(ice, "attribute FINGERPRINT must be 4 bytes length, packet discarded"); - - delete packet; - return nullptr; - } - - hasFingerprint = true; - fingerprintAttrPos = pos; - fingerprint = Utils::Byte::Get4Bytes(attrValuePos, 0); - packet->SetFingerprint(); - - break; - } - - case Attribute::ERROR_CODE: - { - // Ensure attribute length >= 4bytes. - if (attrLength < 4) - { - MS_WARN_TAG(ice, "attribute ERROR-CODE must be >= 4bytes length, packet discarded"); - - delete packet; - return nullptr; - } - - uint8_t errorClass = Utils::Byte::Get1Byte(attrValuePos, 2); - uint8_t errorNumber = Utils::Byte::Get1Byte(attrValuePos, 3); - auto errorCode = static_cast(errorClass * 100 + errorNumber); - - packet->SetErrorCode(errorCode); - - break; - } - - default:; - } - - // Set next attribute position. - pos = - static_cast(Utils::Byte::PadTo4Bytes(static_cast(pos + 4 + attrLength))); - } - - // Ensure current position matches the total length. - if (pos != len) - { - MS_WARN_TAG(ice, "computed packet size does not match total size, packet discarded"); - - delete packet; - return nullptr; - } - - // If it has FINGERPRINT attribute then verify it. - if (hasFingerprint) - { - // Compute the CRC32 of the received packet up to (but excluding) the - // FINGERPRINT attribute and XOR it with 0x5354554e. - uint32_t computedFingerprint = GetCRC32(data, fingerprintAttrPos) ^ 0x5354554e; - - // Compare with the FINGERPRINT value in the packet. - if (fingerprint != computedFingerprint) - { - MS_WARN_TAG( - ice, - "computed FINGERPRINT value does not match the value in the packet, " - "packet discarded"); - - delete packet; - return nullptr; - } - } - - return packet; } - /* Instance methods. */ + return true; +} - StunPacket::StunPacket( - Class klass, Method method, const uint8_t* transactionId, const uint8_t* data, size_t size) - : klass(klass), method(method), transactionId(transactionId), data(const_cast(data)), - size(size) - { - MS_TRACE(); +bool StunAttrData::loadFromData(const uint8_t *buf, size_t len) { + auto p = StunAttribute::loadHeader(buf); + setData((const char *)p, _length); + return true; +} + +bool StunAttrData::storeToData() { + _length = _data_content.size(); + auto p = StunAttribute::storeHeader(); + memcpy(p, _data_content.data(), _data_content.size()); + return true; +} + +bool StunAttrRealm::loadFromData(const uint8_t *buf, size_t len) { + auto p = StunAttribute::loadHeader(buf); + _realm.assign((const char *)p, _length); + return true; +} + +bool StunAttrRealm::storeToData() { + _length = _realm.size(); + auto p = StunAttribute::storeHeader(); + memcpy(p, _realm.data(), _realm.size()); + return true; +} + +bool StunAttrNonce::loadFromData(const uint8_t *buf, size_t len) { + auto p = StunAttribute::loadHeader(buf); + _nonce.assign((const char *)p, _length); + return true; +} + +bool StunAttrNonce::storeToData() { + _length = _nonce.size(); + auto p = StunAttribute::storeHeader(); + memcpy(p, _nonce.data(), _nonce.size()); + return true; +} + +bool StunAttrRequestedTransport::loadFromData(const uint8_t *buf, size_t len) { + auto p = StunAttribute::loadHeader(buf); + _protocol = (Protocol)p[0]; + return true; +} + +bool StunAttrRequestedTransport::storeToData() { + _length = 4; + auto p = StunAttribute::storeHeader(); + p[0] = (uint8_t)_protocol; + return true; +} + +bool StunAttrPriority::loadFromData(const uint8_t *buf, size_t len) { + auto p = StunAttribute::loadHeader(buf); + _priority = Byte::Get4Bytes(p, 0); + return true; +} + +bool StunAttrPriority::storeToData() { + _length = 4; + auto p = StunAttribute::storeHeader(); + Byte::Set4Bytes(p, 0, _priority); + return true; +} + +bool StunAttrUseCandidate::loadFromData(const uint8_t *buf, size_t len) { + StunAttribute::loadHeader(buf); + return true; +} + +bool StunAttrUseCandidate::storeToData() { + _length = 0; + StunAttribute::storeHeader(); + return true; +} + +bool StunAttrFingerprint::loadFromData(const uint8_t *buf, size_t len) { + auto p = StunAttribute::loadHeader(buf); + _fingerprint = Byte::Get4Bytes(p, 0); + return true; +} + +bool StunAttrFingerprint::storeToData() { + _length = 4; + auto p = StunAttribute::storeHeader(); + Byte::Set4Bytes(p, 0, _fingerprint); + return true; +} + +bool StunAttrIceControlled::loadFromData(const uint8_t *buf, size_t len) { + auto p = StunAttribute::loadHeader(buf); + _tiebreaker = Byte::Get8Bytes(p, 0); + return true; +} + +bool StunAttrIceControlled::storeToData() { + _length = 8; + auto p = StunAttribute::storeHeader(); + Byte::Set8Bytes(p, 0, _tiebreaker); + return true; +} + +bool StunAttrIceControlling::loadFromData(const uint8_t *buf, size_t len) { + auto p = StunAttribute::loadHeader(buf); + _tiebreaker = Byte::Get8Bytes(p, 0); + return true; +} + +bool StunAttrIceControlling::storeToData() { + _length = 8; + auto p = StunAttribute::storeHeader(); + Byte::Set8Bytes(p, 0, _tiebreaker); + return true; +} + +/////////////////////////////////////////////////// +// StunPacket + +const uint8_t StunPacket::_magicCookie[] = { 0x21, 0x12, 0xA4, 0x42 }; + +/* Class methods. */ +bool StunPacket::isStun(const uint8_t *data, size_t len) { + // reference https://www.rfc-editor.org/rfc/rfc8489.html#section-6.3 + return + // STUN headers are 20 bytes. + (len >= 20) && + // checks that the first two bits are 0 + ((data[0] & 0xC0) == 0) && + // that the Magic Cookie field has the correct value + (data[4] == StunPacket::_magicCookie[0]) && (data[5] == StunPacket::_magicCookie[1]) && (data[6] == StunPacket::_magicCookie[2]) + && (data[7] == StunPacket::_magicCookie[3]); +} + +/* + The message type field is decomposed further into the following + structure: + + 0 1 + 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + +--+--+-+-+-+-+-+-+-+-+-+-+-+-+ + |M |M |M|M|M|C|M|M|M|C|M|M|M|M| + |11|10|9|8|7|1|6|5|4|0|3|2|1|0| + +--+--+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 3: Format of STUN Message Type Field + + Here the bits in the message type field are shown as most significant + (M11) through least significant (M0). M11 through M0 represent a 12- + bit encoding of the method. C1 and C0 represent a 2-bit encoding of + the class. +*/ +StunPacket::Class StunPacket::getClass(const uint8_t *data, size_t len) { + return StunPacket::Class(((data[0] & 0x01) << 1) | ((data[1] & 0x10) >> 4)); +} + +StunPacket::Method StunPacket::getMethod(const uint8_t *data, size_t len) { + uint16_t msgType = Byte::Get2Bytes(data, 0); + return StunPacket::Method((msgType & 0x000F) | ((msgType & 0x00E0) >> 1) | ((msgType & 0x3E00) >> 2)); +} + +StunPacket::Ptr StunPacket::parse(const uint8_t *data, size_t len) { + // TraceL; + + if (!StunPacket::isStun(data, len)) { + return nullptr; } - StunPacket::~StunPacket() - { - MS_TRACE(); + // Get length field. + uint16_t msgLength = Byte::Get2Bytes(data, 2); + + // length field must be total size minus header's 20 bytes, and must be multiple of 4 Bytes. + if ((static_cast(msgLength) != len - 20) || ((msgLength & 0x03) != 0)) { + WarnL << "length field + 20 does not match total size (or it is not multiple of 4 bytes), packet discarded"; + return nullptr; } -#if 0 - void StunPacket::Dump() const - { - MS_TRACE(); + auto msgMethod = getMethod(data, len); + auto msgClass = getClass(data, len); - MS_DUMP(""); + auto packet = std::make_shared(msgClass, msgMethod, (const char *)data + 8); + packet->loadFromData(data, len); + return packet; +} - std::string klass; - switch (this->klass) - { - case Class::REQUEST: - klass = "Request"; - break; - case Class::INDICATION: - klass = "Indication"; - break; - case Class::SUCCESS_RESPONSE: - klass = "SuccessResponse"; - break; - case Class::ERROR_RESPONSE: - klass = "ErrorResponse"; - break; - } - if (this->method == Method::BINDING) - { - MS_DUMP(" Binding %s", klass.c_str()); - } - else - { - // This prints the unknown method number. Example: TURN Allocate => 0x003. - MS_DUMP(" %s with unknown method %#.3x", klass.c_str(), static_cast(this->method)); - } - MS_DUMP(" size: %zu bytes", this->size); +std::string StunPacket::mappingClassEnum2Str(Class klass) { + switch (klass) { + case StunPacket::Class::REQUEST: return "REQUEST"; + case StunPacket::Class::INDICATION: return "INDICATION"; + case StunPacket::Class::SUCCESS_RESPONSE: return "SUCCESS_RESPONSE"; + case StunPacket::Class::ERROR_RESPONSE: return "ERROR_RESPONSE"; + default: break; + } + return "invalid"; +} - thread_local static char transactionId[25]; +std::string StunPacket::mappingMethodEnum2Str(Method method) { + switch (method) { + case StunPacket::Method::BINDING: return "BINDING"; + case StunPacket::Method::ALLOCATE: return "ALLOCATE"; + case StunPacket::Method::REFRESH: return "REFRESH"; + case StunPacket::Method::SEND: return "SEND"; + case StunPacket::Method::DATA: return "DATA"; + case StunPacket::Method::CREATEPERMISSION: return "CREATEPERMISSION"; + case StunPacket::Method::CHANNELBIND: return "CHANNELBIND"; + default: break; + } + return "invalid"; +} - for (int i{ 0 }; i < 12; ++i) - { - // NOTE: n must be 3 because snprintf adds a \0 after printed chars. - std::snprintf(transactionId + (i * 2), 3, "%.2x", this->transactionId[i]); - } - MS_DUMP(" transactionId: %s", transactionId); - if (this->errorCode != 0u) - MS_DUMP(" errorCode: %" PRIu16, this->errorCode); - if (!this->username.empty()) - MS_DUMP(" username: %s", this->username.c_str()); - if (this->priority != 0u) - MS_DUMP(" priority: %" PRIu32, this->priority); - if (this->iceControlling != 0u) - MS_DUMP(" iceControlling: %" PRIu64, this->iceControlling); - if (this->iceControlled != 0u) - MS_DUMP(" iceControlled: %" PRIu64, this->iceControlled); - if (this->hasUseCandidate) - MS_DUMP(" useCandidate"); - if (this->xorMappedAddress != nullptr) - { - int family; - uint16_t port; - std::string ip; +StunPacket::StunPacket(Class klass, Method method, const char* transId) + : _klass(klass) + , _method(method) { + // TraceL; + if (transId) { + _transaction_id.assign(transId, 12); + } else { + refreshTransactionId(); + } +} - Utils::IP::GetAddressInfo(this->xorMappedAddress, family, ip, port); +StunPacket::~StunPacket() { + // TraceL; +} - MS_DUMP(" xorMappedAddress: %s : %" PRIu16, ip.c_str(), port); - } - if (this->messageIntegrity != nullptr) - { - static char messageIntegrity[41]; +std::string StunPacket::dumpString(bool transId) const { + std::string ret = "class=" + getClassStr() + ", method=" + getMethodStr(); + if (transId) { + ret += ", transaction=" + hexdump(_transaction_id.data(), _transaction_id.size()); + } + return ret; +} - for (int i{ 0 }; i < 20; ++i) - { - std::snprintf(messageIntegrity + (i * 2), 3, "%.2x", this->messageIntegrity[i]); +void StunPacket::addAttribute(StunAttribute::Ptr attr) { + _attribute_map.emplace(attr->type(), std::move(attr)); +} + +void StunPacket::removeAttribute(StunAttribute::Type type) { + _attribute_map.erase(type); +} + +bool StunPacket::hasAttribute(StunAttribute::Type type) const { + return _attribute_map.count(type) > 0; +} + +StunAttribute::Ptr StunPacket::getAttribute(StunAttribute::Type type) const { + auto it = _attribute_map.find(type); + if (it != _attribute_map.end()) { + return it->second; + } + return nullptr; +} + +std::string StunPacket::getUsername() const { + auto attr = getAttribute(); + return attr ? attr->getUsername() : ""; +} + +uint64_t StunPacket::getPriority() const { + auto attr = getAttribute(); + return attr ? attr->getPriority() : 0; +} + +StunAttrErrorCode::Code StunPacket::getErrorCode() const { + auto attr = getAttribute(); + return attr ? attr->getErrorCode() : StunAttrErrorCode::Code::Invalid; +} + +StunPacket::Authentication StunPacket::checkAuthentication(const std::string &ufrag, const std::string &password) const { + // TraceL; + auto attr_message_integrity = getAttribute(); + switch (_klass) { + case Class::REQUEST: { + if (!attr_message_integrity) { + return Authentication::UNAUTHORIZED; } - MS_DUMP(" messageIntegrity: %s", messageIntegrity); - } - if (this->hasFingerprint) - MS_DUMP(" has fingerprint"); - - MS_DUMP(""); - } -#endif - - StunPacket::Authentication StunPacket::CheckAuthentication( - const std::string& localUsername, const std::string& localPassword) - { - MS_TRACE(); - - switch (this->klass) - { - case Class::REQUEST: - case Class::INDICATION: - { - // Both USERNAME and MESSAGE-INTEGRITY must be present. - if (!this->messageIntegrity || this->username.empty()) - return Authentication::BAD_REQUEST; - - // Check that USERNAME attribute begins with our local username plus ":". - size_t localUsernameLen = localUsername.length(); - - if ( - this->username.length() <= localUsernameLen || this->username.at(localUsernameLen) != ':' || - (this->username.compare(0, localUsernameLen, localUsername) != 0)) - { + if (getMethod() == Method::ALLOCATE || getMethod() == Method::REFRESH || + getMethod() == Method::CREATEPERMISSION || getMethod() == Method::CHANNELBIND) { + // TURN认证:USERNAME应该等于ufrag + std::string username = getUsername(); + if (username != ufrag) { + TraceL << "TURN USERNAME validation failed, expected: " << ufrag << ", got: " << username; return Authentication::UNAUTHORIZED; } - - break; - } - // This method cannot check authentication in received responses (as we - // are ICE-Lite and don't generate requests). - case Class::SUCCESS_RESPONSE: - case Class::ERROR_RESPONSE: - { - MS_ERROR("cannot check authentication for a STUN response"); - - return Authentication::BAD_REQUEST; + } else { + // ICE认证:USERNAME格式为 local-ufrag:remote-ufrag(仅用于ICE BINDING请求) + std::string username = getUsername(); + if (!username.empty()) { + size_t localUsernameLen = ufrag.length(); + if (username.length() <= localUsernameLen || username.at(localUsernameLen) != ':' || + (username.compare(0, localUsernameLen, ufrag) != 0)) { + DebugL << "ICE USERNAME format validation failed, expected format: " << ufrag << ":remote-ufrag, got: " << username; + return Authentication::UNAUTHORIZED; + } + } } + break; } + // This method cannot check authentication in received responses (as we + // are ICE-Lite and don't generate requests). + case Class::INDICATION: return Authentication::OK; + case Class::SUCCESS_RESPONSE: + case Class::ERROR_RESPONSE: break; + } + if (attr_message_integrity) { // If there is FINGERPRINT it must be discarded for MESSAGE-INTEGRITY calculation, // so the header length field must be modified (and later restored). - if (this->hasFingerprint) + if (hasAttribute(StunAttribute::Type::FINGERPRINT)) { // Set the header length field: full size - header length (20) - FINGERPRINT length (8). - Utils::Byte::Set2Bytes(this->data, 2, static_cast(this->size - 20 - 8)); - - // Calculate the HMAC-SHA1 of the message according to MESSAGE-INTEGRITY rules. - auto computedMessageIntegrity = openssl_HMACsha1( - localPassword.data(),localPassword.size(), this->data, (this->messageIntegrity - 4) - this->data); - - Authentication result; - - // Compare the computed HMAC-SHA1 with the MESSAGE-INTEGRITY in the packet. - if (std::memcmp(this->messageIntegrity, computedMessageIntegrity.data(), computedMessageIntegrity.size()) == 0) - result = Authentication::OK; - else - result = Authentication::UNAUTHORIZED; - - // Restore the header length field. - if (this->hasFingerprint) - Utils::Byte::Set2Bytes(this->data, 2, static_cast(this->size - 20)); - - return result; - } - - StunPacket* StunPacket::CreateSuccessResponse() - { - MS_TRACE(); - - MS_ASSERT( - this->klass == Class::REQUEST, - "attempt to create a success response for a non Request STUN packet"); - - return new StunPacket(Class::SUCCESS_RESPONSE, this->method, this->transactionId, nullptr, 0); - } - - StunPacket* StunPacket::CreateErrorResponse(uint16_t errorCode) - { - MS_TRACE(); - - MS_ASSERT( - this->klass == Class::REQUEST, - "attempt to create an error response for a non Request STUN packet"); - - auto* response = - new StunPacket(Class::ERROR_RESPONSE, this->method, this->transactionId, nullptr, 0); - - response->SetErrorCode(errorCode); - - return response; - } - - void StunPacket::Authenticate(const std::string& password) - { - // Just for Request, Indication and SuccessResponse messages. - if (this->klass == Class::ERROR_RESPONSE) - { - MS_ERROR("cannot set password for ErrorResponse messages"); - - return; + Byte::Set2Bytes((uint8_t *)_data->data(), 2, _data->size() - HEADER_SIZE - 8); } - this->password = password; - } + auto attr_realm = getAttribute(); + auto attr_nonce = getAttribute(); - void StunPacket::Serialize(uint8_t* buffer) - { - MS_TRACE(); + std::string key = password; + if (attr_nonce && attr_realm) { + // 使用长期凭证机制 + // 根据RFC 5389/5766标准:key = MD5(username ":" realm ":" password) + auto realm = attr_realm->getRealm(); + std::string input = ufrag + ":" + std::string(realm.data(), realm.size()) + ":" + password; + key = openssl_MD5(input.data(), input.size()); - // Some useful variables. - uint16_t usernamePaddedLen{ 0 }; - uint16_t xorMappedAddressPaddedLen{ 0 }; - bool addXorMappedAddress = - ((this->xorMappedAddress != nullptr) && this->method == StunPacket::Method::BINDING && - this->klass == Class::SUCCESS_RESPONSE); - bool addErrorCode = ((this->errorCode != 0u) && this->klass == Class::ERROR_RESPONSE); - bool addMessageIntegrity = (this->klass != Class::ERROR_RESPONSE && !this->password.empty()); - bool addFingerprint{ true }; // Do always. - - // Update data pointer. - this->data = buffer; - - // First calculate the total required size for the entire packet. - this->size = 20; // Header. - - if (!this->username.empty()) - { - usernamePaddedLen = Utils::Byte::PadTo4Bytes(static_cast(this->username.length())); - this->size += 4 + usernamePaddedLen; + // DebugL << "ufrag: " << ufrag; + // DebugL << "realm: " << realm.data(); + // DebugL << "password: " << password; + // DebugL << "input: " << input; } - if (this->priority != 0u) - this->size += 4 + 4; + auto computedMessageIntegrity = openssl_HMACsha1(key.data(), key.size(), _data->data(), _message_integrity_data_len); - if (this->iceControlling != 0u) - this->size += 4 + 8; + // DebugL << "cal MessageIntegrity"; + // DebugL << "password: " << password; + // DebugL << "key: " << toolkit::hexdump(key.data(), key.size()); + // DebugL << "data: " << toolkit::hexdump(_data->data(), _message_integrity_data_len); + // DebugL << "_message_integrity_data_len: " << _message_integrity_data_len; + // DebugL << "_hmac: " << toolkit::hexdump(attr_message_integrity->_hmac.data(), attr_message_integrity->_hmac.size()); + // DebugL << "cal: " << toolkit::hexdump(computedMessageIntegrity.data(), computedMessageIntegrity.size()); - if (this->iceControlled != 0u) - this->size += 4 + 8; + if (attr_message_integrity->getHmac() != computedMessageIntegrity) { + return Authentication::UNAUTHORIZED; + } - if (this->hasUseCandidate) - this->size += 4; + if (hasAttribute(StunAttribute::Type::FINGERPRINT)) { + Byte::Set2Bytes((uint8_t*)_data->data(), 2, _data->size() - HEADER_SIZE); + } + } - if (addXorMappedAddress) - { - switch (this->xorMappedAddress->sa_family) - { - case AF_INET: - { - xorMappedAddressPaddedLen = 8; - this->size += 4 + 8; - - break; - } - - case AF_INET6: - { - xorMappedAddressPaddedLen = 20; - this->size += 4 + 20; - - break; - } - - default: - { - MS_ERROR("invalid inet family in XOR-MAPPED-ADDRESS attribute"); - - addXorMappedAddress = false; - } + // FINGERPRINT验证 + if (hasAttribute(StunAttribute::Type::FINGERPRINT)) { + auto attr_fingerprint = getAttribute(); + if (attr_fingerprint) { + // 计算FINGERPRINT:对除FINGERPRINT属性外的整个包计算CRC32 + uint32_t computedFingerprint = getCRC32((uint8_t*)_data->data(), _data->size() - 8) ^ 0x5354554e; + if (attr_fingerprint->getFingerprint() != computedFingerprint) { + // DebugL << "FINGERPRINT verification failed, expected: " << std::hex << computedFingerprint + // << ", got: " << attr_fingerprint->getFingerprint(); + return Authentication::UNAUTHORIZED; + } else { + // TraceL << "FINGERPRINT verification passed"; } } + } - if (addErrorCode) - this->size += 4 + 4; + return Authentication::OK; +} - if (addMessageIntegrity) - this->size += 4 + 20; +void StunPacket::serialize() { + //TraceL; - if (addFingerprint) - this->size += 4 + 4; + _data = BufferRaw::create(); + for (auto it : _attribute_map) { + it.second->storeToData(); + } - // Merge class and method fields into type. - uint16_t typeField = (static_cast(this->method) & 0x0f80) << 2; + auto attr_size = getAttrSize(); - typeField |= (static_cast(this->method) & 0x0070) << 1; - typeField |= (static_cast(this->method) & 0x000f); - typeField |= (static_cast(this->klass) & 0x02) << 7; - typeField |= (static_cast(this->klass) & 0x01) << 4; + if (getClass() == StunPacket::Class::ERROR_RESPONSE) { + setNeedFingerprint(false); + setNeedMessageIntegrity(false); + } - // Set type field. - Utils::Byte::Set2Bytes(buffer, 0, typeField); - // Set length field. - Utils::Byte::Set2Bytes(buffer, 2, static_cast(this->size) - 20); - // Set magic cookie. - std::memcpy(buffer + 4, StunPacket::magicCookie, 4); - // Set TransactionId field. - std::memcpy(buffer + 8, this->transactionId, 12); - // Update the transaction ID pointer. - this->transactionId = buffer + 8; - // Add atributes. - size_t pos{ 20 }; + if (getClass() == StunPacket::Class::INDICATION) { + setNeedMessageIntegrity(false); + } - // Add USERNAME. - if (usernamePaddedLen != 0u) - { - Utils::Byte::Set2Bytes(buffer, pos, static_cast(Attribute::USERNAME)); - Utils::Byte::Set2Bytes(buffer, pos + 2, static_cast(this->username.length())); - std::memcpy(buffer + pos + 4, this->username.c_str(), this->username.length()); - pos += 4 + usernamePaddedLen; - } + auto message_integrity_size = getNeedMessageIntegrity() ? 24 : 0; + auto fingerprint_size = getNeedFingerprint() ? 8 : 0; - // Add PRIORITY. - if (this->priority != 0u) - { - Utils::Byte::Set2Bytes(buffer, pos, static_cast(Attribute::PRIORITY)); - Utils::Byte::Set2Bytes(buffer, pos + 2, 4); - Utils::Byte::Set4Bytes(buffer, pos + 4, this->priority); - pos += 4 + 4; - } + auto packet_size = HEADER_SIZE + attr_size + message_integrity_size + fingerprint_size; + _data->setCapacity(packet_size); + _data->setSize(packet_size); - // Add ICE-CONTROLLING. - if (this->iceControlling != 0u) - { - Utils::Byte::Set2Bytes(buffer, pos, static_cast(Attribute::ICE_CONTROLLING)); - Utils::Byte::Set2Bytes(buffer, pos + 2, 8); - Utils::Byte::Set8Bytes(buffer, pos + 4, this->iceControlling); - pos += 4 + 8; - } + // Merge class and method fields into type. + uint16_t typeField = (static_cast(_method) & 0x0f80) << 2; - // Add ICE-CONTROLLED. - if (this->iceControlled != 0u) - { - Utils::Byte::Set2Bytes(buffer, pos, static_cast(Attribute::ICE_CONTROLLED)); - Utils::Byte::Set2Bytes(buffer, pos + 2, 8); - Utils::Byte::Set8Bytes(buffer, pos + 4, this->iceControlled); - pos += 4 + 8; - } + typeField |= (static_cast(_method) & 0x0070) << 1; + typeField |= (static_cast(_method) & 0x000f); + typeField |= (static_cast(_klass) & 0x02) << 7; + typeField |= (static_cast(_klass) & 0x01) << 4; - // Add USE-CANDIDATE. - if (this->hasUseCandidate) - { - Utils::Byte::Set2Bytes(buffer, pos, static_cast(Attribute::USE_CANDIDATE)); - Utils::Byte::Set2Bytes(buffer, pos + 2, 0); - pos += 4; - } + // Set type field. + Byte::Set2Bytes((unsigned char *)_data->data(), 0, typeField); + uint16_t initial_length = static_cast(attr_size + message_integrity_size); + Byte::Set2Bytes((unsigned char *)_data->data(), 2, initial_length); + // Set magic cookie. + std::memcpy(_data->data() + 4, StunPacket::_magicCookie, 4); + // Set TransactionId field. + std::memcpy(_data->data() + 8, _transaction_id.data(), 12); - // Add XOR-MAPPED-ADDRESS - if (addXorMappedAddress) - { - Utils::Byte::Set2Bytes(buffer, pos, static_cast(Attribute::XOR_MAPPED_ADDRESS)); - Utils::Byte::Set2Bytes(buffer, pos + 2, xorMappedAddressPaddedLen); - - uint8_t* attrValue = buffer + pos + 4; - - switch (this->xorMappedAddress->sa_family) - { - case AF_INET: - { - // Set first byte to 0. - attrValue[0] = 0; - // Set inet family. - attrValue[1] = 0x01; - // Set port and XOR it. - std::memcpy( - attrValue + 2, - &(reinterpret_cast(this->xorMappedAddress))->sin_port, - 2); - attrValue[2] ^= StunPacket::magicCookie[0]; - attrValue[3] ^= StunPacket::magicCookie[1]; - // Set address and XOR it. - std::memcpy( - attrValue + 4, - &(reinterpret_cast(this->xorMappedAddress))->sin_addr.s_addr, - 4); - attrValue[4] ^= StunPacket::magicCookie[0]; - attrValue[5] ^= StunPacket::magicCookie[1]; - attrValue[6] ^= StunPacket::magicCookie[2]; - attrValue[7] ^= StunPacket::magicCookie[3]; - - pos += 4 + 8; - - break; - } - - case AF_INET6: - { - // Set first byte to 0. - attrValue[0] = 0; - // Set inet family. - attrValue[1] = 0x02; - // Set port and XOR it. - std::memcpy( - attrValue + 2, - &(reinterpret_cast(this->xorMappedAddress))->sin6_port, - 2); - attrValue[2] ^= StunPacket::magicCookie[0]; - attrValue[3] ^= StunPacket::magicCookie[1]; - // Set address and XOR it. - std::memcpy( - attrValue + 4, - &(reinterpret_cast(this->xorMappedAddress))->sin6_addr.s6_addr, - 16); - attrValue[4] ^= StunPacket::magicCookie[0]; - attrValue[5] ^= StunPacket::magicCookie[1]; - attrValue[6] ^= StunPacket::magicCookie[2]; - attrValue[7] ^= StunPacket::magicCookie[3]; - attrValue[8] ^= this->transactionId[0]; - attrValue[9] ^= this->transactionId[1]; - attrValue[10] ^= this->transactionId[2]; - attrValue[11] ^= this->transactionId[3]; - attrValue[12] ^= this->transactionId[4]; - attrValue[13] ^= this->transactionId[5]; - attrValue[14] ^= this->transactionId[6]; - attrValue[15] ^= this->transactionId[7]; - attrValue[16] ^= this->transactionId[8]; - attrValue[17] ^= this->transactionId[9]; - attrValue[18] ^= this->transactionId[10]; - attrValue[19] ^= this->transactionId[11]; - - pos += 4 + 20; - - break; - } - } - } - - // Add ERROR-CODE. - if (addErrorCode) - { - Utils::Byte::Set2Bytes(buffer, pos, static_cast(Attribute::ERROR_CODE)); - Utils::Byte::Set2Bytes(buffer, pos + 2, 4); - - auto codeClass = static_cast(this->errorCode / 100); - uint8_t codeNumber = static_cast(this->errorCode) - (codeClass * 100); - - Utils::Byte::Set2Bytes(buffer, pos + 4, 0); - Utils::Byte::Set1Byte(buffer, pos + 6, codeClass); - Utils::Byte::Set1Byte(buffer, pos + 7, codeNumber); - pos += 4 + 4; + storeAttrMessage(); + if (message_integrity_size) { + auto ufrag = _peer_ufrag; + auto password = _peer_password; + if (getClass() == StunPacket::Class::SUCCESS_RESPONSE || + getClass() == StunPacket::Class::ERROR_RESPONSE) { + ufrag = _ufrag; + password = _password; } // Add MESSAGE-INTEGRITY. - if (addMessageIntegrity) - { - // Ignore FINGERPRINT. - if (addFingerprint) - Utils::Byte::Set2Bytes(buffer, 2, static_cast(this->size - 20 - 8)); + auto attr_nonce = getAttribute(); + auto attr_realm = getAttribute(); + // FIXME: need use SASLprep(password) replace password + // 根据RFC 5766标准:key = MD5(username ":" realm ":" SASLprep(password)) + std::string key = password; + if (attr_nonce && attr_realm) { + // 使用长期凭证机制 + // key = MD5(username ":" realm ":" password) + auto realm = attr_realm->getRealm(); + std::string username = ufrag; // 对于response消息,使用ufrag作为username + std::string input = username + ":" + std::string(realm.data(), realm.size()) + ":" + password; + key = openssl_MD5(input.data(), input.size()); - // Calculate the HMAC-SHA1 of the packet according to MESSAGE-INTEGRITY rules. - auto computedMessageIntegrity = openssl_HMACsha1(this->password.data(), this->password.size(), buffer, pos); - - Utils::Byte::Set2Bytes(buffer, pos, static_cast(Attribute::MESSAGE_INTEGRITY)); - Utils::Byte::Set2Bytes(buffer, pos + 2, 20); - std::memcpy(buffer + pos + 4, computedMessageIntegrity.data(), computedMessageIntegrity.size()); - - // Update the pointer. - this->messageIntegrity = buffer + pos + 4; - pos += 4 + 20; - - // Restore length field. - if (addFingerprint) - Utils::Byte::Set2Bytes(buffer, 2, static_cast(this->size - 20)); - } - else - { - // Unset the pointer (if it was set). - this->messageIntegrity = nullptr; + // DebugL << "Long-term credential used for response:"; + // DebugL << "ufrag: " << ufrag; + // DebugL << "realm: " << std::string(realm.data(), realm.size()); + // DebugL << "password: " << password; + // DebugL << "input: " << input; + // DebugL << "MD5 key: " << toolkit::hexdump(key.data(), key.size()); } - // Add FINGERPRINT. - if (addFingerprint) - { - // Compute the CRC32 of the packet up to (but excluding) the FINGERPRINT - // attribute and XOR it with 0x5354554e. - uint32_t computedFingerprint = GetCRC32(buffer, pos) ^ 0x5354554e; + size_t mi_calc_len = HEADER_SIZE + attr_size; + auto computedMessageIntegrity = openssl_HMACsha1(key.data(), key.size(), _data->data(), mi_calc_len); + auto attr_message_integrity = std::make_shared(); + attr_message_integrity->setHmac(computedMessageIntegrity); + attr_message_integrity->storeToData(); + memcpy((unsigned char *)_data->data() + HEADER_SIZE + attr_size, attr_message_integrity->data(), attr_message_integrity->size()); - Utils::Byte::Set2Bytes(buffer, pos, static_cast(Attribute::FINGERPRINT)); - Utils::Byte::Set2Bytes(buffer, pos + 2, 4); - Utils::Byte::Set4Bytes(buffer, pos + 4, computedFingerprint); - pos += 4 + 4; - - // Set flag. - this->hasFingerprint = true; - } - else - { - this->hasFingerprint = false; - } - - MS_ASSERT(pos == this->size, "pos != this->size"); + // DebugL << "Serialize MESSAGE-INTEGRITY:"; + // DebugL << "password: \"" << password << "\""; + // DebugL << "key: " << toolkit::hexdump(key.data(), key.size()); + // DebugL << "hmac_calculated: " << toolkit::hexdump(computedMessageIntegrity.data(), computedMessageIntegrity.size()); } + + if (fingerprint_size) { + // Add FINGERPRINT. + // Compute the CRC32 of the packet up to (but excluding) the FINGERPRINT + uint16_t final_length = static_cast(attr_size + message_integrity_size + fingerprint_size); + Byte::Set2Bytes((unsigned char *)_data->data(), 2, final_length); + size_t fp_calc_len = HEADER_SIZE + attr_size + message_integrity_size; + uint32_t computedFingerprint = getCRC32((unsigned char *)_data->data(), fp_calc_len) ^ 0x5354554e; + + auto attr_fingerprint = std::make_shared(); + attr_fingerprint->setFingerprint(computedFingerprint); + attr_fingerprint->storeToData(); + memcpy((unsigned char *)_data->data() + HEADER_SIZE + attr_size + message_integrity_size, attr_fingerprint->data(), attr_fingerprint->size()); + } +} + +StunPacket::Ptr StunPacket::createSuccessResponse() const { + // TraceL; + CHECK(_klass == Class::REQUEST, "attempt to create a success response for a non Request STUN packet"); + + auto packet = std::make_shared(Class::SUCCESS_RESPONSE, _method, _transaction_id.c_str()); + + // 复制认证相关属性到响应包中,用于MESSAGE-INTEGRITY计算 + auto attr_realm = getAttribute(StunAttribute::Type::REALM); + if (attr_realm) { + packet->addAttribute(attr_realm); + } + + auto attr_nonce = getAttribute(StunAttribute::Type::NONCE); + if (attr_nonce) { + packet->addAttribute(attr_nonce); + DebugL << "Copied NONCE attribute to response"; + } + + return packet; +} + +StunPacket::Ptr StunPacket::createErrorResponse(StunAttrErrorCode::Code errorCode) const { + TraceL; + CHECK(_klass == Class::REQUEST, "attempt to create an error response for a non Request STUN packet"); + auto ret = std::make_shared(Class::ERROR_RESPONSE, _method, _transaction_id.c_str()); + auto attr = std::make_shared(); + attr->setErrorCode(errorCode); + ret->addAttribute(std::move(attr)); + return ret; +} + +char *StunPacket::data() const { + return _data ? _data->data() : nullptr; +} + +size_t StunPacket::size() const { + return _data ? _data->size() : 0; +} + +bool StunPacket::loadFromData(const uint8_t *buf, size_t len) { + if (HEADER_SIZE > len) { + WarnL << "size too small " << len; + return false; + } + + _data = BufferRaw::create(); + _data->assign((const char *)(buf), len); + + _transaction_id.assign((const char *)buf + 8, 12); + + if (len == HEADER_SIZE) { + return true; + } + + return loadAttrMessage(buf + HEADER_SIZE, len - HEADER_SIZE); +} + +bool StunPacket::loadAttrMessage(const uint8_t *buf, size_t len) { + _attribute_map.clear(); + _message_integrity_data_len = HEADER_SIZE + len; + + uint8_t *ptr = const_cast(buf); + StunAttribute::Ptr attr = nullptr; + while (ptr < buf + len) { + auto type = (StunAttribute::Type)Byte::Get2Bytes(ptr, 0); + size_t length = Byte::Get2Bytes(ptr, 2); + size_t lengthAlign = Byte::PadTo4Bytes((uint16_t)length); + + switch (type) { + case StunAttribute::Type::MAPPED_ADDRESS: attr = std::make_shared(); break; + case StunAttribute::Type::USERNAME: attr = std::make_shared(); break; + case StunAttribute::Type::MESSAGE_INTEGRITY: + attr = std::make_shared(); + _message_integrity_data_len = HEADER_SIZE + ptr - buf; + break; + case StunAttribute::Type::ERROR_CODE: attr = std::make_shared(); break; + case StunAttribute::Type::CHANNEL_NUMBER: attr = std::make_shared(); break; + case StunAttribute::Type::LIFETIME: attr = std::make_shared(); break; + case StunAttribute::Type::DATA: attr = std::make_shared(); break; + case StunAttribute::Type::REALM: attr = std::make_shared(); break; + case StunAttribute::Type::NONCE: attr = std::make_shared(); break; + case StunAttribute::Type::REQUESTED_TRANSPORT: attr = std::make_shared(); break; + case StunAttribute::Type::XOR_PEER_ADDRESS: attr = std::make_shared(_transaction_id); break; + case StunAttribute::Type::XOR_RELAYED_ADDRESS: attr = std::make_shared(_transaction_id); break; + case StunAttribute::Type::XOR_MAPPED_ADDRESS: attr = std::make_shared(_transaction_id); break; + + case StunAttribute::Type::PRIORITY: attr = std::make_shared(); break; + case StunAttribute::Type::USE_CANDIDATE: attr = std::make_shared(); break; + case StunAttribute::Type::FINGERPRINT: attr = std::make_shared(); break; + case StunAttribute::Type::ICE_CONTROLLED: attr = std::make_shared(); break; + case StunAttribute::Type::ICE_CONTROLLING: attr = std::make_shared(); break; + case StunAttribute::Type::GOOG_NETWORK_INFO: + case StunAttribute::Type::SOFTWARE: + break; + default: WarnL << "not support Attribute " << (uint16_t)type << "," << toolkit::hexdump(ptr, 2); break; + } + + if (attr) { + if (ptr + lengthAlign + 4 > buf + len) { + WarnL << "the attribute length exceeds the remaining size, packet discarded"; + return false; + } + + if (attr->loadFromData(ptr, StunAttribute::ATTR_HEADER_SIZE + length)) { + _attribute_map.emplace(type, std::move(attr)); + + } else { + if (StunAttribute::isComprehensionRequired(ptr, 4)) { + WarnL << "parse a Comprehension Required Stun Attribute failed, type=" << (uint16_t)type << " len=" << length; + return false; + } + WarnL << "parse Stun Attribute failed type=" << (uint16_t)type << " len=" << length; + } + attr = nullptr; + } + + ptr += lengthAlign + StunAttribute::ATTR_HEADER_SIZE; + } + return true; +} + +bool StunPacket::storeAttrMessage() { + uint8_t *buf = (uint8_t *)_data->data() + HEADER_SIZE; + for (auto &pr : _attribute_map) { + memcpy(buf, pr.second->data(), pr.second->size()); + buf += pr.second->size(); + } + return true; +} + +size_t StunPacket::getAttrSize() const { + size_t size = 0; + for (auto &pr : _attribute_map) { + size += pr.second->size(); + } + return size; +} + +SuccessResponsePacket::SuccessResponsePacket(Method method, const std::string& transaction_id) : + StunPacket(Class::SUCCESS_RESPONSE, method, transaction_id.c_str()) { +} + +ErrorResponsePacket::ErrorResponsePacket(Method method, const std::string& transaction_id, StunAttrErrorCode::Code error_code) : + StunPacket(Class::ERROR_RESPONSE, method, transaction_id.c_str()) { + DebugL; + auto attr = std::make_shared(); + attr->setErrorCode(error_code); + addAttribute(std::move(attr)); +} + } // namespace RTC diff --git a/webrtc/StunPacket.hpp b/webrtc/StunPacket.hpp index 2776a9b6..294ee4a5 100644 --- a/webrtc/StunPacket.hpp +++ b/webrtc/StunPacket.hpp @@ -1,213 +1,689 @@ -/** -ISC License +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. +*/ -Copyright © 2015, Iñaki Baz Castillo +#ifndef ZLMEDIAKIT_WEBRTC_STUN_PACKET_HPP +#define ZLMEDIAKIT_WEBRTC_STUN_PACKET_HPP -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef MS_RTC_STUN_PACKET_HPP -#define MS_RTC_STUN_PACKET_HPP - - -#include "logger.h" -#include "Utils.hpp" #include +#include "Util/Byte.hpp" +#include "Network/Buffer.h" +#include "Network/sockutil.h" -namespace RTC -{ - class StunPacket - { - public: - // STUN message class. - enum class Class : uint16_t - { - REQUEST = 0, - INDICATION = 1, - SUCCESS_RESPONSE = 2, - ERROR_RESPONSE = 3 - }; +namespace RTC { +// reference https://rcf-editor.org/rfc/rfc8489 +// reference https://rcf-editor.org/rfc/rfc8656 +// reference https://rcf-editor.org/rfc/rfc8445 - // STUN message method. - enum class Method : uint16_t - { - BINDING = 1 - }; +//////////// Attribute ////////////////////////// +// reference https://rcf-editor.org/rfc/rfc8489 +/* +0 1 2 3 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Type | Length | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Value (variable) .... ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + Figure 4: Format of STUN Attributes + reference https://www.rfc-editor.org/rfc/rfc8489.html#section-14 +*/ +class StunAttribute { +public: + // Attribute type. + enum class Type : uint16_t { + MAPPED_ADDRESS = 0x0001, + RESPONSE_ADDRESS = 0x0002, // Reserved; was RESPONSE-ADDRESS prior to [RFC5389] + CHANGE_REQUEST = 0x0003, // Reserved; was CHANGE-REQUEST prior to [RFC5389] + CHANGED_ADDRESS = 0x0005, // Reserved; was CHANGED-ADDRESS prior to [RFC5389] + USERNAME = 0x0006, + PASSWORD = 0x0005, // Reserved; was PASSWORD prior to [RFC5389] + MESSAGE_INTEGRITY = 0x0008, + ERROR_CODE = 0x0009, + UNKNOWN_ATTRIBUTES = 0x000A, + REFLECTED_FROM = 0x000B, // Reserved; was REFLECTED-FROM prior to [RFC5389] + CHANNEL_NUMBER = 0x000C, // [RFC5766] + LIFETIME = 0x000D, // [RFC5766] + BANDWIDTH = 0x0010, // Reserved; [RFC5766] + XOR_PEER_ADDRESS = 0x0012, // [RFC5766] + DATA = 0x0013, // [RFC5766] + REALM = 0x0014, + NONCE = 0x0015, + XOR_RELAYED_ADDRESS = 0x0016, // [RFC5766] + EVEN_PORT = 0x0018, // [RFC5766] + REQUESTED_TRANSPORT = 0x0019, // [RFC5766] + DONT_FRAGMENT = 0x001A, // [RFC5766] + MESSAGE_INTEGRITY_SHA256 = 0x001C, + USERHASH = 0x001E, + PASSWORD_ALGORITHM = 0x001D, + XOR_MAPPED_ADDRESS = 0x0020, + TIMER_VAL = 0x0021, // Reserved; [RFC5766] + RESERVATION_TOKEN = 0x0022, // [RFC5766] + PRIORITY = 0x0024, + USE_CANDIDATE = 0x0025, - // Attribute type. - enum class Attribute : uint16_t - { - MAPPED_ADDRESS = 0x0001, - USERNAME = 0x0006, - MESSAGE_INTEGRITY = 0x0008, - ERROR_CODE = 0x0009, - UNKNOWN_ATTRIBUTES = 0x000A, - REALM = 0x0014, - NONCE = 0x0015, - XOR_MAPPED_ADDRESS = 0x0020, - PRIORITY = 0x0024, - USE_CANDIDATE = 0x0025, - SOFTWARE = 0x8022, - ALTERNATE_SERVER = 0x8023, - FINGERPRINT = 0x8028, - ICE_CONTROLLED = 0x8029, - ICE_CONTROLLING = 0x802A - }; - - // Authentication result. - enum class Authentication - { - OK = 0, - UNAUTHORIZED = 1, - BAD_REQUEST = 2 - }; - - public: - static bool IsStun(const uint8_t* data, size_t len) - { - // clang-format off - return ( - // STUN headers are 20 bytes. - (len >= 20) && - // DOC: https://tools.ietf.org/html/draft-ietf-avtcore-rfc5764-mux-fixes - (data[0] < 3) && - // Magic cookie must match. - (data[4] == StunPacket::magicCookie[0]) && (data[5] == StunPacket::magicCookie[1]) && - (data[6] == StunPacket::magicCookie[2]) && (data[7] == StunPacket::magicCookie[3]) - ); - // clang-format on - } - static StunPacket* Parse(const uint8_t* data, size_t len); - - private: - static const uint8_t magicCookie[]; - - public: - StunPacket( - Class klass, Method method, const uint8_t* transactionId, const uint8_t* data, size_t size); - ~StunPacket(); - - void Dump() const; - Class GetClass() const - { - return this->klass; - } - Method GetMethod() const - { - return this->method; - } - const uint8_t* GetData() const - { - return this->data; - } - size_t GetSize() const - { - return this->size; - } - void SetUsername(const char* username, size_t len) - { - this->username.assign(username, len); - } - void SetPriority(uint32_t priority) - { - this->priority = priority; - } - void SetIceControlling(uint64_t iceControlling) - { - this->iceControlling = iceControlling; - } - void SetIceControlled(uint64_t iceControlled) - { - this->iceControlled = iceControlled; - } - void SetUseCandidate() - { - this->hasUseCandidate = true; - } - void SetXorMappedAddress(const struct sockaddr* xorMappedAddress) - { - this->xorMappedAddress = xorMappedAddress; - } - void SetErrorCode(uint16_t errorCode) - { - this->errorCode = errorCode; - } - void SetMessageIntegrity(const uint8_t* messageIntegrity) - { - this->messageIntegrity = messageIntegrity; - } - void SetFingerprint() - { - this->hasFingerprint = true; - } - const std::string& GetUsername() const - { - return this->username; - } - uint32_t GetPriority() const - { - return this->priority; - } - uint64_t GetIceControlling() const - { - return this->iceControlling; - } - uint64_t GetIceControlled() const - { - return this->iceControlled; - } - bool HasUseCandidate() const - { - return this->hasUseCandidate; - } - uint16_t GetErrorCode() const - { - return this->errorCode; - } - bool HasMessageIntegrity() const - { - return (this->messageIntegrity ? true : false); - } - bool HasFingerprint() const - { - return this->hasFingerprint; - } - Authentication CheckAuthentication( - const std::string& localUsername, const std::string& localPassword); - StunPacket* CreateSuccessResponse(); - StunPacket* CreateErrorResponse(uint16_t errorCode); - void Authenticate(const std::string& password); - void Serialize(uint8_t* buffer); - - private: - // Passed by argument. - Class klass; // 2 bytes. - Method method; // 2 bytes. - const uint8_t* transactionId{ nullptr }; // 12 bytes. - uint8_t* data{ nullptr }; // Pointer to binary data. - size_t size{ 0u }; // The full message size (including header). - // STUN attributes. - std::string username; // Less than 513 bytes. - uint32_t priority{ 0u }; // 4 bytes unsigned integer. - uint64_t iceControlling{ 0u }; // 8 bytes unsigned integer. - uint64_t iceControlled{ 0u }; // 8 bytes unsigned integer. - bool hasUseCandidate{ false }; // 0 bytes. - const uint8_t* messageIntegrity{ nullptr }; // 20 bytes. - bool hasFingerprint{ false }; // 4 bytes. - const struct sockaddr* xorMappedAddress{ nullptr }; // 8 or 20 bytes. - uint16_t errorCode{ 0u }; // 4 bytes (no reason phrase). - std::string password; + //Comprehension-optional range (0x8000-0xFFFF) + PASSWORD_ALGORITHMS = 0x8002, + ALTERNATE_DOMAIN = 0x8003, + SOFTWARE = 0x8022, + ALTERNATE_SERVER = 0x8023, + FINGERPRINT = 0x8028, + ICE_CONTROLLED = 0x8029, + ICE_CONTROLLING = 0x802A, + GOOG_NETWORK_INFO = 0xC057, }; + + static const size_t ATTR_HEADER_SIZE = 4; + static bool isComprehensionRequired(const uint8_t *data, size_t len); + + using Ptr = std::shared_ptr; + StunAttribute(StunAttribute::Type type) : _type(type) {} + virtual ~StunAttribute() = default; + + char *data() { return _data ? _data->data() : nullptr; } + char *body() { return _data ? _data->data() + ATTR_HEADER_SIZE : nullptr; } + size_t size() const { return _data ? _data->size() : 0; } + + Type type() const { return _type; } + + virtual bool loadFromData(const uint8_t *buf, size_t len) = 0; + virtual bool storeToData() = 0; + // virtual std::string dump() = 0; + +protected: + const uint8_t * loadHeader(const uint8_t *buf); + uint8_t * storeHeader(); + +protected: + Type _type; + uint16_t _length; + toolkit::BufferRaw::Ptr _data; +}; + +/* +0 1 2 3 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|0 0 0 0 0 0 0 0| Family | Port | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | +| Address (32 bits or 128 bits) | +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + Figure 5: Format of MAPPED-ADDRESS Attribute + reference https://www.rfc-editor.org/rfc/rfc8489.html#page-37 +*/ +class StunAttrMappedAddress : public StunAttribute { +public: + using Ptr = std::shared_ptr; + static constexpr Type TYPE = StunAttribute::Type::MAPPED_ADDRESS; + StunAttrMappedAddress() : StunAttribute(TYPE) {}; + virtual ~StunAttrMappedAddress() = default; + + bool loadFromData(const uint8_t *buf, size_t len) override; + bool storeToData() override; + // std::string dump() override; +}; + +class StunAttrUserName : public StunAttribute { +public: + using Ptr = std::shared_ptr; + static constexpr Type TYPE = StunAttribute::Type::USERNAME; + StunAttrUserName() : StunAttribute(TYPE) {}; + virtual ~StunAttrUserName() = default; + + bool loadFromData(const uint8_t *buf, size_t len) override; + bool storeToData() override; + // std::string dump() override; + + void setUsername(std::string username) { _username = std::move(username); } + + const std::string& getUsername() const { return _username; } + +private: + std::string _username; +}; + +class StunAttrMessageIntegrity : public StunAttribute { +public: + using Ptr = std::shared_ptr; + static constexpr Type TYPE = StunAttribute::Type::MESSAGE_INTEGRITY; + StunAttrMessageIntegrity() : StunAttribute(TYPE) {}; + virtual ~StunAttrMessageIntegrity() = default; + + bool loadFromData(const uint8_t *buf, size_t len) override; + bool storeToData() override; + // std::string dump() override; + + void setHmac(std::string hmac) { _hmac = std::move(hmac); } + const std::string &getHmac() const { return _hmac; } +private: + std::string _hmac; +}; + +class StunAttrErrorCode : public StunAttribute { +public: + using Ptr = std::shared_ptr; + static constexpr Type TYPE = StunAttribute::Type::ERROR_CODE; + StunAttrErrorCode() : StunAttribute(TYPE) {}; + virtual ~StunAttrErrorCode() = default; + + enum class Code : uint16_t { + Invalid = 0, // + TryAlternate = 300, //尝试备用服务器 + BadRequest = 400, + Unauthorized = 401, + Forbidden = 403, //禁止 + RequestTimedOut = 408, //请求超时(客户端认为此事务已经失败) + UnknownAttribute = 420, + AllocationMismatch = 438, + StaleNonce = 438, //NONCE 不再有效,客户端应使用响应中的NONCE重试 + AddressFamilyNotSupported = 440, //不支持的协议簇 + WrongCredentials = 441, //凭据错误 + UnsupportedTransportAddress = 442, //不支持的传输地址 + AllocationQuotaReached = 486, //alloction 达到上限,客户端应该至少等待一分钟后重新尝试创建 + RoleConflict = 487, //角色冲突 + ServerError = 500, //服务器临时错误,客户端应重试 + InsuficientCapacity = 508, //容量不足,没有更多可用的中继传输地址 + }; + + bool loadFromData(const uint8_t *buf, size_t len) override; + bool storeToData() override; + // std::string dump() override; + + void setErrorCode(Code error_code) { _error_code = error_code; } + Code getErrorCode() const { return _error_code; } +private: + Code _error_code; +}; + +class StunAttrChannelNumber : public StunAttribute { +public: + using Ptr = std::shared_ptr; + static constexpr Type TYPE = StunAttribute::Type::CHANNEL_NUMBER; + StunAttrChannelNumber() : StunAttribute(TYPE) {}; + virtual ~StunAttrChannelNumber() = default; + + bool loadFromData(const uint8_t *buf, size_t len) override; + bool storeToData() override; + + void setChannelNumber(uint16_t channel_number) { _channel_number = channel_number; } + uint16_t getChannelNumber() const { return _channel_number; } +private: + uint16_t _channel_number; +}; + +class StunAttrLifeTime : public StunAttribute { +public: + using Ptr = std::shared_ptr; + static constexpr Type TYPE = StunAttribute::Type::LIFETIME; + StunAttrLifeTime() : StunAttribute(TYPE) {}; + ~StunAttrLifeTime() = default; + + bool loadFromData(const uint8_t *buf, size_t len) override; + bool storeToData() override; + // std::string dump() override; + + void setLifetime(uint32_t lifetime) { _lifetime = lifetime; } + uint32_t getLifetime() const { return _lifetime; } +private: + uint32_t _lifetime; +}; + +/* +0 1 2 3 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|0 0 0 0 0 0 0 0| Family | X-Port | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| X-Address (Variable) ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + Figure 6: Format of XOR-MAPPED-ADDRESS Attribute + reference https://www.rfc-editor.org/rfc/rfc8489.html#page-38 +*/ +class StunAttrXorPeerAddress : public StunAttribute { +public: + using Ptr = std::shared_ptr; + static constexpr Type TYPE = StunAttribute::Type::XOR_PEER_ADDRESS; + StunAttrXorPeerAddress(std::string transaction_id) + : StunAttribute(TYPE) + , _transaction_id(std::move(transaction_id)) {} + virtual ~StunAttrXorPeerAddress() = default; + + bool loadFromData(const uint8_t *buf, size_t len) override; + bool storeToData() override; + // std::string dump() override; + + void setAddr(const struct sockaddr_storage &addr) { _addr = addr; } + const struct sockaddr_storage& getAddr() const { return _addr; } + + std::string getIp() const { return toolkit::SockUtil::inet_ntoa((struct sockaddr *)&_addr); } + uint16_t getPort() const { return toolkit::SockUtil::inet_port((struct sockaddr *)&_addr); } + +protected: + struct sockaddr_storage _addr; + std::string _transaction_id; +}; + +class StunAttrData : public StunAttribute { +public: + using Ptr = std::shared_ptr; + static constexpr Type TYPE = StunAttribute::Type::DATA; + StunAttrData() : StunAttribute(TYPE) {}; + virtual ~StunAttrData() = default; + + bool loadFromData(const uint8_t *buf, size_t len) override; + bool storeToData() override; + + void setData(std::string data) { _data_content = std::move(data); } + void setData(const char *data, int size) { _data_content.assign(data, size); } + const std::string &getData() const { return _data_content; } + +private: + std::string _data_content; +}; + +class StunAttrRealm : public StunAttribute { +public: + using Ptr = std::shared_ptr; + static constexpr Type TYPE = StunAttribute::Type::REALM; + StunAttrRealm() : StunAttribute(TYPE) {}; + virtual ~StunAttrRealm() = default; + + bool loadFromData(const uint8_t *buf, size_t len) override; + bool storeToData() override; + // std::string dump() override; + + void setRealm(std::string realm) { _realm = std::move(realm); } + const std::string &getRealm() const { return _realm; } +private: + // 长度小于128字符 + std::string _realm; +}; + +class StunAttrNonce : public StunAttribute { +public: + using Ptr = std::shared_ptr; + static constexpr Type TYPE = StunAttribute::Type::NONCE; + StunAttrNonce() : StunAttribute(TYPE) {}; + virtual ~StunAttrNonce() = default; + + bool loadFromData(const uint8_t *buf, size_t len) override; + bool storeToData() override; + // std::string dump() override; + + void setNonce(std::string nonce) { _nonce = std::move(nonce); } + const std::string& getNonce() const { return _nonce; } +private: + // 长度小于128字符 + std::string _nonce; +}; + +class StunAttrXorRelayedAddress : public StunAttrXorPeerAddress { +public: + using Ptr = std::shared_ptr; + static constexpr Type TYPE = StunAttribute::Type::XOR_RELAYED_ADDRESS; + StunAttrXorRelayedAddress(std::string transaction_id) : StunAttrXorPeerAddress(std::move(transaction_id)) { + _type = TYPE; + } + virtual ~StunAttrXorRelayedAddress() = default; +}; + +class StunAttrXorMappedAddress : public StunAttrXorPeerAddress { +public: + using Ptr = std::shared_ptr; + static constexpr Type TYPE = StunAttribute::Type::XOR_MAPPED_ADDRESS; + StunAttrXorMappedAddress(std::string transaction_id) : StunAttrXorPeerAddress(std::move(transaction_id)) { + _type = TYPE; + } + virtual ~StunAttrXorMappedAddress() = default; +}; + +/* +0 1 2 3 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Protocol | RFFU | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + reference https://www.rfc-editor.org/rfc/rfc5766.html#section-14.7 +*/ +class StunAttrRequestedTransport : public StunAttribute { +public: + using Ptr = std::shared_ptr; + static constexpr Type TYPE = StunAttribute::Type::REQUESTED_TRANSPORT; + StunAttrRequestedTransport() : StunAttribute(TYPE) {}; + virtual ~StunAttrRequestedTransport() = default; + + bool loadFromData(const uint8_t *buf, size_t len) override; + bool storeToData() override; + // std::string dump() override; + + enum class Protocol : uint8_t { + // This specification only allows the use of codepoint 17 (User Datagram Protocol). + UDP = 0x11, + }; + + void setProtocol(Protocol protocol) { _protocol = protocol; } + Protocol getProtocol() const { return _protocol; } +private: + Protocol _protocol = Protocol::UDP; +}; + +class StunAttrPriority : public StunAttribute { +public: + using Ptr = std::shared_ptr; + static constexpr Type TYPE = StunAttribute::Type::PRIORITY; + StunAttrPriority() : StunAttribute(TYPE) {}; + virtual ~StunAttrPriority() = default; + + bool loadFromData(const uint8_t *buf, size_t len) override; + bool storeToData() override; + // std::string dump() override; + + void setPriority(uint64_t priority) { _priority = priority; } + uint64_t getPriority() const { return _priority; } +private: + uint32_t _priority; +}; + +class StunAttrUseCandidate : public StunAttribute { +public: + using Ptr = std::shared_ptr; + static constexpr Type TYPE = StunAttribute::Type::USE_CANDIDATE; + StunAttrUseCandidate() : StunAttribute(TYPE) {}; + virtual ~StunAttrUseCandidate() = default; + + bool loadFromData(const uint8_t *buf, size_t len) override; + bool storeToData() override; + // std::string dump() override; +}; + +class StunAttrFingerprint : public StunAttribute { +public: + using Ptr = std::shared_ptr; + static constexpr Type TYPE = StunAttribute::Type::FINGERPRINT; + StunAttrFingerprint() : StunAttribute(TYPE) {}; + virtual ~StunAttrFingerprint() = default; + + bool loadFromData(const uint8_t *buf, size_t len) override; + bool storeToData() override; + // std::string dump() override; + + void setFingerprint(uint32_t fingerprint) { _fingerprint = fingerprint; } + uint32_t getFingerprint() const { return _fingerprint; } +private: + uint32_t _fingerprint; +}; + +class StunAttrIceControlled : public StunAttribute { +public: + using Ptr = std::shared_ptr; + static constexpr Type TYPE = StunAttribute::Type::ICE_CONTROLLED; + StunAttrIceControlled() : StunAttribute(TYPE) {}; + virtual ~StunAttrIceControlled() = default; + + bool loadFromData(const uint8_t *buf, size_t len) override; + bool storeToData() override; + // std::string dump() override; + + void setTiebreaker(uint64_t tiebreaker) { _tiebreaker = tiebreaker; } + uint64_t getTiebreaker() const { return _tiebreaker; } +private: + uint64_t _tiebreaker = 0; // 8 bytes unsigned integer. +}; + +class StunAttrIceControlling : public StunAttribute { +public: + using Ptr = std::shared_ptr; + static constexpr Type TYPE = StunAttribute::Type::ICE_CONTROLLING; + StunAttrIceControlling() : StunAttribute(TYPE) {}; + virtual ~StunAttrIceControlling() = default; + + bool loadFromData(const uint8_t *buf, size_t len) override; + bool storeToData() override; + // std::string dump() override; + + void setTiebreaker(uint64_t tiebreaker) { _tiebreaker = tiebreaker; } + uint64_t getTiebreaker() const { return _tiebreaker; } +private: + uint64_t _tiebreaker = 0; // 8 bytes unsigned integer. +}; + +//////////// STUN ////////////////////////// +/* +0 1 2 3 +0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|0 0| STUN Message Type | Message Length | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Magic Cookie | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | +| Transaction ID (96 bits) | +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + Figure 2: Format of STUN Message Header + reference https://www.rfc-editor.org/rfc/rfc8489.html#section-5 */ +class StunPacket : public toolkit::Buffer { +public: + using Ptr = std::shared_ptr; + + // STUN message class. + enum class Class : uint8_t { + REQUEST = 0, + INDICATION = 1, + SUCCESS_RESPONSE = 2, + ERROR_RESPONSE = 3 + }; + + // STUN message method. + enum class Method : uint16_t { + BINDING = 0x001, + + //TURN Extended + //https://www.rfc-editor.org/rfc/rfc5766.html#section-13 + ALLOCATE = 0x003, // (only request/response semantics defined) + REFRESH = 0x004, // (only request/response semantics defined) + SEND = 0x006, // (only indication semantics defined) + DATA = 0x007, // (only indication semantics defined) + CREATEPERMISSION = 0x008, // (only request/response semantics defined + CHANNELBIND = 0x009, // (only request/response semantics defined) + }; + + // Authentication result. + enum class Authentication { + OK = 0, + UNAUTHORIZED = 1, + BAD_REQUEST = 2 + }; + + struct EnumClassHash { + template + std::size_t operator()(T t) const { + return static_cast(t); + } + }; + struct ClassMethodHash { + bool operator()(std::pair key) const { + std::size_t h = 0; + h ^= std::hash()((uint8_t)key.first) << 1; + h ^= std::hash()((uint8_t)key.second) << 2; + return h; + } + }; + + static const size_t HEADER_SIZE = 20; + static const uint8_t _magicCookie[]; + + static bool isStun(const uint8_t *data, size_t len); + static Class getClass(const uint8_t *data, size_t len); + static Method getMethod(const uint8_t *data, size_t len); + static StunPacket::Ptr parse(const uint8_t *data, size_t len); + static std::string mappingClassEnum2Str(Class klass); + static std::string mappingMethodEnum2Str(Method method); + + StunPacket(Class klass, Method method, const char* transId = nullptr); + virtual ~StunPacket(); + + Class getClass() const { return _klass; } + + Method getMethod() const { return _method; } + + std::string getClassStr() const { return StrPrinter << mappingClassEnum2Str(_klass) << "(" << (uint32_t)_klass << ")"; } + + std::string getMethodStr() const { return StrPrinter << mappingMethodEnum2Str(_method) << "(" << (uint32_t)_method << ")"; } + + std::string dumpString(bool transId = false) const; + + const std::string& getTransactionId() const { return _transaction_id; } + + void setUfrag(std::string ufrag) { _ufrag = std::move(ufrag); } + const std::string& getUfrag() const { return _ufrag; } + + void setPassword(std::string password) { _password = std::move(password); } + const std::string& getPassword() const { return _password; } + + void setPeerUfrag(std::string peer_ufrag) { _peer_ufrag = std::move(peer_ufrag); } + const std::string& getPeerUfrag() const { return _peer_ufrag; } + + void setPeerPassword(std::string peer_password) { _peer_password = std::move(peer_password); } + const std::string& getPeerPassword() const { return _peer_password; } + + void setNeedMessageIntegrity(bool flag) { _need_message_integrity = flag; } + bool getNeedMessageIntegrity() const { return _need_message_integrity; } + + void setNeedFingerprint(bool flag) { _need_fingerprint = flag; } + bool getNeedFingerprint() const { return _need_fingerprint; } + + void refreshTransactionId() { _transaction_id = toolkit::makeRandStr(12, false); } + + void addAttribute(StunAttribute::Ptr attr); + void removeAttribute(StunAttribute::Type type); + bool hasAttribute(StunAttribute::Type type) const; + StunAttribute::Ptr getAttribute(StunAttribute::Type type) const; + + template + std::shared_ptr getAttribute() const { + auto attr = getAttribute(T::TYPE); + if (attr) { + return std::dynamic_pointer_cast(attr); + } + return nullptr; + } + + std::string getUsername() const; + uint64_t getPriority() const; + StunAttrErrorCode::Code getErrorCode() const; + + Authentication checkAuthentication(const std::string &ufrag, const std::string &password) const; + void serialize(); + + StunPacket::Ptr createSuccessResponse() const; + StunPacket::Ptr createErrorResponse(StunAttrErrorCode::Code errorCode) const; + + ///////Buffer override/////// + char *data() const override; + size_t size() const override; + +private: + bool loadFromData(const uint8_t *buf, size_t len); + + // attribute + bool loadAttrMessage(const uint8_t *buf, size_t len); + bool storeAttrMessage(); + size_t getAttrSize() const; + +protected: + + Class _klass; + Method _method; + std::string _transaction_id; // 12 bytes/96bits. + std::map _attribute_map; + toolkit::BufferRaw::Ptr _data; + std::string _ufrag; + std::string _password; + std::string _peer_ufrag; + std::string _peer_password; + size_t _message_integrity_data_len = 0; //MESSAGE_INTEGRITY属性之前的字段 + + bool _need_message_integrity = true; + bool _need_fingerprint = true; +}; + +class BindingPacket : public StunPacket { +public: + BindingPacket() : StunPacket(Class::REQUEST, Method::BINDING) {}; + virtual ~BindingPacket() {}; +}; + +class SuccessResponsePacket : public StunPacket { +public: + SuccessResponsePacket(Method method, const std::string& transaction_id); + virtual ~SuccessResponsePacket() {}; +}; + +class ErrorResponsePacket : public StunPacket { +public: + ErrorResponsePacket(Method method, const std::string& transaction_id, StunAttrErrorCode::Code error_code); + virtual ~ErrorResponsePacket() {}; +}; + +//////////// TURN ////////////////////////// + +class TurnPacket : public StunPacket { +public: + TurnPacket(Class klass, Method method) : StunPacket(klass, method) {} + virtual ~TurnPacket() {}; +}; + +class AllocatePacket : public TurnPacket { +public: + AllocatePacket() : TurnPacket(Class::REQUEST, Method::ALLOCATE) {}; + virtual ~AllocatePacket() {}; +}; + +class RefreshPacket : public TurnPacket { +public: + RefreshPacket() : TurnPacket(Class::REQUEST, Method::REFRESH) {}; + virtual ~RefreshPacket() {}; +}; + +class CreatePermissionPacket : public TurnPacket { +public: + CreatePermissionPacket() : TurnPacket(Class::REQUEST, Method::CREATEPERMISSION) {}; + virtual ~CreatePermissionPacket() {}; +}; + +class ChannelBindPacket : public TurnPacket { +public: + ChannelBindPacket() : TurnPacket(Class::REQUEST, Method::CHANNELBIND) {}; + virtual ~ChannelBindPacket() {}; +}; + +class SendIndicationPacket : public TurnPacket { +public: + SendIndicationPacket() : TurnPacket(Class::INDICATION, Method::SEND) {}; + virtual ~SendIndicationPacket() {}; +}; + +class DataIndicationPacket : public TurnPacket { +public: + DataIndicationPacket() : TurnPacket(Class::INDICATION, Method::DATA) {}; + virtual ~DataIndicationPacket() {}; +}; + +class DataPacket : public TurnPacket { +public: + DataPacket() : TurnPacket(Class::INDICATION, Method::DATA) {}; + virtual ~DataPacket() {}; +}; + } // namespace RTC #endif diff --git a/webrtc/USAGE.md b/webrtc/USAGE.md new file mode 100644 index 00000000..67542f0a --- /dev/null +++ b/webrtc/USAGE.md @@ -0,0 +1,256 @@ +# WebRTC 使用说明 + +## WebRTC 架构 + +### 1. SFU 模式架构 (WHIP/WHEP) + +SFU 模式通过服务器中继媒体流,支持多路复用和转码: + +``` + WebRTC SFU 模式 (WHIP/WHEP) + + 推流端 (WHIP) 拉流端 (WHEP) + +----------------+ +-----------------+ + | Encoder | | Player | + | (Browser/ZLM) | | (Browser/ZLM) | + +----------------+ +-----------------+ + | | + | WHIP Protocol | WHEP Protocol + | (WebRTC ingest) | (WebRTC playback) + | | + v v + +-------------------------------------------------------------------+ + | ZLMediaKit Server | + +-------------------------------------------------------------------+ + - WHIP: WebRTC-HTTP Ingestion Protocol (推流) + - WHEP: WebRTC-HTTP Egress Protocol (拉流) +``` + +### 2. P2P 模式架构 + +P2P 模式允许客户端之间直接建立连接,减少服务器负载: +基于Websocket的自定义信令协议 + +``` + WebRTt WC P2P 模式 + + 客户端 A 客户端 B + +------------+ +-------------+ + | Browser/ZLM| | Browser/ZLM | + +------------+ +-------------+ + | | + | 1. 信令交换 (SDP Offer/Answer) | + | 2. ICE Candidate 交换 | + +---------------- -----+-----------------------+ + | | | + | +-----------------------+ | + | | ZLMediaKit Server | | + | | 信令服务器 (WebSocket) | | + | | STUN 服务器 | | + | | TURN 服务器 | | + | +-----------------------+ | + | | + +-----------------------------------------------+ + 直接P2P连接 +``` + +## HTTP API 接口 + +### 1. WebRTC 房间管理 + +#### `/index/api/addWebrtcRoomKeeper` +添加WebRTC到指定信令服务器,用于在信令服务器中维持房间连接。 + +**请求参数:** +- `secret`: 接口访问密钥 +- `server_host`: 信令服务器主机地址 +- `server_port`: 信令服务器端口 +- `room_id`: 房间ID,信令服务器会对该ID进行唯一性检查 + +#### `/index/api/delWebrtcRoomKeeper` +删除指定的信令服务器。 + +**请求参数:** +- `secret`: 接口访问密钥 +- `room_key`: 房间保持器的唯一标识符 + +#### `/index/api/listWebrtcRoomKeepers` +列出所有信令服务器。 + +**请求参数:** +- `secret`: 接口访问密钥 + +### 2. WebRTC 房间会话管理 + +#### `/index/api/listWebrtcRooms` +列出所有活跃的WebRTC Peer会话信息。 + +**请求参数:** +- `secret`: 接口访问密钥 + +### 3. WebRTC 推流和拉流接口 + +ZLMediaKit 支持通过标准的流代理接口来创建WebRTC推流和拉流,支持两种信令模式: + +##### `/index/api/addStreamProxy` - WebRTC 拉流代理 + +通过此接口可以创建WebRTC拉流代理,支持两种信令协议模式。 + +**请求参数:** +- `secret`: 接口访问密钥 +- `vhost`: 虚拟主机名,默认为 `__defaultVhost__` +- `app`: 应用名 +- `stream`: 流ID +- `url`: WebRTC源URL,支持两种格式 + +**WebRTC URL 格式:** + +1. **WHIP/WHEP 模式 (SFU)** - 标准HTTP信令协议: + ``` + # HTTP + webrtc://server_host:server_port/app/stream_id?signaling_protocols=0 + + # HTTPS (暂未实现) + webrtcs://server_host:server_port/app/stream_id?signaling_protocols=0 + ``` + +2. **WebSocket P2P 模式** - 自定义信令协议: + ``` + # WebSocket + webrtc://signaling_server_host:signaling_server_port/app/stream_id?signaling_protocols=1&peer_room_id=target_room_id + + # WebSocket Secure (暂未实现) + webrtcs://signaling_server_host:signaling_server_port/app/stream_id?signaling_protocols=1&peer_room_id=target_room_id + ``` + +**请求示例:** +```bash +# WHIP/WHEP 模式拉流 +curl -X POST "http://127.0.0.1/index/api/addStreamProxy" \ + -d "secret=your_secret" \ + -d "vhost=__defaultVhost__" \ + -d "app=live" \ + -d "stream=test" \ + -d "url=webrtc://source.server.com:80/live/source_stream?signaling_protocols=0" + +# P2P 模式拉流 +curl -X POST "http://127.0.0.1/index/api/addStreamProxy" \ + -d "secret=your_secret" \ + -d "vhost=__defaultVhost__" \ + -d "app=live" \ + -d "stream=test" \ + -d "url=webrtc://signaling.server.com:3000/live/source_stream??signaling_protocols=1%26peer_room_id=target_room_id" +``` + +#### `/index/api/addStreamPusherProxy` - WebRTC 推流代理 (暂未实现) + +通过此接口可以创建WebRTC推流代理,将现有流推送到WebRTC目标服务器。 + +**请求参数:** +- `secret`: 接口访问密钥 +- `schema`: 源流协议 (如: rtmp, rtsp, hls等) +- `vhost`: 虚拟主机名 +- `app`: 应用名 +- `stream`: 源流ID +- `dst_url`: WebRTC目标推流URL + +**WebRTC 推流 URL 格式:** + +1. **WHIP 模式 (SFU)** - 推流到支持WHIP的服务器: + ``` + # HTTP + webrtc://target_server:port/app/stream_id?signaling_protocols=0 + + # HTTPS (暂未实现) + webrtcs://target_server:port/app/stream_id?signaling_protocols=0 + ``` + +2. **WebSocket P2P 模式** - 推流到P2P房间 + ``` + # WebSocket + webrtc://signaling_server:port/app/stream_id?signaling_protocols=1&peer_room_id=target_room + # WebSocket Secure + webrtcs://signaling_server:port/app/stream_id?signaling_protocols=1&peer_room_id=target_room + ``` + +**请求示例:** +```bash +# 将RTSP流推送到WHIP服务器 +curl -X POST "http://127.0.0.1/index/api/addStreamPusherProxy" \ + -d "secret=your_secret" \ + -d "schema=rtsp" \ + -d "vhost=__defaultVhost__" \ + -d "app=live" \ + -d "stream=test" \ + -d "dst_url=webrtc://target.server.com:80/live/target_stream?signaling_protocols=0" + +# 将RTSP流推送到P2P房间 +curl -X POST "http://127.0.0.1/index/api/addStreamPusherProxy" \ + -d "secret=your_secret" \ + -d "schema=rtsp" \ + -d "vhost=__defaultVhost__" \ + -d "app=live" \ + -d "stream=test" \ + -d "dst_url=webrtc://signaling.server.com:3000/live/room_stream?signaling_protocols=1%26peer_room_id=target_room_id" +``` + +#### URL 参数说明 + +- `signaling_protocols`: 信令协议类型 + - `0`: WHIP/WHEP 模式(默认) + - **协议**: 基于HTTP的标准WebRTC信令协议 + - **应用场景**: SFU(选择性转发单元)模式,适合广播和多人会议 + - `1`: WebSocket P2P 模式 + - **协议**: 基于WebSocket的自定义信令协议 + - **应用场景**: 点对点直连,适合低延迟通话和私人通信 +- `peer_room_id`: P2P模式下的目标房间ID(仅P2P模式需要) + +### 4. WebRTC 代理播放器信息查询 + +#### `/index/api/getWebrtcProxyPlayerInfo` +获取WebRTC代理播放器的连接信息和状态。 + +**请求参数:** +- `secret`: 接口访问密钥 +- `key`: 代理播放器标识符 + + +## WebRTC 相关配置项 + +在 `config.ini` 中的 `[rtc]` 配置段: + +``` ini +[rtc] +#webrtc 信令服务器端口 +signalingPort=3000 +#STUN/TURN服务器端口 +icePort=3478 +#STUN/TURN端口是否使能TURN服务 +enableTurn=1 + +#TURN服务分配端口池 +portRange=50000-65000 + +#ICE传输策略:0=不限制(默认),1=仅支持Relay转发,2=仅支持P2P直连 +iceTransportPolicy=0 + +#STUN/TURN 服务Ice密码 +iceUfrag=ZLMediaKit +icePwd=ZLMediaKit +``` + +## Examples +- [zlm_peerconnection](https://gitee.com/libwebrtc_develop/libwebrtc/tree/feature-zlm/examples/zlm_peerconnection) + 一个基于libwebrtc 实现的zlm p2p 代理拉流简单示例 + +## 注意事项 + +1. **防火墙配置**: 确保 WebRTC 相关端口已开放 + - 信令端口: 3000 (默认) + - STUN/TURN 端口: 3478 (默认) + - TURN Alloc 端口范围: 50000-65000(默认) + +## 暂未实现的功能: +- Webrtc信令服务的安全校验 +- 自定义外部STUN/TURN 服务器的配置 +- webrtc代理推流 diff --git a/webrtc/Utils.hpp b/webrtc/Utils.hpp deleted file mode 100644 index d1386504..00000000 --- a/webrtc/Utils.hpp +++ /dev/null @@ -1,118 +0,0 @@ -/** -ISC License - -Copyright © 2015, Iñaki Baz Castillo - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef MS_UTILS_HPP -#define MS_UTILS_HPP - -#if defined(_WIN32) -#include -#include -#pragma comment (lib, "Ws2_32.lib") -#else -#include -#endif // defined(_WIN32) - -#include // PRIu64, etc -#include // size_t -#include // uint8_t, etc - -namespace Utils { - -class Byte { -public: - /** - * Getters below get value in Host Byte Order. - * Setters below set value in Network Byte Order. - */ - static uint8_t Get1Byte(const uint8_t *data, size_t i); - static uint16_t Get2Bytes(const uint8_t *data, size_t i); - static uint32_t Get3Bytes(const uint8_t *data, size_t i); - static uint32_t Get4Bytes(const uint8_t *data, size_t i); - static uint64_t Get8Bytes(const uint8_t *data, size_t i); - static void Set1Byte(uint8_t *data, size_t i, uint8_t value); - static void Set2Bytes(uint8_t *data, size_t i, uint16_t value); - static void Set3Bytes(uint8_t *data, size_t i, uint32_t value); - static void Set4Bytes(uint8_t *data, size_t i, uint32_t value); - static void Set8Bytes(uint8_t *data, size_t i, uint64_t value); - static uint16_t PadTo4Bytes(uint16_t size); - static uint32_t PadTo4Bytes(uint32_t size); -}; - -/* Inline static methods. */ - -inline uint8_t Byte::Get1Byte(const uint8_t *data, size_t i) { return data[i]; } - -inline uint16_t Byte::Get2Bytes(const uint8_t *data, size_t i) { - return uint16_t{data[i + 1]} | uint16_t{data[i]} << 8; -} - -inline uint32_t Byte::Get3Bytes(const uint8_t *data, size_t i) { - return uint32_t{data[i + 2]} | uint32_t{data[i + 1]} << 8 | uint32_t{data[i]} << 16; -} - -inline uint32_t Byte::Get4Bytes(const uint8_t *data, size_t i) { - return uint32_t{data[i + 3]} | uint32_t{data[i + 2]} << 8 | uint32_t{data[i + 1]} << 16 | - uint32_t{data[i]} << 24; -} - -inline uint64_t Byte::Get8Bytes(const uint8_t *data, size_t i) { - return uint64_t{Byte::Get4Bytes(data, i)} << 32 | Byte::Get4Bytes(data, i + 4); -} - -inline void Byte::Set1Byte(uint8_t *data, size_t i, uint8_t value) { data[i] = value; } - -inline void Byte::Set2Bytes(uint8_t *data, size_t i, uint16_t value) { - data[i + 1] = static_cast(value); - data[i] = static_cast(value >> 8); -} - -inline void Byte::Set3Bytes(uint8_t *data, size_t i, uint32_t value) { - data[i + 2] = static_cast(value); - data[i + 1] = static_cast(value >> 8); - data[i] = static_cast(value >> 16); -} - -inline void Byte::Set4Bytes(uint8_t *data, size_t i, uint32_t value) { - data[i + 3] = static_cast(value); - data[i + 2] = static_cast(value >> 8); - data[i + 1] = static_cast(value >> 16); - data[i] = static_cast(value >> 24); -} - -inline void Byte::Set8Bytes(uint8_t *data, size_t i, uint64_t value) { - data[i + 7] = static_cast(value); - data[i + 6] = static_cast(value >> 8); - data[i + 5] = static_cast(value >> 16); - data[i + 4] = static_cast(value >> 24); - data[i + 3] = static_cast(value >> 32); - data[i + 2] = static_cast(value >> 40); - data[i + 1] = static_cast(value >> 48); - data[i] = static_cast(value >> 56); -} - -inline uint16_t Byte::PadTo4Bytes(uint16_t size) { - // If size is not multiple of 32 bits then pad it. - if (size & 0x03) - return (size & 0xFFFC) + 4; - else - return size; -} - -}// namespace Utils - -#endif diff --git a/webrtc/WebRtcClient.cpp b/webrtc/WebRtcClient.cpp new file mode 100755 index 00000000..3cd40b04 --- /dev/null +++ b/webrtc/WebRtcClient.cpp @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "Network/TcpClient.h" +#include "Common/config.h" +#include "Common/Parser.h" +#include "WebRtcClient.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +// # WebRTCUrl format +// ## whep/whip over http sfu: webrtc://server_host:server_port/{{app}}/{{streamid}} +// ## whep/whip over https sfu: webrtcs://server_host:server_port/{{app}}/{{streamid}} +// ## websocket p2p: webrtc://{{signaling_server_host}}:{{signaling_server_port}}/{{app}}/{{streamid}}?room_id={{peer_room_id}} +// ## websockets p2p: webrtcs://{{signaling_server_host}}:{{signaling_server_port}}/{{app}}/{{streamid}}?room_id={{peer_room_id}} +void WebRTCUrl::parse(const string &strUrl, bool isPlayer) { + DebugL << "url: " << strUrl; + _full_url = strUrl; + auto url = strUrl; + auto pos = url.find("?"); + if (pos != string::npos) { + _params = url.substr(pos + 1); + url.erase(pos); + } + + auto schema_pos = url.find("://"); + if (schema_pos != string::npos) { + auto schema = url.substr(0, schema_pos); + _is_ssl = strcasecmp(schema.data(), "webrtcs") == 0; + } else { + schema_pos = -3; + } + // set default port + _port = _is_ssl ? 443 : 80; + auto split_vec = split(url.substr(schema_pos + 3), "/"); + if (split_vec.size() > 0) { + splitUrl(split_vec[0], _host, _port); + _vhost = _host; + if (_vhost == "localhost" || isIP(_vhost.data())) { + // 如果访问的是localhost或ip,那么则为默认虚拟主机 + _vhost = DEFAULT_VHOST; + } + } + if (split_vec.size() > 1) { + _app = split_vec[1]; + } + if (split_vec.size() > 2) { + string stream_id; + for (size_t i = 2; i < split_vec.size(); ++i) { + stream_id.append(split_vec[i] + "/"); + } + if (stream_id.back() == '/') { + stream_id.pop_back(); + } + _stream = stream_id; + } + + // for vhost + auto kv = Parser::parseArgs(_params); + auto it = kv.find(VHOST_KEY); + if (it != kv.end()) { + _vhost = it->second; + } + + GET_CONFIG(bool, enableVhost, General::kEnableVhost); + if (!enableVhost || _vhost.empty()) { + // 如果关闭虚拟主机或者虚拟主机为空,则设置虚拟主机为默认 + _vhost = DEFAULT_VHOST; + } + + // for peer_room_id + it = kv.find("peer_room_id"); + if (it != kv.end()) { + _peer_room_id = it->second; + } + + it = kv.find("signaling_protocols"); + if (it != kv.end()) { + _signaling_protocols = (WebRtcTransport::SignalingProtocols)(stoi(it->second)); + } + + auto suffix = _host + ":" + to_string(_port); + suffix += (isPlayer ? "/index/api/whep" : "/index/api/whip"); + suffix += "?app=" + _app + "&stream=" + _stream; + if (!_params.empty()) { + suffix += "&" + _params; + } + if (_is_ssl) { + _negotiate_url = StrPrinter << "https://" << suffix << endl; + } else { + _negotiate_url = StrPrinter << "http://" << suffix << endl; + } +} + +//////////// WebRtcClient ////////////////////////// + +WebRtcClient::WebRtcClient(toolkit::EventPoller::Ptr poller) { + DebugL; + _poller = poller ? std::move(poller) : EventPollerPool::Instance().getPoller(); +} + +WebRtcClient::~WebRtcClient() { + doBye(); + DebugL; +} + +void WebRtcClient::startConnect() { + DebugL; + doNegotiate(); +} + +void WebRtcClient::connectivityCheck() { + DebugL; + return _transport->connectivityCheckForSFU(); +} + +void WebRtcClient::onNegotiateFinish() { + DebugL; + _is_negotiate_finished = true; + if (WebRtcTransport::SignalingProtocols::WEBSOCKET == _url._signaling_protocols) { + // P2P模式需要gathering candidates + gatheringCandidate(_peer->getIceServer()); + } else if (WebRtcTransport::SignalingProtocols::WHEP_WHIP == _url._signaling_protocols) { + // SFU模式不会存在IP不通的情况, answer中就携带了candidates, 直接进行connectivityCheck + connectivityCheck(); + } +} + +void WebRtcClient::doNegotiate() { + DebugL; + switch (_url._signaling_protocols) { + case WebRtcTransport::SignalingProtocols::WHEP_WHIP: return doNegotiateWhepOrWhip(); + case WebRtcTransport::SignalingProtocols::WEBSOCKET: return doNegotiateWebsocket(); + default: throw std::invalid_argument(StrPrinter << "not support signaling_protocols: " << (int)_url._signaling_protocols); + } +} + +void WebRtcClient::doNegotiateWhepOrWhip() { + DebugL << _url._negotiate_url; + + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + auto offer_sdp = _transport->createOfferSdp(); + DebugL << "send offer:\n" << offer_sdp; + + _negotiate = make_shared(); + _negotiate->setMethod("POST"); + _negotiate->setBody(std::move(offer_sdp)); + _negotiate->startRequester(_url._negotiate_url, [weak_self](const toolkit::SockException &ex, const Parser &response) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + if (ex) { + WarnL << "network err:" << ex; + strong_self->onResult(ex); + return; + } + + DebugL << "status:" << response.status() << "\r\n" + << "Location:\r\n" + << response.getHeader()["Location"] << "\r\nrecv answer:\n" + << response.content(); + strong_self->_url._delete_url = response.getHeader()["Location"]; + if ("201" != response.status()) { + strong_self->onResult(SockException(Err_other, response.content())); + return; + } + strong_self->_transport->setAnswerSdp(response.content()); + strong_self->onNegotiateFinish(); + }, getTimeOutSec()); +} + +void WebRtcClient::doNegotiateWebsocket() { + DebugL; +#if 0 + //TODO: 当前暂将每一路呼叫都使用一个独立的peer_connection,不复用 + _peer = getWebrtcRoomKeeper(_url._host, _url._port); + if (_peer) { + checkIn(); + return; + } +#endif + + // 未注册的,先增加注册流程,并在此次播放结束后注销 + InfoL << (StrPrinter << "register to signaling server " << _url._host << "::" << _url._port << " first"); + auto room_id = "ringing_" + makeRandStr(16); + _peer = make_shared(_url._host, _url._port, _url._is_ssl, room_id); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + _peer->setOnConnect([weak_self](const SockException &ex) { + if (auto strong_self = weak_self.lock()) { + if (ex) { + strong_self->onResult(ex); + return; + } + + auto cb = [weak_self](const SockException &ex, const string &key) { + if (auto strong_self = weak_self.lock()) { + strong_self->checkIn(); + } + }; + strong_self->_peer->regist(cb); + } + }); + _peer->connect(); +} + +void WebRtcClient::checkIn() { + DebugL; + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + auto tuple = MediaTuple(_url._vhost, _url._app, _url._stream, _url._params); + _peer->checkIn(_url._peer_room_id, tuple, _transport->getIdentifier(), _transport->createOfferSdp(), isPlayer(), + [weak_self](const SockException &ex, const std::string &answer) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + if (ex) { + WarnL << "network err:" << ex; + strong_self->onResult(ex); + return; + } + + strong_self->_transport->setAnswerSdp(answer); + strong_self->onNegotiateFinish(); + }, getTimeOutSec()); +} + +void WebRtcClient::checkOut() { + DebugL; + auto tuple = MediaTuple(_url._vhost, _url._app, _url._stream); + if (_peer) { + _peer->checkOut(_url._peer_room_id); + _peer->unregist([](const SockException &ex) {}); + } +} + +void WebRtcClient::candidate(const std::string &candidate, const std::string &ufrag, const std::string &pwd) { + _peer->candidate(_transport->getIdentifier(), candidate, ufrag, pwd); +} + +void WebRtcClient::gatheringCandidate(IceServerInfo::Ptr ice_server) { + DebugL; + std::weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); + _transport->gatheringCandidate(ice_server, [weak_self](const std::string& transport_identifier, const std::string& candidate, + const std::string& ufrag, const std::string& pwd) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + strong_self->candidate(candidate, ufrag, pwd); + }); +} + +void WebRtcClient::doBye() { + DebugL; + if (!_is_negotiate_finished) { + return; + } + + switch (_url._signaling_protocols) { + case WebRtcTransport::SignalingProtocols::WHEP_WHIP: return doByeWhepOrWhip(); + case WebRtcTransport::SignalingProtocols::WEBSOCKET: return checkOut(); + default: throw std::invalid_argument(StrPrinter << "not support signaling_protocols: " << (int)_url._signaling_protocols); + } + _is_negotiate_finished = false; +} + +void WebRtcClient::doByeWhepOrWhip() { + DebugL; + if (!_negotiate) { + return; + } + _negotiate->setMethod("DELETE"); + _negotiate->setBody(""); + _negotiate->startRequester(_url._delete_url, [](const toolkit::SockException &ex, const Parser &response) { + if (ex) { + WarnL << "network err:" << ex; + return; + } + DebugL << "status:" << response.status(); + }, getTimeOutSec()); +} + +float WebRtcClient::getTimeOutSec() { + GET_CONFIG(uint32_t, timeout, Rtc::kTimeOutSec); + if (timeout <= 0) { + WarnL << "config rtc. " << Rtc::kTimeOutSec << ": " << timeout << " not vaild"; + return 5.0; + } + return (float)timeout; +} + +} /* namespace mediakit */ diff --git a/webrtc/WebRtcClient.h b/webrtc/WebRtcClient.h new file mode 100755 index 00000000..7110f849 --- /dev/null +++ b/webrtc/WebRtcClient.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLMEDIAKIT_WEBRTC_CLIENT_H +#define ZLMEDIAKIT_WEBRTC_CLIENT_H + +#include "Network/Socket.h" +#include "Poller/Timer.h" +#include "Util/TimeTicker.h" +#include "Http/HttpRequester.h" +#include "Sdp.h" +#include "WebRtcTransport.h" +#include "WebRtcSignalingPeer.h" +#include +#include + +namespace mediakit { + +// 解析webrtc 信令url的工具类 +class WebRTCUrl { +public: + bool _is_ssl; + std::string _full_url; + std::string _negotiate_url; // for whep or whip + std::string _delete_url; // for whep or whip + std::string _target_secret; + std::string _params; + std::string _host; + uint16_t _port; + std::string _vhost; + std::string _app; + std::string _stream; + WebRtcTransport::SignalingProtocols _signaling_protocols = WebRtcTransport::SignalingProtocols::WHEP_WHIP; + std::string _peer_room_id; // peer room_id + +public: + void parse(const std::string &url, bool isPlayer); + +private: +}; + +namespace Rtc { +typedef enum { + Signaling_Invalid = -1, + Signaling_WHEP_WHIP = 0, + Signaling_WEBSOCKET = 1, +} eSignalingProtocols; +} // namespace Rtc + +// 实现了webrtc代理功能 +class WebRtcClient : public std::enable_shared_from_this { +public: + using Ptr = std::shared_ptr; + + WebRtcClient(toolkit::EventPoller::Ptr poller); + virtual ~WebRtcClient(); + + const toolkit::EventPoller::Ptr &getPoller() const { return _poller; } + void setPoller(toolkit::EventPoller::Ptr poller) { _poller = std::move(poller); } + + // 获取WebRTC transport,用于API查询 + const WebRtcTransport::Ptr &getWebRtcTransport() const { return _transport; } + +protected: + virtual bool isPlayer() = 0; + virtual void startConnect(); + virtual void onResult(const toolkit::SockException &ex) = 0; + virtual void onNegotiateFinish(); + virtual float getTimeOutSec(); + + void doNegotiate(); + void doNegotiateWebsocket(); + void doNegotiateWhepOrWhip(); + void checkIn(); + void doBye(); + void doByeWhepOrWhip(); + void checkOut(); + + void gatheringCandidate(IceServerInfo::Ptr ice_server); + void connectivityCheck(); + void candidate(const std::string &candidate, const std::string &ufrag, const std::string &pwd); + +protected: + toolkit::EventPoller::Ptr _poller; + + // for _negotiate_sdp + WebRTCUrl _url; + HttpRequester::Ptr _negotiate = nullptr; + WebRtcSignalingPeer::Ptr _peer = nullptr; + WebRtcTransport::Ptr _transport = nullptr; + bool _is_negotiate_finished = false; + +private: + std::map _socket_map; +}; + +} /*namespace mediakit */ +#endif /* ZLMEDIAKIT_WEBRTC_CLIENT_H */ diff --git a/webrtc/WebRtcEchoTest.cpp b/webrtc/WebRtcEchoTest.cpp index fcbd1266..f74a0187 100644 --- a/webrtc/WebRtcEchoTest.cpp +++ b/webrtc/WebRtcEchoTest.cpp @@ -10,6 +10,8 @@ #include "WebRtcEchoTest.h" +using namespace toolkit; + namespace mediakit { WebRtcEchoTest::Ptr WebRtcEchoTest::create(const EventPoller::Ptr &poller) { diff --git a/webrtc/WebRtcEchoTest.h b/webrtc/WebRtcEchoTest.h index e6249ff2..fffb5292 100644 --- a/webrtc/WebRtcEchoTest.h +++ b/webrtc/WebRtcEchoTest.h @@ -18,7 +18,7 @@ namespace mediakit { class WebRtcEchoTest : public WebRtcTransportImp { public: using Ptr = std::shared_ptr; - static Ptr create(const EventPoller::Ptr &poller); + static Ptr create(const toolkit::EventPoller::Ptr &poller); protected: ///////WebRtcTransportImp override/////// @@ -31,7 +31,7 @@ protected: void onBeforeEncryptRtcp(const char *buf, int &len, void *ctx) override {}; private: - WebRtcEchoTest(const EventPoller::Ptr &poller); + WebRtcEchoTest(const toolkit::EventPoller::Ptr &poller); }; }// namespace mediakit diff --git a/webrtc/WebRtcPlayer.cpp b/webrtc/WebRtcPlayer.cpp index a78dc881..4f11f17d 100644 --- a/webrtc/WebRtcPlayer.cpp +++ b/webrtc/WebRtcPlayer.cpp @@ -1,330 +1,338 @@ -/* - * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. - * - * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). - * - * Use of this source code is governed by MIT-like license that can be found in the - * LICENSE file in the root of the source tree. All contributing project authors - * may be found in the AUTHORS file in the root of the source tree. - */ - -#include "WebRtcPlayer.h" - -#include "Common/config.h" -#include "Extension/Factory.h" -#include "Util/base64.h" - -using namespace std; - -namespace mediakit { - -namespace Rtc { -#define RTC_FIELD "rtc." -const string kBfilter = RTC_FIELD "bfilter"; -static onceToken token([]() { mINI::Instance()[kBfilter] = 0; }); -} // namespace Rtc - -H264BFrameFilter::H264BFrameFilter() - : _last_seq(0) - , _last_stamp(0) - , _first_packet(true) {} - -RtpPacket::Ptr H264BFrameFilter::processPacket(const RtpPacket::Ptr &packet) { - if (!packet) { - return nullptr; - } - - if (isH264BFrame(packet)) { - return nullptr; - } - - auto cur_stamp = packet->getStamp(); - auto cur_seq = packet->getSeq(); - - if (_first_packet) { - _first_packet = false; - _last_seq = cur_seq; - _last_stamp = cur_stamp; - } - - // 处理时间戳连续性问题 - if (cur_stamp < _last_stamp) { - return nullptr; - } - _last_stamp = cur_stamp; - - // 处理 seq 连续性问题 - if (cur_seq > _last_seq + 4) { - RtpHeader *header = packet->getHeader(); - _last_seq = (_last_seq + 1) & 0xFFFF; - header->seq = htons(_last_seq); - } - - return packet; -} - -bool H264BFrameFilter::isH264BFrame(const RtpPacket::Ptr &packet) const { - uint8_t *payload = packet->getPayload(); - size_t payload_size = packet->getPayloadSize(); - - if (payload_size < 1) { - return false; - } - - uint8_t nal_unit_type = payload[0] & 0x1F; - switch (nal_unit_type) { - case 24: // STAP-A - return handleStapA(payload, payload_size); - case 28: // FU-A - return handleFua(payload, payload_size); - default: - if (nal_unit_type < 24) { - return isBFrameByNalType(nal_unit_type, payload + 1, payload_size - 1); - } - return false; - } -} - -bool H264BFrameFilter::handleStapA(const uint8_t *payload, size_t payload_size) const { - size_t offset = 1; - while (offset + 2 <= payload_size) { - uint16_t nalu_size = (payload[offset] << 8) | payload[offset + 1]; - offset += 2; - if (offset + nalu_size > payload_size || nalu_size < 1) { - return false; - } - uint8_t original_nal_type = payload[offset] & 0x1F; - if (original_nal_type < 24) { - if (isBFrameByNalType(original_nal_type, payload + offset + 1, nalu_size - 1)) { - return true; - } - } - offset += nalu_size; - } - return false; -} - -bool H264BFrameFilter::handleFua(const uint8_t *payload, size_t payload_size) const { - if (payload_size < 2) { - return false; - } - uint8_t fu_header = payload[1]; - uint8_t original_nal_type = fu_header & 0x1F; - bool start_bit = fu_header & 0x80; - if (start_bit) { - return isBFrameByNalType(original_nal_type, payload + 2, payload_size - 2); - } - return false; -} - -bool H264BFrameFilter::isBFrameByNalType(uint8_t nal_type, const uint8_t *data, size_t size) const { - if (size < 1) { - return false; - } - - if (nal_type != NAL_NIDR && nal_type != NAL_PARTITION_A && nal_type != NAL_PARTITION_B && nal_type != NAL_PARTITION_C) { - return false; - } - - uint8_t slice_type = extractSliceType(data, size); - return slice_type == H264SliceTypeB || slice_type == H264SliceTypeB1; -} - -int H264BFrameFilter::decodeExpGolomb(const uint8_t *data, size_t size, size_t &bitPos) const { - if (bitPos >= size * 8) - return -1; - - int leadingZeroBits = 0; - while (bitPos < size * 8 && !getBit(data, bitPos++)) { - leadingZeroBits++; - } - - int result = (1 << leadingZeroBits) - 1; - for (int i = 0; i < leadingZeroBits; i++) { - if (bitPos < size * 8) { - result += getBit(data, bitPos++) << (leadingZeroBits - i - 1); - } - } - - return result; -} - -int H264BFrameFilter::getBit(const uint8_t *data, size_t pos) const { - size_t byteIndex = pos / 8; - size_t bitOffset = pos % 8; - uint8_t byte = data[byteIndex]; - return (byte >> (7 - bitOffset)) & 0x01; -} - -uint8_t H264BFrameFilter::extractSliceType(const uint8_t *data, size_t size) const { - size_t bitPos = 0; - int first_mb_in_slice = decodeExpGolomb(data, size, bitPos); - int slice_type = decodeExpGolomb(data, size, bitPos); - - if (slice_type >= 0 && slice_type <= 9) { - return static_cast(slice_type); - } - return -1; -} - -WebRtcPlayer::Ptr WebRtcPlayer::create(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info) { - WebRtcPlayer::Ptr ret(new WebRtcPlayer(poller, src, info), [](WebRtcPlayer *ptr) { - ptr->onDestory(); - delete ptr; - }); - ret->onCreate(); - return ret; -} - -WebRtcPlayer::WebRtcPlayer(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info) - : WebRtcTransportImp(poller) { - _media_info = info; - _play_src = src; - CHECK(src); - - GET_CONFIG(bool, direct_proxy, Rtsp::kDirectProxy); - _send_config_frames_once = direct_proxy; - - GET_CONFIG(bool, enable, Rtc::kBfilter); - _bfliter_flag = enable; - _is_h264 = false; - _bfilter = std::make_shared(); -} - -void WebRtcPlayer::onStartWebRTC() { - auto playSrc = _play_src.lock(); - if (!playSrc) { - onShutdown(SockException(Err_shutdown, "rtsp media source was shutdown")); - return; - } - WebRtcTransportImp::onStartWebRTC(); - if (canSendRtp()) { - playSrc->pause(false); - _reader = playSrc->getRing()->attach(getPoller(), true); - weak_ptr weak_self = static_pointer_cast(shared_from_this()); - weak_ptr weak_session = static_pointer_cast(getSession()); - _reader->setGetInfoCB([weak_session]() { - Any ret; - ret.set(static_pointer_cast(weak_session.lock())); - return ret; - }); - _reader->setReadCB([weak_self](const RtspMediaSource::RingDataType &pkt) { - auto strong_self = weak_self.lock(); - if (!strong_self) { - return; - } - - if (strong_self->_send_config_frames_once && !pkt->empty()) { - const auto &first_rtp = pkt->front(); - strong_self->sendConfigFrames(first_rtp->getSeq(), first_rtp->sample_rate, first_rtp->getStamp(), first_rtp->ntp_stamp); - strong_self->_send_config_frames_once = false; - } - - size_t i = 0; - pkt->for_each([&](const RtpPacket::Ptr &rtp) { - if (strong_self->_bfliter_flag) { - if (TrackVideo == rtp->type && strong_self->_is_h264) { - auto rtp_filter = strong_self->_bfilter->processPacket(rtp); - if (rtp_filter) { - strong_self->onSendRtp(rtp_filter, ++i == pkt->size()); - } - } else { - strong_self->onSendRtp(rtp, ++i == pkt->size()); - } - } else { - strong_self->onSendRtp(rtp, ++i == pkt->size()); - } - }); - }); - _reader->setDetachCB([weak_self]() { - auto strong_self = weak_self.lock(); - if (!strong_self) { - return; - } - strong_self->onShutdown(SockException(Err_shutdown, "rtsp ring buffer detached")); - }); - - _reader->setMessageCB([weak_self](const toolkit::Any &data) { - auto strong_self = weak_self.lock(); - if (!strong_self) { - return; - } - if (data.is()) { - auto &buffer = data.get(); - // PPID 51: 文本string [AUTO-TRANSLATED:69a8cf81] - // PPID 51: Text string - // PPID 53: 二进制 [AUTO-TRANSLATED:faf00c3e] - // PPID 53: Binary - strong_self->sendDatachannel(0, 51, buffer.data(), buffer.size()); - } else { - WarnL << "Send unknown message type to webrtc player: " << data.type_name(); - } - }); - } -} -void WebRtcPlayer::onDestory() { - auto duration = getDuration(); - auto bytes_usage = getBytesUsage(); - // 流量统计事件广播 [AUTO-TRANSLATED:6b0b1234] - // Traffic statistics event broadcast - GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); - if (_reader && getSession()) { - WarnL << "RTC播放器(" << _media_info.shortUrl() << ")结束播放,耗时(s):" << duration; - if (bytes_usage >= iFlowThreshold * 1024) { - NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, bytes_usage, duration, true, *getSession()); - } - } - WebRtcTransportImp::onDestory(); -} - -void WebRtcPlayer::onRtcConfigure(RtcConfigure &configure) const { - auto playSrc = _play_src.lock(); - if (!playSrc) { - return; - } - WebRtcTransportImp::onRtcConfigure(configure); - // 这是播放 [AUTO-TRANSLATED:d93c019e] - // This is playing - configure.audio.direction = configure.video.direction = RtpDirection::sendonly; - configure.setPlayRtspInfo(playSrc->getSdp()); -} - -void WebRtcPlayer::sendConfigFrames(uint32_t before_seq, uint32_t sample_rate, uint32_t timestamp, uint64_t ntp_timestamp) { - auto play_src = _play_src.lock(); - if (!play_src) { - return; - } - SdpParser parser(play_src->getSdp()); - auto video_sdp = parser.getTrack(TrackVideo); - if (!video_sdp) { - return; - } - auto video_track = dynamic_pointer_cast(Factory::getTrackBySdp(video_sdp)); - if (!video_track) { - return; - } - _is_h264 = video_track->getCodecId() == CodecH264; - auto frames = video_track->getConfigFrames(); - if (frames.empty()) { - return; - } - auto encoder = mediakit::Factory::getRtpEncoderByCodecId(video_track->getCodecId(), 0); - if (!encoder) { - return; - } - - GET_CONFIG(uint32_t, video_mtu, Rtp::kVideoMtuSize); - encoder->setRtpInfo(0, video_mtu, sample_rate, 0, 0, 0); - - auto seq = before_seq - frames.size(); - for (const auto &frame : frames) { - auto rtp = encoder->getRtpInfo().makeRtp(TrackVideo, frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize(), false, 0); - auto header = rtp->getHeader(); - header->seq = htons(seq++); - header->stamp = htonl(timestamp); - rtp->ntp_stamp = ntp_timestamp; - onSendRtp(rtp, false); - } -} - +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "WebRtcPlayer.h" + +#include "Common/config.h" +#include "Extension/Factory.h" +#include "Util/base64.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +namespace Rtc { +#define RTC_FIELD "rtc." +const string kBfilter = RTC_FIELD "bfilter"; +static onceToken token([]() { mINI::Instance()[kBfilter] = 0; }); +} // namespace Rtc + +H264BFrameFilter::H264BFrameFilter() + : _last_seq(0) + , _last_stamp(0) + , _first_packet(true) {} + +RtpPacket::Ptr H264BFrameFilter::processPacket(const RtpPacket::Ptr &packet) { + if (!packet) { + return nullptr; + } + + if (isH264BFrame(packet)) { + return nullptr; + } + + auto cur_stamp = packet->getStamp(); + auto cur_seq = packet->getSeq(); + + if (_first_packet) { + _first_packet = false; + _last_seq = cur_seq; + _last_stamp = cur_stamp; + } + + // 处理时间戳连续性问题 + if (cur_stamp < _last_stamp) { + return nullptr; + } + _last_stamp = cur_stamp; + + // 处理 seq 连续性问题 + if (cur_seq > _last_seq + 4) { + RtpHeader *header = packet->getHeader(); + _last_seq = (_last_seq + 1) & 0xFFFF; + header->seq = htons(_last_seq); + } + + return packet; +} + +bool H264BFrameFilter::isH264BFrame(const RtpPacket::Ptr &packet) const { + uint8_t *payload = packet->getPayload(); + size_t payload_size = packet->getPayloadSize(); + + if (payload_size < 1) { + return false; + } + + uint8_t nal_unit_type = payload[0] & 0x1F; + switch (nal_unit_type) { + case 24: // STAP-A + return handleStapA(payload, payload_size); + case 28: // FU-A + return handleFua(payload, payload_size); + default: + if (nal_unit_type < 24) { + return isBFrameByNalType(nal_unit_type, payload + 1, payload_size - 1); + } + return false; + } +} + +bool H264BFrameFilter::handleStapA(const uint8_t *payload, size_t payload_size) const { + size_t offset = 1; + while (offset + 2 <= payload_size) { + uint16_t nalu_size = (payload[offset] << 8) | payload[offset + 1]; + offset += 2; + if (offset + nalu_size > payload_size || nalu_size < 1) { + return false; + } + uint8_t original_nal_type = payload[offset] & 0x1F; + if (original_nal_type < 24) { + if (isBFrameByNalType(original_nal_type, payload + offset + 1, nalu_size - 1)) { + return true; + } + } + offset += nalu_size; + } + return false; +} + +bool H264BFrameFilter::handleFua(const uint8_t *payload, size_t payload_size) const { + if (payload_size < 2) { + return false; + } + uint8_t fu_header = payload[1]; + uint8_t original_nal_type = fu_header & 0x1F; + bool start_bit = fu_header & 0x80; + if (start_bit) { + return isBFrameByNalType(original_nal_type, payload + 2, payload_size - 2); + } + return false; +} + +bool H264BFrameFilter::isBFrameByNalType(uint8_t nal_type, const uint8_t *data, size_t size) const { + if (size < 1) { + return false; + } + + if (nal_type != NAL_NIDR && nal_type != NAL_PARTITION_A && nal_type != NAL_PARTITION_B && nal_type != NAL_PARTITION_C) { + return false; + } + + uint8_t slice_type = extractSliceType(data, size); + return slice_type == H264SliceTypeB || slice_type == H264SliceTypeB1; +} + +int H264BFrameFilter::decodeExpGolomb(const uint8_t *data, size_t size, size_t &bitPos) const { + if (bitPos >= size * 8) + return -1; + + int leadingZeroBits = 0; + while (bitPos < size * 8 && !getBit(data, bitPos++)) { + leadingZeroBits++; + } + + int result = (1 << leadingZeroBits) - 1; + for (int i = 0; i < leadingZeroBits; i++) { + if (bitPos < size * 8) { + result += getBit(data, bitPos++) << (leadingZeroBits - i - 1); + } + } + + return result; +} + +int H264BFrameFilter::getBit(const uint8_t *data, size_t pos) const { + size_t byteIndex = pos / 8; + size_t bitOffset = pos % 8; + uint8_t byte = data[byteIndex]; + return (byte >> (7 - bitOffset)) & 0x01; +} + +uint8_t H264BFrameFilter::extractSliceType(const uint8_t *data, size_t size) const { + size_t bitPos = 0; + int first_mb_in_slice = decodeExpGolomb(data, size, bitPos); + int slice_type = decodeExpGolomb(data, size, bitPos); + + if (slice_type >= 0 && slice_type <= 9) { + return static_cast(slice_type); + } + return -1; +} + +WebRtcPlayer::Ptr WebRtcPlayer::create(const EventPoller::Ptr &poller, + const RtspMediaSource::Ptr &src, + const MediaInfo &info, + WebRtcTransport::Role role, + WebRtcTransport::SignalingProtocols signaling_protocols) { + WebRtcPlayer::Ptr ret(new WebRtcPlayer(poller, src, info), [](WebRtcPlayer *ptr) { + ptr->onDestory(); + delete ptr; + }); + ret->setRole(role); + ret->setSignalingProtocols(signaling_protocols); + ret->onCreate(); + return ret; +} + +WebRtcPlayer::WebRtcPlayer(const EventPoller::Ptr &poller, + const RtspMediaSource::Ptr &src, + const MediaInfo &info) : WebRtcTransportImp(poller) { + _media_info = info; + _play_src = src; + CHECK(src); + + GET_CONFIG(bool, direct_proxy, Rtsp::kDirectProxy); + _send_config_frames_once = direct_proxy; + + GET_CONFIG(bool, enable, Rtc::kBfilter); + _bfliter_flag = enable; + _is_h264 = false; + _bfilter = std::make_shared(); +} + +void WebRtcPlayer::onStartWebRTC() { + auto playSrc = _play_src.lock(); + if (!playSrc) { + onShutdown(SockException(Err_shutdown, "rtsp media source was shutdown")); + return; + } + WebRtcTransportImp::onStartWebRTC(); + if (canSendRtp()) { + playSrc->pause(false); + _reader = playSrc->getRing()->attach(getPoller(), true); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + weak_ptr weak_session = static_pointer_cast(getSession()); + _reader->setGetInfoCB([weak_session]() { + Any ret; + ret.set(static_pointer_cast(weak_session.lock())); + return ret; + }); + _reader->setReadCB([weak_self](const RtspMediaSource::RingDataType &pkt) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + + if (strong_self->_send_config_frames_once && !pkt->empty()) { + const auto &first_rtp = pkt->front(); + strong_self->sendConfigFrames(first_rtp->getSeq(), first_rtp->sample_rate, first_rtp->getStamp(), first_rtp->ntp_stamp); + strong_self->_send_config_frames_once = false; + } + + size_t i = 0; + pkt->for_each([&](const RtpPacket::Ptr &rtp) { + if (strong_self->_bfliter_flag) { + if (TrackVideo == rtp->type && strong_self->_is_h264) { + auto rtp_filter = strong_self->_bfilter->processPacket(rtp); + if (rtp_filter) { + strong_self->onSendRtp(rtp_filter, ++i == pkt->size()); + } + } else { + strong_self->onSendRtp(rtp, ++i == pkt->size()); + } + } else { + strong_self->onSendRtp(rtp, ++i == pkt->size()); + } + }); + }); + _reader->setDetachCB([weak_self]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + strong_self->onShutdown(SockException(Err_shutdown, "rtsp ring buffer detached")); + }); + + _reader->setMessageCB([weak_self](const toolkit::Any &data) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + if (data.is()) { + auto &buffer = data.get(); + // PPID 51: 文本string [AUTO-TRANSLATED:69a8cf81] + // PPID 51: Text string + // PPID 53: 二进制 [AUTO-TRANSLATED:faf00c3e] + // PPID 53: Binary + strong_self->sendDatachannel(0, 51, buffer.data(), buffer.size()); + } else { + WarnL << "Send unknown message type to webrtc player: " << data.type_name(); + } + }); + } +} +void WebRtcPlayer::onDestory() { + auto duration = getDuration(); + auto bytes_usage = getBytesUsage(); + // 流量统计事件广播 [AUTO-TRANSLATED:6b0b1234] + // Traffic statistics event broadcast + GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); + if (_reader && getSession()) { + WarnL << "RTC播放器(" << _media_info.shortUrl() << ")结束播放,耗时(s):" << duration; + if (bytes_usage >= iFlowThreshold * 1024) { + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, bytes_usage, duration, true, *getSession()); + } + } + WebRtcTransportImp::onDestory(); +} + +void WebRtcPlayer::onRtcConfigure(RtcConfigure &configure) const { + auto playSrc = _play_src.lock(); + if (!playSrc) { + return; + } + WebRtcTransportImp::onRtcConfigure(configure); + // 这是播放 [AUTO-TRANSLATED:d93c019e] + // This is playing + configure.audio.direction = configure.video.direction = RtpDirection::sendonly; + configure.setPlayRtspInfo(playSrc->getSdp()); +} + +void WebRtcPlayer::sendConfigFrames(uint32_t before_seq, uint32_t sample_rate, uint32_t timestamp, uint64_t ntp_timestamp) { + auto play_src = _play_src.lock(); + if (!play_src) { + return; + } + SdpParser parser(play_src->getSdp()); + auto video_sdp = parser.getTrack(TrackVideo); + if (!video_sdp) { + return; + } + auto video_track = dynamic_pointer_cast(Factory::getTrackBySdp(video_sdp)); + if (!video_track) { + return; + } + _is_h264 = video_track->getCodecId() == CodecH264; + auto frames = video_track->getConfigFrames(); + if (frames.empty()) { + return; + } + auto encoder = mediakit::Factory::getRtpEncoderByCodecId(video_track->getCodecId(), 0); + if (!encoder) { + return; + } + + GET_CONFIG(uint32_t, video_mtu, Rtp::kVideoMtuSize); + encoder->setRtpInfo(0, video_mtu, sample_rate, 0, 0, 0); + + auto seq = before_seq - frames.size(); + for (const auto &frame : frames) { + auto rtp = encoder->getRtpInfo().makeRtp(TrackVideo, frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize(), false, 0); + auto header = rtp->getHeader(); + header->seq = htons(seq++); + header->stamp = htonl(timestamp); + rtp->ntp_stamp = ntp_timestamp; + onSendRtp(rtp, false); + } +} + }// namespace mediakit \ No newline at end of file diff --git a/webrtc/WebRtcPlayer.h b/webrtc/WebRtcPlayer.h index 4105b664..4e47b8ff 100644 --- a/webrtc/WebRtcPlayer.h +++ b/webrtc/WebRtcPlayer.h @@ -1,164 +1,165 @@ -/* - * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. - * - * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). - * - * Use of this source code is governed by MIT-like license that can be found in the - * LICENSE file in the root of the source tree. All contributing project authors - * may be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef ZLMEDIAKIT_WEBRTCPLAYER_H -#define ZLMEDIAKIT_WEBRTCPLAYER_H - -#include "Rtsp/RtspMediaSource.h" -#include "WebRtcTransport.h" - -namespace mediakit { -/** - * @brief H.264 B 帧过滤器 - * 用于从 H.264 RTP 流中移除 B 帧 - */ -class H264BFrameFilter { -public: - /** - * ISO_IEC_14496-10-AVC-2012 - * Table 7-6 – Name association to slice_type - */ - enum H264SliceType { - H264SliceTypeP = 0, - H264SliceTypeB = 1, - H264SliceTypeI = 2, - H264SliceTypeSP = 3, - H264SliceTypeSI = 4, - H264SliceTypeP1 = 5, - H264SliceTypeB1 = 6, - H264SliceTypeI1 = 7, - H264SliceTypeSP1 = 8, - H264SliceTypeSI1 = 9, - }; - - enum H264NALUType { - NAL_NIDR = 1, - NAL_PARTITION_A = 2, - NAL_PARTITION_B = 3, - NAL_PARTITION_C = 4, - NAL_IDR = 5, - }; - - H264BFrameFilter(); - - ~H264BFrameFilter() = default; - - /** - * @brief 处理单个 RTP 包,移除 B 帧 - * @param packet 输入的 RTP 包 - * @return 如果不是 B 帧则返回原包,否则返回 nullptr - */ - RtpPacket::Ptr processPacket(const RtpPacket::Ptr &packet); - -private: - /** - * @brief 判断 RTP 包是否包含 H.264 的 B 帧 - * @param packet RTP 包 - * @return 如果是 B 帧返回 true,否则返回 false - */ - bool isH264BFrame(const RtpPacket::Ptr &packet) const; - - /** - * @brief 根据 NAL 类型和数据判断是否是 B 帧 - * @param nal_type NAL 单元类型 - * @param data NAL 单元数据(不含 NAL 头) - * @param size 数据大小 - * @return 如果是 B 帧返回 true,否则返回 false - */ - bool isBFrameByNalType(uint8_t nal_type, const uint8_t *data, size_t size) const; - - /** - * @brief 解析指数哥伦布编码 - * @param data 数据缓冲区 - * @param size 缓冲区大小 - * @param bits_offset 位偏移量 - * @return 解析出的数值 - */ - int decodeExpGolomb(const uint8_t *data, size_t size, size_t &bitPos) const; - - /** - * @brief 从比特流中读取位 - * @param data 数据缓冲区 - * @param size 缓冲区大小 - * @return 读取的位值(0 或 1) - */ - int getBit(const uint8_t *data, size_t size) const; - - /** - * @brief 提取切片类型值 - * @param data 数据缓冲区 - * @param size 缓冲区大小 - * @return 切片类型值 - */ - uint8_t extractSliceType(const uint8_t *data, size_t size) const; - - /** - * @brief 处理FU-A分片 - * @param payload 数据缓冲区 - * @param payload_size 缓冲区大小 - * @return 如果是 B 帧返回 true,否则返回 false - */ - bool handleFua(const uint8_t *payload, size_t payload_size) const; - - /** - * @brief 处理 STAP-A 组合包 - * @param payload 数据缓冲区 - * @param payload_size 缓冲区大小 - * @return 如果是 B 帧返回 true,否则返回 false - */ - bool handleStapA(const uint8_t *payload, size_t payload_size) const; - - -private: - uint16_t _last_seq; // 维护输出流的序列号 - uint32_t _last_stamp; // 维护输出流的时间戳 - bool _first_packet; // 是否是第一个包的标记 -}; - -class WebRtcPlayer : public WebRtcTransportImp { -public: - using Ptr = std::shared_ptr; - static Ptr create(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info); - MediaInfo getMediaInfo() { return _media_info; } - -protected: - ///////WebRtcTransportImp override/////// - void onStartWebRTC() override; - void onDestory() override; - void onRtcConfigure(RtcConfigure &configure) const override; - -private: - WebRtcPlayer(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info); - - void sendConfigFrames(uint32_t before_seq, uint32_t sample_rate, uint32_t timestamp, uint64_t ntp_timestamp); - -private: - // 媒体相关元数据 [AUTO-TRANSLATED:f4cf8045] - // Media related metadata - MediaInfo _media_info; - // 播放的rtsp源 [AUTO-TRANSLATED:9963eed1] - // Playing rtsp source - std::weak_ptr _play_src; - - // rtp 直接转发情况下通常会缺少 sps/pps, 在转发 rtp 前, 先发送一次相关帧信息, 部分情况下是可以播放的 [AUTO-TRANSLATED:65fdf16a] - // In the case of direct RTP forwarding, sps/pps is usually missing. Before forwarding RTP, send the relevant frame information once. In some cases, it can be played. - bool _send_config_frames_once { false }; - - // 播放rtsp源的reader对象 [AUTO-TRANSLATED:7b305055] - // Reader object for playing rtsp source - RtspMediaSource::RingType::RingReader::Ptr _reader; - - bool _is_h264 { false }; - bool _bfliter_flag { false }; - std::shared_ptr _bfilter; -}; - -}// namespace mediakit -#endif // ZLMEDIAKIT_WEBRTCPLAYER_H +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLMEDIAKIT_WEBRTCPLAYER_H +#define ZLMEDIAKIT_WEBRTCPLAYER_H + +#include "WebRtcTransport.h" +#include "Rtsp/RtspMediaSource.h" + +namespace mediakit { +/** + * @brief H.264 B 帧过滤器 + * 用于从 H.264 RTP 流中移除 B 帧 + */ +class H264BFrameFilter { +public: + /** + * ISO_IEC_14496-10-AVC-2012 + * Table 7-6 – Name association to slice_type + */ + enum H264SliceType { + H264SliceTypeP = 0, + H264SliceTypeB = 1, + H264SliceTypeI = 2, + H264SliceTypeSP = 3, + H264SliceTypeSI = 4, + H264SliceTypeP1 = 5, + H264SliceTypeB1 = 6, + H264SliceTypeI1 = 7, + H264SliceTypeSP1 = 8, + H264SliceTypeSI1 = 9, + }; + + enum H264NALUType { + NAL_NIDR = 1, + NAL_PARTITION_A = 2, + NAL_PARTITION_B = 3, + NAL_PARTITION_C = 4, + NAL_IDR = 5, + }; + + H264BFrameFilter(); + + ~H264BFrameFilter() = default; + + /** + * @brief 处理单个 RTP 包,移除 B 帧 + * @param packet 输入的 RTP 包 + * @return 如果不是 B 帧则返回原包,否则返回 nullptr + */ + RtpPacket::Ptr processPacket(const RtpPacket::Ptr &packet); + +private: + /** + * @brief 判断 RTP 包是否包含 H.264 的 B 帧 + * @param packet RTP 包 + * @return 如果是 B 帧返回 true,否则返回 false + */ + bool isH264BFrame(const RtpPacket::Ptr &packet) const; + + /** + * @brief 根据 NAL 类型和数据判断是否是 B 帧 + * @param nal_type NAL 单元类型 + * @param data NAL 单元数据(不含 NAL 头) + * @param size 数据大小 + * @return 如果是 B 帧返回 true,否则返回 false + */ + bool isBFrameByNalType(uint8_t nal_type, const uint8_t *data, size_t size) const; + + /** + * @brief 解析指数哥伦布编码 + * @param data 数据缓冲区 + * @param size 缓冲区大小 + * @param bits_offset 位偏移量 + * @return 解析出的数值 + */ + int decodeExpGolomb(const uint8_t *data, size_t size, size_t &bitPos) const; + + /** + * @brief 从比特流中读取位 + * @param data 数据缓冲区 + * @param size 缓冲区大小 + * @return 读取的位值(0 或 1) + */ + int getBit(const uint8_t *data, size_t size) const; + + /** + * @brief 提取切片类型值 + * @param data 数据缓冲区 + * @param size 缓冲区大小 + * @return 切片类型值 + */ + uint8_t extractSliceType(const uint8_t *data, size_t size) const; + + /** + * @brief 处理FU-A分片 + * @param payload 数据缓冲区 + * @param payload_size 缓冲区大小 + * @return 如果是 B 帧返回 true,否则返回 false + */ + bool handleFua(const uint8_t *payload, size_t payload_size) const; + + /** + * @brief 处理 STAP-A 组合包 + * @param payload 数据缓冲区 + * @param payload_size 缓冲区大小 + * @return 如果是 B 帧返回 true,否则返回 false + */ + bool handleStapA(const uint8_t *payload, size_t payload_size) const; + + +private: + uint16_t _last_seq; // 维护输出流的序列号 + uint32_t _last_stamp; // 维护输出流的时间戳 + bool _first_packet; // 是否是第一个包的标记 +}; + +class WebRtcPlayer : public WebRtcTransportImp { +public: + using Ptr = std::shared_ptr; + static Ptr create(const toolkit::EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info, + WebRtcTransport::Role role, WebRtcTransport::SignalingProtocols signaling_protocols); + MediaInfo getMediaInfo() { return _media_info; } + +protected: + ///////WebRtcTransportImp override/////// + void onStartWebRTC() override; + void onDestory() override; + void onRtcConfigure(RtcConfigure &configure) const override; + +private: + WebRtcPlayer(const toolkit::EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info); + + void sendConfigFrames(uint32_t before_seq, uint32_t sample_rate, uint32_t timestamp, uint64_t ntp_timestamp); + +private: + // 媒体相关元数据 [AUTO-TRANSLATED:f4cf8045] + // Media related metadata + MediaInfo _media_info; + // 播放的rtsp源 [AUTO-TRANSLATED:9963eed1] + // Playing rtsp source + std::weak_ptr _play_src; + + // rtp 直接转发情况下通常会缺少 sps/pps, 在转发 rtp 前, 先发送一次相关帧信息, 部分情况下是可以播放的 [AUTO-TRANSLATED:65fdf16a] + // In the case of direct RTP forwarding, sps/pps is usually missing. Before forwarding RTP, send the relevant frame information once. In some cases, it can be played. + bool _send_config_frames_once { false }; + + // 播放rtsp源的reader对象 [AUTO-TRANSLATED:7b305055] + // Reader object for playing rtsp source + RtspMediaSource::RingType::RingReader::Ptr _reader; + + bool _is_h264 { false }; + bool _bfliter_flag { false }; + std::shared_ptr _bfilter; +}; + +}// namespace mediakit +#endif // ZLMEDIAKIT_WEBRTCPLAYER_H diff --git a/webrtc/WebRtcProxyPlayer.cpp b/webrtc/WebRtcProxyPlayer.cpp new file mode 100755 index 00000000..4b403d54 --- /dev/null +++ b/webrtc/WebRtcProxyPlayer.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "WebRtcProxyPlayer.h" +#include "WebRtcProxyPlayerImp.h" +#include "WebRtcPusher.h" +#include "Common/config.h" +#include "Http/HlsPlayer.h" +#include "Rtsp/RtspMediaSourceImp.h" + +using namespace toolkit; +using namespace std; + +namespace mediakit { + +WebRtcProxyPlayer::WebRtcProxyPlayer(const EventPoller::Ptr &poller) + : WebRtcClient(poller) { + DebugL; +} + +WebRtcProxyPlayer::~WebRtcProxyPlayer(void) { + DebugL; +} + +void WebRtcProxyPlayer::play(const string &strUrl) { + DebugL; + try { + _url.parse(strUrl, isPlayer()); + } catch (std::exception &ex) { + onResult(SockException(Err_other, StrPrinter << "illegal webrtc url:" << ex.what())); + return; + } + + startConnect(); +} + +void WebRtcProxyPlayer::teardown() { + DebugL; + doBye(); +} + +void WebRtcProxyPlayer::pause(bool bPause) { + DebugL; +} + +void WebRtcProxyPlayer::speed(float speed) { + DebugL; +} + +float WebRtcProxyPlayer::getTimeOutSec() { + auto timeoutMS = (*this)[Client::kTimeoutMS].as(); + return (float)timeoutMS / (float)1000; +} + +void WebRtcProxyPlayer::onNegotiateFinish() { + DebugL; + onResult(SockException(Err_success, "webrtc play success")); + WebRtcClient::onNegotiateFinish(); +} + +/////////////////////////////////////////////////// +// WebRtcProxyPlayerImp + +void WebRtcProxyPlayerImp::startConnect() { + DebugL; + MediaInfo info(_url._full_url); + ProtocolOption option; + std::weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); + _transport = WebRtcPlayerClient::create(getPoller(), WebRtcTransport::Role::CLIENT, _url._signaling_protocols); + _transport->setOnShutdown([weak_self](const SockException &ex) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + strong_self->onResult(ex); + }); + WebRtcClient::startConnect(); +} + +void WebRtcProxyPlayerImp::onResult(const SockException &ex) { + if (!ex) { + // 播放成功 + _benchmark_mode = (*this)[Client::kBenchmarkMode].as(); + + WebRtcPlayerClient::Ptr transport = std::dynamic_pointer_cast(_transport); + auto media_src = dynamic_pointer_cast(_media_src); + transport->setMediaSource(media_src); + std::weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); + if (!ex) { + transport->setOnStartWebRTC([weak_self, ex]() { + if (auto strong_self = weak_self.lock()) { + strong_self->onPlayResult(ex); + } + }); + } + } else { + WarnL << ex.getErrCode() << " " << ex.what(); + if (ex.getErrCode() == Err_shutdown) { + // 主动shutdown的,不触发回调 + return; + } + + if (!_is_negotiate_finished) { + onPlayResult(ex); + } else { + onShutdown(ex); + } + } +} + +std::vector WebRtcProxyPlayerImp::getTracks(bool ready /*= true*/) const { + auto transport = static_pointer_cast(_transport); + return transport ? transport->getTracks(ready) : Super::getTracks(ready); +} + +void WebRtcProxyPlayerImp::addTrackCompleted() { +} + +} /* namespace mediakit */ diff --git a/webrtc/WebRtcProxyPlayer.h b/webrtc/WebRtcProxyPlayer.h new file mode 100755 index 00000000..b65668d1 --- /dev/null +++ b/webrtc/WebRtcProxyPlayer.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLMEDIAKIT_WEBRTC_PROXY_PLAYER_H +#define ZLMEDIAKIT_WEBRTC_PROXY_PLAYER_H + +#include "Network/Socket.h" +#include "Player/PlayerBase.h" +#include "Poller/Timer.h" +#include "Util/TimeTicker.h" +#include "WebRtcClient.h" +#include +#include + +namespace mediakit { + +// 实现了webrtc代理拉流功能 +class WebRtcProxyPlayer + : public PlayerBase , public WebRtcClient { +public: + using Ptr = std::shared_ptr; + + WebRtcProxyPlayer(const toolkit::EventPoller::Ptr &poller); + ~WebRtcProxyPlayer() override; + + //// PlayerBase override//// + void play(const std::string &strUrl) override; + void teardown() override; + void pause(bool pause) override; + void speed(float speed) override; + +protected: + + //// WebRtcClient override//// + bool isPlayer() override {return true;} + float getTimeOutSec() override; + void onNegotiateFinish() override; + +protected: + //是否为性能测试模式 + bool _benchmark_mode = false; + + //超时功能实现 + toolkit::Ticker _recv_ticker; + std::shared_ptr _check_timer; +}; + +} /* namespace mediakit */ +#endif /* ZLMEDIAKIT_WEBRTC_PROXY_PLAYER_H */ diff --git a/webrtc/WebRtcProxyPlayerImp.h b/webrtc/WebRtcProxyPlayerImp.h new file mode 100755 index 00000000..8b536202 --- /dev/null +++ b/webrtc/WebRtcProxyPlayerImp.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLMEDIAKIT_WEBRTC_PROXY_PLAYER_IMP_H +#define ZLMEDIAKIT_WEBRTC_PROXY_PLAYER_IMP_H + +#include "WebRtcProxyPlayer.h" + +namespace mediakit { + +class WebRtcProxyPlayerImp + : public PlayerImp + , private TrackListener { +public: + using Ptr = std::shared_ptr; + using Super = PlayerImp; + + WebRtcProxyPlayerImp(const toolkit::EventPoller::Ptr &poller) : Super(poller) {} + ~WebRtcProxyPlayerImp() override { DebugL; } + +private: + + //// WebRtcProxyPlayer override//// + void startConnect() override; + + //// PlayerBase override//// + void onResult(const toolkit::SockException &ex) override; + std::vector getTracks(bool ready = true) const override; + + //// TrackListener override//// + bool addTrack(const Track::Ptr &track) override { return true; } + void addTrackCompleted() override; +}; + +} /* namespace mediakit */ +#endif /* ZLMEDIAKIT_WEBRTC_PROXY_PLAYER_IMP_H */ diff --git a/webrtc/WebRtcProxyPusher.cpp b/webrtc/WebRtcProxyPusher.cpp new file mode 100755 index 00000000..e67b1923 --- /dev/null +++ b/webrtc/WebRtcProxyPusher.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "WebRtcProxyPusher.h" +#include "Common/config.h" +#include "Http/HlsPlayer.h" +#include "Rtsp/RtspMediaSourceImp.h" +#include "WebRtcPlayer.h" + +using namespace toolkit; +using namespace std; + +namespace mediakit { + +WebRtcProxyPusher::WebRtcProxyPusher(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src) + : WebRtcClient(poller) { + _push_src = src; + DebugL; +} + +WebRtcProxyPusher::~WebRtcProxyPusher(void) { + teardown(); + DebugL; +} + +void WebRtcProxyPusher::publish(const string &strUrl) { + DebugL; + try { + _url.parse(strUrl, isPlayer()); + } catch (std::exception &ex) { + onResult(SockException(Err_other, StrPrinter << "illegal webrtc url:" << ex.what())); + return; + } + + startConnect(); +} + +void WebRtcProxyPusher::teardown() { + DebugL; + _transport = nullptr; +} + +void WebRtcProxyPusher::onResult(const SockException &ex) { + DebugL << ex; + if (!ex) { + onPublishResult(ex); + } else { + if (!_is_negotiate_finished) { + onPublishResult(ex); + } else { + onShutdown(ex); + } + } +} + +float WebRtcProxyPusher::getTimeOutSec() { + auto timeoutMS = (*this)[Client::kTimeoutMS].as(); + return (float)timeoutMS / (float)1000; +} + +void WebRtcProxyPusher::startConnect() { + DebugL; + MediaInfo info(_url._full_url); + info.schema = "rtc"; + auto src = _push_src.lock(); + if (!src) { + onResult(SockException(Err_other, "media source released")); + return; + } + std::weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); + _transport = WebRtcPlayer::create(getPoller(), src, info, WebRtcTransport::Role::CLIENT, _url._signaling_protocols); + _transport->setOnShutdown([weak_self](const SockException &ex) { + if (auto strong_self = weak_self.lock()) { + strong_self->onResult(ex); + } + }); + _transport->setOnStartWebRTC([weak_self]() { + if (auto strong_self = weak_self.lock()) { + strong_self->onResult(SockException()); + } + }); + WebRtcClient::startConnect(); +} + +} /* namespace mediakit */ diff --git a/webrtc/WebRtcProxyPusher.h b/webrtc/WebRtcProxyPusher.h new file mode 100755 index 00000000..9ba75cc0 --- /dev/null +++ b/webrtc/WebRtcProxyPusher.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLMEDIAKIT_WEBRTC_PROXY_PUSHER_H +#define ZLMEDIAKIT_WEBRTC_PROXY_PUSHER_H + +#include "Network/Socket.h" +#include "Pusher/PusherBase.h" +#include "Poller/Timer.h" +#include "Util/TimeTicker.h" +#include "WebRtcClient.h" +#include +#include + +namespace mediakit { + +// 实现了webrtc代理拉流功能 +class WebRtcProxyPusher + : public PusherBase , public WebRtcClient { +public: + using Ptr = std::shared_ptr; + + WebRtcProxyPusher(const toolkit::EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src); + ~WebRtcProxyPusher() override; + + //// PusherBase override//// + void publish(const std::string &url) override; + void teardown() override; + +protected: + //// WebRtcClient override//// + void startConnect() override; + bool isPlayer() override { return false; } + void onResult(const toolkit::SockException &ex) override; + float getTimeOutSec() override; + +protected: + std::weak_ptr _push_src; +}; + +using WebRtcProxyPusherImp = PusherImp; + +} /* namespace mediakit */ +#endif /* ZLMEDIAKIT_WEBRTC_PROXY_PUSHER_H */ diff --git a/webrtc/WebRtcPusher.cpp b/webrtc/WebRtcPusher.cpp index 0a9c7c62..1abab3b3 100644 --- a/webrtc/WebRtcPusher.cpp +++ b/webrtc/WebRtcPusher.cpp @@ -1,169 +1,232 @@ -/* - * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. - * - * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). - * - * Use of this source code is governed by MIT-like license that can be found in the - * LICENSE file in the root of the source tree. All contributing project authors - * may be found in the AUTHORS file in the root of the source tree. - */ - -#include "WebRtcPusher.h" -#include "Common/config.h" -#include "Rtsp/RtspMediaSourceImp.h" - -using namespace std; - -namespace mediakit { - -WebRtcPusher::Ptr WebRtcPusher::create(const EventPoller::Ptr &poller, - const RtspMediaSource::Ptr &src, - const std::shared_ptr &ownership, - const MediaInfo &info, - const ProtocolOption &option) { - WebRtcPusher::Ptr ret(new WebRtcPusher(poller, src, ownership, info, option), [](WebRtcPusher *ptr) { - ptr->onDestory(); - delete ptr; - }); - ret->onCreate(); - return ret; -} - -WebRtcPusher::WebRtcPusher(const EventPoller::Ptr &poller, - const RtspMediaSource::Ptr &src, - const std::shared_ptr &ownership, - const MediaInfo &info, - const ProtocolOption &option) : WebRtcTransportImp(poller) { - _media_info = info; - _push_src = src; - _push_src_ownership = ownership; - _continue_push_ms = option.continue_push_ms; - CHECK(_push_src); -} - -bool WebRtcPusher::close(MediaSource &sender) { - onShutdown(SockException(Err_shutdown, "close media: " + sender.getUrl())); - // 主动关闭推流,那么不延时注销 [AUTO-TRANSLATED:ee7cc580] - // Actively close the stream, then do not delay the logout - _push_src = nullptr; - return true; -} - -int WebRtcPusher::totalReaderCount(MediaSource &sender) { - auto total_count = _push_src ? _push_src->totalReaderCount() : 0; - if (_simulcast) { - std::lock_guard lock(_mtx); - for (auto &src : _push_src_sim) { - total_count += src.second->totalReaderCount(); - } - } - return total_count; -} - -MediaOriginType WebRtcPusher::getOriginType(MediaSource &sender) const { - return MediaOriginType::rtc_push; -} - -string WebRtcPusher::getOriginUrl(MediaSource &sender) const { - return _media_info.full_url; -} - -std::shared_ptr WebRtcPusher::getOriginSock(MediaSource &sender) const { - return static_pointer_cast(getSession()); -} - -toolkit::EventPoller::Ptr WebRtcPusher::getOwnerPoller(MediaSource &sender) { - return getPoller(); -} - -void WebRtcPusher::onRecvRtp(MediaTrack &track, const string &rid, RtpPacket::Ptr rtp) { - if (!_simulcast) { - assert(_push_src); - _push_src->onWrite(rtp, false); - return; - } - - if (rtp->type == TrackAudio) { - // 音频 [AUTO-TRANSLATED:a577d8e1] - // Audio - for (auto &pr : _push_src_sim) { - pr.second->onWrite(rtp, false); - } - } else { - // 视频 [AUTO-TRANSLATED:904730ac] - // Video - std::lock_guard lock(_mtx); - auto &src = _push_src_sim[rid]; - if (!src) { - const auto& stream = _push_src->getMediaTuple().stream; - auto src_imp = _push_src->clone(rid.empty() ? stream : stream + '_' + rid); - _push_src_sim_ownership[rid] = src_imp->getOwnership(); - src_imp->setListener(static_pointer_cast(shared_from_this())); - src = src_imp; - } - src->onWrite(std::move(rtp), false); - } -} - -void WebRtcPusher::onStartWebRTC() { - WebRtcTransportImp::onStartWebRTC(); - _simulcast = _answer_sdp->supportSimulcast(); - if (canRecvRtp()) { - _push_src->setSdp(_answer_sdp->toRtspSdp()); - } -} - -void WebRtcPusher::onDestory() { - auto duration = getDuration(); - auto bytes_usage = getBytesUsage(); - // 流量统计事件广播 [AUTO-TRANSLATED:6b0b1234] - // Traffic statistics event broadcast - GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); - - if (getSession()) { - WarnL << "RTC推流器(" << _media_info.shortUrl() << ")结束推流,耗时(s):" << duration; - if (bytes_usage >= iFlowThreshold * 1024) { - NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, bytes_usage, duration, false, *getSession()); - } - } - - if (_push_src && _continue_push_ms) { - // 取消所有权 [AUTO-TRANSLATED:4895d8fa] - // Cancel ownership - _push_src_ownership = nullptr; - // 延时10秒注销流 [AUTO-TRANSLATED:e1bb11f9] - // Delay 10 seconds to log out the stream - auto push_src = std::move(_push_src); - getPoller()->doDelayTask(_continue_push_ms, [push_src]() { return 0; }); - } - WebRtcTransportImp::onDestory(); -} - -void WebRtcPusher::onRtcConfigure(RtcConfigure &configure) const { - WebRtcTransportImp::onRtcConfigure(configure); - // 这只是推流 [AUTO-TRANSLATED:f877bf98] - // This is just pushing the stream - configure.audio.direction = configure.video.direction = RtpDirection::recvonly; -} - -float WebRtcPusher::getLossRate(MediaSource &sender,TrackType type) { - return WebRtcTransportImp::getLossRate(type); -} - -void WebRtcPusher::OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) { - // 主动关闭推流,那么不等待重推 [AUTO-TRANSLATED:1ff514d7] - // Actively close the stream, then do not wait for re-pushing - _push_src = nullptr; - WebRtcTransportImp::OnDtlsTransportClosed(dtlsTransport); -} - -void WebRtcPusher::onRtcpBye() { - WebRtcTransportImp::onRtcpBye(); -} - -void WebRtcPusher::onShutdown(const SockException &ex) { - _push_src = nullptr; - WebRtcTransportImp::onShutdown(ex); -} - -}// namespace mediakit \ No newline at end of file +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "WebRtcPusher.h" +#include "Common/config.h" +#include "Rtsp/RtspMediaSourceImp.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +WebRtcPusher::Ptr WebRtcPusher::create(const EventPoller::Ptr &poller, + const RtspMediaSource::Ptr &src, + const std::shared_ptr &ownership, + const MediaInfo &info, + const ProtocolOption &option, + WebRtcTransport::Role role, + WebRtcTransport::SignalingProtocols signaling_protocols) { + WebRtcPusher::Ptr pusher(new WebRtcPusher(poller, src, ownership, info, option), [](WebRtcPusher *ptr) { + ptr->onDestory(); + delete ptr; + }); + + pusher->setRole(role); + pusher->setSignalingProtocols(signaling_protocols); + pusher->onCreate(); + return pusher; +} + +WebRtcPusher::WebRtcPusher(const EventPoller::Ptr &poller, + const RtspMediaSource::Ptr &src, + const std::shared_ptr &ownership, + const MediaInfo &info, + const ProtocolOption &option) : WebRtcTransportImp(poller) { + _media_info = info; + _push_src = src; + _push_src_ownership = ownership; + _continue_push_ms = option.continue_push_ms; + CHECK(_push_src); +} + +bool WebRtcPusher::close(MediaSource &sender) { + onShutdown(SockException(Err_shutdown, "close media: " + sender.getUrl())); + // 主动关闭推流,那么不延时注销 [AUTO-TRANSLATED:ee7cc580] + // Actively close the stream, then do not delay the logout + _push_src = nullptr; + return true; +} + +int WebRtcPusher::totalReaderCount(MediaSource &sender) { + auto total_count = _push_src ? _push_src->totalReaderCount() : 0; + if (_simulcast) { + std::lock_guard lock(_mtx); + for (auto &src : _push_src_sim) { + total_count += src.second->totalReaderCount(); + } + } + return total_count; +} + +MediaOriginType WebRtcPusher::getOriginType(MediaSource &sender) const { + return MediaOriginType::rtc_push; +} + +string WebRtcPusher::getOriginUrl(MediaSource &sender) const { + return _media_info.full_url; +} + +std::shared_ptr WebRtcPusher::getOriginSock(MediaSource &sender) const { + return static_pointer_cast(getSession()); +} + +toolkit::EventPoller::Ptr WebRtcPusher::getOwnerPoller(MediaSource &sender) { + return getPoller(); +} + +void WebRtcPusher::onRecvRtp(MediaTrack &track, const string &rid, RtpPacket::Ptr rtp) { + if (!_simulcast) { + assert(_push_src); + _push_src->onWrite(rtp, false); + return; + } + + if (rtp->type == TrackAudio) { + // 音频 [AUTO-TRANSLATED:a577d8e1] + // Audio + for (auto &pr : _push_src_sim) { + pr.second->onWrite(rtp, false); + } + } else { + // 视频 [AUTO-TRANSLATED:904730ac] + // Video + std::lock_guard lock(_mtx); + auto &src = _push_src_sim[rid]; + if (!src) { + const auto& stream = _push_src->getMediaTuple().stream; + auto src_imp = _push_src->clone(rid.empty() ? stream : stream + '_' + rid); + _push_src_sim_ownership[rid] = src_imp->getOwnership(); + src_imp->setListener(static_pointer_cast(shared_from_this())); + src = src_imp; + } + src->onWrite(std::move(rtp), false); + } +} + +void WebRtcPusher::onStartWebRTC() { + WebRtcTransportImp::onStartWebRTC(); + _simulcast = _answer_sdp->supportSimulcast(); + if (canRecvRtp()) { + _push_src->setSdp(_answer_sdp->toRtspSdp()); + } +} + +void WebRtcPusher::onDestory() { + auto duration = getDuration(); + auto bytes_usage = getBytesUsage(); + // 流量统计事件广播 [AUTO-TRANSLATED:6b0b1234] + // Traffic statistics event broadcast + GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); + + if (getSession()) { + WarnL << "RTC推流器(" << _media_info.shortUrl() << ")结束推流,耗时(s):" << duration; + if (bytes_usage >= iFlowThreshold * 1024) { + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, bytes_usage, duration, false, *getSession()); + } + } + + if (_push_src && _continue_push_ms) { + // 取消所有权 [AUTO-TRANSLATED:4895d8fa] + // Cancel ownership + _push_src_ownership = nullptr; + // 延时10秒注销流 [AUTO-TRANSLATED:e1bb11f9] + // Delay 10 seconds to log out the stream + auto push_src = std::move(_push_src); + getPoller()->doDelayTask(_continue_push_ms, [push_src]() { return 0; }); + } + WebRtcTransportImp::onDestory(); +} + +void WebRtcPusher::onRtcConfigure(RtcConfigure &configure) const { + WebRtcTransportImp::onRtcConfigure(configure); + // 这只是推流 [AUTO-TRANSLATED:f877bf98] + // This is just pushing the stream + configure.audio.direction = configure.video.direction = RtpDirection::recvonly; +} + +float WebRtcPusher::getLossRate(MediaSource &sender,TrackType type) { + return WebRtcTransportImp::getLossRate(type); +} + +void WebRtcPusher::OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) { + // 主动关闭推流,那么不等待重推 [AUTO-TRANSLATED:1ff514d7] + // Actively close the stream, then do not wait for re-pushing + _push_src = nullptr; + WebRtcTransportImp::OnDtlsTransportClosed(dtlsTransport); +} + +void WebRtcPusher::onRtcpBye() { + WebRtcTransportImp::onRtcpBye(); +} + +void WebRtcPusher::onShutdown(const SockException &ex) { + _push_src = nullptr; + WebRtcTransportImp::onShutdown(ex); +} + +//////////////////////////////////////////////////////////////////////////////////////// + +WebRtcPlayerClient::Ptr WebRtcPlayerClient::create(const EventPoller::Ptr &poller, WebRtcTransport::Role role, + WebRtcTransport::SignalingProtocols signaling_protocols) { + WebRtcPlayerClient::Ptr pusher(new WebRtcPlayerClient(poller), [](WebRtcPlayerClient *ptr) { + ptr->onDestory(); + delete ptr; + }); + + pusher->setRole(role); + pusher->setSignalingProtocols(signaling_protocols); + pusher->onCreate(); + return pusher; +} + +WebRtcPlayerClient::WebRtcPlayerClient(const EventPoller::Ptr &poller) + : WebRtcTransportImp(poller) { + _demuxer = std::make_shared(); +} + +void WebRtcPlayerClient::onRecvRtp(MediaTrack &track, const string &rid, RtpPacket::Ptr rtp) { + auto key_pos = _demuxer->inputRtp(rtp); + if (_push_src) { + _push_src->onWrite(rtp, key_pos); + } +} + +void WebRtcPlayerClient::onStartWebRTC() { + WebRtcTransportImp::onStartWebRTC(); + CHECK(!_answer_sdp->supportSimulcast()); + auto sdp = _answer_sdp->toRtspSdp(); + if (canRecvRtp()) { + if (_push_src) { + _push_src->setSdp(sdp); + } + _demuxer->loadSdp(sdp); + } +} + +void WebRtcPlayerClient::onRtcConfigure(RtcConfigure &configure) const { + WebRtcTransportImp::onRtcConfigure(configure); + // 这只是推流 [AUTO-TRANSLATED:f877bf98] + // This is just pushing the stream + configure.audio.direction = configure.video.direction = RtpDirection::recvonly; +} + +vector WebRtcPlayerClient::getTracks(bool ready) const { + return _demuxer->getTracks(ready); +} + +void WebRtcPlayerClient::setMediaSource(RtspMediaSource::Ptr src) { + _push_src = std::move(src); + if (_push_src && canRecvRtp()) { + _push_src->setSdp(_answer_sdp->toRtspSdp()); + } +} + +}// namespace mediakit diff --git a/webrtc/WebRtcPusher.h b/webrtc/WebRtcPusher.h index 3cf3f94b..93aacbca 100644 --- a/webrtc/WebRtcPusher.h +++ b/webrtc/WebRtcPusher.h @@ -1,87 +1,113 @@ -/* - * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. - * - * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). - * - * Use of this source code is governed by MIT-like license that can be found in the - * LICENSE file in the root of the source tree. All contributing project authors - * may be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef ZLMEDIAKIT_WEBRTCPUSHER_H -#define ZLMEDIAKIT_WEBRTCPUSHER_H - -#include "WebRtcTransport.h" -#include "Rtsp/RtspMediaSource.h" - -namespace mediakit { - -class WebRtcPusher : public WebRtcTransportImp, public MediaSourceEvent { -public: - using Ptr = std::shared_ptr; - static Ptr create(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, - const std::shared_ptr &ownership, const MediaInfo &info, const ProtocolOption &option); - -protected: - ///////WebRtcTransportImp override/////// - void onStartWebRTC() override; - void onDestory() override; - void onRtcConfigure(RtcConfigure &configure) const override; - void onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) override; - void onShutdown(const SockException &ex) override; - void onRtcpBye() override; - // // dtls相关的回调 //// [AUTO-TRANSLATED:31a1f32c] - // // dtls related callbacks //// - void OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) override; - -protected: - ///////MediaSourceEvent override/////// - // 关闭 [AUTO-TRANSLATED:92392f02] - // Close - bool close(MediaSource &sender) override; - // 播放总人数 [AUTO-TRANSLATED:c42a3161] - // Total number of players - int totalReaderCount(MediaSource &sender) override; - // 获取媒体源类型 [AUTO-TRANSLATED:34290a69] - // Get media source type - MediaOriginType getOriginType(MediaSource &sender) const override; - // 获取媒体源url或者文件路径 [AUTO-TRANSLATED:fa34d795] - // Get media source url or file path - std::string getOriginUrl(MediaSource &sender) const override; - // 获取媒体源客户端相关信息 [AUTO-TRANSLATED:037ef910] - // Get media source client related information - std::shared_ptr getOriginSock(MediaSource &sender) const override; - // 由于支持断连续推,存在OwnerPoller变更的可能 [AUTO-TRANSLATED:1c863b40] - // Due to support for discontinuous pushing, there is a possibility of OwnerPoller changes - toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override; - // 获取丢包率 [AUTO-TRANSLATED:ec61b378] - // Get packet loss rate - float getLossRate(MediaSource &sender,TrackType type) override; - -private: - WebRtcPusher(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, - const std::shared_ptr &ownership, const MediaInfo &info, const ProtocolOption &option); - -private: - bool _simulcast = false; - // 断连续推延时 [AUTO-TRANSLATED:13ad578a] - // Discontinuous pushing delay - uint32_t _continue_push_ms = 0; - // 媒体相关元数据 [AUTO-TRANSLATED:f4cf8045] - // Media related metadata - MediaInfo _media_info; - // 推流的rtsp源 [AUTO-TRANSLATED:4f976bca] - // Rtsp source of the stream - RtspMediaSource::Ptr _push_src; - // 推流所有权 [AUTO-TRANSLATED:d0ddf5c7] - // Stream ownership - std::shared_ptr _push_src_ownership; - // 推流的rtsp源,支持simulcast [AUTO-TRANSLATED:44be9120] - // Rtsp source of the stream, supports simulcast - std::recursive_mutex _mtx; - std::unordered_map _push_src_sim; - std::unordered_map > _push_src_sim_ownership; -}; - -}// namespace mediakit -#endif //ZLMEDIAKIT_WEBRTCPUSHER_H +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLMEDIAKIT_WEBRTCPUSHER_H +#define ZLMEDIAKIT_WEBRTCPUSHER_H + +#include "WebRtcTransport.h" +#include "Rtsp/RtspDemuxer.h" +#include "Rtsp/RtspMediaSource.h" + +namespace mediakit { + +class WebRtcPusher : public WebRtcTransportImp, public MediaSourceEvent { +public: + using Ptr = std::shared_ptr; + static Ptr create(const toolkit::EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, + const std::shared_ptr &ownership, const MediaInfo &info, const ProtocolOption &option, + WebRtcTransport::Role role, WebRtcTransport::SignalingProtocols signaling_protocols); + +protected: + ///////WebRtcTransportImp override/////// + void onStartWebRTC() override; + void onDestory() override; + void onRtcConfigure(RtcConfigure &configure) const override; + void onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) override; + void onShutdown(const toolkit::SockException &ex) override; + void onRtcpBye() override; + // // dtls相关的回调 //// [AUTO-TRANSLATED:31a1f32c] + // // dtls related callbacks //// + void OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) override; + +protected: + ///////MediaSourceEvent override/////// + // 关闭 [AUTO-TRANSLATED:92392f02] + // Close + bool close(MediaSource &sender) override; + // 播放总人数 [AUTO-TRANSLATED:c42a3161] + // Total number of players + int totalReaderCount(MediaSource &sender) override; + // 获取媒体源类型 [AUTO-TRANSLATED:34290a69] + // Get media source type + MediaOriginType getOriginType(MediaSource &sender) const override; + // 获取媒体源url或者文件路径 [AUTO-TRANSLATED:fa34d795] + // Get media source url or file path + std::string getOriginUrl(MediaSource &sender) const override; + // 获取媒体源客户端相关信息 [AUTO-TRANSLATED:037ef910] + // Get media source client related information + std::shared_ptr getOriginSock(MediaSource &sender) const override; + // 由于支持断连续推,存在OwnerPoller变更的可能 [AUTO-TRANSLATED:1c863b40] + // Due to support for discontinuous pushing, there is a possibility of OwnerPoller changes + toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override; + // 获取丢包率 [AUTO-TRANSLATED:ec61b378] + // Get packet loss rate + float getLossRate(MediaSource &sender,TrackType type) override; + +private: + WebRtcPusher(const toolkit::EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, + const std::shared_ptr &ownership, const MediaInfo &info, const ProtocolOption &option); + +private: + bool _simulcast = false; + // 断连续推延时 [AUTO-TRANSLATED:13ad578a] + // Discontinuous pushing delay + uint32_t _continue_push_ms = 0; + // 媒体相关元数据 [AUTO-TRANSLATED:f4cf8045] + // Media related metadata + MediaInfo _media_info; + // 推流的rtsp源 [AUTO-TRANSLATED:4f976bca] + // Rtsp source of the stream + RtspMediaSource::Ptr _push_src; + // 推流所有权 [AUTO-TRANSLATED:d0ddf5c7] + // Stream ownership + std::shared_ptr _push_src_ownership; + // 推流的rtsp源,支持simulcast [AUTO-TRANSLATED:44be9120] + // Rtsp source of the stream, supports simulcast + std::recursive_mutex _mtx; + std::unordered_map _push_src_sim; + std::unordered_map > _push_src_sim_ownership; +}; + +class WebRtcPlayerClient : public WebRtcTransportImp { +public: + using Ptr = std::shared_ptr; + static Ptr create(const toolkit::EventPoller::Ptr &poller, WebRtcTransport::Role role, WebRtcTransport::SignalingProtocols signaling_protocols); + + void setMediaSource(RtspMediaSource::Ptr src); + std::vector getTracks(bool ready) const; + +protected: + ///////WebRtcTransportImp override/////// + void onStartWebRTC() override; + void onRtcConfigure(RtcConfigure &configure) const override; + void onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) override; + +private: + WebRtcPlayerClient(const toolkit::EventPoller::Ptr &poller); + +private: + RtspDemuxer::Ptr _demuxer; + // 推流的rtsp源 [AUTO-TRANSLATED:4f976bca] + // Rtsp source of the stream + RtspMediaSource::Ptr _push_src; +}; + +}// namespace mediakit +#endif //ZLMEDIAKIT_WEBRTCPUSHER_H diff --git a/webrtc/WebRtcSession.cpp b/webrtc/WebRtcSession.cpp index d5fb06e9..96ff55b5 100644 --- a/webrtc/WebRtcSession.cpp +++ b/webrtc/WebRtcSession.cpp @@ -1,166 +1,163 @@ -/* - * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. - * - * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). - * - * Use of this source code is governed by MIT-like license that can be found in the - * LICENSE file in the root of the source tree. All contributing project authors - * may be found in the AUTHORS file in the root of the source tree. - */ - -#include "WebRtcSession.h" -#include "Util/util.h" -#include "Network/TcpServer.h" -#include "Common/config.h" -#include "IceServer.hpp" -#include "WebRtcTransport.h" - -using namespace std; - -namespace mediakit { - -static string getUserName(const char *buf, size_t len) { - if (!RTC::StunPacket::IsStun((const uint8_t *) buf, len)) { - return ""; - } - std::unique_ptr packet(RTC::StunPacket::Parse((const uint8_t *) buf, len)); - if (!packet) { - return ""; - } - if (packet->GetClass() != RTC::StunPacket::Class::REQUEST || - packet->GetMethod() != RTC::StunPacket::Method::BINDING) { - return ""; - } - // 收到binding request请求 [AUTO-TRANSLATED:eff4d773] - // Received binding request - auto vec = split(packet->GetUsername(), ":"); - return vec[0]; -} - -EventPoller::Ptr WebRtcSession::queryPoller(const Buffer::Ptr &buffer) { - auto user_name = getUserName(buffer->data(), buffer->size()); - if (user_name.empty()) { - return nullptr; - } - auto ret = WebRtcTransportManager::Instance().getItem(user_name); - return ret ? ret->getPoller() : nullptr; -} - -//////////////////////////////////////////////////////////////////////////////// - -WebRtcSession::WebRtcSession(const Socket::Ptr &sock) : Session(sock) { - _over_tcp = sock->sockType() == SockNum::Sock_TCP; -} - -void WebRtcSession::attachServer(const Server &server) { - _server = std::static_pointer_cast(const_cast(server).shared_from_this()); -} - -void WebRtcSession::onRecv_l(const char *data, size_t len) { - if (_find_transport) { - // 只允许寻找一次transport [AUTO-TRANSLATED:446fae53] - // Only allow searching for transport once - _find_transport = false; - auto user_name = getUserName(data, len); - auto transport = WebRtcTransportManager::Instance().getItem(user_name); - CHECK(transport); - - // WebRtcTransport在其他poller线程上,需要切换poller线程并重新创建WebRtcSession对象 [AUTO-TRANSLATED:7e5534cf] - // WebRtcTransport is on another poller thread, need to switch poller thread and recreate WebRtcSession object - if (!transport->getPoller()->isCurrentThread()) { - auto sock = Socket::createSocket(transport->getPoller(), false); - // 1、克隆socket(fd不变),切换poller线程到WebRtcTransport所在线程 [AUTO-TRANSLATED:f930bfab] - // 1. Clone socket (fd remains unchanged), switch poller thread to the thread where WebRtcTransport is located - sock->cloneSocket(*(getSock())); - auto server = _server; - std::string str(data, len); - sock->getPoller()->async([sock, server, str](){ - auto strong_server = server.lock(); - if (strong_server) { - auto session = static_pointer_cast(strong_server->createSession(sock)); - // 2、创建新的WebRtcSession对象(绑定到WebRtcTransport所在线程),重新处理一遍ice binding request命令 [AUTO-TRANSLATED:c75203bb] - // 2. Create a new WebRtcSession object (bound to the thread where WebRtcTransport is located), reprocess the ice binding request command - session->onRecv_l(str.data(), str.size()); - } - }); - // 3、销毁原先的socket和WebRtcSession(原先的对象跟WebRtcTransport不在同一条线程) [AUTO-TRANSLATED:a6d6d63f] - // 3. Destroy the original socket and WebRtcSession (the original object is not on the same thread as WebRtcTransport) - throw std::runtime_error("webrtc over tcp change poller: " + getPoller()->getThreadName() + " -> " + sock->getPoller()->getThreadName()); - } - _transport = std::move(transport); - InfoP(this); - } - _ticker.resetTime(); - CHECK(_transport); - _transport->inputSockData((char *)data, len, this); -} - -void WebRtcSession::onRecv(const Buffer::Ptr &buffer) { - if (_over_tcp) { - input(buffer->data(), buffer->size()); - } else { - onRecv_l(buffer->data(), buffer->size()); - } -} - -void WebRtcSession::onError(const SockException &err) { - // udp链接超时,但是rtc链接不一定超时,因为可能存在链接迁移的情况 [AUTO-TRANSLATED:aaa9672f] - // UDP connection timeout, but RTC connection may not timeout, because there may be connection migration - // 在udp链接迁移时,新的WebRtcSession对象将接管WebRtcTransport对象的生命周期 [AUTO-TRANSLATED:7e7d19df] - // When UDP connection migrates, the new WebRtcSession object will take over the life cycle of the WebRtcTransport object - // 本WebRtcSession对象将在超时后自动销毁 [AUTO-TRANSLATED:bc903a06] - // This WebRtcSession object will be automatically destroyed after timeout - WarnP(this) << err; - - if (!_transport) { - return; - } - auto self = static_pointer_cast(shared_from_this()); - auto transport = std::move(_transport); - getPoller()->async([transport, self]() mutable { - // 延时减引用,防止使用transport对象时,销毁对象 [AUTO-TRANSLATED:09dd6609] - // Delay decrementing the reference count to prevent the object from being destroyed when using the transport object - transport->removeTuple(self.get()); - // 确保transport在Session对象前销毁,防止WebRtcTransport::onDestory()时获取不到Session对象 [AUTO-TRANSLATED:acd8bd77] - // Ensure that the transport is destroyed before the Session object to prevent WebRtcTransport::onDestory() from not being able to get the Session object - transport = nullptr; - }, false); -} - -void WebRtcSession::onManager() { - GET_CONFIG(float, timeoutSec, Rtc::kTimeOutSec); - if (!_transport && _ticker.createdTime() > timeoutSec * 1000) { - shutdown(SockException(Err_timeout, "illegal webrtc connection")); - return; - } - if (_ticker.elapsedTime() > timeoutSec * 1000) { - shutdown(SockException(Err_timeout, "webrtc connection timeout")); - return; - } -} - -ssize_t WebRtcSession::onRecvHeader(const char *data, size_t len) { - onRecv_l(data + 2, len - 2); - return 0; -} - -const char *WebRtcSession::onSearchPacketTail(const char *data, size_t len) { - if (len < 2) { - // 数据不够 [AUTO-TRANSLATED:830a2785] - // Not enough data - return nullptr; - } - uint16_t length = (((uint8_t *)data)[0] << 8) | ((uint8_t *)data)[1]; - if (len < (size_t)(length + 2)) { - // 数据不够 [AUTO-TRANSLATED:830a2785] - // Not enough data - return nullptr; - } - // 返回rtp包末尾 [AUTO-TRANSLATED:5134cf6f] - // Return the end of the RTP packet - return data + 2 + length; -} - -}// namespace mediakit - - +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "WebRtcSession.h" +#include "Util/util.h" +#include "Network/TcpServer.h" +#include "Common/config.h" +#include "IceTransport.hpp" +#include "WebRtcTransport.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +static string getUserName(const char *buf, size_t len) { + if (!RTC::StunPacket::isStun((const uint8_t *) buf, len)) { + return ""; + } + auto packet = RTC::StunPacket::parse((const uint8_t *) buf, len); + if (!packet) { + return ""; + } + + // 收到binding request请求 [AUTO-TRANSLATED:eff4d773] + // Received binding request + auto vec = split(packet->getUsername(), ":"); + return vec[0]; +} + +EventPoller::Ptr WebRtcSession::queryPoller(const Buffer::Ptr &buffer) { + auto user_name = getUserName(buffer->data(), buffer->size()); + if (user_name.empty()) { + return nullptr; + } + auto ret = WebRtcTransportManager::Instance().getItem(user_name); + return ret ? ret->getPoller() : nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// + +WebRtcSession::WebRtcSession(const Socket::Ptr &sock) : Session(sock) { + _over_tcp = sock->sockType() == SockNum::Sock_TCP; +} + +void WebRtcSession::attachServer(const Server &server) { + _server = std::static_pointer_cast(const_cast(server).shared_from_this()); +} + +void WebRtcSession::onRecv_l(const char *data, size_t len) { + if (_find_transport) { + // 只允许寻找一次transport [AUTO-TRANSLATED:446fae53] + // Only allow searching for transport once + _find_transport = false; + auto user_name = getUserName(data, len); + auto transport = WebRtcTransportManager::Instance().getItem(user_name); + CHECK(transport); + + // WebRtcTransport在其他poller线程上,需要切换poller线程并重新创建WebRtcSession对象 [AUTO-TRANSLATED:7e5534cf] + // WebRtcTransport is on another poller thread, need to switch poller thread and recreate WebRtcSession object + if (!transport->getPoller()->isCurrentThread()) { + auto sock = Socket::createSocket(transport->getPoller(), false); + // 1、克隆socket(fd不变),切换poller线程到WebRtcTransport所在线程 [AUTO-TRANSLATED:f930bfab] + // 1. Clone socket (fd remains unchanged), switch poller thread to the thread where WebRtcTransport is located + sock->cloneSocket(*(getSock())); + auto server = _server; + std::string str(data, len); + sock->getPoller()->async([sock, server, str](){ + auto strong_server = server.lock(); + if (strong_server) { + auto session = static_pointer_cast(strong_server->createSession(sock)); + // 2、创建新的WebRtcSession对象(绑定到WebRtcTransport所在线程),重新处理一遍ice binding request命令 [AUTO-TRANSLATED:c75203bb] + // 2. Create a new WebRtcSession object (bound to the thread where WebRtcTransport is located), reprocess the ice binding request command + session->onRecv_l(str.data(), str.size()); + } + }); + // 3、销毁原先的socket和WebRtcSession(原先的对象跟WebRtcTransport不在同一条线程) [AUTO-TRANSLATED:a6d6d63f] + // 3. Destroy the original socket and WebRtcSession (the original object is not on the same thread as WebRtcTransport) + throw std::runtime_error("webrtc over tcp change poller: " + getPoller()->getThreadName() + " -> " + sock->getPoller()->getThreadName()); + } + _transport = std::move(transport); + InfoP(this); + } + _ticker.resetTime(); + CHECK(_transport); + auto self = static_pointer_cast(shared_from_this()); + _transport->inputSockData(data, len, self); +} + +void WebRtcSession::onRecv(const Buffer::Ptr &buffer) { + if (_over_tcp) { + input(buffer->data(), buffer->size()); + } else { + onRecv_l(buffer->data(), buffer->size()); + } +} + +void WebRtcSession::onError(const SockException &err) { + // udp链接超时,但是rtc链接不一定超时,因为可能存在链接迁移的情况 [AUTO-TRANSLATED:aaa9672f] + // UDP connection timeout, but RTC connection may not timeout, because there may be connection migration + // 在udp链接迁移时,新的WebRtcSession对象将接管WebRtcTransport对象的生命周期 [AUTO-TRANSLATED:7e7d19df] + // When UDP connection migrates, the new WebRtcSession object will take over the life cycle of the WebRtcTransport object + // 本WebRtcSession对象将在超时后自动销毁 [AUTO-TRANSLATED:bc903a06] + // This WebRtcSession object will be automatically destroyed after timeout + WarnP(this) << err; + + if (!_transport) { + return; + } + auto self = static_pointer_cast(shared_from_this()); + auto transport = std::move(_transport); + getPoller()->async([transport, self]() mutable { + // 延时减引用,防止使用transport对象时,销毁对象 [AUTO-TRANSLATED:09dd6609] + // Delay decrementing the reference count to prevent the object from being destroyed when using the transport object + transport->removePair(self.get()); + // 确保transport在Session对象前销毁,防止WebRtcTransport::onDestory()时获取不到Session对象 [AUTO-TRANSLATED:acd8bd77] + // Ensure that the transport is destroyed before the Session object to prevent WebRtcTransport::onDestory() from not being able to get the Session object + transport = nullptr; + }, false); +} + +void WebRtcSession::onManager() { + GET_CONFIG(float, timeoutSec, Rtc::kTimeOutSec); + if (!_transport && _ticker.createdTime() > timeoutSec * 1000) { + shutdown(SockException(Err_timeout, "illegal webrtc connection")); + return; + } + if (_ticker.elapsedTime() > timeoutSec * 1000) { + shutdown(SockException(Err_timeout, "webrtc connection timeout")); + return; + } +} + +ssize_t WebRtcSession::onRecvHeader(const char *data, size_t len) { + onRecv_l(data + 2, len - 2); + return 0; +} + +const char *WebRtcSession::onSearchPacketTail(const char *data, size_t len) { + if (len < 2) { + // 数据不够 [AUTO-TRANSLATED:830a2785] + // Not enough data + return nullptr; + } + uint16_t length = (((uint8_t *)data)[0] << 8) | ((uint8_t *)data)[1]; + if (len < (size_t)(length + 2)) { + // 数据不够 [AUTO-TRANSLATED:830a2785] + // Not enough data + return nullptr; + } + // 返回rtp包末尾 [AUTO-TRANSLATED:5134cf6f] + // Return the end of the RTP packet + return data + 2 + length; +} + +}// namespace mediakit diff --git a/webrtc/WebRtcSession.h b/webrtc/WebRtcSession.h index 3bfd96bc..7afbb2c4 100644 --- a/webrtc/WebRtcSession.h +++ b/webrtc/WebRtcSession.h @@ -21,18 +21,18 @@ namespace toolkit { } namespace mediakit { + class WebRtcTransportImp; -using namespace toolkit; -class WebRtcSession : public Session, public HttpRequestSplitter { +class WebRtcSession : public toolkit::Session, public HttpRequestSplitter { public: - WebRtcSession(const Socket::Ptr &sock); + WebRtcSession(const toolkit::Socket::Ptr &sock); - void attachServer(const Server &server) override; - void onRecv(const Buffer::Ptr &) override; - void onError(const SockException &err) override; + void attachServer(const toolkit::Server &server) override; + void onRecv(const toolkit::Buffer::Ptr &) override; + void onError(const toolkit::SockException &err) override; void onManager() override; - static EventPoller::Ptr queryPoller(const Buffer::Ptr &buffer); + static toolkit::EventPoller::Ptr queryPoller(const toolkit::Buffer::Ptr &buffer); protected: WebRtcTransportImp::Ptr _transport; @@ -47,7 +47,7 @@ private: private: bool _over_tcp = false; bool _find_transport = true; - Ticker _ticker; + toolkit::Ticker _ticker; std::weak_ptr _server; }; diff --git a/webrtc/WebRtcSignalingMsg.cpp b/webrtc/WebRtcSignalingMsg.cpp new file mode 100644 index 00000000..230f285f --- /dev/null +++ b/webrtc/WebRtcSignalingMsg.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "WebRtcSignalingMsg.h" + +namespace mediakit { +namespace Rtc { + +// WebRTC 信令消息键名和值常量定义 +const char* const CLASS_KEY = "class"; +const char* const CLASS_VALUE_REQUEST = "request"; +const char* const CLASS_VALUE_INDICATION = "indication"; // 指示类型,不需要应答 +const char* const CLASS_VALUE_ACCEPT = "accept"; // 作为CLASS_VALUE_REQUEST的应答 +const char* const CLASS_VALUE_REJECT = "reject"; // 作为CLASS_VALUE_REQUEST的应答 +const char* const METHOD_KEY = "method"; +const char* const METHOD_VALUE_REGISTER = "register"; // 注册 +const char* const METHOD_VALUE_UNREGISTER = "unregister"; // 注销 +const char* const METHOD_VALUE_CALL = "call"; // 呼叫(取流或推流) + +const char* const METHOD_VALUE_BYE = "bye"; // 挂断 +const char* const METHOD_VALUE_CANDIDATE = "candidate"; +const char* const TRANSACTION_ID_KEY = "transaction_id"; // 消息id,每条消息拥有一个唯一的id +const char* const ROOM_ID_KEY = "room_id"; +const char* const GUEST_ID_KEY = "guest_id"; // 每个独立的会话,会拥有一个唯一的guest_id +const char* const SENDER_KEY = "sender"; +const char* const TYPE_KEY = "type"; +const char* const TYPE_VALUE_PLAY = "play"; // 拉流 +const char* const TYPE_VALUE_PUSH = "push"; // 推流 +const char* const REASON_KEY = "reason"; +const char* const CALL_VHOST_KEY = "vhost"; +const char* const CALL_APP_KEY = "app"; +const char* const CALL_STREAM_KEY = "stream"; +const char* const SDP_KEY = "sdp"; + +const char* const ICE_SERVERS_KEY = "ice_servers"; +const char* const CANDIDATE_KEY = "candidate"; +const char* const URL_KEY = "url"; +const char* const UFRAG_KEY = "ufrag"; +const char* const PWD_KEY = "pwd"; + +} // namespace Rtc +} // namespace mediakit diff --git a/webrtc/WebRtcSignalingMsg.h b/webrtc/WebRtcSignalingMsg.h new file mode 100644 index 00000000..89fed39e --- /dev/null +++ b/webrtc/WebRtcSignalingMsg.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + + +#ifndef ZLMEDIAKIT_WEBRTC_SIGNALING_MSG_H +#define ZLMEDIAKIT_WEBRTC_SIGNALING_MSG_H + +#include "server/WebApi.h" + +namespace mediakit { +namespace Rtc { + +#define SIGNALING_MSG_ARGS const HttpAllArgs& allArgs + +// WebRTC 信令消息键名和值常量声明 +extern const char* const CLASS_KEY; +extern const char* const CLASS_VALUE_REQUEST; +extern const char* const CLASS_VALUE_INDICATION; // 指示类型,不需要应答 +extern const char* const CLASS_VALUE_ACCEPT; // 作为CLASS_VALUE_REQUEST的应答 +extern const char* const CLASS_VALUE_REJECT; // 作为CLASS_VALUE_REQUEST的应答 +extern const char* const METHOD_KEY; +extern const char* const METHOD_VALUE_REGISTER; // 注册 +extern const char* const METHOD_VALUE_UNREGISTER; // 注销 +extern const char* const METHOD_VALUE_CALL; // 呼叫(取流或推流) + +extern const char* const METHOD_VALUE_BYE; // 挂断 +extern const char* const METHOD_VALUE_CANDIDATE; +extern const char* const TRANSACTION_ID_KEY; // 消息id,每条消息拥有一个唯一的id +extern const char* const ROOM_ID_KEY; +extern const char* const GUEST_ID_KEY; // 每个独立的会话,会拥有一个唯一的guest_id +extern const char* const SENDER_KEY; +extern const char* const TYPE_KEY; +extern const char* const TYPE_VALUE_PLAY; // 拉流 +extern const char* const TYPE_VALUE_PUSH; // 推流 +extern const char* const REASON_KEY; +extern const char* const CALL_VHOST_KEY; +extern const char* const CALL_APP_KEY; +extern const char* const CALL_STREAM_KEY; +extern const char* const SDP_KEY; + +extern const char* const ICE_SERVERS_KEY; +extern const char* const CANDIDATE_KEY; +extern const char* const URL_KEY; +extern const char* const UFRAG_KEY; +extern const char* const PWD_KEY; + +} // namespace Rtc +} // namespace mediakit +// + +#endif //ZLMEDIAKIT_WEBRTC_SIGNALING_PEER_H diff --git a/webrtc/WebRtcSignalingPeer.cpp b/webrtc/WebRtcSignalingPeer.cpp new file mode 100644 index 00000000..f224e820 --- /dev/null +++ b/webrtc/WebRtcSignalingPeer.cpp @@ -0,0 +1,687 @@ +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "WebRtcSignalingPeer.h" +#include "WebRtcSignalingMsg.h" +#include "Util/util.h" +#include "Common/config.h" +#include "json/value.h" + +using namespace std; +using namespace toolkit; +using namespace mediakit::Rtc; + +namespace mediakit { + +// 注册到的信令服务器列表 +// 不允许注册到同一个服务器地址 +static ServiceController s_room_keepers; + +static inline string getRoomKeepersKey(const string &host, uint16_t &port) { + return host + ":" + std::to_string(port); +} + +void addWebrtcRoomKeeper(const string &host, uint16_t port, const std::string& room_id, bool ssl, + const function &cb) { + DebugL; + auto key = getRoomKeepersKey(host, port); + if (s_room_keepers.find(key)) { + //已经发起注册了 + cb(SockException(Err_success), key); + return; + } + auto peer = s_room_keepers.make(key, host, port, ssl, room_id); + peer->setOnShutdown([key] (const SockException &ex) { + InfoL << "webrtc peer shutdown, key: " << key << ", " << ex.what(); + s_room_keepers.erase(key); + }); + + peer->setOnConnect([peer, cb] (const SockException &ex) { + peer->regist(cb); + }); + peer->connect(); +} + +void delWebrtcRoomKeeper(const std::string &key, const std::function &cb) { + auto peer = s_room_keepers.find(key); + if (!peer) { + return cb(SockException(Err_other, "room_key not exist")); + } + peer->unregist(cb); + s_room_keepers.erase(key); +} + +void listWebrtcRoomKeepers(const std::function &cb) { + s_room_keepers.for_each(cb); +} + +Json::Value ToJson(const WebRtcSignalingPeer::Ptr& p) { + return p->makeInfoJson(); +} + +WebRtcSignalingPeer::Ptr getWebrtcRoomKeeper(const string &host, uint16_t port) { + return s_room_keepers.find(getRoomKeepersKey(host, port)); +} + +//////////// WebRtcSignalingPeer ////////////////////////// + +WebRtcSignalingPeer::WebRtcSignalingPeer(const std::string &host, uint16_t port, bool ssl, const std::string &room_id, const EventPoller::Ptr &poller) + : WebSocketClient(poller) + , _room_id(room_id) { + TraceL; + // TODO: not support wss now + _ws_url = StrPrinter << (ssl ? "wss://" : "ws://") + host << ":" << port << "/signaling"; + _room_key = getRoomKeepersKey(host, port); +} + +WebRtcSignalingPeer::~WebRtcSignalingPeer() { + DebugL << "room_id: " << _room_id; +} + +void WebRtcSignalingPeer::connect() { + DebugL; + startWebSocket(_ws_url); +} + +void WebRtcSignalingPeer::regist(const function &cb) { + DebugL; + std::weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); + getPoller()->async([weak_self, cb]() mutable { + if (auto strong_self = weak_self.lock()) { + strong_self->sendRegisterRequest(std::move(cb)); + } + }); +} + +void WebRtcSignalingPeer::unregist(const function &cb) { + DebugL; + auto trigger = [cb](const SockException &ex, std::string msg) { cb(ex); }; + std::weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); + getPoller()->async([weak_self, trigger]() mutable { + if (auto strong_self = weak_self.lock()) { + strong_self->sendUnregisterRequest(std::move(trigger)); + } + }); +} + +void WebRtcSignalingPeer::checkIn(const std::string& peer_room_id, const MediaTuple &tuple, const std::string& identifier, + const std::string& offer, bool is_play, + const function &cb, float timeout_sec) { + DebugL; + std::weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); + getPoller()->async([=] () mutable { + TraceL; + if (auto strong_self = weak_self.lock()) { + auto guest_id = strong_self->_room_id + "_" + makeRandStr(16); + strong_self->_tours.emplace(peer_room_id, std::make_pair(guest_id, identifier)); + auto trigger = ([cb, peer_room_id, weak_self](const SockException &ex, const std::string &msg) { + auto strong_self = weak_self.lock(); + if (ex && strong_self) { + strong_self->_tours.erase(peer_room_id); + } + return cb(ex, msg); + }); + strong_self->sendCallRequest(peer_room_id, guest_id, tuple, offer, is_play, std::move(trigger)); + } + }); +} + +void WebRtcSignalingPeer::checkOut(const std::string& peer_room_id) { + DebugL; + std::weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); + getPoller()->async([=] () { + TraceL; + if (auto strong_self = weak_self.lock()) { + auto it = strong_self->_tours.find(peer_room_id); + if (it != strong_self->_tours.end()) { + auto &guest_id = it->second.first; + strong_self->sendByeIndication(peer_room_id, guest_id); + strong_self->_tours.erase(it); + } + } + }); +} + +void WebRtcSignalingPeer::candidate(const std::string& transport_identifier, const std::string& candidate, const std::string& ice_ufrag, const std::string& ice_pwd) { + std::weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); + getPoller()->async([=] () { + if (auto strong_self = weak_self.lock()) { + strong_self->sendCandidateIndication(transport_identifier, candidate, ice_ufrag, ice_pwd); + } + }); +} + +void WebRtcSignalingPeer::processOffer(SIGNALING_MSG_ARGS, WebRtcInterface &transport) { + try { + auto sdp = transport.getAnswerSdp((const std::string )allArgs[SDP_KEY]); + auto tuple = MediaTuple(allArgs[CALL_VHOST_KEY], allArgs[CALL_APP_KEY], allArgs[CALL_STREAM_KEY]); + answer(allArgs[GUEST_ID_KEY], tuple, transport.getIdentifier(), sdp, allArgs[TYPE_KEY] == TYPE_VALUE_PLAY, allArgs[TRANSACTION_ID_KEY]); + + std::weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); + transport.gatheringCandidate(_ice_server, [weak_self](const std::string& transport_identifier, + const std::string& candidate, const std::string& ufrag, const std::string& pwd) { + if (auto strong_self = weak_self.lock()) { + strong_self->candidate(transport_identifier, candidate, ufrag, pwd); + } + }); + } catch (std::exception &ex) { + Json::Value body; + body[METHOD_KEY] = allArgs[METHOD_KEY]; + body[ROOM_ID_KEY] = allArgs[ROOM_ID_KEY]; + body[GUEST_ID_KEY] = allArgs[GUEST_ID_KEY]; + body[CALL_VHOST_KEY] = allArgs[CALL_VHOST_KEY]; + body[CALL_APP_KEY] = allArgs[CALL_APP_KEY]; + body[CALL_STREAM_KEY] = allArgs[CALL_STREAM_KEY]; + body[TYPE_KEY] = allArgs[TYPE_KEY]; + sendRefusesResponse(body, allArgs[TRANSACTION_ID_KEY], ex.what()); + } +} + +void WebRtcSignalingPeer::answer(const std::string& guest_id, const MediaTuple &tuple, const std::string& identifier, const std::string& sdp, bool is_play, const std::string& transaction_id) { + _peer_guests.emplace(guest_id, identifier); + sendCallAccept(guest_id, tuple, sdp, is_play, transaction_id); +} + +void WebRtcSignalingPeer::setOnConnect(function cb) { + _on_connect = cb ? std::move(cb) : [](const SockException &) {}; +} + +void WebRtcSignalingPeer::onConnect(const SockException &ex) { + TraceL; + if (_on_connect) { + _on_connect(ex); + } + if (!ex) { + createResponseExpiredTimer(); + } +} + +void WebRtcSignalingPeer::setOnShutdown(function cb) { + _on_shutdown = cb ? std::move(cb) : [](const SockException &) {}; +} + +void WebRtcSignalingPeer::onShutdown(const SockException &ex) { + TraceL; + if (_on_shutdown) { + _on_shutdown(ex); + } +} + +void WebRtcSignalingPeer::onRecv(const Buffer::Ptr &buffer) { + TraceL << "recv msg:\r\n" << buffer->data(); + + Json::Value args; + Json::Reader reader; + reader.parse(buffer->data(), args); + Parser parser; + HttpAllArgs allArgs(parser, args); + + CHECK_ARGS(METHOD_KEY, TRANSACTION_ID_KEY); + + using MsgHandler = void (WebRtcSignalingPeer::*)(SIGNALING_MSG_ARGS); + static std::unordered_map, MsgHandler, ClassMethodHash> s_msg_handlers; + + static onceToken token([]() { + s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_ACCEPT, METHOD_VALUE_REGISTER), &WebRtcSignalingPeer::handleRegisterAccept); + s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_REJECT, METHOD_VALUE_REGISTER), &WebRtcSignalingPeer::handleRegisterReject); + s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_ACCEPT, METHOD_VALUE_UNREGISTER), &WebRtcSignalingPeer::handleUnregisterAccept); + s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_REJECT, METHOD_VALUE_UNREGISTER), &WebRtcSignalingPeer::handleUnregisterReject); + + s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_REQUEST, METHOD_VALUE_CALL), &WebRtcSignalingPeer::handleCallRequest); + s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_ACCEPT, METHOD_VALUE_CALL), &WebRtcSignalingPeer::handleCallAccept); + s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_REJECT, METHOD_VALUE_CALL), &WebRtcSignalingPeer::handleCallReject); + + s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_INDICATION, METHOD_VALUE_CANDIDATE), &WebRtcSignalingPeer::handleCandidateIndication); + s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_INDICATION, METHOD_VALUE_BYE), &WebRtcSignalingPeer::handleByeIndication); + }); + + auto it = s_msg_handlers.find(std::make_pair(allArgs[CLASS_KEY], allArgs[METHOD_KEY])); + if (it == s_msg_handlers.end()) { + WarnL << "unsupported class: "<< allArgs[CLASS_KEY] << ", method: " << allArgs[METHOD_KEY] << ", ignore"; + return; + } + return (this->*(it->second))(allArgs); +} + +void WebRtcSignalingPeer::onError(const SockException &err) { + WarnL << "room_id: " << _room_id; + s_room_keepers.erase(_room_key); + // 除非对端显式的发送了注销执行,否则因为网络异常导致的会话中断,不影响已经进行通信的webrtc会话,仅作移除 +} + +bool WebRtcSignalingPeer::responseFilter(SIGNALING_MSG_ARGS, ResponseTrigger& trigger) { + if (allArgs[CLASS_KEY] != CLASS_VALUE_ACCEPT && allArgs[CLASS_KEY] != CLASS_VALUE_REJECT) { + return false; + } + + for (auto &pr : _response_list) { + auto &transaction_id = pr.first; + // mismatch transaction_id + if (transaction_id != allArgs[TRANSACTION_ID_KEY] && !transaction_id.empty()) { + continue; + } + + auto &handle = pr.second; + if (allArgs[METHOD_KEY] != handle.method) { + WarnL << "recv response method: " << allArgs[METHOD_KEY] << " mismatch request method: " << handle.method; + return false; + } + + trigger = std::move(handle.cb); + _response_list.erase(transaction_id); + return true; + } + return false; +} + +void WebRtcSignalingPeer::sendRegisterRequest(ResponseTrigger trigger) { + TraceL; + Json::Value body; + body[CLASS_KEY] = CLASS_VALUE_REQUEST; + body[METHOD_KEY] = METHOD_VALUE_REGISTER; + body[ROOM_ID_KEY] = getRoomId(); + sendRequest(body, std::move(trigger)); +} + +void WebRtcSignalingPeer::handleRegisterAccept(SIGNALING_MSG_ARGS) { + TraceL; + ResponseTrigger trigger; + if (!responseFilter(allArgs, trigger)) { + return; + } + + auto jsonArgs = allArgs.getArgs(); + auto ice_servers = jsonArgs[ICE_SERVERS_KEY]; + if (ice_servers.type() != Json::ValueType::arrayValue) { + _StrPrinter msg; + msg << "illegal \"" << ICE_SERVERS_KEY << "\" point"; + WarnL << msg; + trigger(SockException(Err_other, msg), getRoomKey()); + return; + } + + if (ice_servers.empty()) { + _StrPrinter msg; + msg << "no ice server found in \"" << ICE_SERVERS_KEY << "\" point"; + WarnL << msg; + trigger(SockException(Err_other, msg), getRoomKey()); + return; + } + + for (auto &ice_server : ice_servers) { + // only support 1 ice_server now + auto url = ice_server[URL_KEY].asString(); + _ice_server = std::make_shared(url); + _ice_server->_ufrag = ice_server[UFRAG_KEY].asString(); + _ice_server->_pwd = ice_server[PWD_KEY].asString(); + } + + trigger(SockException(Err_success), getRoomKey()); +} + +void WebRtcSignalingPeer::handleRegisterReject(SIGNALING_MSG_ARGS) { + TraceL; + ResponseTrigger trigger; + if (!responseFilter(allArgs, trigger)) { + return; + } + + auto ex = SockException(Err_other, StrPrinter << "register refuses by server, reason: " << allArgs[REASON_KEY]); + trigger(ex, getRoomKey()); + onShutdown(ex); +} + +void WebRtcSignalingPeer::sendUnregisterRequest(ResponseTrigger trigger) { + TraceL; + Json::Value body; + body[CLASS_KEY] = CLASS_VALUE_REQUEST; + body[METHOD_KEY] = METHOD_VALUE_UNREGISTER; + body[ROOM_ID_KEY] = _room_id; + sendRequest(body, std::move(trigger)); +} + +void WebRtcSignalingPeer::handleUnregisterAccept(SIGNALING_MSG_ARGS) { + ResponseTrigger trigger; + if (!responseFilter(allArgs, trigger)) { + return; + } + + trigger(SockException(Err_success), getRoomKey()); +} + +void WebRtcSignalingPeer::handleUnregisterReject(SIGNALING_MSG_ARGS) { + ResponseTrigger trigger; + if (!responseFilter(allArgs, trigger)) { + return; + } + + auto ex = SockException(Err_other, StrPrinter << "unregister refuses by server, reason: " << allArgs[REASON_KEY]); + trigger(ex, getRoomKey()); +} + +void WebRtcSignalingPeer::sendCallRequest(const std::string& peer_room_id, const std::string& guest_id, const MediaTuple &tuple, const std::string& sdp, bool is_play, ResponseTrigger trigger) { + DebugL; + Json::Value body; + body[CLASS_KEY] = CLASS_VALUE_REQUEST; + body[METHOD_KEY] = METHOD_VALUE_CALL; + body[TYPE_KEY] = is_play? TYPE_VALUE_PLAY : TYPE_VALUE_PUSH; + body[GUEST_ID_KEY] = guest_id; //our guest id + body[ROOM_ID_KEY] = peer_room_id; + body[CALL_VHOST_KEY] = tuple.vhost; + body[CALL_APP_KEY] = tuple.app; + body[CALL_STREAM_KEY] = tuple.stream; + body[SDP_KEY] = sdp; + sendRequest(body, std::move(trigger)); +} + +void WebRtcSignalingPeer::sendCallAccept(const std::string& peer_guest_id, const MediaTuple &tuple, const std::string& sdp, bool is_play, const std::string& transaction_id) { + DebugL; + Json::Value body; + body[CLASS_KEY] = CLASS_VALUE_ACCEPT; + body[METHOD_KEY] = METHOD_VALUE_CALL; + body[TRANSACTION_ID_KEY] = transaction_id; + body[TYPE_KEY] = is_play? TYPE_VALUE_PLAY : TYPE_VALUE_PUSH; + body[GUEST_ID_KEY] = peer_guest_id; + body[ROOM_ID_KEY] = _room_id; //our room id + body[CALL_VHOST_KEY] = tuple.vhost; + body[CALL_APP_KEY] = tuple.app; + body[CALL_STREAM_KEY] = tuple.stream; + body[SDP_KEY] = sdp; + sendPacket(body); +} + +void WebRtcSignalingPeer::handleCallRequest(SIGNALING_MSG_ARGS) { + DebugL; + CHECK_ARGS(GUEST_ID_KEY, ROOM_ID_KEY, CALL_VHOST_KEY, CALL_APP_KEY, CALL_STREAM_KEY, TYPE_KEY); + + if (allArgs[ROOM_ID_KEY] != getRoomId()) { + WarnL << "target room_id: " << allArgs[ROOM_ID_KEY] << "mismatch our room_id: " << getRoomId(); + return; + } + + auto args = std::make_shared>(allArgs, allArgs[GUEST_ID_KEY]); + std::weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); + WebRtcPluginManager::Instance().negotiateSdp(*this, allArgs[TYPE_KEY], *args, [allArgs, weak_self](const WebRtcInterface &exchanger) mutable { + if (auto strong_self = weak_self.lock()) { + strong_self->processOffer(allArgs, const_cast(exchanger)); + } + }); +} + +void WebRtcSignalingPeer::handleCallAccept(SIGNALING_MSG_ARGS) { + DebugL; + ResponseTrigger trigger; + if (!responseFilter(allArgs, trigger)) { + return; + } + + CHECK_ARGS(GUEST_ID_KEY, ROOM_ID_KEY, CALL_VHOST_KEY, CALL_APP_KEY, CALL_STREAM_KEY, TYPE_KEY); + + auto room_id = allArgs[ROOM_ID_KEY]; + auto it = _tours.find(room_id); + if (it == _tours.end()) { + WarnL << "not found room_id: " << room_id << " in tours"; + return; + } + + auto &guest_id = it->second.first; + if (allArgs[GUEST_ID_KEY] != guest_id) { + WarnL << "guest_id: " << allArgs[GUEST_ID_KEY] << "mismatch our guest_id: " << guest_id; + return; + } + + trigger(SockException(Err_success), allArgs[SDP_KEY]); +} + +void WebRtcSignalingPeer::handleCallReject(SIGNALING_MSG_ARGS) { + DebugL; + ResponseTrigger trigger; + if (!responseFilter(allArgs, trigger)) { + return; + } + + CHECK_ARGS(GUEST_ID_KEY, ROOM_ID_KEY, CALL_VHOST_KEY, CALL_APP_KEY, CALL_STREAM_KEY, TYPE_KEY); + + auto room_id = allArgs[ROOM_ID_KEY]; + auto it = _tours.find(room_id); + if (it == _tours.end()) { + WarnL << "not found room_id: " << room_id << " in tours"; + return; + } + + auto &guest_id = it->second.first; + if (allArgs[GUEST_ID_KEY] != guest_id) { + WarnL << "guest_id: " << allArgs[GUEST_ID_KEY] << "mismatch our guest_id: " << guest_id; + return; + } + + _tours.erase(room_id); + trigger(SockException(Err_other, StrPrinter << "call refuses by server, reason: " << allArgs[REASON_KEY]), ""); +} + +void WebRtcSignalingPeer::handleCandidateIndication(SIGNALING_MSG_ARGS) { + DebugL; + CHECK_ARGS(GUEST_ID_KEY, ROOM_ID_KEY, CANDIDATE_KEY, UFRAG_KEY, PWD_KEY); + + std::string identifier; + std::string room_id = allArgs[ROOM_ID_KEY]; + std::string guest_id = allArgs[GUEST_ID_KEY]; + //作为被叫 + if (room_id == getRoomId()) { + auto it = _peer_guests.find(guest_id); + if (it == _peer_guests.end()) { + WarnL << "not found guest_id: " << guest_id; + return; + } + identifier = it->second; + } else { + //作为主叫 + for (auto it : _tours) { + if (room_id != it.first) { + continue; + } + + auto info = it.second; + if (guest_id != info.first) { + break; + } + identifier = info.second; + } + } + + TraceL << "recv remote candidate: " << allArgs[CANDIDATE_KEY]; + + if (identifier.empty()) { + WarnL << "target room_id: " << room_id << " not match our room_id: " << getRoomId() + << ", and target guest_id: " << guest_id << " not match"; + return; + } + + auto transport = WebRtcTransportManager::Instance().getItem(identifier); + if (transport) { + SdpAttrCandidate candidate_attr; + candidate_attr.parse(allArgs[CANDIDATE_KEY]); + transport->connectivityCheck(candidate_attr, allArgs[UFRAG_KEY], allArgs[PWD_KEY]); + } + +} + +void WebRtcSignalingPeer::handleByeIndication(SIGNALING_MSG_ARGS) { + DebugL; + CHECK_ARGS(GUEST_ID_KEY, ROOM_ID_KEY); + + if (allArgs[ROOM_ID_KEY] != getRoomId()) { + WarnL << "target room_id: " << allArgs[ROOM_ID_KEY] << "not match our room_id: " << getRoomId(); + return; + } + + auto it = _peer_guests.find(allArgs[GUEST_ID_KEY]); + if (it == _peer_guests.end()) { + WarnL << "not found guest_id: " << allArgs[GUEST_ID_KEY]; + return; + } + + auto identifier = it->second; + _peer_guests.erase(it); + auto obj = WebRtcTransportManager::Instance().getItem(identifier); + if (obj) { + obj->safeShutdown(SockException(Err_shutdown, "deleted by websocket signaling server")); + } +} + +void WebRtcSignalingPeer::sendByeIndication(const std::string& peer_room_id, const std::string &guest_id) { + DebugL; + Json::Value body; + body[CLASS_KEY] = CLASS_VALUE_INDICATION; + body[METHOD_KEY] = METHOD_VALUE_BYE; + body[GUEST_ID_KEY] = guest_id; //our guest id + body[ROOM_ID_KEY] = peer_room_id; + body[SENDER_KEY] = guest_id; + sendIndication(body); +} + +void WebRtcSignalingPeer::sendCandidateIndication(const std::string& transport_identifier, const std::string& candidate, const std::string& ice_ufrag, const std::string& ice_pwd) { + TraceL; + Json::Value body; + body[CLASS_KEY] = CLASS_VALUE_INDICATION; + body[METHOD_KEY] = METHOD_VALUE_CANDIDATE; + body[CANDIDATE_KEY] = candidate; + body[UFRAG_KEY] = ice_ufrag; + body[PWD_KEY] = ice_pwd; + + //作为被叫 + for (auto &pr : _peer_guests) { + if (pr.second == transport_identifier) { + body[ROOM_ID_KEY] = _room_id; + body[GUEST_ID_KEY] = pr.first; //peer_guest_id + body[SENDER_KEY] = _room_id; + return sendIndication(body); + } + } + + //作为主叫 + for (auto &pr : _tours) { + auto &info = pr.second; + if (info.second == transport_identifier) { + body[ROOM_ID_KEY] = pr.first; //peer room id + body[GUEST_ID_KEY] = info.first; //our_guest_id + body[SENDER_KEY] = info.first; + return sendIndication(body); + } + } +} + +void WebRtcSignalingPeer::sendAcceptResponse(const std::string& method, const std::string& transaction_id, const std::string& room_id, + const std::string& guest_id, const std::string& reason) { + // TODO +} + +void WebRtcSignalingPeer::sendRefusesResponse(Json::Value &body, const std::string& transaction_id, const std::string& reason) { + body[CLASS_KEY] = CLASS_VALUE_REJECT; + body[REASON_KEY] = reason; + sendResponse(body, transaction_id); +} + +void WebRtcSignalingPeer::sendRequest(Json::Value& body, ResponseTrigger trigger, float seconds) { + auto transaction_id = makeRandStr(32); + body[TRANSACTION_ID_KEY] = transaction_id; + + ResponseTuple tuple; + tuple.ttl_ms = seconds * 1000; + tuple.method = body[METHOD_KEY].asString(); + tuple.cb = std::move(trigger); + _response_list.emplace(std::move(transaction_id), std::move(tuple)); + sendPacket(body); +} + +void WebRtcSignalingPeer::sendIndication(Json::Value &body) { + auto transaction_id = makeRandStr(32); + body[TRANSACTION_ID_KEY] = transaction_id; + sendPacket(body); +} + +void WebRtcSignalingPeer::sendResponse(Json::Value &body, const std::string& transaction_id) { + body[TRANSACTION_ID_KEY] = transaction_id; + sendPacket(body); +} + +void WebRtcSignalingPeer::sendPacket(Json::Value& body) { + auto msg = body.toStyledString(); + DebugL << "send msg: " << msg; + SockSender::send(std::move(msg)); +} + +Json::Value WebRtcSignalingPeer::makeInfoJson() { + Json::Value item; + item["room_id"] = getRoomId(); + item["room_key"] = getRoomKey(); + + Json::Value peer_guests_obj(Json::arrayValue); + auto peer_guests = _peer_guests; + for(auto &guest : peer_guests) { + Json::Value obj; + obj["guest_id"] = guest.first; + obj["transport_identifier"] = guest.second; + peer_guests_obj.append(std::move(obj)); + } + item["guests"] = std::move(peer_guests_obj); + + Json::Value tours_obj(Json::arrayValue); + auto tours = _tours; + for(auto &tour : tours){ + Json::Value obj; + obj["room_id"] = tour.first; + obj["guest_id"] = tour.second.first; + obj["transport_identifier"] = tour.second.second; + tours_obj.append(std::move(obj)); + } + item["tours"] = std::move(tours_obj); + return item; +} + +void WebRtcSignalingPeer::createResponseExpiredTimer() { + std::weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); + _expire_timer = std::make_shared(0.2, [weak_self]() { + if (auto strong_self = weak_self.lock()) { + strong_self->checkResponseExpired(); + return true; // 继续定时器 + } + return false; + }, getPoller()); +} + +void WebRtcSignalingPeer::checkResponseExpired() { + //FIXME: 移动到专门的超时timer中处理 +#if 0 + // 设置计时器以检测 offer 响应超时 + _offer_timeout_timer = std::make_shared( + timeout_sec, + [this, cb, peer_room_id]() { + _tours.erase(peer_room_id); + return false; // 停止计时器 + }, + getPoller() + ); +#endif + + for (auto it = _response_list.begin(); it != _response_list.end();) { + auto &tuple = it->second; + if (!tuple.expired()) { + ++it; + continue; + } + // over time + WarnL << "transaction_id: " << it->first << ", method: " << tuple.method << " recv response over time"; + tuple.cb(SockException(Err_timeout, "recv response timeout"), ""); + it = _response_list.erase(it); + } +} + +}// namespace mediakit diff --git a/webrtc/WebRtcSignalingPeer.h b/webrtc/WebRtcSignalingPeer.h new file mode 100644 index 00000000..7eaf179f --- /dev/null +++ b/webrtc/WebRtcSignalingPeer.h @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + + +#ifndef ZLMEDIAKIT_WEBRTC_SIGNALING_PEER_H +#define ZLMEDIAKIT_WEBRTC_SIGNALING_PEER_H + +#include +#include "Poller/Timer.h" +#include "Network/Session.h" +#include "Http/WebSocketClient.h" +#include "webrtc/WebRtcSignalingMsg.h" +#include "webrtc/WebRtcTransport.h" + +namespace mediakit { + +class WebRtcSignalingPeer : public WebSocketClient { +public: + struct ClassMethodHash { + bool operator()(std::pair key) const { + std::size_t h = 0; + h ^= std::hash()(key.first) << 0; + h ^= std::hash()(key.second) << 1; + return h; + } + }; + using Ptr = std::shared_ptr; + WebRtcSignalingPeer(const std::string &host, uint16_t port, bool ssl, const std::string &room_id, const toolkit::EventPoller::Ptr &poller = nullptr); + virtual ~WebRtcSignalingPeer(); + + void connect(); + void regist(const std::function &cb); + void unregist(const std::function &cb); + void checkIn(const std::string& peer_room_id, const MediaTuple &tuple, const std::string& identifier, + const std::string& offer, bool is_play, const std::function &cb, float timeout_sec); + void checkOut(const std::string& peer_room_id); + void candidate(const std::string& transport_identifier, const std::string& candidate, const std::string& ice_ufrag, const std::string& ice_pwd); + + void processOffer(SIGNALING_MSG_ARGS, WebRtcInterface &transport); + void answer(const std::string& guest_id, const MediaTuple &tuple, const std::string& identifier, const std::string& sdp, bool is_play, const std::string& transaction_id); + + const std::string& getRoomKey() const { + return _room_key; + } + + const std::string& getRoomId() const { + return _room_id; + } + + const RTC::IceServerInfo::Ptr& getIceServer() const { + return _ice_server; + } + + //// TcpClient override//// + void setOnConnect(std::function cb); + void onConnect(const toolkit::SockException &ex) override; + void setOnShutdown(std::function cb); + void onShutdown(const toolkit::SockException &ex); + void onRecv(const toolkit::Buffer::Ptr &) override; + void onError(const toolkit::SockException &err) override; + + Json::Value makeInfoJson(); + +protected: + void checkResponseExpired(); + void createResponseExpiredTimer(); + + using ResponseTrigger = std::function; + struct ResponseTuple { + toolkit::Ticker ticker; + uint32_t ttl_ms; + std::string method; + ResponseTrigger cb; + + bool expired() { + return ticker.elapsedTime() > ttl_ms; + } + }; + + bool responseFilter(SIGNALING_MSG_ARGS, ResponseTrigger& trigger); + + void sendRegisterRequest(ResponseTrigger trigger); + void handleRegisterAccept(SIGNALING_MSG_ARGS); + + void handleRegisterReject(SIGNALING_MSG_ARGS); + void sendUnregisterRequest(ResponseTrigger trigger); + void handleUnregisterAccept(SIGNALING_MSG_ARGS); + void handleUnregisterReject(SIGNALING_MSG_ARGS); + + void sendCallRequest(const std::string& peer_room_id, const std::string& guest_id, const MediaTuple &tuple, const std::string& sdp, bool is_play, ResponseTrigger trigger); + void sendCallAccept(const std::string& peer_guest_id, const MediaTuple &tuple, const std::string& sdp, bool is_play, const std::string& transaction_id); + void handleCallRequest(SIGNALING_MSG_ARGS); + void handleCallAccept(SIGNALING_MSG_ARGS); + void handleCallReject(SIGNALING_MSG_ARGS); + + void sendCandidateIndication(const std::string& transport_identifier, const std::string& candidate, const std::string& ice_ufrag, const std::string& ice_pwd); + void handleCandidateIndication(SIGNALING_MSG_ARGS); + + void sendByeIndication(const std::string& peer_room_id, const std::string &guest_id); + void handleByeIndication(SIGNALING_MSG_ARGS); + + void sendAcceptResponse(const std::string& method, const std::string& transaction_id, const std::string& room_id, const std::string& guest_id, const std::string& reason); + void sendRefusesResponse(Json::Value &body, const std::string& transaction_id, const std::string& reason); + + void sendIndication(Json::Value &body); + void sendRequest(Json::Value& body, ResponseTrigger trigger, float seconds = 10); + void sendResponse(Json::Value &body, const std::string& transaction_id); + void sendPacket(Json::Value& body); + +private: + toolkit::Timer::Ptr _expire_timer; + std::string _ws_url; + std::string _room_key; + std::string _room_id; + std::unordered_map _peer_guests; //作为被叫 + std::unordered_map> _tours; //作为主叫 + RTC::IceServerInfo::Ptr _ice_server; + std::unordered_map _response_list; + + std::function _on_connect; + std::function _on_shutdown; + toolkit::Timer::Ptr _offer_timeout_timer = nullptr; +}; + +void addWebrtcRoomKeeper(const std::string &host, uint16_t port, const std::string& room_id, bool ssl, + const std::function &cb); +void delWebrtcRoomKeeper(const std::string &key, const std::function &cb); +void listWebrtcRoomKeepers(const std::function &cb); +Json::Value ToJson(const WebRtcSignalingPeer::Ptr& p); +WebRtcSignalingPeer::Ptr getWebrtcRoomKeeper(const std::string &host, uint16_t port); + +} // namespace mediakit + +#endif // ZLMEDIAKIT_WEBRTC_SIGNALING_PEER_H diff --git a/webrtc/WebRtcSignalingSession.cpp b/webrtc/WebRtcSignalingSession.cpp new file mode 100644 index 00000000..4f8e6d79 --- /dev/null +++ b/webrtc/WebRtcSignalingSession.cpp @@ -0,0 +1,488 @@ +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "Util/util.h" +#include "Common/config.h" +#include "WebRtcTransport.h" +#include "WebRtcSignalingMsg.h" +#include "WebRtcSignalingSession.h" + +using namespace std; +using namespace toolkit; +using namespace mediakit::Rtc; + +namespace mediakit { + +// 注册上来的peer列表 +static std::atomic s_room_idx_generate { 1 }; +static ServiceController s_rooms; + +void listWebrtcRooms(const std::function &cb) { + s_rooms.for_each(cb); +} + +Json::Value ToJson(const WebRtcSignalingSession::Ptr &p) { + return p->makeInfoJson(); +} + +WebRtcSignalingSession::Ptr getWebrtcRoomKeeper(const string &room_id) { + return s_rooms.find(room_id); +} + +//////////// WebRtcSignalingSession ////////////////////////// + +WebRtcSignalingSession::WebRtcSignalingSession(const Socket::Ptr &sock) : Session(sock) { + DebugL; +} + +WebRtcSignalingSession::~WebRtcSignalingSession() { + DebugL << "room_id: " << _room_id; +} + +void WebRtcSignalingSession::onRecv(const Buffer::Ptr &buffer) { + DebugL << "recv msg:\r\n" << buffer->data(); + + Json::Value args; + Json::Reader reader; + reader.parse(buffer->data(), args); + Parser parser; + HttpAllArgs allArgs(parser, args); + + using MsgHandler = void (WebRtcSignalingSession::*)(SIGNALING_MSG_ARGS); + static std::unordered_map, MsgHandler, ClassMethodHash> s_msg_handlers; + + static onceToken token([]() { + s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_REQUEST, METHOD_VALUE_REGISTER), &WebRtcSignalingSession::handleRegisterRequest); + s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_REQUEST, METHOD_VALUE_UNREGISTER), &WebRtcSignalingSession::handleUnregisterRequest); + s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_REQUEST, METHOD_VALUE_CALL), &WebRtcSignalingSession::handleCallRequest); + s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_ACCEPT, METHOD_VALUE_CALL), &WebRtcSignalingSession::handleCallAccept); + s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_REJECT, METHOD_VALUE_CALL), &WebRtcSignalingSession::handleCallReject); + s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_INDICATION, METHOD_VALUE_BYE), &WebRtcSignalingSession::handleByeIndication); + s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_INDICATION, METHOD_VALUE_CANDIDATE), &WebRtcSignalingSession::handleCandidateIndication); + }); + + try { + CHECK_ARGS(CLASS_KEY, METHOD_KEY, TRANSACTION_ID_KEY); + auto it = s_msg_handlers.find(std::make_pair(allArgs[CLASS_KEY], allArgs[METHOD_KEY])); + if (it == s_msg_handlers.end()) { + WarnL << " not support class: " << allArgs[CLASS_KEY] << ", method: " << allArgs[METHOD_KEY] << ", ignore"; + return; + } + + (this->*(it->second))(allArgs); + } catch (std::exception &ex) { + WarnL << "process msg fail: " << ex.what(); + } +} + +void WebRtcSignalingSession::onError(const SockException &err) { + WarnL << "room_id: " << _room_id; + notifyByeIndication(); + s_rooms.erase(_room_id); +} + +void WebRtcSignalingSession::onManager() { + // Websocket会话会自行定时发送PING/PONG 消息,并进行超时自己管理,该对象暂时不需要心跳超时处理 +} + +void WebRtcSignalingSession::handleRegisterRequest(SIGNALING_MSG_ARGS) { + DebugL; + + std::string room_id; + Json::Value body; + body[METHOD_KEY] = METHOD_VALUE_REGISTER; + + // 如果客户端没有提供 room_id,服务端自动分配一个 + if (allArgs[ROOM_ID_KEY].empty()) { + auto idx = s_room_idx_generate.fetch_add(1); + room_id = std::to_string(idx) + "_" + makeRandStr(16); + DebugL << "auto generated room_id: " << room_id; + } else { + room_id = allArgs[ROOM_ID_KEY]; + if (s_rooms.find(room_id)) { + // 已经注册了 + body[ROOM_ID_KEY] = room_id; + return sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], "room id conflict"); + } + } + + body[ROOM_ID_KEY] = room_id; + + _room_id = room_id; + s_rooms.emplace(_room_id, shared_from_this()); + sendRegisterAccept(body, allArgs[TRANSACTION_ID_KEY]); +} + +void WebRtcSignalingSession::handleUnregisterRequest(SIGNALING_MSG_ARGS) { + DebugL; + CHECK_ARGS(ROOM_ID_KEY); + + Json::Value body; + body[METHOD_KEY] = METHOD_VALUE_UNREGISTER; + body[ROOM_ID_KEY] = allArgs[ROOM_ID_KEY]; + + if (_room_id.empty()) { + return sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], "unregistered"); + } + + if (allArgs[ROOM_ID_KEY] != getRoomId()) { + return sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], StrPrinter << "room_id: \"" << allArgs[ROOM_ID_KEY] << "\" not match room_id:" << getRoomId()); + } + + sendAcceptResponse(body, allArgs[TRANSACTION_ID_KEY]); + + // 同时主动向所有连接的对端会话发送bye + notifyByeIndication(); + + if (s_rooms.find(_room_id)) { + s_rooms.erase(_room_id); + } +} + +void WebRtcSignalingSession::handleCallRequest(SIGNALING_MSG_ARGS) { + DebugL; + CHECK_ARGS(TRANSACTION_ID_KEY, GUEST_ID_KEY, ROOM_ID_KEY, CALL_VHOST_KEY, CALL_APP_KEY, CALL_STREAM_KEY, TYPE_KEY, SDP_KEY); + + Json::Value body; + body[METHOD_KEY] = METHOD_VALUE_CALL; + body[ROOM_ID_KEY] = allArgs[ROOM_ID_KEY]; + body[GUEST_ID_KEY] = allArgs[GUEST_ID_KEY]; + body[CALL_VHOST_KEY] = allArgs[CALL_VHOST_KEY]; + body[CALL_APP_KEY] = allArgs[CALL_APP_KEY]; + body[CALL_STREAM_KEY] = allArgs[CALL_STREAM_KEY]; + body[TYPE_KEY] = allArgs[TYPE_KEY]; + if (_room_id.empty()) { + return sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], "should register first"); + } + auto peer_id = allArgs[ROOM_ID_KEY]; + auto session = getWebrtcRoomKeeper(peer_id); + if (!session) { + return sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], StrPrinter << "room_id: \"" << peer_id << "\" unregistered"); + } + + _tours.emplace(allArgs[GUEST_ID_KEY], peer_id); + // forwardOffer + weak_ptr sender_ptr = static_pointer_cast(shared_from_this()); + session->forwardCallRequest(sender_ptr, allArgs); +} + +void WebRtcSignalingSession::handleCallAccept(SIGNALING_MSG_ARGS) { + DebugL; + CHECK_ARGS(GUEST_ID_KEY, ROOM_ID_KEY, CALL_VHOST_KEY, CALL_APP_KEY, CALL_STREAM_KEY); + + Json::Value body; + body[ROOM_ID_KEY] = allArgs[ROOM_ID_KEY]; + + if (_room_id.empty()) { + return sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], "should register first"); + } + + auto it = _guests.find(allArgs[GUEST_ID_KEY]); + if (it == _guests.end()) { + WarnL << "guest_id: \"" << allArgs[GUEST_ID_KEY] << "\" not register"; + return; + } + auto session = it->second.lock(); + if (!session) { + WarnL << "guest_id: \"" << allArgs[GUEST_ID_KEY] << "\" leave alreadly"; + return; + } + + session->forwardCallAccept(allArgs); +} + +void WebRtcSignalingSession::handleByeIndication(SIGNALING_MSG_ARGS) { + DebugL; + CHECK_ARGS(GUEST_ID_KEY, ROOM_ID_KEY); + auto guest_id = allArgs[GUEST_ID_KEY]; + + Json::Value body; + body[METHOD_KEY] = METHOD_VALUE_BYE; + body[ROOM_ID_KEY] = allArgs[ROOM_ID_KEY]; + body[GUEST_ID_KEY] = guest_id; + if (_room_id.empty()) { + return sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], "should register first"); + } + if (allArgs[ROOM_ID_KEY] == getRoomId()) { + // 作为被叫方,接收bye + auto it = _guests.find(guest_id); + if (it == _guests.end()) { + WarnL << "guest_id: \"" << guest_id << "\" not register"; + return; + } + auto session = it->second.lock(); + if (!session) { + WarnL << "guest_id: \"" << guest_id << "\" leave alreadly"; + return; + } + _guests.erase(guest_id); + session->forwardBye(allArgs); + } else { + // 作为主叫方,接受bye + auto session = getWebrtcRoomKeeper(allArgs[ROOM_ID_KEY]); + if (!session) { + WarnL << "room_id: \"" << allArgs[ROOM_ID_KEY] << "\" not register"; + return; + } + _tours.erase(guest_id); + session->forwardBye(allArgs); + } +} + +void WebRtcSignalingSession::handleCandidateIndication(SIGNALING_MSG_ARGS) { + DebugL; + CHECK_ARGS(TRANSACTION_ID_KEY, GUEST_ID_KEY, ROOM_ID_KEY, CANDIDATE_KEY, UFRAG_KEY, PWD_KEY); + + Json::Value body; + body[METHOD_KEY] = METHOD_VALUE_CANDIDATE; + body[ROOM_ID_KEY] = allArgs[ROOM_ID_KEY]; + + if (_room_id.empty()) { + sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], "should register first"); + } else { + handleOtherMsg(allArgs); + } +} + +void WebRtcSignalingSession::handleOtherMsg(SIGNALING_MSG_ARGS) { + DebugL; + if (allArgs[ROOM_ID_KEY] == getRoomId()) { + // 作为被叫方,接收bye + auto guest_id = allArgs[GUEST_ID_KEY]; + auto it = _guests.find(guest_id); + if (it == _guests.end()) { + WarnL << "guest_id: \"" << guest_id << "\" not register"; + return; + } + auto session = it->second.lock(); + if (!session) { + WarnL << "guest_id: \"" << guest_id << "\" leave alreadly"; + return; + } + + session->forwardPacket(allArgs); + } else { + // 作为主叫方,接受bye + auto session = getWebrtcRoomKeeper(allArgs[ROOM_ID_KEY]); + if (!session) { + WarnL << "room_id: \"" << allArgs[ROOM_ID_KEY] << "\" not register"; + return; + } + session->forwardPacket(allArgs); + } +} + +void WebRtcSignalingSession::notifyByeIndication() { + DebugL; + + Json::Value allArgs; + allArgs[CLASS_KEY] = CLASS_VALUE_INDICATION; + allArgs[METHOD_KEY] = METHOD_VALUE_BYE; + allArgs[REASON_KEY] = "peer unregister"; + // 作为被叫方 + for (auto it : _guests) { + auto session = it.second.lock(); + if (session) { + allArgs[TRANSACTION_ID_KEY] = makeRandStr(32); + allArgs[GUEST_ID_KEY] = it.first; + allArgs[ROOM_ID_KEY] = getRoomId(); + session->forwardBye(allArgs); + } + } + + // 作为主叫方 + for (auto it : _tours) { + auto guest_id = it.first; + auto peer_room_id = it.second; + auto session = getWebrtcRoomKeeper(peer_room_id); + if (session) { + allArgs[TRANSACTION_ID_KEY] = makeRandStr(32); + allArgs[GUEST_ID_KEY] = guest_id; + allArgs[ROOM_ID_KEY] = peer_room_id; + session->forwardBye(allArgs); + } + } +} + +void WebRtcSignalingSession::forwardCallRequest(WebRtcSignalingSession::WeakPtr sender, SIGNALING_MSG_ARGS) { + DebugL; + WeakPtr weak_self = std::static_pointer_cast(shared_from_this()); + getPoller()->async([weak_self, sender, allArgs]() { + if (auto strong_self = weak_self.lock()) { + strong_self->_guests.emplace(allArgs[GUEST_ID_KEY], sender); + strong_self->sendPacket(allArgs.getArgs()); + } + }); +} + +void WebRtcSignalingSession::forwardCallAccept(SIGNALING_MSG_ARGS) { + DebugL; + WeakPtr weak_self = std::static_pointer_cast(shared_from_this()); + getPoller()->async([weak_self, allArgs]() { + if (auto strong_self = weak_self.lock()) { + strong_self->sendPacket(allArgs.getArgs()); + } + }); +} + +void WebRtcSignalingSession::forwardBye(SIGNALING_MSG_ARGS) { + DebugL; + WeakPtr weak_self = std::static_pointer_cast(shared_from_this()); + getPoller()->async([weak_self, allArgs]() { + if (auto strong_self = weak_self.lock()) { + if (allArgs[ROOM_ID_KEY] == strong_self->getRoomId()) { + // 作为被叫 + strong_self->_guests.erase(allArgs[GUEST_ID_KEY]); + } else { + // 作为主叫 + strong_self->_tours.erase(allArgs[GUEST_ID_KEY]); + } + strong_self->sendPacket(allArgs.getArgs()); + } + }); +} + +void WebRtcSignalingSession::forwardBye(Json::Value allArgs) { + DebugL; + WeakPtr weak_self = std::static_pointer_cast(shared_from_this()); + getPoller()->async([weak_self, allArgs]() { + if (auto strong_self = weak_self.lock()) { + if (allArgs[ROOM_ID_KEY] == strong_self->getRoomId()) { + // 作为被叫 + strong_self->_guests.erase(allArgs[GUEST_ID_KEY].asString()); + } else { + // 作为主叫 + strong_self->_tours.erase(allArgs[GUEST_ID_KEY].asString()); + } + strong_self->sendPacket(allArgs); + } + }); +} + +void WebRtcSignalingSession::forwardPacket(SIGNALING_MSG_ARGS) { + WeakPtr weak_self = std::static_pointer_cast(shared_from_this()); + getPoller()->async([weak_self, allArgs]() { + if (auto strong_self = weak_self.lock()) { + strong_self->sendPacket(allArgs.getArgs()); + } + }); +} + +void WebRtcSignalingSession::sendRegisterAccept(Json::Value& body, const std::string& transaction_id) { + DebugL; + body[CLASS_KEY] = CLASS_VALUE_ACCEPT; + + Json::Value ice_server; + GET_CONFIG(uint16_t, icePort, Rtc::kIcePort); + GET_CONFIG(bool, enable_turn, Rtc::kEnableTurn); + GET_CONFIG(string, iceUfrag, Rtc::kIceUfrag); + GET_CONFIG(string, icePwd, Rtc::kIcePwd); + GET_CONFIG_FUNC(std::vector, extern_ips, Rtc::kExternIP, [](string str) { + std::vector ret; + if (str.length()) { + ret = split(str, ","); + } + translateIPFromEnv(ret); + return ret; + }); + + // 如果配置了extern_ips, 则选择第一个作为turn服务器的ip + // 如果没配置获取网卡接口 + std::string extern_ip; + if (!extern_ips.empty()) { + extern_ip = extern_ips.front(); + } else { + extern_ip = SockUtil::get_local_ip(); + } + + // TODO: support multi extern ip + // TODO: support third stun/turn server + + std::string url; + // SUPPORT: + // stun:host:port?transport=udp + // turn:host:port?transport=udp + + // NOT SUPPORT NOW TODO: + // turns:host:port?transport=udp + // turn:host:port?transport=tcp + // turns:host:port?transport=tcp + // stuns:host:port?transport=udp + // stuns:host:port?transport=udp + // stun:host:port?transport=tcp + if (enable_turn) { + url = "turn:" + extern_ip + ":" + std::to_string(icePort) + "?transport=udp"; + } else { + url = "stun:" + extern_ip + ":" + std::to_string(icePort) + "?transport=udp"; + } + + ice_server[URL_KEY] = url; + ice_server[UFRAG_KEY] = iceUfrag; + ice_server[PWD_KEY] = icePwd; + + Json::Value ice_servers; + ice_servers.append(ice_server); + + body[ICE_SERVERS_KEY] = ice_servers; + + sendAcceptResponse(body, transaction_id); +} + +void WebRtcSignalingSession::sendAcceptResponse(Json::Value &body, const std::string &transaction_id) { + TraceL; + body[CLASS_KEY] = CLASS_VALUE_ACCEPT; + return sendResponse(body, transaction_id); +} + +void WebRtcSignalingSession::sendRejectResponse(Json::Value &body, const std::string &transaction_id, const std::string &reason) { + DebugL; + body[CLASS_KEY] = CLASS_VALUE_REJECT; + body[REASON_KEY] = reason; + return sendResponse(body, transaction_id); +} + +void WebRtcSignalingSession::sendResponse(Json::Value &body, const std::string &transaction_id) { + DebugL; + body[TRANSACTION_ID_KEY] = transaction_id; + return sendPacket(body); +} + +void WebRtcSignalingSession::sendPacket(const Json::Value &body) { + auto msg = body.toStyledString(); + TraceL << "send msg: " << msg; + SockSender::send(msg); +} + +Json::Value WebRtcSignalingSession::makeInfoJson() { + Json::Value item; + item["room_id"] = getRoomId(); + + Json::Value tours_obj(Json::arrayValue); + auto tours = _tours; + for (auto &tour : tours) { + Json::Value obj; + obj["guest_id"] = tour.first; + obj["room_id"] = tour.second; + tours_obj.append(std::move(obj)); + } + item["tours"] = std::move(tours_obj); + + Json::Value guests_obj(Json::arrayValue); + auto guests = _guests; + for (auto &guest : guests) { + Json::Value obj; + obj["guest_id"] = guest.first; + guests_obj.append(std::move(obj)); + } + item["guests"] = std::move(guests_obj); + return item; +} + +} // namespace mediakit diff --git a/webrtc/WebRtcSignalingSession.h b/webrtc/WebRtcSignalingSession.h new file mode 100644 index 00000000..557895a0 --- /dev/null +++ b/webrtc/WebRtcSignalingSession.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLMEDIAKIT_WEBRTC_SIGNALING_SESSION_H +#define ZLMEDIAKIT_WEBRTC_SIGNALING_SESSION_H + +#include "Network/Session.h" +#include "Http/WebSocketSession.h" +#include "webrtc/WebRtcSignalingMsg.h" + +namespace mediakit { + +// webrtc 信令, 基于websocket实现 +class WebRtcSignalingSession : public toolkit::Session { +public: + struct ClassMethodHash { + bool operator()(std::pair key) const { + std::size_t h = 0; + h ^= std::hash()(key.first) << 0; + h ^= std::hash()(key.second) << 1; + return h; + } + }; + + using Ptr = std::shared_ptr; + using WeakPtr = std::weak_ptr; + + WebRtcSignalingSession(const toolkit::Socket::Ptr &sock); + virtual ~WebRtcSignalingSession(); + + Json::Value makeInfoJson(); + + std::string getRoomId() { return _room_id; }; + + //// Session override//// + void onRecv(const toolkit::Buffer::Ptr &) override; + void onError(const toolkit::SockException &err) override; + void onManager() override; + +protected: + void handleRegisterRequest(SIGNALING_MSG_ARGS); + void handleUnregisterRequest(SIGNALING_MSG_ARGS); + void handleCallRequest(SIGNALING_MSG_ARGS); + void handleCallAccept(SIGNALING_MSG_ARGS); + #define handleCallReject handleCallAccept + void handleByeIndication(SIGNALING_MSG_ARGS); + void handleCandidateIndication(SIGNALING_MSG_ARGS); + void handleOtherMsg(SIGNALING_MSG_ARGS); + + void notifyByeIndication(); + void forwardCallRequest(WebRtcSignalingSession::WeakPtr sender, SIGNALING_MSG_ARGS); + void forwardCallAccept(SIGNALING_MSG_ARGS); + void forwardBye(SIGNALING_MSG_ARGS); + void forwardBye(Json::Value allArgs); + void forwardPacket(SIGNALING_MSG_ARGS); + + void sendRegisterAccept(Json::Value& body, const std::string& transaction_id); + void sendAcceptResponse(Json::Value &body, const std::string& transaction_id); + void sendRejectResponse(Json::Value &body, const std::string& transaction_id, const std::string& reason); + + void sendResponse(Json::Value &body, const std::string& transaction_id); + void sendPacket(const Json::Value &body); + +private: + std::string _room_id; // + std::unordered_map _tours; //作为主叫 + std::unordered_map _guests; //作为被叫 +}; + +using WebRtcWebcosktSignalingSession = WebSocketSession; +using WebRtcWebcosktSignalSslSession = WebSocketSession; + +void listWebrtcRooms(const std::function &cb); +Json::Value ToJson(const WebRtcSignalingSession::Ptr& p); +WebRtcSignalingSession::Ptr getWebrtcRoomKeeper(const std::string &room_id); +}// namespace mediakit + +#endif //ZLMEDIAKIT_WEBRTC_SIGNALING_SESSION_H diff --git a/webrtc/WebRtcTransport.cpp b/webrtc/WebRtcTransport.cpp index b2b822a2..acba72e5 100644 --- a/webrtc/WebRtcTransport.cpp +++ b/webrtc/WebRtcTransport.cpp @@ -1,1545 +1,1867 @@ -/* - * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. - * - * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). - * - * Use of this source code is governed by MIT-like license that can be found in the - * LICENSE file in the root of the source tree. All contributing project authors - * may be found in the AUTHORS file in the root of the source tree. - */ - -#include -#include -#include "Util/base64.h" -#include "Network/sockutil.h" -#include "Common/config.h" -#include "Nack.h" -#include "RtpExt.h" -#include "Rtcp/Rtcp.h" -#include "Rtcp/RtcpFCI.h" -#include "Rtcp/RtcpContext.h" -#include "Rtsp/Rtsp.h" -#include "Rtsp/RtpReceiver.h" -#include "WebRtcTransport.h" - -#include "WebRtcEchoTest.h" -#include "WebRtcPlayer.h" -#include "WebRtcPusher.h" -#include "Rtsp/RtspMediaSourceImp.h" - -#define RTP_SSRC_OFFSET 1 -#define RTX_SSRC_OFFSET 2 -#define RTP_CNAME "zlmediakit-rtp" -#define RTP_LABEL "zlmediakit-label" -#define RTP_MSLABEL "zlmediakit-mslabel" - -using namespace std; - -namespace mediakit { - -// RTC配置项目 [AUTO-TRANSLATED:19940011] -// RTC configuration project -namespace Rtc { -#define RTC_FIELD "rtc." -// rtp和rtcp接受超时时间 [AUTO-TRANSLATED:0f318cc0] -// rtp and rtcp receive timeout -const string kTimeOutSec = RTC_FIELD "timeoutSec"; -// 服务器外网ip [AUTO-TRANSLATED:23283ba6] -// Server external network ip -const string kExternIP = RTC_FIELD "externIP"; -// 设置remb比特率,非0时关闭twcc并开启remb。该设置在rtc推流时有效,可以控制推流画质 [AUTO-TRANSLATED:412801db] -// Set remb bitrate, when it is not 0, turn off twcc and turn on remb. This setting is valid when rtc pushes the stream, and can control the pushing stream quality -const string kRembBitRate = RTC_FIELD "rembBitRate"; -// webrtc单端口udp服务器 [AUTO-TRANSLATED:d17271ea] -// webrtc single-port udp server -const string kPort = RTC_FIELD "port"; -const string kTcpPort = RTC_FIELD "tcpPort"; - -// 比特率设置 [AUTO-TRANSLATED:2c75f5bc] -// Bitrate setting -const string kStartBitrate = RTC_FIELD "start_bitrate"; -const string kMaxBitrate = RTC_FIELD "max_bitrate"; -const string kMinBitrate = RTC_FIELD "min_bitrate"; - -// 数据通道设置 [AUTO-TRANSLATED:2dc48bc3] -// Data channel setting -const string kDataChannelEcho = RTC_FIELD "datachannel_echo"; - -static onceToken token([]() { - mINI::Instance()[kTimeOutSec] = 15; - mINI::Instance()[kExternIP] = ""; - mINI::Instance()[kRembBitRate] = 0; - mINI::Instance()[kPort] = 8000; - mINI::Instance()[kTcpPort] = 8000; - - mINI::Instance()[kStartBitrate] = 0; - mINI::Instance()[kMaxBitrate] = 0; - mINI::Instance()[kMinBitrate] = 0; - - mINI::Instance()[kDataChannelEcho] = true; -}); - -} // namespace RTC - -static atomic s_key { 0 }; - -static void translateIPFromEnv(std::vector &v) { - for (auto iter = v.begin(); iter != v.end();) { - if (start_with(*iter, "$")) { - auto ip = toolkit::getEnv(*iter); - if (ip.empty()) { - iter = v.erase(iter); - } else { - *iter++ = ip; - } - } else { - ++iter; - } - } -} - -static std::string getServerPrefix() { - // stun_user_name格式: base64(ip+udp_port+tcp_port) + _ + number [AUTO-TRANSLATED:cc3c5902] - // stun_user_name format: base64(ip+udp_port+tcp_port) + _ + number - // 其中ip为二进制char[4], udp_port/tcp_port为大端 uint16. [AUTO-TRANSLATED:92ea5521] - // Where ip is binary char[4], udp_port/tcp_port is big-endian uint16. - // number为自增长数,确保短时间内唯一 [AUTO-TRANSLATED:d31aada9] - // number is an auto-incrementing number, ensuring uniqueness in a short period of time - GET_CONFIG(uint16_t, udp_port, Rtc::kPort); - GET_CONFIG(uint16_t, tcp_port, Rtc::kTcpPort); - char buf[8]; - auto host = SockUtil::get_local_ip(); - auto addr = SockUtil::make_sockaddr(host.data(), udp_port); - // 拷贝ipv4地址 [AUTO-TRANSLATED:49c16eed] - // Copy ipv4 address - memcpy(buf, &(reinterpret_cast(&addr)->sin_addr), 4); - // 拷贝udp端口 [AUTO-TRANSLATED:ebb750d3] - // Copy udp port - memcpy(buf + 4, &(reinterpret_cast(&addr)->sin_port), 2); - // tcp端口转大端模式 [AUTO-TRANSLATED:4f2293de] - // Convert tcp port to big-endian mode - addr = SockUtil::make_sockaddr(host.data(), tcp_port); - // 拷贝tcp端口 [AUTO-TRANSLATED:23191878] - // Copy tcp port - memcpy(buf + 6, &(reinterpret_cast(&addr)->sin_port), 2); - auto ret = encodeBase64(string(buf, 8)) + '_'; - InfoL << "MediaServer(" << host << ":" << udp_port << ":" << tcp_port << ") prefix: " << ret; - return ret; -} - -const char* sockTypeStr(Session* session) { - if (session) { - switch (session->getSock()->sockType()) { - case SockNum::Sock_TCP: return "tcp"; - case SockNum::Sock_UDP: return "udp"; - default: break; - } - } - return "unknown"; -} - -WebRtcTransport::WebRtcTransport(const EventPoller::Ptr &poller) { - _poller = poller; - static auto prefix = getServerPrefix(); - _identifier = prefix + to_string(++s_key); - _packet_pool.setSize(64); -} - -void WebRtcTransport::onCreate() { - _dtls_transport = std::make_shared(_poller, this); - _ice_server = std::make_shared(this, _identifier, makeRandStr(24)); -} - -void WebRtcTransport::onDestory() { -#ifdef ENABLE_SCTP - _sctp = nullptr; -#endif - _dtls_transport = nullptr; - _ice_server = nullptr; -} - -const EventPoller::Ptr &WebRtcTransport::getPoller() const { - return _poller; -} - -const string &WebRtcTransport::getIdentifier() const { - return _identifier; -} - -const std::string& WebRtcTransport::deleteRandStr() const { - if (_delete_rand_str.empty()) { - _delete_rand_str = makeRandStr(32); - } - return _delete_rand_str; -} - -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -void WebRtcTransport::OnIceServerSendStunPacket( - const RTC::IceServer *iceServer, const RTC::StunPacket *packet, RTC::TransportTuple *tuple) { - sendSockData((char *)packet->GetData(), packet->GetSize(), tuple); -} - -void WebRtcTransportImp::OnIceServerSelectedTuple(const RTC::IceServer *iceServer, RTC::TransportTuple *tuple) { - InfoL << getIdentifier() << " select tuple " << sockTypeStr(tuple) << " " << tuple->get_peer_ip() << ":" << tuple->get_peer_port(); - tuple->setSendFlushFlag(false); - unrefSelf(); -} - -void WebRtcTransport::OnIceServerConnected(const RTC::IceServer *iceServer) { - InfoL << getIdentifier(); -} - -void WebRtcTransport::OnIceServerCompleted(const RTC::IceServer *iceServer) { - InfoL << getIdentifier(); - if (_answer_sdp->media[0].role == DtlsRole::passive) { - _dtls_transport->Run(RTC::DtlsTransport::Role::SERVER); - } else { - _dtls_transport->Run(RTC::DtlsTransport::Role::CLIENT); - } -} - -void WebRtcTransport::OnIceServerDisconnected(const RTC::IceServer *iceServer) { - InfoL << getIdentifier(); -} - -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -void WebRtcTransport::OnDtlsTransportConnected( - const RTC::DtlsTransport *dtlsTransport, RTC::SrtpSession::CryptoSuite srtpCryptoSuite, uint8_t *srtpLocalKey, - size_t srtpLocalKeyLen, uint8_t *srtpRemoteKey, size_t srtpRemoteKeyLen, std::string &remoteCert) { - InfoL << getIdentifier(); - _srtp_session_send = std::make_shared( - RTC::SrtpSession::Type::OUTBOUND, srtpCryptoSuite, srtpLocalKey, srtpLocalKeyLen); - _srtp_session_recv = std::make_shared( - RTC::SrtpSession::Type::INBOUND, srtpCryptoSuite, srtpRemoteKey, srtpRemoteKeyLen); -#ifdef ENABLE_SCTP - _sctp = std::make_shared(getPoller(), this, 128, 128, 262144, true); - _sctp->TransportConnected(); -#endif - onStartWebRTC(); -} - -#pragma pack(push, 1) -struct DtlsHeader { - uint8_t content_type; - uint16_t dtls_version; - uint16_t epoch; - uint8_t seq[6]; - uint16_t length; - uint8_t payload[1]; -}; -#pragma pack(pop) - -void WebRtcTransport::OnDtlsTransportSendData( - const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) { - size_t offset = 0; - while(offset < len) { - auto *header = reinterpret_cast(data + offset); - auto length = ntohs(header->length) + offsetof(DtlsHeader, payload); - sendSockData((char *)data + offset, length, nullptr); - offset += length; - } -} - -void WebRtcTransport::OnDtlsTransportConnecting(const RTC::DtlsTransport *dtlsTransport) { - InfoL << getIdentifier(); -} - -void WebRtcTransport::OnDtlsTransportFailed(const RTC::DtlsTransport *dtlsTransport) { - InfoL << getIdentifier(); - onShutdown(SockException(Err_shutdown, "dtls transport failed")); -} - -void WebRtcTransport::OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) { - InfoL << getIdentifier(); - onShutdown(SockException(Err_shutdown, "dtls close notify received")); -} - -void WebRtcTransport::OnDtlsTransportApplicationDataReceived( - const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) { -#ifdef ENABLE_SCTP - _sctp->ProcessSctpData(data, len); -#else - InfoL << hexdump(data, len); -#endif -} - -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#ifdef ENABLE_SCTP -void WebRtcTransport::OnSctpAssociationConnecting(RTC::SctpAssociation *sctpAssociation) { - TraceL << getIdentifier(); - try { - NOTICE_EMIT(BroadcastRtcSctpConnectArgs, Broadcast::kBroadcastRtcSctpConnecting, *this); - } catch (std::exception &ex) { - WarnL << "Exception occurred: " << ex.what(); - } -} - -void WebRtcTransport::OnSctpAssociationConnected(RTC::SctpAssociation *sctpAssociation) { - InfoL << getIdentifier(); - try { - NOTICE_EMIT(BroadcastRtcSctpConnectArgs, Broadcast::kBroadcastRtcSctpConnected, *this); - } catch (std::exception &ex) { - WarnL << "Exception occurred: " << ex.what(); - } -} - -void WebRtcTransport::OnSctpAssociationFailed(RTC::SctpAssociation *sctpAssociation) { - WarnL << getIdentifier(); - try { - NOTICE_EMIT(BroadcastRtcSctpConnectArgs, Broadcast::kBroadcastRtcSctpFailed, *this); - } catch (std::exception &ex) { - WarnL << "Exception occurred: " << ex.what(); - } -} - -void WebRtcTransport::OnSctpAssociationClosed(RTC::SctpAssociation *sctpAssociation) { - InfoL << getIdentifier(); - try { - NOTICE_EMIT(BroadcastRtcSctpConnectArgs, Broadcast::kBroadcastRtcSctpClosed, *this); - } catch (std::exception &ex) { - WarnL << "Exception occurred: " << ex.what(); - } -} - -void WebRtcTransport::OnSctpAssociationSendData( - RTC::SctpAssociation *sctpAssociation, const uint8_t *data, size_t len) { - try { - NOTICE_EMIT(BroadcastRtcSctpSendArgs, Broadcast::kBroadcastRtcSctpSend, *this, data, len); - } catch (std::exception &ex) { - WarnL << "Exception occurred: " << ex.what(); - } - _dtls_transport->SendApplicationData(data, len); -} - -void WebRtcTransport::OnSctpAssociationMessageReceived( - RTC::SctpAssociation *sctpAssociation, uint16_t streamId, uint32_t ppid, const uint8_t *msg, size_t len) { - InfoL << getIdentifier() << " " << streamId << " " << ppid << " " << len << " " << string((char *)msg, len); - RTC::SctpStreamParameters params; - params.streamId = streamId; - - GET_CONFIG(bool, datachannel_echo, Rtc::kDataChannelEcho); - if (datachannel_echo) { - // 回显数据 [AUTO-TRANSLATED:7868d3a4] - // Echo data - _sctp->SendSctpMessage(params, ppid, msg, len); - } - - try { - NOTICE_EMIT(BroadcastRtcSctpReceivedArgs, Broadcast::kBroadcastRtcSctpReceived, *this, streamId, ppid, msg, len); - } catch (std::exception &ex) { - WarnL << "Exception occurred: " << ex.what(); - } -} -#endif - -void WebRtcTransport::sendDatachannel(uint16_t streamId, uint32_t ppid, const char *msg, size_t len) { -#ifdef ENABLE_SCTP - if (_sctp) { - RTC::SctpStreamParameters params; - params.streamId = streamId; - _sctp->SendSctpMessage(params, ppid, (uint8_t *)msg, len); - } -#else - WarnL << "WebRTC datachannel disabled!"; -#endif -} - -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -void WebRtcTransport::sendSockData(const char *buf, size_t len, RTC::TransportTuple *tuple) { - auto pkt = _packet_pool.obtain2(); - pkt->assign(buf, len); - onSendSockData(std::move(pkt), true, tuple ? tuple : _ice_server->GetSelectedTuple()); -} - -Session::Ptr WebRtcTransport::getSession() const { - auto tuple = _ice_server ? _ice_server->GetSelectedTuple(true) : nullptr; - return tuple ? static_pointer_cast(tuple->shared_from_this()) : nullptr; -} - -void WebRtcTransport::sendRtcpRemb(uint32_t ssrc, size_t bit_rate) { - auto remb = FCI_REMB::create({ ssrc }, (uint32_t)bit_rate); - auto fb = RtcpFB::create(PSFBType::RTCP_PSFB_REMB, remb.data(), remb.size()); - fb->ssrc = htonl(0); - fb->ssrc_media = htonl(ssrc); - sendRtcpPacket((char *)fb.get(), fb->getSize(), true); -} - -void WebRtcTransport::sendRtcpPli(uint32_t ssrc) { - auto pli = RtcpFB::create(PSFBType::RTCP_PSFB_PLI); - pli->ssrc = htonl(0); - pli->ssrc_media = htonl(ssrc); - sendRtcpPacket((char *)pli.get(), pli->getSize(), true); -} - -string getFingerprint(const string &algorithm_str, const std::shared_ptr &transport) { - auto algorithm = RTC::DtlsTransport::GetFingerprintAlgorithm(algorithm_str); - for (auto &finger_prints : transport->GetLocalFingerprints()) { - if (finger_prints.algorithm == algorithm) { - return finger_prints.value; - } - } - throw std::invalid_argument(StrPrinter << "不支持的加密算法:" << algorithm_str); -} - -void WebRtcTransport::setRemoteDtlsFingerprint(const RtcSession &remote) { - // 设置远端dtls签名 [AUTO-TRANSLATED:746d5f9c] - // Set remote dtls signature - RTC::DtlsTransport::Fingerprint remote_fingerprint; - remote_fingerprint.algorithm - = RTC::DtlsTransport::GetFingerprintAlgorithm(_offer_sdp->media[0].fingerprint.algorithm); - remote_fingerprint.value = _offer_sdp->media[0].fingerprint.hash; - _dtls_transport->SetRemoteFingerprint(remote_fingerprint); -} - -void WebRtcTransport::onRtcConfigure(RtcConfigure &configure) const { - SdpAttrFingerprint fingerprint; - fingerprint.algorithm = _offer_sdp->media[0].fingerprint.algorithm; - fingerprint.hash = getFingerprint(fingerprint.algorithm, _dtls_transport); - configure.setDefaultSetting( - _ice_server->GetUsernameFragment(), _ice_server->GetPassword(), RtpDirection::sendrecv, fingerprint); - - // 开启remb后关闭twcc,因为开启twcc后remb无效 [AUTO-TRANSLATED:8a8feca2] - // Turn off twcc after turning on remb, because remb is invalid after turning on twcc - GET_CONFIG(size_t, remb_bit_rate, Rtc::kRembBitRate); - configure.enableTWCC(!remb_bit_rate); -} - -static void setSdpBitrate(RtcSession &sdp) { - GET_CONFIG(size_t, max_bitrate, Rtc::kMaxBitrate); - GET_CONFIG(size_t, min_bitrate, Rtc::kMinBitrate); - GET_CONFIG(size_t, start_bitrate, Rtc::kStartBitrate); - - auto m = (RtcMedia *)(sdp.getMedia(TrackType::TrackVideo)); - if (m) { - auto &plan = m->plan[0]; - if (max_bitrate) plan.fmtp["x-google-max-bitrate"] = std::to_string(max_bitrate); - if (min_bitrate) plan.fmtp["x-google-min-bitrate"] = std::to_string(min_bitrate); - if (start_bitrate) plan.fmtp["x-google-start-bitrate"] = std::to_string(start_bitrate); - } -} - -std::string WebRtcTransport::getAnswerSdp(const string &offer) { - try { - // // 解析offer sdp //// [AUTO-TRANSLATED:87c1f337] - // // Parse offer sdp //// - _offer_sdp = std::make_shared(); - _offer_sdp->loadFrom(offer); - onCheckSdp(SdpType::offer, *_offer_sdp); - _offer_sdp->checkValid(); - setRemoteDtlsFingerprint(*_offer_sdp); - - // // sdp 配置 //// [AUTO-TRANSLATED:718a72e2] - // // sdp configuration //// - RtcConfigure configure; - onRtcConfigure(configure); - - // // 生成answer sdp //// [AUTO-TRANSLATED:a139475e] - // // Generate answer sdp //// - _answer_sdp = configure.createAnswer(*_offer_sdp); - onCheckSdp(SdpType::answer, *_answer_sdp); - setSdpBitrate(*_answer_sdp); - _answer_sdp->checkValid(); - return _answer_sdp->toString(); - } catch (exception &ex) { - onShutdown(SockException(Err_shutdown, ex.what())); - throw; - } -} - -static bool isDtls(char *buf) { - return ((*buf > 19) && (*buf < 64)); -} - -void WebRtcTransport::inputSockData(char *buf, int len, RTC::TransportTuple *tuple) { - if (RTC::StunPacket::IsStun((const uint8_t *)buf, len)) { - std::unique_ptr packet(RTC::StunPacket::Parse((const uint8_t *)buf, len)); - if (!packet) { - WarnL << "parse stun error"; - return; - } - _ice_server->ProcessStunPacket(packet.get(), tuple); - return; - } - if (isDtls(buf)) { - _dtls_transport->ProcessDtlsData((uint8_t *)buf, len); - return; - } - if (isRtp(buf, len)) { - if (!_srtp_session_recv) { - WarnL << "received rtp packet when dtls not completed from:" << tuple->get_peer_ip(); - return; - } - if (_srtp_session_recv->DecryptSrtp((uint8_t *)buf, &len)) { - onRtp(buf, len, _ticker.createdTime()); - } - return; - } - if (isRtcp(buf, len)) { - if (!_srtp_session_recv) { - WarnL << "received rtcp packet when dtls not completed from:" << tuple->get_peer_ip(); - return; - } - if (_srtp_session_recv->DecryptSrtcp((uint8_t *)buf, &len)) { - onRtcp(buf, len); - } - return; - } -} - -void WebRtcTransport::sendRtpPacket(const char *buf, int len, bool flush, void *ctx) { - if (_srtp_session_send) { - auto pkt = _packet_pool.obtain2(); - // 预留rtx加入的两个字节 [AUTO-TRANSLATED:d1eb5cd7] - // Reserve two bytes for rtx joining - pkt->setCapacity((size_t)len + SRTP_MAX_TRAILER_LEN + 2); - memcpy(pkt->data(), buf, len); - onBeforeEncryptRtp(pkt->data(), len, ctx); - if (_srtp_session_send->EncryptRtp(reinterpret_cast(pkt->data()), &len)) { - pkt->setSize(len); - onSendSockData(std::move(pkt), flush); - } - } -} - -void WebRtcTransport::sendRtcpPacket(const char *buf, int len, bool flush, void *ctx) { - if (_srtp_session_send) { - auto pkt = _packet_pool.obtain2(); - // 预留rtx加入的两个字节 [AUTO-TRANSLATED:d1eb5cd7] - // Reserve two bytes for rtx joining - pkt->setCapacity((size_t)len + SRTP_MAX_TRAILER_LEN + 2); - memcpy(pkt->data(), buf, len); - onBeforeEncryptRtcp(pkt->data(), len, ctx); - if (_srtp_session_send->EncryptRtcp(reinterpret_cast(pkt->data()), &len)) { - pkt->setSize(len); - onSendSockData(std::move(pkt), flush); - } - } -} - -/////////////////////////////////////////////////////////////////////////////////// - -void WebRtcTransportImp::onCreate() { - WebRtcTransport::onCreate(); - registerSelf(); - - weak_ptr weak_self = static_pointer_cast(shared_from_this()); - GET_CONFIG(float, timeoutSec, Rtc::kTimeOutSec); - _timer = std::make_shared( - timeoutSec / 2, - [weak_self]() { - auto strong_self = weak_self.lock(); - if (!strong_self) { - return false; - } - if (strong_self->_alive_ticker.elapsedTime() > timeoutSec * 1000) { - strong_self->onShutdown(SockException(Err_timeout, "接受rtp/rtcp/datachannel超时")); - } - return true; - }, - getPoller()); - - _twcc_ctx.setOnSendTwccCB([this](uint32_t ssrc, string fci) { onSendTwcc(ssrc, fci); }); -} - -void WebRtcTransportImp::OnDtlsTransportApplicationDataReceived(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) { - WebRtcTransport::OnDtlsTransportApplicationDataReceived(dtlsTransport, data, len); -#ifdef ENABLE_SCTP - if (_answer_sdp->isOnlyDatachannel()) { - _alive_ticker.resetTime(); - } -#endif -} - -WebRtcTransportImp::WebRtcTransportImp(const EventPoller::Ptr &poller) : WebRtcTransport(poller) { - InfoL << getIdentifier(); -} - -WebRtcTransportImp::~WebRtcTransportImp() { - InfoL << getIdentifier(); -} - -void WebRtcTransportImp::onDestory() { - WebRtcTransport::onDestory(); - unregisterSelf(); -} - -void WebRtcTransportImp::onSendSockData(Buffer::Ptr buf, bool flush, RTC::TransportTuple *tuple) { - if (tuple == nullptr) { - tuple = _ice_server->GetSelectedTuple(); - if (!tuple) { - WarnL << "send data failed:" << buf->size(); - return; - } - } - - // 一次性发送一帧的rtp数据,提高网络io性能 [AUTO-TRANSLATED:fbab421e] - // Send one frame of rtp data at a time to improve network io performance - if (tuple->getSock()->sockType() == SockNum::Sock_TCP) { - // 增加tcp两字节头 [AUTO-TRANSLATED:62159f79] - // Add two-byte header to tcp - auto len = buf->size(); - char tcp_len[2] = { 0 }; - tcp_len[0] = (len >> 8) & 0xff; - tcp_len[1] = len & 0xff; - tuple->SockSender::send(tcp_len, 2); - } - tuple->send(std::move(buf)); - - if (flush) { - tuple->flushAll(); - } -} - -/////////////////////////////////////////////////////////////////// - -bool WebRtcTransportImp::canSendRtp() const { - for (auto &m : _answer_sdp->media) { - if (m.direction == RtpDirection::sendrecv || m.direction == RtpDirection::sendonly) { - return true; - } - } - return false; -} - -bool WebRtcTransportImp::canRecvRtp() const { - for (auto &m : _answer_sdp->media) { - if (m.direction == RtpDirection::sendrecv || m.direction == RtpDirection::recvonly) { - return true; - } - } - return false; -} - -void WebRtcTransportImp::onStartWebRTC() { - // 获取ssrc和pt相关信息,届时收到rtp和rtcp时分别可以根据pt和ssrc找到相关的信息 [AUTO-TRANSLATED:39828247] - // Get ssrc and pt related information, so that when receiving rtp and rtcp, you can find the relevant information according to pt and ssrc respectively - for (auto &m_answer : _answer_sdp->media) { - if (m_answer.type == TrackApplication) { - continue; - } - auto m_offer = _offer_sdp->getMedia(m_answer.type); - auto track = std::make_shared(); - - track->media = &m_answer; - track->answer_ssrc_rtp = m_answer.getRtpSSRC(); - track->answer_ssrc_rtx = m_answer.getRtxSSRC(); - track->offer_ssrc_rtp = m_offer->getRtpSSRC(); - track->offer_ssrc_rtx = m_offer->getRtxSSRC(); - track->plan_rtp = &m_answer.plan[0]; - track->plan_rtx = m_answer.getRelatedRtxPlan(track->plan_rtp->pt); - track->rtcp_context_send = std::make_shared(); - - // rtp track type --> MediaTrack - if (m_answer.direction == RtpDirection::sendonly || m_answer.direction == RtpDirection::sendrecv) { - // 该类型的track 才支持发送 [AUTO-TRANSLATED:b7c1e631] - // This type of track supports sending - _type_to_track[m_answer.type] = track; - } - // send ssrc --> MediaTrack - _ssrc_to_track[track->answer_ssrc_rtp] = track; - _ssrc_to_track[track->answer_ssrc_rtx] = track; - - // recv ssrc --> MediaTrack - _ssrc_to_track[track->offer_ssrc_rtp] = track; - _ssrc_to_track[track->offer_ssrc_rtx] = track; - - // rtp pt --> MediaTrack - _pt_to_track.emplace( - track->plan_rtp->pt, std::unique_ptr(new WrappedRtpTrack(track, _twcc_ctx, *this))); - if (track->plan_rtx) { - // rtx pt --> MediaTrack - _pt_to_track.emplace(track->plan_rtx->pt, std::unique_ptr(new WrappedRtxTrack(track))); - } - // 记录rtp ext类型与id的关系,方便接收或发送rtp时修改rtp ext id [AUTO-TRANSLATED:5736bd34] - // Record the relationship between rtp ext type and id, which is convenient for modifying rtp ext id when receiving or sending rtp - track->rtp_ext_ctx = std::make_shared(m_answer); - weak_ptr weak_track = track; - track->rtp_ext_ctx->setOnGetRtp([this, weak_track](uint8_t pt, uint32_t ssrc, const string &rid) { - // ssrc --> MediaTrack - auto track = weak_track.lock(); - assert(track); - _ssrc_to_track[ssrc] = std::move(track); - InfoL << "get rtp, pt:" << (int)pt << ", ssrc:" << ssrc << ", rid:" << rid; - }); - - size_t index = 0; - for (auto &ssrc : m_offer->rtp_ssrc_sim) { - // 记录ssrc对应的MediaTrack [AUTO-TRANSLATED:8e344bc1] - // Record the MediaTrack corresponding to ssrc - _ssrc_to_track[ssrc.ssrc] = track; - if (m_offer->rtp_rids.size() > index) { - // 支持firefox的simulcast, 提前映射好ssrc和rid的关系 [AUTO-TRANSLATED:86f3e5bf] - // Support firefox's simulcast, map the relationship between ssrc and rid in advance - track->rtp_ext_ctx->setRid(ssrc.ssrc, m_offer->rtp_rids[index]); - } else { - // SDP munging没有rid, 它通过group-ssrc:SIM给出ssrc列表; [AUTO-TRANSLATED:d6cd0b5b] - // SDP munging does not have rid, it gives the ssrc list through group-ssrc:SIM; - // 系统又要有rid,这里手工生成rid,并为其绑定ssrc [AUTO-TRANSLATED:f4988139] - // The system also needs a rid, so we manually generate a rid and bind it to the ssrc - std::string rid = "r" + std::to_string(index); - track->rtp_ext_ctx->setRid(ssrc.ssrc, rid); - if (ssrc.rtx_ssrc) { - track->rtp_ext_ctx->setRid(ssrc.rtx_ssrc, rid); - } - } - ++index; - } - } -} - -void WebRtcTransportImp::onCheckAnswer(RtcSession &sdp) { - // 修改answer sdp的ip、端口信息 [AUTO-TRANSLATED:ff72ec1e] - // Modify the ip and port information of the answer sdp - GET_CONFIG_FUNC(std::vector, extern_ips, Rtc::kExternIP, [](string str) { - std::vector ret; - if (str.length()) { - ret = split(str, ","); - } - translateIPFromEnv(ret); - return ret; - }); - for (auto &m : sdp.media) { - m.addr.reset(); - m.addr.address = extern_ips.empty() ? _local_ip.empty() ? SockUtil::get_local_ip() : _local_ip : extern_ips[0]; - m.rtcp_addr.reset(); - m.rtcp_addr.address = m.addr.address; - - GET_CONFIG(uint16_t, udp_port, Rtc::kPort); - GET_CONFIG(uint16_t, tcp_port, Rtc::kTcpPort); - m.port = m.port ? (udp_port ? udp_port : tcp_port) : 0; - if (m.type != TrackApplication) { - m.rtcp_addr.port = m.port; - } - sdp.origin.address = m.addr.address; - } - - if (!canSendRtp()) { - // 设置我们发送的rtp的ssrc [AUTO-TRANSLATED:3704484a] - // Set the ssrc of the rtp we send - return; - } - - for (auto &m : sdp.media) { - if (m.type == TrackApplication) { - continue; - } - if (!m.rtp_rtx_ssrc.empty()) { - // 已经生成了ssrc [AUTO-TRANSLATED:5883cab8] - // The ssrc has been generated - continue; - } - // 添加answer sdp的ssrc信息 [AUTO-TRANSLATED:ab4c3fad] - // Add the ssrc information to the answer sdp - m.rtp_rtx_ssrc.emplace_back(); - auto &ssrc = m.rtp_rtx_ssrc.back(); - // 发送的ssrc我们随便定义,因为在发送rtp时会修改为此值 [AUTO-TRANSLATED:ee8d77f0] - // We can define the ssrc we send at will, because it will be modified to this value when sending rtp - ssrc.ssrc = m.type + RTP_SSRC_OFFSET; - ssrc.cname = RTP_CNAME; - ssrc.label = std::string(RTP_LABEL) + '-' + m.mid; - ssrc.mslabel = RTP_MSLABEL; - ssrc.msid = ssrc.mslabel + ' ' + ssrc.label; - - if (m.getRelatedRtxPlan(m.plan[0].pt)) { - // rtx ssrc - ssrc.rtx_ssrc = ssrc.ssrc + RTX_SSRC_OFFSET; - } - } -} - -void WebRtcTransportImp::onCheckSdp(SdpType type, RtcSession &sdp) { - switch (type) { - case SdpType::answer: - onCheckAnswer(sdp); - break; - case SdpType::offer: - break; - default: /*不可达*/ - assert(0); - break; - } -} - -SdpAttrCandidate::Ptr -makeIceCandidate(std::string ip, uint16_t port, uint32_t priority = 100, std::string proto = "udp") { - auto candidate = std::make_shared(); - // rtp端口 [AUTO-TRANSLATED:b0addb27] - // rtp port - candidate->component = 1; - candidate->transport = proto; - candidate->foundation = proto + "candidate"; - // 优先级,单candidate时随便 [AUTO-TRANSLATED:7c85d820] - // Priority, random when there is only one candidate - candidate->priority = priority; - candidate->address = std::move(ip); - candidate->port = port; - candidate->type = "host"; - if (proto == "tcp") { - candidate->type += " tcptype passive"; - } - return candidate; -} - -void WebRtcTransportImp::onRtcConfigure(RtcConfigure &configure) const { - WebRtcTransport::onRtcConfigure(configure); - if (!_cands.empty()) { - for (auto &cand : _cands) { - configure.addCandidate(cand); - } - return; - } - - GET_CONFIG(uint16_t, local_udp_port, Rtc::kPort); - GET_CONFIG(uint16_t, local_tcp_port, Rtc::kTcpPort); - // 添加接收端口candidate信息 [AUTO-TRANSLATED:cc9a6a90] - // Add the receiving port candidate information - GET_CONFIG_FUNC(std::vector, extern_ips, Rtc::kExternIP, [](string str) { - std::vector ret; - if (str.length()) { - ret = split(str, ","); - } - translateIPFromEnv(ret); - return ret; - }); - if (extern_ips.empty()) { - std::string local_ip = _local_ip.empty() ? SockUtil::get_local_ip() : _local_ip; - if (local_udp_port) { configure.addCandidate(*makeIceCandidate(local_ip, local_udp_port, 120, "udp")); } - if (local_tcp_port) { configure.addCandidate(*makeIceCandidate(local_ip, local_tcp_port, _preferred_tcp ? 125 : 115, "tcp")); } - } else { - const uint32_t delta = 10; - uint32_t priority = 100 + delta * extern_ips.size(); - for (auto ip : extern_ips) { - if (local_udp_port) { configure.addCandidate(*makeIceCandidate(ip, local_udp_port, priority, "udp")); } - if (local_tcp_port) { configure.addCandidate(*makeIceCandidate(ip, local_tcp_port, priority - (_preferred_tcp ? -5 : 5), "tcp")); } - priority -= delta; - } - } -} - -void WebRtcTransportImp::setPreferredTcp(bool flag) { - _preferred_tcp = flag; -} - -void WebRtcTransportImp::setLocalIp(std::string local_ip) { - _local_ip = std::move(local_ip); -} - -void WebRtcTransportImp::setIceCandidate(vector cands) { - _cands = std::move(cands); -} - -/////////////////////////////////////////////////////////////////// - -class RtpChannel : public RtpTrackImp, public std::enable_shared_from_this { -public: - RtpChannel(EventPoller::Ptr poller, RtpTrackImp::OnSorted cb, function on_nack) { - _poller = std::move(poller); - _on_nack = std::move(on_nack); - setOnSorted(std::move(cb)); - // 设置jitter buffer参数 [AUTO-TRANSLATED:eede98b6] - // Set jitter buffer parameters - GET_CONFIG(uint32_t, nack_maxms, Rtc::kNackMaxMS); - GET_CONFIG(uint32_t, nack_max_rtp, Rtc::kNackMaxSize); - RtpTrackImp::setParams(nack_max_rtp, nack_maxms, nack_max_rtp / 2); - _nack_ctx.setOnNack([this](const FCI_NACK &nack) { onNack(nack); }); - } - - RtpPacket::Ptr inputRtp(TrackType type, int sample_rate, uint8_t *ptr, size_t len, bool is_rtx) { - auto rtp = RtpTrack::inputRtp(type, sample_rate, ptr, len); - if (!rtp) { - return rtp; - } - auto seq = rtp->getSeq(); - _nack_ctx.received(seq, is_rtx); - if (!is_rtx) { - // 统计rtp接受情况,便于生成nack rtcp包 [AUTO-TRANSLATED:57e0f80d] - // Statistics of rtp reception, which is convenient for generating nack rtcp packets - _rtcp_context.onRtp(seq, rtp->getStamp(), rtp->ntp_stamp, sample_rate, len); - } - return rtp; - } - - Buffer::Ptr createRtcpRR(RtcpHeader *sr, uint32_t ssrc) { - _rtcp_context.onRtcp(sr); - return _rtcp_context.createRtcpRR(ssrc, getSSRC()); - } - - float getLossRate() { - auto expected = _rtcp_context.getExpectedPacketsInterval(); - if (!expected) { - return -1; - } - return _rtcp_context.getLostInterval() * 100 / expected; - } - -private: - void starNackTimer() { - if (_delay_task) { - return; - } - weak_ptr weak_self = shared_from_this(); - _delay_task = _poller->doDelayTask(10, [weak_self]() -> uint64_t { - auto strong_self = weak_self.lock(); - if (!strong_self) { - return 0; - } - auto ret = strong_self->_nack_ctx.reSendNack(); - if (!ret) { - strong_self->_delay_task = nullptr; - } - return ret; - }); - } - - void onNack(const FCI_NACK &nack) { - _on_nack(nack); - starNackTimer(); - } - -private: - NackContext _nack_ctx; - RtcpContextForRecv _rtcp_context; - EventPoller::Ptr _poller; - EventPoller::DelayTask::Ptr _delay_task; - function _on_nack; -}; - -std::shared_ptr MediaTrack::getRtpChannel(uint32_t ssrc) const { - auto it_chn = rtp_channel.find(rtp_ext_ctx->getRid(ssrc)); - if (it_chn == rtp_channel.end()) { - return nullptr; - } - return it_chn->second; -} - -float WebRtcTransportImp::getLossRate(TrackType type) { - for (auto &pr : _ssrc_to_track) { - auto ssrc = pr.first; - auto &track = pr.second; - auto rtp_chn = track->getRtpChannel(ssrc); - if (rtp_chn) { - if (track->media && type == track->media->type) { - return rtp_chn->getLossRate(); - } - } - } - return -1; -} - -void WebRtcTransportImp::onRtcp(const char *buf, size_t len) { - _bytes_usage += len; - auto rtcps = RtcpHeader::loadFromBytes((char *)buf, len); - for (auto rtcp : rtcps) { - switch ((RtcpType)rtcp->pt) { - case RtcpType::RTCP_SR: { - _alive_ticker.resetTime(); - // 对方汇报rtp发送情况 [AUTO-TRANSLATED:1389b0c8] - // The other party reports the rtp sending situation - RtcpSR *sr = (RtcpSR *)rtcp; - auto it = _ssrc_to_track.find(sr->ssrc); - if (it != _ssrc_to_track.end()) { - auto &track = it->second; - auto rtp_chn = track->getRtpChannel(sr->ssrc); - if (!rtp_chn) { - WarnL << "未识别的sr rtcp包:" << rtcp->dumpString(); - } else { - // 设置rtp时间戳与ntp时间戳的对应关系 [AUTO-TRANSLATED:e92f4749] - // Set the correspondence between rtp timestamp and ntp timestamp - rtp_chn->setNtpStamp(sr->rtpts, sr->getNtpUnixStampMS()); - auto rr = rtp_chn->createRtcpRR(sr, track->answer_ssrc_rtp); - sendRtcpPacket(rr->data(), rr->size(), true); - } - } else { - WarnL << "未识别的sr rtcp包:" << rtcp->dumpString(); - } - break; - } - case RtcpType::RTCP_RR: { - _alive_ticker.resetTime(); - // 对方汇报rtp接收情况 [AUTO-TRANSLATED:77f50a28] - // The other party reports the rtp receiving situation - RtcpRR *rr = (RtcpRR *)rtcp; - for (auto item : rr->getItemList()) { - auto it = _ssrc_to_track.find(item->ssrc); - if (it != _ssrc_to_track.end()) { - auto &track = it->second; - track->rtcp_context_send->onRtcp(rtcp); - auto sr = track->rtcp_context_send->createRtcpSR(track->answer_ssrc_rtp); - sendRtcpPacket(sr->data(), sr->size(), true); - } else { - WarnL << "未识别的rr rtcp包:" << rtcp->dumpString(); - } - } - break; - } - case RtcpType::RTCP_BYE: { - // 对方汇报停止发送rtp [AUTO-TRANSLATED:96ad0cf3] - // The other party reports the stop sending rtp - RtcpBye *bye = (RtcpBye *)rtcp; - for (auto ssrc : bye->getSSRC()) { - auto it = _ssrc_to_track.find(*ssrc); - if (it == _ssrc_to_track.end()) { - WarnL << "未识别的bye rtcp包:" << rtcp->dumpString(); - continue; - } - _ssrc_to_track.erase(it); - } - onRtcpBye(); - // bye 会在 sender audio track mute 时出现, 因此不能作为 shutdown 的依据 [AUTO-TRANSLATED:d9fdfaac] - // Bye will appear when the sender audio track is muted, so it cannot be used as the basis for shutdown - break; - } - case RtcpType::RTCP_PSFB: - case RtcpType::RTCP_RTPFB: { - if ((RtcpType)rtcp->pt == RtcpType::RTCP_PSFB) { - break; - } - // RTPFB - switch ((RTPFBType)rtcp->report_count) { - case RTPFBType::RTCP_RTPFB_NACK: { - RtcpFB *fb = (RtcpFB *)rtcp; - auto it = _ssrc_to_track.find(fb->ssrc_media); - if (it == _ssrc_to_track.end()) { - WarnL << "未识别的 rtcp包:" << rtcp->dumpString(); - return; - } - auto &track = it->second; - auto &fci = fb->getFci(); - track->nack_list.forEach(fci, [&](const RtpPacket::Ptr &rtp) { - // rtp重传 [AUTO-TRANSLATED:62a37e46] - // rtp retransmission - onSendRtp(rtp, true, true); - }); - break; - } - default: - break; - } - break; - } - case RtcpType::RTCP_XR: { - RtcpXRRRTR *xr = (RtcpXRRRTR *)rtcp; - if (xr->bt != 4) { - break; - } - auto it = _ssrc_to_track.find(xr->ssrc); - if (it == _ssrc_to_track.end()) { - WarnL << "未识别的 rtcp包:" << rtcp->dumpString(); - return; - } - auto &track = it->second; - track->rtcp_context_send->onRtcp(rtcp); - auto xrdlrr = track->rtcp_context_send->createRtcpXRDLRR(track->answer_ssrc_rtp, track->answer_ssrc_rtp); - sendRtcpPacket(xrdlrr->data(), xrdlrr->size(), true); - - break; - } - default: - break; - } - } -} - -/////////////////////////////////////////////////////////////////// - -void WebRtcTransportImp::createRtpChannel(const string &rid, uint32_t ssrc, MediaTrack &track) { - // rid --> RtpReceiverImp - auto &ref = track.rtp_channel[rid]; - weak_ptr weak_self = static_pointer_cast(shared_from_this()); - ref = std::make_shared( - getPoller(), [&track, this, rid](RtpPacket::Ptr rtp) mutable { onSortedRtp(track, rid, std::move(rtp)); }, - [&track, weak_self, ssrc](const FCI_NACK &nack) mutable { - // nack发送可能由定时器异步触发 [AUTO-TRANSLATED:186d6723] - // Nack sending may be triggered asynchronously by a timer - auto strong_self = weak_self.lock(); - if (strong_self) { - strong_self->onSendNack(track, nack, ssrc); - } - }); - InfoL << "create rtp receiver of ssrc:" << ssrc << ", rid:" << rid << ", codec:" << track.plan_rtp->codec; -} - -void WebRtcTransportImp::updateTicker() { - _alive_ticker.resetTime(); -} - -void WebRtcTransportImp::onRtp(const char *buf, size_t len, uint64_t stamp_ms) { - _bytes_usage += len; - _alive_ticker.resetTime(); - - RtpHeader *rtp = (RtpHeader *)buf; - // 根据接收到的rtp的pt信息,找到该流的信息 [AUTO-TRANSLATED:9a97682c] - // Find the information of the stream according to the pt information of the received rtp - auto it = _pt_to_track.find(rtp->pt); - if (it == _pt_to_track.end()) { - WarnL << "unknown rtp pt:" << (int)rtp->pt; - return; - } - it->second->inputRtp(buf, len, stamp_ms, rtp); -} - -void WrappedRtpTrack::inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) { -#if 0 - auto seq = ntohs(rtp->seq); - if (track->media->type == TrackVideo && seq % 100 == 0) { - // 此处模拟接受丢包 [AUTO-TRANSLATED:7e8c2e5c] - // Simulate packet loss here - return; - } -#endif - - auto ssrc = ntohl(rtp->ssrc); - - // 修改ext id至统一 [AUTO-TRANSLATED:0769b0ec] - // Modify the ext id to be unified - string rid; - auto twcc_ext = track->rtp_ext_ctx->changeRtpExtId(rtp, true, &rid, RtpExtType::transport_cc); - - if (twcc_ext) { - _twcc_ctx.onRtp(ssrc, twcc_ext.getTransportCCSeq(), stamp_ms); - } - - auto &ref = track->rtp_channel[rid]; - if (!ref) { - _transport.createRtpChannel(rid, ssrc, *track); - } - - // 解析并排序rtp [AUTO-TRANSLATED:d382b65d] - // Parse and sort rtp - ref->inputRtp(track->media->type, track->plan_rtp->sample_rate, (uint8_t *)buf, len, false); -} - -void WrappedRtxTrack::inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) { - // 修改ext id至统一 [AUTO-TRANSLATED:0769b0ec] - // Modify the ext id to be unified - string rid; - track->rtp_ext_ctx->changeRtpExtId(rtp, true, &rid, RtpExtType::transport_cc); - - auto &ref = track->rtp_channel[rid]; - if (!ref) { - // 再接收到对应的rtp前,丢弃rtx包 [AUTO-TRANSLATED:d4ca6d69] - // Discard rtx packets before receiving the corresponding rtp - WarnL << "unknown rtx rtp, rid:" << rid << ", ssrc:" << ntohl(rtp->ssrc) << ", codec:" << track->plan_rtp->codec - << ", seq:" << ntohs(rtp->seq); - return; - } - - // 这里是rtx重传包 [AUTO-TRANSLATED:6efd3766] - // This is the rtx retransmission packet - // https://datatracker.ietf.org/doc/html/rfc4588#section-4 - auto payload = rtp->getPayloadData(); - auto size = rtp->getPayloadSize(len); - if (size < 2) { - return; - } - - // 前两个字节是原始的rtp的seq [AUTO-TRANSLATED:c57ff92d] - // The first two bytes are the original rtp seq - auto origin_seq = payload[0] << 8 | payload[1]; - // rtx 转换为 rtp [AUTO-TRANSLATED:be27f61b] - // rtx converted to rtp - rtp->pt = track->plan_rtp->pt; - rtp->seq = htons(origin_seq); - rtp->ssrc = htonl(ref->getSSRC()); - - memmove((uint8_t *)buf + 2, buf, payload - (uint8_t *)buf); - buf += 2; - len -= 2; - ref->inputRtp(track->media->type, track->plan_rtp->sample_rate, (uint8_t *)buf, len, true); -} - -void WebRtcTransportImp::onSendNack(MediaTrack &track, const FCI_NACK &nack, uint32_t ssrc) { - auto rtcp = RtcpFB::create(RTPFBType::RTCP_RTPFB_NACK, &nack, FCI_NACK::kSize); - rtcp->ssrc = htonl(track.answer_ssrc_rtp); - rtcp->ssrc_media = htonl(ssrc); - sendRtcpPacket((char *)rtcp.get(), rtcp->getSize(), true); -} - -void WebRtcTransportImp::onSendTwcc(uint32_t ssrc, const string &twcc_fci) { - auto rtcp = RtcpFB::create(RTPFBType::RTCP_RTPFB_TWCC, twcc_fci.data(), twcc_fci.size()); - rtcp->ssrc = htonl(0); - rtcp->ssrc_media = htonl(ssrc); - sendRtcpPacket((char *)rtcp.get(), rtcp->getSize(), true); -} - -/////////////////////////////////////////////////////////////////// - -void WebRtcTransportImp::onSortedRtp(MediaTrack &track, const string &rid, RtpPacket::Ptr rtp) { - if (track.media->type == TrackVideo && _pli_ticker.elapsedTime() > 2000) { - // 定期发送pli请求关键帧,方便非rtc等协议 [AUTO-TRANSLATED:b992f020] - // Regularly send pli requests for key frames, which is convenient for non-rtc protocols - _pli_ticker.resetTime(); - sendRtcpPli(rtp->getSSRC()); - - // 开启remb,则发送remb包调节比特率 [AUTO-TRANSLATED:20e98cea] - // If remb is enabled, send remb packets to adjust the bitrate - GET_CONFIG(size_t, remb_bit_rate, Rtc::kRembBitRate); - if (remb_bit_rate && _answer_sdp->supportRtcpFb(SdpConst::kRembRtcpFb)) { - sendRtcpRemb(rtp->getSSRC(), remb_bit_rate); - } - } - - onRecvRtp(track, rid, std::move(rtp)); -} - -/////////////////////////////////////////////////////////////////// - -void WebRtcTransportImp::onSendRtp(const RtpPacket::Ptr &rtp, bool flush, bool rtx) { - auto &track = _type_to_track[rtp->type]; - if (!track) { - // 忽略,对方不支持该编码类型 [AUTO-TRANSLATED:498ee936] - // Ignore, the other party does not support this encoding type - return; - } - if (!rtx) { - // 统计rtp发送情况,好做sr汇报 [AUTO-TRANSLATED:142028b2] - // Statistics of RTP sending, for SR reporting - track->rtcp_context_send->onRtp( - rtp->getSeq(), rtp->getStamp(), rtp->ntp_stamp, rtp->sample_rate, - rtp->size() - RtpPacket::kRtpTcpHeaderSize); - track->nack_list.pushBack(rtp); -#if 0 - // 此处模拟发送丢包 [AUTO-TRANSLATED:9612f08e] - // Simulate packet loss here - if (rtp->type == TrackVideo && rtp->getSeq() % 100 == 0) { - return; - } -#endif - } else { - // 发送rtx重传包 [AUTO-TRANSLATED:ae60e1fd] - // Send RTX retransmission packets - // TraceL << "send rtx rtp:" << rtp->getSeq(); - } - pair ctx { rtx, track.get() }; - sendRtpPacket(rtp->data() + RtpPacket::kRtpTcpHeaderSize, rtp->size() - RtpPacket::kRtpTcpHeaderSize, flush, &ctx); - _bytes_usage += rtp->size() - RtpPacket::kRtpTcpHeaderSize; -} - -void WebRtcTransportImp::onBeforeEncryptRtp(const char *buf, int &len, void *ctx) { - auto pr = (pair *)ctx; - auto header = (RtpHeader *)buf; - - if (!pr->first || !pr->second->plan_rtx) { - // 普通的rtp,或者不支持rtx, 修改目标pt和ssrc [AUTO-TRANSLATED:e1264971] - // Ordinary RTP, or does not support RTX, modify the target PT and SSRC - pr->second->rtp_ext_ctx->changeRtpExtId(header, false); - header->pt = pr->second->plan_rtp->pt; - header->ssrc = htonl(pr->second->answer_ssrc_rtp); - } else { - // 重传的rtp, rtx [AUTO-TRANSLATED:e863a518] - // Retransmitted RTP, RTX - pr->second->rtp_ext_ctx->changeRtpExtId(header, false); - header->pt = pr->second->plan_rtx->pt; - if (pr->second->answer_ssrc_rtx) { - // 有rtx单独的ssrc,有些情况下,浏览器支持rtx,但是未指定rtx单独的ssrc [AUTO-TRANSLATED:181cee9a] - // RTX has a separate SSRC, in some cases, the browser supports RTX, but does not specify a separate SSRC for RTX - header->ssrc = htonl(pr->second->answer_ssrc_rtx); - } else { - // 未单独指定rtx的ssrc,那么使用rtp的ssrc [AUTO-TRANSLATED:dcafdd75] - // If RTX SSRC is not specified separately, use the RTP SSRC - header->ssrc = htonl(pr->second->answer_ssrc_rtp); - } - - auto origin_seq = ntohs(header->seq); - // seq跟原来的不一样 [AUTO-TRANSLATED:803f9a5e] - // The sequence is different from the original - header->seq = htons(_rtx_seq[pr->second->media->type]); - ++_rtx_seq[pr->second->media->type]; - - auto payload = header->getPayloadData(); - auto payload_size = header->getPayloadSize(len); - if (payload_size) { - // rtp负载后移两个字节,这两个字节用于存放osn [AUTO-TRANSLATED:b7eed70e] - // The RTP payload is shifted two bytes, these two bytes are used to store OSN - // https://datatracker.ietf.org/doc/html/rfc4588#section-4 - memmove(payload + 2, payload, payload_size); - } - payload[0] = origin_seq >> 8; - payload[1] = origin_seq & 0xFF; - len += 2; - } -} - -void WebRtcTransportImp::safeShutdown(const SockException &ex) { - std::weak_ptr weak_self = static_pointer_cast(shared_from_this()); - getPoller()->async([ex, weak_self]() { - if (auto strong_self = weak_self.lock()) { - strong_self->onShutdown(ex); - } - }); -} - -void WebRtcTransportImp::onShutdown(const SockException &ex) { - WarnL << ex; - unrefSelf(); - for (auto &tuple : _ice_server->GetTuples()) { - tuple->shutdown(ex); - } -} - -void WebRtcTransportImp::removeTuple(RTC::TransportTuple *tuple) { - InfoL << getIdentifier() << " remove tuple " << tuple->get_peer_ip() << ":" << tuple->get_peer_port(); - this->_ice_server->RemoveTuple(tuple); -} - -uint64_t WebRtcTransportImp::getBytesUsage() const { - return _bytes_usage; -} - -uint64_t WebRtcTransportImp::getDuration() const { - return _alive_ticker.createdTime() / 1000; -} - -void WebRtcTransportImp::onRtcpBye(){} - -///////////////////////////////////////////////////////////////////////////////////////////// - -void WebRtcTransportImp::registerSelf() { - _self = static_pointer_cast(shared_from_this()); - WebRtcTransportManager::Instance().addItem(getIdentifier(), _self); -} - -void WebRtcTransportImp::unrefSelf() { - _self = nullptr; -} - -void WebRtcTransportImp::unregisterSelf() { - unrefSelf(); - WebRtcTransportManager::Instance().removeItem(getIdentifier()); -} - -WebRtcTransportManager &WebRtcTransportManager::Instance() { - static WebRtcTransportManager s_instance; - return s_instance; -} - -void WebRtcTransportManager::addItem(const string &key, const WebRtcTransportImp::Ptr &ptr) { - lock_guard lck(_mtx); - _map[key] = ptr; -} - -WebRtcTransportImp::Ptr WebRtcTransportManager::getItem(const string &key) { - if (key.empty()) { - return nullptr; - } - lock_guard lck(_mtx); - auto it = _map.find(key); - if (it == _map.end()) { - return nullptr; - } - return it->second.lock(); -} - -void WebRtcTransportManager::removeItem(const string &key) { - lock_guard lck(_mtx); - _map.erase(key); -} - -////////////////////////////////////////////////////////////////////////////////////////////// - -WebRtcPluginManager &WebRtcPluginManager::Instance() { - static WebRtcPluginManager s_instance; - return s_instance; -} - -void WebRtcPluginManager::registerPlugin(const string &type, Plugin cb) { - lock_guard lck(_mtx_creator); - _map_creator[type] = std::move(cb); -} - - -void WebRtcPluginManager::setListener(Listener cb) { - lock_guard lck(_mtx_creator); - _listener = std::move(cb); -} - -void WebRtcPluginManager::negotiateSdp(Session &sender, const string &type, const WebRtcArgs &args, const onCreateWebRtc &cb_in) { - onCreateWebRtc cb; - lock_guard lck(_mtx_creator); - if (_listener) { - auto listener = _listener; - auto args_ptr = args.shared_from_this(); - auto sender_ptr = static_pointer_cast(sender.shared_from_this()); - cb = [listener, sender_ptr, type, args_ptr, cb_in](const WebRtcInterface &rtc) { - listener(*sender_ptr, type, *args_ptr, rtc); - cb_in(rtc); - }; - } else { - cb = cb_in; - } - - auto it = _map_creator.find(type); - if (it == _map_creator.end()) { - cb_in(WebRtcException(SockException(Err_other, "the type can not supported"))); - return; - } - it->second(sender, args, cb); -} - -void echo_plugin(Session &sender, const WebRtcArgs &args, const onCreateWebRtc &cb) { - cb(*WebRtcEchoTest::create(EventPollerPool::Instance().getPoller())); -} - -void push_plugin(Session &sender, const WebRtcArgs &args, const onCreateWebRtc &cb) { - MediaInfo info(args["url"]); - Broadcast::PublishAuthInvoker invoker = [cb, info](const string &err, const ProtocolOption &option) mutable { - if (!err.empty()) { - cb(WebRtcException(SockException(Err_other, err))); - return; - } - - RtspMediaSourceImp::Ptr push_src; - std::shared_ptr push_src_ownership; - auto src = MediaSource::find(RTSP_SCHEMA, info.vhost, info.app, info.stream); - auto push_failed = (bool)src; - - while (src) { - // 尝试断连后继续推流 [AUTO-TRANSLATED:9eaaa6dd] - // Try to continue streaming after disconnecting - auto rtsp_src = dynamic_pointer_cast(src); - if (!rtsp_src) { - // 源不是rtsp推流产生的 [AUTO-TRANSLATED:47b87993] - // The source is not generated by RTSP streaming - break; - } - auto ownership = rtsp_src->getOwnership(); - if (!ownership) { - // 获取推流源所有权失败 [AUTO-TRANSLATED:256190b2] - // Failed to get the ownership of the streaming source - break; - } - push_src = std::move(rtsp_src); - push_src_ownership = std::move(ownership); - push_failed = false; - break; - } - - if (push_failed) { - cb(WebRtcException(SockException(Err_other, "already publishing"))); - return; - } - - if (!push_src) { - push_src = std::make_shared(info); - push_src_ownership = push_src->getOwnership(); - push_src->setProtocolOption(option); - } - auto rtc = WebRtcPusher::create(EventPollerPool::Instance().getPoller(), push_src, push_src_ownership, info, option); - push_src->setListener(rtc); - cb(*rtc); - }; - - // rtsp推流需要鉴权 [AUTO-TRANSLATED:c2cbb7ed] - // RTSP streaming requires authentication - auto flag = NOTICE_EMIT(BroadcastMediaPublishArgs, Broadcast::kBroadcastMediaPublish, MediaOriginType::rtc_push, info, invoker, sender); - if (!flag) { - // 该事件无人监听,默认不鉴权 [AUTO-TRANSLATED:e1fbc6ae] - // No one is listening to this event, authentication is not enabled by default - invoker("", ProtocolOption()); - } -} - -void play_plugin(Session &sender, const WebRtcArgs &args, const onCreateWebRtc &cb) { - MediaInfo info(args["url"]); - auto session_ptr = static_pointer_cast(sender.shared_from_this()); - Broadcast::AuthInvoker invoker = [cb, info, session_ptr](const string &err) mutable { - if (!err.empty()) { - cb(WebRtcException(SockException(Err_other, err))); - return; - } - - // webrtc播放的是rtsp的源 [AUTO-TRANSLATED:649ae489] - // WebRTC plays the RTSP source - info.schema = RTSP_SCHEMA; - MediaSource::findAsync(info, session_ptr, [=](const MediaSource::Ptr &src_in) mutable { - auto src = dynamic_pointer_cast(src_in); - if (!src) { - cb(WebRtcException(SockException(Err_other, "stream not found"))); - return; - } - // 还原成rtc,目的是为了hook时识别哪种播放协议 [AUTO-TRANSLATED:fe8dd2dc] - // Restore to RTC, the purpose is to identify which playback protocol during hooking - info.schema = "rtc"; - auto rtc = WebRtcPlayer::create(EventPollerPool::Instance().getPoller(), src, info); - cb(*rtc); - }); - }; - - // 广播通用播放url鉴权事件 [AUTO-TRANSLATED:81e24be4] - // Broadcast a universal playback URL authentication event - auto flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, info, invoker, sender); - if (!flag) { - // 该事件无人监听,默认不鉴权 [AUTO-TRANSLATED:e1fbc6ae] - // No one is listening to this event, authentication is not enabled by default - invoker(""); - } -} - -static void setWebRtcArgs(const WebRtcArgs &args, WebRtcInterface &rtc) { - { - static auto is_vaild_ip = [](const std::string &ip) -> bool { - int a, b, c, d; - return sscanf(ip.c_str(), "%d.%d.%d.%d", &a, &b, &c, &d) == 4; - }; - std::string host = args["Host"]; - if (!host.empty()) { - auto local_ip = host.substr(0, host.find(':')); - if (!is_vaild_ip(local_ip) || local_ip == "127.0.0.1") { - local_ip = ""; - } - rtc.setLocalIp(std::move(local_ip)); - } - } - - bool preferred_tcp = args["preferred_tcp"]; - { - rtc.setPreferredTcp(preferred_tcp); - } - - { - vector cands; - { - auto cand_str = trim(args["cand_udp"]); - auto ip_port = toolkit::split(cand_str, ":"); - if (ip_port.size() == 2) { - // udp优先 [AUTO-TRANSLATED:b428f63d] - // UDP priority - auto ice_cand = makeIceCandidate(ip_port[0], atoi(ip_port[1].data()), preferred_tcp ? 100 : 120, "udp"); - cands.emplace_back(std::move(*ice_cand)); - } - } - { - auto cand_str = trim(args["cand_tcp"]); - auto ip_port = toolkit::split(cand_str, ":"); - if (ip_port.size() == 2) { - // tcp模式 [AUTO-TRANSLATED:62fddf91] - // TCP mode - auto ice_cand = makeIceCandidate(ip_port[0], atoi(ip_port[1].data()), preferred_tcp ? 120 : 100, "tcp"); - cands.emplace_back(std::move(*ice_cand)); - } - } - if (!cands.empty()) { - // udp优先 [AUTO-TRANSLATED:b428f63d] - // UDP priority - rtc.setIceCandidate(std::move(cands)); - } - } -} - -static onceToken s_rtc_auto_register([]() { -#if !defined (NDEBUG) - // debug模式才开启echo插件 [AUTO-TRANSLATED:48fcb116] - // Enable echo plugin only in debug mode - WebRtcPluginManager::Instance().registerPlugin("echo", echo_plugin); -#endif - WebRtcPluginManager::Instance().registerPlugin("push", push_plugin); - WebRtcPluginManager::Instance().registerPlugin("play", play_plugin); - WebRtcPluginManager::Instance().setListener([](Session &sender, const std::string &type, const WebRtcArgs &args, const WebRtcInterface &rtc) { - setWebRtcArgs(args, const_cast(rtc)); - }); -}); - -}// namespace mediakit +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include +#include +#include "Util/base64.h" +#include "Network/sockutil.h" +#include "Common/config.h" +#include "Nack.h" +#include "RtpExt.h" +#include "Rtcp/Rtcp.h" +#include "Rtcp/RtcpFCI.h" +#include "Rtcp/RtcpContext.h" +#include "Rtsp/Rtsp.h" +#include "Rtsp/RtpReceiver.h" +#include "WebRtcTransport.h" + +#include "WebRtcEchoTest.h" +#include "WebRtcPlayer.h" +#include "WebRtcPusher.h" +#include "Rtsp/RtspMediaSourceImp.h" + +#define RTP_SSRC_OFFSET 1 +#define RTX_SSRC_OFFSET 2 +#define RTP_CNAME "zlmediakit-rtp" +#define RTP_LABEL "zlmediakit-label" +#define RTP_MSLABEL "zlmediakit-mslabel" + +using namespace std; + +using namespace toolkit; +namespace mediakit { + +// RTC配置项目 [AUTO-TRANSLATED:19940011] +// RTC configuration project +namespace Rtc { +#define RTC_FIELD "rtc." +// rtp和rtcp接受超时时间 [AUTO-TRANSLATED:0f318cc0] +// rtp and rtcp receive timeout +const string kTimeOutSec = RTC_FIELD "timeoutSec"; +// 服务器外网ip [AUTO-TRANSLATED:23283ba6] +// Server external network ip +const string kExternIP = RTC_FIELD "externIP"; +const string kInterfaces = RTC_FIELD "interfaces"; +// 设置remb比特率,非0时关闭twcc并开启remb。该设置在rtc推流时有效,可以控制推流画质 [AUTO-TRANSLATED:412801db] +// Set remb bitrate, when it is not 0, turn off twcc and turn on remb. This setting is valid when rtc pushes the stream, and can control the pushing stream quality +const string kRembBitRate = RTC_FIELD "rembBitRate"; +// webrtc单端口udp服务器 [AUTO-TRANSLATED:d17271ea] +// webrtc single-port udp server +const string kPort = RTC_FIELD "port"; +const string kTcpPort = RTC_FIELD "tcpPort"; +// webrtc SignalingServerPort udp server +const string kSignalingPort = RTC_FIELD "signalingPort"; +const string kSignalingSslPort = RTC_FIELD "signalingSslPort"; +// webrtc iceServer udp server +const string kIcePort = RTC_FIELD "icePort"; +const string kIceTcpPort = RTC_FIELD "iceTcpPort"; +// webrtc enable turn or only enable stun +const string kEnableTurn = RTC_FIELD "enableTurn"; +// webrtc ice ufrag and pwd [AUTO-TRANSLATED:2f0d1b3c] +const string kIceUfrag = RTC_FIELD "iceUfrag"; +const string kIcePwd = RTC_FIELD "icePwd"; +const string kIceTransportPolicy = RTC_FIELD "iceTransportPolicy"; + +// 比特率设置 [AUTO-TRANSLATED:2c75f5bc] +// Bitrate setting +const string kStartBitrate = RTC_FIELD "start_bitrate"; +const string kMaxBitrate = RTC_FIELD "max_bitrate"; +const string kMinBitrate = RTC_FIELD "min_bitrate"; + +// 数据通道设置 [AUTO-TRANSLATED:2dc48bc3] +// Data channel setting +const string kDataChannelEcho = RTC_FIELD "datachannel_echo"; + +static onceToken token([]() { + mINI::Instance()[kTimeOutSec] = 15; + mINI::Instance()[kExternIP] = ""; + mINI::Instance()[kInterfaces] = ""; + mINI::Instance()[kRembBitRate] = 0; + mINI::Instance()[kPort] = 8000; + mINI::Instance()[kTcpPort] = 8000; + + mINI::Instance()[kStartBitrate] = 0; + mINI::Instance()[kMaxBitrate] = 0; + mINI::Instance()[kMinBitrate] = 0; + + mINI::Instance()[kDataChannelEcho] = true; + + mINI::Instance()[kSignalingPort] = 3000; + mINI::Instance()[kSignalingSslPort] = 3001; + mINI::Instance()[kIcePort] = 3478; + mINI::Instance()[kIceTcpPort] = 3478; + mINI::Instance()[kEnableTurn] = 1; + mINI::Instance()[kIceTransportPolicy] = 0; // 默认值:不限制(kAll) + mINI::Instance()[kIceUfrag] = "ZLMediaKit"; + mINI::Instance()[kIcePwd] = "ZLMediaKit"; +}); + +} // namespace Rtc + +static atomic s_key { 0 }; + +static std::string getServerPrefix() { + // stun_user_name格式: base64(ip+udp_port+tcp_port) + _ + number [AUTO-TRANSLATED:cc3c5902] + // stun_user_name format: base64(ip+udp_port+tcp_port) + _ + number + // 其中ip为二进制char[4], udp_port/tcp_port为大端 uint16. [AUTO-TRANSLATED:92ea5521] + // Where ip is binary char[4], udp_port/tcp_port is big-endian uint16. + // number为自增长数,确保短时间内唯一 [AUTO-TRANSLATED:d31aada9] + // number is an auto-incrementing number, ensuring uniqueness in a short period of time + GET_CONFIG(uint16_t, udp_port, Rtc::kPort); + GET_CONFIG(uint16_t, tcp_port, Rtc::kTcpPort); + char buf[8]; + auto host = SockUtil::get_local_ip(); + auto addr = SockUtil::make_sockaddr(host.data(), udp_port); + // 拷贝ipv4地址 [AUTO-TRANSLATED:49c16eed] + // Copy ipv4 address + memcpy(buf, &(reinterpret_cast(&addr)->sin_addr), 4); + // 拷贝udp端口 [AUTO-TRANSLATED:ebb750d3] + // Copy udp port + memcpy(buf + 4, &(reinterpret_cast(&addr)->sin_port), 2); + // tcp端口转大端模式 [AUTO-TRANSLATED:4f2293de] + // Convert tcp port to big-endian mode + addr = SockUtil::make_sockaddr(host.data(), tcp_port); + // 拷贝tcp端口 [AUTO-TRANSLATED:23191878] + // Copy tcp port + memcpy(buf + 6, &(reinterpret_cast(&addr)->sin_port), 2); + auto ret = encodeBase64(string(buf, 8)) + '_'; + InfoL << "MediaServer(" << host << ":" << udp_port << ":" << tcp_port << ") prefix: " << ret; + return ret; +} + +static std::string mappingCandidateTypeEnum2Str(CandidateInfo::AddressType type) { + switch (type) { + case CandidateInfo::AddressType::HOST: return "host"; + case CandidateInfo::AddressType::SRFLX: return "srflx"; + case CandidateInfo::AddressType::PRFLX: return "prflx"; + case CandidateInfo::AddressType::RELAY: return "relay"; + default: break; + } + return "invalid"; +} + +static CandidateInfo::AddressType mappingCandidateTypeStr2Enum(const std::string &type) { + if (strcasecmp(type.c_str(), "host") == 0) { + return CandidateInfo::AddressType::HOST; + } + if (strcasecmp(type.c_str(), "srflx") == 0) { + return CandidateInfo::AddressType::SRFLX; + } + if (strcasecmp(type.c_str(), "prflx") == 0) { + return CandidateInfo::AddressType::PRFLX; + } + if (strcasecmp(type.c_str(), "relay") == 0) { + return CandidateInfo::AddressType::RELAY; + } + return CandidateInfo::AddressType::INVALID; +} + +// 根据RFC 5245标准计算foundation +// 1. IP地址类型(IPv4/IPv6) +// 2. 传输协议(UDP/TCP) +// 3. 候选类型(host/srflx/prflx/relay) +// 4. STUN/TURN服务器地址(对于srflx和relay类型) +static std::string calculateFoundation(const std::string& ip, const std::string& proto, const std::string& type, const std::string& stun_server = "") { + // 将协议和类型转换为小写以确保一致性 + std::string proto_lower = proto; + std::string type_lower = type; + std::transform(proto_lower.begin(), proto_lower.end(), proto_lower.begin(), ::tolower); + std::transform(type_lower.begin(), type_lower.end(), type_lower.begin(), ::tolower); + + std::string foundation_base = type_lower + "-" + ip + "-" + proto_lower; + + // 对于server reflexive和relay候选,需要包含STUN/TURN服务器地址 + if ((type_lower == "srflx" || type_lower == "relay") && !stun_server.empty()) { + foundation_base += "-" + stun_server; + } + + std::hash hasher; + size_t hash_value = hasher(foundation_base); + char foundation_str[9]; + snprintf(foundation_str, sizeof(foundation_str), "%08x", (unsigned int)(hash_value & 0xFFFFFFFF)); + return foundation_str; +} + +static SdpAttrCandidate::Ptr makeIceCandidate(std::string ip, uint16_t port, uint32_t priority = 100, + const std::string &proto = "udp", const std::string &type = "host", + const std::string &base_host = "", uint16_t base_port = 0, const std::string &stun_server = "") { + auto candidate = std::make_shared(); + candidate->foundation = calculateFoundation(ip, proto, type, stun_server); + candidate->component = 1; + candidate->transport = proto; + candidate->priority = priority; + candidate->address = std::move(ip); + candidate->port = port; + candidate->type = type; + if (strcasecmp(proto.c_str(), "tcp") == 0) { + candidate->type += " tcptype passive"; + } + + if (type != "host" && !base_host.empty() && base_port > 0) { + candidate->arr.emplace_back("raddr", base_host); + candidate->arr.emplace_back("rport", std::to_string(base_port)); + } + + return candidate; +} + +static CandidateInfo::Ptr makeCandidateInfoBySdpAttr(const SdpAttrCandidate& candidate_attr, const std::string& ufrag, const std::string& pwd) { + auto candidate = std::make_shared(); + candidate->_type = mappingCandidateTypeStr2Enum(candidate_attr.type); + candidate->_priority = candidate_attr.priority; + + candidate->_addr._host = candidate_attr.address; + candidate->_addr._port = candidate_attr.port; + candidate->_base_addr._host = candidate->_addr._host; + candidate->_base_addr._port = candidate->_addr._port; + candidate->_priority = candidate_attr.priority; + candidate->_ufrag = ufrag; + candidate->_pwd = pwd; + + if (CandidateInfo::AddressType::HOST == candidate->_type) { + candidate->_base_addr = candidate->_addr; + } else { + for (auto &pr : candidate_attr.arr) { + if (pr.first == "raddr") { + candidate->_base_addr._host = pr.second; + } + if (pr.first == "rport") { + candidate->_base_addr._port = atoi(pr.second.data()); + } + } + } + + if (strcasecmp(candidate_attr.transport.c_str(), "udp") == 0) { + candidate->_transport = CandidateTuple::TransportType::UDP; + candidate->_secure = CandidateTuple::SecureType::NOT_SECURE; + } else if (strcasecmp(candidate_attr.transport.c_str(), "tcp") == 0) { + candidate->_transport = CandidateTuple::TransportType::TCP; + candidate->_secure = CandidateTuple::SecureType::NOT_SECURE; + } + + return candidate; +} + +const char* WebRtcTransport::SignalingProtocolsStr(SignalingProtocols protocol) { + switch (protocol) { + case SignalingProtocols::WHEP_WHIP: return "whep_whip"; + case SignalingProtocols::WEBSOCKET: return "websocket"; + default: return "invalid"; + } +} + +const char* WebRtcTransport::RoleStr(Role role) { + switch (role) { + case Role::CLIENT: return "client"; + case Role::PEER: return "peer"; + default: return "none"; + } +} + +WebRtcTransport::WebRtcTransport(const EventPoller::Ptr &poller) { + _poller = poller; + static auto prefix = getServerPrefix(); + _identifier = prefix + to_string(++s_key); + _packet_pool.setSize(64); +} + +void WebRtcTransport::onCreate() { + _dtls_transport = std::make_shared(_poller, this); + IceAgent::Role role = IceAgent::Role::Controlling; + IceAgent::Implementation implementation = IceAgent::Implementation::Full; + + if (_role == Role::PEER) { + role = IceAgent::Role::Controlled; + if (_signaling_protocols == SignalingProtocols::WHEP_WHIP) { + implementation = IceAgent::Implementation::Lite; + } + } + + _ice_agent = std::make_shared(this, implementation, role, _identifier, makeRandStr(24), getPoller()); + _ice_agent->initialize(); +} + +void WebRtcTransport::onDestory() { +#ifdef ENABLE_SCTP + _sctp = nullptr; +#endif + _dtls_transport = nullptr; + _ice_agent = nullptr; +} + +const string &WebRtcTransport::getIdentifier() const { + return _identifier; +} + +const std::string &WebRtcTransport::deleteRandStr() const { + if (_delete_rand_str.empty()) { + _delete_rand_str = makeRandStr(32); + } + return _delete_rand_str; +} + +void WebRtcTransport::getTransportInfo(const std::function& callback) const { + if (!callback) { + return; + } + + std::weak_ptr weak_self = shared_from_this(); + _poller->async([weak_self, callback]() { + Json::Value result; + auto strong_self = weak_self.lock(); + if (!strong_self) { + result["error"] = "Transport object destroyed"; + callback(std::move(result)); + return; + } + + try { + result["transport_id"] = strong_self->_identifier; + result["role"] = RoleStr(strong_self->_role); + result["signaling_protocol"] = SignalingProtocolsStr(strong_self->_signaling_protocols); + + result["has_offer_sdp"] = (strong_self->_offer_sdp != nullptr); + result["has_answer_sdp"] = (strong_self->_answer_sdp != nullptr); + result["dtls_state"] = strong_self->_dtls_transport? "connected" : "disconnected"; + result["srtp_send_ready"] = (strong_self->_srtp_session_send != nullptr); + result["srtp_recv_ready"] = (strong_self->_srtp_session_recv != nullptr); + + // ICE 连接检查列表信息 + if (strong_self->_ice_agent) { + Json::Value ice_info = strong_self->_ice_agent->getChecklistInfo(); + result["ice_checklists"] = ice_info; + } else { + result["ice_checklists"] = Json::nullValue; + } + + + } catch (const std::exception& ex) { + result["error"] = std::string("Exception occurred: ") + ex.what(); + } + + callback(std::move(result)); + }); +} + +void WebRtcTransport::gatheringCandidate(IceServerInfo::Ptr ice_server, onGatheringCandidateCB cb) { + _on_gathering_candidate = std::move(cb); + _ice_agent->setIceServer(ice_server); + return _ice_agent->gatheringCandidate(ice_server, true, ice_server->_schema == IceServerInfo::SchemaType::TURN); +} + +void WebRtcTransport::connectivityCheck(SdpAttrCandidate candidate_attr, const std::string& ufrag, const std::string& pwd) { + DebugL; + auto candidate = makeCandidateInfoBySdpAttr(candidate_attr, ufrag, pwd); + return _ice_agent->connectivityCheck(*candidate); +} + +void WebRtcTransport::connectivityCheckForSFU() { + DebugL; + // Connectivity Checks 连通性测试 + + auto answer_sdp = answerSdp(); + // TODO: 暂不支持每个媒体源,RTP,RTCP独立的candidates + for (auto &media : answer_sdp->media) { + for (auto &item : media.candidate) { + auto candidate = makeCandidateInfoBySdpAttr(item, media.ice_ufrag, media.ice_pwd); + _ice_agent->gatheringCandidate(candidate, false, false); + _ice_agent->connectivityCheck(*candidate); + } + } +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void WebRtcTransport::onIceTransportCompleted() { + InfoL << getIdentifier(); + + if (!_answer_sdp) { + onShutdown(SockException(Err_other, "answer sdp not ready")); + return; + } + + _recv_ticker.resetTime(); + auto timeout = getTimeOutSec(); + weak_ptr weakSelf = static_pointer_cast(shared_from_this()); + _check_timer = std::make_shared(timeout / 2, [weakSelf, timeout]() { + auto strongSelf = weakSelf.lock(); + if (!strongSelf) { + return false; + } + if (strongSelf->_recv_ticker.elapsedTime() > timeout * 1000) { + // 接收媒体数据包超时 + strongSelf->onShutdown(SockException(Err_timeout, "webrtc data receive timeout")); + return false; + } + + return true; + }, getPoller()); + + if ((getRole() == Role::PEER && _answer_sdp->media[0].role == DtlsRole::passive) + || (getRole() == Role::CLIENT && _answer_sdp->media[0].role == DtlsRole::active)) { + _dtls_transport->Run(RTC::DtlsTransport::Role::SERVER); + } else { + _dtls_transport->Run(RTC::DtlsTransport::Role::CLIENT); + } +} + +void WebRtcTransport::onIceTransportDisconnected() { + InfoL << getIdentifier(); +} + +void WebRtcTransport::onIceTransportGatheringCandidate(const IceTransport::Pair::Ptr &pair, const CandidateInfo &candidate) { + InfoL << getIdentifier() << " get local candidate type " << candidate.dumpString(); + if (_on_gathering_candidate) { + auto type = mappingCandidateTypeEnum2Str(candidate._type); + auto sdpAttrCandidate = makeIceCandidate(candidate._addr._host, candidate._addr._port, candidate._priority, "udp", type, candidate._base_addr._host, candidate._base_addr._port); + _on_gathering_candidate(getIdentifier(), sdpAttrCandidate->toString(), candidate._ufrag, candidate._pwd); + } +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void WebRtcTransport::setOnStartWebRTC(std::function on_start) { + _on_start = std::move(on_start); +} + +void WebRtcTransport::OnDtlsTransportConnected( + const RTC::DtlsTransport *dtlsTransport, RTC::SrtpSession::CryptoSuite srtpCryptoSuite, uint8_t *srtpLocalKey, + size_t srtpLocalKeyLen, uint8_t *srtpRemoteKey, size_t srtpRemoteKeyLen, std::string &remoteCert) { + InfoL << getIdentifier(); + _srtp_session_send = std::make_shared( + RTC::SrtpSession::Type::OUTBOUND, srtpCryptoSuite, srtpLocalKey, srtpLocalKeyLen); + _srtp_session_recv = std::make_shared( + RTC::SrtpSession::Type::INBOUND, srtpCryptoSuite, srtpRemoteKey, srtpRemoteKeyLen); +#ifdef ENABLE_SCTP + _sctp = std::make_shared(getPoller(), this, 128, 128, 262144, true); + _sctp->TransportConnected(); +#endif + onStartWebRTC(); + if (_on_start) { + _on_start(); + } +} + +#pragma pack(push, 1) +struct DtlsHeader { + uint8_t content_type; + uint16_t dtls_version; + uint16_t epoch; + uint8_t seq[6]; + uint16_t length; + uint8_t payload[1]; +}; +#pragma pack(pop) + +void WebRtcTransport::OnDtlsTransportSendData(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) { + size_t offset = 0; + while (offset < len) { + auto *header = reinterpret_cast(data + offset); + auto length = ntohs(header->length) + offsetof(DtlsHeader, payload); + sendSockData((char *)data + offset, length); + offset += length; + } +} + +void WebRtcTransport::OnDtlsTransportConnecting(const RTC::DtlsTransport *dtlsTransport) { + InfoL << getIdentifier(); +} + +void WebRtcTransport::OnDtlsTransportFailed(const RTC::DtlsTransport *dtlsTransport) { + InfoL << getIdentifier(); + onShutdown(SockException(Err_shutdown, "dtls transport failed")); +} + +void WebRtcTransport::OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) { + InfoL << getIdentifier(); + onShutdown(SockException(Err_shutdown, "dtls close notify received")); +} + +void WebRtcTransport::OnDtlsTransportApplicationDataReceived( + const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) { +#ifdef ENABLE_SCTP + _sctp->ProcessSctpData(data, len); +#else + InfoL << hexdump(data, len); +#endif +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#ifdef ENABLE_SCTP +void WebRtcTransport::OnSctpAssociationConnecting(RTC::SctpAssociation *sctpAssociation) { + TraceL << getIdentifier(); + try { + NOTICE_EMIT(BroadcastRtcSctpConnectArgs, Broadcast::kBroadcastRtcSctpConnecting, *this); + } catch (std::exception &ex) { + WarnL << "Exception occurred: " << ex.what(); + } +} + +void WebRtcTransport::OnSctpAssociationConnected(RTC::SctpAssociation *sctpAssociation) { + InfoL << getIdentifier(); + try { + NOTICE_EMIT(BroadcastRtcSctpConnectArgs, Broadcast::kBroadcastRtcSctpConnected, *this); + } catch (std::exception &ex) { + WarnL << "Exception occurred: " << ex.what(); + } +} + +void WebRtcTransport::OnSctpAssociationFailed(RTC::SctpAssociation *sctpAssociation) { + WarnL << getIdentifier(); + try { + NOTICE_EMIT(BroadcastRtcSctpConnectArgs, Broadcast::kBroadcastRtcSctpFailed, *this); + } catch (std::exception &ex) { + WarnL << "Exception occurred: " << ex.what(); + } +} + +void WebRtcTransport::OnSctpAssociationClosed(RTC::SctpAssociation *sctpAssociation) { + InfoL << getIdentifier(); + try { + NOTICE_EMIT(BroadcastRtcSctpConnectArgs, Broadcast::kBroadcastRtcSctpClosed, *this); + } catch (std::exception &ex) { + WarnL << "Exception occurred: " << ex.what(); + } +} + +void WebRtcTransport::OnSctpAssociationSendData( + RTC::SctpAssociation *sctpAssociation, const uint8_t *data, size_t len) { + try { + NOTICE_EMIT(BroadcastRtcSctpSendArgs, Broadcast::kBroadcastRtcSctpSend, *this, data, len); + } catch (std::exception &ex) { + WarnL << "Exception occurred: " << ex.what(); + } + _dtls_transport->SendApplicationData(data, len); +} + +void WebRtcTransport::OnSctpAssociationMessageReceived( + RTC::SctpAssociation *sctpAssociation, uint16_t streamId, uint32_t ppid, const uint8_t *msg, size_t len) { + InfoL << getIdentifier() << " " << streamId << " " << ppid << " " << len << " " << string((char *)msg, len); + RTC::SctpStreamParameters params; + params.streamId = streamId; + + GET_CONFIG(bool, datachannel_echo, Rtc::kDataChannelEcho); + if (datachannel_echo) { + // 回显数据 [AUTO-TRANSLATED:7868d3a4] + // Echo data + _sctp->SendSctpMessage(params, ppid, msg, len); + } + + try { + NOTICE_EMIT(BroadcastRtcSctpReceivedArgs, Broadcast::kBroadcastRtcSctpReceived, *this, streamId, ppid, msg, len); + } catch (std::exception &ex) { + WarnL << "Exception occurred: " << ex.what(); + } +} +#endif + +void WebRtcTransport::sendDatachannel(uint16_t streamId, uint32_t ppid, const char *msg, size_t len) { +#ifdef ENABLE_SCTP + if (_sctp) { + RTC::SctpStreamParameters params; + params.streamId = streamId; + _sctp->SendSctpMessage(params, ppid, (uint8_t *)msg, len); + } +#else + WarnL << "WebRTC datachannel disabled!"; +#endif +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void WebRtcTransport::sendSockData(const char *buf, size_t len, const IceTransport::Pair::Ptr &pair) { + auto pkt = _packet_pool.obtain2(); + pkt->assign(buf, len); + onSendSockData(std::move(pkt), true, pair); +} + +Session::Ptr WebRtcTransport::getSession() const { + auto pair = _ice_agent->getSelectedPair(); + return pair ? static_pointer_cast(pair->_socket->shared_from_this()) : nullptr; +} + +void WebRtcTransport::removePair(const SocketHelper *socket) { + _ice_agent->removePair(socket); +} + +void WebRtcTransport::setOnShutdown(function cb) { + _on_shutdown = cb ? std::move(cb) : [](const SockException &) {}; +} + +void WebRtcTransport::onShutdown(const SockException &ex) { + TraceL << ex; + if (_on_shutdown) { + _on_shutdown(ex); + } + if (_ice_agent) { + for (auto &pair : _ice_agent->getPairs()) { + if (pair->_socket) { + pair->_socket->shutdown(ex); + } + } + } +} + +void WebRtcTransport::sendRtcpRemb(uint32_t ssrc, size_t bit_rate) { + auto remb = FCI_REMB::create({ ssrc }, (uint32_t)bit_rate); + auto fb = RtcpFB::create(PSFBType::RTCP_PSFB_REMB, remb.data(), remb.size()); + fb->ssrc = htonl(0); + fb->ssrc_media = htonl(ssrc); + sendRtcpPacket((char *)fb.get(), fb->getSize(), true); +} + +void WebRtcTransport::sendRtcpPli(uint32_t ssrc) { + auto pli = RtcpFB::create(PSFBType::RTCP_PSFB_PLI); + pli->ssrc = htonl(0); + pli->ssrc_media = htonl(ssrc); + sendRtcpPacket((char *)pli.get(), pli->getSize(), true); +} + +string getFingerprint(const string &algorithm_str, const std::shared_ptr &transport) { + auto algorithm = RTC::DtlsTransport::GetFingerprintAlgorithm(algorithm_str); + for (auto &finger_prints : transport->GetLocalFingerprints()) { + if (finger_prints.algorithm == algorithm) { + return finger_prints.value; + } + } + throw std::invalid_argument(StrPrinter << "不支持的加密算法:" << algorithm_str); +} + +void WebRtcTransport::setRemoteDtlsFingerprint(SdpType type, const RtcSession &remote) { + // 设置远端dtls签名 [AUTO-TRANSLATED:746d5f9c] + // Set remote dtls signature + auto &media = (type == SdpType::answer) ? _answer_sdp->media[0] : _offer_sdp->media[0]; + RTC::DtlsTransport::Fingerprint remote_fingerprint; + remote_fingerprint.algorithm = RTC::DtlsTransport::GetFingerprintAlgorithm(media.fingerprint.algorithm); + remote_fingerprint.value = media.fingerprint.hash; + _dtls_transport->SetRemoteFingerprint(remote_fingerprint); +} + +void WebRtcTransport::onRtcConfigure(RtcConfigure &configure) const { + SdpAttrFingerprint fingerprint; + fingerprint.algorithm = _offer_sdp ? _offer_sdp->media[0].fingerprint.algorithm : "sha-256"; + fingerprint.hash = getFingerprint(fingerprint.algorithm, _dtls_transport); + configure.setDefaultSetting(_ice_agent->getUfrag(), _ice_agent->getPassword(), RtpDirection::sendrecv, fingerprint); + + // 开启remb后关闭twcc,因为开启twcc后remb无效 [AUTO-TRANSLATED:8a8feca2] + // Turn off twcc after turning on remb, because remb is invalid after turning on twcc + GET_CONFIG(size_t, remb_bit_rate, Rtc::kRembBitRate); + configure.enableTWCC(!remb_bit_rate); +} + +static void setSdpBitrate(RtcSession &sdp) { + GET_CONFIG(size_t, max_bitrate, Rtc::kMaxBitrate); + GET_CONFIG(size_t, min_bitrate, Rtc::kMinBitrate); + GET_CONFIG(size_t, start_bitrate, Rtc::kStartBitrate); + + auto m = (RtcMedia *)(sdp.getMedia(TrackType::TrackVideo)); + if (m) { + auto &plan = m->plan[0]; + if (max_bitrate) plan.fmtp["x-google-max-bitrate"] = std::to_string(max_bitrate); + if (min_bitrate) plan.fmtp["x-google-min-bitrate"] = std::to_string(min_bitrate); + if (start_bitrate) plan.fmtp["x-google-start-bitrate"] = std::to_string(start_bitrate); + } +} + +std::string WebRtcTransport::createOfferSdp() { + try { + RtcConfigure configure; + onRtcConfigure(configure); + _offer_sdp = configure.createOffer(); + return _offer_sdp->toString(); + } catch (exception &ex) { + onShutdown(SockException(Err_shutdown, ex.what())); + throw; + } +} + +std::string WebRtcTransport::getAnswerSdp(const string &offer) { + try { + // // 解析offer sdp //// [AUTO-TRANSLATED:87c1f337] + // // Parse offer sdp //// + _offer_sdp = std::make_shared(); + _offer_sdp->loadFrom(offer); + onCheckSdp(SdpType::offer, *_offer_sdp); + _offer_sdp->checkValid(); + setRemoteDtlsFingerprint(SdpType::offer, *_offer_sdp); + + // // sdp 配置 //// [AUTO-TRANSLATED:718a72e2] + // // sdp configuration //// + RtcConfigure configure; + onRtcConfigure(configure); + + // // 生成answer sdp //// [AUTO-TRANSLATED:a139475e] + // // Generate answer sdp //// + _answer_sdp = configure.createAnswer(*_offer_sdp); + onCheckSdp(SdpType::answer, *_answer_sdp); + setSdpBitrate(*_answer_sdp); + _answer_sdp->checkValid(); + return _answer_sdp->toString(); + } catch (exception &ex) { + onShutdown(SockException(Err_shutdown, ex.what())); + throw; + } +} + +void WebRtcTransport::setAnswerSdp(const std::string &answer) { + try { + _answer_sdp = std::make_shared(); + _answer_sdp->loadFrom(answer); + onCheckSdp(SdpType::answer, *_answer_sdp); + _answer_sdp->checkValid(); + setRemoteDtlsFingerprint(SdpType::answer, *_answer_sdp); + } catch (exception &ex) { + onShutdown(SockException(Err_shutdown, ex.what())); + throw; + } +} + +static bool isDtls(const char *buf) { + return ((*buf > 19) && (*buf < 64)); +} + +void WebRtcTransport::inputSockData(const char *buf, int len, const SocketHelper::Ptr& socket, struct sockaddr *addr, int addr_len) { + IceTransport::Pair::Ptr pair; + if (addr != nullptr) { + auto peer_host = SockUtil::inet_ntoa(addr); + auto peer_port = SockUtil::inet_port(addr); + pair = std::make_shared(socket, std::move(peer_host), peer_port); + } else { + pair = std::make_shared(socket); + } + return inputSockData(buf, len, pair); +} + +void WebRtcTransport::inputSockData(const char *buf, int len, const IceTransport::Pair::Ptr& pair) { + // DebugL; + _recv_ticker.resetTime(); + if (_ice_agent->processSocketData((const uint8_t *)buf, len, pair)) { + return; + } + if (isDtls(buf)) { + _dtls_transport->ProcessDtlsData((uint8_t *)buf, len); + return; + } + if (isRtp(buf, len)) { + if (!_srtp_session_recv) { + WarnL << "received rtp packet when dtls not completed from:" << pair->get_peer_ip(); + return; + } + if (_srtp_session_recv->DecryptSrtp((uint8_t *)buf, &len)) { + onRtp(buf, len, _ticker.createdTime()); + } + return; + } + if (isRtcp(buf, len)) { + if (!_srtp_session_recv) { + WarnL << "received rtcp packet when dtls not completed from:" << pair->get_peer_ip(); + return; + } + if (_srtp_session_recv->DecryptSrtcp((uint8_t *)buf, &len)) { + onRtcp(buf, len); + } + return; + } +} + +void WebRtcTransport::sendRtpPacket(const char *buf, int len, bool flush, void *ctx) { + if (_srtp_session_send) { + auto pkt = _packet_pool.obtain2(); + // 预留rtx加入的两个字节 [AUTO-TRANSLATED:d1eb5cd7] + // Reserve two bytes for rtx joining + pkt->setCapacity((size_t)len + SRTP_MAX_TRAILER_LEN + 2); + memcpy(pkt->data(), buf, len); + onBeforeEncryptRtp(pkt->data(), len, ctx); + if (_srtp_session_send->EncryptRtp(reinterpret_cast(pkt->data()), &len)) { + pkt->setSize(len); + onSendSockData(std::move(pkt), flush); + } + } +} + +void WebRtcTransport::sendRtcpPacket(const char *buf, int len, bool flush, void *ctx) { + if (_srtp_session_send) { + auto pkt = _packet_pool.obtain2(); + // 预留rtx加入的两个字节 [AUTO-TRANSLATED:d1eb5cd7] + // Reserve two bytes for rtx joining + pkt->setCapacity((size_t)len + SRTP_MAX_TRAILER_LEN + 2); + memcpy(pkt->data(), buf, len); + onBeforeEncryptRtcp(pkt->data(), len, ctx); + if (_srtp_session_send->EncryptRtcp(reinterpret_cast(pkt->data()), &len)) { + pkt->setSize(len); + onSendSockData(std::move(pkt), flush); + } + } +} + +/////////////////////////////////////////////////////////////////////////////////// + +void WebRtcTransportImp::onCreate() { + WebRtcTransport::onCreate(); + registerSelf(); + + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + GET_CONFIG(float, timeoutSec, Rtc::kTimeOutSec); + _timer = std::make_shared( + timeoutSec / 2, + [weak_self]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return false; + } + if (strong_self->_alive_ticker.elapsedTime() > timeoutSec * 1000) { + strong_self->onShutdown(SockException(Err_timeout, "接受rtp/rtcp/datachannel超时")); + } + return true; + }, + getPoller()); + + _twcc_ctx.setOnSendTwccCB([this](uint32_t ssrc, string fci) { onSendTwcc(ssrc, fci); }); +} + +void WebRtcTransportImp::OnDtlsTransportApplicationDataReceived(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) { + WebRtcTransport::OnDtlsTransportApplicationDataReceived(dtlsTransport, data, len); +#ifdef ENABLE_SCTP + if (_answer_sdp->isOnlyDatachannel()) { + _alive_ticker.resetTime(); + } +#endif +} + +WebRtcTransportImp::WebRtcTransportImp(const EventPoller::Ptr &poller) : WebRtcTransport(poller) { + InfoL << getIdentifier(); +} + +WebRtcTransportImp::~WebRtcTransportImp() { + InfoL << getIdentifier(); +} + +void WebRtcTransportImp::onDestory() { + WebRtcTransport::onDestory(); + unregisterSelf(); +} + +void WebRtcTransportImp::onSendSockData(Buffer::Ptr buf, bool flush, const IceTransport::Pair::Ptr& pair) { + return _ice_agent->sendSocketData(buf, pair, flush); +} + +/////////////////////////////////////////////////////////////////// +bool WebRtcTransportImp::canSendRtp(const RtcMedia& m) const { + return (getRole() == WebRtcTransport::Role::PEER && m.direction == RtpDirection::sendonly) + || (getRole() == WebRtcTransport::Role::CLIENT && m.direction == RtpDirection::recvonly) + || (m.direction == RtpDirection::sendrecv); +} + +bool WebRtcTransportImp::canRecvRtp(const RtcMedia& m) const { + return (getRole() == WebRtcTransport::Role::PEER && m.direction == RtpDirection::recvonly) + || (getRole() == WebRtcTransport::Role::CLIENT && m.direction == RtpDirection::sendonly) + || (m.direction == RtpDirection::sendrecv); +} + +bool WebRtcTransportImp::canSendRtp() const { + for (auto &m : _answer_sdp->media) { + if (canSendRtp(m)) { + return true; + } + } + return false; +} + +bool WebRtcTransportImp::canRecvRtp() const { + for (auto &m : _answer_sdp->media) { + if (canRecvRtp(m)) { + return true; + } + } + return false; +} + +void WebRtcTransportImp::onStartWebRTC() { + // 获取ssrc和pt相关信息,届时收到rtp和rtcp时分别可以根据pt和ssrc找到相关的信息 [AUTO-TRANSLATED:39828247] + // Get ssrc and pt related information, so that when receiving rtp and rtcp, you can find the relevant information according to pt and ssrc respectively + for (auto &m_answer : _answer_sdp->media) { + if (m_answer.type == TrackApplication) { + continue; + } + auto m_offer = _offer_sdp->getMedia(m_answer.type); + auto track = std::make_shared(); + + track->media = &m_answer; + track->answer_ssrc_rtp = m_answer.getRtpSSRC(); + track->answer_ssrc_rtx = m_answer.getRtxSSRC(); + track->offer_ssrc_rtp = m_offer->getRtpSSRC(); + track->offer_ssrc_rtx = m_offer->getRtxSSRC(); + track->plan_rtp = &m_answer.plan[0]; + track->plan_rtx = m_answer.getRelatedRtxPlan(track->plan_rtp->pt); + track->rtcp_context_send = std::make_shared(); + + // rtp track type --> MediaTrack + if (canSendRtp(m_answer)) { + // 该类型的track 才支持发送 [AUTO-TRANSLATED:b7c1e631] + // This type of track supports sending + _type_to_track[m_answer.type] = track; + } + // send ssrc --> MediaTrack + _ssrc_to_track[track->answer_ssrc_rtp] = track; + _ssrc_to_track[track->answer_ssrc_rtx] = track; + + // recv ssrc --> MediaTrack + _ssrc_to_track[track->offer_ssrc_rtp] = track; + _ssrc_to_track[track->offer_ssrc_rtx] = track; + + // rtp pt --> MediaTrack + _pt_to_track.emplace( + track->plan_rtp->pt, std::unique_ptr(new WrappedRtpTrack(track, _twcc_ctx, *this))); + if (track->plan_rtx) { + // rtx pt --> MediaTrack + _pt_to_track.emplace(track->plan_rtx->pt, std::unique_ptr(new WrappedRtxTrack(track))); + } + // 记录rtp ext类型与id的关系,方便接收或发送rtp时修改rtp ext id [AUTO-TRANSLATED:5736bd34] + // Record the relationship between rtp ext type and id, which is convenient for modifying rtp ext id when receiving or sending rtp + track->rtp_ext_ctx = std::make_shared(m_answer); + weak_ptr weak_track = track; + track->rtp_ext_ctx->setOnGetRtp([this, weak_track](uint8_t pt, uint32_t ssrc, const string &rid) { + // ssrc --> MediaTrack + auto track = weak_track.lock(); + assert(track); + _ssrc_to_track[ssrc] = std::move(track); + InfoL << "get rtp, pt:" << (int)pt << ", ssrc:" << ssrc << ", rid:" << rid; + }); + + size_t index = 0; + for (auto &ssrc : m_offer->rtp_ssrc_sim) { + // 记录ssrc对应的MediaTrack [AUTO-TRANSLATED:8e344bc1] + // Record the MediaTrack corresponding to ssrc + _ssrc_to_track[ssrc.ssrc] = track; + if (m_offer->rtp_rids.size() > index) { + // 支持firefox的simulcast, 提前映射好ssrc和rid的关系 [AUTO-TRANSLATED:86f3e5bf] + // Support firefox's simulcast, map the relationship between ssrc and rid in advance + track->rtp_ext_ctx->setRid(ssrc.ssrc, m_offer->rtp_rids[index]); + } else { + // SDP munging没有rid, 它通过group-ssrc:SIM给出ssrc列表; [AUTO-TRANSLATED:d6cd0b5b] + // SDP munging does not have rid, it gives the ssrc list through group-ssrc:SIM; + // 系统又要有rid,这里手工生成rid,并为其绑定ssrc [AUTO-TRANSLATED:f4988139] + // The system also needs a rid, so we manually generate a rid and bind it to the ssrc + std::string rid = "r" + std::to_string(index); + track->rtp_ext_ctx->setRid(ssrc.ssrc, rid); + if (ssrc.rtx_ssrc) { + track->rtp_ext_ctx->setRid(ssrc.rtx_ssrc, rid); + } + } + ++index; + } + } +} + +void WebRtcTransportImp::onCheckAnswer(RtcSession &sdp) { + // 修改answer sdp的ip、端口信息 [AUTO-TRANSLATED:ff72ec1e] + // Modify the ip and port information of the answer sdp + GET_CONFIG_FUNC(std::vector, extern_ips, Rtc::kExternIP, [](string str) { + std::vector ret; + if (str.length()) { + ret = split(str, ","); + } + translateIPFromEnv(ret); + return ret; + }); + for (auto &m : sdp.media) { + m.addr.reset(); + m.addr.address = extern_ips.empty() ? _local_ip.empty() ? SockUtil::get_local_ip() : _local_ip : extern_ips[0]; + m.rtcp_addr.reset(); + m.rtcp_addr.address = m.addr.address; + + GET_CONFIG(uint16_t, udp_port, Rtc::kPort); + GET_CONFIG(uint16_t, tcp_port, Rtc::kTcpPort); + m.port = m.port ? (udp_port ? udp_port : tcp_port) : 0; + if (m.type != TrackApplication) { + m.rtcp_addr.port = m.port; + } + sdp.origin.address = m.addr.address; + } + + if (!canSendRtp()) { + // 设置我们发送的rtp的ssrc [AUTO-TRANSLATED:3704484a] + // Set the ssrc of the rtp we send + return; + } + + for (auto &m : sdp.media) { + if (m.type == TrackApplication) { + continue; + } + if (!m.rtp_rtx_ssrc.empty()) { + // 已经生成了ssrc [AUTO-TRANSLATED:5883cab8] + // The ssrc has been generated + continue; + } + // 添加answer sdp的ssrc信息 [AUTO-TRANSLATED:ab4c3fad] + // Add the ssrc information to the answer sdp + m.rtp_rtx_ssrc.emplace_back(); + auto &ssrc = m.rtp_rtx_ssrc.back(); + // 发送的ssrc我们随便定义,因为在发送rtp时会修改为此值 [AUTO-TRANSLATED:ee8d77f0] + // We can define the ssrc we send at will, because it will be modified to this value when sending rtp + ssrc.ssrc = m.type + RTP_SSRC_OFFSET; + ssrc.cname = RTP_CNAME; + ssrc.label = std::string(RTP_LABEL) + '-' + m.mid; + ssrc.mslabel = RTP_MSLABEL; + ssrc.msid = ssrc.mslabel + ' ' + ssrc.label; + + if (m.getRelatedRtxPlan(m.plan[0].pt)) { + // rtx ssrc + ssrc.rtx_ssrc = ssrc.ssrc + RTX_SSRC_OFFSET; + } + } +} + +void WebRtcTransportImp::onCheckSdp(SdpType type, RtcSession &sdp) { + switch (type) { + case SdpType::answer: + onCheckAnswer(sdp); + break; + case SdpType::offer: + break; + default: /*不可达*/ + assert(0); + break; + } +} + + +void WebRtcTransportImp::onRtcConfigure(RtcConfigure &configure) const { + WebRtcTransport::onRtcConfigure(configure); + if (!_cands.empty()) { + for (auto &cand : _cands) { + configure.addCandidate(cand); + } + return; + } + + //P2P的不直接在answer中返回candication + if (getSignalingProtocols() == SignalingProtocols::WHEP_WHIP) { + + GET_CONFIG(uint16_t, local_udp_port, Rtc::kPort); + GET_CONFIG(uint16_t, local_tcp_port, Rtc::kTcpPort); + // 添加接收端口candidate信息 [AUTO-TRANSLATED:cc9a6a90] + // Add the receiving port candidate information + GET_CONFIG_FUNC(std::vector, extern_ips, Rtc::kExternIP, [](string str) { + std::vector ret; + if (str.length()) { + ret = split(str, ","); + } + translateIPFromEnv(ret); + return ret; + }); + if (extern_ips.empty()) { + std::string local_ip = _local_ip.empty() ? SockUtil::get_local_ip() : _local_ip; + if (local_udp_port) { + configure.addCandidate(*makeIceCandidate(local_ip, local_udp_port, 120, "udp")); + } + if (local_tcp_port) { + configure.addCandidate(*makeIceCandidate(local_ip, local_tcp_port, _preferred_tcp ? 125 : 115, "tcp")); + } + } else { + const uint32_t delta = 10; + uint32_t priority = 100 + delta * extern_ips.size(); + for (auto ip : extern_ips) { + if (local_udp_port) { + configure.addCandidate(*makeIceCandidate(ip, local_udp_port, priority, "udp")); + } + if (local_tcp_port) { + configure.addCandidate(*makeIceCandidate(ip, local_tcp_port, priority - (_preferred_tcp ? -5 : 5), "tcp")); + } + priority -= delta; + } + } + } +} + +void WebRtcTransportImp::setPreferredTcp(bool flag) { + _preferred_tcp = flag; +} + +void WebRtcTransportImp::setLocalIp(std::string local_ip) { + _local_ip = std::move(local_ip); +} + +void WebRtcTransportImp::setIceCandidate(vector cands) { + _cands = std::move(cands); +} + +/////////////////////////////////////////////////////////////////// + +class RtpChannel : public RtpTrackImp, public std::enable_shared_from_this { +public: + RtpChannel(EventPoller::Ptr poller, RtpTrackImp::OnSorted cb, function on_nack) { + _poller = std::move(poller); + _on_nack = std::move(on_nack); + setOnSorted(std::move(cb)); + // 设置jitter buffer参数 [AUTO-TRANSLATED:eede98b6] + // Set jitter buffer parameters + GET_CONFIG(uint32_t, nack_maxms, Rtc::kNackMaxMS); + GET_CONFIG(uint32_t, nack_max_rtp, Rtc::kNackMaxSize); + RtpTrackImp::setParams(nack_max_rtp, nack_maxms, nack_max_rtp / 2); + _nack_ctx.setOnNack([this](const FCI_NACK &nack) { onNack(nack); }); + } + + RtpPacket::Ptr inputRtp(TrackType type, int sample_rate, uint8_t *ptr, size_t len, bool is_rtx) { + auto rtp = RtpTrack::inputRtp(type, sample_rate, ptr, len); + if (!rtp) { + return rtp; + } + auto seq = rtp->getSeq(); + _nack_ctx.received(seq, is_rtx); + if (!is_rtx) { + // 统计rtp接受情况,便于生成nack rtcp包 [AUTO-TRANSLATED:57e0f80d] + // Statistics of rtp reception, which is convenient for generating nack rtcp packets + _rtcp_context.onRtp(seq, rtp->getStamp(), rtp->ntp_stamp, sample_rate, len); + } + return rtp; + } + + Buffer::Ptr createRtcpRR(RtcpHeader *sr, uint32_t ssrc) { + if (sr) { + _rtcp_context.onRtcp(sr); + } + return _rtcp_context.createRtcpRR(ssrc, getSSRC()); + } + + float getLossRate() { + auto expected = _rtcp_context.getExpectedPacketsInterval(); + if (!expected) { + return -1; + } + return _rtcp_context.getLostInterval() * 100 / expected; + } + +private: + void starNackTimer() { + if (_delay_task) { + return; + } + weak_ptr weak_self = shared_from_this(); + _delay_task = _poller->doDelayTask(10, [weak_self]() -> uint64_t { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return 0; + } + auto ret = strong_self->_nack_ctx.reSendNack(); + if (!ret) { + strong_self->_delay_task = nullptr; + } + return ret; + }); + } + + void onNack(const FCI_NACK &nack) { + _on_nack(nack); + starNackTimer(); + } + +private: + NackContext _nack_ctx; + RtcpContextForRecv _rtcp_context; + EventPoller::Ptr _poller; + EventPoller::DelayTask::Ptr _delay_task; + function _on_nack; +}; + +std::shared_ptr MediaTrack::getRtpChannel(uint32_t ssrc) const { + auto it_chn = rtp_channel.find(rtp_ext_ctx->getRid(ssrc)); + if (it_chn == rtp_channel.end()) { + return nullptr; + } + return it_chn->second; +} + +float WebRtcTransportImp::getLossRate(TrackType type) { + for (auto &pr : _ssrc_to_track) { + auto ssrc = pr.first; + auto &track = pr.second; + auto rtp_chn = track->getRtpChannel(ssrc); + if (rtp_chn) { + if (track->media && type == track->media->type) { + return rtp_chn->getLossRate(); + } + } + } + return -1; +} + +void WebRtcTransportImp::onRtcp(const char *buf, size_t len) { + _bytes_usage += len; + auto rtcps = RtcpHeader::loadFromBytes((char *)buf, len); + for (auto rtcp : rtcps) { + switch ((RtcpType)rtcp->pt) { + case RtcpType::RTCP_SR: { + _alive_ticker.resetTime(); + // 对方汇报rtp发送情况 [AUTO-TRANSLATED:1389b0c8] + // The other party reports the rtp sending situation + RtcpSR *sr = (RtcpSR *)rtcp; + auto it = _ssrc_to_track.find(sr->ssrc); + if (it != _ssrc_to_track.end()) { + auto &track = it->second; + auto rtp_chn = track->getRtpChannel(sr->ssrc); + if (!rtp_chn) { + WarnL << "未识别的sr rtcp包:" << rtcp->dumpString(); + } else { + // 设置rtp时间戳与ntp时间戳的对应关系 [AUTO-TRANSLATED:e92f4749] + // Set the correspondence between rtp timestamp and ntp timestamp + rtp_chn->setNtpStamp(sr->rtpts, sr->getNtpUnixStampMS()); + auto rr = rtp_chn->createRtcpRR(sr, track->answer_ssrc_rtp); + sendRtcpPacket(rr->data(), rr->size(), true); + _rtcp_rr_send_ticker.resetTime(); + } + } else { + WarnL << "未识别的sr rtcp包:" << rtcp->dumpString(); + } + break; + } + case RtcpType::RTCP_RR: { + _alive_ticker.resetTime(); + // 对方汇报rtp接收情况 [AUTO-TRANSLATED:77f50a28] + // The other party reports the rtp receiving situation + RtcpRR *rr = (RtcpRR *)rtcp; + for (auto item : rr->getItemList()) { + auto it = _ssrc_to_track.find(item->ssrc); + if (it != _ssrc_to_track.end()) { + auto &track = it->second; + track->rtcp_context_send->onRtcp(rtcp); + auto sr = track->rtcp_context_send->createRtcpSR(track->answer_ssrc_rtp); + sendRtcpPacket(sr->data(), sr->size(), true); + _rtcp_sr_send_ticker.resetTime(); + } else { + WarnL << "未识别的rr rtcp包:" << rtcp->dumpString(); + } + } + break; + } + case RtcpType::RTCP_BYE: { + // 对方汇报停止发送rtp [AUTO-TRANSLATED:96ad0cf3] + // The other party reports the stop sending rtp + RtcpBye *bye = (RtcpBye *)rtcp; + for (auto ssrc : bye->getSSRC()) { + auto it = _ssrc_to_track.find(*ssrc); + if (it == _ssrc_to_track.end()) { + WarnL << "未识别的bye rtcp包:" << rtcp->dumpString(); + continue; + } + _ssrc_to_track.erase(it); + } + onRtcpBye(); + // bye 会在 sender audio track mute 时出现, 因此不能作为 shutdown 的依据 [AUTO-TRANSLATED:d9fdfaac] + // Bye will appear when the sender audio track is muted, so it cannot be used as the basis for shutdown + break; + } + case RtcpType::RTCP_PSFB: + case RtcpType::RTCP_RTPFB: { + if ((RtcpType)rtcp->pt == RtcpType::RTCP_PSFB) { + break; + } + // RTPFB + switch ((RTPFBType)rtcp->report_count) { + case RTPFBType::RTCP_RTPFB_NACK: { + RtcpFB *fb = (RtcpFB *)rtcp; + auto it = _ssrc_to_track.find(fb->ssrc_media); + if (it == _ssrc_to_track.end()) { + WarnL << "未识别的 rtcp包:" << rtcp->dumpString(); + return; + } + auto &track = it->second; + auto &fci = fb->getFci(); + track->nack_list.forEach(fci, [&](const RtpPacket::Ptr &rtp) { + // rtp重传 [AUTO-TRANSLATED:62a37e46] + // rtp retransmission + onSendRtp(rtp, true, true); + }); + break; + } + default: + break; + } + break; + } + case RtcpType::RTCP_XR: { + RtcpXRRRTR *xr = (RtcpXRRRTR *)rtcp; + if (xr->bt != 4) { + break; + } + auto it = _ssrc_to_track.find(xr->ssrc); + if (it == _ssrc_to_track.end()) { + WarnL << "未识别的 rtcp包:" << rtcp->dumpString(); + return; + } + auto &track = it->second; + track->rtcp_context_send->onRtcp(rtcp); + auto xrdlrr = track->rtcp_context_send->createRtcpXRDLRR(track->answer_ssrc_rtp, track->answer_ssrc_rtp); + sendRtcpPacket(xrdlrr->data(), xrdlrr->size(), true); + + break; + } + default: + break; + } + } +} + +/////////////////////////////////////////////////////////////////// + +void WebRtcTransportImp::createRtpChannel(const string &rid, uint32_t ssrc, MediaTrack &track) { + // rid --> RtpReceiverImp + auto &ref = track.rtp_channel[rid]; + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + ref = std::make_shared( + getPoller(), [&track, this, rid](RtpPacket::Ptr rtp) mutable { onSortedRtp(track, rid, std::move(rtp)); }, + [&track, weak_self, ssrc](const FCI_NACK &nack) mutable { + // nack发送可能由定时器异步触发 [AUTO-TRANSLATED:186d6723] + // Nack sending may be triggered asynchronously by a timer + auto strong_self = weak_self.lock(); + if (strong_self) { + strong_self->onSendNack(track, nack, ssrc); + } + }); + InfoL << "create rtp receiver of ssrc:" << ssrc << ", rid:" << rid << ", codec:" << track.plan_rtp->codec; +} + +void WebRtcTransportImp::updateTicker() { + _alive_ticker.resetTime(); +} + +void WebRtcTransportImp::onRtp(const char *buf, size_t len, uint64_t stamp_ms) { + _bytes_usage += len; + _alive_ticker.resetTime(); + + RtpHeader *rtp = (RtpHeader *)buf; + // 根据接收到的rtp的pt信息,找到该流的信息 [AUTO-TRANSLATED:9a97682c] + // Find the information of the stream according to the pt information of the received rtp + auto it = _pt_to_track.find(rtp->pt); + if (it == _pt_to_track.end()) { + WarnL << "unknown rtp pt:" << (int)rtp->pt; + return; + } + + if (_rtcp_rr_send_ticker.elapsedTime() > 5000) { + _rtcp_rr_send_ticker.resetTime(); + + auto ssrc = ntohl(rtp->ssrc); + auto track_it = _ssrc_to_track.find(ssrc); + if (track_it != _ssrc_to_track.end()) { + auto &track = track_it->second; + auto rtp_chn = track->getRtpChannel(ssrc); + if (rtp_chn) { + auto rr = rtp_chn->createRtcpRR(nullptr, track->answer_ssrc_rtp); + if (rr && rr->size() > 0) { + sendRtcpPacket(rr->data(), rr->size(), true); + } + } + } + } + + it->second->inputRtp(buf, len, stamp_ms, rtp); +} + +void WrappedRtpTrack::inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) { +#if 0 + auto seq = ntohs(rtp->seq); + if (track->media->type == TrackVideo && seq % 100 == 0) { + // 此处模拟接受丢包 [AUTO-TRANSLATED:7e8c2e5c] + // Simulate packet loss here + return; + } +#endif + + auto ssrc = ntohl(rtp->ssrc); + + // 修改ext id至统一 [AUTO-TRANSLATED:0769b0ec] + // Modify the ext id to be unified + string rid; + auto twcc_ext = track->rtp_ext_ctx->changeRtpExtId(rtp, true, &rid, RtpExtType::transport_cc); + + if (twcc_ext) { + _twcc_ctx.onRtp(ssrc, twcc_ext.getTransportCCSeq(), stamp_ms); + } + + auto &ref = track->rtp_channel[rid]; + if (!ref) { + _transport.createRtpChannel(rid, ssrc, *track); + } + + // 解析并排序rtp [AUTO-TRANSLATED:d382b65d] + // Parse and sort rtp + ref->inputRtp(track->media->type, track->plan_rtp->sample_rate, (uint8_t *)buf, len, false); +} + +void WrappedRtxTrack::inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) { + // 修改ext id至统一 [AUTO-TRANSLATED:0769b0ec] + // Modify the ext id to be unified + string rid; + track->rtp_ext_ctx->changeRtpExtId(rtp, true, &rid, RtpExtType::transport_cc); + + auto &ref = track->rtp_channel[rid]; + if (!ref) { + // 再接收到对应的rtp前,丢弃rtx包 [AUTO-TRANSLATED:d4ca6d69] + // Discard rtx packets before receiving the corresponding rtp + WarnL << "unknown rtx rtp, rid:" << rid << ", ssrc:" << ntohl(rtp->ssrc) << ", codec:" << track->plan_rtp->codec + << ", seq:" << ntohs(rtp->seq); + return; + } + + // 这里是rtx重传包 [AUTO-TRANSLATED:6efd3766] + // This is the rtx retransmission packet + // https://datatracker.ietf.org/doc/html/rfc4588#section-4 + auto payload = rtp->getPayloadData(); + auto size = rtp->getPayloadSize(len); + if (size < 2) { + return; + } + + // 前两个字节是原始的rtp的seq [AUTO-TRANSLATED:c57ff92d] + // The first two bytes are the original rtp seq + auto origin_seq = payload[0] << 8 | payload[1]; + // rtx 转换为 rtp [AUTO-TRANSLATED:be27f61b] + // rtx converted to rtp + rtp->pt = track->plan_rtp->pt; + rtp->seq = htons(origin_seq); + rtp->ssrc = htonl(ref->getSSRC()); + + memmove((uint8_t *)buf + 2, buf, payload - (uint8_t *)buf); + buf += 2; + len -= 2; + ref->inputRtp(track->media->type, track->plan_rtp->sample_rate, (uint8_t *)buf, len, true); +} + +void WebRtcTransportImp::onSendNack(MediaTrack &track, const FCI_NACK &nack, uint32_t ssrc) { + auto rtcp = RtcpFB::create(RTPFBType::RTCP_RTPFB_NACK, &nack, FCI_NACK::kSize); + rtcp->ssrc = htonl(track.answer_ssrc_rtp); + rtcp->ssrc_media = htonl(ssrc); + sendRtcpPacket((char *)rtcp.get(), rtcp->getSize(), true); +} + +void WebRtcTransportImp::onSendTwcc(uint32_t ssrc, const string &twcc_fci) { + auto rtcp = RtcpFB::create(RTPFBType::RTCP_RTPFB_TWCC, twcc_fci.data(), twcc_fci.size()); + rtcp->ssrc = htonl(0); + rtcp->ssrc_media = htonl(ssrc); + sendRtcpPacket((char *)rtcp.get(), rtcp->getSize(), true); +} + +/////////////////////////////////////////////////////////////////// + +void WebRtcTransportImp::onSortedRtp(MediaTrack &track, const string &rid, RtpPacket::Ptr rtp) { + if (track.media->type == TrackVideo && _pli_ticker.elapsedTime() > 2000) { + // 定期发送pli请求关键帧,方便非rtc等协议 [AUTO-TRANSLATED:b992f020] + // Regularly send pli requests for key frames, which is convenient for non-rtc protocols + _pli_ticker.resetTime(); + sendRtcpPli(rtp->getSSRC()); + + // 开启remb,则发送remb包调节比特率 [AUTO-TRANSLATED:20e98cea] + // If remb is enabled, send remb packets to adjust the bitrate + GET_CONFIG(size_t, remb_bit_rate, Rtc::kRembBitRate); + if (remb_bit_rate && _answer_sdp->supportRtcpFb(SdpConst::kRembRtcpFb)) { + sendRtcpRemb(rtp->getSSRC(), remb_bit_rate); + } + } + + onRecvRtp(track, rid, std::move(rtp)); +} + +/////////////////////////////////////////////////////////////////// + +void WebRtcTransportImp::onSendRtp(const RtpPacket::Ptr &rtp, bool flush, bool rtx) { + auto &track = _type_to_track[rtp->type]; + if (!track) { + // 忽略,对方不支持该编码类型 [AUTO-TRANSLATED:498ee936] + // Ignore, the other party does not support this encoding type + return; + } + if (!rtx) { + // 统计rtp发送情况,好做sr汇报 [AUTO-TRANSLATED:142028b2] + // Statistics of RTP sending, for SR reporting + track->rtcp_context_send->onRtp( + rtp->getSeq(), rtp->getStamp(), rtp->ntp_stamp, rtp->sample_rate, + rtp->size() - RtpPacket::kRtpTcpHeaderSize); + track->nack_list.pushBack(rtp); +#if 0 + // 此处模拟发送丢包 [AUTO-TRANSLATED:9612f08e] + // Simulate packet loss here + if (rtp->type == TrackVideo && rtp->getSeq() % 100 == 0) { + return; + } +#endif + } else { + // 发送rtx重传包 [AUTO-TRANSLATED:ae60e1fd] + // Send RTX retransmission packets + // TraceL << "send rtx rtp:" << rtp->getSeq(); + } + pair ctx { rtx, track.get() }; + sendRtpPacket(rtp->data() + RtpPacket::kRtpTcpHeaderSize, rtp->size() - RtpPacket::kRtpTcpHeaderSize, flush, &ctx); + _bytes_usage += rtp->size() - RtpPacket::kRtpTcpHeaderSize; + + if (_rtcp_sr_send_ticker.elapsedTime() > 5000) { + _rtcp_sr_send_ticker.resetTime(); + if (track->rtcp_context_send) { + auto sr = track->rtcp_context_send->createRtcpSR(track->answer_ssrc_rtp); + if (sr && sr->size() > 0) { + sendRtcpPacket(sr->data(), sr->size(), true); + } + } + } +} + +void WebRtcTransportImp::onBeforeEncryptRtp(const char *buf, int &len, void *ctx) { + auto pr = (pair *)ctx; + auto header = (RtpHeader *)buf; + + if (!pr->first || !pr->second->plan_rtx) { + // 普通的rtp,或者不支持rtx, 修改目标pt和ssrc [AUTO-TRANSLATED:e1264971] + // Ordinary RTP, or does not support RTX, modify the target PT and SSRC + pr->second->rtp_ext_ctx->changeRtpExtId(header, false); + header->pt = pr->second->plan_rtp->pt; + header->ssrc = htonl(pr->second->answer_ssrc_rtp); + } else { + // 重传的rtp, rtx [AUTO-TRANSLATED:e863a518] + // Retransmitted RTP, RTX + pr->second->rtp_ext_ctx->changeRtpExtId(header, false); + header->pt = pr->second->plan_rtx->pt; + if (pr->second->answer_ssrc_rtx) { + // 有rtx单独的ssrc,有些情况下,浏览器支持rtx,但是未指定rtx单独的ssrc [AUTO-TRANSLATED:181cee9a] + // RTX has a separate SSRC, in some cases, the browser supports RTX, but does not specify a separate SSRC for RTX + header->ssrc = htonl(pr->second->answer_ssrc_rtx); + } else { + // 未单独指定rtx的ssrc,那么使用rtp的ssrc [AUTO-TRANSLATED:dcafdd75] + // If RTX SSRC is not specified separately, use the RTP SSRC + header->ssrc = htonl(pr->second->answer_ssrc_rtp); + } + + auto origin_seq = ntohs(header->seq); + // seq跟原来的不一样 [AUTO-TRANSLATED:803f9a5e] + // The sequence is different from the original + header->seq = htons(_rtx_seq[pr->second->media->type]); + ++_rtx_seq[pr->second->media->type]; + + auto payload = header->getPayloadData(); + auto payload_size = header->getPayloadSize(len); + if (payload_size) { + // rtp负载后移两个字节,这两个字节用于存放osn [AUTO-TRANSLATED:b7eed70e] + // The RTP payload is shifted two bytes, these two bytes are used to store OSN + // https://datatracker.ietf.org/doc/html/rfc4588#section-4 + memmove(payload + 2, payload, payload_size); + } + payload[0] = origin_seq >> 8; + payload[1] = origin_seq & 0xFF; + len += 2; + } +} + +void WebRtcTransportImp::safeShutdown(const SockException &ex) { + std::weak_ptr weak_self = static_pointer_cast(shared_from_this()); + getPoller()->async([ex, weak_self]() { + if (auto strong_self = weak_self.lock()) { + strong_self->onShutdown(ex); + } + }); +} + +void WebRtcTransportImp::onShutdown(const SockException &ex) { + WarnL << ex; + WebRtcTransport::onShutdown(ex); + unrefSelf(); +} + +uint64_t WebRtcTransportImp::getBytesUsage() const { + return _bytes_usage; +} + +uint64_t WebRtcTransportImp::getDuration() const { + return _alive_ticker.createdTime() / 1000; +} + +void WebRtcTransportImp::onRtcpBye(){} + +///////////////////////////////////////////////////////////////////////////////////////////// + +void WebRtcTransportImp::registerSelf() { + _self = static_pointer_cast(shared_from_this()); + WebRtcTransportManager::Instance().addItem(getIdentifier(), _self); +} + +void WebRtcTransportImp::unrefSelf() { + _self = nullptr; +} + +void WebRtcTransportImp::unregisterSelf() { + DebugL; + unrefSelf(); + WebRtcTransportManager::Instance().removeItem(getIdentifier()); +} + +WebRtcTransportManager &WebRtcTransportManager::Instance() { + static WebRtcTransportManager s_instance; + return s_instance; +} + +void WebRtcTransportManager::addItem(const string &key, const WebRtcTransportImp::Ptr &ptr) { + lock_guard lck(_mtx); + _map[key] = ptr; +} + +WebRtcTransportImp::Ptr WebRtcTransportManager::getItem(const string &key) { + if (key.empty()) { + return nullptr; + } + lock_guard lck(_mtx); + auto it = _map.find(key); + if (it == _map.end()) { + return nullptr; + } + return it->second.lock(); +} + +void WebRtcTransportManager::removeItem(const string &key) { + lock_guard lck(_mtx); + _map.erase(key); +} + +////////////////////////////////////////////////////////////////////////////////////////////// + +WebRtcPluginManager &WebRtcPluginManager::Instance() { + static WebRtcPluginManager s_instance; + return s_instance; +} + +void WebRtcPluginManager::registerPlugin(const string &type, Plugin cb) { + lock_guard lck(_mtx_creator); + _map_creator[type] = std::move(cb); +} + +void WebRtcPluginManager::setListener(Listener cb) { + lock_guard lck(_mtx_creator); + _listener = std::move(cb); +} + +void WebRtcPluginManager::negotiateSdp(SocketHelper& sender, const string &type, const WebRtcArgs &args, const onCreateWebRtc &cb_in) { + onCreateWebRtc cb; + lock_guard lck(_mtx_creator); + if (_listener) { + auto listener = _listener; + auto args_ptr = args.shared_from_this(); + auto sender_ptr = static_pointer_cast(sender.shared_from_this()); + cb = [listener, sender_ptr, type, args_ptr, cb_in](const WebRtcInterface &rtc) { + listener(*sender_ptr, type, *args_ptr, rtc); + cb_in(rtc); + }; + } else { + cb = cb_in; + } + + auto it = _map_creator.find(type); + if (it == _map_creator.end()) { + cb_in(WebRtcException(SockException(Err_other, "the type can not supported"))); + return; + } + it->second(sender, args, cb); +} + +void echo_plugin(SocketHelper& sender, const WebRtcArgs &args, const onCreateWebRtc &cb) { + cb(*WebRtcEchoTest::create(EventPollerPool::Instance().getPoller())); +} + +void push_plugin(SocketHelper& sender, const WebRtcArgs &args, const onCreateWebRtc &cb) { + MediaInfo info(args["url"]); + Broadcast::PublishAuthInvoker invoker = [cb, info](const string &err, const ProtocolOption &option) mutable { + if (!err.empty()) { + cb(WebRtcException(SockException(Err_other, err))); + return; + } + + RtspMediaSourceImp::Ptr push_src; + std::shared_ptr push_src_ownership; + auto src = MediaSource::find(RTSP_SCHEMA, info.vhost, info.app, info.stream); + auto push_failed = (bool)src; + + while (src) { + // 尝试断连后继续推流 [AUTO-TRANSLATED:9eaaa6dd] + // Try to continue streaming after disconnecting + auto rtsp_src = dynamic_pointer_cast(src); + if (!rtsp_src) { + // 源不是rtsp推流产生的 [AUTO-TRANSLATED:47b87993] + // The source is not generated by RTSP streaming + break; + } + auto ownership = rtsp_src->getOwnership(); + if (!ownership) { + // 获取推流源所有权失败 [AUTO-TRANSLATED:256190b2] + // Failed to get the ownership of the streaming source + break; + } + push_src = std::move(rtsp_src); + push_src_ownership = std::move(ownership); + push_failed = false; + break; + } + + if (push_failed) { + cb(WebRtcException(SockException(Err_other, "already publishing"))); + return; + } + + if (!push_src) { + push_src = std::make_shared(info); + push_src_ownership = push_src->getOwnership(); + push_src->setProtocolOption(option); + } + auto rtc = WebRtcPusher::create(EventPollerPool::Instance().getPoller(), push_src, push_src_ownership, info, option, + WebRtcTransport::Role::PEER, WebRtcTransport::SignalingProtocols::WHEP_WHIP); + push_src->setListener(rtc); + cb(*rtc); + }; + + // rtsp推流需要鉴权 [AUTO-TRANSLATED:c2cbb7ed] + // RTSP streaming requires authentication + auto flag = NOTICE_EMIT(BroadcastMediaPublishArgs, Broadcast::kBroadcastMediaPublish, MediaOriginType::rtc_push, info, invoker, sender); + if (!flag) { + // 该事件无人监听,默认不鉴权 [AUTO-TRANSLATED:e1fbc6ae] + // No one is listening to this event, authentication is not enabled by default + invoker("", ProtocolOption()); + } +} + +void play_plugin(SocketHelper &sender, const WebRtcArgs &args, const onCreateWebRtc &cb) { + + MediaInfo info(args["url"]); + auto session_ptr = static_pointer_cast(sender.shared_from_this()); + Broadcast::AuthInvoker invoker = [cb, info, session_ptr](const string &err) mutable { + if (!err.empty()) { + cb(WebRtcException(SockException(Err_other, err))); + return; + } + + // webrtc播放的是rtsp的源 [AUTO-TRANSLATED:649ae489] + // WebRTC plays the RTSP source + info.schema = RTSP_SCHEMA; + MediaSource::findAsync(info, session_ptr, [=](const MediaSource::Ptr &src_in) mutable { + auto src = dynamic_pointer_cast(src_in); + if (!src) { + cb(WebRtcException(SockException(Err_other, "stream not found"))); + return; + } + // 还原成rtc,目的是为了hook时识别哪种播放协议 [AUTO-TRANSLATED:fe8dd2dc] + // Restore to RTC, the purpose is to identify which playback protocol during hooking + info.schema = "rtc"; + auto rtc = WebRtcPlayer::create(EventPollerPool::Instance().getPoller(), src, info, + WebRtcTransport::Role::PEER, WebRtcTransport::SignalingProtocols::WHEP_WHIP); + cb(*rtc); + }); + }; + + // 广播通用播放url鉴权事件 [AUTO-TRANSLATED:81e24be4] + // Broadcast a universal playback URL authentication event + auto flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, info, invoker, sender); + if (!flag) { + // 该事件无人监听,默认不鉴权 [AUTO-TRANSLATED:e1fbc6ae] + // No one is listening to this event, authentication is not enabled by default + invoker(""); + } +} + +static void setWebRtcArgs(const WebRtcArgs &args, WebRtcInterface &rtc) { + { + static auto is_vaild_ip = [](const std::string &ip) -> bool { + int a, b, c, d; + return sscanf(ip.c_str(), "%d.%d.%d.%d", &a, &b, &c, &d) == 4; + }; + std::string host = args["Host"]; + if (!host.empty()) { + auto local_ip = host.substr(0, host.find(':')); + if (!is_vaild_ip(local_ip) || local_ip == "127.0.0.1") { + local_ip = ""; + } + rtc.setLocalIp(std::move(local_ip)); + } + } + + bool preferred_tcp = args["preferred_tcp"]; + { + rtc.setPreferredTcp(preferred_tcp); + } + + { + vector cands; + { + auto cand_str = trim(args["cand_udp"]); + auto ip_port = toolkit::split(cand_str, ":"); + if (ip_port.size() == 2) { + // udp优先 [AUTO-TRANSLATED:b428f63d] + // UDP priority + auto ice_cand = makeIceCandidate(ip_port[0], atoi(ip_port[1].data()), preferred_tcp ? 100 : 120, "udp"); + cands.emplace_back(std::move(*ice_cand)); + } + } + { + auto cand_str = trim(args["cand_tcp"]); + auto ip_port = toolkit::split(cand_str, ":"); + if (ip_port.size() == 2) { + // tcp模式 [AUTO-TRANSLATED:62fddf91] + // TCP mode + auto ice_cand = makeIceCandidate(ip_port[0], atoi(ip_port[1].data()), preferred_tcp ? 120 : 100, "tcp"); + cands.emplace_back(std::move(*ice_cand)); + } + } + if (!cands.empty()) { + // udp优先 [AUTO-TRANSLATED:b428f63d] + // UDP priority + rtc.setIceCandidate(std::move(cands)); + } + } +} + +float WebRtcTransport::getTimeOutSec() { + GET_CONFIG(uint32_t, timeout, Rtc::kTimeOutSec); + if (timeout <= 0) { + WarnL << "config rtc. " << Rtc::kTimeOutSec << ": " << timeout << " not vaild"; + return 5; + } + return (float)timeout; +} + +static onceToken s_rtc_auto_register([]() { +#if !defined (NDEBUG) + // debug模式才开启echo插件 [AUTO-TRANSLATED:48fcb116] + // Enable echo plugin only in debug mode + WebRtcPluginManager::Instance().registerPlugin("echo", echo_plugin); +#endif + WebRtcPluginManager::Instance().registerPlugin("push", push_plugin); + WebRtcPluginManager::Instance().registerPlugin("play", play_plugin); + WebRtcPluginManager::Instance().setListener([](SocketHelper& sender, const std::string &type, const WebRtcArgs &args, const WebRtcInterface &rtc) { + setWebRtcArgs(args, const_cast(rtc)); + }); +}); + +void WebRtcTransport::onIceTransportRecvData(const toolkit::Buffer::Ptr& buffer, const IceTransport::Pair::Ptr& pair) { + return inputSockData(buffer->data(), buffer->size(), pair); +} + +void translateIPFromEnv(std::vector &v) { + for (auto iter = v.begin(); iter != v.end();) { + if (start_with(*iter, "$")) { + auto ip = toolkit::getEnv(*iter); + if (ip.empty()) { + iter = v.erase(iter); + } else { + *iter++ = ip; + } + } else { + ++iter; + } + } +} + +}// namespace mediakit diff --git a/webrtc/WebRtcTransport.h b/webrtc/WebRtcTransport.h index 79c65bbb..f2c00898 100644 --- a/webrtc/WebRtcTransport.h +++ b/webrtc/WebRtcTransport.h @@ -1,400 +1,449 @@ -/* - * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. - * - * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). - * - * Use of this source code is governed by MIT-like license that can be found in the - * LICENSE file in the root of the source tree. All contributing project authors - * may be found in the AUTHORS file in the root of the source tree. - */ - -#pragma once - -#include -#include -#include "DtlsTransport.hpp" -#include "IceServer.hpp" -#include "SrtpSession.hpp" -#include "StunPacket.hpp" -#include "Sdp.h" -#include "Util/mini.h" -#include "Poller/EventPoller.h" -#include "Network/Socket.h" -#include "Network/Session.h" -#include "Nack.h" -#include "TwccContext.h" -#include "SctpAssociation.hpp" -#include "Rtcp/RtcpContext.h" - -namespace mediakit { - -// RTC配置项目 [AUTO-TRANSLATED:65784416] -// RTC configuration project -namespace Rtc { -extern const std::string kPort; -extern const std::string kTcpPort; -extern const std::string kTimeOutSec; -}//namespace RTC - -class WebRtcInterface { -public: - virtual ~WebRtcInterface() = default; - virtual std::string getAnswerSdp(const std::string &offer) = 0; - virtual const std::string& getIdentifier() const = 0; - virtual const std::string& deleteRandStr() const { static std::string s_null; return s_null; } - virtual void setIceCandidate(std::vector cands) {} - virtual void setLocalIp(std::string localIp) {} - virtual void setPreferredTcp(bool flag) {} -}; - -class WebRtcException : public WebRtcInterface { -public: - WebRtcException(const SockException &ex) : _ex(ex) {}; - std::string getAnswerSdp(const std::string &offer) override { - throw _ex; - } - const std::string &getIdentifier() const override { - static std::string s_null; - return s_null; - } - -private: - SockException _ex; -}; - -class WebRtcTransport : public WebRtcInterface, public RTC::DtlsTransport::Listener, public RTC::IceServer::Listener, public std::enable_shared_from_this -#ifdef ENABLE_SCTP - , public RTC::SctpAssociation::Listener -#endif -{ -public: - using Ptr = std::shared_ptr; - WebRtcTransport(const EventPoller::Ptr &poller); - - /** - * 创建对象 - * Create object - - * [AUTO-TRANSLATED:830344e4] - */ - virtual void onCreate(); - - /** - * 销毁对象 - * Destroy object - - * [AUTO-TRANSLATED:1016b97b] - */ - virtual void onDestory(); - - /** - * 创建webrtc answer sdp - * @param offer offer sdp - * @return answer sdp - * Create webrtc answer sdp - * @param offer offer sdp - * @return answer sdp - - * [AUTO-TRANSLATED:d9b027d7] - */ - std::string getAnswerSdp(const std::string &offer) override final; - - /** - * 获取对象唯一id - * Get object unique id - - * [AUTO-TRANSLATED:9ad519c6] - */ - const std::string& getIdentifier() const override; - const std::string& deleteRandStr() const override; - - /** - * socket收到udp数据 - * @param buf 数据指针 - * @param len 数据长度 - * @param tuple 数据来源 - * Socket receives udp data - * @param buf data pointer - * @param len data length - * @param tuple data source - - * [AUTO-TRANSLATED:1ee86069] - */ - void inputSockData(char *buf, int len, RTC::TransportTuple *tuple); - - /** - * 发送rtp - * @param buf rtcp内容 - * @param len rtcp长度 - * @param flush 是否flush socket - * @param ctx 用户指针 - * Send rtp - * @param buf rtcp content - * @param len rtcp length - * @param flush whether to flush socket - * @param ctx user pointer - - * [AUTO-TRANSLATED:aa833695] - */ - void sendRtpPacket(const char *buf, int len, bool flush, void *ctx = nullptr); - void sendRtcpPacket(const char *buf, int len, bool flush, void *ctx = nullptr); - void sendDatachannel(uint16_t streamId, uint32_t ppid, const char *msg, size_t len); - - const EventPoller::Ptr& getPoller() const; - Session::Ptr getSession() const; - -protected: - // // dtls相关的回调 //// [AUTO-TRANSLATED:31a1f32c] - // // dtls related callbacks //// - void OnDtlsTransportConnecting(const RTC::DtlsTransport *dtlsTransport) override; - void OnDtlsTransportConnected(const RTC::DtlsTransport *dtlsTransport, - RTC::SrtpSession::CryptoSuite srtpCryptoSuite, - uint8_t *srtpLocalKey, - size_t srtpLocalKeyLen, - uint8_t *srtpRemoteKey, - size_t srtpRemoteKeyLen, - std::string &remoteCert) override; - - void OnDtlsTransportFailed(const RTC::DtlsTransport *dtlsTransport) override; - void OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) override; - void OnDtlsTransportSendData(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) override; - void OnDtlsTransportApplicationDataReceived(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) override; - -protected: - // // ice相关的回调 /// [AUTO-TRANSLATED:30abf693] - // // ice related callbacks /// - void OnIceServerSendStunPacket(const RTC::IceServer *iceServer, const RTC::StunPacket *packet, RTC::TransportTuple *tuple) override; - void OnIceServerConnected(const RTC::IceServer *iceServer) override; - void OnIceServerCompleted(const RTC::IceServer *iceServer) override; - void OnIceServerDisconnected(const RTC::IceServer *iceServer) override; - -#ifdef ENABLE_SCTP - void OnSctpAssociationConnecting(RTC::SctpAssociation* sctpAssociation) override; - void OnSctpAssociationConnected(RTC::SctpAssociation* sctpAssociation) override; - void OnSctpAssociationFailed(RTC::SctpAssociation* sctpAssociation) override; - void OnSctpAssociationClosed(RTC::SctpAssociation* sctpAssociation) override; - void OnSctpAssociationSendData(RTC::SctpAssociation* sctpAssociation, const uint8_t* data, size_t len) override; - void OnSctpAssociationMessageReceived(RTC::SctpAssociation *sctpAssociation, uint16_t streamId, uint32_t ppid, - const uint8_t *msg, size_t len) override; -#endif - -protected: - virtual void onStartWebRTC() = 0; - virtual void onRtcConfigure(RtcConfigure &configure) const; - virtual void onCheckSdp(SdpType type, RtcSession &sdp) = 0; - virtual void onSendSockData(Buffer::Ptr buf, bool flush = true, RTC::TransportTuple *tuple = nullptr) = 0; - - virtual void onRtp(const char *buf, size_t len, uint64_t stamp_ms) = 0; - virtual void onRtcp(const char *buf, size_t len) = 0; - virtual void onShutdown(const SockException &ex) = 0; - virtual void onBeforeEncryptRtp(const char *buf, int &len, void *ctx) = 0; - virtual void onBeforeEncryptRtcp(const char *buf, int &len, void *ctx) = 0; - virtual void onRtcpBye() = 0; - -protected: - void sendRtcpRemb(uint32_t ssrc, size_t bit_rate); - void sendRtcpPli(uint32_t ssrc); - -private: - void sendSockData(const char *buf, size_t len, RTC::TransportTuple *tuple); - void setRemoteDtlsFingerprint(const RtcSession &remote); - -protected: - RtcSession::Ptr _offer_sdp; - RtcSession::Ptr _answer_sdp; - std::shared_ptr _ice_server; - -private: - mutable std::string _delete_rand_str; - std::string _identifier; - EventPoller::Ptr _poller; - std::shared_ptr _dtls_transport; - std::shared_ptr _srtp_session_send; - std::shared_ptr _srtp_session_recv; - Ticker _ticker; - // 循环池 [AUTO-TRANSLATED:b7059f37] - // Cycle pool - ResourcePool _packet_pool; - -#ifdef ENABLE_SCTP - RTC::SctpAssociationImp::Ptr _sctp; -#endif -}; - -class RtpChannel; -class MediaTrack { -public: - using Ptr = std::shared_ptr; - const RtcCodecPlan *plan_rtp; - const RtcCodecPlan *plan_rtx; - uint32_t offer_ssrc_rtp = 0; - uint32_t offer_ssrc_rtx = 0; - uint32_t answer_ssrc_rtp = 0; - uint32_t answer_ssrc_rtx = 0; - const RtcMedia *media; - RtpExtContext::Ptr rtp_ext_ctx; - - //for send rtp - NackList nack_list; - RtcpContext::Ptr rtcp_context_send; - - //for recv rtp - std::unordered_map > rtp_channel; - std::shared_ptr getRtpChannel(uint32_t ssrc) const; -}; - -struct WrappedMediaTrack { - MediaTrack::Ptr track; - explicit WrappedMediaTrack(MediaTrack::Ptr ptr): track(ptr) {} - virtual ~WrappedMediaTrack() {} - virtual void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) = 0; -}; - -struct WrappedRtxTrack: public WrappedMediaTrack { - explicit WrappedRtxTrack(MediaTrack::Ptr ptr) - : WrappedMediaTrack(std::move(ptr)) {} - void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) override; -}; - -class WebRtcTransportImp; - -struct WrappedRtpTrack : public WrappedMediaTrack { - explicit WrappedRtpTrack(MediaTrack::Ptr ptr, TwccContext& twcc, WebRtcTransportImp& t) - : WrappedMediaTrack(std::move(ptr)) - , _twcc_ctx(twcc) - , _transport(t) {} - TwccContext& _twcc_ctx; - WebRtcTransportImp& _transport; - void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) override; -}; - -class WebRtcTransportImp : public WebRtcTransport { -public: - using Ptr = std::shared_ptr; - ~WebRtcTransportImp() override; - - uint64_t getBytesUsage() const; - uint64_t getDuration() const; - bool canSendRtp() const; - bool canRecvRtp() const; - void onSendRtp(const RtpPacket::Ptr &rtp, bool flush, bool rtx = false); - - void createRtpChannel(const std::string &rid, uint32_t ssrc, MediaTrack &track); - void removeTuple(RTC::TransportTuple* tuple); - void safeShutdown(const SockException &ex); - - void setPreferredTcp(bool flag) override; - void setLocalIp(std::string local_ip) override; - void setIceCandidate(std::vector cands) override; - -protected: - void OnIceServerSelectedTuple(const RTC::IceServer *iceServer, RTC::TransportTuple *tuple) override; - WebRtcTransportImp(const EventPoller::Ptr &poller); - void OnDtlsTransportApplicationDataReceived(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) override; - void onStartWebRTC() override; - void onSendSockData(Buffer::Ptr buf, bool flush = true, RTC::TransportTuple *tuple = nullptr) override; - void onCheckSdp(SdpType type, RtcSession &sdp) override; - void onRtcConfigure(RtcConfigure &configure) const override; - - void onRtp(const char *buf, size_t len, uint64_t stamp_ms) override; - void onRtcp(const char *buf, size_t len) override; - void onBeforeEncryptRtp(const char *buf, int &len, void *ctx) override; - void onBeforeEncryptRtcp(const char *buf, int &len, void *ctx) override {}; - void onCreate() override; - void onDestory() override; - void onShutdown(const SockException &ex) override; - virtual void onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) {} - void updateTicker(); - float getLossRate(TrackType type); - void onRtcpBye() override; - -private: - void onSortedRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp); - void onSendNack(MediaTrack &track, const FCI_NACK &nack, uint32_t ssrc); - void onSendTwcc(uint32_t ssrc, const std::string &twcc_fci); - - void registerSelf(); - void unregisterSelf(); - void unrefSelf(); - void onCheckAnswer(RtcSession &sdp); - -private: - bool _preferred_tcp = false; - uint16_t _rtx_seq[2] = {0, 0}; - // 用掉的总流量 [AUTO-TRANSLATED:713b61c9] - // Total traffic used - uint64_t _bytes_usage = 0; - // 保持自我强引用 [AUTO-TRANSLATED:c2dc228f] - // Keep self strong reference - Ptr _self; - // 检测超时的定时器 [AUTO-TRANSLATED:a58e1388] - // Timeout detection timer - Timer::Ptr _timer; - // 刷新计时器 [AUTO-TRANSLATED:61eb11e5] - // Refresh timer - Ticker _alive_ticker; - // pli rtcp计时器 [AUTO-TRANSLATED:a1a5fd18] - // pli rtcp timer - Ticker _pli_ticker; - // twcc rtcp发送上下文对象 [AUTO-TRANSLATED:aef6476a] - // twcc rtcp send context object - TwccContext _twcc_ctx; - // 根据发送rtp的track类型获取相关信息 [AUTO-TRANSLATED:ff31c272] - // Get relevant information based on the track type of the sent rtp - MediaTrack::Ptr _type_to_track[2]; - // 根据rtcp的ssrc获取相关信息,收发rtp和rtx的ssrc都会记录 [AUTO-TRANSLATED:6c57cd48] - // Get relevant information based on the ssrc of the rtcp, the ssrc of sending and receiving rtp and rtx will be recorded - std::unordered_map _ssrc_to_track; - // 根据接收rtp的pt获取相关信息 [AUTO-TRANSLATED:39e56d7d] - // Get relevant information based on the pt of the received rtp - std::unordered_map> _pt_to_track; - std::vector _cands; - // http访问时的host ip [AUTO-TRANSLATED:e8fe6957] - // Host ip for http access - std::string _local_ip; -}; - -class WebRtcTransportManager { -public: - friend class WebRtcTransportImp; - static WebRtcTransportManager &Instance(); - WebRtcTransportImp::Ptr getItem(const std::string &key); - -private: - WebRtcTransportManager() = default; - void addItem(const std::string &key, const WebRtcTransportImp::Ptr &ptr); - void removeItem(const std::string &key); - -private: - mutable std::mutex _mtx; - std::unordered_map > _map; -}; - -class WebRtcArgs : public std::enable_shared_from_this { -public: - virtual ~WebRtcArgs() = default; - virtual variant operator[](const std::string &key) const = 0; -}; - -using onCreateWebRtc = std::function; -class WebRtcPluginManager { -public: - using Plugin = std::function; - using Listener = std::function; - - static WebRtcPluginManager &Instance(); - - void registerPlugin(const std::string &type, Plugin cb); - void setListener(Listener cb); - void negotiateSdp(Session &sender, const std::string &type, const WebRtcArgs &args, const onCreateWebRtc &cb); - -private: - WebRtcPluginManager() = default; - -private: - mutable std::mutex _mtx_creator; - Listener _listener; - std::unordered_map _map_creator; -}; - -}// namespace mediakit \ No newline at end of file +/* + * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT-like license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLMEDIAKIT_WEBRTC_TRANSPORT_H +#define ZLMEDIAKIT_WEBRTC_TRANSPORT_H + +#include +#include +#include +#include "DtlsTransport.hpp" +#include "IceTransport.hpp" +#include "SrtpSession.hpp" +#include "StunPacket.hpp" +#include "Sdp.h" +#include "Util/mini.h" +#include "Poller/EventPoller.h" +#include "Network/Socket.h" +#include "Network/Session.h" +#include "Nack.h" +#include "TwccContext.h" +#include "SctpAssociation.hpp" +#include "Rtcp/RtcpContext.h" +#include "Rtsp/RtspMediaSource.h" + +using namespace RTC; +namespace mediakit { + +// ICE transport policy enum +enum class IceTransportPolicy { + kAll = 0, // 不限制,支持所有连接类型(默认) + kRelayOnly = 1, // 仅支持Relay转发 + kP2POnly = 2 // 仅支持P2P直连 +}; + +// RTC配置项目 [AUTO-TRANSLATED:65784416] +// RTC configuration project +namespace Rtc { +extern const std::string kPort; +extern const std::string kTcpPort; +extern const std::string kTimeOutSec; +extern const std::string kSignalingPort; +extern const std::string kSignalingSslPort; +extern const std::string kIcePort; +extern const std::string kIceTcpPort; +extern const std::string kEnableTurn; +extern const std::string kIceTransportPolicy; +extern const std::string kIceUfrag; +extern const std::string kIcePwd; +extern const std::string kExternIP; +extern const std::string kInterfaces; +}//namespace RTC + +class WebRtcInterface { +public: + virtual ~WebRtcInterface() = default; + virtual std::string getAnswerSdp(const std::string &offer) = 0; + virtual std::string createOfferSdp() = 0; + virtual void setAnswerSdp(const std::string &answer) = 0; + virtual const std::string& getIdentifier() const = 0; + virtual const std::string& deleteRandStr() const { static std::string s_null; return s_null; } + virtual void setIceCandidate(std::vector cands) {} + virtual void setLocalIp(std::string localIp) {} + virtual void setPreferredTcp(bool flag) {} + + using onGatheringCandidateCB = std::function; + virtual void gatheringCandidate(IceServerInfo::Ptr ice_server, onGatheringCandidateCB cb = nullptr) = 0; +}; + +class WebRtcException : public WebRtcInterface { +public: + WebRtcException(const toolkit::SockException &ex) : _ex(ex) {}; + + std::string createOfferSdp() override { + throw _ex; + } + + std::string getAnswerSdp(const std::string &offer) override { + throw _ex; + } + + void setAnswerSdp(const std::string &answer) override { + throw _ex; + } + + void gatheringCandidate(IceServerInfo::Ptr ice_server, onGatheringCandidateCB cb = nullptr) override { + throw _ex; + } + + const std::string &getIdentifier() const override { + static std::string s_null; + return s_null; + } + +private: + toolkit::SockException _ex; +}; + +class WebRtcTransport : public WebRtcInterface, public RTC::DtlsTransport::Listener, public IceTransport::Listener, public std::enable_shared_from_this +#ifdef ENABLE_SCTP + , public RTC::SctpAssociation::Listener +#endif +{ +public: + enum class Role { + NONE = 0, + CLIENT, + PEER, + }; + static const char* RoleStr(Role role); + + enum class SignalingProtocols { + Invalid = -1, + WHEP_WHIP = 0, + WEBSOCKET = 1, //FOR P2P + }; + static const char* SignalingProtocolsStr(SignalingProtocols protocol); + + using WeakPtr = std::weak_ptr; + using Ptr = std::shared_ptr; + WebRtcTransport(const toolkit::EventPoller::Ptr &poller); + + virtual void onCreate(); + + virtual void onDestory(); + + std::string getAnswerSdp(const std::string &offer) override; + void setAnswerSdp(const std::string &answer) override; + + const RtcSession::Ptr& answerSdp() const { + return _answer_sdp; + } + + std::string createOfferSdp() override; + + const std::string& getIdentifier() const override; + const std::string& deleteRandStr() const override; + + void inputSockData(const char *buf, int len, const toolkit::SocketHelper::Ptr& socket, struct sockaddr *addr = nullptr, int addr_len = 0); + void inputSockData(const char *buf, int len, const IceTransport::Pair::Ptr& pair = nullptr); + void sendRtpPacket(const char *buf, int len, bool flush, void *ctx = nullptr); + void sendRtcpPacket(const char *buf, int len, bool flush, void *ctx = nullptr); + void sendDatachannel(uint16_t streamId, uint32_t ppid, const char *msg, size_t len); + + const toolkit::EventPoller::Ptr &getPoller() const { return _poller; } + void setPoller(toolkit::EventPoller::Ptr poller) { _poller = std::move(poller); } + + toolkit::Session::Ptr getSession() const; + void removePair(const toolkit::SocketHelper *socket); + + Role getRole() const { return _role; } + void setRole(Role role) { _role = role; } + + SignalingProtocols getSignalingProtocols() const { return _signaling_protocols; } + void setSignalingProtocols(SignalingProtocols signaling_protocols) { _signaling_protocols = signaling_protocols; } + + float getTimeOutSec(); + + void getTransportInfo(const std::function &callback) const; + + void setOnShutdown(std::function cb); + + void gatheringCandidate(IceServerInfo::Ptr ice_server, onGatheringCandidateCB cb = nullptr) override; + void connectivityCheck(SdpAttrCandidate candidate_attr, const std::string &ufrag, const std::string &pwd); + void connectivityCheckForSFU(); + + void setOnStartWebRTC(std::function on_start); + +protected: + // DtlsTransport::Listener; dtls相关的回调 + void OnDtlsTransportConnecting(const RTC::DtlsTransport *dtlsTransport) override; + void OnDtlsTransportConnected(const RTC::DtlsTransport *dtlsTransport, + RTC::SrtpSession::CryptoSuite srtpCryptoSuite, + uint8_t *srtpLocalKey, + size_t srtpLocalKeyLen, + uint8_t *srtpRemoteKey, + size_t srtpRemoteKeyLen, + std::string &remoteCert) override; + void OnDtlsTransportFailed(const RTC::DtlsTransport *dtlsTransport) override; + void OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) override; + void OnDtlsTransportSendData(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) override; + void OnDtlsTransportApplicationDataReceived(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) override; + +protected: + // ice相关的回调; IceTransport::Listener. + void onIceTransportRecvData(const toolkit::Buffer::Ptr& buffer, const IceTransport::Pair::Ptr& pair) override; + void onIceTransportGatheringCandidate(const IceTransport::Pair::Ptr& pair, const CandidateInfo& candidate) override; + void onIceTransportCompleted() override; + void onIceTransportDisconnected() override; + + // SctpAssociation::Listener +#ifdef ENABLE_SCTP + void OnSctpAssociationConnecting(RTC::SctpAssociation* sctpAssociation) override; + void OnSctpAssociationConnected(RTC::SctpAssociation* sctpAssociation) override; + void OnSctpAssociationFailed(RTC::SctpAssociation* sctpAssociation) override; + void OnSctpAssociationClosed(RTC::SctpAssociation* sctpAssociation) override; + void OnSctpAssociationSendData(RTC::SctpAssociation* sctpAssociation, const uint8_t* data, size_t len) override; + void OnSctpAssociationMessageReceived(RTC::SctpAssociation *sctpAssociation, uint16_t streamId, uint32_t ppid, + const uint8_t *msg, size_t len) override; +#endif + +protected: + virtual void onStartWebRTC() = 0; + virtual void onRtcConfigure(RtcConfigure &configure) const; + virtual void onCheckSdp(SdpType type, RtcSession &sdp) = 0; + virtual void onSendSockData(toolkit::Buffer::Ptr buf, bool flush = true, const IceTransport::Pair::Ptr& pair = nullptr) = 0; + + virtual void onRtp(const char *buf, size_t len, uint64_t stamp_ms) = 0; + virtual void onRtcp(const char *buf, size_t len) = 0; + virtual void onShutdown(const toolkit::SockException &ex); + virtual void onBeforeEncryptRtp(const char *buf, int &len, void *ctx) = 0; + virtual void onBeforeEncryptRtcp(const char *buf, int &len, void *ctx) = 0; + virtual void onRtcpBye() = 0; + +protected: + void sendRtcpRemb(uint32_t ssrc, size_t bit_rate); + void sendRtcpPli(uint32_t ssrc); + +private: + void sendSockData(const char *buf, size_t len, const IceTransport::Pair::Ptr& pair = nullptr); + void setRemoteDtlsFingerprint(SdpType type, const RtcSession &remote); + +protected: + SignalingProtocols _signaling_protocols = SignalingProtocols::WHEP_WHIP; + Role _role = Role::PEER; + RtcSession::Ptr _offer_sdp; + RtcSession::Ptr _answer_sdp; + + IceAgent::Ptr _ice_agent; + onGatheringCandidateCB _on_gathering_candidate = nullptr; + +private: + mutable std::string _delete_rand_str; + std::string _identifier; + toolkit::EventPoller::Ptr _poller; + DtlsTransport::Ptr _dtls_transport; + SrtpSession::Ptr _srtp_session_send; + SrtpSession::Ptr _srtp_session_recv; + toolkit::Ticker _ticker; + // 循环池 [AUTO-TRANSLATED:b7059f37] + // Cycle pool + toolkit::ResourcePool _packet_pool; + + //超时功能实现 + toolkit::Ticker _recv_ticker; + std::shared_ptr _check_timer; + std::function _on_start; + std::function _on_shutdown; + +#ifdef ENABLE_SCTP + RTC::SctpAssociationImp::Ptr _sctp; +#endif +}; + +class RtpChannel; +class MediaTrack { +public: + using Ptr = std::shared_ptr; + const RtcCodecPlan *plan_rtp; + const RtcCodecPlan *plan_rtx; + uint32_t offer_ssrc_rtp = 0; + uint32_t offer_ssrc_rtx = 0; + uint32_t answer_ssrc_rtp = 0; + uint32_t answer_ssrc_rtx = 0; + const RtcMedia *media; + RtpExtContext::Ptr rtp_ext_ctx; + + //for send rtp + NackList nack_list; + RtcpContext::Ptr rtcp_context_send; + + //for recv rtp + std::unordered_map > rtp_channel; + std::shared_ptr getRtpChannel(uint32_t ssrc) const; +}; + +struct WrappedMediaTrack { + MediaTrack::Ptr track; + explicit WrappedMediaTrack(MediaTrack::Ptr ptr): track(ptr) {} + virtual ~WrappedMediaTrack() {} + virtual void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) = 0; +}; + +struct WrappedRtxTrack: public WrappedMediaTrack { + explicit WrappedRtxTrack(MediaTrack::Ptr ptr) + : WrappedMediaTrack(std::move(ptr)) {} + void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) override; +}; + +class WebRtcTransportImp; + +struct WrappedRtpTrack : public WrappedMediaTrack { + explicit WrappedRtpTrack(MediaTrack::Ptr ptr, TwccContext& twcc, WebRtcTransportImp& t) + : WrappedMediaTrack(std::move(ptr)) + , _twcc_ctx(twcc) + , _transport(t) {} + TwccContext& _twcc_ctx; + WebRtcTransportImp& _transport; + void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) override; +}; + +class WebRtcTransportImp : public WebRtcTransport { +public: + using Ptr = std::shared_ptr; + ~WebRtcTransportImp() override; + + uint64_t getBytesUsage() const; + uint64_t getDuration() const; + bool canSendRtp() const; + bool canRecvRtp() const; + bool canSendRtp(const RtcMedia& media) const; + bool canRecvRtp(const RtcMedia& media) const; + void onSendRtp(const RtpPacket::Ptr &rtp, bool flush, bool rtx = false); + + void createRtpChannel(const std::string &rid, uint32_t ssrc, MediaTrack &track); + void safeShutdown(const toolkit::SockException &ex); + + void setPreferredTcp(bool flag) override; + void setLocalIp(std::string local_ip) override; + void setIceCandidate(std::vector cands) override; + +protected: + + // // ice相关的回调 /// [AUTO-TRANSLATED:30abf693] + // // ice related callbacks /// + + WebRtcTransportImp(const toolkit::EventPoller::Ptr &poller); + void OnDtlsTransportApplicationDataReceived(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) override; + void onStartWebRTC() override; + void onSendSockData(toolkit::Buffer::Ptr buf, bool flush = true, const IceTransport::Pair::Ptr& pair = nullptr) override; + void onCheckSdp(SdpType type, RtcSession &sdp) override; + void onRtcConfigure(RtcConfigure &configure) const override; + + void onRtp(const char *buf, size_t len, uint64_t stamp_ms) override; + void onRtcp(const char *buf, size_t len) override; + void onBeforeEncryptRtp(const char *buf, int &len, void *ctx) override; + void onBeforeEncryptRtcp(const char *buf, int &len, void *ctx) override {}; + void onCreate() override; + void onDestory() override; + void onShutdown(const toolkit::SockException &ex) override; + virtual void onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) {} + void updateTicker(); + float getLossRate(TrackType type); + void onRtcpBye() override; + +private: + void onSortedRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp); + void onSendNack(MediaTrack &track, const FCI_NACK &nack, uint32_t ssrc); + void onSendTwcc(uint32_t ssrc, const std::string &twcc_fci); + + void registerSelf(); + void unregisterSelf(); + void unrefSelf(); + void onCheckAnswer(RtcSession &sdp); + +private: + bool _preferred_tcp = false; + uint16_t _rtx_seq[2] = {0, 0}; + // 用掉的总流量 [AUTO-TRANSLATED:713b61c9] + // Total traffic used + uint64_t _bytes_usage = 0; + // 保持自我强引用 [AUTO-TRANSLATED:c2dc228f] + // Keep self strong reference + Ptr _self; + // 检测超时的定时器 [AUTO-TRANSLATED:a58e1388] + // Timeout detection timer + toolkit::Timer::Ptr _timer; + // 刷新计时器 [AUTO-TRANSLATED:61eb11e5] + // Refresh timer + toolkit::Ticker _alive_ticker; + // pli rtcp计时器 [AUTO-TRANSLATED:a1a5fd18] + // pli rtcp timer + toolkit::Ticker _pli_ticker; + + toolkit::Ticker _rtcp_sr_send_ticker; + toolkit::Ticker _rtcp_rr_send_ticker; + + // twcc rtcp发送上下文对象 [AUTO-TRANSLATED:aef6476a] + // twcc rtcp send context object + TwccContext _twcc_ctx; + // 根据发送rtp的track类型获取相关信息 [AUTO-TRANSLATED:ff31c272] + // Get relevant information based on the track type of the sent rtp + MediaTrack::Ptr _type_to_track[2]; + // 根据rtcp的ssrc获取相关信息,收发rtp和rtx的ssrc都会记录 [AUTO-TRANSLATED:6c57cd48] + // Get relevant information based on the ssrc of the rtcp, the ssrc of sending and receiving rtp and rtx will be recorded + std::unordered_map _ssrc_to_track; + // 根据接收rtp的pt获取相关信息 [AUTO-TRANSLATED:39e56d7d] + // Get relevant information based on the pt of the received rtp + std::unordered_map> _pt_to_track; + std::vector _cands; + // http访问时的host ip [AUTO-TRANSLATED:e8fe6957] + // Host ip for http access + std::string _local_ip; +}; + +class WebRtcTransportManager { +public: + friend class WebRtcTransportImp; + static WebRtcTransportManager &Instance(); + WebRtcTransportImp::Ptr getItem(const std::string &key); + +private: + WebRtcTransportManager() = default; + void addItem(const std::string &key, const WebRtcTransportImp::Ptr &ptr); + void removeItem(const std::string &key); + +private: + mutable std::mutex _mtx; + std::unordered_map > _map; +}; + +class WebRtcArgs : public std::enable_shared_from_this { +public: + virtual ~WebRtcArgs() = default; + virtual toolkit::variant operator[](const std::string &key) const = 0; +}; + +using onCreateWebRtc = std::function; +class WebRtcPluginManager { +public: + using Plugin = std::function; + using Listener = std::function; + + static WebRtcPluginManager &Instance(); + + void registerPlugin(const std::string &type, Plugin cb); + void setListener(Listener cb); + void negotiateSdp(toolkit::SocketHelper& sender, const std::string &type, const WebRtcArgs &args, const onCreateWebRtc &cb); + +private: + WebRtcPluginManager() = default; + +private: + mutable std::mutex _mtx_creator; + Listener _listener; + std::unordered_map _map_creator; +}; + +void translateIPFromEnv(std::vector &v); + +}// namespace mediakit + +#endif // ZLMEDIAKIT_WEBRTC_TRANSPORT_H diff --git a/webrtc/readme.md b/webrtc/readme.md index 3ac82dee..1f2c2c07 100644 --- a/webrtc/readme.md +++ b/webrtc/readme.md @@ -1,13 +1,6 @@ # 致谢与声明 本文件夹下部分文件提取自[MediaSoup](https://github.com/versatica/mediasoup) ,分别为: -- ice相关功能: - - IceServer.cpp - - IceServer.hpp - - StunPacket.cpp - - StunPacket.hpp - - Utils.hpp - - dtls相关功能: - DtlsTransport.cpp - DtlsTransport.hpp diff --git a/webrtc/webrtcSignal.txt b/webrtc/webrtcSignal.txt new file mode 100644 index 00000000..f09bd176 --- /dev/null +++ b/webrtc/webrtcSignal.txt @@ -0,0 +1,132 @@ +webrtc websocket 信令 + +# register 注册 + +``` json +#client/peer --> server +{ + "class" : "request", + "method" : "register", + "transaction_id" : "HFaq5Jp2agKfDjizOT5jGpiPtOQ8yays" + "room_id" : "room_1", +} +#server --> client/peer +#success +#支持turn +{ + "class" : "accept", + "method" : "register", + "transaction_id" : "HFaq5Jp2agKfDjizOT5jGpiPtOQ8yays" + "room_id" : "room_1", + "ice_servers" : [ { + "pwd" : "ZLMediaKit", + "ufrag" : "ZLMediaKit", + "url" : "turn:10.9.120.61:3478?transport=udp" + } + ], +} + +#不支持turn +{ + "class" : "accept", + "method" : "register", + "transaction_id" : "HFaq5Jp2agKfDjizOT5jGpiPtOQ8yays" + "room_id" : "room_1", + "ice_servers" : [ { + "pwd" : "ZLMediaKit", + "ufrag" : "ZLMediaKit", + "url" : "stun:10.9.120.61:3478?transport=udp" + } + ], +} +``` + +#fail +``` json +{ + "class" : "reject", + "method" : "register", + "transaction_id" : "2DiOjTulA4Glp9Si7yHdQypibAn2LPaX" + "reason" : "alreadly register", + "room_id" : "room_1", +} +``` + +# unregister 注销 +# client --> server +```json +{ + "class" : "request", + "method" : "unregister", + "transaction_id" : "0Xbgr86OIacWvjJIc03EsxH3QIF1ou8m" + "room_id" : "room_1", +} +``` +# server --> client +# success +``` json +{ + "class" : "accept", + "method" : "unregister", + "room_id" : "room1", + "transaction_id" : "0Xbgr86OIacWvjJIc03EsxH3QIF1ou8m" +} + +``` + +# 呼叫 +# client --> server,server -透传-> peer +{ + "class" : "request", + "method" : "call", + "transaction_id" : "qUpN8C49bGiyOHk6WNanAFq2viSkk6HC", + "guest_id" : "guest1_EDuVWIxLUMlDDKDa", + "room_id" : "room_1", + "type" : "play", + "vhost" : "__defaultVhost__", + "app" : "live", + "stream" : "test", + "sdp" : "v=0\r\no=- 7040255305116218076 1 IN IP4 0.0.0.0\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0 1\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS\r\nm=video 9 UDP/TLS/RTP/SAVPF 102 124 123 102 124 123 35 98 100 96\r\nc=IN IP4 0.0.0.0\r\na=ice-ufrag:rBIAAR9AH0A=_1\r\na=ice-pwd:V1WhKKOK9jrhmLPmZemhcO5h\r\na=ice-options:trickle\r\na=fingerprint:sha-256 B4:51:C0:D2:0E:60:70:C2:CD:40:3A:8E:33:EB:FC:67:F6:29:72:89:AC:23:48:90:A0:D7:C0:07:44:7B:F1:79\r\na=setup:active\r\na=mid:0\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\na=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\na=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\na=extmap:10 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07\r\na=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r\na=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\na=extmap:14 urn:ietf:params:rtp-hdrext:toffset\r\na=recvonly\r\na=rtcp-mux\r\na=rtcp-rsize\r\na=rtpmap:102 H264/90000\r\na=rtcp-fb:102 ccm fir\r\na=rtcp-fb:102 goog-remb\r\na=rtcp-fb:102 nack\r\na=rtcp-fb:102 nack pli\r\na=rtcp-fb:102 transport-cc\r\na=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f\r\na=rtpmap:124 H264/90000\r\na=rtcp-fb:124 ccm fir\r\na=rtcp-fb:124 goog-remb\r\na=rtcp-fb:124 nack\r\na=rtcp-fb:124 nack pli\r\na=rtcp-fb:124 transport-cc\r\na=fmtp:124 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f\r\na=rtpmap:123 H264/90000\r\na=rtcp-fb:123 ccm fir\r\na=rtcp-fb:123 goog-remb\r\na=rtcp-fb:123 nack\r\na=rtcp-fb:123 nack pli\r\na=rtcp-fb:123 transport-cc\r\na=fmtp:123 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f\r\na=rtpmap:102 H264/90000\r\na=rtcp-fb:102 ccm fir\r\na=rtcp-fb:102 goog-remb\r\na=rtcp-fb:102 nack\r\na=rtcp-fb:102 nack pli\r\na=rtcp-fb:102 transport-cc\r\na=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f\r\na=rtpmap:124 H264/90000\r\na=rtcp-fb:124 ccm fir\r\na=rtcp-fb:124 goog-remb\r\na=rtcp-fb:124 nack\r\na=rtcp-fb:124 nack pli\r\na=rtcp-fb:124 transport-cc\r\na=fmtp:124 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f\r\na=rtpmap:123 H264/90000\r\na=rtcp-fb:123 ccm fir\r\na=rtcp-fb:123 goog-remb\r\na=rtcp-fb:123 nack\r\na=rtcp-fb:123 nack pli\r\na=rtcp-fb:123 transport-cc\r\na=fmtp:123 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f\r\na=rtpmap:35 AV1/90000\r\na=rtcp-fb:35 ccm fir\r\na=rtcp-fb:35 goog-remb\r\na=rtcp-fb:35 nack\r\na=rtcp-fb:35 nack pli\r\na=rtcp-fb:35 transport-cc\r\na=rtpmap:98 VP9/90000\r\na=rtcp-fb:98 ccm fir\r\na=rtcp-fb:98 goog-remb\r\na=rtcp-fb:98 nack\r\na=rtcp-fb:98 nack pli\r\na=rtcp-fb:98 transport-cc\r\na=fmtp:98 profile-id==0\r\na=rtpmap:100 VP9/90000\r\na=rtcp-fb:100 ccm fir\r\na=rtcp-fb:100 goog-remb\r\na=rtcp-fb:100 nack\r\na=rtcp-fb:100 nack pli\r\na=rtcp-fb:100 transport-cc\r\na=fmtp:100 profile-id==2\r\na=rtpmap:96 VP8/90000\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 goog-remb\r\na=rtcp-fb:96 nack\r\na=rtcp-fb:96 nack pli\r\na=rtcp-fb:96 transport-cc\r\nm=audio 9 UDP/TLS/RTP/SAVPF 0 8 111 96\r\nc=IN IP4 0.0.0.0\r\na=ice-ufrag:rBIAAR9AH0A=_1\r\na=ice-pwd:V1WhKKOK9jrhmLPmZemhcO5h\r\na=ice-options:trickle\r\na=fingerprint:sha-256 B4:51:C0:D2:0E:60:70:C2:CD:40:3A:8E:33:EB:FC:67:F6:29:72:89:AC:23:48:90:A0:D7:C0:07:44:7B:F1:79\r\na=setup:active\r\na=mid:1\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\na=extmap:9 urn:ietf:params:rtp-hdrext:csrc-audio-level\r\na=recvonly\r\na=rtcp-mux\r\na=rtcp-rsize\r\na=rtpmap:0 PCMU/8000\r\na=rtcp-fb:0 goog-remb\r\na=rtcp-fb:0 transport-cc\r\na=rtpmap:8 PCMA/8000\r\na=rtcp-fb:8 goog-remb\r\na=rtcp-fb:8 transport-cc\r\na=rtpmap:111 opus/48000\r\na=rtcp-fb:111 goog-remb\r\na=rtcp-fb:111 transport-cc\r\na=rtpmap:96 mpeg4-generic/48000\r\na=rtcp-fb:96 goog-remb\r\na=rtcp-fb:96 transport-cc\r\n" +} + +#peer->server, server -透传->client +{ + "class" : "accept", + "method" : "call", + "transaction_id" : "qUpN8C49bGiyOHk6WNanAFq2viSkk6HC", + "guest_id" : "guest1_EDuVWIxLUMlDDKDa", + "room_id" : "room1", + "vhost" : "__defaultVhost__", + "app" : "live", + "stream" : "test", + "type" : "play", + "sdp" : "v=0\r\no=- 7040255305116218076 1 IN IP4 10.9.120.61\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0 1\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS\r\na=ice-lite\r\nm=video 28000 UDP/TLS/RTP/SAVPF 102\r\nc=IN IP4 10.9.120.61\r\na=rtcp:28000 IN IP4 10.9.120.61\r\na=ice-ufrag:rBIAAW1gbWA=_1\r\na=ice-pwd:NmPNJgMbz9z2kH3g97yZFbCn\r\na=ice-options:trickle\r\na=fingerprint:sha-256 B4:51:C0:D2:0E:60:70:C2:CD:40:3A:8E:33:EB:FC:67:F6:29:72:89:AC:23:48:90:A0:D7:C0:07:44:7B:F1:79\r\na=setup:passive\r\na=mid:0\r\na=ice-lite\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\na=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\na=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\na=extmap:10 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07\r\na=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r\na=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\na=extmap:14 urn:ietf:params:rtp-hdrext:toffset\r\na=sendonly\r\na=rtcp-mux\r\na=rtpmap:102 H264/90000\r\na=rtcp-fb:102 ccm fir\r\na=rtcp-fb:102 goog-remb\r\na=rtcp-fb:102 nack\r\na=rtcp-fb:102 nack pli\r\na=rtcp-fb:102 transport-cc\r\na=fmtp:102 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f\r\na=msid:zlmediakit-mslabel zlmediakit-label-0\r\na=ssrc:1 cname:zlmediakit-rtp\r\na=ssrc:1 msid:zlmediakit-mslabel zlmediakit-label-0\r\na=ssrc:1 mslabel:zlmediakit-mslabel\r\na=ssrc:1 label:zlmediakit-label-0\r\nm=audio 28000 UDP/TLS/RTP/SAVPF 0\r\nc=IN IP4 10.9.120.61\r\na=rtcp:28000 IN IP4 10.9.120.61\r\na=ice-ufrag:rBIAAW1gbWA=_1\r\na=ice-pwd:NmPNJgMbz9z2kH3g97yZFbCn\r\na=ice-options:trickle\r\na=fingerprint:sha-256 B4:51:C0:D2:0E:60:70:C2:CD:40:3A:8E:33:EB:FC:67:F6:29:72:89:AC:23:48:90:A0:D7:C0:07:44:7B:F1:79\r\na=setup:passive\r\na=mid:1\r\na=ice-lite\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\na=extmap:9 urn:ietf:params:rtp-hdrext:csrc-audio-level\r\na=inactive\r\na=rtcp-mux\r\na=rtpmap:0 PCMU/8000/1\r\na=rtcp-fb:0 goog-remb\r\na=rtcp-fb:0 transport-cc\r\na=msid:zlmediakit-mslabel zlmediakit-label-1\r\na=ssrc:2 cname:zlmediakit-rtp\r\na=ssrc:2 msid:zlmediakit-mslabel zlmediakit-label-1\r\na=ssrc:2 mslabel:zlmediakit-mslabel\r\na=ssrc:2 label:zlmediakit-label-1\r\n" +} + +# candidate +```peer--> server -透传-> peer +{ + "class" : "indication", + "method" : "candidate", + "transaction_id" : "7oEa2vcYvps7aZ1g9UGIPoFf5PrTl2N9", + "guest_id" : "guest1_n9WyhNMR42EvkOvE", + "room_id" : "room_1", + "candidate" : "7e0de214 1 udp 2113955071 192.168.1.1 46411 typ host", + "ufrag" : "rBIAAW1gbWA=_1" + "pwd" : "gDNJZM0uVLlnNnthaE41KXOp", +} + +# bye + +``` +{ + "class" : "indication", + "method" : "bye", + "transaction_id" : "86RdplPz21Ow9DwR1gvXjsAdmh30TAf3" + "guest_id" : "guest1_n9WyhNMR42EvkOvE", + "reason" : "peer unregister", + "room_id" : "room_1", +} +``` + +