feat: 增加webrtc代理拉流 (#4389)
Some checks failed
Android / build (push) Has been cancelled
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Docker / build (push) Has been cancelled
Linux / build (push) Has been cancelled
macOS / build (push) Has been cancelled
Windows / build (push) Has been cancelled

- 增加客户端模式,支持主动拉流、推流:
   - 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 <mtdxc@126.com>
Co-authored-by: cqm <cqm@97kid.com>
This commit is contained in:
baigao-X 2025-09-20 16:23:30 +08:00 committed by GitHub
parent 97d2a1fb08
commit 3fb43c5fef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
72 changed files with 16912 additions and 10319 deletions

View File

@ -18,10 +18,15 @@ jobs:
with: with:
vcpkgDirectory: '${{github.workspace}}/vcpkg' vcpkgDirectory: '${{github.workspace}}/vcpkg'
vcpkgTriplet: arm64-osx vcpkgTriplet: arm64-osx
# 2024.06.01 # 2025.07.11
vcpkgGitCommitId: '47364fbc300756f64f7876b549d9422d5f3ec0d3' vcpkgGitCommitId: 'efcfaaf60d7ec57a159fc3110403d939bfb69729'
vcpkgArguments: 'openssl libsrtp[openssl] usrsctp' vcpkgArguments: 'openssl libsrtp[openssl] usrsctp'
- name: 安装指定 CMake
uses: jwlawson/actions-setup-cmake@v2
with:
cmake-version: '3.30.5'
- name: 编译 - name: 编译
uses: lukka/run-cmake@v3 uses: lukka/run-cmake@v3
with: with:

@ -1 +1 @@
Subproject commit 69098a18b9af0c47549d9a271c054d13ca92b006 Subproject commit ca98c98457b1163cca1f7d8db62827c115fec6d1

View File

@ -472,6 +472,17 @@ if(ENABLE_SRT)
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_SRT) update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_SRT)
endif() 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: # Solution folders:
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------

View File

@ -47,7 +47,7 @@
## 功能清单 ## 功能清单
### 功能一览 ### 功能一览
<img width="749" alt="image" src="https://github.com/user-attachments/assets/8cf5911b-4603-4aa0-8e24-0acb0c616a82" /> <img width="749" alt="功能预览" src="https://github.com/user-attachments/assets/7072fe1c-e2b3-47e9-bd50-e5266523edf1">
- RTSP[S] - RTSP[S]
- RTSP[S] 服务器支持RTMP/MP4/HLS转RTSP[S],支持亚马逊echo show这样的设备 - RTSP[S] 服务器支持RTMP/MP4/HLS转RTSP[S],支持亚马逊echo show这样的设备
@ -131,6 +131,8 @@
- 支持webrtc over tcp模式 - 支持webrtc over tcp模式
- 优秀的nack、jitter buffer算法, 抗丢包能力卓越 - 优秀的nack、jitter buffer算法, 抗丢包能力卓越
- 支持whip/whep协议 - 支持whip/whep协议
- [支持ice-full,支持作为webrtc客户端拉流、推流以及p2p模式](./webrtc/USAGE.md)
- [SRT支持](./srt/srt.md) - [SRT支持](./srt/srt.md)
- 其他 - 其他
- 支持丰富的restful api以及web hook事件 - 支持丰富的restful api以及web hook事件

View File

@ -45,7 +45,7 @@
## Feature List ## Feature List
### Overview of Features ### Overview of Features
<img width="800" alt="Overview of Features" src="https://github.com/ZLMediaKit/ZLMediaKit/assets/11495632/481ea769-5b27-495e-bf7d-31191e6af9d2"> <img width="749" alt="Overview of Features" src="https://github.com/user-attachments/assets/7072fe1c-e2b3-47e9-bd50-e5266523edf1">
- RTSP[S] - RTSP[S]
- RTSP[S] server, supports RTMP/MP4/HLS to RTSP[S] conversion, supports devices such as Amazon Echo Show - 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 - Supports WebRTC over TCP mode
- Excellent NACK and jitter buffer algorithms with outstanding packet loss resistance - Excellent NACK and jitter buffer algorithms with outstanding packet loss resistance
- Supports WHIP/WHEP protocols - 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) - [SRT support](./srt/srt.md)
- Others - Others
- Supports rich RESTful APIs and webhook events - Supports rich RESTful APIs and webhook events

View File

@ -346,11 +346,30 @@ udp_recv_socket_buffer=4194304
merge_frame=1 merge_frame=1
[rtc] [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播放推流、播放超时时间 #rtc播放推流、播放超时时间
timeoutSec=15 timeoutSec=15
#本机对rtc客户端的可见ip作为服务器时一般为公网ip可有多个用','分开当置空时会自动获取网卡ip #本机对rtc客户端的可见ip作为服务器时一般为公网ip可有多个用','分开当置空时会自动获取网卡ip
#同时支持环境变量,以$开头,如"$EXTERN_IP"; 请参考https://github.com/ZLMediaKit/ZLMediaKit/pull/1786 #同时支持环境变量,以$开头,如"$EXTERN_IP"; 请参考https://github.com/ZLMediaKit/ZLMediaKit/pull/1786
externIP= externIP=
#当指定了interfaces,ICE服务器会使用指定网卡bind socket
#以解决公网IP使用弹性公网IP配置实现(部署机器无法bind该公网ip的问题)
#支持环境变量,以$开头,如"$PRIVATE_IP"
interfaces=
#rtc udp服务器监听端口号所有rtc客户端将通过该端口传输stun/dtls/srtp/srtcp数据 #rtc udp服务器监听端口号所有rtc客户端将通过该端口传输stun/dtls/srtp/srtcp数据
#该端口是多线程的,同时支持客户端网络切换导致的连接迁移 #该端口是多线程的,同时支持客户端网络切换导致的连接迁移
#需要注意的是如果服务器在nat内需要做端口映射时必须确保外网映射端口跟该端口一致 #需要注意的是如果服务器在nat内需要做端口映射时必须确保外网映射端口跟该端口一致
@ -358,7 +377,7 @@ port=8000
#rtc tcp服务器监听端口号在udp 不通的情况下会使用tcp传输数据 #rtc tcp服务器监听端口号在udp 不通的情况下会使用tcp传输数据
#该端口是多线程的,同时支持客户端网络切换导致的连接迁移 #该端口是多线程的,同时支持客户端网络切换导致的连接迁移
#需要注意的是如果服务器在nat内需要做端口映射时必须确保外网映射端口跟该端口一致 #需要注意的是如果服务器在nat内需要做端口映射时必须确保外网映射端口跟该端口一致
tcpPort = 8000 tcpPort=8000
#设置remb比特率非0时关闭twcc并开启remb。该设置在rtc推流时有效可以控制推流画质 #设置remb比特率非0时关闭twcc并开启remb。该设置在rtc推流时有效可以控制推流画质
#目前已经实现twcc自动调整码率关闭remb根据真实网络状况调整码率 #目前已经实现twcc自动调整码率关闭remb根据真实网络状况调整码率
rembBitRate=0 rembBitRate=0

View File

@ -2732,6 +2732,154 @@
} }
}, },
"response": [] "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": [ "event": [

View File

@ -57,6 +57,10 @@
#include "../webrtc/WebRtcPlayer.h" #include "../webrtc/WebRtcPlayer.h"
#include "../webrtc/WebRtcPusher.h" #include "../webrtc/WebRtcPusher.h"
#include "../webrtc/WebRtcEchoTest.h" #include "../webrtc/WebRtcEchoTest.h"
#include "../webrtc/WebRtcSignalingPeer.h"
#include "../webrtc/WebRtcSignalingSession.h"
#include "../webrtc/WebRtcProxyPlayer.h"
#include "../webrtc/WebRtcProxyPlayerImp.h"
#endif #endif
#if defined(ENABLE_VERSION) #if defined(ENABLE_VERSION)
@ -314,84 +318,6 @@ static inline void addHttpListener(){
}); });
} }
template <typename Type>
class ServiceController {
public:
using Pointer = std::shared_ptr<Type>;
void clear() {
decltype(_map) copy;
{
std::lock_guard<std::recursive_mutex> lck(_mtx);
copy.swap(_map);
}
}
size_t erase(const std::string &key) {
Pointer erase_ptr;
{
std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> lck(_mtx);
return _map.size();
}
Pointer find(const std::string &key) const {
std::lock_guard<std::recursive_mutex> lck(_mtx);
auto it = _map.find(key);
if (it == _map.end()) {
return nullptr;
}
return it->second;
}
void for_each(const std::function<void(const std::string&, const Pointer&)>& cb) {
std::lock_guard<std::recursive_mutex> lck(_mtx);
auto it = _map.begin();
while (it != _map.end()) {
cb(it->first, it->second);
it++;
}
}
template<class ..._Args>
Pointer make(const std::string &key, _Args&& ...__args) {
// assert(!find(key));
auto server = std::make_shared<Type>(std::forward<_Args>(__args)...);
std::lock_guard<std::recursive_mutex> lck(_mtx);
auto it = _map.emplace(key, server);
assert(it.second);
return server;
}
template<class ..._Args>
Pointer makeWithAction(const std::string &key, function<void(Pointer)> action, _Args&& ...__args) {
// assert(!find(key));
auto server = std::make_shared<Type>(std::forward<_Args>(__args)...);
action(server);
std::lock_guard<std::recursive_mutex> lck(_mtx);
auto it = _map.emplace(key, server);
assert(it.second);
return server;
}
private:
std::unordered_map<std::string, Pointer> _map;
mutable std::recursive_mutex _mtx;
};
// 拉流代理器列表 [AUTO-TRANSLATED:6dcfb11f] // 拉流代理器列表 [AUTO-TRANSLATED:6dcfb11f]
// Pull stream proxy list // Pull stream proxy list
static ServiceController<PlayerProxy> s_player_proxy; static ServiceController<PlayerProxy> s_player_proxy;
@ -2127,35 +2053,6 @@ void installWebApi() {
}); });
#ifdef ENABLE_WEBRTC #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){ api_regist("/index/api/webrtc",[](API_ARGS_STRING_ASYNC){
CHECK_ARGS("type"); CHECK_ARGS("type");
auto type = allArgs["type"]; auto type = allArgs["type"];
@ -2163,7 +2060,7 @@ void installWebApi() {
CHECK(!offer.empty(), "http body(webrtc offer sdp) is empty"); CHECK(!offer.empty(), "http body(webrtc offer sdp) is empty");
auto &session = static_cast<Session&>(sender); auto &session = static_cast<Session&>(sender);
auto args = std::make_shared<WebRtcArgsImp>(allArgs, sender.getIdentifier()); auto args = std::make_shared<WebRtcArgsImp<std::string>>(allArgs, sender.getIdentifier());
WebRtcPluginManager::Instance().negotiateSdp(session, type, *args, [invoker, val, offer, headerOut](const WebRtcInterface &exchanger) mutable { WebRtcPluginManager::Instance().negotiateSdp(session, type, *args, [invoker, val, offer, headerOut](const WebRtcInterface &exchanger) mutable {
auto &handler = const_cast<WebRtcInterface &>(exchanger); auto &handler = const_cast<WebRtcInterface &>(exchanger);
try { try {
@ -2186,7 +2083,7 @@ void installWebApi() {
auto &session = static_cast<Session&>(sender); auto &session = static_cast<Session&>(sender);
auto location = std::string(session.overSsl() ? "https://" : "http://") + allArgs["host"] + delete_webrtc_url; auto location = std::string(session.overSsl() ? "https://" : "http://") + allArgs["host"] + delete_webrtc_url;
auto args = std::make_shared<WebRtcArgsImp>(allArgs, sender.getIdentifier()); auto args = std::make_shared<WebRtcArgsImp<std::string>>(allArgs, sender.getIdentifier());
WebRtcPluginManager::Instance().negotiateSdp(session, type, *args, [invoker, offer, headerOut, location](const WebRtcInterface &exchanger) mutable { WebRtcPluginManager::Instance().negotiateSdp(session, type, *args, [invoker, offer, headerOut, location](const WebRtcInterface &exchanger) mutable {
auto &handler = const_cast<WebRtcInterface &>(exchanger); auto &handler = const_cast<WebRtcInterface &>(exchanger);
try { try {
@ -2220,6 +2117,103 @@ void installWebApi() {
obj->safeShutdown(SockException(Err_shutdown, "deleted by http api")); obj->safeShutdown(SockException(Err_shutdown, "deleted by http api"));
invoker(200, headerOut, ""); 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<WebRtcProxyPlayerImp>(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 #endif
#if defined(ENABLE_VERSION) #if defined(ENABLE_VERSION)

View File

@ -1,228 +1,364 @@
/* /*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
* *
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). * 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 * 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 * 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. * may be found in the AUTHORS file in the root of the source tree.
*/ */
#ifndef ZLMEDIAKIT_WEBAPI_H #ifndef ZLMEDIAKIT_WEBAPI_H
#define ZLMEDIAKIT_WEBAPI_H #define ZLMEDIAKIT_WEBAPI_H
#include <string> #include <string>
#include <functional> #include <functional>
#include "json/json.h" #include "json/json.h"
#include "Common/Parser.h" #include "Common/Parser.h"
#include "Network/Socket.h" #include "Network/Socket.h"
#include "Http/HttpSession.h" #include "Http/HttpSession.h"
#include "Common/MultiMediaSourceMuxer.h" #include "Common/MultiMediaSourceMuxer.h"
// 配置文件路径 [AUTO-TRANSLATED:8a373c2f] #if defined(ENABLE_WEBRTC)
// Configuration file path #include "webrtc/WebRtcTransport.h"
extern std::string g_ini_file; #endif
namespace mediakit { // 配置文件路径 [AUTO-TRANSLATED:8a373c2f]
// //////////RTSP服务器配置/////////// [AUTO-TRANSLATED:950e1981] // Configuration file path
// //////////RTSP server configuration/////////// extern std::string g_ini_file;
namespace Rtsp {
extern const std::string kPort; namespace mediakit {
} //namespace Rtsp // //////////RTSP服务器配置/////////// [AUTO-TRANSLATED:950e1981]
// //////////RTSP server configuration///////////
// //////////RTMP服务器配置/////////// [AUTO-TRANSLATED:8de6f41f] namespace Rtsp {
// //////////RTMP server configuration/////////// extern const std::string kPort;
namespace Rtmp { } //namespace Rtsp
extern const std::string kPort;
} //namespace RTMP // //////////RTMP服务器配置/////////// [AUTO-TRANSLATED:8de6f41f]
} // namespace mediakit // //////////RTMP server configuration///////////
namespace Rtmp {
namespace API { extern const std::string kPort;
typedef enum { } //namespace RTMP
NotFound = -500,//未找到 } // namespace mediakit
Exception = -400,//代码抛异常
InvalidArgs = -300,//参数不合法 namespace API {
SqlFailed = -200,//sql执行失败 typedef enum {
AuthFailed = -100,//鉴权失败 NotFound = -500,//未找到
OtherFailed = -1,//业务代码执行失败, Exception = -400,//代码抛异常
Success = 0//执行成功 InvalidArgs = -300,//参数不合法
} ApiErr; SqlFailed = -200,//sql执行失败
AuthFailed = -100,//鉴权失败
extern const std::string kSecret; OtherFailed = -1,//业务代码执行失败,
}//namespace API Success = 0//执行成功
} ApiErr;
class ApiRetException: public std::runtime_error {
public: extern const std::string kSecret;
ApiRetException(const char *str = "success" ,int code = API::Success):runtime_error(str){ }//namespace API
_code = code;
} class ApiRetException: public std::runtime_error {
int code(){ return _code; } public:
private: ApiRetException(const char *str = "success" ,int code = API::Success):runtime_error(str){
int _code; _code = code;
}; }
int code(){ return _code; }
class AuthException : public ApiRetException { private:
public: int _code;
AuthException(const char *str):ApiRetException(str,API::AuthFailed){} };
};
class AuthException : public ApiRetException {
class InvalidArgsException: public ApiRetException { public:
public: AuthException(const char *str):ApiRetException(str,API::AuthFailed){}
InvalidArgsException(const char *str):ApiRetException(str,API::InvalidArgs){} };
};
class InvalidArgsException: public ApiRetException {
class SuccessException: public ApiRetException { public:
public: InvalidArgsException(const char *str):ApiRetException(str,API::InvalidArgs){}
SuccessException():ApiRetException("success",API::Success){} };
};
class SuccessException: public ApiRetException {
using ApiArgsType = std::map<std::string, std::string, mediakit::StrCaseCompare>; public:
SuccessException():ApiRetException("success",API::Success){}
template<typename Args, typename Key> };
std::string getValue(Args &args, const Key &key) {
auto it = args.find(key); using ApiArgsType = std::map<std::string, std::string, mediakit::StrCaseCompare>;
if (it == args.end()) {
return ""; template<typename Args, typename Key>
} std::string getValue(Args &args, const Key &key) {
return it->second; auto it = args.find(key);
} if (it == args.end()) {
return "";
template<typename Key> }
std::string getValue(Json::Value &args, const Key &key) { return it->second;
auto value = args.find(key); }
if (value == nullptr) {
return ""; template<typename Key>
} std::string getValue(Json::Value &args, const Key &key) {
return value->asString(); auto value = args.find(key);
} if (value == nullptr) {
return "";
template<typename Key> }
std::string getValue(std::string &args, const Key &key) { return value->asString();
return ""; }
}
template<typename Key>
template <typename Key> std::string getValue(std::string &args, const Key &key) {
std::string getValue(const mediakit::Parser &parser, const Key &key) { return "";
auto ret = getValue(parser.getUrlArgs(), key); }
if (!ret.empty()) {
return ret; template <typename Key>
} std::string getValue(const mediakit::Parser &parser, const Key &key) {
return getValue(parser.getHeader(), key); auto ret = getValue(parser.getUrlArgs(), key);
} if (!ret.empty()) {
return ret;
template<typename Args, typename Key> }
std::string getValue(const mediakit::Parser &parser, Args &args, const Key &key) { return getValue(parser.getHeader(), key);
auto ret = getValue(args, key); }
if (!ret.empty()) {
return ret; template<typename Key>
} std::string getValue(mediakit::Parser &parser, const Key &key) {
return getValue(parser, key); return getValue((const mediakit::Parser &) parser, key);
} }
template<typename Args> template<typename Args, typename Key>
class HttpAllArgs { std::string getValue(const mediakit::Parser &parser, Args &args, const Key &key) {
mediakit::Parser* _parser = nullptr; auto ret = getValue(args, key);
Args* _args = nullptr; if (!ret.empty()) {
public: return ret;
const mediakit::Parser& parser; }
Args& args; return getValue(parser, key);
}
HttpAllArgs(const mediakit::Parser &p, Args &a): parser(p), args(a) {}
template<typename Args>
HttpAllArgs(const HttpAllArgs &that): _parser(new mediakit::Parser(that.parser)), class HttpAllArgs {
_args(new Args(that.args)), mediakit::Parser* _parser = nullptr;
parser(*_parser), args(*_args) {} Args* _args = nullptr;
~HttpAllArgs() { public:
if (_parser) { const mediakit::Parser& parser;
delete _parser; Args& args;
}
if (_args) { HttpAllArgs(const mediakit::Parser &p, Args &a): parser(p), args(a) {}
delete _args;
} HttpAllArgs(const HttpAllArgs &that): _parser(new mediakit::Parser(that.parser)),
} _args(new Args(that.args)),
parser(*_parser), args(*_args) {}
template<typename Key> ~HttpAllArgs() {
toolkit::variant operator[](const Key &key) const { if (_parser) {
return (toolkit::variant)getValue(parser, args, key); delete _parser;
} }
}; if (_args) {
delete _args;
using ArgsMap = HttpAllArgs<ApiArgsType>; }
using ArgsJson = HttpAllArgs<Json::Value>; }
using ArgsString = HttpAllArgs<std::string>;
template<typename Key>
#define API_ARGS_MAP toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const ArgsMap &allArgs, Json::Value &val toolkit::variant operator[](const Key &key) const {
#define API_ARGS_MAP_ASYNC API_ARGS_MAP, const mediakit::HttpSession::HttpResponseInvoker &invoker return (toolkit::variant)getValue(parser, args, key);
#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 const Args& getArgs() const {
#define API_ARGS_STRING_ASYNC API_ARGS_STRING, const mediakit::HttpSession::HttpResponseInvoker &invoker return args;
#define API_ARGS_VALUE sender, headerOut, allArgs, val }
// 注册http请求参数是map<string, variant, StrCaseCompare>类型的http api [AUTO-TRANSLATED:8a273897] const mediakit::Parser &getParser() const {
// Register http request parameters as map<string, variant, StrCaseCompare> type http api return parser;
void api_regist(const std::string &api_path, const std::function<void(API_ARGS_MAP)> &func); }
// 注册http请求参数是map<string, variant, StrCaseCompare>类型,但是可以异步回复的的http api [AUTO-TRANSLATED:9da5d5f5] };
// Register http request parameters as map<string, variant, StrCaseCompare> type, but can be replied asynchronously http api
void api_regist(const std::string &api_path, const std::function<void(API_ARGS_MAP_ASYNC)> &func); using ArgsMap = HttpAllArgs<ApiArgsType>;
using ArgsJson = HttpAllArgs<Json::Value>;
// 注册http请求参数是Json::Value类型的http api(可以支持多级嵌套的json参数对象) [AUTO-TRANSLATED:c4794456] using ArgsString = HttpAllArgs<std::string>;
// 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<void(API_ARGS_JSON)> &func); #define API_ARGS_MAP toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const ArgsMap &allArgs, Json::Value &val
// 注册http请求参数是Json::Value类型但是可以异步回复的的http api [AUTO-TRANSLATED:742e57fd] #define API_ARGS_MAP_ASYNC API_ARGS_MAP, const mediakit::HttpSession::HttpResponseInvoker &invoker
// Register http request parameters as Json::Value type, but can be replied asynchronously http api #define API_ARGS_JSON toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const ArgsJson &allArgs, Json::Value &val
void api_regist(const std::string &api_path, const std::function<void(API_ARGS_JSON_ASYNC)> &func); #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
// 注册http请求参数是http原始请求信息的http api [AUTO-TRANSLATED:72d3fe93] #define API_ARGS_STRING_ASYNC API_ARGS_STRING, const mediakit::HttpSession::HttpResponseInvoker &invoker
// Register http request parameters as http original request information http api #define API_ARGS_VALUE sender, headerOut, allArgs, val
void api_regist(const std::string &api_path, const std::function<void(API_ARGS_STRING)> &func);
// 注册http请求参数是http原始请求信息的异步回复的http api [AUTO-TRANSLATED:49feefa8] // 注册http请求参数是map<string, variant, StrCaseCompare>类型的http api [AUTO-TRANSLATED:8a273897]
// Register http request parameters as http original request information asynchronous reply http api // Register http request parameters as map<string, variant, StrCaseCompare> type http api
void api_regist(const std::string &api_path, const std::function<void(API_ARGS_STRING_ASYNC)> &func); void api_regist(const std::string &api_path, const std::function<void(API_ARGS_MAP)> &func);
// 注册http请求参数是map<string, variant, StrCaseCompare>类型,但是可以异步回复的的http api [AUTO-TRANSLATED:9da5d5f5]
template<typename Args, typename Key> // Register http request parameters as map<string, variant, StrCaseCompare> type, but can be replied asynchronously http api
bool checkArgs(Args &args, const Key &key) { void api_regist(const std::string &api_path, const std::function<void(API_ARGS_MAP_ASYNC)> &func);
return !args[key].empty();
} // 注册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)
template<typename Args, typename Key, typename ...KeyTypes> void api_regist(const std::string &api_path, const std::function<void(API_ARGS_JSON)> &func);
bool checkArgs(Args &args, const Key &key, const KeyTypes &...keys) { // 注册http请求参数是Json::Value类型但是可以异步回复的的http api [AUTO-TRANSLATED:742e57fd]
return checkArgs(args, key) && checkArgs(args, keys...); // 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<void(API_ARGS_JSON_ASYNC)> &func);
// 检查http url中或body中或http header参数是否为空的宏 [AUTO-TRANSLATED:9de001a4] // 注册http请求参数是http原始请求信息的http api [AUTO-TRANSLATED:72d3fe93]
// Check whether the http url, body or http header parameters are empty // Register http request parameters as http original request information http api
#define CHECK_ARGS(...) \ void api_regist(const std::string &api_path, const std::function<void(API_ARGS_STRING)> &func);
if(!checkArgs(allArgs,##__VA_ARGS__)){ \ // 注册http请求参数是http原始请求信息的异步回复的http api [AUTO-TRANSLATED:49feefa8]
throw InvalidArgsException("Required parameter missed: " #__VA_ARGS__); \ // Register http request parameters as http original request information asynchronous reply http api
} void api_regist(const std::string &api_path, const std::function<void(API_ARGS_STRING_ASYNC)> &func);
// 检查http参数中是否附带secret密钥的宏127.0.0.1的ip不检查密钥 [AUTO-TRANSLATED:7546956c] template<typename Args, typename Key>
// Check whether the http parameters contain the secret key, the ip of 127.0.0.1 does not check the key bool checkArgs(Args &args, const Key &key) {
// 同时检测是否在ip白名单内 [AUTO-TRANSLATED:d12f963d] return !args[key].empty();
// Check whether it is in the ip whitelist at the same time }
#define CHECK_SECRET() \
do { \ template<typename Args, typename Key, typename ...KeyTypes>
auto ip = sender.get_peer_ip(); \ bool checkArgs(Args &args, const Key &key, const KeyTypes &...keys) {
if (!HttpFileManager::isIPAllowed(ip)) { \ return checkArgs(args, key) && checkArgs(args, keys...);
throw AuthException("Your ip is not allowed to access the service."); \ }
} \
CHECK_ARGS("secret"); \ // 检查http url中或body中或http header参数是否为空的宏 [AUTO-TRANSLATED:9de001a4]
if (api_secret != allArgs["secret"]) { \ // Check whether the http url, body or http header parameters are empty
throw AuthException("Incorrect secret"); \ #define CHECK_ARGS(...) \
} \ if(!checkArgs(allArgs,##__VA_ARGS__)){ \
} while(false); throw InvalidArgsException("Required parameter missed: " #__VA_ARGS__); \
}
void installWebApi();
void unInstallWebApi(); // 检查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
#if defined(ENABLE_RTPPROXY) // 同时检测是否在ip白名单内 [AUTO-TRANSLATED:d12f963d]
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); // Check whether it is in the ip whitelist at the same time
#endif #define CHECK_SECRET() \
do { \
Json::Value makeMediaSourceJson(mediakit::MediaSource &media); auto ip = sender.get_peer_ip(); \
void getStatisticJson(const std::function<void(Json::Value &val)> &cb); if (!HttpFileManager::isIPAllowed(ip)) { \
void addStreamProxy(const mediakit::MediaTuple &tuple, const std::string &url, int retry_count, throw AuthException("Your ip is not allowed to access the service."); \
const mediakit::ProtocolOption &option, int rtp_type, float timeout_sec, const toolkit::mINI &args, } \
const std::function<void(const toolkit::SockException &ex, const std::string &key)> &cb); CHECK_ARGS("secret"); \
#endif //ZLMEDIAKIT_WEBAPI_H 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<void(Json::Value &val)> &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<void(const toolkit::SockException &ex, const std::string &key)> &cb);
template <typename Type>
class ServiceController {
public:
using Pointer = std::shared_ptr<Type>;
std::unordered_map<std::string, Pointer> _map;
mutable std::recursive_mutex _mtx;
void clear() {
decltype(_map) copy;
{
std::lock_guard<std::recursive_mutex> lck(_mtx);
copy.swap(_map);
}
}
size_t erase(const std::string &key) {
Pointer erase_ptr;
{
std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> lck(_mtx);
return _map.size();
}
Pointer find(const std::string &key) const {
std::lock_guard<std::recursive_mutex> lck(_mtx);
auto it = _map.find(key);
if (it == _map.end()) {
return nullptr;
}
return it->second;
}
void for_each(const std::function<void(const std::string&, const Pointer&)>& cb) {
std::lock_guard<std::recursive_mutex> lck(_mtx);
auto it = _map.begin();
while (it != _map.end()) {
cb(it->first, it->second);
it++;
}
}
template<class ..._Args>
Pointer make(const std::string &key, _Args&& ...__args) {
// assert(!find(key));
auto server = std::make_shared<Type>(std::forward<_Args>(__args)...);
std::lock_guard<std::recursive_mutex> lck(_mtx);
auto it = _map.emplace(key, server);
assert(it.second);
return server;
}
template<class ..._Args>
Pointer makeWithAction(const std::string &key, std::function<void(Pointer)> action, _Args&& ...__args) {
// assert(!find(key));
auto server = std::make_shared<Type>(std::forward<_Args>(__args)...);
action(server);
std::lock_guard<std::recursive_mutex> lck(_mtx);
auto it = _map.emplace(key, server);
assert(it.second);
return server;
}
template<class ..._Args>
Pointer emplace(const std::string &key, _Args&& ...__args) {
// assert(!find(key));
auto server = std::static_pointer_cast<Type>(std::forward<_Args>(__args)...);
std::lock_guard<std::recursive_mutex> lck(_mtx);
auto it = _map.emplace(key, server);
assert(it.second);
return server;
}
};
#if defined(ENABLE_WEBRTC)
template <typename Args>
class WebRtcArgsImp : public mediakit::WebRtcArgs {
public:
WebRtcArgsImp(const HttpAllArgs<Args> &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> _args;
std::string _session_id;
};
#endif
#endif //ZLMEDIAKIT_WEBAPI_H

View File

@ -30,6 +30,8 @@
#if defined(ENABLE_WEBRTC) #if defined(ENABLE_WEBRTC)
#include "../webrtc/WebRtcTransport.h" #include "../webrtc/WebRtcTransport.h"
#include "../webrtc/WebRtcSession.h" #include "../webrtc/WebRtcSession.h"
#include "../webrtc/WebRtcSignalingSession.h"
#include "../webrtc/IceSession.hpp"
#endif #endif
#if defined(ENABLE_SRT) #if defined(ENABLE_SRT)
@ -368,8 +370,17 @@ int start_main(int argc,char *argv[]) {
} }
return Socket::createSocket(new_poller, false); return Socket::createSocket(new_poller, false);
}); });
auto signaleSrv = std::make_shared<TcpServer>();
auto signalsSrv = std::make_shared<TcpServer>();
auto iceTcpSrv = std::make_shared<TcpServer>();
auto iceSrv = std::make_shared<UdpServer>();
uint16_t rtcPort = mINI::Instance()[Rtc::kPort]; uint16_t rtcPort = mINI::Instance()[Rtc::kPort];
uint16_t rtcTcpPort = mINI::Instance()[Rtc::kTcpPort]; 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) #endif//defined(ENABLE_WEBRTC)
@ -435,6 +446,12 @@ int start_main(int argc,char *argv[]) {
if (rtcTcpPort) { rtcSrv_tcp->start<WebRtcSession>(rtcTcpPort, listen_ip);} if (rtcTcpPort) { rtcSrv_tcp->start<WebRtcSession>(rtcTcpPort, listen_ip);}
//webrtc 信令服务器
if (signalingPort) { signaleSrv->start<WebRtcWebcosktSignalingSession>(signalingPort);}
if (signalSslPort) { signalsSrv->start<WebRtcWebcosktSignalSslSession>(signalSslPort);}
//STUN/TURN服务
if (icePort) { iceSrv->start<IceSession>(icePort);}
if (iceTcpPort) { iceTcpSrv->start<IceSession>(iceTcpPort);}
#endif//defined(ENABLE_WEBRTC) #endif//defined(ENABLE_WEBRTC)
#if defined(ENABLE_SRT) #if defined(ENABLE_SRT)

View File

@ -30,7 +30,7 @@ namespace mediakit {
* [AUTO-TRANSLATED:f214f734] * [AUTO-TRANSLATED:f214f734]
*/ */
#if !defined(ENABLE_VERSION) #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 #else
const char kServerName[] = "ZLMediaKit(git hash:" COMMIT_HASH "/" COMMIT_TIME ",branch:" BRANCH_NAME ",build time:" BUILD_TIME ")"; const char kServerName[] = "ZLMediaKit(git hash:" COMMIT_HASH "/" COMMIT_TIME ",branch:" BRANCH_NAME ",build time:" BUILD_TIME ")";
#endif #endif

View File

@ -62,6 +62,7 @@
} }
#endif // CLEAR_ARR #endif // CLEAR_ARR
#define RTC_SCHEMA "rtc"
#define RTSP_SCHEMA "rtsp" #define RTSP_SCHEMA "rtsp"
#define RTMP_SCHEMA "rtmp" #define RTMP_SCHEMA "rtmp"
#define TS_SCHEMA "ts" #define TS_SCHEMA "ts"

View File

@ -163,7 +163,7 @@ static std::shared_ptr<char> getSharedMmap(const string &file_path, int64_t &fil
if (addr_ == nullptr) { if (addr_ == nullptr) {
mmap_close(hfile, hmapping, addr_); mmap_close(hfile, hmapping, addr_);
WarnL << "MapViewOfFile() " << file_path << " failed:"; WarnL << "MapViewOfFile() " << file_path << " failed:";
return nullptr; return nullptr;
} }

View File

@ -26,6 +26,7 @@ public:
void play(const std::string &url) override; void play(const std::string &url) override;
toolkit::EventPoller::Ptr getPoller(); toolkit::EventPoller::Ptr getPoller();
void setOnCreateSocket(toolkit::Socket::onCreateSocket cb); void setOnCreateSocket(toolkit::Socket::onCreateSocket cb);
const PlayerBase::Ptr& getDelegate() const { return _delegate; }
private: private:
toolkit::EventPoller::Ptr _poller; toolkit::EventPoller::Ptr _poller;

View File

@ -18,7 +18,9 @@
#ifdef ENABLE_SRT #ifdef ENABLE_SRT
#include "Srt/SrtPlayerImp.h" #include "Srt/SrtPlayerImp.h"
#endif // ENABLE_SRT #endif // ENABLE_SRT
#ifdef ENABLE_WEBRTC
#include "../webrtc/WebRtcProxyPlayerImp.h"
#endif // ENABLE_WEBRTC
using namespace std; using namespace std;
using namespace toolkit; 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); return PlayerBase::Ptr(new SrtPlayerImp(poller), release_func);
} }
#endif//ENABLE_SRT #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); throw std::invalid_argument("not supported play schema:" + url_in);
} }

View File

@ -286,6 +286,10 @@ float PlayerProxy::getLossRate(MediaSource &sender, TrackType type) {
return getPacketLossRate(type); return getPacketLossRate(type);
} }
toolkit::EventPoller::Ptr PlayerProxy::getOwnerPoller(MediaSource &sender) {
return getPoller();
}
TranslationInfo PlayerProxy::getTranslationInfo() { TranslationInfo PlayerProxy::getTranslationInfo() {
return _transtalion_info; return _transtalion_info;
} }

View File

@ -151,6 +151,7 @@ private:
std::string getOriginUrl(MediaSource &sender) const override; std::string getOriginUrl(MediaSource &sender) const override;
std::shared_ptr<toolkit::SockInfo> getOriginSock(MediaSource &sender) const override; std::shared_ptr<toolkit::SockInfo> getOriginSock(MediaSource &sender) const override;
float getLossRate(MediaSource &sender, TrackType type) override; float getLossRate(MediaSource &sender, TrackType type) override;
toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override;
void rePlay(const std::string &strUrl, int iFailedCnt); void rePlay(const std::string &strUrl, int iFailedCnt);
void onPlaySuccess(); void onPlaySuccess();

View File

@ -15,6 +15,9 @@
#ifdef ENABLE_SRT #ifdef ENABLE_SRT
#include "Srt/SrtPusher.h" #include "Srt/SrtPusher.h"
#endif // ENABLE_SRT #endif // ENABLE_SRT
#ifdef ENABLE_WEBRTC
#include "../webrtc/WebRtcProxyPusher.h"
#endif // ENABLE_WEBRTC
using namespace toolkit; using namespace toolkit;
@ -23,7 +26,8 @@ namespace mediakit {
static bool checkMediaSourceAndUrlMatch(const MediaSource::Ptr &src, const std::string &url) { static bool checkMediaSourceAndUrlMatch(const MediaSource::Ptr &src, const std::string &url) {
std::string prefix = findSubString(url.data(), NULL, "://"); 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<RtspMediaSource>(src); auto rtsp_src = std::dynamic_pointer_cast<RtspMediaSource>(src);
if (!rtsp_src) { if (!rtsp_src) {
return false; return false;
@ -91,6 +95,11 @@ PusherBase::Ptr PusherBase::createPusher(const EventPoller::Ptr &in_poller,
} }
#endif//ENABLE_SRT #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<RtspMediaSource>(src)), release_func);
}
#endif//ENABLE_WEBRTC
throw std::invalid_argument("not supported push schema:" + url); throw std::invalid_argument("not supported push schema:" + url);
} }

View File

@ -69,7 +69,7 @@ public:
virtual size_t getSendSpeed() { return 0; } virtual size_t getSendSpeed() { return 0; }
virtual size_t getSendTotalBytes() { return 0; } virtual size_t getSendTotalBytes() { return 0; }
protected: protected:
virtual void onShutdown(const toolkit::SockException &ex) = 0; virtual void onShutdown(const toolkit::SockException &ex) = 0;
virtual void onPublishResult(const toolkit::SockException &ex) = 0; virtual void onPublishResult(const toolkit::SockException &ex) = 0;
@ -139,11 +139,11 @@ public:
size_t getSendSpeed() override { size_t getSendSpeed() override {
return _delegate ? _delegate->getSendSpeed() : Parent::getSendSpeed(); return _delegate ? _delegate->getSendSpeed() : Parent::getSendSpeed();
} }
size_t getSendTotalBytes() override { size_t getSendTotalBytes() override {
return _delegate ? _delegate->getSendTotalBytes() : Parent::getSendTotalBytes(); return _delegate ? _delegate->getSendTotalBytes() : Parent::getSendTotalBytes();
} }
protected: protected:
void onShutdown(const toolkit::SockException &ex) override { void onShutdown(const toolkit::SockException &ex) override {
if (_on_shutdown) { if (_on_shutdown) {

View File

@ -17,7 +17,7 @@ using namespace std;
namespace mediakit { namespace mediakit {
HlsMaker::HlsMaker(bool is_fmp4, float seg_duration, uint32_t seg_number, bool seg_keep) { HlsMaker::HlsMaker(bool is_fmp4, float seg_duration, uint32_t seg_number, bool seg_keep) {
_is_fmp4 = is_fmp4; _is_fmp4 = is_fmp4;
// 最小允许设置为00个切片代表点播 [AUTO-TRANSLATED:19235e8e] // 最小允许设置为00个切片代表点播 [AUTO-TRANSLATED:19235e8e]
// Minimum allowed setting is 0, 0 slices represent on-demand // Minimum allowed setting is 0, 0 slices represent on-demand
_seg_number = seg_number; _seg_number = seg_number;

View File

@ -27,6 +27,11 @@ struct MediaTuple {
std::string shortUrl() const { std::string shortUrl() const {
return vhost + '/' + app + '/' + stream; 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 { class RecordInfo: public MediaTuple {

View File

@ -29,7 +29,7 @@ public:
size_t getSendSpeed() override; size_t getSendSpeed() override;
size_t getSendTotalBytes() override; size_t getSendTotalBytes() override;
protected: protected:
//for Tcpclient override //for Tcpclient override
void onRecv(const toolkit::Buffer::Ptr &buf) override; void onRecv(const toolkit::Buffer::Ptr &buf) override;

View File

@ -1012,9 +1012,9 @@ float SrtCaller::getTimeOutSec() {
GET_CONFIG(uint32_t, timeout, SRT::kTimeOutSec); GET_CONFIG(uint32_t, timeout, SRT::kTimeOutSec);
if (timeout <= 0) { if (timeout <= 0) {
WarnL << "config srt " << kTimeOutSec << " not vaild"; WarnL << "config srt " << kTimeOutSec << " not vaild";
return 5 * 1000; return 5.0f;
} }
return (float)timeout * (float)1000; return (float)timeout;
}; };
std::string SrtCaller::generateStreamId() { std::string SrtCaller::generateStreamId() {

View File

@ -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) { 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) #if defined(ENABLE_OPENSSL)
EVP_CIPHER_CTX* ctx = NULL; EVP_CIPHER_CTX* ctx = NULL;
*outLen = 0; *outLen = 0;
do { do {
if (!(ctx = EVP_CIPHER_CTX_new())) { if (!(ctx = EVP_CIPHER_CTX_new())) {
WarnL << "EVP_CIPHER_CTX_new fail"; WarnL << "EVP_CIPHER_CTX_new fail";
break; 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)) { if (1 != EVP_EncryptInit_ex(ctx, aes_key_len_mapping_wrap_cipher(key_len), NULL, key, NULL)) {
WarnL << "EVP_EncryptInit_ex fail"; WarnL << "EVP_EncryptInit_ex fail";
break; break;
} }
int len1 = 0; int len1 = 0;
if (1 != EVP_EncryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) { if (1 != EVP_EncryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) {
WarnL << "EVP_EncryptUpdate fail"; WarnL << "EVP_EncryptUpdate fail";
break; break;
} }
int len2 = 0; int len2 = 0;
if (1 != EVP_EncryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) { if (1 != EVP_EncryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) {
WarnL << "EVP_EncryptFinal_ex fail"; WarnL << "EVP_EncryptFinal_ex fail";
break; break;
} }
*outLen = len1 + len2; *outLen = len1 + len2;
} while (0); } while (0);
if (ctx != NULL) { if (ctx != NULL) {
EVP_CIPHER_CTX_free(ctx); EVP_CIPHER_CTX_free(ctx);
} }
return *outLen != 0; return *outLen != 0;
#else #else
return false; return false;
#endif #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) { 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) #if defined(ENABLE_OPENSSL)
EVP_CIPHER_CTX* ctx = NULL; EVP_CIPHER_CTX* ctx = NULL;
*outLen = 0; *outLen = 0;
do { do {
if (!(ctx = EVP_CIPHER_CTX_new())) { if (!(ctx = EVP_CIPHER_CTX_new())) {
WarnL << "EVP_CIPHER_CTX_new fail"; 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)) { if (1 != EVP_DecryptInit_ex(ctx, aes_key_len_mapping_wrap_cipher(key_len), NULL, key, NULL)) {
WarnL << "EVP_DecryptInit_ex fail"; WarnL << "EVP_DecryptInit_ex fail";
break; break;
} }
//设置pkcs7padding //设置pkcs7padding
if (1 != EVP_CIPHER_CTX_set_padding(ctx, 1)) { 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; break;
} }
int len1 = 0; int len1 = 0;
if (1 != EVP_DecryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) { if (1 != EVP_DecryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) {
WarnL << "EVP_DecryptUpdate fail"; WarnL << "EVP_DecryptUpdate fail";
break; break;
} }
int len2 = 0; int len2 = 0;
if (1 != EVP_DecryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) { if (1 != EVP_DecryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) {
WarnL << "EVP_DecryptFinal_ex fail"; WarnL << "EVP_DecryptFinal_ex fail";
break; break;
} }
*outLen = len1 + len2; *outLen = len1 + len2;
} while (0); } while (0);
if (ctx != NULL) { if (ctx != NULL) {
EVP_CIPHER_CTX_free(ctx); EVP_CIPHER_CTX_free(ctx);
} }
return *outLen != 0; return *outLen != 0;
#else #else
return false; 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) { 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) #if defined(ENABLE_OPENSSL)
EVP_CIPHER_CTX* ctx = NULL; EVP_CIPHER_CTX* ctx = NULL;
*outLen = 0; *outLen = 0;
do { do {
if (!(ctx = EVP_CIPHER_CTX_new())) { if (!(ctx = EVP_CIPHER_CTX_new())) {
WarnL << "EVP_CIPHER_CTX_new fail"; WarnL << "EVP_CIPHER_CTX_new fail";
break; 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)) { if (1 != EVP_EncryptInit_ex(ctx, aes_key_len_mapping_ctr_cipher(key_len), NULL, key, iv)) {
WarnL << "EVP_EncryptInit_ex fail"; WarnL << "EVP_EncryptInit_ex fail";
break; break;
} }
int len1 = 0; int len1 = 0;
if (1 != EVP_EncryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) { if (1 != EVP_EncryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) {
WarnL << "EVP_EncryptUpdate fail"; WarnL << "EVP_EncryptUpdate fail";
break; break;
} }
int len2 = 0; int len2 = 0;
if (1 != EVP_EncryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) { if (1 != EVP_EncryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) {
WarnL << "EVP_EncryptFinal_ex fail"; WarnL << "EVP_EncryptFinal_ex fail";
break; break;
} }
*outLen = len1 + len2; *outLen = len1 + len2;
} while (0); } while (0);
if (ctx != NULL) { if (ctx != NULL) {
EVP_CIPHER_CTX_free(ctx); EVP_CIPHER_CTX_free(ctx);
} }
return *outLen != 0; return *outLen != 0;
#else #else
return false; return false;
#endif #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) { 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) #if defined(ENABLE_OPENSSL)
EVP_CIPHER_CTX* ctx = NULL; EVP_CIPHER_CTX* ctx = NULL;
*outLen = 0; *outLen = 0;
do { do {
if (!(ctx = EVP_CIPHER_CTX_new())) { if (!(ctx = EVP_CIPHER_CTX_new())) {
WarnL << "EVP_CIPHER_CTX_new fail"; WarnL << "EVP_CIPHER_CTX_new fail";
break; 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"; WarnL << "EVP_DecryptInit_ex fail";
break; break;
} }
int len1 = 0; int len1 = 0;
if (1 != EVP_DecryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) { if (1 != EVP_DecryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) {
WarnL << "EVP_DecryptUpdate fail"; WarnL << "EVP_DecryptUpdate fail";
break; break;
} }
int len2 = 0; int len2 = 0;
if (1 != EVP_DecryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) { if (1 != EVP_DecryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) {
WarnL << "EVP_DecryptFinal_ex fail"; WarnL << "EVP_DecryptFinal_ex fail";
break; break;
} }
*outLen = len1 + len2; *outLen = len1 + len2;
} while (0); } while (0);
if (ctx != NULL) { if (ctx != NULL) {
EVP_CIPHER_CTX_free(ctx); EVP_CIPHER_CTX_free(ctx);
} }
return *outLen != 0; return *outLen != 0;
#else #else
return false; return false;

View File

@ -353,7 +353,7 @@ bool HandshakePacket::loadExtMessage(uint8_t *buf, size_t len) {
case HSExt::SRT_CMD_SID: ext = std::make_shared<HSExtStreamID>(); break; case HSExt::SRT_CMD_SID: ext = std::make_shared<HSExtStreamID>(); break;
case HSExt::SRT_CMD_KMREQ: case HSExt::SRT_CMD_KMREQ:
case HSExt::SRT_CMD_KMRSP: case HSExt::SRT_CMD_KMRSP:
ext = std::make_shared<HSExtKeyMaterial>(); break; ext = std::make_shared<HSExtKeyMaterial>(); break;
default: WarnL << "not support ext " << type; break; default: WarnL << "not support ext " << type; break;
} }
if (ext) { if (ext) {

View File

@ -1,29 +1,29 @@
#ifndef ZLMEDIAKIT_SRT_SESSION_H #ifndef ZLMEDIAKIT_SRT_SESSION_H
#define ZLMEDIAKIT_SRT_SESSION_H #define ZLMEDIAKIT_SRT_SESSION_H
#include "Network/Session.h" #include "Network/Session.h"
#include "SrtTransport.hpp" #include "SrtTransport.hpp"
namespace SRT { namespace SRT {
using namespace toolkit; using namespace toolkit;
class SrtSession : public Session { class SrtSession : public Session {
public: public:
SrtSession(const Socket::Ptr &sock); SrtSession(const Socket::Ptr &sock);
void onRecv(const Buffer::Ptr &) override; void onRecv(const Buffer::Ptr &) override;
void onError(const SockException &err) override; void onError(const SockException &err) override;
void onManager() override; void onManager() override;
void attachServer(const toolkit::Server &server) override; void attachServer(const toolkit::Server &server) override;
static EventPoller::Ptr queryPoller(const Buffer::Ptr &buffer); static EventPoller::Ptr queryPoller(const Buffer::Ptr &buffer);
private: private:
bool _find_transport = true; bool _find_transport = true;
Ticker _ticker; Ticker _ticker;
struct sockaddr_storage _peer_addr; struct sockaddr_storage _peer_addr;
SrtTransport::Ptr _transport; SrtTransport::Ptr _transport;
}; };
} // namespace SRT } // namespace SRT
#endif // ZLMEDIAKIT_SRT_SESSION_H #endif // ZLMEDIAKIT_SRT_SESSION_H

View File

@ -400,7 +400,7 @@ void SrtTransport::sendMsgDropReq(uint32_t first, uint32_t last) {
} }
void SrtTransport::tryAnnounceKeyMaterial() { void SrtTransport::tryAnnounceKeyMaterial() {
//TraceL; //TraceL;
if (!_crypto) { if (!_crypto) {
return; return;

View File

@ -169,8 +169,8 @@ private:
// for encryption // for encryption
Crypto::Ptr _crypto; Crypto::Ptr _crypto;
Timer::Ptr _announce_timer; Timer::Ptr _announce_timer;
KeyMaterialPacket::Ptr _announce_req; KeyMaterialPacket::Ptr _announce_req;
}; };
class SrtTransportManager { class SrtTransportManager {

File diff suppressed because it is too large Load Diff

View File

@ -1,254 +1,254 @@
/** /**
ISC License ISC License
Copyright © 2015, Iñaki Baz Castillo <ibc@aliax.net> Copyright © 2015, Iñaki Baz Castillo <ibc@aliax.net>
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies. copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/ */
#ifndef MS_RTC_DTLS_TRANSPORT_HPP #ifndef MS_RTC_DTLS_TRANSPORT_HPP
#define MS_RTC_DTLS_TRANSPORT_HPP #define MS_RTC_DTLS_TRANSPORT_HPP
#include "SrtpSession.hpp" #include "SrtpSession.hpp"
#include <openssl/bio.h> #include <openssl/bio.h>
#include <openssl/ssl.h> #include <openssl/ssl.h>
#include <openssl/x509.h> #include <openssl/x509.h>
#include <map> #include <map>
#include <string> #include <string>
#include <vector> #include <vector>
#include "Poller/Timer.h" #include "Poller/Timer.h"
#include "Poller/EventPoller.h" #include "Poller/EventPoller.h"
using namespace toolkit;
namespace RTC
namespace RTC {
{ class DtlsTransport : public std::enable_shared_from_this<DtlsTransport>
class DtlsTransport : public std::enable_shared_from_this<DtlsTransport> {
{ public:
public: using Ptr = std::shared_ptr<DtlsTransport>;
enum class DtlsState enum class DtlsState
{ {
NEW = 1, NEW = 1,
CONNECTING, CONNECTING,
CONNECTED, CONNECTED,
FAILED, FAILED,
CLOSED CLOSED
}; };
public: public:
enum class Role enum class Role
{ {
NONE = 0, NONE = 0,
AUTO = 1, AUTO = 1,
CLIENT, CLIENT,
SERVER SERVER
}; };
public: public:
enum class FingerprintAlgorithm enum class FingerprintAlgorithm
{ {
NONE = 0, NONE = 0,
SHA1 = 1, SHA1 = 1,
SHA224, SHA224,
SHA256, SHA256,
SHA384, SHA384,
SHA512 SHA512
}; };
public: public:
struct Fingerprint struct Fingerprint
{ {
FingerprintAlgorithm algorithm{ FingerprintAlgorithm::NONE }; FingerprintAlgorithm algorithm{ FingerprintAlgorithm::NONE };
std::string value; std::string value;
}; };
private: private:
struct SrtpCryptoSuiteMapEntry struct SrtpCryptoSuiteMapEntry
{ {
RTC::SrtpSession::CryptoSuite cryptoSuite; RTC::SrtpSession::CryptoSuite cryptoSuite;
const char* name; const char* name;
}; };
class DtlsEnvironment : public std::enable_shared_from_this<DtlsEnvironment> class DtlsEnvironment : public std::enable_shared_from_this<DtlsEnvironment>
{ {
public: public:
using Ptr = std::shared_ptr<DtlsEnvironment>; using Ptr = std::shared_ptr<DtlsEnvironment>;
~DtlsEnvironment(); ~DtlsEnvironment();
static DtlsEnvironment& Instance(); static DtlsEnvironment& Instance();
private: private:
DtlsEnvironment(); DtlsEnvironment();
void GenerateCertificateAndPrivateKey(); void GenerateCertificateAndPrivateKey();
bool ReadCertificateAndPrivateKeyFromContext(SSL_CTX *ctx); bool ReadCertificateAndPrivateKeyFromContext(SSL_CTX *ctx);
void CreateSslCtx(); void CreateSslCtx();
void GenerateFingerprints(); void GenerateFingerprints();
public: public:
X509* certificate{ nullptr }; X509* certificate{ nullptr };
EVP_PKEY* privateKey{ nullptr }; EVP_PKEY* privateKey{ nullptr };
SSL_CTX* sslCtx{ nullptr }; SSL_CTX* sslCtx{ nullptr };
std::vector<Fingerprint> localFingerprints; std::vector<Fingerprint> localFingerprints;
}; };
public: public:
class Listener class Listener
{ {
public: public:
// DTLS is in the process of negotiating a secure connection. Incoming // DTLS is in the process of negotiating a secure connection. Incoming
// media can flow through. // media can flow through.
// NOTE: The caller MUST NOT call any method during this callback. // NOTE: The caller MUST NOT call any method during this callback.
virtual void OnDtlsTransportConnecting(const RTC::DtlsTransport* dtlsTransport) = 0; virtual void OnDtlsTransportConnecting(const RTC::DtlsTransport* dtlsTransport) = 0;
// DTLS has completed negotiation of a secure connection (including DTLS-SRTP // DTLS has completed negotiation of a secure connection (including DTLS-SRTP
// and remote fingerprint verification). Outgoing media can now flow through. // and remote fingerprint verification). Outgoing media can now flow through.
// NOTE: The caller MUST NOT call any method during this callback. // NOTE: The caller MUST NOT call any method during this callback.
virtual void OnDtlsTransportConnected( virtual void OnDtlsTransportConnected(
const RTC::DtlsTransport* dtlsTransport, const RTC::DtlsTransport* dtlsTransport,
RTC::SrtpSession::CryptoSuite srtpCryptoSuite, RTC::SrtpSession::CryptoSuite srtpCryptoSuite,
uint8_t* srtpLocalKey, uint8_t* srtpLocalKey,
size_t srtpLocalKeyLen, size_t srtpLocalKeyLen,
uint8_t* srtpRemoteKey, uint8_t* srtpRemoteKey,
size_t srtpRemoteKeyLen, size_t srtpRemoteKeyLen,
std::string& remoteCert) = 0; std::string& remoteCert) = 0;
// The DTLS connection has been closed as the result of an error (such as a // 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). // DTLS alert or a failure to validate the remote fingerprint).
virtual void OnDtlsTransportFailed(const RTC::DtlsTransport* dtlsTransport) = 0; virtual void OnDtlsTransportFailed(const RTC::DtlsTransport* dtlsTransport) = 0;
// The DTLS connection has been closed due to receipt of a close_notify alert. // The DTLS connection has been closed due to receipt of a close_notify alert.
virtual void OnDtlsTransportClosed(const RTC::DtlsTransport* dtlsTransport) = 0; virtual void OnDtlsTransportClosed(const RTC::DtlsTransport* dtlsTransport) = 0;
// Need to send DTLS data to the peer. // Need to send DTLS data to the peer.
virtual void OnDtlsTransportSendData( virtual void OnDtlsTransportSendData(
const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) = 0; const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) = 0;
// DTLS application data received. // DTLS application data received.
virtual void OnDtlsTransportApplicationDataReceived( virtual void OnDtlsTransportApplicationDataReceived(
const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) = 0; const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) = 0;
}; };
public: public:
static Role StringToRole(const std::string& role) static Role StringToRole(const std::string& role)
{ {
auto it = DtlsTransport::string2Role.find(role); auto it = DtlsTransport::string2Role.find(role);
if (it != DtlsTransport::string2Role.end()) if (it != DtlsTransport::string2Role.end())
return it->second; return it->second;
else else
return DtlsTransport::Role::NONE; return DtlsTransport::Role::NONE;
} }
static FingerprintAlgorithm GetFingerprintAlgorithm(const std::string& fingerprint) static FingerprintAlgorithm GetFingerprintAlgorithm(const std::string& fingerprint)
{ {
auto it = DtlsTransport::string2FingerprintAlgorithm.find(fingerprint); auto it = DtlsTransport::string2FingerprintAlgorithm.find(fingerprint);
if (it != DtlsTransport::string2FingerprintAlgorithm.end()) if (it != DtlsTransport::string2FingerprintAlgorithm.end())
return it->second; return it->second;
else else
return DtlsTransport::FingerprintAlgorithm::NONE; return DtlsTransport::FingerprintAlgorithm::NONE;
} }
static std::string& GetFingerprintAlgorithmString(FingerprintAlgorithm fingerprint) static std::string& GetFingerprintAlgorithmString(FingerprintAlgorithm fingerprint)
{ {
auto it = DtlsTransport::fingerprintAlgorithm2String.find(fingerprint); auto it = DtlsTransport::fingerprintAlgorithm2String.find(fingerprint);
return it->second; return it->second;
} }
static bool IsDtls(const uint8_t* data, size_t len) static bool IsDtls(const uint8_t* data, size_t len)
{ {
// clang-format off // clang-format off
return ( return (
// Minimum DTLS record length is 13 bytes. // Minimum DTLS record length is 13 bytes.
(len >= 13) && (len >= 13) &&
// DOC: https://tools.ietf.org/html/draft-ietf-avtcore-rfc5764-mux-fixes // DOC: https://tools.ietf.org/html/draft-ietf-avtcore-rfc5764-mux-fixes
(data[0] > 19 && data[0] < 64) (data[0] > 19 && data[0] < 64)
); );
// clang-format on // clang-format on
} }
private: private:
static std::map<std::string, Role> string2Role; static std::map<std::string, Role> string2Role;
static std::map<std::string, FingerprintAlgorithm> string2FingerprintAlgorithm; static std::map<std::string, FingerprintAlgorithm> string2FingerprintAlgorithm;
static std::map<FingerprintAlgorithm, std::string> fingerprintAlgorithm2String; static std::map<FingerprintAlgorithm, std::string> fingerprintAlgorithm2String;
static std::vector<SrtpCryptoSuiteMapEntry> srtpCryptoSuites; static std::vector<SrtpCryptoSuiteMapEntry> srtpCryptoSuites;
public: public:
DtlsTransport(EventPoller::Ptr poller, Listener* listener); DtlsTransport(toolkit::EventPoller::Ptr poller, Listener* listener);
~DtlsTransport(); ~DtlsTransport();
public: public:
void Dump() const; void Dump() const;
void Run(Role localRole); void Run(Role localRole);
std::vector<Fingerprint>& GetLocalFingerprints() const std::vector<Fingerprint>& GetLocalFingerprints() const
{ {
return env->localFingerprints; return env->localFingerprints;
} }
bool SetRemoteFingerprint(Fingerprint fingerprint); bool SetRemoteFingerprint(Fingerprint fingerprint);
void ProcessDtlsData(const uint8_t* data, size_t len); void ProcessDtlsData(const uint8_t* data, size_t len);
DtlsState GetState() const DtlsState GetState() const
{ {
return this->state; return this->state;
} }
Role GetLocalRole() const Role GetLocalRole() const
{ {
return this->localRole; return this->localRole;
} }
void SendApplicationData(const uint8_t* data, size_t len); void SendApplicationData(const uint8_t* data, size_t len);
private: private:
bool IsRunning() const bool IsRunning() const
{ {
switch (this->state) switch (this->state)
{ {
case DtlsState::NEW: case DtlsState::NEW:
return false; return false;
case DtlsState::CONNECTING: case DtlsState::CONNECTING:
case DtlsState::CONNECTED: case DtlsState::CONNECTED:
return true; return true;
case DtlsState::FAILED: case DtlsState::FAILED:
case DtlsState::CLOSED: case DtlsState::CLOSED:
return false; return false;
} }
// Make GCC 4.9 happy. // Make GCC 4.9 happy.
return false; return false;
} }
void Reset(); void Reset();
bool CheckStatus(int returnCode); bool CheckStatus(int returnCode);
void SendPendingOutgoingDtlsData(); void SendPendingOutgoingDtlsData();
bool SetTimeout(); bool SetTimeout();
bool ProcessHandshake(); bool ProcessHandshake();
bool CheckRemoteFingerprint(); bool CheckRemoteFingerprint();
void ExtractSrtpKeys(RTC::SrtpSession::CryptoSuite srtpCryptoSuite); void ExtractSrtpKeys(RTC::SrtpSession::CryptoSuite srtpCryptoSuite);
RTC::SrtpSession::CryptoSuite GetNegotiatedSrtpCryptoSuite(); RTC::SrtpSession::CryptoSuite GetNegotiatedSrtpCryptoSuite();
private: private:
void OnSslInfo(int where, int ret); void OnSslInfo(int where, int ret);
void OnTimer(); void OnTimer();
private: private:
DtlsEnvironment::Ptr env; DtlsEnvironment::Ptr env;
EventPoller::Ptr poller; toolkit::EventPoller::Ptr poller;
// Passed by argument. // Passed by argument.
Listener* listener{ nullptr }; Listener* listener{ nullptr };
// Allocated by this. // Allocated by this.
SSL* ssl{ nullptr }; SSL* ssl{ nullptr };
BIO* sslBioFromNetwork{ nullptr }; // The BIO from which ssl reads. BIO* sslBioFromNetwork{ nullptr }; // The BIO from which ssl reads.
BIO* sslBioToNetwork{ nullptr }; // The BIO in which ssl writes. BIO* sslBioToNetwork{ nullptr }; // The BIO in which ssl writes.
Timer::Ptr timer; toolkit::Timer::Ptr timer;
// Others. // Others.
DtlsState state{ DtlsState::NEW }; DtlsState state{ DtlsState::NEW };
Role localRole{ Role::NONE }; Role localRole{ Role::NONE };
Fingerprint remoteFingerprint; Fingerprint remoteFingerprint;
bool handshakeDone{ false }; bool handshakeDone{ false };
bool handshakeDoneNow{ false }; bool handshakeDoneNow{ false };
std::string remoteCert; std::string remoteCert;
//最大不超过mtu //最大不超过mtu
static constexpr int SslReadBufferSize{ 2000 }; static constexpr int SslReadBufferSize{ 2000 };
uint8_t sslReadBuffer[SslReadBufferSize]; uint8_t sslReadBuffer[SslReadBufferSize];
}; };
} // namespace RTC } // namespace RTC
#endif #endif

View File

@ -1,528 +0,0 @@
/**
ISC License
Copyright © 2015, Iñaki Baz Castillo <ibc@aliax.net>
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 <utility>
#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<unsigned int>(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<unsigned int>(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<uint32_t>(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<RTC::TransportTuple>(storedTuple->shared_from_this());
// Notify the listener.
this->listener->OnIceServerSelectedTuple(this, this->selectedTuple);
}
} // namespace RTC

View File

@ -1,138 +0,0 @@
/**
ISC License
Copyright © 2015, Iñaki Baz Castillo <ibc@aliax.net>
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 <list>
#include <string>
#include <functional>
#include <memory>
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<RTC::TransportTuple *>& 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<RTC::TransportTuple *> tuples;
RTC::TransportTuple *selectedTuple { nullptr };
std::weak_ptr<RTC::TransportTuple> lastSelectedTuple;
//最大不超过mtu
static constexpr size_t StunSerializeBufferSize{ 1600 };
uint8_t StunSerializeBuffer[StunSerializeBufferSize];
};
} // namespace RTC
#endif

139
webrtc/IceSession.cpp Normal file
View File

@ -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<IceServer>(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<IceTransport::Pair>(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<std::mutex> lck(_mtx);
_map[key] = ptr;
}
IceSession::Ptr IceSessionManager::getItem(const std::string& key) {
assert(!key.empty());
std::lock_guard<std::mutex> 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<std::mutex> lck(_mtx);
_map.erase(key);
}
}// namespace mediakit

70
webrtc/IceSession.hpp Normal file
View File

@ -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<IceSession>;
using WeakPtr = std::weak_ptr<IceSession>;
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<std::string, std::weak_ptr<IceSession>> _map;
};
}// namespace mediakit
#endif //ZLMEDIAKIT_WEBRTC_ICE_SESSION_H

2026
webrtc/IceTransport.cpp Normal file

File diff suppressed because it is too large Load Diff

754
webrtc/IceTransport.hpp Normal file
View File

@ -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 <map>
#include <list>
#include <string>
#include <memory>
#include <algorithm>
#include <functional>
#include <unordered_map>
#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>;
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<std::string>()(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>;
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>;
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<IceTransport> {
public:
using Ptr = std::shared_ptr<IceTransport>;
class Pair {
public:
using Ptr = std::shared_ptr<Pair>;
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<sockaddr_storage> 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<sockaddr_storage>();
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<const struct sockaddr *>(a->_relayed_addr.get()), reinterpret_cast<const struct sockaddr *>(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<sockaddr_storage> _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<void(const StunPacket::Ptr&, const Pair::Ptr&)>;
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<std::string /*transcation ID*/, RequestInfo> _response_handlers;
std::unordered_map<std::pair<StunPacket::Class, StunPacket::Method>, MsgHandler, StunPacket::ClassMethodHash> _request_handlers;
// for local
std::string _ufrag;
std::string _password;
// For permissions
std::unordered_map<sockaddr_storage /*peer ip:port*/, uint64_t /* create or fresh time*/,
toolkit::SockUtil::SockAddrHash, toolkit::SockUtil::SockAddrEqual> _permissions;
// For Channel Bind
std::unordered_map<uint16_t /*channel number*/, sockaddr_storage /*peer ip:port*/> _channel_bindings;
std::unordered_map<uint16_t /*channel number*/, uint64_t /*bind or fresh time*/> _channel_binding_times;
// For STUN request retry
std::shared_ptr<toolkit::Timer> _retry_timer;
};
class IceServer : public IceTransport {
public:
using Ptr = std::shared_ptr<IceServer>;
using WeakPtr = std::weak_ptr<IceServer>;
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<toolkit::BufferLikeString> _nonce_list;
std::unordered_map<sockaddr_storage /*peer ip:port*/, std::pair<std::shared_ptr<uint16_t> /* 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<IceAgent>;
// 候选者对信息结构
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<Pair::Ptr> 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<toolkit::Timer> _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<Pair> _last_selected_pair;
// 双向索引的候选地址管理结构
struct SocketCandidateManager {
// socket -> candidates 的一对多映射
std::unordered_map<toolkit::SocketHelper::Ptr, std::vector<CandidateInfo>> socket_to_candidates;
// candidate -> socket 的映射(用于快速查找)
std::unordered_map<CandidateInfo, toolkit::SocketHelper::Ptr, CandidateTuple::ClassHash, CandidateTuple::ClassEqual> candidate_to_socket;
// 按类型分组的socket列表方便遍历
std::vector<toolkit::SocketHelper::Ptr> _host_sockets; // HOST类型socket
std::vector<toolkit::SocketHelper::Ptr> _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<CandidateInfo> getCandidates(const toolkit::SocketHelper::Ptr& socket) const {
auto it = socket_to_candidates.find(socket);
return (it != socket_to_candidates.end()) ? it->second : std::vector<CandidateInfo>();
}
// 获取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<toolkit::SocketHelper::Ptr> getAllSockets() const {
std::vector<toolkit::SocketHelper::Ptr> 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<CandidateInfo> getAllCandidates() const {
std::vector<CandidateInfo> 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<toolkit::SocketHelper::Ptr>& getHostSockets() const {
return _host_sockets;
}
// 获取relay sockets
const std::vector<toolkit::SocketHelper::Ptr>& 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<CandidateInfo, CandidateTuple::ClassHash, CandidateTuple::ClassEqual>;
CandidateSet _remote_candidates;
//TODO:当前仅支持多数据流复用一个checklist
std::vector<std::shared_ptr<CandidatePair>> _check_list;
std::vector<std::shared_ptr<CandidatePair>> _valid_list;
std::shared_ptr<CandidatePair> _select_candidate_pair;
};
} // namespace RTC
#endif //ZLMEDIAKIT_WEBRTC_ICE_TRANSPORT_HPP

View File

@ -1,90 +1,90 @@
/* /*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
* *
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). * 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 * 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 * 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. * may be found in the AUTHORS file in the root of the source tree.
*/ */
#ifndef ZLMEDIAKIT_NACK_H #ifndef ZLMEDIAKIT_NACK_H
#define ZLMEDIAKIT_NACK_H #define ZLMEDIAKIT_NACK_H
#include <set> #include <set>
#include <map> #include <map>
#include <deque> #include <deque>
#include <unordered_map> #include <unordered_map>
#include "Rtsp/Rtsp.h" #include "Rtsp/Rtsp.h"
#include "Rtcp/RtcpFCI.h" #include "Rtcp/RtcpFCI.h"
namespace mediakit { namespace mediakit {
// RTC配置项目 [AUTO-TRANSLATED:19940011] // RTC配置项目 [AUTO-TRANSLATED:19940011]
// RTC configuration project // RTC configuration project
namespace Rtc { namespace Rtc {
// ~ nack发送端rtp接收端 [AUTO-TRANSLATED:bb169205] // ~ nack发送端rtp接收端 [AUTO-TRANSLATED:bb169205]
// ~ nack sender, rtp receiver // ~ nack sender, rtp receiver
// 最大保留的rtp丢包状态个数 [AUTO-TRANSLATED:70eee442] // 最大保留的rtp丢包状态个数 [AUTO-TRANSLATED:70eee442]
// Maximum number of retained rtp packet loss states // Maximum number of retained rtp packet loss states
extern const std::string kNackMaxSize; extern const std::string kNackMaxSize;
// rtp丢包状态最长保留时间 [AUTO-TRANSLATED:f9306375] // rtp丢包状态最长保留时间 [AUTO-TRANSLATED:f9306375]
// Maximum retention time for rtp packet loss states // Maximum retention time for rtp packet loss states
extern const std::string kNackMaxMS; extern const std::string kNackMaxMS;
} // namespace Rtc } // namespace Rtc
class NackList { class NackList {
public: public:
void pushBack(RtpPacket::Ptr rtp); void pushBack(RtpPacket::Ptr rtp);
void forEach(const FCI_NACK &nack, const std::function<void(const RtpPacket::Ptr &rtp)> &cb); void forEach(const FCI_NACK &nack, const std::function<void(const RtpPacket::Ptr &rtp)> &cb);
private: private:
void popFront(); void popFront();
uint32_t getCacheMS(); uint32_t getCacheMS();
int64_t getNtpStamp(uint16_t seq); int64_t getNtpStamp(uint16_t seq);
RtpPacket::Ptr *getRtp(uint16_t seq); RtpPacket::Ptr *getRtp(uint16_t seq);
private: private:
uint32_t _cache_ms_check = 0; uint32_t _cache_ms_check = 0;
std::deque<uint16_t> _nack_cache_seq; std::deque<uint16_t> _nack_cache_seq;
std::unordered_map<uint16_t, RtpPacket::Ptr> _nack_cache_pkt; std::unordered_map<uint16_t, RtpPacket::Ptr> _nack_cache_pkt;
}; };
class NackContext { class NackContext {
public: public:
using Ptr = std::shared_ptr<NackContext>; using Ptr = std::shared_ptr<NackContext>;
using onNack = std::function<void(const FCI_NACK &nack)>; using onNack = std::function<void(const FCI_NACK &nack)>;
NackContext(); NackContext();
void received(uint16_t seq, bool is_rtx = false); void received(uint16_t seq, bool is_rtx = false);
void setOnNack(onNack cb); void setOnNack(onNack cb);
uint64_t reSendNack(); uint64_t reSendNack();
private: private:
void eraseFrontSeq(); void eraseFrontSeq();
void doNack(const FCI_NACK &nack, bool record_nack); void doNack(const FCI_NACK &nack, bool record_nack);
void recordNack(const FCI_NACK &nack); void recordNack(const FCI_NACK &nack);
void clearNackStatus(uint16_t seq); void clearNackStatus(uint16_t seq);
void makeNack(uint16_t max, bool flush = false); void makeNack(uint16_t max, bool flush = false);
private: private:
bool _started = false; bool _started = false;
int _rtt = 50; int _rtt = 50;
onNack _cb; onNack _cb;
std::set<uint16_t> _seq; std::set<uint16_t> _seq;
// 最新nack包中的rtp seq值 [AUTO-TRANSLATED:6984d95a] // 最新nack包中的rtp seq值 [AUTO-TRANSLATED:6984d95a]
// RTP seq value in the latest nack packet // RTP seq value in the latest nack packet
uint16_t _nack_seq = 0; uint16_t _nack_seq = 0;
struct NackStatus { struct NackStatus {
uint64_t first_stamp; uint64_t first_stamp;
uint64_t update_stamp; uint64_t update_stamp;
uint32_t nack_count = 0; uint32_t nack_count = 0;
}; };
std::map<uint16_t /*seq*/, NackStatus> _nack_send_status; std::map<uint16_t /*seq*/, NackStatus> _nack_send_status;
}; };
} // namespace mediakit } // namespace mediakit
#endif //ZLMEDIAKIT_NACK_H #endif //ZLMEDIAKIT_NACK_H

File diff suppressed because it is too large Load Diff

210
webrtc/RtpMap.h Normal file
View File

@ -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 <set>
#include <map>
#include <string>
#include <vector>
#include <memory>
#include <iostream>
#include <iomanip>
#include <cassert>
#include "Extension/Frame.h"
namespace mediakit {
class RtpMap {
public:
using Ptr = std::shared_ptr<RtpMap>;
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<std::string /*key*/, std::string /*value*/> &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<std::string /*key*/, std::string /*value*/> _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

View File

@ -3,7 +3,7 @@
#ifdef ENABLE_SCTP #ifdef ENABLE_SCTP
#include <usrsctp.h> #include <usrsctp.h>
#include "Utils.hpp" #include "Util/Byte.hpp"
#include "Poller/EventPoller.h" #include "Poller/EventPoller.h"
namespace RTC namespace RTC
@ -62,8 +62,8 @@ namespace RTC
return ( return (
(len >= 12) && (len >= 12) &&
// Must have Source Port Number and Destination Port Number set to 5000 (hack). // Must have Source Port Number and Destination Port Number set to 5000 (hack).
(Utils::Byte::Get2Bytes(data, 0) == 5000) && (toolkit::Byte::Get2Bytes(data, 0) == 5000) &&
(Utils::Byte::Get2Bytes(data, 2) == 5000) (toolkit::Byte::Get2Bytes(data, 2) == 5000)
); );
// clang-format on // clang-format on
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,64 +1,65 @@
/** /**
ISC License ISC License
Copyright © 2015, Iñaki Baz Castillo <ibc@aliax.net> Copyright © 2015, Iñaki Baz Castillo <ibc@aliax.net>
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies. copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/ */
#ifndef MS_RTC_SRTP_SESSION_HPP #ifndef MS_RTC_SRTP_SESSION_HPP
#define MS_RTC_SRTP_SESSION_HPP #define MS_RTC_SRTP_SESSION_HPP
#include "Utils.hpp" #include "Util/Byte.hpp"
#include <memory> #include <memory>
typedef struct srtp_ctx_t_ *srtp_t; typedef struct srtp_ctx_t_ *srtp_t;
namespace RTC { namespace RTC {
class DepLibSRTP; class DepLibSRTP;
class SrtpSession { class SrtpSession {
public: public:
enum class CryptoSuite { using Ptr = std::shared_ptr<SrtpSession>;
NONE = 0, enum class CryptoSuite {
AES_CM_128_HMAC_SHA1_80 = 1, NONE = 0,
AES_CM_128_HMAC_SHA1_32, AES_CM_128_HMAC_SHA1_80 = 1,
AEAD_AES_256_GCM, AES_CM_128_HMAC_SHA1_32,
AEAD_AES_128_GCM AEAD_AES_256_GCM,
}; AEAD_AES_128_GCM
};
public:
enum class Type { INBOUND = 1, OUTBOUND }; public:
enum class Type { INBOUND = 1, OUTBOUND };
public:
SrtpSession(Type type, CryptoSuite cryptoSuite, uint8_t *key, size_t keyLen); public:
~SrtpSession(); SrtpSession(Type type, CryptoSuite cryptoSuite, uint8_t *key, size_t keyLen);
~SrtpSession();
public:
bool EncryptRtp(uint8_t *data, int *len); public:
bool DecryptSrtp(uint8_t *data, int *len); bool EncryptRtp(uint8_t *data, int *len);
bool EncryptRtcp(uint8_t *data, int *len); bool DecryptSrtp(uint8_t *data, int *len);
bool DecryptSrtcp(uint8_t *data, int *len); bool EncryptRtcp(uint8_t *data, int *len);
void RemoveStream(uint32_t ssrc); bool DecryptSrtcp(uint8_t *data, int *len);
void RemoveStream(uint32_t ssrc);
private:
// Allocated by this. private:
srtp_t session { nullptr }; // Allocated by this.
std::shared_ptr<DepLibSRTP> _env; srtp_t session { nullptr };
}; std::shared_ptr<DepLibSRTP> _env;
};
} // namespace RTC
} // namespace RTC
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@ -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 <ibc@aliax.net> #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 <string> #include <string>
#include "Util/Byte.hpp"
#include "Network/Buffer.h"
#include "Network/sockutil.h"
namespace RTC namespace RTC {
{ // reference https://rcf-editor.org/rfc/rfc8489
class StunPacket // reference https://rcf-editor.org/rfc/rfc8656
{ // reference https://rcf-editor.org/rfc/rfc8445
public:
// STUN message class.
enum class Class : uint16_t
{
REQUEST = 0,
INDICATION = 1,
SUCCESS_RESPONSE = 2,
ERROR_RESPONSE = 3
};
// STUN message method. //////////// Attribute //////////////////////////
enum class Method : uint16_t // reference https://rcf-editor.org/rfc/rfc8489
{ /*
BINDING = 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 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. //Comprehension-optional range (0x8000-0xFFFF)
enum class Attribute : uint16_t PASSWORD_ALGORITHMS = 0x8002,
{ ALTERNATE_DOMAIN = 0x8003,
MAPPED_ADDRESS = 0x0001, SOFTWARE = 0x8022,
USERNAME = 0x0006, ALTERNATE_SERVER = 0x8023,
MESSAGE_INTEGRITY = 0x0008, FINGERPRINT = 0x8028,
ERROR_CODE = 0x0009, ICE_CONTROLLED = 0x8029,
UNKNOWN_ATTRIBUTES = 0x000A, ICE_CONTROLLING = 0x802A,
REALM = 0x0014, GOOG_NETWORK_INFO = 0xC057,
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;
}; };
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(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<StunAttrMappedAddress>;
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<StunAttrUserName>;
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<StunAttrMessageIntegrity>;
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<StunAttrErrorCode>;
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<StunAttrChannelNumber>;
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<StunAttrLifeTime>;
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<StunAttrXorPeerAddress>;
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<StunAttrData>;
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<StunAttrRealm>;
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<StunAttrNonce>;
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<StunAttrXorRelayedAddress>;
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<StunAttrXorPeerAddress>;
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<StunAttrRequestedTransport>;
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<StunAttrPriority>;
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<StunAttrUseCandidate>;
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<StunAttrFingerprint>;
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<StunAttrIceControlled>;
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<StunAttrIceControlling>;
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<StunPacket>;
// 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 <typename T>
std::size_t operator()(T t) const {
return static_cast<std::size_t>(t);
}
};
struct ClassMethodHash {
bool operator()(std::pair<StunPacket::Class, StunPacket::Method> key) const {
std::size_t h = 0;
h ^= std::hash<uint8_t>()((uint8_t)key.first) << 1;
h ^= std::hash<uint8_t>()((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 <typename T>
std::shared_ptr<T> getAttribute() const {
auto attr = getAttribute(T::TYPE);
if (attr) {
return std::dynamic_pointer_cast<T>(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<StunAttribute::Type, StunAttribute::Ptr> _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 } // namespace RTC
#endif #endif

256
webrtc/USAGE.md Normal file
View File

@ -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代理推流

View File

@ -1,118 +0,0 @@
/**
ISC License
Copyright © 2015, Iñaki Baz Castillo <ibc@aliax.net>
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 <winsock2.h>
#include <ws2tcpip.h>
#pragma comment (lib, "Ws2_32.lib")
#else
#include <arpa/inet.h>
#endif // defined(_WIN32)
#include <cinttypes>// PRIu64, etc
#include <cstddef>// size_t
#include <cstdint>// 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<uint8_t>(value);
data[i] = static_cast<uint8_t>(value >> 8);
}
inline void Byte::Set3Bytes(uint8_t *data, size_t i, uint32_t value) {
data[i + 2] = static_cast<uint8_t>(value);
data[i + 1] = static_cast<uint8_t>(value >> 8);
data[i] = static_cast<uint8_t>(value >> 16);
}
inline void Byte::Set4Bytes(uint8_t *data, size_t i, uint32_t value) {
data[i + 3] = static_cast<uint8_t>(value);
data[i + 2] = static_cast<uint8_t>(value >> 8);
data[i + 1] = static_cast<uint8_t>(value >> 16);
data[i] = static_cast<uint8_t>(value >> 24);
}
inline void Byte::Set8Bytes(uint8_t *data, size_t i, uint64_t value) {
data[i + 7] = static_cast<uint8_t>(value);
data[i + 6] = static_cast<uint8_t>(value >> 8);
data[i + 5] = static_cast<uint8_t>(value >> 16);
data[i + 4] = static_cast<uint8_t>(value >> 24);
data[i + 3] = static_cast<uint8_t>(value >> 32);
data[i + 2] = static_cast<uint8_t>(value >> 40);
data[i + 1] = static_cast<uint8_t>(value >> 48);
data[i] = static_cast<uint8_t>(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

303
webrtc/WebRtcClient.cpp Executable file
View File

@ -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<WebRtcClient> weak_self = static_pointer_cast<WebRtcClient>(shared_from_this());
auto offer_sdp = _transport->createOfferSdp();
DebugL << "send offer:\n" << offer_sdp;
_negotiate = make_shared<HttpRequester>();
_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<WebRtcSignalingPeer>(_url._host, _url._port, _url._is_ssl, room_id);
weak_ptr<WebRtcClient> weak_self = static_pointer_cast<WebRtcClient>(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<WebRtcClient> weak_self = static_pointer_cast<WebRtcClient>(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<WebRtcClient> weak_self = std::static_pointer_cast<WebRtcClient>(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 */

105
webrtc/WebRtcClient.h Executable file
View File

@ -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 <memory>
#include <string>
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<WebRtcClient> {
public:
using Ptr = std::shared_ptr<WebRtcClient>;
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<std::string /*candidate key*/, toolkit::SocketHelper::Ptr> _socket_map;
};
} /*namespace mediakit */
#endif /* ZLMEDIAKIT_WEBRTC_CLIENT_H */

View File

@ -10,6 +10,8 @@
#include "WebRtcEchoTest.h" #include "WebRtcEchoTest.h"
using namespace toolkit;
namespace mediakit { namespace mediakit {
WebRtcEchoTest::Ptr WebRtcEchoTest::create(const EventPoller::Ptr &poller) { WebRtcEchoTest::Ptr WebRtcEchoTest::create(const EventPoller::Ptr &poller) {

View File

@ -18,7 +18,7 @@ namespace mediakit {
class WebRtcEchoTest : public WebRtcTransportImp { class WebRtcEchoTest : public WebRtcTransportImp {
public: public:
using Ptr = std::shared_ptr<WebRtcEchoTest>; using Ptr = std::shared_ptr<WebRtcEchoTest>;
static Ptr create(const EventPoller::Ptr &poller); static Ptr create(const toolkit::EventPoller::Ptr &poller);
protected: protected:
///////WebRtcTransportImp override/////// ///////WebRtcTransportImp override///////
@ -31,7 +31,7 @@ protected:
void onBeforeEncryptRtcp(const char *buf, int &len, void *ctx) override {}; void onBeforeEncryptRtcp(const char *buf, int &len, void *ctx) override {};
private: private:
WebRtcEchoTest(const EventPoller::Ptr &poller); WebRtcEchoTest(const toolkit::EventPoller::Ptr &poller);
}; };
}// namespace mediakit }// namespace mediakit

View File

@ -1,330 +1,338 @@
/* /*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
* *
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). * 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 * 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 * 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. * may be found in the AUTHORS file in the root of the source tree.
*/ */
#include "WebRtcPlayer.h" #include "WebRtcPlayer.h"
#include "Common/config.h" #include "Common/config.h"
#include "Extension/Factory.h" #include "Extension/Factory.h"
#include "Util/base64.h" #include "Util/base64.h"
using namespace std; using namespace std;
using namespace toolkit;
namespace mediakit {
namespace mediakit {
namespace Rtc {
#define RTC_FIELD "rtc." namespace Rtc {
const string kBfilter = RTC_FIELD "bfilter"; #define RTC_FIELD "rtc."
static onceToken token([]() { mINI::Instance()[kBfilter] = 0; }); const string kBfilter = RTC_FIELD "bfilter";
} // namespace Rtc static onceToken token([]() { mINI::Instance()[kBfilter] = 0; });
} // namespace Rtc
H264BFrameFilter::H264BFrameFilter()
: _last_seq(0) H264BFrameFilter::H264BFrameFilter()
, _last_stamp(0) : _last_seq(0)
, _first_packet(true) {} , _last_stamp(0)
, _first_packet(true) {}
RtpPacket::Ptr H264BFrameFilter::processPacket(const RtpPacket::Ptr &packet) {
if (!packet) { RtpPacket::Ptr H264BFrameFilter::processPacket(const RtpPacket::Ptr &packet) {
return nullptr; if (!packet) {
} return nullptr;
}
if (isH264BFrame(packet)) {
return nullptr; if (isH264BFrame(packet)) {
} return nullptr;
}
auto cur_stamp = packet->getStamp();
auto cur_seq = packet->getSeq(); auto cur_stamp = packet->getStamp();
auto cur_seq = packet->getSeq();
if (_first_packet) {
_first_packet = false; if (_first_packet) {
_last_seq = cur_seq; _first_packet = false;
_last_stamp = cur_stamp; _last_seq = cur_seq;
} _last_stamp = cur_stamp;
}
// 处理时间戳连续性问题
if (cur_stamp < _last_stamp) { // 处理时间戳连续性问题
return nullptr; if (cur_stamp < _last_stamp) {
} return nullptr;
_last_stamp = cur_stamp; }
_last_stamp = cur_stamp;
// 处理 seq 连续性问题
if (cur_seq > _last_seq + 4) { // 处理 seq 连续性问题
RtpHeader *header = packet->getHeader(); if (cur_seq > _last_seq + 4) {
_last_seq = (_last_seq + 1) & 0xFFFF; RtpHeader *header = packet->getHeader();
header->seq = htons(_last_seq); _last_seq = (_last_seq + 1) & 0xFFFF;
} header->seq = htons(_last_seq);
}
return packet;
} return packet;
}
bool H264BFrameFilter::isH264BFrame(const RtpPacket::Ptr &packet) const {
uint8_t *payload = packet->getPayload(); bool H264BFrameFilter::isH264BFrame(const RtpPacket::Ptr &packet) const {
size_t payload_size = packet->getPayloadSize(); uint8_t *payload = packet->getPayload();
size_t payload_size = packet->getPayloadSize();
if (payload_size < 1) {
return false; if (payload_size < 1) {
} return false;
}
uint8_t nal_unit_type = payload[0] & 0x1F;
switch (nal_unit_type) { uint8_t nal_unit_type = payload[0] & 0x1F;
case 24: // STAP-A switch (nal_unit_type) {
return handleStapA(payload, payload_size); case 24: // STAP-A
case 28: // FU-A return handleStapA(payload, payload_size);
return handleFua(payload, payload_size); case 28: // FU-A
default: return handleFua(payload, payload_size);
if (nal_unit_type < 24) { default:
return isBFrameByNalType(nal_unit_type, payload + 1, payload_size - 1); if (nal_unit_type < 24) {
} return isBFrameByNalType(nal_unit_type, payload + 1, payload_size - 1);
return false; }
} return false;
} }
}
bool H264BFrameFilter::handleStapA(const uint8_t *payload, size_t payload_size) const {
size_t offset = 1; bool H264BFrameFilter::handleStapA(const uint8_t *payload, size_t payload_size) const {
while (offset + 2 <= payload_size) { size_t offset = 1;
uint16_t nalu_size = (payload[offset] << 8) | payload[offset + 1]; while (offset + 2 <= payload_size) {
offset += 2; uint16_t nalu_size = (payload[offset] << 8) | payload[offset + 1];
if (offset + nalu_size > payload_size || nalu_size < 1) { offset += 2;
return false; if (offset + nalu_size > payload_size || nalu_size < 1) {
} return false;
uint8_t original_nal_type = payload[offset] & 0x1F; }
if (original_nal_type < 24) { uint8_t original_nal_type = payload[offset] & 0x1F;
if (isBFrameByNalType(original_nal_type, payload + offset + 1, nalu_size - 1)) { if (original_nal_type < 24) {
return true; if (isBFrameByNalType(original_nal_type, payload + offset + 1, nalu_size - 1)) {
} return true;
} }
offset += nalu_size; }
} offset += nalu_size;
return false; }
} return false;
}
bool H264BFrameFilter::handleFua(const uint8_t *payload, size_t payload_size) const {
if (payload_size < 2) { bool H264BFrameFilter::handleFua(const uint8_t *payload, size_t payload_size) const {
return false; if (payload_size < 2) {
} return false;
uint8_t fu_header = payload[1]; }
uint8_t original_nal_type = fu_header & 0x1F; uint8_t fu_header = payload[1];
bool start_bit = fu_header & 0x80; uint8_t original_nal_type = fu_header & 0x1F;
if (start_bit) { bool start_bit = fu_header & 0x80;
return isBFrameByNalType(original_nal_type, payload + 2, payload_size - 2); if (start_bit) {
} return isBFrameByNalType(original_nal_type, payload + 2, payload_size - 2);
return false; }
} return false;
}
bool H264BFrameFilter::isBFrameByNalType(uint8_t nal_type, const uint8_t *data, size_t size) const {
if (size < 1) { bool H264BFrameFilter::isBFrameByNalType(uint8_t nal_type, const uint8_t *data, size_t size) const {
return false; 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; 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; 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) int H264BFrameFilter::decodeExpGolomb(const uint8_t *data, size_t size, size_t &bitPos) const {
return -1; if (bitPos >= size * 8)
return -1;
int leadingZeroBits = 0;
while (bitPos < size * 8 && !getBit(data, bitPos++)) { int leadingZeroBits = 0;
leadingZeroBits++; while (bitPos < size * 8 && !getBit(data, bitPos++)) {
} leadingZeroBits++;
}
int result = (1 << leadingZeroBits) - 1;
for (int i = 0; i < leadingZeroBits; i++) { int result = (1 << leadingZeroBits) - 1;
if (bitPos < size * 8) { for (int i = 0; i < leadingZeroBits; i++) {
result += getBit(data, bitPos++) << (leadingZeroBits - i - 1); if (bitPos < size * 8) {
} result += getBit(data, bitPos++) << (leadingZeroBits - i - 1);
} }
}
return result;
} return result;
}
int H264BFrameFilter::getBit(const uint8_t *data, size_t pos) const {
size_t byteIndex = pos / 8; int H264BFrameFilter::getBit(const uint8_t *data, size_t pos) const {
size_t bitOffset = pos % 8; size_t byteIndex = pos / 8;
uint8_t byte = data[byteIndex]; size_t bitOffset = pos % 8;
return (byte >> (7 - bitOffset)) & 0x01; 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; uint8_t H264BFrameFilter::extractSliceType(const uint8_t *data, size_t size) const {
int first_mb_in_slice = decodeExpGolomb(data, size, bitPos); size_t bitPos = 0;
int slice_type = decodeExpGolomb(data, size, bitPos); 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<uint8_t>(slice_type); if (slice_type >= 0 && slice_type <= 9) {
} return static_cast<uint8_t>(slice_type);
return -1; }
} 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) { WebRtcPlayer::Ptr WebRtcPlayer::create(const EventPoller::Ptr &poller,
ptr->onDestory(); const RtspMediaSource::Ptr &src,
delete ptr; const MediaInfo &info,
}); WebRtcTransport::Role role,
ret->onCreate(); WebRtcTransport::SignalingProtocols signaling_protocols) {
return ret; WebRtcPlayer::Ptr ret(new WebRtcPlayer(poller, src, info), [](WebRtcPlayer *ptr) {
} ptr->onDestory();
delete ptr;
WebRtcPlayer::WebRtcPlayer(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info) });
: WebRtcTransportImp(poller) { ret->setRole(role);
_media_info = info; ret->setSignalingProtocols(signaling_protocols);
_play_src = src; ret->onCreate();
CHECK(src); return ret;
}
GET_CONFIG(bool, direct_proxy, Rtsp::kDirectProxy);
_send_config_frames_once = direct_proxy; WebRtcPlayer::WebRtcPlayer(const EventPoller::Ptr &poller,
const RtspMediaSource::Ptr &src,
GET_CONFIG(bool, enable, Rtc::kBfilter); const MediaInfo &info) : WebRtcTransportImp(poller) {
_bfliter_flag = enable; _media_info = info;
_is_h264 = false; _play_src = src;
_bfilter = std::make_shared<H264BFrameFilter>(); CHECK(src);
}
GET_CONFIG(bool, direct_proxy, Rtsp::kDirectProxy);
void WebRtcPlayer::onStartWebRTC() { _send_config_frames_once = direct_proxy;
auto playSrc = _play_src.lock();
if (!playSrc) { GET_CONFIG(bool, enable, Rtc::kBfilter);
onShutdown(SockException(Err_shutdown, "rtsp media source was shutdown")); _bfliter_flag = enable;
return; _is_h264 = false;
} _bfilter = std::make_shared<H264BFrameFilter>();
WebRtcTransportImp::onStartWebRTC(); }
if (canSendRtp()) {
playSrc->pause(false); void WebRtcPlayer::onStartWebRTC() {
_reader = playSrc->getRing()->attach(getPoller(), true); auto playSrc = _play_src.lock();
weak_ptr<WebRtcPlayer> weak_self = static_pointer_cast<WebRtcPlayer>(shared_from_this()); if (!playSrc) {
weak_ptr<Session> weak_session = static_pointer_cast<Session>(getSession()); onShutdown(SockException(Err_shutdown, "rtsp media source was shutdown"));
_reader->setGetInfoCB([weak_session]() { return;
Any ret; }
ret.set(static_pointer_cast<Session>(weak_session.lock())); WebRtcTransportImp::onStartWebRTC();
return ret; if (canSendRtp()) {
}); playSrc->pause(false);
_reader->setReadCB([weak_self](const RtspMediaSource::RingDataType &pkt) { _reader = playSrc->getRing()->attach(getPoller(), true);
auto strong_self = weak_self.lock(); weak_ptr<WebRtcPlayer> weak_self = static_pointer_cast<WebRtcPlayer>(shared_from_this());
if (!strong_self) { weak_ptr<Session> weak_session = static_pointer_cast<Session>(getSession());
return; _reader->setGetInfoCB([weak_session]() {
} Any ret;
ret.set(static_pointer_cast<Session>(weak_session.lock()));
if (strong_self->_send_config_frames_once && !pkt->empty()) { return ret;
const auto &first_rtp = pkt->front(); });
strong_self->sendConfigFrames(first_rtp->getSeq(), first_rtp->sample_rate, first_rtp->getStamp(), first_rtp->ntp_stamp); _reader->setReadCB([weak_self](const RtspMediaSource::RingDataType &pkt) {
strong_self->_send_config_frames_once = false; auto strong_self = weak_self.lock();
} if (!strong_self) {
return;
size_t i = 0; }
pkt->for_each([&](const RtpPacket::Ptr &rtp) {
if (strong_self->_bfliter_flag) { if (strong_self->_send_config_frames_once && !pkt->empty()) {
if (TrackVideo == rtp->type && strong_self->_is_h264) { const auto &first_rtp = pkt->front();
auto rtp_filter = strong_self->_bfilter->processPacket(rtp); strong_self->sendConfigFrames(first_rtp->getSeq(), first_rtp->sample_rate, first_rtp->getStamp(), first_rtp->ntp_stamp);
if (rtp_filter) { strong_self->_send_config_frames_once = false;
strong_self->onSendRtp(rtp_filter, ++i == pkt->size()); }
}
} else { size_t i = 0;
strong_self->onSendRtp(rtp, ++i == pkt->size()); pkt->for_each([&](const RtpPacket::Ptr &rtp) {
} if (strong_self->_bfliter_flag) {
} else { if (TrackVideo == rtp->type && strong_self->_is_h264) {
strong_self->onSendRtp(rtp, ++i == pkt->size()); auto rtp_filter = strong_self->_bfilter->processPacket(rtp);
} if (rtp_filter) {
}); strong_self->onSendRtp(rtp_filter, ++i == pkt->size());
}); }
_reader->setDetachCB([weak_self]() { } else {
auto strong_self = weak_self.lock(); strong_self->onSendRtp(rtp, ++i == pkt->size());
if (!strong_self) { }
return; } else {
} strong_self->onSendRtp(rtp, ++i == pkt->size());
strong_self->onShutdown(SockException(Err_shutdown, "rtsp ring buffer detached")); }
}); });
});
_reader->setMessageCB([weak_self](const toolkit::Any &data) { _reader->setDetachCB([weak_self]() {
auto strong_self = weak_self.lock(); auto strong_self = weak_self.lock();
if (!strong_self) { if (!strong_self) {
return; return;
} }
if (data.is<Buffer>()) { strong_self->onShutdown(SockException(Err_shutdown, "rtsp ring buffer detached"));
auto &buffer = data.get<Buffer>(); });
// PPID 51: 文本string [AUTO-TRANSLATED:69a8cf81]
// PPID 51: Text string _reader->setMessageCB([weak_self](const toolkit::Any &data) {
// PPID 53: 二进制 [AUTO-TRANSLATED:faf00c3e] auto strong_self = weak_self.lock();
// PPID 53: Binary if (!strong_self) {
strong_self->sendDatachannel(0, 51, buffer.data(), buffer.size()); return;
} else { }
WarnL << "Send unknown message type to webrtc player: " << data.type_name(); if (data.is<Buffer>()) {
} auto &buffer = data.get<Buffer>();
}); // PPID 51: 文本string [AUTO-TRANSLATED:69a8cf81]
} // PPID 51: Text string
} // PPID 53: 二进制 [AUTO-TRANSLATED:faf00c3e]
void WebRtcPlayer::onDestory() { // PPID 53: Binary
auto duration = getDuration(); strong_self->sendDatachannel(0, 51, buffer.data(), buffer.size());
auto bytes_usage = getBytesUsage(); } else {
// 流量统计事件广播 [AUTO-TRANSLATED:6b0b1234] WarnL << "Send unknown message type to webrtc player: " << data.type_name();
// 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) { void WebRtcPlayer::onDestory() {
NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, bytes_usage, duration, true, *getSession()); auto duration = getDuration();
} auto bytes_usage = getBytesUsage();
} // 流量统计事件广播 [AUTO-TRANSLATED:6b0b1234]
WebRtcTransportImp::onDestory(); // Traffic statistics event broadcast
} GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
if (_reader && getSession()) {
void WebRtcPlayer::onRtcConfigure(RtcConfigure &configure) const { WarnL << "RTC播放器(" << _media_info.shortUrl() << ")结束播放,耗时(s):" << duration;
auto playSrc = _play_src.lock(); if (bytes_usage >= iFlowThreshold * 1024) {
if (!playSrc) { NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, bytes_usage, duration, true, *getSession());
return; }
} }
WebRtcTransportImp::onRtcConfigure(configure); WebRtcTransportImp::onDestory();
// 这是播放 [AUTO-TRANSLATED:d93c019e] }
// This is playing
configure.audio.direction = configure.video.direction = RtpDirection::sendonly; void WebRtcPlayer::onRtcConfigure(RtcConfigure &configure) const {
configure.setPlayRtspInfo(playSrc->getSdp()); auto playSrc = _play_src.lock();
} if (!playSrc) {
return;
void WebRtcPlayer::sendConfigFrames(uint32_t before_seq, uint32_t sample_rate, uint32_t timestamp, uint64_t ntp_timestamp) { }
auto play_src = _play_src.lock(); WebRtcTransportImp::onRtcConfigure(configure);
if (!play_src) { // 这是播放 [AUTO-TRANSLATED:d93c019e]
return; // This is playing
} configure.audio.direction = configure.video.direction = RtpDirection::sendonly;
SdpParser parser(play_src->getSdp()); configure.setPlayRtspInfo(playSrc->getSdp());
auto video_sdp = parser.getTrack(TrackVideo); }
if (!video_sdp) {
return; void WebRtcPlayer::sendConfigFrames(uint32_t before_seq, uint32_t sample_rate, uint32_t timestamp, uint64_t ntp_timestamp) {
} auto play_src = _play_src.lock();
auto video_track = dynamic_pointer_cast<VideoTrack>(Factory::getTrackBySdp(video_sdp)); if (!play_src) {
if (!video_track) { return;
return; }
} SdpParser parser(play_src->getSdp());
_is_h264 = video_track->getCodecId() == CodecH264; auto video_sdp = parser.getTrack(TrackVideo);
auto frames = video_track->getConfigFrames(); if (!video_sdp) {
if (frames.empty()) { return;
return; }
} auto video_track = dynamic_pointer_cast<VideoTrack>(Factory::getTrackBySdp(video_sdp));
auto encoder = mediakit::Factory::getRtpEncoderByCodecId(video_track->getCodecId(), 0); if (!video_track) {
if (!encoder) { return;
return; }
} _is_h264 = video_track->getCodecId() == CodecH264;
auto frames = video_track->getConfigFrames();
GET_CONFIG(uint32_t, video_mtu, Rtp::kVideoMtuSize); if (frames.empty()) {
encoder->setRtpInfo(0, video_mtu, sample_rate, 0, 0, 0); return;
}
auto seq = before_seq - frames.size(); auto encoder = mediakit::Factory::getRtpEncoderByCodecId(video_track->getCodecId(), 0);
for (const auto &frame : frames) { if (!encoder) {
auto rtp = encoder->getRtpInfo().makeRtp(TrackVideo, frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize(), false, 0); return;
auto header = rtp->getHeader(); }
header->seq = htons(seq++);
header->stamp = htonl(timestamp); GET_CONFIG(uint32_t, video_mtu, Rtp::kVideoMtuSize);
rtp->ntp_stamp = ntp_timestamp; encoder->setRtpInfo(0, video_mtu, sample_rate, 0, 0, 0);
onSendRtp(rtp, false);
} 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 }// namespace mediakit

View File

@ -1,164 +1,165 @@
/* /*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
* *
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). * 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 * 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 * 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. * may be found in the AUTHORS file in the root of the source tree.
*/ */
#ifndef ZLMEDIAKIT_WEBRTCPLAYER_H #ifndef ZLMEDIAKIT_WEBRTCPLAYER_H
#define ZLMEDIAKIT_WEBRTCPLAYER_H #define ZLMEDIAKIT_WEBRTCPLAYER_H
#include "Rtsp/RtspMediaSource.h" #include "WebRtcTransport.h"
#include "WebRtcTransport.h" #include "Rtsp/RtspMediaSource.h"
namespace mediakit { namespace mediakit {
/** /**
* @brief H.264 B * @brief H.264 B
* H.264 RTP B * H.264 RTP B
*/ */
class H264BFrameFilter { class H264BFrameFilter {
public: public:
/** /**
* ISO_IEC_14496-10-AVC-2012 * ISO_IEC_14496-10-AVC-2012
* Table 7-6 Name association to slice_type * Table 7-6 Name association to slice_type
*/ */
enum H264SliceType { enum H264SliceType {
H264SliceTypeP = 0, H264SliceTypeP = 0,
H264SliceTypeB = 1, H264SliceTypeB = 1,
H264SliceTypeI = 2, H264SliceTypeI = 2,
H264SliceTypeSP = 3, H264SliceTypeSP = 3,
H264SliceTypeSI = 4, H264SliceTypeSI = 4,
H264SliceTypeP1 = 5, H264SliceTypeP1 = 5,
H264SliceTypeB1 = 6, H264SliceTypeB1 = 6,
H264SliceTypeI1 = 7, H264SliceTypeI1 = 7,
H264SliceTypeSP1 = 8, H264SliceTypeSP1 = 8,
H264SliceTypeSI1 = 9, H264SliceTypeSI1 = 9,
}; };
enum H264NALUType { enum H264NALUType {
NAL_NIDR = 1, NAL_NIDR = 1,
NAL_PARTITION_A = 2, NAL_PARTITION_A = 2,
NAL_PARTITION_B = 3, NAL_PARTITION_B = 3,
NAL_PARTITION_C = 4, NAL_PARTITION_C = 4,
NAL_IDR = 5, NAL_IDR = 5,
}; };
H264BFrameFilter(); H264BFrameFilter();
~H264BFrameFilter() = default; ~H264BFrameFilter() = default;
/** /**
* @brief RTP B * @brief RTP B
* @param packet RTP * @param packet RTP
* @return B nullptr * @return B nullptr
*/ */
RtpPacket::Ptr processPacket(const RtpPacket::Ptr &packet); RtpPacket::Ptr processPacket(const RtpPacket::Ptr &packet);
private: private:
/** /**
* @brief RTP H.264 B * @brief RTP H.264 B
* @param packet RTP * @param packet RTP
* @return B true false * @return B true false
*/ */
bool isH264BFrame(const RtpPacket::Ptr &packet) const; bool isH264BFrame(const RtpPacket::Ptr &packet) const;
/** /**
* @brief NAL B * @brief NAL B
* @param nal_type NAL * @param nal_type NAL
* @param data NAL NAL * @param data NAL NAL
* @param size * @param size
* @return B true false * @return B true false
*/ */
bool isBFrameByNalType(uint8_t nal_type, const uint8_t *data, size_t size) const; bool isBFrameByNalType(uint8_t nal_type, const uint8_t *data, size_t size) const;
/** /**
* @brief * @brief
* @param data * @param data
* @param size * @param size
* @param bits_offset * @param bits_offset
* @return * @return
*/ */
int decodeExpGolomb(const uint8_t *data, size_t size, size_t &bitPos) const; int decodeExpGolomb(const uint8_t *data, size_t size, size_t &bitPos) const;
/** /**
* @brief * @brief
* @param data * @param data
* @param size * @param size
* @return 0 1 * @return 0 1
*/ */
int getBit(const uint8_t *data, size_t size) const; int getBit(const uint8_t *data, size_t size) const;
/** /**
* @brief * @brief
* @param data * @param data
* @param size * @param size
* @return * @return
*/ */
uint8_t extractSliceType(const uint8_t *data, size_t size) const; uint8_t extractSliceType(const uint8_t *data, size_t size) const;
/** /**
* @brief FU-A分片 * @brief FU-A分片
* @param payload * @param payload
* @param payload_size * @param payload_size
* @return B true false * @return B true false
*/ */
bool handleFua(const uint8_t *payload, size_t payload_size) const; bool handleFua(const uint8_t *payload, size_t payload_size) const;
/** /**
* @brief STAP-A * @brief STAP-A
* @param payload * @param payload
* @param payload_size * @param payload_size
* @return B true false * @return B true false
*/ */
bool handleStapA(const uint8_t *payload, size_t payload_size) const; bool handleStapA(const uint8_t *payload, size_t payload_size) const;
private: private:
uint16_t _last_seq; // 维护输出流的序列号 uint16_t _last_seq; // 维护输出流的序列号
uint32_t _last_stamp; // 维护输出流的时间戳 uint32_t _last_stamp; // 维护输出流的时间戳
bool _first_packet; // 是否是第一个包的标记 bool _first_packet; // 是否是第一个包的标记
}; };
class WebRtcPlayer : public WebRtcTransportImp { class WebRtcPlayer : public WebRtcTransportImp {
public: public:
using Ptr = std::shared_ptr<WebRtcPlayer>; using Ptr = std::shared_ptr<WebRtcPlayer>;
static Ptr create(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info); static Ptr create(const toolkit::EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info,
MediaInfo getMediaInfo() { return _media_info; } WebRtcTransport::Role role, WebRtcTransport::SignalingProtocols signaling_protocols);
MediaInfo getMediaInfo() { return _media_info; }
protected:
///////WebRtcTransportImp override/////// protected:
void onStartWebRTC() override; ///////WebRtcTransportImp override///////
void onDestory() override; void onStartWebRTC() override;
void onRtcConfigure(RtcConfigure &configure) const override; void onDestory() override;
void onRtcConfigure(RtcConfigure &configure) const override;
private:
WebRtcPlayer(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info); 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);
void sendConfigFrames(uint32_t before_seq, uint32_t sample_rate, uint32_t timestamp, uint64_t ntp_timestamp);
private:
// 媒体相关元数据 [AUTO-TRANSLATED:f4cf8045] private:
// Media related metadata // 媒体相关元数据 [AUTO-TRANSLATED:f4cf8045]
MediaInfo _media_info; // Media related metadata
// 播放的rtsp源 [AUTO-TRANSLATED:9963eed1] MediaInfo _media_info;
// Playing rtsp source // 播放的rtsp源 [AUTO-TRANSLATED:9963eed1]
std::weak_ptr<RtspMediaSource> _play_src; // Playing rtsp source
std::weak_ptr<RtspMediaSource> _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. // rtp 直接转发情况下通常会缺少 sps/pps, 在转发 rtp 前, 先发送一次相关帧信息, 部分情况下是可以播放的 [AUTO-TRANSLATED:65fdf16a]
bool _send_config_frames_once { false }; // 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 // 播放rtsp源的reader对象 [AUTO-TRANSLATED:7b305055]
RtspMediaSource::RingType::RingReader::Ptr _reader; // Reader object for playing rtsp source
RtspMediaSource::RingType::RingReader::Ptr _reader;
bool _is_h264 { false };
bool _bfliter_flag { false }; bool _is_h264 { false };
std::shared_ptr<H264BFrameFilter> _bfilter; bool _bfliter_flag { false };
}; std::shared_ptr<H264BFrameFilter> _bfilter;
};
}// namespace mediakit
#endif // ZLMEDIAKIT_WEBRTCPLAYER_H }// namespace mediakit
#endif // ZLMEDIAKIT_WEBRTCPLAYER_H

126
webrtc/WebRtcProxyPlayer.cpp Executable file
View File

@ -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<uint64_t>();
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<WebRtcProxyPlayerImp> weak_self = std::static_pointer_cast<WebRtcProxyPlayerImp>(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<int>();
WebRtcPlayerClient::Ptr transport = std::dynamic_pointer_cast<WebRtcPlayerClient>(_transport);
auto media_src = dynamic_pointer_cast<RtspMediaSource>(_media_src);
transport->setMediaSource(media_src);
std::weak_ptr<WebRtcProxyPlayerImp> weak_self = std::static_pointer_cast<WebRtcProxyPlayerImp>(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<Track::Ptr> WebRtcProxyPlayerImp::getTracks(bool ready /*= true*/) const {
auto transport = static_pointer_cast<WebRtcPlayerClient>(_transport);
return transport ? transport->getTracks(ready) : Super::getTracks(ready);
}
void WebRtcProxyPlayerImp::addTrackCompleted() {
}
} /* namespace mediakit */

56
webrtc/WebRtcProxyPlayer.h Executable file
View File

@ -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 <memory>
#include <string>
namespace mediakit {
// 实现了webrtc代理拉流功能
class WebRtcProxyPlayer
: public PlayerBase , public WebRtcClient {
public:
using Ptr = std::shared_ptr<WebRtcProxyPlayer>;
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<toolkit::Timer> _check_timer;
};
} /* namespace mediakit */
#endif /* ZLMEDIAKIT_WEBRTC_PROXY_PLAYER_H */

43
webrtc/WebRtcProxyPlayerImp.h Executable file
View File

@ -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<WebRtcProxyPlayer, PlayerBase>
, private TrackListener {
public:
using Ptr = std::shared_ptr<WebRtcProxyPlayerImp>;
using Super = PlayerImp<WebRtcProxyPlayer, PlayerBase>;
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<Track::Ptr> 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 */

92
webrtc/WebRtcProxyPusher.cpp Executable file
View File

@ -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<uint64_t>();
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<WebRtcProxyPusher> weak_self = std::static_pointer_cast<WebRtcProxyPusher>(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 */

51
webrtc/WebRtcProxyPusher.h Executable file
View File

@ -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 <memory>
#include <string>
namespace mediakit {
// 实现了webrtc代理拉流功能
class WebRtcProxyPusher
: public PusherBase , public WebRtcClient {
public:
using Ptr = std::shared_ptr<WebRtcProxyPusher>;
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<RtspMediaSource> _push_src;
};
using WebRtcProxyPusherImp = PusherImp<WebRtcProxyPusher, PusherBase>;
} /* namespace mediakit */
#endif /* ZLMEDIAKIT_WEBRTC_PROXY_PUSHER_H */

View File

@ -1,169 +1,232 @@
/* /*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
* *
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). * 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 * 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 * 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. * may be found in the AUTHORS file in the root of the source tree.
*/ */
#include "WebRtcPusher.h" #include "WebRtcPusher.h"
#include "Common/config.h" #include "Common/config.h"
#include "Rtsp/RtspMediaSourceImp.h" #include "Rtsp/RtspMediaSourceImp.h"
using namespace std; using namespace std;
using namespace toolkit;
namespace mediakit {
namespace mediakit {
WebRtcPusher::Ptr WebRtcPusher::create(const EventPoller::Ptr &poller,
const RtspMediaSource::Ptr &src, WebRtcPusher::Ptr WebRtcPusher::create(const EventPoller::Ptr &poller,
const std::shared_ptr<void> &ownership, const RtspMediaSource::Ptr &src,
const MediaInfo &info, const std::shared_ptr<void> &ownership,
const ProtocolOption &option) { const MediaInfo &info,
WebRtcPusher::Ptr ret(new WebRtcPusher(poller, src, ownership, info, option), [](WebRtcPusher *ptr) { const ProtocolOption &option,
ptr->onDestory(); WebRtcTransport::Role role,
delete ptr; WebRtcTransport::SignalingProtocols signaling_protocols) {
}); WebRtcPusher::Ptr pusher(new WebRtcPusher(poller, src, ownership, info, option), [](WebRtcPusher *ptr) {
ret->onCreate(); ptr->onDestory();
return ret; delete ptr;
} });
WebRtcPusher::WebRtcPusher(const EventPoller::Ptr &poller, pusher->setRole(role);
const RtspMediaSource::Ptr &src, pusher->setSignalingProtocols(signaling_protocols);
const std::shared_ptr<void> &ownership, pusher->onCreate();
const MediaInfo &info, return pusher;
const ProtocolOption &option) : WebRtcTransportImp(poller) { }
_media_info = info;
_push_src = src; WebRtcPusher::WebRtcPusher(const EventPoller::Ptr &poller,
_push_src_ownership = ownership; const RtspMediaSource::Ptr &src,
_continue_push_ms = option.continue_push_ms; const std::shared_ptr<void> &ownership,
CHECK(_push_src); const MediaInfo &info,
} const ProtocolOption &option) : WebRtcTransportImp(poller) {
_media_info = info;
bool WebRtcPusher::close(MediaSource &sender) { _push_src = src;
onShutdown(SockException(Err_shutdown, "close media: " + sender.getUrl())); _push_src_ownership = ownership;
// 主动关闭推流,那么不延时注销 [AUTO-TRANSLATED:ee7cc580] _continue_push_ms = option.continue_push_ms;
// Actively close the stream, then do not delay the logout CHECK(_push_src);
_push_src = nullptr; }
return true;
} bool WebRtcPusher::close(MediaSource &sender) {
onShutdown(SockException(Err_shutdown, "close media: " + sender.getUrl()));
int WebRtcPusher::totalReaderCount(MediaSource &sender) { // 主动关闭推流,那么不延时注销 [AUTO-TRANSLATED:ee7cc580]
auto total_count = _push_src ? _push_src->totalReaderCount() : 0; // Actively close the stream, then do not delay the logout
if (_simulcast) { _push_src = nullptr;
std::lock_guard<std::recursive_mutex> lock(_mtx); return true;
for (auto &src : _push_src_sim) { }
total_count += src.second->totalReaderCount();
} int WebRtcPusher::totalReaderCount(MediaSource &sender) {
} auto total_count = _push_src ? _push_src->totalReaderCount() : 0;
return total_count; if (_simulcast) {
} std::lock_guard<std::recursive_mutex> lock(_mtx);
for (auto &src : _push_src_sim) {
MediaOriginType WebRtcPusher::getOriginType(MediaSource &sender) const { total_count += src.second->totalReaderCount();
return MediaOriginType::rtc_push; }
} }
return total_count;
string WebRtcPusher::getOriginUrl(MediaSource &sender) const { }
return _media_info.full_url;
} MediaOriginType WebRtcPusher::getOriginType(MediaSource &sender) const {
return MediaOriginType::rtc_push;
std::shared_ptr<SockInfo> WebRtcPusher::getOriginSock(MediaSource &sender) const { }
return static_pointer_cast<SockInfo>(getSession());
} string WebRtcPusher::getOriginUrl(MediaSource &sender) const {
return _media_info.full_url;
toolkit::EventPoller::Ptr WebRtcPusher::getOwnerPoller(MediaSource &sender) { }
return getPoller();
} std::shared_ptr<SockInfo> WebRtcPusher::getOriginSock(MediaSource &sender) const {
return static_pointer_cast<SockInfo>(getSession());
void WebRtcPusher::onRecvRtp(MediaTrack &track, const string &rid, RtpPacket::Ptr rtp) { }
if (!_simulcast) {
assert(_push_src); toolkit::EventPoller::Ptr WebRtcPusher::getOwnerPoller(MediaSource &sender) {
_push_src->onWrite(rtp, false); return getPoller();
return; }
}
void WebRtcPusher::onRecvRtp(MediaTrack &track, const string &rid, RtpPacket::Ptr rtp) {
if (rtp->type == TrackAudio) { if (!_simulcast) {
// 音频 [AUTO-TRANSLATED:a577d8e1] assert(_push_src);
// Audio _push_src->onWrite(rtp, false);
for (auto &pr : _push_src_sim) { return;
pr.second->onWrite(rtp, false); }
}
} else { if (rtp->type == TrackAudio) {
// 视频 [AUTO-TRANSLATED:904730ac] // 音频 [AUTO-TRANSLATED:a577d8e1]
// Video // Audio
std::lock_guard<std::recursive_mutex> lock(_mtx); for (auto &pr : _push_src_sim) {
auto &src = _push_src_sim[rid]; pr.second->onWrite(rtp, false);
if (!src) { }
const auto& stream = _push_src->getMediaTuple().stream; } else {
auto src_imp = _push_src->clone(rid.empty() ? stream : stream + '_' + rid); // 视频 [AUTO-TRANSLATED:904730ac]
_push_src_sim_ownership[rid] = src_imp->getOwnership(); // Video
src_imp->setListener(static_pointer_cast<WebRtcPusher>(shared_from_this())); std::lock_guard<std::recursive_mutex> lock(_mtx);
src = src_imp; auto &src = _push_src_sim[rid];
} if (!src) {
src->onWrite(std::move(rtp), false); 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<WebRtcPusher>(shared_from_this()));
void WebRtcPusher::onStartWebRTC() { src = src_imp;
WebRtcTransportImp::onStartWebRTC(); }
_simulcast = _answer_sdp->supportSimulcast(); src->onWrite(std::move(rtp), false);
if (canRecvRtp()) { }
_push_src->setSdp(_answer_sdp->toRtspSdp()); }
}
} void WebRtcPusher::onStartWebRTC() {
WebRtcTransportImp::onStartWebRTC();
void WebRtcPusher::onDestory() { _simulcast = _answer_sdp->supportSimulcast();
auto duration = getDuration(); if (canRecvRtp()) {
auto bytes_usage = getBytesUsage(); _push_src->setSdp(_answer_sdp->toRtspSdp());
// 流量统计事件广播 [AUTO-TRANSLATED:6b0b1234] }
// Traffic statistics event broadcast }
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
void WebRtcPusher::onDestory() {
if (getSession()) { auto duration = getDuration();
WarnL << "RTC推流器(" << _media_info.shortUrl() << ")结束推流,耗时(s):" << duration; auto bytes_usage = getBytesUsage();
if (bytes_usage >= iFlowThreshold * 1024) { // 流量统计事件广播 [AUTO-TRANSLATED:6b0b1234]
NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, bytes_usage, duration, false, *getSession()); // Traffic statistics event broadcast
} GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
}
if (getSession()) {
if (_push_src && _continue_push_ms) { WarnL << "RTC推流器(" << _media_info.shortUrl() << ")结束推流,耗时(s):" << duration;
// 取消所有权 [AUTO-TRANSLATED:4895d8fa] if (bytes_usage >= iFlowThreshold * 1024) {
// Cancel ownership NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, bytes_usage, duration, false, *getSession());
_push_src_ownership = nullptr; }
// 延时10秒注销流 [AUTO-TRANSLATED:e1bb11f9] }
// Delay 10 seconds to log out the stream
auto push_src = std::move(_push_src); if (_push_src && _continue_push_ms) {
getPoller()->doDelayTask(_continue_push_ms, [push_src]() { return 0; }); // 取消所有权 [AUTO-TRANSLATED:4895d8fa]
} // Cancel ownership
WebRtcTransportImp::onDestory(); _push_src_ownership = nullptr;
} // 延时10秒注销流 [AUTO-TRANSLATED:e1bb11f9]
// Delay 10 seconds to log out the stream
void WebRtcPusher::onRtcConfigure(RtcConfigure &configure) const { auto push_src = std::move(_push_src);
WebRtcTransportImp::onRtcConfigure(configure); getPoller()->doDelayTask(_continue_push_ms, [push_src]() { return 0; });
// 这只是推流 [AUTO-TRANSLATED:f877bf98] }
// This is just pushing the stream WebRtcTransportImp::onDestory();
configure.audio.direction = configure.video.direction = RtpDirection::recvonly; }
}
void WebRtcPusher::onRtcConfigure(RtcConfigure &configure) const {
float WebRtcPusher::getLossRate(MediaSource &sender,TrackType type) { WebRtcTransportImp::onRtcConfigure(configure);
return WebRtcTransportImp::getLossRate(type); // 这只是推流 [AUTO-TRANSLATED:f877bf98]
} // This is just pushing the stream
configure.audio.direction = configure.video.direction = RtpDirection::recvonly;
void WebRtcPusher::OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) { }
// 主动关闭推流,那么不等待重推 [AUTO-TRANSLATED:1ff514d7]
// Actively close the stream, then do not wait for re-pushing float WebRtcPusher::getLossRate(MediaSource &sender,TrackType type) {
_push_src = nullptr; return WebRtcTransportImp::getLossRate(type);
WebRtcTransportImp::OnDtlsTransportClosed(dtlsTransport); }
}
void WebRtcPusher::OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) {
void WebRtcPusher::onRtcpBye() { // 主动关闭推流,那么不等待重推 [AUTO-TRANSLATED:1ff514d7]
WebRtcTransportImp::onRtcpBye(); // Actively close the stream, then do not wait for re-pushing
} _push_src = nullptr;
WebRtcTransportImp::OnDtlsTransportClosed(dtlsTransport);
void WebRtcPusher::onShutdown(const SockException &ex) { }
_push_src = nullptr;
WebRtcTransportImp::onShutdown(ex); void WebRtcPusher::onRtcpBye() {
} WebRtcTransportImp::onRtcpBye();
}
}// namespace mediakit
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<RtspDemuxer>();
}
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<Track::Ptr> 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

View File

@ -1,87 +1,113 @@
/* /*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
* *
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). * 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 * 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 * 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. * may be found in the AUTHORS file in the root of the source tree.
*/ */
#ifndef ZLMEDIAKIT_WEBRTCPUSHER_H #ifndef ZLMEDIAKIT_WEBRTCPUSHER_H
#define ZLMEDIAKIT_WEBRTCPUSHER_H #define ZLMEDIAKIT_WEBRTCPUSHER_H
#include "WebRtcTransport.h" #include "WebRtcTransport.h"
#include "Rtsp/RtspMediaSource.h" #include "Rtsp/RtspDemuxer.h"
#include "Rtsp/RtspMediaSource.h"
namespace mediakit {
namespace mediakit {
class WebRtcPusher : public WebRtcTransportImp, public MediaSourceEvent {
public: class WebRtcPusher : public WebRtcTransportImp, public MediaSourceEvent {
using Ptr = std::shared_ptr<WebRtcPusher>; public:
static Ptr create(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, using Ptr = std::shared_ptr<WebRtcPusher>;
const std::shared_ptr<void> &ownership, const MediaInfo &info, const ProtocolOption &option); static Ptr create(const toolkit::EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src,
const std::shared_ptr<void> &ownership, const MediaInfo &info, const ProtocolOption &option,
protected: WebRtcTransport::Role role, WebRtcTransport::SignalingProtocols signaling_protocols);
///////WebRtcTransportImp override///////
void onStartWebRTC() override; protected:
void onDestory() override; ///////WebRtcTransportImp override///////
void onRtcConfigure(RtcConfigure &configure) const override; void onStartWebRTC() override;
void onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) override; void onDestory() override;
void onShutdown(const SockException &ex) override; void onRtcConfigure(RtcConfigure &configure) const override;
void onRtcpBye() override; void onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) override;
// // dtls相关的回调 //// [AUTO-TRANSLATED:31a1f32c] void onShutdown(const toolkit::SockException &ex) override;
// // dtls related callbacks //// void onRtcpBye() override;
void OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) override; // // dtls相关的回调 //// [AUTO-TRANSLATED:31a1f32c]
// // dtls related callbacks ////
protected: void OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) override;
///////MediaSourceEvent override///////
// 关闭 [AUTO-TRANSLATED:92392f02] protected:
// Close ///////MediaSourceEvent override///////
bool close(MediaSource &sender) override; // 关闭 [AUTO-TRANSLATED:92392f02]
// 播放总人数 [AUTO-TRANSLATED:c42a3161] // Close
// Total number of players bool close(MediaSource &sender) override;
int totalReaderCount(MediaSource &sender) override; // 播放总人数 [AUTO-TRANSLATED:c42a3161]
// 获取媒体源类型 [AUTO-TRANSLATED:34290a69] // Total number of players
// Get media source type int totalReaderCount(MediaSource &sender) override;
MediaOriginType getOriginType(MediaSource &sender) const override; // 获取媒体源类型 [AUTO-TRANSLATED:34290a69]
// 获取媒体源url或者文件路径 [AUTO-TRANSLATED:fa34d795] // Get media source type
// Get media source url or file path MediaOriginType getOriginType(MediaSource &sender) const override;
std::string getOriginUrl(MediaSource &sender) const override; // 获取媒体源url或者文件路径 [AUTO-TRANSLATED:fa34d795]
// 获取媒体源客户端相关信息 [AUTO-TRANSLATED:037ef910] // Get media source url or file path
// Get media source client related information std::string getOriginUrl(MediaSource &sender) const override;
std::shared_ptr<SockInfo> getOriginSock(MediaSource &sender) const override; // 获取媒体源客户端相关信息 [AUTO-TRANSLATED:037ef910]
// 由于支持断连续推存在OwnerPoller变更的可能 [AUTO-TRANSLATED:1c863b40] // Get media source client related information
// Due to support for discontinuous pushing, there is a possibility of OwnerPoller changes std::shared_ptr<toolkit::SockInfo> getOriginSock(MediaSource &sender) const override;
toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override; // 由于支持断连续推存在OwnerPoller变更的可能 [AUTO-TRANSLATED:1c863b40]
// 获取丢包率 [AUTO-TRANSLATED:ec61b378] // Due to support for discontinuous pushing, there is a possibility of OwnerPoller changes
// Get packet loss rate toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override;
float getLossRate(MediaSource &sender,TrackType type) override; // 获取丢包率 [AUTO-TRANSLATED:ec61b378]
// Get packet loss rate
private: float getLossRate(MediaSource &sender,TrackType type) override;
WebRtcPusher(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src,
const std::shared_ptr<void> &ownership, const MediaInfo &info, const ProtocolOption &option); private:
WebRtcPusher(const toolkit::EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src,
private: const std::shared_ptr<void> &ownership, const MediaInfo &info, const ProtocolOption &option);
bool _simulcast = false;
// 断连续推延时 [AUTO-TRANSLATED:13ad578a] private:
// Discontinuous pushing delay bool _simulcast = false;
uint32_t _continue_push_ms = 0; // 断连续推延时 [AUTO-TRANSLATED:13ad578a]
// 媒体相关元数据 [AUTO-TRANSLATED:f4cf8045] // Discontinuous pushing delay
// Media related metadata uint32_t _continue_push_ms = 0;
MediaInfo _media_info; // 媒体相关元数据 [AUTO-TRANSLATED:f4cf8045]
// 推流的rtsp源 [AUTO-TRANSLATED:4f976bca] // Media related metadata
// Rtsp source of the stream MediaInfo _media_info;
RtspMediaSource::Ptr _push_src; // 推流的rtsp源 [AUTO-TRANSLATED:4f976bca]
// 推流所有权 [AUTO-TRANSLATED:d0ddf5c7] // Rtsp source of the stream
// Stream ownership RtspMediaSource::Ptr _push_src;
std::shared_ptr<void> _push_src_ownership; // 推流所有权 [AUTO-TRANSLATED:d0ddf5c7]
// 推流的rtsp源,支持simulcast [AUTO-TRANSLATED:44be9120] // Stream ownership
// Rtsp source of the stream, supports simulcast std::shared_ptr<void> _push_src_ownership;
std::recursive_mutex _mtx; // 推流的rtsp源,支持simulcast [AUTO-TRANSLATED:44be9120]
std::unordered_map<std::string/*rid*/, RtspMediaSource::Ptr> _push_src_sim; // Rtsp source of the stream, supports simulcast
std::unordered_map<std::string/*rid*/, std::shared_ptr<void> > _push_src_sim_ownership; std::recursive_mutex _mtx;
}; std::unordered_map<std::string/*rid*/, RtspMediaSource::Ptr> _push_src_sim;
std::unordered_map<std::string/*rid*/, std::shared_ptr<void> > _push_src_sim_ownership;
}// namespace mediakit };
#endif //ZLMEDIAKIT_WEBRTCPUSHER_H
class WebRtcPlayerClient : public WebRtcTransportImp {
public:
using Ptr = std::shared_ptr<WebRtcPlayerClient>;
static Ptr create(const toolkit::EventPoller::Ptr &poller, WebRtcTransport::Role role, WebRtcTransport::SignalingProtocols signaling_protocols);
void setMediaSource(RtspMediaSource::Ptr src);
std::vector<Track::Ptr> 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

View File

@ -1,166 +1,163 @@
/* /*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
* *
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). * 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 * 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 * 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. * may be found in the AUTHORS file in the root of the source tree.
*/ */
#include "WebRtcSession.h" #include "WebRtcSession.h"
#include "Util/util.h" #include "Util/util.h"
#include "Network/TcpServer.h" #include "Network/TcpServer.h"
#include "Common/config.h" #include "Common/config.h"
#include "IceServer.hpp" #include "IceTransport.hpp"
#include "WebRtcTransport.h" #include "WebRtcTransport.h"
using namespace std; using namespace std;
using namespace toolkit;
namespace mediakit {
namespace mediakit {
static string getUserName(const char *buf, size_t len) {
if (!RTC::StunPacket::IsStun((const uint8_t *) buf, len)) { static string getUserName(const char *buf, size_t len) {
return ""; if (!RTC::StunPacket::isStun((const uint8_t *) buf, len)) {
} return "";
std::unique_ptr<RTC::StunPacket> packet(RTC::StunPacket::Parse((const uint8_t *) buf, len)); }
if (!packet) { auto packet = RTC::StunPacket::parse((const uint8_t *) buf, len);
return ""; 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
// 收到binding request请求 [AUTO-TRANSLATED:eff4d773] auto vec = split(packet->getUsername(), ":");
// Received binding request return vec[0];
auto vec = split(packet->GetUsername(), ":"); }
return vec[0];
} EventPoller::Ptr WebRtcSession::queryPoller(const Buffer::Ptr &buffer) {
auto user_name = getUserName(buffer->data(), buffer->size());
EventPoller::Ptr WebRtcSession::queryPoller(const Buffer::Ptr &buffer) { if (user_name.empty()) {
auto user_name = getUserName(buffer->data(), buffer->size()); return nullptr;
if (user_name.empty()) { }
return nullptr; auto ret = WebRtcTransportManager::Instance().getItem(user_name);
} return ret ? ret->getPoller() : 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;
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<toolkit::TcpServer>(const_cast<Server &>(server).shared_from_this());
void WebRtcSession::attachServer(const Server &server) { }
_server = std::static_pointer_cast<toolkit::TcpServer>(const_cast<Server &>(server).shared_from_this());
} void WebRtcSession::onRecv_l(const char *data, size_t len) {
if (_find_transport) {
void WebRtcSession::onRecv_l(const char *data, size_t len) { // 只允许寻找一次transport [AUTO-TRANSLATED:446fae53]
if (_find_transport) { // Only allow searching for transport once
// 只允许寻找一次transport [AUTO-TRANSLATED:446fae53] _find_transport = false;
// Only allow searching for transport once auto user_name = getUserName(data, len);
_find_transport = false; auto transport = WebRtcTransportManager::Instance().getItem(user_name);
auto user_name = getUserName(data, len); CHECK(transport);
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
// WebRtcTransport在其他poller线程上需要切换poller线程并重新创建WebRtcSession对象 [AUTO-TRANSLATED:7e5534cf] if (!transport->getPoller()->isCurrentThread()) {
// WebRtcTransport is on another poller thread, need to switch poller thread and recreate WebRtcSession object auto sock = Socket::createSocket(transport->getPoller(), false);
if (!transport->getPoller()->isCurrentThread()) { // 1、克隆socket(fd不变)切换poller线程到WebRtcTransport所在线程 [AUTO-TRANSLATED:f930bfab]
auto sock = Socket::createSocket(transport->getPoller(), false); // 1. Clone socket (fd remains unchanged), switch poller thread to the thread where WebRtcTransport is located
// 1、克隆socket(fd不变)切换poller线程到WebRtcTransport所在线程 [AUTO-TRANSLATED:f930bfab] sock->cloneSocket(*(getSock()));
// 1. Clone socket (fd remains unchanged), switch poller thread to the thread where WebRtcTransport is located auto server = _server;
sock->cloneSocket(*(getSock())); std::string str(data, len);
auto server = _server; sock->getPoller()->async([sock, server, str](){
std::string str(data, len); auto strong_server = server.lock();
sock->getPoller()->async([sock, server, str](){ if (strong_server) {
auto strong_server = server.lock(); auto session = static_pointer_cast<WebRtcSession>(strong_server->createSession(sock));
if (strong_server) { // 2、创建新的WebRtcSession对象(绑定到WebRtcTransport所在线程)重新处理一遍ice binding request命令 [AUTO-TRANSLATED:c75203bb]
auto session = static_pointer_cast<WebRtcSession>(strong_server->createSession(sock)); // 2. Create a new WebRtcSession object (bound to the thread where WebRtcTransport is located), reprocess the ice binding request command
// 2、创建新的WebRtcSession对象(绑定到WebRtcTransport所在线程)重新处理一遍ice binding request命令 [AUTO-TRANSLATED:c75203bb] session->onRecv_l(str.data(), str.size());
// 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)
// 3、销毁原先的socket和WebRtcSession(原先的对象跟WebRtcTransport不在同一条线程) [AUTO-TRANSLATED:a6d6d63f] throw std::runtime_error("webrtc over tcp change poller: " + getPoller()->getThreadName() + " -> " + sock->getPoller()->getThreadName());
// 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);
_transport = std::move(transport); }
InfoP(this); _ticker.resetTime();
} CHECK(_transport);
_ticker.resetTime(); auto self = static_pointer_cast<WebRtcSession>(shared_from_this());
CHECK(_transport); _transport->inputSockData(data, len, self);
_transport->inputSockData((char *)data, len, this); }
}
void WebRtcSession::onRecv(const Buffer::Ptr &buffer) {
void WebRtcSession::onRecv(const Buffer::Ptr &buffer) { if (_over_tcp) {
if (_over_tcp) { input(buffer->data(), buffer->size());
input(buffer->data(), buffer->size()); } else {
} else { onRecv_l(buffer->data(), buffer->size());
onRecv_l(buffer->data(), buffer->size()); }
} }
}
void WebRtcSession::onError(const SockException &err) {
void WebRtcSession::onError(const SockException &err) { // udp链接超时但是rtc链接不一定超时因为可能存在链接迁移的情况 [AUTO-TRANSLATED:aaa9672f]
// udp链接超时但是rtc链接不一定超时因为可能存在链接迁移的情况 [AUTO-TRANSLATED:aaa9672f] // UDP connection timeout, but RTC connection may not timeout, because there may be connection migration
// UDP connection timeout, but RTC connection may not timeout, because there may be connection migration // 在udp链接迁移时新的WebRtcSession对象将接管WebRtcTransport对象的生命周期 [AUTO-TRANSLATED:7e7d19df]
// 在udp链接迁移时新的WebRtcSession对象将接管WebRtcTransport对象的生命周期 [AUTO-TRANSLATED:7e7d19df] // When UDP connection migrates, the new WebRtcSession object will take over the life cycle of the WebRtcTransport object
// When UDP connection migrates, the new WebRtcSession object will take over the life cycle of the WebRtcTransport object // 本WebRtcSession对象将在超时后自动销毁 [AUTO-TRANSLATED:bc903a06]
// 本WebRtcSession对象将在超时后自动销毁 [AUTO-TRANSLATED:bc903a06] // This WebRtcSession object will be automatically destroyed after timeout
// This WebRtcSession object will be automatically destroyed after timeout WarnP(this) << err;
WarnP(this) << err;
if (!_transport) {
if (!_transport) { return;
return; }
} auto self = static_pointer_cast<WebRtcSession>(shared_from_this());
auto self = static_pointer_cast<WebRtcSession>(shared_from_this()); auto transport = std::move(_transport);
auto transport = std::move(_transport); getPoller()->async([transport, self]() mutable {
getPoller()->async([transport, self]() mutable { // 延时减引用防止使用transport对象时销毁对象 [AUTO-TRANSLATED:09dd6609]
// 延时减引用防止使用transport对象时销毁对象 [AUTO-TRANSLATED:09dd6609] // Delay decrementing the reference count to prevent the object from being destroyed when using the transport object
// Delay decrementing the reference count to prevent the object from being destroyed when using the transport object transport->removePair(self.get());
transport->removeTuple(self.get()); // 确保transport在Session对象前销毁防止WebRtcTransport::onDestory()时获取不到Session对象 [AUTO-TRANSLATED:acd8bd77]
// 确保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
// 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;
transport = nullptr; }, false);
}, false); }
}
void WebRtcSession::onManager() {
void WebRtcSession::onManager() { GET_CONFIG(float, timeoutSec, Rtc::kTimeOutSec);
GET_CONFIG(float, timeoutSec, Rtc::kTimeOutSec); if (!_transport && _ticker.createdTime() > timeoutSec * 1000) {
if (!_transport && _ticker.createdTime() > timeoutSec * 1000) { shutdown(SockException(Err_timeout, "illegal webrtc connection"));
shutdown(SockException(Err_timeout, "illegal webrtc connection")); return;
return; }
} if (_ticker.elapsedTime() > timeoutSec * 1000) {
if (_ticker.elapsedTime() > timeoutSec * 1000) { shutdown(SockException(Err_timeout, "webrtc connection timeout"));
shutdown(SockException(Err_timeout, "webrtc connection timeout")); return;
return; }
} }
}
ssize_t WebRtcSession::onRecvHeader(const char *data, size_t len) {
ssize_t WebRtcSession::onRecvHeader(const char *data, size_t len) { onRecv_l(data + 2, len - 2);
onRecv_l(data + 2, len - 2); return 0;
return 0; }
}
const char *WebRtcSession::onSearchPacketTail(const char *data, size_t len) {
const char *WebRtcSession::onSearchPacketTail(const char *data, size_t len) { if (len < 2) {
if (len < 2) { // 数据不够 [AUTO-TRANSLATED:830a2785]
// 数据不够 [AUTO-TRANSLATED:830a2785] // Not enough data
// Not enough data return nullptr;
return nullptr; }
} uint16_t length = (((uint8_t *)data)[0] << 8) | ((uint8_t *)data)[1];
uint16_t length = (((uint8_t *)data)[0] << 8) | ((uint8_t *)data)[1]; if (len < (size_t)(length + 2)) {
if (len < (size_t)(length + 2)) { // 数据不够 [AUTO-TRANSLATED:830a2785]
// 数据不够 [AUTO-TRANSLATED:830a2785] // Not enough data
// Not enough data return nullptr;
return nullptr; }
} // 返回rtp包末尾 [AUTO-TRANSLATED:5134cf6f]
// 返回rtp包末尾 [AUTO-TRANSLATED:5134cf6f] // Return the end of the RTP packet
// Return the end of the RTP packet return data + 2 + length;
return data + 2 + length; }
}
}// namespace mediakit
}// namespace mediakit

View File

@ -21,18 +21,18 @@ namespace toolkit {
} }
namespace mediakit { namespace mediakit {
class WebRtcTransportImp; class WebRtcTransportImp;
using namespace toolkit;
class WebRtcSession : public Session, public HttpRequestSplitter { class WebRtcSession : public toolkit::Session, public HttpRequestSplitter {
public: public:
WebRtcSession(const Socket::Ptr &sock); WebRtcSession(const toolkit::Socket::Ptr &sock);
void attachServer(const Server &server) override; void attachServer(const toolkit::Server &server) override;
void onRecv(const Buffer::Ptr &) override; void onRecv(const toolkit::Buffer::Ptr &) override;
void onError(const SockException &err) override; void onError(const toolkit::SockException &err) override;
void onManager() override; void onManager() override;
static EventPoller::Ptr queryPoller(const Buffer::Ptr &buffer); static toolkit::EventPoller::Ptr queryPoller(const toolkit::Buffer::Ptr &buffer);
protected: protected:
WebRtcTransportImp::Ptr _transport; WebRtcTransportImp::Ptr _transport;
@ -47,7 +47,7 @@ private:
private: private:
bool _over_tcp = false; bool _over_tcp = false;
bool _find_transport = true; bool _find_transport = true;
Ticker _ticker; toolkit::Ticker _ticker;
std::weak_ptr<toolkit::TcpServer> _server; std::weak_ptr<toolkit::TcpServer> _server;
}; };

View File

@ -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

View File

@ -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<Json::Value>& 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

View File

@ -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<WebRtcSignalingPeer> 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<void(const SockException &ex, const string &key)> &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<void(const SockException &ex)> &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<void(const std::string& key, const WebRtcSignalingPeer::Ptr& p)> &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<TcpClient>(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<void(const SockException &ex, const string &key)> &cb) {
DebugL;
std::weak_ptr<WebRtcSignalingPeer> weak_self = std::static_pointer_cast<WebRtcSignalingPeer>(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<void(const SockException &ex)> &cb) {
DebugL;
auto trigger = [cb](const SockException &ex, std::string msg) { cb(ex); };
std::weak_ptr<WebRtcSignalingPeer> weak_self = std::static_pointer_cast<WebRtcSignalingPeer>(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<void(const SockException &ex, const std::string& answer)> &cb, float timeout_sec) {
DebugL;
std::weak_ptr<WebRtcSignalingPeer> weak_self = std::static_pointer_cast<WebRtcSignalingPeer>(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<WebRtcSignalingPeer> weak_self = std::static_pointer_cast<WebRtcSignalingPeer>(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<WebRtcSignalingPeer> weak_self = std::static_pointer_cast<WebRtcSignalingPeer>(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<WebRtcSignalingPeer> weak_self = std::static_pointer_cast<WebRtcSignalingPeer>(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<void(const SockException &ex)> 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<void(const SockException &ex)> 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<decltype(args)> allArgs(parser, args);
CHECK_ARGS(METHOD_KEY, TRANSACTION_ID_KEY);
using MsgHandler = void (WebRtcSignalingPeer::*)(SIGNALING_MSG_ARGS);
static std::unordered_map<std::pair<std::string /*class*/, std::string /*method*/>, 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<RTC::IceServerInfo>(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<WebRtcArgsImp<Json::Value>>(allArgs, allArgs[GUEST_ID_KEY]);
std::weak_ptr<WebRtcSignalingPeer> weak_self = std::static_pointer_cast<WebRtcSignalingPeer>(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<WebRtcInterface &>(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<WebRtcSignalingPeer> weak_self = std::static_pointer_cast<WebRtcSignalingPeer>(shared_from_this());
_expire_timer = std::make_shared<Timer>(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<Timer>(
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

View File

@ -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 <chrono>
#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<toolkit::TcpClient> {
public:
struct ClassMethodHash {
bool operator()(std::pair<std::string /*class*/, std::string /*method*/> key) const {
std::size_t h = 0;
h ^= std::hash<std::string>()(key.first) << 0;
h ^= std::hash<std::string>()(key.second) << 1;
return h;
}
};
using Ptr = std::shared_ptr<WebRtcSignalingPeer>;
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<void(const toolkit::SockException &ex, const std::string &key)> &cb);
void unregist(const std::function<void(const toolkit::SockException &ex)> &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<void(const toolkit::SockException &ex, const std::string& answer)> &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<void(const toolkit::SockException &ex)> cb);
void onConnect(const toolkit::SockException &ex) override;
void setOnShutdown(std::function<void(const toolkit::SockException &ex)> 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<void(const toolkit::SockException &ex, std::string /*msg*/)>;
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<std::string /*peer_guest_id*/, std::string /*transport_identifier*/> _peer_guests; //作为被叫
std::unordered_map<std::string /*peer_room_id*/, std::pair<std::string /*guest_id*/, std::string /*transport_identifier*/>> _tours; //作为主叫
RTC::IceServerInfo::Ptr _ice_server;
std::unordered_map<std::string /*transcation ID*/, ResponseTuple> _response_list;
std::function<void(const toolkit::SockException &ex)> _on_connect;
std::function<void(const toolkit::SockException &ex)> _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<void(const toolkit::SockException &ex, const std::string &key)> &cb);
void delWebrtcRoomKeeper(const std::string &key, const std::function<void(const toolkit::SockException &ex)> &cb);
void listWebrtcRoomKeepers(const std::function<void(const std::string& key, const WebRtcSignalingPeer::Ptr& p)> &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

View File

@ -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<uint32_t> s_room_idx_generate { 1 };
static ServiceController<WebRtcSignalingSession> s_rooms;
void listWebrtcRooms(const std::function<void(const std::string &key, const WebRtcSignalingSession::Ptr &p)> &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<decltype(args)> allArgs(parser, args);
using MsgHandler = void (WebRtcSignalingSession::*)(SIGNALING_MSG_ARGS);
static std::unordered_map<std::pair<std::string /*class*/, std::string /*method*/>, 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<WebRtcSignalingSession> sender_ptr = static_pointer_cast<WebRtcSignalingSession>(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<WebRtcSignalingSession>(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<WebRtcSignalingSession>(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<WebRtcSignalingSession>(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<WebRtcSignalingSession>(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<WebRtcSignalingSession>(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<std::string>, extern_ips, Rtc::kExternIP, [](string str) {
std::vector<std::string> 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

View File

@ -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<std::string /*class*/, std::string /*method*/> key) const {
std::size_t h = 0;
h ^= std::hash<std::string>()(key.first) << 0;
h ^= std::hash<std::string>()(key.second) << 1;
return h;
}
};
using Ptr = std::shared_ptr<WebRtcSignalingSession>;
using WeakPtr = std::weak_ptr<WebRtcSignalingSession>;
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<std::string /*guest id*/, std::string /*peer_room_id*/> _tours; //作为主叫
std::unordered_map<std::string /*peer_guest_id*/, WebRtcSignalingSession::WeakPtr /*session*/> _guests; //作为被叫
};
using WebRtcWebcosktSignalingSession = WebSocketSession<WebRtcSignalingSession, HttpSession>;
using WebRtcWebcosktSignalSslSession = WebSocketSession<WebRtcSignalingSession, HttpsSession>;
void listWebrtcRooms(const std::function<void(const std::string& key, const WebRtcSignalingSession::Ptr& p)> &cb);
Json::Value ToJson(const WebRtcSignalingSession::Ptr& p);
WebRtcSignalingSession::Ptr getWebrtcRoomKeeper(const std::string &room_id);
}// namespace mediakit
#endif //ZLMEDIAKIT_WEBRTC_SIGNALING_SESSION_H

File diff suppressed because it is too large Load Diff

View File

@ -1,400 +1,449 @@
/* /*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved. * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
* *
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). * 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 * 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 * 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. * may be found in the AUTHORS file in the root of the source tree.
*/ */
#pragma once #ifndef ZLMEDIAKIT_WEBRTC_TRANSPORT_H
#define ZLMEDIAKIT_WEBRTC_TRANSPORT_H
#include <memory>
#include <string> #include <memory>
#include "DtlsTransport.hpp" #include <string>
#include "IceServer.hpp" #include <functional>
#include "SrtpSession.hpp" #include "DtlsTransport.hpp"
#include "StunPacket.hpp" #include "IceTransport.hpp"
#include "Sdp.h" #include "SrtpSession.hpp"
#include "Util/mini.h" #include "StunPacket.hpp"
#include "Poller/EventPoller.h" #include "Sdp.h"
#include "Network/Socket.h" #include "Util/mini.h"
#include "Network/Session.h" #include "Poller/EventPoller.h"
#include "Nack.h" #include "Network/Socket.h"
#include "TwccContext.h" #include "Network/Session.h"
#include "SctpAssociation.hpp" #include "Nack.h"
#include "Rtcp/RtcpContext.h" #include "TwccContext.h"
#include "SctpAssociation.hpp"
namespace mediakit { #include "Rtcp/RtcpContext.h"
#include "Rtsp/RtspMediaSource.h"
// RTC配置项目 [AUTO-TRANSLATED:65784416]
// RTC configuration project using namespace RTC;
namespace Rtc { namespace mediakit {
extern const std::string kPort;
extern const std::string kTcpPort; // ICE transport policy enum
extern const std::string kTimeOutSec; enum class IceTransportPolicy {
}//namespace RTC kAll = 0, // 不限制,支持所有连接类型(默认)
kRelayOnly = 1, // 仅支持Relay转发
class WebRtcInterface { kP2POnly = 2 // 仅支持P2P直连
public: };
virtual ~WebRtcInterface() = default;
virtual std::string getAnswerSdp(const std::string &offer) = 0; // RTC配置项目 [AUTO-TRANSLATED:65784416]
virtual const std::string& getIdentifier() const = 0; // RTC configuration project
virtual const std::string& deleteRandStr() const { static std::string s_null; return s_null; } namespace Rtc {
virtual void setIceCandidate(std::vector<SdpAttrCandidate> cands) {} extern const std::string kPort;
virtual void setLocalIp(std::string localIp) {} extern const std::string kTcpPort;
virtual void setPreferredTcp(bool flag) {} extern const std::string kTimeOutSec;
}; extern const std::string kSignalingPort;
extern const std::string kSignalingSslPort;
class WebRtcException : public WebRtcInterface { extern const std::string kIcePort;
public: extern const std::string kIceTcpPort;
WebRtcException(const SockException &ex) : _ex(ex) {}; extern const std::string kEnableTurn;
std::string getAnswerSdp(const std::string &offer) override { extern const std::string kIceTransportPolicy;
throw _ex; extern const std::string kIceUfrag;
} extern const std::string kIcePwd;
const std::string &getIdentifier() const override { extern const std::string kExternIP;
static std::string s_null; extern const std::string kInterfaces;
return s_null; }//namespace RTC
}
class WebRtcInterface {
private: public:
SockException _ex; virtual ~WebRtcInterface() = default;
}; virtual std::string getAnswerSdp(const std::string &offer) = 0;
virtual std::string createOfferSdp() = 0;
class WebRtcTransport : public WebRtcInterface, public RTC::DtlsTransport::Listener, public RTC::IceServer::Listener, public std::enable_shared_from_this<WebRtcTransport> virtual void setAnswerSdp(const std::string &answer) = 0;
#ifdef ENABLE_SCTP virtual const std::string& getIdentifier() const = 0;
, public RTC::SctpAssociation::Listener virtual const std::string& deleteRandStr() const { static std::string s_null; return s_null; }
#endif virtual void setIceCandidate(std::vector<SdpAttrCandidate> cands) {}
{ virtual void setLocalIp(std::string localIp) {}
public: virtual void setPreferredTcp(bool flag) {}
using Ptr = std::shared_ptr<WebRtcTransport>;
WebRtcTransport(const EventPoller::Ptr &poller); using onGatheringCandidateCB = std::function<void(const std::string& transport_identifier, const std::string& candidate, const std::string& ufrag, const std::string& pwd)>;
virtual void gatheringCandidate(IceServerInfo::Ptr ice_server, onGatheringCandidateCB cb = nullptr) = 0;
/** };
*
* Create object class WebRtcException : public WebRtcInterface {
public:
* [AUTO-TRANSLATED:830344e4] WebRtcException(const toolkit::SockException &ex) : _ex(ex) {};
*/
virtual void onCreate(); std::string createOfferSdp() override {
throw _ex;
/** }
*
* Destroy object std::string getAnswerSdp(const std::string &offer) override {
throw _ex;
* [AUTO-TRANSLATED:1016b97b] }
*/
virtual void onDestory(); void setAnswerSdp(const std::string &answer) override {
throw _ex;
/** }
* webrtc answer sdp
* @param offer offer sdp void gatheringCandidate(IceServerInfo::Ptr ice_server, onGatheringCandidateCB cb = nullptr) override {
* @return answer sdp throw _ex;
* Create webrtc answer sdp }
* @param offer offer sdp
* @return answer sdp const std::string &getIdentifier() const override {
static std::string s_null;
* [AUTO-TRANSLATED:d9b027d7] return s_null;
*/ }
std::string getAnswerSdp(const std::string &offer) override final;
private:
/** toolkit::SockException _ex;
* id };
* Get object unique id
class WebRtcTransport : public WebRtcInterface, public RTC::DtlsTransport::Listener, public IceTransport::Listener, public std::enable_shared_from_this<WebRtcTransport>
* [AUTO-TRANSLATED:9ad519c6] #ifdef ENABLE_SCTP
*/ , public RTC::SctpAssociation::Listener
const std::string& getIdentifier() const override; #endif
const std::string& deleteRandStr() const override; {
public:
/** enum class Role {
* socket收到udp数据 NONE = 0,
* @param buf CLIENT,
* @param len PEER,
* @param tuple };
* Socket receives udp data static const char* RoleStr(Role role);
* @param buf data pointer
* @param len data length enum class SignalingProtocols {
* @param tuple data source Invalid = -1,
WHEP_WHIP = 0,
* [AUTO-TRANSLATED:1ee86069] WEBSOCKET = 1, //FOR P2P
*/ };
void inputSockData(char *buf, int len, RTC::TransportTuple *tuple); static const char* SignalingProtocolsStr(SignalingProtocols protocol);
/** using WeakPtr = std::weak_ptr<WebRtcTransport>;
* rtp using Ptr = std::shared_ptr<WebRtcTransport>;
* @param buf rtcp内容 WebRtcTransport(const toolkit::EventPoller::Ptr &poller);
* @param len rtcp长度
* @param flush flush socket virtual void onCreate();
* @param ctx
* Send rtp virtual void onDestory();
* @param buf rtcp content
* @param len rtcp length std::string getAnswerSdp(const std::string &offer) override;
* @param flush whether to flush socket void setAnswerSdp(const std::string &answer) override;
* @param ctx user pointer
const RtcSession::Ptr& answerSdp() const {
* [AUTO-TRANSLATED:aa833695] return _answer_sdp;
*/ }
void sendRtpPacket(const char *buf, int len, bool flush, void *ctx = nullptr);
void sendRtcpPacket(const char *buf, int len, bool flush, void *ctx = nullptr); std::string createOfferSdp() override;
void sendDatachannel(uint16_t streamId, uint32_t ppid, const char *msg, size_t len);
const std::string& getIdentifier() const override;
const EventPoller::Ptr& getPoller() const; const std::string& deleteRandStr() const override;
Session::Ptr getSession() const;
void inputSockData(const char *buf, int len, const toolkit::SocketHelper::Ptr& socket, struct sockaddr *addr = nullptr, int addr_len = 0);
protected: void inputSockData(const char *buf, int len, const IceTransport::Pair::Ptr& pair = nullptr);
// // dtls相关的回调 //// [AUTO-TRANSLATED:31a1f32c] void sendRtpPacket(const char *buf, int len, bool flush, void *ctx = nullptr);
// // dtls related callbacks //// void sendRtcpPacket(const char *buf, int len, bool flush, void *ctx = nullptr);
void OnDtlsTransportConnecting(const RTC::DtlsTransport *dtlsTransport) override; void sendDatachannel(uint16_t streamId, uint32_t ppid, const char *msg, size_t len);
void OnDtlsTransportConnected(const RTC::DtlsTransport *dtlsTransport,
RTC::SrtpSession::CryptoSuite srtpCryptoSuite, const toolkit::EventPoller::Ptr &getPoller() const { return _poller; }
uint8_t *srtpLocalKey, void setPoller(toolkit::EventPoller::Ptr poller) { _poller = std::move(poller); }
size_t srtpLocalKeyLen,
uint8_t *srtpRemoteKey, toolkit::Session::Ptr getSession() const;
size_t srtpRemoteKeyLen, void removePair(const toolkit::SocketHelper *socket);
std::string &remoteCert) override;
Role getRole() const { return _role; }
void OnDtlsTransportFailed(const RTC::DtlsTransport *dtlsTransport) override; void setRole(Role role) { _role = role; }
void OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) override;
void OnDtlsTransportSendData(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) override; SignalingProtocols getSignalingProtocols() const { return _signaling_protocols; }
void OnDtlsTransportApplicationDataReceived(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) override; void setSignalingProtocols(SignalingProtocols signaling_protocols) { _signaling_protocols = signaling_protocols; }
protected: float getTimeOutSec();
// // ice相关的回调 /// [AUTO-TRANSLATED:30abf693]
// // ice related callbacks /// void getTransportInfo(const std::function<void(Json::Value)> &callback) const;
void OnIceServerSendStunPacket(const RTC::IceServer *iceServer, const RTC::StunPacket *packet, RTC::TransportTuple *tuple) override;
void OnIceServerConnected(const RTC::IceServer *iceServer) override; void setOnShutdown(std::function<void(const toolkit::SockException &ex)> cb);
void OnIceServerCompleted(const RTC::IceServer *iceServer) override;
void OnIceServerDisconnected(const RTC::IceServer *iceServer) override; void gatheringCandidate(IceServerInfo::Ptr ice_server, onGatheringCandidateCB cb = nullptr) override;
void connectivityCheck(SdpAttrCandidate candidate_attr, const std::string &ufrag, const std::string &pwd);
#ifdef ENABLE_SCTP void connectivityCheckForSFU();
void OnSctpAssociationConnecting(RTC::SctpAssociation* sctpAssociation) override;
void OnSctpAssociationConnected(RTC::SctpAssociation* sctpAssociation) override; void setOnStartWebRTC(std::function<void()> on_start);
void OnSctpAssociationFailed(RTC::SctpAssociation* sctpAssociation) override;
void OnSctpAssociationClosed(RTC::SctpAssociation* sctpAssociation) override; protected:
void OnSctpAssociationSendData(RTC::SctpAssociation* sctpAssociation, const uint8_t* data, size_t len) override; // DtlsTransport::Listener; dtls相关的回调
void OnSctpAssociationMessageReceived(RTC::SctpAssociation *sctpAssociation, uint16_t streamId, uint32_t ppid, void OnDtlsTransportConnecting(const RTC::DtlsTransport *dtlsTransport) override;
const uint8_t *msg, size_t len) override; void OnDtlsTransportConnected(const RTC::DtlsTransport *dtlsTransport,
#endif RTC::SrtpSession::CryptoSuite srtpCryptoSuite,
uint8_t *srtpLocalKey,
protected: size_t srtpLocalKeyLen,
virtual void onStartWebRTC() = 0; uint8_t *srtpRemoteKey,
virtual void onRtcConfigure(RtcConfigure &configure) const; size_t srtpRemoteKeyLen,
virtual void onCheckSdp(SdpType type, RtcSession &sdp) = 0; std::string &remoteCert) override;
virtual void onSendSockData(Buffer::Ptr buf, bool flush = true, RTC::TransportTuple *tuple = nullptr) = 0; void OnDtlsTransportFailed(const RTC::DtlsTransport *dtlsTransport) override;
void OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) override;
virtual void onRtp(const char *buf, size_t len, uint64_t stamp_ms) = 0; void OnDtlsTransportSendData(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) override;
virtual void onRtcp(const char *buf, size_t len) = 0; void OnDtlsTransportApplicationDataReceived(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) override;
virtual void onShutdown(const SockException &ex) = 0;
virtual void onBeforeEncryptRtp(const char *buf, int &len, void *ctx) = 0; protected:
virtual void onBeforeEncryptRtcp(const char *buf, int &len, void *ctx) = 0; // ice相关的回调; IceTransport::Listener.
virtual void onRtcpBye() = 0; void onIceTransportRecvData(const toolkit::Buffer::Ptr& buffer, const IceTransport::Pair::Ptr& pair) override;
void onIceTransportGatheringCandidate(const IceTransport::Pair::Ptr& pair, const CandidateInfo& candidate) override;
protected: void onIceTransportCompleted() override;
void sendRtcpRemb(uint32_t ssrc, size_t bit_rate); void onIceTransportDisconnected() override;
void sendRtcpPli(uint32_t ssrc);
// SctpAssociation::Listener
private: #ifdef ENABLE_SCTP
void sendSockData(const char *buf, size_t len, RTC::TransportTuple *tuple); void OnSctpAssociationConnecting(RTC::SctpAssociation* sctpAssociation) override;
void setRemoteDtlsFingerprint(const RtcSession &remote); void OnSctpAssociationConnected(RTC::SctpAssociation* sctpAssociation) override;
void OnSctpAssociationFailed(RTC::SctpAssociation* sctpAssociation) override;
protected: void OnSctpAssociationClosed(RTC::SctpAssociation* sctpAssociation) override;
RtcSession::Ptr _offer_sdp; void OnSctpAssociationSendData(RTC::SctpAssociation* sctpAssociation, const uint8_t* data, size_t len) override;
RtcSession::Ptr _answer_sdp; void OnSctpAssociationMessageReceived(RTC::SctpAssociation *sctpAssociation, uint16_t streamId, uint32_t ppid,
std::shared_ptr<RTC::IceServer> _ice_server; const uint8_t *msg, size_t len) override;
#endif
private:
mutable std::string _delete_rand_str; protected:
std::string _identifier; virtual void onStartWebRTC() = 0;
EventPoller::Ptr _poller; virtual void onRtcConfigure(RtcConfigure &configure) const;
std::shared_ptr<RTC::DtlsTransport> _dtls_transport; virtual void onCheckSdp(SdpType type, RtcSession &sdp) = 0;
std::shared_ptr<RTC::SrtpSession> _srtp_session_send; virtual void onSendSockData(toolkit::Buffer::Ptr buf, bool flush = true, const IceTransport::Pair::Ptr& pair = nullptr) = 0;
std::shared_ptr<RTC::SrtpSession> _srtp_session_recv;
Ticker _ticker; virtual void onRtp(const char *buf, size_t len, uint64_t stamp_ms) = 0;
// 循环池 [AUTO-TRANSLATED:b7059f37] virtual void onRtcp(const char *buf, size_t len) = 0;
// Cycle pool virtual void onShutdown(const toolkit::SockException &ex);
ResourcePool<BufferRaw> _packet_pool; virtual void onBeforeEncryptRtp(const char *buf, int &len, void *ctx) = 0;
virtual void onBeforeEncryptRtcp(const char *buf, int &len, void *ctx) = 0;
#ifdef ENABLE_SCTP virtual void onRtcpBye() = 0;
RTC::SctpAssociationImp::Ptr _sctp;
#endif protected:
}; void sendRtcpRemb(uint32_t ssrc, size_t bit_rate);
void sendRtcpPli(uint32_t ssrc);
class RtpChannel;
class MediaTrack { private:
public: void sendSockData(const char *buf, size_t len, const IceTransport::Pair::Ptr& pair = nullptr);
using Ptr = std::shared_ptr<MediaTrack>; void setRemoteDtlsFingerprint(SdpType type, const RtcSession &remote);
const RtcCodecPlan *plan_rtp;
const RtcCodecPlan *plan_rtx; protected:
uint32_t offer_ssrc_rtp = 0; SignalingProtocols _signaling_protocols = SignalingProtocols::WHEP_WHIP;
uint32_t offer_ssrc_rtx = 0; Role _role = Role::PEER;
uint32_t answer_ssrc_rtp = 0; RtcSession::Ptr _offer_sdp;
uint32_t answer_ssrc_rtx = 0; RtcSession::Ptr _answer_sdp;
const RtcMedia *media;
RtpExtContext::Ptr rtp_ext_ctx; IceAgent::Ptr _ice_agent;
onGatheringCandidateCB _on_gathering_candidate = nullptr;
//for send rtp
NackList nack_list; private:
RtcpContext::Ptr rtcp_context_send; mutable std::string _delete_rand_str;
std::string _identifier;
//for recv rtp toolkit::EventPoller::Ptr _poller;
std::unordered_map<std::string/*rid*/, std::shared_ptr<RtpChannel> > rtp_channel; DtlsTransport::Ptr _dtls_transport;
std::shared_ptr<RtpChannel> getRtpChannel(uint32_t ssrc) const; SrtpSession::Ptr _srtp_session_send;
}; SrtpSession::Ptr _srtp_session_recv;
toolkit::Ticker _ticker;
struct WrappedMediaTrack { // 循环池 [AUTO-TRANSLATED:b7059f37]
MediaTrack::Ptr track; // Cycle pool
explicit WrappedMediaTrack(MediaTrack::Ptr ptr): track(ptr) {} toolkit::ResourcePool<toolkit::BufferRaw> _packet_pool;
virtual ~WrappedMediaTrack() {}
virtual void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) = 0; //超时功能实现
}; toolkit::Ticker _recv_ticker;
std::shared_ptr<toolkit::Timer> _check_timer;
struct WrappedRtxTrack: public WrappedMediaTrack { std::function<void()> _on_start;
explicit WrappedRtxTrack(MediaTrack::Ptr ptr) std::function<void(const toolkit::SockException &ex)> _on_shutdown;
: WrappedMediaTrack(std::move(ptr)) {}
void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) override; #ifdef ENABLE_SCTP
}; RTC::SctpAssociationImp::Ptr _sctp;
#endif
class WebRtcTransportImp; };
struct WrappedRtpTrack : public WrappedMediaTrack { class RtpChannel;
explicit WrappedRtpTrack(MediaTrack::Ptr ptr, TwccContext& twcc, WebRtcTransportImp& t) class MediaTrack {
: WrappedMediaTrack(std::move(ptr)) public:
, _twcc_ctx(twcc) using Ptr = std::shared_ptr<MediaTrack>;
, _transport(t) {} const RtcCodecPlan *plan_rtp;
TwccContext& _twcc_ctx; const RtcCodecPlan *plan_rtx;
WebRtcTransportImp& _transport; uint32_t offer_ssrc_rtp = 0;
void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) override; uint32_t offer_ssrc_rtx = 0;
}; uint32_t answer_ssrc_rtp = 0;
uint32_t answer_ssrc_rtx = 0;
class WebRtcTransportImp : public WebRtcTransport { const RtcMedia *media;
public: RtpExtContext::Ptr rtp_ext_ctx;
using Ptr = std::shared_ptr<WebRtcTransportImp>;
~WebRtcTransportImp() override; //for send rtp
NackList nack_list;
uint64_t getBytesUsage() const; RtcpContext::Ptr rtcp_context_send;
uint64_t getDuration() const;
bool canSendRtp() const; //for recv rtp
bool canRecvRtp() const; std::unordered_map<std::string/*rid*/, std::shared_ptr<RtpChannel> > rtp_channel;
void onSendRtp(const RtpPacket::Ptr &rtp, bool flush, bool rtx = false); std::shared_ptr<RtpChannel> getRtpChannel(uint32_t ssrc) const;
};
void createRtpChannel(const std::string &rid, uint32_t ssrc, MediaTrack &track);
void removeTuple(RTC::TransportTuple* tuple); struct WrappedMediaTrack {
void safeShutdown(const SockException &ex); MediaTrack::Ptr track;
explicit WrappedMediaTrack(MediaTrack::Ptr ptr): track(ptr) {}
void setPreferredTcp(bool flag) override; virtual ~WrappedMediaTrack() {}
void setLocalIp(std::string local_ip) override; virtual void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) = 0;
void setIceCandidate(std::vector<SdpAttrCandidate> cands) override; };
protected: struct WrappedRtxTrack: public WrappedMediaTrack {
void OnIceServerSelectedTuple(const RTC::IceServer *iceServer, RTC::TransportTuple *tuple) override; explicit WrappedRtxTrack(MediaTrack::Ptr ptr)
WebRtcTransportImp(const EventPoller::Ptr &poller); : WrappedMediaTrack(std::move(ptr)) {}
void OnDtlsTransportApplicationDataReceived(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) override; void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) override;
void onStartWebRTC() override; };
void onSendSockData(Buffer::Ptr buf, bool flush = true, RTC::TransportTuple *tuple = nullptr) override;
void onCheckSdp(SdpType type, RtcSession &sdp) override; class WebRtcTransportImp;
void onRtcConfigure(RtcConfigure &configure) const override;
struct WrappedRtpTrack : public WrappedMediaTrack {
void onRtp(const char *buf, size_t len, uint64_t stamp_ms) override; explicit WrappedRtpTrack(MediaTrack::Ptr ptr, TwccContext& twcc, WebRtcTransportImp& t)
void onRtcp(const char *buf, size_t len) override; : WrappedMediaTrack(std::move(ptr))
void onBeforeEncryptRtp(const char *buf, int &len, void *ctx) override; , _twcc_ctx(twcc)
void onBeforeEncryptRtcp(const char *buf, int &len, void *ctx) override {}; , _transport(t) {}
void onCreate() override; TwccContext& _twcc_ctx;
void onDestory() override; WebRtcTransportImp& _transport;
void onShutdown(const SockException &ex) override; void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) override;
virtual void onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) {} };
void updateTicker();
float getLossRate(TrackType type); class WebRtcTransportImp : public WebRtcTransport {
void onRtcpBye() override; public:
using Ptr = std::shared_ptr<WebRtcTransportImp>;
private: ~WebRtcTransportImp() override;
void onSortedRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp);
void onSendNack(MediaTrack &track, const FCI_NACK &nack, uint32_t ssrc); uint64_t getBytesUsage() const;
void onSendTwcc(uint32_t ssrc, const std::string &twcc_fci); uint64_t getDuration() const;
bool canSendRtp() const;
void registerSelf(); bool canRecvRtp() const;
void unregisterSelf(); bool canSendRtp(const RtcMedia& media) const;
void unrefSelf(); bool canRecvRtp(const RtcMedia& media) const;
void onCheckAnswer(RtcSession &sdp); void onSendRtp(const RtpPacket::Ptr &rtp, bool flush, bool rtx = false);
private: void createRtpChannel(const std::string &rid, uint32_t ssrc, MediaTrack &track);
bool _preferred_tcp = false; void safeShutdown(const toolkit::SockException &ex);
uint16_t _rtx_seq[2] = {0, 0};
// 用掉的总流量 [AUTO-TRANSLATED:713b61c9] void setPreferredTcp(bool flag) override;
// Total traffic used void setLocalIp(std::string local_ip) override;
uint64_t _bytes_usage = 0; void setIceCandidate(std::vector<SdpAttrCandidate> cands) override;
// 保持自我强引用 [AUTO-TRANSLATED:c2dc228f]
// Keep self strong reference protected:
Ptr _self;
// 检测超时的定时器 [AUTO-TRANSLATED:a58e1388] // // ice相关的回调 /// [AUTO-TRANSLATED:30abf693]
// Timeout detection timer // // ice related callbacks ///
Timer::Ptr _timer;
// 刷新计时器 [AUTO-TRANSLATED:61eb11e5] WebRtcTransportImp(const toolkit::EventPoller::Ptr &poller);
// Refresh timer void OnDtlsTransportApplicationDataReceived(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) override;
Ticker _alive_ticker; void onStartWebRTC() override;
// pli rtcp计时器 [AUTO-TRANSLATED:a1a5fd18] void onSendSockData(toolkit::Buffer::Ptr buf, bool flush = true, const IceTransport::Pair::Ptr& pair = nullptr) override;
// pli rtcp timer void onCheckSdp(SdpType type, RtcSession &sdp) override;
Ticker _pli_ticker; void onRtcConfigure(RtcConfigure &configure) const override;
// twcc rtcp发送上下文对象 [AUTO-TRANSLATED:aef6476a]
// twcc rtcp send context object void onRtp(const char *buf, size_t len, uint64_t stamp_ms) override;
TwccContext _twcc_ctx; void onRtcp(const char *buf, size_t len) override;
// 根据发送rtp的track类型获取相关信息 [AUTO-TRANSLATED:ff31c272] void onBeforeEncryptRtp(const char *buf, int &len, void *ctx) override;
// Get relevant information based on the track type of the sent rtp void onBeforeEncryptRtcp(const char *buf, int &len, void *ctx) override {};
MediaTrack::Ptr _type_to_track[2]; void onCreate() override;
// 根据rtcp的ssrc获取相关信息收发rtp和rtx的ssrc都会记录 [AUTO-TRANSLATED:6c57cd48] void onDestory() override;
// Get relevant information based on the ssrc of the rtcp, the ssrc of sending and receiving rtp and rtx will be recorded void onShutdown(const toolkit::SockException &ex) override;
std::unordered_map<uint32_t/*ssrc*/, MediaTrack::Ptr> _ssrc_to_track; virtual void onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) {}
// 根据接收rtp的pt获取相关信息 [AUTO-TRANSLATED:39e56d7d] void updateTicker();
// Get relevant information based on the pt of the received rtp float getLossRate(TrackType type);
std::unordered_map<uint8_t/*pt*/, std::unique_ptr<WrappedMediaTrack>> _pt_to_track; void onRtcpBye() override;
std::vector<SdpAttrCandidate> _cands;
// http访问时的host ip [AUTO-TRANSLATED:e8fe6957] private:
// Host ip for http access void onSortedRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp);
std::string _local_ip; void onSendNack(MediaTrack &track, const FCI_NACK &nack, uint32_t ssrc);
}; void onSendTwcc(uint32_t ssrc, const std::string &twcc_fci);
class WebRtcTransportManager { void registerSelf();
public: void unregisterSelf();
friend class WebRtcTransportImp; void unrefSelf();
static WebRtcTransportManager &Instance(); void onCheckAnswer(RtcSession &sdp);
WebRtcTransportImp::Ptr getItem(const std::string &key);
private:
private: bool _preferred_tcp = false;
WebRtcTransportManager() = default; uint16_t _rtx_seq[2] = {0, 0};
void addItem(const std::string &key, const WebRtcTransportImp::Ptr &ptr); // 用掉的总流量 [AUTO-TRANSLATED:713b61c9]
void removeItem(const std::string &key); // Total traffic used
uint64_t _bytes_usage = 0;
private: // 保持自我强引用 [AUTO-TRANSLATED:c2dc228f]
mutable std::mutex _mtx; // Keep self strong reference
std::unordered_map<std::string, std::weak_ptr<WebRtcTransportImp> > _map; Ptr _self;
}; // 检测超时的定时器 [AUTO-TRANSLATED:a58e1388]
// Timeout detection timer
class WebRtcArgs : public std::enable_shared_from_this<WebRtcArgs> { toolkit::Timer::Ptr _timer;
public: // 刷新计时器 [AUTO-TRANSLATED:61eb11e5]
virtual ~WebRtcArgs() = default; // Refresh timer
virtual variant operator[](const std::string &key) const = 0; toolkit::Ticker _alive_ticker;
}; // pli rtcp计时器 [AUTO-TRANSLATED:a1a5fd18]
// pli rtcp timer
using onCreateWebRtc = std::function<void(const WebRtcInterface &rtc)>; toolkit::Ticker _pli_ticker;
class WebRtcPluginManager {
public: toolkit::Ticker _rtcp_sr_send_ticker;
using Plugin = std::function<void(Session &sender, const WebRtcArgs &args, const onCreateWebRtc &cb)>; toolkit::Ticker _rtcp_rr_send_ticker;
using Listener = std::function<void(Session &sender, const std::string &type, const WebRtcArgs &args, const WebRtcInterface &rtc)>;
// twcc rtcp发送上下文对象 [AUTO-TRANSLATED:aef6476a]
static WebRtcPluginManager &Instance(); // twcc rtcp send context object
TwccContext _twcc_ctx;
void registerPlugin(const std::string &type, Plugin cb); // 根据发送rtp的track类型获取相关信息 [AUTO-TRANSLATED:ff31c272]
void setListener(Listener cb); // Get relevant information based on the track type of the sent rtp
void negotiateSdp(Session &sender, const std::string &type, const WebRtcArgs &args, const onCreateWebRtc &cb); MediaTrack::Ptr _type_to_track[2];
// 根据rtcp的ssrc获取相关信息收发rtp和rtx的ssrc都会记录 [AUTO-TRANSLATED:6c57cd48]
private: // Get relevant information based on the ssrc of the rtcp, the ssrc of sending and receiving rtp and rtx will be recorded
WebRtcPluginManager() = default; std::unordered_map<uint32_t/*ssrc*/, MediaTrack::Ptr> _ssrc_to_track;
// 根据接收rtp的pt获取相关信息 [AUTO-TRANSLATED:39e56d7d]
private: // Get relevant information based on the pt of the received rtp
mutable std::mutex _mtx_creator; std::unordered_map<uint8_t/*pt*/, std::unique_ptr<WrappedMediaTrack>> _pt_to_track;
Listener _listener; std::vector<SdpAttrCandidate> _cands;
std::unordered_map<std::string, Plugin> _map_creator; // http访问时的host ip [AUTO-TRANSLATED:e8fe6957]
}; // Host ip for http access
std::string _local_ip;
}// namespace mediakit };
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<std::string, std::weak_ptr<WebRtcTransportImp> > _map;
};
class WebRtcArgs : public std::enable_shared_from_this<WebRtcArgs> {
public:
virtual ~WebRtcArgs() = default;
virtual toolkit::variant operator[](const std::string &key) const = 0;
};
using onCreateWebRtc = std::function<void(const WebRtcInterface &rtc)>;
class WebRtcPluginManager {
public:
using Plugin = std::function<void(toolkit::SocketHelper& sender, const WebRtcArgs &args, const onCreateWebRtc &cb)>;
using Listener = std::function<void(toolkit::SocketHelper& sender, const std::string &type, const WebRtcArgs &args, const WebRtcInterface &rtc)>;
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<std::string, Plugin> _map_creator;
};
void translateIPFromEnv(std::vector<std::string> &v);
}// namespace mediakit
#endif // ZLMEDIAKIT_WEBRTC_TRANSPORT_H

View File

@ -1,13 +1,6 @@
# 致谢与声明 # 致谢与声明
本文件夹下部分文件提取自[MediaSoup](https://github.com/versatica/mediasoup) ,分别为: 本文件夹下部分文件提取自[MediaSoup](https://github.com/versatica/mediasoup) ,分别为:
- ice相关功能
- IceServer.cpp
- IceServer.hpp
- StunPacket.cpp
- StunPacket.hpp
- Utils.hpp
- dtls相关功能 - dtls相关功能
- DtlsTransport.cpp - DtlsTransport.cpp
- DtlsTransport.hpp - DtlsTransport.hpp

132
webrtc/webrtcSignal.txt Normal file
View File

@ -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",
}
```