mirror of
https://gitee.com/xia-chu/ZLMediaKit.git
synced 2026-05-06 10:57:50 +08:00
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
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:
parent
97d2a1fb08
commit
3fb43c5fef
9
.github/workflows/macos.yml
vendored
9
.github/workflows/macos.yml
vendored
@ -18,10 +18,15 @@ jobs:
|
||||
with:
|
||||
vcpkgDirectory: '${{github.workspace}}/vcpkg'
|
||||
vcpkgTriplet: arm64-osx
|
||||
# 2024.06.01
|
||||
vcpkgGitCommitId: '47364fbc300756f64f7876b549d9422d5f3ec0d3'
|
||||
# 2025.07.11
|
||||
vcpkgGitCommitId: 'efcfaaf60d7ec57a159fc3110403d939bfb69729'
|
||||
vcpkgArguments: 'openssl libsrtp[openssl] usrsctp'
|
||||
|
||||
- name: 安装指定 CMake
|
||||
uses: jwlawson/actions-setup-cmake@v2
|
||||
with:
|
||||
cmake-version: '3.30.5'
|
||||
|
||||
- name: 编译
|
||||
uses: lukka/run-cmake@v3
|
||||
with:
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit 69098a18b9af0c47549d9a271c054d13ca92b006
|
||||
Subproject commit ca98c98457b1163cca1f7d8db62827c115fec6d1
|
||||
@ -472,6 +472,17 @@ if(ENABLE_SRT)
|
||||
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_SRT)
|
||||
endif()
|
||||
|
||||
if(ENABLE_WEBRTC)
|
||||
# 查找 srtp 是否安装
|
||||
find_package(SRTP QUIET)
|
||||
if(SRTP_FOUND AND ENABLE_OPENSSL)
|
||||
message(STATUS "found library: ${SRTP_LIBRARIES}, ENABLE_WEBRTC defined")
|
||||
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_WEBRTC)
|
||||
else()
|
||||
set(ENABLE_WEBRTC OFF)
|
||||
message(WARNING "srtp 未找到, WebRTC 相关功能打开失败")
|
||||
endif()
|
||||
endif()
|
||||
# ----------------------------------------------------------------------------
|
||||
# Solution folders:
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
@ -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] 服务器,支持RTMP/MP4/HLS转RTSP[S],支持亚马逊echo show这样的设备
|
||||
@ -131,6 +131,8 @@
|
||||
- 支持webrtc over tcp模式
|
||||
- 优秀的nack、jitter buffer算法, 抗丢包能力卓越
|
||||
- 支持whip/whep协议
|
||||
- [支持ice-full,支持作为webrtc客户端拉流、推流以及p2p模式](./webrtc/USAGE.md)
|
||||
|
||||
- [SRT支持](./srt/srt.md)
|
||||
- 其他
|
||||
- 支持丰富的restful api以及web hook事件
|
||||
|
||||
@ -45,7 +45,7 @@
|
||||
|
||||
## Feature List
|
||||
### 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] server, supports RTMP/MP4/HLS to RTSP[S] conversion, supports devices such as Amazon Echo Show
|
||||
@ -124,6 +124,8 @@
|
||||
- Supports WebRTC over TCP mode
|
||||
- Excellent NACK and jitter buffer algorithms with outstanding packet loss resistance
|
||||
- Supports WHIP/WHEP protocols
|
||||
- [Supports ice-full, works as a WebRTC client for pulling streams, pushing streams, and P2P mode](./webrtc/USAGE.md)
|
||||
|
||||
- [SRT support](./srt/srt.md)
|
||||
- Others
|
||||
- Supports rich RESTful APIs and webhook events
|
||||
|
||||
@ -346,11 +346,30 @@ udp_recv_socket_buffer=4194304
|
||||
merge_frame=1
|
||||
|
||||
[rtc]
|
||||
#webrtc 信令服务器端口
|
||||
signalingPort=3000
|
||||
signalingSslPort=3001
|
||||
#STUN/TURN服务器端口
|
||||
icePort=3478
|
||||
iceTcpPort=3478
|
||||
#STUN/TURN端口是否使能TURN服务
|
||||
enableTurn=1
|
||||
#ICE传输策略:0=不限制(默认),1=仅支持Relay转发,2=仅支持P2P直连
|
||||
iceTransportPolicy=0
|
||||
#STUN/TURN 服务Ice密码
|
||||
iceUfrag=ZLMediaKit
|
||||
icePwd=ZLMediaKit
|
||||
#TURN服务分配端口池
|
||||
portRange=50000-65000
|
||||
#rtc播放推流、播放超时时间
|
||||
timeoutSec=15
|
||||
#本机对rtc客户端的可见ip,作为服务器时一般为公网ip,可有多个,用','分开,当置空时,会自动获取网卡ip
|
||||
#同时支持环境变量,以$开头,如"$EXTERN_IP"; 请参考:https://github.com/ZLMediaKit/ZLMediaKit/pull/1786
|
||||
externIP=
|
||||
#当指定了interfaces,ICE服务器会使用指定网卡bind socket
|
||||
#以解决公网IP使用弹性公网IP配置实现(部署机器无法bind该公网ip的问题)
|
||||
#支持环境变量,以$开头,如"$PRIVATE_IP"
|
||||
interfaces=
|
||||
#rtc udp服务器监听端口号,所有rtc客户端将通过该端口传输stun/dtls/srtp/srtcp数据,
|
||||
#该端口是多线程的,同时支持客户端网络切换导致的连接迁移
|
||||
#需要注意的是,如果服务器在nat内,需要做端口映射时,必须确保外网映射端口跟该端口一致
|
||||
@ -358,7 +377,7 @@ port=8000
|
||||
#rtc tcp服务器监听端口号,在udp 不通的情况下,会使用tcp传输数据
|
||||
#该端口是多线程的,同时支持客户端网络切换导致的连接迁移
|
||||
#需要注意的是,如果服务器在nat内,需要做端口映射时,必须确保外网映射端口跟该端口一致
|
||||
tcpPort = 8000
|
||||
tcpPort=8000
|
||||
#设置remb比特率,非0时关闭twcc并开启remb。该设置在rtc推流时有效,可以控制推流画质
|
||||
#目前已经实现twcc自动调整码率,关闭remb根据真实网络状况调整码率
|
||||
rembBitRate=0
|
||||
|
||||
@ -2732,6 +2732,154 @@
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "WebRTC-注册到信令服务器(addWebrtcRoomKeeper)",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{ZLMediaKit_URL}}/index/api/addWebrtcRoomKeeper?secret={{ZLMediaKit_secret}}&server_host=127.0.0.1&server_port=3000&room_id=peer_1",
|
||||
"host": [
|
||||
"{{ZLMediaKit_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"index",
|
||||
"api",
|
||||
"addWebrtcRoomKeeper"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}"
|
||||
},
|
||||
{
|
||||
"key": "server_host",
|
||||
"value": "127.0.0.1",
|
||||
"description": "要注册到的信令服务器地址"
|
||||
},
|
||||
{
|
||||
"key": "server_port",
|
||||
"value": "3000",
|
||||
"description": "要注册到的信令服务器端口"
|
||||
},
|
||||
{
|
||||
"key": "room_id",
|
||||
"value": "peer_1",
|
||||
"description": "要注册到的roomid"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "WebRTC-从信令服务器注销(delWebrtcRoomKeeper)",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{ZLMediaKit_URL}}/index/api/delWebrtcRoomKeeper?secret={{ZLMediaKit_secret}}&room_key=",
|
||||
"host": [
|
||||
"{{ZLMediaKit_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"index",
|
||||
"api",
|
||||
"delWebrtcRoomKeeper"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}"
|
||||
},
|
||||
{
|
||||
"key": "room_key",
|
||||
"value": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "WebRTC-Peer查看注册信息(listWebrtcRoomKeepers)",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{ZLMediaKit_URL}}/index/api/listWebrtcRoomKeepers?secret={{ZLMediaKit_secret}}",
|
||||
"host": [
|
||||
"{{ZLMediaKit_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"index",
|
||||
"api",
|
||||
"listWebrtcRoomKeepers"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "WebRTC-信令服务器查看注册信息(listWebrtcRooms)",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{ZLMediaKit_URL}}/index/api/listWebrtcRooms?secret={{ZLMediaKit_secret}}",
|
||||
"host": [
|
||||
"{{ZLMediaKit_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"index",
|
||||
"api",
|
||||
"listWebrtcRooms"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "WebRTC-查看WebRTCProxyPlayer连接信息(getWebrtcProxyPlayerInfo)",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{ZLMediaKit_URL}}/index/api/getWebrtcProxyPlayerInfo?secret={{ZLMediaKit_secret}}&key=__defaultVhost__/live/test",
|
||||
"host": [
|
||||
"{{ZLMediaKit_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"index",
|
||||
"api",
|
||||
"getWebrtcProxyPlayerInfo"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}"
|
||||
},
|
||||
{
|
||||
"key": "key",
|
||||
"value": "__defaultVhost__/live/test"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
],
|
||||
"event": [
|
||||
|
||||
@ -57,6 +57,10 @@
|
||||
#include "../webrtc/WebRtcPlayer.h"
|
||||
#include "../webrtc/WebRtcPusher.h"
|
||||
#include "../webrtc/WebRtcEchoTest.h"
|
||||
#include "../webrtc/WebRtcSignalingPeer.h"
|
||||
#include "../webrtc/WebRtcSignalingSession.h"
|
||||
#include "../webrtc/WebRtcProxyPlayer.h"
|
||||
#include "../webrtc/WebRtcProxyPlayerImp.h"
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_VERSION)
|
||||
@ -314,84 +318,6 @@ static inline void addHttpListener(){
|
||||
});
|
||||
}
|
||||
|
||||
template <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]
|
||||
// Pull stream proxy list
|
||||
static ServiceController<PlayerProxy> s_player_proxy;
|
||||
@ -2127,35 +2053,6 @@ void installWebApi() {
|
||||
});
|
||||
|
||||
#ifdef ENABLE_WEBRTC
|
||||
class WebRtcArgsImp : public WebRtcArgs {
|
||||
public:
|
||||
WebRtcArgsImp(const ArgsString &args, std::string session_id)
|
||||
: _args(args)
|
||||
, _session_id(std::move(session_id)) {}
|
||||
~WebRtcArgsImp() override = default;
|
||||
|
||||
toolkit::variant operator[](const string &key) const override {
|
||||
if (key == "url") {
|
||||
return getUrl();
|
||||
}
|
||||
return _args[key];
|
||||
}
|
||||
|
||||
private:
|
||||
string getUrl() const{
|
||||
auto &allArgs = _args;
|
||||
CHECK_ARGS("app", "stream");
|
||||
|
||||
string auth = _args["Authorization"]; // Authorization Bearer
|
||||
return StrPrinter << "rtc://" << _args["Host"] << "/" << _args["app"] << "/" << _args["stream"] << "?"
|
||||
<< _args.parser.params() + "&session=" + _session_id + (auth.empty() ? "" : ("&Authorization=" + auth));
|
||||
}
|
||||
|
||||
private:
|
||||
ArgsString _args;
|
||||
std::string _session_id;
|
||||
};
|
||||
|
||||
api_regist("/index/api/webrtc",[](API_ARGS_STRING_ASYNC){
|
||||
CHECK_ARGS("type");
|
||||
auto type = allArgs["type"];
|
||||
@ -2163,7 +2060,7 @@ void installWebApi() {
|
||||
CHECK(!offer.empty(), "http body(webrtc offer sdp) is empty");
|
||||
|
||||
auto &session = static_cast<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 {
|
||||
auto &handler = const_cast<WebRtcInterface &>(exchanger);
|
||||
try {
|
||||
@ -2186,7 +2083,7 @@ void installWebApi() {
|
||||
|
||||
auto &session = static_cast<Session&>(sender);
|
||||
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 {
|
||||
auto &handler = const_cast<WebRtcInterface &>(exchanger);
|
||||
try {
|
||||
@ -2220,6 +2117,103 @@ void installWebApi() {
|
||||
obj->safeShutdown(SockException(Err_shutdown, "deleted by http api"));
|
||||
invoker(200, headerOut, "");
|
||||
});
|
||||
|
||||
// 获取WebRTCProxyPlayer 连接信息
|
||||
api_regist("/index/api/getWebrtcProxyPlayerInfo", [](API_ARGS_MAP_ASYNC) {
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("key");
|
||||
|
||||
auto player_proxy = s_player_proxy.find(allArgs["key"]);
|
||||
if (!player_proxy) {
|
||||
throw ApiRetException("Stream proxy not found", API::NotFound);
|
||||
}
|
||||
|
||||
auto media_player = player_proxy->getDelegate();
|
||||
if (!media_player) {
|
||||
throw ApiRetException("Media player not found", API::OtherFailed);
|
||||
}
|
||||
|
||||
auto webrtc_player_imp = std::dynamic_pointer_cast<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
|
||||
|
||||
#if defined(ENABLE_VERSION)
|
||||
|
||||
592
server/WebApi.h
592
server/WebApi.h
@ -1,228 +1,364 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT-like license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef ZLMEDIAKIT_WEBAPI_H
|
||||
#define ZLMEDIAKIT_WEBAPI_H
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include "json/json.h"
|
||||
#include "Common/Parser.h"
|
||||
#include "Network/Socket.h"
|
||||
#include "Http/HttpSession.h"
|
||||
#include "Common/MultiMediaSourceMuxer.h"
|
||||
|
||||
// 配置文件路径 [AUTO-TRANSLATED:8a373c2f]
|
||||
// Configuration file path
|
||||
extern std::string g_ini_file;
|
||||
|
||||
namespace mediakit {
|
||||
// //////////RTSP服务器配置/////////// [AUTO-TRANSLATED:950e1981]
|
||||
// //////////RTSP server configuration///////////
|
||||
namespace Rtsp {
|
||||
extern const std::string kPort;
|
||||
} //namespace Rtsp
|
||||
|
||||
// //////////RTMP服务器配置/////////// [AUTO-TRANSLATED:8de6f41f]
|
||||
// //////////RTMP server configuration///////////
|
||||
namespace Rtmp {
|
||||
extern const std::string kPort;
|
||||
} //namespace RTMP
|
||||
} // namespace mediakit
|
||||
|
||||
namespace API {
|
||||
typedef enum {
|
||||
NotFound = -500,//未找到
|
||||
Exception = -400,//代码抛异常
|
||||
InvalidArgs = -300,//参数不合法
|
||||
SqlFailed = -200,//sql执行失败
|
||||
AuthFailed = -100,//鉴权失败
|
||||
OtherFailed = -1,//业务代码执行失败,
|
||||
Success = 0//执行成功
|
||||
} ApiErr;
|
||||
|
||||
extern const std::string kSecret;
|
||||
}//namespace API
|
||||
|
||||
class ApiRetException: public std::runtime_error {
|
||||
public:
|
||||
ApiRetException(const char *str = "success" ,int code = API::Success):runtime_error(str){
|
||||
_code = code;
|
||||
}
|
||||
int code(){ return _code; }
|
||||
private:
|
||||
int _code;
|
||||
};
|
||||
|
||||
class AuthException : public ApiRetException {
|
||||
public:
|
||||
AuthException(const char *str):ApiRetException(str,API::AuthFailed){}
|
||||
};
|
||||
|
||||
class InvalidArgsException: public ApiRetException {
|
||||
public:
|
||||
InvalidArgsException(const char *str):ApiRetException(str,API::InvalidArgs){}
|
||||
};
|
||||
|
||||
class SuccessException: public ApiRetException {
|
||||
public:
|
||||
SuccessException():ApiRetException("success",API::Success){}
|
||||
};
|
||||
|
||||
using ApiArgsType = std::map<std::string, std::string, mediakit::StrCaseCompare>;
|
||||
|
||||
template<typename Args, typename Key>
|
||||
std::string getValue(Args &args, const Key &key) {
|
||||
auto it = args.find(key);
|
||||
if (it == args.end()) {
|
||||
return "";
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
template<typename Key>
|
||||
std::string getValue(Json::Value &args, const Key &key) {
|
||||
auto value = args.find(key);
|
||||
if (value == nullptr) {
|
||||
return "";
|
||||
}
|
||||
return value->asString();
|
||||
}
|
||||
|
||||
template<typename Key>
|
||||
std::string getValue(std::string &args, const Key &key) {
|
||||
return "";
|
||||
}
|
||||
|
||||
template <typename Key>
|
||||
std::string getValue(const mediakit::Parser &parser, const Key &key) {
|
||||
auto ret = getValue(parser.getUrlArgs(), key);
|
||||
if (!ret.empty()) {
|
||||
return ret;
|
||||
}
|
||||
return getValue(parser.getHeader(), key);
|
||||
}
|
||||
|
||||
template<typename Args, typename Key>
|
||||
std::string getValue(const mediakit::Parser &parser, Args &args, const Key &key) {
|
||||
auto ret = getValue(args, key);
|
||||
if (!ret.empty()) {
|
||||
return ret;
|
||||
}
|
||||
return getValue(parser, key);
|
||||
}
|
||||
|
||||
template<typename Args>
|
||||
class HttpAllArgs {
|
||||
mediakit::Parser* _parser = nullptr;
|
||||
Args* _args = nullptr;
|
||||
public:
|
||||
const mediakit::Parser& parser;
|
||||
Args& args;
|
||||
|
||||
HttpAllArgs(const mediakit::Parser &p, Args &a): parser(p), args(a) {}
|
||||
|
||||
HttpAllArgs(const HttpAllArgs &that): _parser(new mediakit::Parser(that.parser)),
|
||||
_args(new Args(that.args)),
|
||||
parser(*_parser), args(*_args) {}
|
||||
~HttpAllArgs() {
|
||||
if (_parser) {
|
||||
delete _parser;
|
||||
}
|
||||
if (_args) {
|
||||
delete _args;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Key>
|
||||
toolkit::variant operator[](const Key &key) const {
|
||||
return (toolkit::variant)getValue(parser, args, key);
|
||||
}
|
||||
};
|
||||
|
||||
using ArgsMap = HttpAllArgs<ApiArgsType>;
|
||||
using ArgsJson = HttpAllArgs<Json::Value>;
|
||||
using ArgsString = HttpAllArgs<std::string>;
|
||||
|
||||
#define API_ARGS_MAP toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const ArgsMap &allArgs, Json::Value &val
|
||||
#define API_ARGS_MAP_ASYNC API_ARGS_MAP, const mediakit::HttpSession::HttpResponseInvoker &invoker
|
||||
#define API_ARGS_JSON toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const ArgsJson &allArgs, Json::Value &val
|
||||
#define API_ARGS_JSON_ASYNC API_ARGS_JSON, const mediakit::HttpSession::HttpResponseInvoker &invoker
|
||||
#define API_ARGS_STRING toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const ArgsString &allArgs, Json::Value &val
|
||||
#define API_ARGS_STRING_ASYNC API_ARGS_STRING, const mediakit::HttpSession::HttpResponseInvoker &invoker
|
||||
#define API_ARGS_VALUE sender, headerOut, allArgs, val
|
||||
|
||||
// 注册http请求参数是map<string, variant, StrCaseCompare>类型的http api [AUTO-TRANSLATED:8a273897]
|
||||
// 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_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);
|
||||
|
||||
// 注册http请求参数是Json::Value类型的http api(可以支持多级嵌套的json参数对象) [AUTO-TRANSLATED:c4794456]
|
||||
// Register http request parameters as Json::Value type http api (can support multi-level nested json parameter objects)
|
||||
void api_regist(const std::string &api_path, const std::function<void(API_ARGS_JSON)> &func);
|
||||
// 注册http请求参数是Json::Value类型,但是可以异步回复的的http api [AUTO-TRANSLATED:742e57fd]
|
||||
// Register http request parameters as Json::Value type, but can be replied asynchronously http api
|
||||
void api_regist(const std::string &api_path, const std::function<void(API_ARGS_JSON_ASYNC)> &func);
|
||||
|
||||
// 注册http请求参数是http原始请求信息的http api [AUTO-TRANSLATED:72d3fe93]
|
||||
// Register http request parameters as http original request information http api
|
||||
void api_regist(const std::string &api_path, const std::function<void(API_ARGS_STRING)> &func);
|
||||
// 注册http请求参数是http原始请求信息的异步回复的http api [AUTO-TRANSLATED:49feefa8]
|
||||
// Register http request parameters as http original request information asynchronous reply http api
|
||||
void api_regist(const std::string &api_path, const std::function<void(API_ARGS_STRING_ASYNC)> &func);
|
||||
|
||||
template<typename Args, typename Key>
|
||||
bool checkArgs(Args &args, const Key &key) {
|
||||
return !args[key].empty();
|
||||
}
|
||||
|
||||
template<typename Args, typename Key, typename ...KeyTypes>
|
||||
bool checkArgs(Args &args, const Key &key, const KeyTypes &...keys) {
|
||||
return checkArgs(args, key) && checkArgs(args, keys...);
|
||||
}
|
||||
|
||||
// 检查http url中或body中或http header参数是否为空的宏 [AUTO-TRANSLATED:9de001a4]
|
||||
// Check whether the http url, body or http header parameters are empty
|
||||
#define CHECK_ARGS(...) \
|
||||
if(!checkArgs(allArgs,##__VA_ARGS__)){ \
|
||||
throw InvalidArgsException("Required parameter missed: " #__VA_ARGS__); \
|
||||
}
|
||||
|
||||
// 检查http参数中是否附带secret密钥的宏,127.0.0.1的ip不检查密钥 [AUTO-TRANSLATED:7546956c]
|
||||
// Check whether the http parameters contain the secret key, the ip of 127.0.0.1 does not check the key
|
||||
// 同时检测是否在ip白名单内 [AUTO-TRANSLATED:d12f963d]
|
||||
// Check whether it is in the ip whitelist at the same time
|
||||
#define CHECK_SECRET() \
|
||||
do { \
|
||||
auto ip = sender.get_peer_ip(); \
|
||||
if (!HttpFileManager::isIPAllowed(ip)) { \
|
||||
throw AuthException("Your ip is not allowed to access the service."); \
|
||||
} \
|
||||
CHECK_ARGS("secret"); \
|
||||
if (api_secret != allArgs["secret"]) { \
|
||||
throw AuthException("Incorrect secret"); \
|
||||
} \
|
||||
} while(false);
|
||||
|
||||
void installWebApi();
|
||||
void unInstallWebApi();
|
||||
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
uint16_t openRtpServer(uint16_t local_port, const mediakit::MediaTuple &tuple, int tcp_mode, const std::string &local_ip, bool re_use_port, uint32_t ssrc, int only_track, bool multiplex=false);
|
||||
#endif
|
||||
|
||||
Json::Value makeMediaSourceJson(mediakit::MediaSource &media);
|
||||
void getStatisticJson(const std::function<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);
|
||||
#endif //ZLMEDIAKIT_WEBAPI_H
|
||||
/*
|
||||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT-like license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef ZLMEDIAKIT_WEBAPI_H
|
||||
#define ZLMEDIAKIT_WEBAPI_H
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include "json/json.h"
|
||||
#include "Common/Parser.h"
|
||||
#include "Network/Socket.h"
|
||||
#include "Http/HttpSession.h"
|
||||
#include "Common/MultiMediaSourceMuxer.h"
|
||||
|
||||
#if defined(ENABLE_WEBRTC)
|
||||
#include "webrtc/WebRtcTransport.h"
|
||||
#endif
|
||||
|
||||
// 配置文件路径 [AUTO-TRANSLATED:8a373c2f]
|
||||
// Configuration file path
|
||||
extern std::string g_ini_file;
|
||||
|
||||
namespace mediakit {
|
||||
// //////////RTSP服务器配置/////////// [AUTO-TRANSLATED:950e1981]
|
||||
// //////////RTSP server configuration///////////
|
||||
namespace Rtsp {
|
||||
extern const std::string kPort;
|
||||
} //namespace Rtsp
|
||||
|
||||
// //////////RTMP服务器配置/////////// [AUTO-TRANSLATED:8de6f41f]
|
||||
// //////////RTMP server configuration///////////
|
||||
namespace Rtmp {
|
||||
extern const std::string kPort;
|
||||
} //namespace RTMP
|
||||
} // namespace mediakit
|
||||
|
||||
namespace API {
|
||||
typedef enum {
|
||||
NotFound = -500,//未找到
|
||||
Exception = -400,//代码抛异常
|
||||
InvalidArgs = -300,//参数不合法
|
||||
SqlFailed = -200,//sql执行失败
|
||||
AuthFailed = -100,//鉴权失败
|
||||
OtherFailed = -1,//业务代码执行失败,
|
||||
Success = 0//执行成功
|
||||
} ApiErr;
|
||||
|
||||
extern const std::string kSecret;
|
||||
}//namespace API
|
||||
|
||||
class ApiRetException: public std::runtime_error {
|
||||
public:
|
||||
ApiRetException(const char *str = "success" ,int code = API::Success):runtime_error(str){
|
||||
_code = code;
|
||||
}
|
||||
int code(){ return _code; }
|
||||
private:
|
||||
int _code;
|
||||
};
|
||||
|
||||
class AuthException : public ApiRetException {
|
||||
public:
|
||||
AuthException(const char *str):ApiRetException(str,API::AuthFailed){}
|
||||
};
|
||||
|
||||
class InvalidArgsException: public ApiRetException {
|
||||
public:
|
||||
InvalidArgsException(const char *str):ApiRetException(str,API::InvalidArgs){}
|
||||
};
|
||||
|
||||
class SuccessException: public ApiRetException {
|
||||
public:
|
||||
SuccessException():ApiRetException("success",API::Success){}
|
||||
};
|
||||
|
||||
using ApiArgsType = std::map<std::string, std::string, mediakit::StrCaseCompare>;
|
||||
|
||||
template<typename Args, typename Key>
|
||||
std::string getValue(Args &args, const Key &key) {
|
||||
auto it = args.find(key);
|
||||
if (it == args.end()) {
|
||||
return "";
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
template<typename Key>
|
||||
std::string getValue(Json::Value &args, const Key &key) {
|
||||
auto value = args.find(key);
|
||||
if (value == nullptr) {
|
||||
return "";
|
||||
}
|
||||
return value->asString();
|
||||
}
|
||||
|
||||
template<typename Key>
|
||||
std::string getValue(std::string &args, const Key &key) {
|
||||
return "";
|
||||
}
|
||||
|
||||
template <typename Key>
|
||||
std::string getValue(const mediakit::Parser &parser, const Key &key) {
|
||||
auto ret = getValue(parser.getUrlArgs(), key);
|
||||
if (!ret.empty()) {
|
||||
return ret;
|
||||
}
|
||||
return getValue(parser.getHeader(), key);
|
||||
}
|
||||
|
||||
template<typename Key>
|
||||
std::string getValue(mediakit::Parser &parser, const Key &key) {
|
||||
return getValue((const mediakit::Parser &) parser, key);
|
||||
}
|
||||
|
||||
template<typename Args, typename Key>
|
||||
std::string getValue(const mediakit::Parser &parser, Args &args, const Key &key) {
|
||||
auto ret = getValue(args, key);
|
||||
if (!ret.empty()) {
|
||||
return ret;
|
||||
}
|
||||
return getValue(parser, key);
|
||||
}
|
||||
|
||||
template<typename Args>
|
||||
class HttpAllArgs {
|
||||
mediakit::Parser* _parser = nullptr;
|
||||
Args* _args = nullptr;
|
||||
public:
|
||||
const mediakit::Parser& parser;
|
||||
Args& args;
|
||||
|
||||
HttpAllArgs(const mediakit::Parser &p, Args &a): parser(p), args(a) {}
|
||||
|
||||
HttpAllArgs(const HttpAllArgs &that): _parser(new mediakit::Parser(that.parser)),
|
||||
_args(new Args(that.args)),
|
||||
parser(*_parser), args(*_args) {}
|
||||
~HttpAllArgs() {
|
||||
if (_parser) {
|
||||
delete _parser;
|
||||
}
|
||||
if (_args) {
|
||||
delete _args;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Key>
|
||||
toolkit::variant operator[](const Key &key) const {
|
||||
return (toolkit::variant)getValue(parser, args, key);
|
||||
}
|
||||
|
||||
const Args& getArgs() const {
|
||||
return args;
|
||||
}
|
||||
|
||||
const mediakit::Parser &getParser() const {
|
||||
return parser;
|
||||
}
|
||||
};
|
||||
|
||||
using ArgsMap = HttpAllArgs<ApiArgsType>;
|
||||
using ArgsJson = HttpAllArgs<Json::Value>;
|
||||
using ArgsString = HttpAllArgs<std::string>;
|
||||
|
||||
#define API_ARGS_MAP toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const ArgsMap &allArgs, Json::Value &val
|
||||
#define API_ARGS_MAP_ASYNC API_ARGS_MAP, const mediakit::HttpSession::HttpResponseInvoker &invoker
|
||||
#define API_ARGS_JSON toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const ArgsJson &allArgs, Json::Value &val
|
||||
#define API_ARGS_JSON_ASYNC API_ARGS_JSON, const mediakit::HttpSession::HttpResponseInvoker &invoker
|
||||
#define API_ARGS_STRING toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const ArgsString &allArgs, Json::Value &val
|
||||
#define API_ARGS_STRING_ASYNC API_ARGS_STRING, const mediakit::HttpSession::HttpResponseInvoker &invoker
|
||||
#define API_ARGS_VALUE sender, headerOut, allArgs, val
|
||||
|
||||
// 注册http请求参数是map<string, variant, StrCaseCompare>类型的http api [AUTO-TRANSLATED:8a273897]
|
||||
// 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_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);
|
||||
|
||||
// 注册http请求参数是Json::Value类型的http api(可以支持多级嵌套的json参数对象) [AUTO-TRANSLATED:c4794456]
|
||||
// Register http request parameters as Json::Value type http api (can support multi-level nested json parameter objects)
|
||||
void api_regist(const std::string &api_path, const std::function<void(API_ARGS_JSON)> &func);
|
||||
// 注册http请求参数是Json::Value类型,但是可以异步回复的的http api [AUTO-TRANSLATED:742e57fd]
|
||||
// Register http request parameters as Json::Value type, but can be replied asynchronously http api
|
||||
void api_regist(const std::string &api_path, const std::function<void(API_ARGS_JSON_ASYNC)> &func);
|
||||
|
||||
// 注册http请求参数是http原始请求信息的http api [AUTO-TRANSLATED:72d3fe93]
|
||||
// Register http request parameters as http original request information http api
|
||||
void api_regist(const std::string &api_path, const std::function<void(API_ARGS_STRING)> &func);
|
||||
// 注册http请求参数是http原始请求信息的异步回复的http api [AUTO-TRANSLATED:49feefa8]
|
||||
// Register http request parameters as http original request information asynchronous reply http api
|
||||
void api_regist(const std::string &api_path, const std::function<void(API_ARGS_STRING_ASYNC)> &func);
|
||||
|
||||
template<typename Args, typename Key>
|
||||
bool checkArgs(Args &args, const Key &key) {
|
||||
return !args[key].empty();
|
||||
}
|
||||
|
||||
template<typename Args, typename Key, typename ...KeyTypes>
|
||||
bool checkArgs(Args &args, const Key &key, const KeyTypes &...keys) {
|
||||
return checkArgs(args, key) && checkArgs(args, keys...);
|
||||
}
|
||||
|
||||
// 检查http url中或body中或http header参数是否为空的宏 [AUTO-TRANSLATED:9de001a4]
|
||||
// Check whether the http url, body or http header parameters are empty
|
||||
#define CHECK_ARGS(...) \
|
||||
if(!checkArgs(allArgs,##__VA_ARGS__)){ \
|
||||
throw InvalidArgsException("Required parameter missed: " #__VA_ARGS__); \
|
||||
}
|
||||
|
||||
// 检查http参数中是否附带secret密钥的宏,127.0.0.1的ip不检查密钥 [AUTO-TRANSLATED:7546956c]
|
||||
// Check whether the http parameters contain the secret key, the ip of 127.0.0.1 does not check the key
|
||||
// 同时检测是否在ip白名单内 [AUTO-TRANSLATED:d12f963d]
|
||||
// Check whether it is in the ip whitelist at the same time
|
||||
#define CHECK_SECRET() \
|
||||
do { \
|
||||
auto ip = sender.get_peer_ip(); \
|
||||
if (!HttpFileManager::isIPAllowed(ip)) { \
|
||||
throw AuthException("Your ip is not allowed to access the service."); \
|
||||
} \
|
||||
CHECK_ARGS("secret"); \
|
||||
if (api_secret != allArgs["secret"]) { \
|
||||
throw AuthException("Incorrect secret"); \
|
||||
} \
|
||||
} while(false);
|
||||
|
||||
void installWebApi();
|
||||
void unInstallWebApi();
|
||||
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
uint16_t openRtpServer(uint16_t local_port, const mediakit::MediaTuple &tuple, int tcp_mode, const std::string &local_ip, bool re_use_port, uint32_t ssrc, int only_track, bool multiplex=false);
|
||||
#endif
|
||||
|
||||
Json::Value makeMediaSourceJson(mediakit::MediaSource &media);
|
||||
void getStatisticJson(const std::function<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
|
||||
|
||||
@ -30,6 +30,8 @@
|
||||
#if defined(ENABLE_WEBRTC)
|
||||
#include "../webrtc/WebRtcTransport.h"
|
||||
#include "../webrtc/WebRtcSession.h"
|
||||
#include "../webrtc/WebRtcSignalingSession.h"
|
||||
#include "../webrtc/IceSession.hpp"
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_SRT)
|
||||
@ -368,8 +370,17 @@ int start_main(int argc,char *argv[]) {
|
||||
}
|
||||
return Socket::createSocket(new_poller, false);
|
||||
});
|
||||
|
||||
auto signaleSrv = std::make_shared<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 rtcTcpPort = mINI::Instance()[Rtc::kTcpPort];
|
||||
uint16_t signalingPort = mINI::Instance()[Rtc::kSignalingPort];
|
||||
uint16_t signalSslPort = mINI::Instance()[Rtc::kSignalingSslPort];
|
||||
uint16_t icePort = mINI::Instance()[Rtc::kIcePort];
|
||||
uint16_t iceTcpPort = mINI::Instance()[Rtc::kIceTcpPort];
|
||||
#endif//defined(ENABLE_WEBRTC)
|
||||
|
||||
|
||||
@ -435,6 +446,12 @@ int start_main(int argc,char *argv[]) {
|
||||
|
||||
if (rtcTcpPort) { rtcSrv_tcp->start<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)
|
||||
|
||||
#if defined(ENABLE_SRT)
|
||||
|
||||
@ -30,7 +30,7 @@ namespace mediakit {
|
||||
* [AUTO-TRANSLATED:f214f734]
|
||||
*/
|
||||
#if !defined(ENABLE_VERSION)
|
||||
const char kServerName[] = "ZLMediaKit-8.0(build in " __DATE__ " " __TIME__ ")";
|
||||
const char kServerName[] = "ZLMediaKit-9.0(build in " __DATE__ " " __TIME__ ")";
|
||||
#else
|
||||
const char kServerName[] = "ZLMediaKit(git hash:" COMMIT_HASH "/" COMMIT_TIME ",branch:" BRANCH_NAME ",build time:" BUILD_TIME ")";
|
||||
#endif
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
}
|
||||
#endif // CLEAR_ARR
|
||||
|
||||
#define RTC_SCHEMA "rtc"
|
||||
#define RTSP_SCHEMA "rtsp"
|
||||
#define RTMP_SCHEMA "rtmp"
|
||||
#define TS_SCHEMA "ts"
|
||||
|
||||
@ -163,7 +163,7 @@ static std::shared_ptr<char> getSharedMmap(const string &file_path, int64_t &fil
|
||||
|
||||
if (addr_ == nullptr) {
|
||||
mmap_close(hfile, hmapping, addr_);
|
||||
WarnL << "MapViewOfFile() " << file_path << " failed:";
|
||||
WarnL << "MapViewOfFile() " << file_path << " failed:";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@ -26,6 +26,7 @@ public:
|
||||
void play(const std::string &url) override;
|
||||
toolkit::EventPoller::Ptr getPoller();
|
||||
void setOnCreateSocket(toolkit::Socket::onCreateSocket cb);
|
||||
const PlayerBase::Ptr& getDelegate() const { return _delegate; }
|
||||
|
||||
private:
|
||||
toolkit::EventPoller::Ptr _poller;
|
||||
|
||||
@ -18,7 +18,9 @@
|
||||
#ifdef ENABLE_SRT
|
||||
#include "Srt/SrtPlayerImp.h"
|
||||
#endif // ENABLE_SRT
|
||||
|
||||
#ifdef ENABLE_WEBRTC
|
||||
#include "../webrtc/WebRtcProxyPlayerImp.h"
|
||||
#endif // ENABLE_WEBRTC
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
@ -84,6 +86,11 @@ PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &in_poller, cons
|
||||
return PlayerBase::Ptr(new SrtPlayerImp(poller), release_func);
|
||||
}
|
||||
#endif//ENABLE_SRT
|
||||
#ifdef ENABLE_WEBRTC
|
||||
if ((strcasecmp("webrtc", prefix.data()) == 0 || strcasecmp("webrtcs", prefix.data()) == 0)) {
|
||||
return PlayerBase::Ptr(new WebRtcProxyPlayerImp(poller), release_func);
|
||||
}
|
||||
#endif//ENABLE_WEBRTC
|
||||
|
||||
throw std::invalid_argument("not supported play schema:" + url_in);
|
||||
}
|
||||
|
||||
@ -286,6 +286,10 @@ float PlayerProxy::getLossRate(MediaSource &sender, TrackType type) {
|
||||
return getPacketLossRate(type);
|
||||
}
|
||||
|
||||
toolkit::EventPoller::Ptr PlayerProxy::getOwnerPoller(MediaSource &sender) {
|
||||
return getPoller();
|
||||
}
|
||||
|
||||
TranslationInfo PlayerProxy::getTranslationInfo() {
|
||||
return _transtalion_info;
|
||||
}
|
||||
|
||||
@ -151,6 +151,7 @@ private:
|
||||
std::string getOriginUrl(MediaSource &sender) const override;
|
||||
std::shared_ptr<toolkit::SockInfo> getOriginSock(MediaSource &sender) const override;
|
||||
float getLossRate(MediaSource &sender, TrackType type) override;
|
||||
toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override;
|
||||
|
||||
void rePlay(const std::string &strUrl, int iFailedCnt);
|
||||
void onPlaySuccess();
|
||||
|
||||
@ -15,6 +15,9 @@
|
||||
#ifdef ENABLE_SRT
|
||||
#include "Srt/SrtPusher.h"
|
||||
#endif // ENABLE_SRT
|
||||
#ifdef ENABLE_WEBRTC
|
||||
#include "../webrtc/WebRtcProxyPusher.h"
|
||||
#endif // ENABLE_WEBRTC
|
||||
|
||||
using namespace toolkit;
|
||||
|
||||
@ -23,7 +26,8 @@ namespace mediakit {
|
||||
static bool checkMediaSourceAndUrlMatch(const MediaSource::Ptr &src, const std::string &url) {
|
||||
std::string prefix = findSubString(url.data(), NULL, "://");
|
||||
|
||||
if (strcasecmp("rtsps", prefix.data()) == 0 || strcasecmp("rtsp", prefix.data()) == 0) {
|
||||
if (strcasecmp("rtsps", prefix.data()) == 0 || strcasecmp("rtsp", prefix.data()) == 0 ||
|
||||
strcasecmp("webrtcs", prefix.data()) == 0 || strcasecmp("webrtc", prefix.data()) == 0 ) {
|
||||
auto rtsp_src = std::dynamic_pointer_cast<RtspMediaSource>(src);
|
||||
if (!rtsp_src) {
|
||||
return false;
|
||||
@ -91,6 +95,11 @@ PusherBase::Ptr PusherBase::createPusher(const EventPoller::Ptr &in_poller,
|
||||
}
|
||||
#endif//ENABLE_SRT
|
||||
|
||||
#ifdef ENABLE_WEBRTC
|
||||
if ((strcasecmp("webrtc", prefix.data()) == 0 || strcasecmp("webrtcs", prefix.data()) == 0)) {
|
||||
return PusherBase::Ptr(new WebRtcProxyPusherImp(poller, std::dynamic_pointer_cast<RtspMediaSource>(src)), release_func);
|
||||
}
|
||||
#endif//ENABLE_WEBRTC
|
||||
|
||||
throw std::invalid_argument("not supported push schema:" + url);
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@ public:
|
||||
|
||||
virtual size_t getSendSpeed() { return 0; }
|
||||
virtual size_t getSendTotalBytes() { return 0; }
|
||||
|
||||
|
||||
protected:
|
||||
virtual void onShutdown(const toolkit::SockException &ex) = 0;
|
||||
virtual void onPublishResult(const toolkit::SockException &ex) = 0;
|
||||
@ -139,11 +139,11 @@ public:
|
||||
size_t getSendSpeed() override {
|
||||
return _delegate ? _delegate->getSendSpeed() : Parent::getSendSpeed();
|
||||
}
|
||||
|
||||
|
||||
size_t getSendTotalBytes() override {
|
||||
return _delegate ? _delegate->getSendTotalBytes() : Parent::getSendTotalBytes();
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
void onShutdown(const toolkit::SockException &ex) override {
|
||||
if (_on_shutdown) {
|
||||
|
||||
@ -17,7 +17,7 @@ using namespace std;
|
||||
namespace mediakit {
|
||||
|
||||
HlsMaker::HlsMaker(bool is_fmp4, float seg_duration, uint32_t seg_number, bool seg_keep) {
|
||||
_is_fmp4 = is_fmp4;
|
||||
_is_fmp4 = is_fmp4;
|
||||
// 最小允许设置为0,0个切片代表点播 [AUTO-TRANSLATED:19235e8e]
|
||||
// Minimum allowed setting is 0, 0 slices represent on-demand
|
||||
_seg_number = seg_number;
|
||||
|
||||
@ -27,6 +27,11 @@ struct MediaTuple {
|
||||
std::string shortUrl() const {
|
||||
return vhost + '/' + app + '/' + stream;
|
||||
}
|
||||
|
||||
MediaTuple() = default;
|
||||
MediaTuple(std::string vhost, std::string app, std::string stream, std::string params = "")
|
||||
: vhost(std::move(vhost)), app(std::move(app)), stream(std::move(stream)), params(std::move(params)) {
|
||||
}
|
||||
};
|
||||
|
||||
class RecordInfo: public MediaTuple {
|
||||
|
||||
@ -29,7 +29,7 @@ public:
|
||||
|
||||
size_t getSendSpeed() override;
|
||||
size_t getSendTotalBytes() override;
|
||||
|
||||
|
||||
protected:
|
||||
//for Tcpclient override
|
||||
void onRecv(const toolkit::Buffer::Ptr &buf) override;
|
||||
|
||||
@ -1012,9 +1012,9 @@ float SrtCaller::getTimeOutSec() {
|
||||
GET_CONFIG(uint32_t, timeout, SRT::kTimeOutSec);
|
||||
if (timeout <= 0) {
|
||||
WarnL << "config srt " << kTimeOutSec << " not vaild";
|
||||
return 5 * 1000;
|
||||
return 5.0f;
|
||||
}
|
||||
return (float)timeout * (float)1000;
|
||||
return (float)timeout;
|
||||
};
|
||||
|
||||
std::string SrtCaller::generateStreamId() {
|
||||
|
||||
146
srt/Crypto.cpp
146
srt/Crypto.cpp
@ -49,11 +49,11 @@ inline const EVP_CIPHER* aes_key_len_mapping_ctr_cipher(int key_len) {
|
||||
static bool aes_wrap(const uint8_t* in, int in_len, uint8_t* out, int* outLen, uint8_t* key, int key_len) {
|
||||
|
||||
#if defined(ENABLE_OPENSSL)
|
||||
EVP_CIPHER_CTX* ctx = NULL;
|
||||
EVP_CIPHER_CTX* ctx = NULL;
|
||||
|
||||
*outLen = 0;
|
||||
|
||||
do {
|
||||
do {
|
||||
if (!(ctx = EVP_CIPHER_CTX_new())) {
|
||||
WarnL << "EVP_CIPHER_CTX_new fail";
|
||||
break;
|
||||
@ -62,29 +62,29 @@ static bool aes_wrap(const uint8_t* in, int in_len, uint8_t* out, int* outLen, u
|
||||
|
||||
if (1 != EVP_EncryptInit_ex(ctx, aes_key_len_mapping_wrap_cipher(key_len), NULL, key, NULL)) {
|
||||
WarnL << "EVP_EncryptInit_ex fail";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
int len1 = 0;
|
||||
if (1 != EVP_EncryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) {
|
||||
int len1 = 0;
|
||||
if (1 != EVP_EncryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) {
|
||||
WarnL << "EVP_EncryptUpdate fail";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
int len2 = 0;
|
||||
if (1 != EVP_EncryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) {
|
||||
int len2 = 0;
|
||||
if (1 != EVP_EncryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) {
|
||||
WarnL << "EVP_EncryptFinal_ex fail";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
*outLen = len1 + len2;
|
||||
} while (0);
|
||||
*outLen = len1 + len2;
|
||||
} while (0);
|
||||
|
||||
if (ctx != NULL) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
}
|
||||
if (ctx != NULL) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
}
|
||||
|
||||
return *outLen != 0;
|
||||
return *outLen != 0;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
@ -103,11 +103,11 @@ static bool aes_wrap(const uint8_t* in, int in_len, uint8_t* out, int* outLen, u
|
||||
static bool aes_unwrap(const uint8_t* in, int in_len, uint8_t* out, int* outLen, uint8_t* key, int key_len) {
|
||||
|
||||
#if defined(ENABLE_OPENSSL)
|
||||
EVP_CIPHER_CTX* ctx = NULL;
|
||||
EVP_CIPHER_CTX* ctx = NULL;
|
||||
|
||||
*outLen = 0;
|
||||
|
||||
do {
|
||||
do {
|
||||
|
||||
if (!(ctx = EVP_CIPHER_CTX_new())) {
|
||||
WarnL << "EVP_CIPHER_CTX_new fail";
|
||||
@ -117,8 +117,8 @@ static bool aes_unwrap(const uint8_t* in, int in_len, uint8_t* out, int* outLen,
|
||||
|
||||
if (1 != EVP_DecryptInit_ex(ctx, aes_key_len_mapping_wrap_cipher(key_len), NULL, key, NULL)) {
|
||||
WarnL << "EVP_DecryptInit_ex fail";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//设置pkcs7padding
|
||||
if (1 != EVP_CIPHER_CTX_set_padding(ctx, 1)) {
|
||||
@ -126,26 +126,26 @@ static bool aes_unwrap(const uint8_t* in, int in_len, uint8_t* out, int* outLen,
|
||||
break;
|
||||
}
|
||||
|
||||
int len1 = 0;
|
||||
if (1 != EVP_DecryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) {
|
||||
int len1 = 0;
|
||||
if (1 != EVP_DecryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) {
|
||||
WarnL << "EVP_DecryptUpdate fail";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
int len2 = 0;
|
||||
if (1 != EVP_DecryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) {
|
||||
int len2 = 0;
|
||||
if (1 != EVP_DecryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) {
|
||||
WarnL << "EVP_DecryptFinal_ex fail";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
*outLen = len1 + len2;
|
||||
} while (0);
|
||||
*outLen = len1 + len2;
|
||||
} while (0);
|
||||
|
||||
if (ctx != NULL) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
}
|
||||
if (ctx != NULL) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
}
|
||||
|
||||
return *outLen != 0;
|
||||
return *outLen != 0;
|
||||
|
||||
#else
|
||||
return false;
|
||||
@ -166,11 +166,11 @@ static bool aes_unwrap(const uint8_t* in, int in_len, uint8_t* out, int* outLen,
|
||||
static bool aes_ctr_encrypt(const uint8_t* in, int in_len, uint8_t* out, int* outLen, uint8_t* key, int key_len, uint8_t* iv) {
|
||||
|
||||
#if defined(ENABLE_OPENSSL)
|
||||
EVP_CIPHER_CTX* ctx = NULL;
|
||||
EVP_CIPHER_CTX* ctx = NULL;
|
||||
|
||||
*outLen = 0;
|
||||
|
||||
do {
|
||||
do {
|
||||
if (!(ctx = EVP_CIPHER_CTX_new())) {
|
||||
WarnL << "EVP_CIPHER_CTX_new fail";
|
||||
break;
|
||||
@ -178,29 +178,29 @@ static bool aes_ctr_encrypt(const uint8_t* in, int in_len, uint8_t* out, int* ou
|
||||
|
||||
if (1 != EVP_EncryptInit_ex(ctx, aes_key_len_mapping_ctr_cipher(key_len), NULL, key, iv)) {
|
||||
WarnL << "EVP_EncryptInit_ex fail";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
int len1 = 0;
|
||||
if (1 != EVP_EncryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) {
|
||||
int len1 = 0;
|
||||
if (1 != EVP_EncryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) {
|
||||
WarnL << "EVP_EncryptUpdate fail";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
int len2 = 0;
|
||||
if (1 != EVP_EncryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) {
|
||||
int len2 = 0;
|
||||
if (1 != EVP_EncryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) {
|
||||
WarnL << "EVP_EncryptFinal_ex fail";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
*outLen = len1 + len2;
|
||||
} while (0);
|
||||
*outLen = len1 + len2;
|
||||
} while (0);
|
||||
|
||||
if (ctx != NULL) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
}
|
||||
if (ctx != NULL) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
}
|
||||
|
||||
return *outLen != 0;
|
||||
return *outLen != 0;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
@ -221,42 +221,42 @@ static bool aes_ctr_encrypt(const uint8_t* in, int in_len, uint8_t* out, int* ou
|
||||
static bool aes_ctr_decrypt(const uint8_t* in, int in_len, uint8_t* out, int* outLen, uint8_t* key, int key_len, uint8_t* iv) {
|
||||
|
||||
#if defined(ENABLE_OPENSSL)
|
||||
EVP_CIPHER_CTX* ctx = NULL;
|
||||
EVP_CIPHER_CTX* ctx = NULL;
|
||||
|
||||
*outLen = 0;
|
||||
|
||||
do {
|
||||
do {
|
||||
|
||||
if (!(ctx = EVP_CIPHER_CTX_new())) {
|
||||
WarnL << "EVP_CIPHER_CTX_new fail";
|
||||
break;
|
||||
}
|
||||
|
||||
if (1 != EVP_DecryptInit_ex(ctx, aes_key_len_mapping_ctr_cipher(key_len), NULL, key, iv)) {
|
||||
if (1 != EVP_DecryptInit_ex(ctx, aes_key_len_mapping_ctr_cipher(key_len), NULL, key, iv)) {
|
||||
WarnL << "EVP_DecryptInit_ex fail";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
int len1 = 0;
|
||||
if (1 != EVP_DecryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) {
|
||||
int len1 = 0;
|
||||
if (1 != EVP_DecryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) {
|
||||
WarnL << "EVP_DecryptUpdate fail";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
int len2 = 0;
|
||||
if (1 != EVP_DecryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) {
|
||||
int len2 = 0;
|
||||
if (1 != EVP_DecryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) {
|
||||
WarnL << "EVP_DecryptFinal_ex fail";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
*outLen = len1 + len2;
|
||||
} while (0);
|
||||
*outLen = len1 + len2;
|
||||
} while (0);
|
||||
|
||||
if (ctx != NULL) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
}
|
||||
if (ctx != NULL) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
}
|
||||
|
||||
return *outLen != 0;
|
||||
return *outLen != 0;
|
||||
|
||||
#else
|
||||
return false;
|
||||
|
||||
@ -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_KMREQ:
|
||||
case HSExt::SRT_CMD_KMRSP:
|
||||
ext = std::make_shared<HSExtKeyMaterial>(); break;
|
||||
ext = std::make_shared<HSExtKeyMaterial>(); break;
|
||||
default: WarnL << "not support ext " << type; break;
|
||||
}
|
||||
if (ext) {
|
||||
|
||||
@ -1,29 +1,29 @@
|
||||
#ifndef ZLMEDIAKIT_SRT_SESSION_H
|
||||
#define ZLMEDIAKIT_SRT_SESSION_H
|
||||
|
||||
#include "Network/Session.h"
|
||||
#include "SrtTransport.hpp"
|
||||
|
||||
namespace SRT {
|
||||
|
||||
using namespace toolkit;
|
||||
|
||||
class SrtSession : public Session {
|
||||
public:
|
||||
SrtSession(const Socket::Ptr &sock);
|
||||
|
||||
void onRecv(const Buffer::Ptr &) override;
|
||||
void onError(const SockException &err) override;
|
||||
void onManager() override;
|
||||
void attachServer(const toolkit::Server &server) override;
|
||||
static EventPoller::Ptr queryPoller(const Buffer::Ptr &buffer);
|
||||
|
||||
private:
|
||||
bool _find_transport = true;
|
||||
Ticker _ticker;
|
||||
struct sockaddr_storage _peer_addr;
|
||||
SrtTransport::Ptr _transport;
|
||||
};
|
||||
|
||||
} // namespace SRT
|
||||
#endif // ZLMEDIAKIT_SRT_SESSION_H
|
||||
#ifndef ZLMEDIAKIT_SRT_SESSION_H
|
||||
#define ZLMEDIAKIT_SRT_SESSION_H
|
||||
|
||||
#include "Network/Session.h"
|
||||
#include "SrtTransport.hpp"
|
||||
|
||||
namespace SRT {
|
||||
|
||||
using namespace toolkit;
|
||||
|
||||
class SrtSession : public Session {
|
||||
public:
|
||||
SrtSession(const Socket::Ptr &sock);
|
||||
|
||||
void onRecv(const Buffer::Ptr &) override;
|
||||
void onError(const SockException &err) override;
|
||||
void onManager() override;
|
||||
void attachServer(const toolkit::Server &server) override;
|
||||
static EventPoller::Ptr queryPoller(const Buffer::Ptr &buffer);
|
||||
|
||||
private:
|
||||
bool _find_transport = true;
|
||||
Ticker _ticker;
|
||||
struct sockaddr_storage _peer_addr;
|
||||
SrtTransport::Ptr _transport;
|
||||
};
|
||||
|
||||
} // namespace SRT
|
||||
#endif // ZLMEDIAKIT_SRT_SESSION_H
|
||||
|
||||
@ -400,7 +400,7 @@ void SrtTransport::sendMsgDropReq(uint32_t first, uint32_t last) {
|
||||
}
|
||||
|
||||
void SrtTransport::tryAnnounceKeyMaterial() {
|
||||
//TraceL;
|
||||
//TraceL;
|
||||
|
||||
if (!_crypto) {
|
||||
return;
|
||||
|
||||
@ -169,8 +169,8 @@ private:
|
||||
|
||||
// for encryption
|
||||
Crypto::Ptr _crypto;
|
||||
Timer::Ptr _announce_timer;
|
||||
KeyMaterialPacket::Ptr _announce_req;
|
||||
Timer::Ptr _announce_timer;
|
||||
KeyMaterialPacket::Ptr _announce_req;
|
||||
};
|
||||
|
||||
class SrtTransportManager {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,254 +1,254 @@
|
||||
/**
|
||||
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_DTLS_TRANSPORT_HPP
|
||||
#define MS_RTC_DTLS_TRANSPORT_HPP
|
||||
|
||||
#include "SrtpSession.hpp"
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "Poller/Timer.h"
|
||||
#include "Poller/EventPoller.h"
|
||||
using namespace toolkit;
|
||||
|
||||
namespace RTC
|
||||
{
|
||||
class DtlsTransport : public std::enable_shared_from_this<DtlsTransport>
|
||||
{
|
||||
public:
|
||||
enum class DtlsState
|
||||
{
|
||||
NEW = 1,
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
FAILED,
|
||||
CLOSED
|
||||
};
|
||||
|
||||
public:
|
||||
enum class Role
|
||||
{
|
||||
NONE = 0,
|
||||
AUTO = 1,
|
||||
CLIENT,
|
||||
SERVER
|
||||
};
|
||||
|
||||
public:
|
||||
enum class FingerprintAlgorithm
|
||||
{
|
||||
NONE = 0,
|
||||
SHA1 = 1,
|
||||
SHA224,
|
||||
SHA256,
|
||||
SHA384,
|
||||
SHA512
|
||||
};
|
||||
|
||||
public:
|
||||
struct Fingerprint
|
||||
{
|
||||
FingerprintAlgorithm algorithm{ FingerprintAlgorithm::NONE };
|
||||
std::string value;
|
||||
};
|
||||
|
||||
private:
|
||||
struct SrtpCryptoSuiteMapEntry
|
||||
{
|
||||
RTC::SrtpSession::CryptoSuite cryptoSuite;
|
||||
const char* name;
|
||||
};
|
||||
|
||||
class DtlsEnvironment : public std::enable_shared_from_this<DtlsEnvironment>
|
||||
{
|
||||
public:
|
||||
using Ptr = std::shared_ptr<DtlsEnvironment>;
|
||||
~DtlsEnvironment();
|
||||
static DtlsEnvironment& Instance();
|
||||
|
||||
private:
|
||||
DtlsEnvironment();
|
||||
void GenerateCertificateAndPrivateKey();
|
||||
bool ReadCertificateAndPrivateKeyFromContext(SSL_CTX *ctx);
|
||||
void CreateSslCtx();
|
||||
void GenerateFingerprints();
|
||||
|
||||
public:
|
||||
X509* certificate{ nullptr };
|
||||
EVP_PKEY* privateKey{ nullptr };
|
||||
SSL_CTX* sslCtx{ nullptr };
|
||||
std::vector<Fingerprint> localFingerprints;
|
||||
};
|
||||
|
||||
public:
|
||||
class Listener
|
||||
{
|
||||
public:
|
||||
// DTLS is in the process of negotiating a secure connection. Incoming
|
||||
// media can flow through.
|
||||
// NOTE: The caller MUST NOT call any method during this callback.
|
||||
virtual void OnDtlsTransportConnecting(const RTC::DtlsTransport* dtlsTransport) = 0;
|
||||
// DTLS has completed negotiation of a secure connection (including DTLS-SRTP
|
||||
// and remote fingerprint verification). Outgoing media can now flow through.
|
||||
// NOTE: The caller MUST NOT call any method during this callback.
|
||||
virtual void OnDtlsTransportConnected(
|
||||
const RTC::DtlsTransport* dtlsTransport,
|
||||
RTC::SrtpSession::CryptoSuite srtpCryptoSuite,
|
||||
uint8_t* srtpLocalKey,
|
||||
size_t srtpLocalKeyLen,
|
||||
uint8_t* srtpRemoteKey,
|
||||
size_t srtpRemoteKeyLen,
|
||||
std::string& remoteCert) = 0;
|
||||
// The DTLS connection has been closed as the result of an error (such as a
|
||||
// DTLS alert or a failure to validate the remote fingerprint).
|
||||
virtual void OnDtlsTransportFailed(const RTC::DtlsTransport* dtlsTransport) = 0;
|
||||
// The DTLS connection has been closed due to receipt of a close_notify alert.
|
||||
virtual void OnDtlsTransportClosed(const RTC::DtlsTransport* dtlsTransport) = 0;
|
||||
// Need to send DTLS data to the peer.
|
||||
virtual void OnDtlsTransportSendData(
|
||||
const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) = 0;
|
||||
// DTLS application data received.
|
||||
virtual void OnDtlsTransportApplicationDataReceived(
|
||||
const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) = 0;
|
||||
};
|
||||
|
||||
public:
|
||||
static Role StringToRole(const std::string& role)
|
||||
{
|
||||
auto it = DtlsTransport::string2Role.find(role);
|
||||
|
||||
if (it != DtlsTransport::string2Role.end())
|
||||
return it->second;
|
||||
else
|
||||
return DtlsTransport::Role::NONE;
|
||||
}
|
||||
static FingerprintAlgorithm GetFingerprintAlgorithm(const std::string& fingerprint)
|
||||
{
|
||||
auto it = DtlsTransport::string2FingerprintAlgorithm.find(fingerprint);
|
||||
|
||||
if (it != DtlsTransport::string2FingerprintAlgorithm.end())
|
||||
return it->second;
|
||||
else
|
||||
return DtlsTransport::FingerprintAlgorithm::NONE;
|
||||
}
|
||||
static std::string& GetFingerprintAlgorithmString(FingerprintAlgorithm fingerprint)
|
||||
{
|
||||
auto it = DtlsTransport::fingerprintAlgorithm2String.find(fingerprint);
|
||||
|
||||
return it->second;
|
||||
}
|
||||
static bool IsDtls(const uint8_t* data, size_t len)
|
||||
{
|
||||
// clang-format off
|
||||
return (
|
||||
// Minimum DTLS record length is 13 bytes.
|
||||
(len >= 13) &&
|
||||
// DOC: https://tools.ietf.org/html/draft-ietf-avtcore-rfc5764-mux-fixes
|
||||
(data[0] > 19 && data[0] < 64)
|
||||
);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
private:
|
||||
static std::map<std::string, Role> string2Role;
|
||||
static std::map<std::string, FingerprintAlgorithm> string2FingerprintAlgorithm;
|
||||
static std::map<FingerprintAlgorithm, std::string> fingerprintAlgorithm2String;
|
||||
static std::vector<SrtpCryptoSuiteMapEntry> srtpCryptoSuites;
|
||||
|
||||
public:
|
||||
DtlsTransport(EventPoller::Ptr poller, Listener* listener);
|
||||
~DtlsTransport();
|
||||
|
||||
public:
|
||||
void Dump() const;
|
||||
void Run(Role localRole);
|
||||
std::vector<Fingerprint>& GetLocalFingerprints() const
|
||||
{
|
||||
return env->localFingerprints;
|
||||
}
|
||||
bool SetRemoteFingerprint(Fingerprint fingerprint);
|
||||
void ProcessDtlsData(const uint8_t* data, size_t len);
|
||||
DtlsState GetState() const
|
||||
{
|
||||
return this->state;
|
||||
}
|
||||
Role GetLocalRole() const
|
||||
{
|
||||
return this->localRole;
|
||||
}
|
||||
void SendApplicationData(const uint8_t* data, size_t len);
|
||||
|
||||
private:
|
||||
bool IsRunning() const
|
||||
{
|
||||
switch (this->state)
|
||||
{
|
||||
case DtlsState::NEW:
|
||||
return false;
|
||||
case DtlsState::CONNECTING:
|
||||
case DtlsState::CONNECTED:
|
||||
return true;
|
||||
case DtlsState::FAILED:
|
||||
case DtlsState::CLOSED:
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make GCC 4.9 happy.
|
||||
return false;
|
||||
}
|
||||
void Reset();
|
||||
bool CheckStatus(int returnCode);
|
||||
void SendPendingOutgoingDtlsData();
|
||||
bool SetTimeout();
|
||||
bool ProcessHandshake();
|
||||
bool CheckRemoteFingerprint();
|
||||
void ExtractSrtpKeys(RTC::SrtpSession::CryptoSuite srtpCryptoSuite);
|
||||
RTC::SrtpSession::CryptoSuite GetNegotiatedSrtpCryptoSuite();
|
||||
|
||||
private:
|
||||
void OnSslInfo(int where, int ret);
|
||||
void OnTimer();
|
||||
|
||||
private:
|
||||
DtlsEnvironment::Ptr env;
|
||||
EventPoller::Ptr poller;
|
||||
// Passed by argument.
|
||||
Listener* listener{ nullptr };
|
||||
// Allocated by this.
|
||||
SSL* ssl{ nullptr };
|
||||
BIO* sslBioFromNetwork{ nullptr }; // The BIO from which ssl reads.
|
||||
BIO* sslBioToNetwork{ nullptr }; // The BIO in which ssl writes.
|
||||
Timer::Ptr timer;
|
||||
// Others.
|
||||
DtlsState state{ DtlsState::NEW };
|
||||
Role localRole{ Role::NONE };
|
||||
Fingerprint remoteFingerprint;
|
||||
bool handshakeDone{ false };
|
||||
bool handshakeDoneNow{ false };
|
||||
std::string remoteCert;
|
||||
//最大不超过mtu
|
||||
static constexpr int SslReadBufferSize{ 2000 };
|
||||
uint8_t sslReadBuffer[SslReadBufferSize];
|
||||
};
|
||||
} // namespace RTC
|
||||
|
||||
#endif
|
||||
/**
|
||||
ISC License
|
||||
|
||||
Copyright © 2015, Iñaki Baz Castillo <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_DTLS_TRANSPORT_HPP
|
||||
#define MS_RTC_DTLS_TRANSPORT_HPP
|
||||
|
||||
#include "SrtpSession.hpp"
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "Poller/Timer.h"
|
||||
#include "Poller/EventPoller.h"
|
||||
|
||||
namespace RTC
|
||||
{
|
||||
class DtlsTransport : public std::enable_shared_from_this<DtlsTransport>
|
||||
{
|
||||
public:
|
||||
using Ptr = std::shared_ptr<DtlsTransport>;
|
||||
enum class DtlsState
|
||||
{
|
||||
NEW = 1,
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
FAILED,
|
||||
CLOSED
|
||||
};
|
||||
|
||||
public:
|
||||
enum class Role
|
||||
{
|
||||
NONE = 0,
|
||||
AUTO = 1,
|
||||
CLIENT,
|
||||
SERVER
|
||||
};
|
||||
|
||||
public:
|
||||
enum class FingerprintAlgorithm
|
||||
{
|
||||
NONE = 0,
|
||||
SHA1 = 1,
|
||||
SHA224,
|
||||
SHA256,
|
||||
SHA384,
|
||||
SHA512
|
||||
};
|
||||
|
||||
public:
|
||||
struct Fingerprint
|
||||
{
|
||||
FingerprintAlgorithm algorithm{ FingerprintAlgorithm::NONE };
|
||||
std::string value;
|
||||
};
|
||||
|
||||
private:
|
||||
struct SrtpCryptoSuiteMapEntry
|
||||
{
|
||||
RTC::SrtpSession::CryptoSuite cryptoSuite;
|
||||
const char* name;
|
||||
};
|
||||
|
||||
class DtlsEnvironment : public std::enable_shared_from_this<DtlsEnvironment>
|
||||
{
|
||||
public:
|
||||
using Ptr = std::shared_ptr<DtlsEnvironment>;
|
||||
~DtlsEnvironment();
|
||||
static DtlsEnvironment& Instance();
|
||||
|
||||
private:
|
||||
DtlsEnvironment();
|
||||
void GenerateCertificateAndPrivateKey();
|
||||
bool ReadCertificateAndPrivateKeyFromContext(SSL_CTX *ctx);
|
||||
void CreateSslCtx();
|
||||
void GenerateFingerprints();
|
||||
|
||||
public:
|
||||
X509* certificate{ nullptr };
|
||||
EVP_PKEY* privateKey{ nullptr };
|
||||
SSL_CTX* sslCtx{ nullptr };
|
||||
std::vector<Fingerprint> localFingerprints;
|
||||
};
|
||||
|
||||
public:
|
||||
class Listener
|
||||
{
|
||||
public:
|
||||
// DTLS is in the process of negotiating a secure connection. Incoming
|
||||
// media can flow through.
|
||||
// NOTE: The caller MUST NOT call any method during this callback.
|
||||
virtual void OnDtlsTransportConnecting(const RTC::DtlsTransport* dtlsTransport) = 0;
|
||||
// DTLS has completed negotiation of a secure connection (including DTLS-SRTP
|
||||
// and remote fingerprint verification). Outgoing media can now flow through.
|
||||
// NOTE: The caller MUST NOT call any method during this callback.
|
||||
virtual void OnDtlsTransportConnected(
|
||||
const RTC::DtlsTransport* dtlsTransport,
|
||||
RTC::SrtpSession::CryptoSuite srtpCryptoSuite,
|
||||
uint8_t* srtpLocalKey,
|
||||
size_t srtpLocalKeyLen,
|
||||
uint8_t* srtpRemoteKey,
|
||||
size_t srtpRemoteKeyLen,
|
||||
std::string& remoteCert) = 0;
|
||||
// The DTLS connection has been closed as the result of an error (such as a
|
||||
// DTLS alert or a failure to validate the remote fingerprint).
|
||||
virtual void OnDtlsTransportFailed(const RTC::DtlsTransport* dtlsTransport) = 0;
|
||||
// The DTLS connection has been closed due to receipt of a close_notify alert.
|
||||
virtual void OnDtlsTransportClosed(const RTC::DtlsTransport* dtlsTransport) = 0;
|
||||
// Need to send DTLS data to the peer.
|
||||
virtual void OnDtlsTransportSendData(
|
||||
const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) = 0;
|
||||
// DTLS application data received.
|
||||
virtual void OnDtlsTransportApplicationDataReceived(
|
||||
const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) = 0;
|
||||
};
|
||||
|
||||
public:
|
||||
static Role StringToRole(const std::string& role)
|
||||
{
|
||||
auto it = DtlsTransport::string2Role.find(role);
|
||||
|
||||
if (it != DtlsTransport::string2Role.end())
|
||||
return it->second;
|
||||
else
|
||||
return DtlsTransport::Role::NONE;
|
||||
}
|
||||
static FingerprintAlgorithm GetFingerprintAlgorithm(const std::string& fingerprint)
|
||||
{
|
||||
auto it = DtlsTransport::string2FingerprintAlgorithm.find(fingerprint);
|
||||
|
||||
if (it != DtlsTransport::string2FingerprintAlgorithm.end())
|
||||
return it->second;
|
||||
else
|
||||
return DtlsTransport::FingerprintAlgorithm::NONE;
|
||||
}
|
||||
static std::string& GetFingerprintAlgorithmString(FingerprintAlgorithm fingerprint)
|
||||
{
|
||||
auto it = DtlsTransport::fingerprintAlgorithm2String.find(fingerprint);
|
||||
|
||||
return it->second;
|
||||
}
|
||||
static bool IsDtls(const uint8_t* data, size_t len)
|
||||
{
|
||||
// clang-format off
|
||||
return (
|
||||
// Minimum DTLS record length is 13 bytes.
|
||||
(len >= 13) &&
|
||||
// DOC: https://tools.ietf.org/html/draft-ietf-avtcore-rfc5764-mux-fixes
|
||||
(data[0] > 19 && data[0] < 64)
|
||||
);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
private:
|
||||
static std::map<std::string, Role> string2Role;
|
||||
static std::map<std::string, FingerprintAlgorithm> string2FingerprintAlgorithm;
|
||||
static std::map<FingerprintAlgorithm, std::string> fingerprintAlgorithm2String;
|
||||
static std::vector<SrtpCryptoSuiteMapEntry> srtpCryptoSuites;
|
||||
|
||||
public:
|
||||
DtlsTransport(toolkit::EventPoller::Ptr poller, Listener* listener);
|
||||
~DtlsTransport();
|
||||
|
||||
public:
|
||||
void Dump() const;
|
||||
void Run(Role localRole);
|
||||
std::vector<Fingerprint>& GetLocalFingerprints() const
|
||||
{
|
||||
return env->localFingerprints;
|
||||
}
|
||||
bool SetRemoteFingerprint(Fingerprint fingerprint);
|
||||
void ProcessDtlsData(const uint8_t* data, size_t len);
|
||||
DtlsState GetState() const
|
||||
{
|
||||
return this->state;
|
||||
}
|
||||
Role GetLocalRole() const
|
||||
{
|
||||
return this->localRole;
|
||||
}
|
||||
void SendApplicationData(const uint8_t* data, size_t len);
|
||||
|
||||
private:
|
||||
bool IsRunning() const
|
||||
{
|
||||
switch (this->state)
|
||||
{
|
||||
case DtlsState::NEW:
|
||||
return false;
|
||||
case DtlsState::CONNECTING:
|
||||
case DtlsState::CONNECTED:
|
||||
return true;
|
||||
case DtlsState::FAILED:
|
||||
case DtlsState::CLOSED:
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make GCC 4.9 happy.
|
||||
return false;
|
||||
}
|
||||
void Reset();
|
||||
bool CheckStatus(int returnCode);
|
||||
void SendPendingOutgoingDtlsData();
|
||||
bool SetTimeout();
|
||||
bool ProcessHandshake();
|
||||
bool CheckRemoteFingerprint();
|
||||
void ExtractSrtpKeys(RTC::SrtpSession::CryptoSuite srtpCryptoSuite);
|
||||
RTC::SrtpSession::CryptoSuite GetNegotiatedSrtpCryptoSuite();
|
||||
|
||||
private:
|
||||
void OnSslInfo(int where, int ret);
|
||||
void OnTimer();
|
||||
|
||||
private:
|
||||
DtlsEnvironment::Ptr env;
|
||||
toolkit::EventPoller::Ptr poller;
|
||||
// Passed by argument.
|
||||
Listener* listener{ nullptr };
|
||||
// Allocated by this.
|
||||
SSL* ssl{ nullptr };
|
||||
BIO* sslBioFromNetwork{ nullptr }; // The BIO from which ssl reads.
|
||||
BIO* sslBioToNetwork{ nullptr }; // The BIO in which ssl writes.
|
||||
toolkit::Timer::Ptr timer;
|
||||
// Others.
|
||||
DtlsState state{ DtlsState::NEW };
|
||||
Role localRole{ Role::NONE };
|
||||
Fingerprint remoteFingerprint;
|
||||
bool handshakeDone{ false };
|
||||
bool handshakeDoneNow{ false };
|
||||
std::string remoteCert;
|
||||
//最大不超过mtu
|
||||
static constexpr int SslReadBufferSize{ 2000 };
|
||||
uint8_t sslReadBuffer[SslReadBufferSize];
|
||||
};
|
||||
} // namespace RTC
|
||||
|
||||
#endif
|
||||
|
||||
@ -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
|
||||
@ -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
139
webrtc/IceSession.cpp
Normal 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
70
webrtc/IceSession.hpp
Normal 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
2026
webrtc/IceTransport.cpp
Normal file
File diff suppressed because it is too large
Load Diff
754
webrtc/IceTransport.hpp
Normal file
754
webrtc/IceTransport.hpp
Normal 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
|
||||
180
webrtc/Nack.h
180
webrtc/Nack.h
@ -1,90 +1,90 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT-like license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef ZLMEDIAKIT_NACK_H
|
||||
#define ZLMEDIAKIT_NACK_H
|
||||
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <deque>
|
||||
#include <unordered_map>
|
||||
#include "Rtsp/Rtsp.h"
|
||||
#include "Rtcp/RtcpFCI.h"
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
// RTC配置项目 [AUTO-TRANSLATED:19940011]
|
||||
// RTC configuration project
|
||||
namespace Rtc {
|
||||
// ~ nack发送端,rtp接收端 [AUTO-TRANSLATED:bb169205]
|
||||
// ~ nack sender, rtp receiver
|
||||
// 最大保留的rtp丢包状态个数 [AUTO-TRANSLATED:70eee442]
|
||||
// Maximum number of retained rtp packet loss states
|
||||
extern const std::string kNackMaxSize;
|
||||
// rtp丢包状态最长保留时间 [AUTO-TRANSLATED:f9306375]
|
||||
// Maximum retention time for rtp packet loss states
|
||||
extern const std::string kNackMaxMS;
|
||||
} // namespace Rtc
|
||||
|
||||
class NackList {
|
||||
public:
|
||||
void pushBack(RtpPacket::Ptr rtp);
|
||||
void forEach(const FCI_NACK &nack, const std::function<void(const RtpPacket::Ptr &rtp)> &cb);
|
||||
|
||||
private:
|
||||
void popFront();
|
||||
uint32_t getCacheMS();
|
||||
int64_t getNtpStamp(uint16_t seq);
|
||||
RtpPacket::Ptr *getRtp(uint16_t seq);
|
||||
|
||||
private:
|
||||
uint32_t _cache_ms_check = 0;
|
||||
std::deque<uint16_t> _nack_cache_seq;
|
||||
std::unordered_map<uint16_t, RtpPacket::Ptr> _nack_cache_pkt;
|
||||
};
|
||||
|
||||
class NackContext {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<NackContext>;
|
||||
using onNack = std::function<void(const FCI_NACK &nack)>;
|
||||
|
||||
NackContext();
|
||||
|
||||
void received(uint16_t seq, bool is_rtx = false);
|
||||
void setOnNack(onNack cb);
|
||||
uint64_t reSendNack();
|
||||
|
||||
private:
|
||||
void eraseFrontSeq();
|
||||
void doNack(const FCI_NACK &nack, bool record_nack);
|
||||
void recordNack(const FCI_NACK &nack);
|
||||
void clearNackStatus(uint16_t seq);
|
||||
void makeNack(uint16_t max, bool flush = false);
|
||||
|
||||
private:
|
||||
bool _started = false;
|
||||
int _rtt = 50;
|
||||
onNack _cb;
|
||||
std::set<uint16_t> _seq;
|
||||
// 最新nack包中的rtp seq值 [AUTO-TRANSLATED:6984d95a]
|
||||
// RTP seq value in the latest nack packet
|
||||
uint16_t _nack_seq = 0;
|
||||
|
||||
struct NackStatus {
|
||||
uint64_t first_stamp;
|
||||
uint64_t update_stamp;
|
||||
uint32_t nack_count = 0;
|
||||
};
|
||||
std::map<uint16_t /*seq*/, NackStatus> _nack_send_status;
|
||||
};
|
||||
|
||||
} // namespace mediakit
|
||||
|
||||
#endif //ZLMEDIAKIT_NACK_H
|
||||
/*
|
||||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT-like license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef ZLMEDIAKIT_NACK_H
|
||||
#define ZLMEDIAKIT_NACK_H
|
||||
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <deque>
|
||||
#include <unordered_map>
|
||||
#include "Rtsp/Rtsp.h"
|
||||
#include "Rtcp/RtcpFCI.h"
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
// RTC配置项目 [AUTO-TRANSLATED:19940011]
|
||||
// RTC configuration project
|
||||
namespace Rtc {
|
||||
// ~ nack发送端,rtp接收端 [AUTO-TRANSLATED:bb169205]
|
||||
// ~ nack sender, rtp receiver
|
||||
// 最大保留的rtp丢包状态个数 [AUTO-TRANSLATED:70eee442]
|
||||
// Maximum number of retained rtp packet loss states
|
||||
extern const std::string kNackMaxSize;
|
||||
// rtp丢包状态最长保留时间 [AUTO-TRANSLATED:f9306375]
|
||||
// Maximum retention time for rtp packet loss states
|
||||
extern const std::string kNackMaxMS;
|
||||
} // namespace Rtc
|
||||
|
||||
class NackList {
|
||||
public:
|
||||
void pushBack(RtpPacket::Ptr rtp);
|
||||
void forEach(const FCI_NACK &nack, const std::function<void(const RtpPacket::Ptr &rtp)> &cb);
|
||||
|
||||
private:
|
||||
void popFront();
|
||||
uint32_t getCacheMS();
|
||||
int64_t getNtpStamp(uint16_t seq);
|
||||
RtpPacket::Ptr *getRtp(uint16_t seq);
|
||||
|
||||
private:
|
||||
uint32_t _cache_ms_check = 0;
|
||||
std::deque<uint16_t> _nack_cache_seq;
|
||||
std::unordered_map<uint16_t, RtpPacket::Ptr> _nack_cache_pkt;
|
||||
};
|
||||
|
||||
class NackContext {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<NackContext>;
|
||||
using onNack = std::function<void(const FCI_NACK &nack)>;
|
||||
|
||||
NackContext();
|
||||
|
||||
void received(uint16_t seq, bool is_rtx = false);
|
||||
void setOnNack(onNack cb);
|
||||
uint64_t reSendNack();
|
||||
|
||||
private:
|
||||
void eraseFrontSeq();
|
||||
void doNack(const FCI_NACK &nack, bool record_nack);
|
||||
void recordNack(const FCI_NACK &nack);
|
||||
void clearNackStatus(uint16_t seq);
|
||||
void makeNack(uint16_t max, bool flush = false);
|
||||
|
||||
private:
|
||||
bool _started = false;
|
||||
int _rtt = 50;
|
||||
onNack _cb;
|
||||
std::set<uint16_t> _seq;
|
||||
// 最新nack包中的rtp seq值 [AUTO-TRANSLATED:6984d95a]
|
||||
// RTP seq value in the latest nack packet
|
||||
uint16_t _nack_seq = 0;
|
||||
|
||||
struct NackStatus {
|
||||
uint64_t first_stamp;
|
||||
uint64_t update_stamp;
|
||||
uint32_t nack_count = 0;
|
||||
};
|
||||
std::map<uint16_t /*seq*/, NackStatus> _nack_send_status;
|
||||
};
|
||||
|
||||
} // namespace mediakit
|
||||
|
||||
#endif //ZLMEDIAKIT_NACK_H
|
||||
|
||||
1318
webrtc/RtpExt.cpp
1318
webrtc/RtpExt.cpp
File diff suppressed because it is too large
Load Diff
210
webrtc/RtpMap.h
Normal file
210
webrtc/RtpMap.h
Normal 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
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
#ifdef ENABLE_SCTP
|
||||
#include <usrsctp.h>
|
||||
#include "Utils.hpp"
|
||||
#include "Util/Byte.hpp"
|
||||
#include "Poller/EventPoller.h"
|
||||
|
||||
namespace RTC
|
||||
@ -62,8 +62,8 @@ namespace RTC
|
||||
return (
|
||||
(len >= 12) &&
|
||||
// Must have Source Port Number and Destination Port Number set to 5000 (hack).
|
||||
(Utils::Byte::Get2Bytes(data, 0) == 5000) &&
|
||||
(Utils::Byte::Get2Bytes(data, 2) == 5000)
|
||||
(toolkit::Byte::Get2Bytes(data, 0) == 5000) &&
|
||||
(toolkit::Byte::Get2Bytes(data, 2) == 5000)
|
||||
);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
3983
webrtc/Sdp.cpp
3983
webrtc/Sdp.cpp
File diff suppressed because it is too large
Load Diff
1555
webrtc/Sdp.h
1555
webrtc/Sdp.h
File diff suppressed because it is too large
Load Diff
@ -1,64 +1,65 @@
|
||||
/**
|
||||
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_SRTP_SESSION_HPP
|
||||
#define MS_RTC_SRTP_SESSION_HPP
|
||||
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
typedef struct srtp_ctx_t_ *srtp_t;
|
||||
|
||||
namespace RTC {
|
||||
|
||||
class DepLibSRTP;
|
||||
|
||||
class SrtpSession {
|
||||
public:
|
||||
enum class CryptoSuite {
|
||||
NONE = 0,
|
||||
AES_CM_128_HMAC_SHA1_80 = 1,
|
||||
AES_CM_128_HMAC_SHA1_32,
|
||||
AEAD_AES_256_GCM,
|
||||
AEAD_AES_128_GCM
|
||||
};
|
||||
|
||||
public:
|
||||
enum class Type { INBOUND = 1, OUTBOUND };
|
||||
|
||||
public:
|
||||
SrtpSession(Type type, CryptoSuite cryptoSuite, uint8_t *key, size_t keyLen);
|
||||
~SrtpSession();
|
||||
|
||||
public:
|
||||
bool EncryptRtp(uint8_t *data, int *len);
|
||||
bool DecryptSrtp(uint8_t *data, int *len);
|
||||
bool EncryptRtcp(uint8_t *data, int *len);
|
||||
bool DecryptSrtcp(uint8_t *data, int *len);
|
||||
void RemoveStream(uint32_t ssrc);
|
||||
|
||||
private:
|
||||
// Allocated by this.
|
||||
srtp_t session { nullptr };
|
||||
std::shared_ptr<DepLibSRTP> _env;
|
||||
};
|
||||
|
||||
} // namespace RTC
|
||||
|
||||
#endif
|
||||
/**
|
||||
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_SRTP_SESSION_HPP
|
||||
#define MS_RTC_SRTP_SESSION_HPP
|
||||
|
||||
#include "Util/Byte.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
typedef struct srtp_ctx_t_ *srtp_t;
|
||||
|
||||
namespace RTC {
|
||||
|
||||
class DepLibSRTP;
|
||||
|
||||
class SrtpSession {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<SrtpSession>;
|
||||
enum class CryptoSuite {
|
||||
NONE = 0,
|
||||
AES_CM_128_HMAC_SHA1_80 = 1,
|
||||
AES_CM_128_HMAC_SHA1_32,
|
||||
AEAD_AES_256_GCM,
|
||||
AEAD_AES_128_GCM
|
||||
};
|
||||
|
||||
public:
|
||||
enum class Type { INBOUND = 1, OUTBOUND };
|
||||
|
||||
public:
|
||||
SrtpSession(Type type, CryptoSuite cryptoSuite, uint8_t *key, size_t keyLen);
|
||||
~SrtpSession();
|
||||
|
||||
public:
|
||||
bool EncryptRtp(uint8_t *data, int *len);
|
||||
bool DecryptSrtp(uint8_t *data, int *len);
|
||||
bool EncryptRtcp(uint8_t *data, int *len);
|
||||
bool DecryptSrtcp(uint8_t *data, int *len);
|
||||
void RemoveStream(uint32_t ssrc);
|
||||
|
||||
private:
|
||||
// Allocated by this.
|
||||
srtp_t session { nullptr };
|
||||
std::shared_ptr<DepLibSRTP> _env;
|
||||
};
|
||||
|
||||
} // namespace RTC
|
||||
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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 "Util/Byte.hpp"
|
||||
#include "Network/Buffer.h"
|
||||
#include "Network/sockutil.h"
|
||||
|
||||
namespace RTC
|
||||
{
|
||||
class StunPacket
|
||||
{
|
||||
public:
|
||||
// STUN message class.
|
||||
enum class Class : uint16_t
|
||||
{
|
||||
REQUEST = 0,
|
||||
INDICATION = 1,
|
||||
SUCCESS_RESPONSE = 2,
|
||||
ERROR_RESPONSE = 3
|
||||
};
|
||||
namespace RTC {
|
||||
// reference https://rcf-editor.org/rfc/rfc8489
|
||||
// reference https://rcf-editor.org/rfc/rfc8656
|
||||
// reference https://rcf-editor.org/rfc/rfc8445
|
||||
|
||||
// STUN message method.
|
||||
enum class Method : uint16_t
|
||||
{
|
||||
BINDING = 1
|
||||
};
|
||||
//////////// Attribute //////////////////////////
|
||||
// reference https://rcf-editor.org/rfc/rfc8489
|
||||
/*
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Type | Length |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Value (variable) ....
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
Figure 4: Format of STUN Attributes
|
||||
reference https://www.rfc-editor.org/rfc/rfc8489.html#section-14
|
||||
*/
|
||||
class StunAttribute {
|
||||
public:
|
||||
// Attribute type.
|
||||
enum class Type : uint16_t {
|
||||
MAPPED_ADDRESS = 0x0001,
|
||||
RESPONSE_ADDRESS = 0x0002, // Reserved; was RESPONSE-ADDRESS prior to [RFC5389]
|
||||
CHANGE_REQUEST = 0x0003, // Reserved; was CHANGE-REQUEST prior to [RFC5389]
|
||||
CHANGED_ADDRESS = 0x0005, // Reserved; was CHANGED-ADDRESS prior to [RFC5389]
|
||||
USERNAME = 0x0006,
|
||||
PASSWORD = 0x0005, // Reserved; was PASSWORD prior to [RFC5389]
|
||||
MESSAGE_INTEGRITY = 0x0008,
|
||||
ERROR_CODE = 0x0009,
|
||||
UNKNOWN_ATTRIBUTES = 0x000A,
|
||||
REFLECTED_FROM = 0x000B, // Reserved; was REFLECTED-FROM prior to [RFC5389]
|
||||
CHANNEL_NUMBER = 0x000C, // [RFC5766]
|
||||
LIFETIME = 0x000D, // [RFC5766]
|
||||
BANDWIDTH = 0x0010, // Reserved; [RFC5766]
|
||||
XOR_PEER_ADDRESS = 0x0012, // [RFC5766]
|
||||
DATA = 0x0013, // [RFC5766]
|
||||
REALM = 0x0014,
|
||||
NONCE = 0x0015,
|
||||
XOR_RELAYED_ADDRESS = 0x0016, // [RFC5766]
|
||||
EVEN_PORT = 0x0018, // [RFC5766]
|
||||
REQUESTED_TRANSPORT = 0x0019, // [RFC5766]
|
||||
DONT_FRAGMENT = 0x001A, // [RFC5766]
|
||||
MESSAGE_INTEGRITY_SHA256 = 0x001C,
|
||||
USERHASH = 0x001E,
|
||||
PASSWORD_ALGORITHM = 0x001D,
|
||||
XOR_MAPPED_ADDRESS = 0x0020,
|
||||
TIMER_VAL = 0x0021, // Reserved; [RFC5766]
|
||||
RESERVATION_TOKEN = 0x0022, // [RFC5766]
|
||||
PRIORITY = 0x0024,
|
||||
USE_CANDIDATE = 0x0025,
|
||||
|
||||
// Attribute type.
|
||||
enum class Attribute : uint16_t
|
||||
{
|
||||
MAPPED_ADDRESS = 0x0001,
|
||||
USERNAME = 0x0006,
|
||||
MESSAGE_INTEGRITY = 0x0008,
|
||||
ERROR_CODE = 0x0009,
|
||||
UNKNOWN_ATTRIBUTES = 0x000A,
|
||||
REALM = 0x0014,
|
||||
NONCE = 0x0015,
|
||||
XOR_MAPPED_ADDRESS = 0x0020,
|
||||
PRIORITY = 0x0024,
|
||||
USE_CANDIDATE = 0x0025,
|
||||
SOFTWARE = 0x8022,
|
||||
ALTERNATE_SERVER = 0x8023,
|
||||
FINGERPRINT = 0x8028,
|
||||
ICE_CONTROLLED = 0x8029,
|
||||
ICE_CONTROLLING = 0x802A
|
||||
};
|
||||
|
||||
// Authentication result.
|
||||
enum class Authentication
|
||||
{
|
||||
OK = 0,
|
||||
UNAUTHORIZED = 1,
|
||||
BAD_REQUEST = 2
|
||||
};
|
||||
|
||||
public:
|
||||
static bool IsStun(const uint8_t* data, size_t len)
|
||||
{
|
||||
// clang-format off
|
||||
return (
|
||||
// STUN headers are 20 bytes.
|
||||
(len >= 20) &&
|
||||
// DOC: https://tools.ietf.org/html/draft-ietf-avtcore-rfc5764-mux-fixes
|
||||
(data[0] < 3) &&
|
||||
// Magic cookie must match.
|
||||
(data[4] == StunPacket::magicCookie[0]) && (data[5] == StunPacket::magicCookie[1]) &&
|
||||
(data[6] == StunPacket::magicCookie[2]) && (data[7] == StunPacket::magicCookie[3])
|
||||
);
|
||||
// clang-format on
|
||||
}
|
||||
static StunPacket* Parse(const uint8_t* data, size_t len);
|
||||
|
||||
private:
|
||||
static const uint8_t magicCookie[];
|
||||
|
||||
public:
|
||||
StunPacket(
|
||||
Class klass, Method method, const uint8_t* transactionId, const uint8_t* data, size_t size);
|
||||
~StunPacket();
|
||||
|
||||
void Dump() const;
|
||||
Class GetClass() const
|
||||
{
|
||||
return this->klass;
|
||||
}
|
||||
Method GetMethod() const
|
||||
{
|
||||
return this->method;
|
||||
}
|
||||
const uint8_t* GetData() const
|
||||
{
|
||||
return this->data;
|
||||
}
|
||||
size_t GetSize() const
|
||||
{
|
||||
return this->size;
|
||||
}
|
||||
void SetUsername(const char* username, size_t len)
|
||||
{
|
||||
this->username.assign(username, len);
|
||||
}
|
||||
void SetPriority(uint32_t priority)
|
||||
{
|
||||
this->priority = priority;
|
||||
}
|
||||
void SetIceControlling(uint64_t iceControlling)
|
||||
{
|
||||
this->iceControlling = iceControlling;
|
||||
}
|
||||
void SetIceControlled(uint64_t iceControlled)
|
||||
{
|
||||
this->iceControlled = iceControlled;
|
||||
}
|
||||
void SetUseCandidate()
|
||||
{
|
||||
this->hasUseCandidate = true;
|
||||
}
|
||||
void SetXorMappedAddress(const struct sockaddr* xorMappedAddress)
|
||||
{
|
||||
this->xorMappedAddress = xorMappedAddress;
|
||||
}
|
||||
void SetErrorCode(uint16_t errorCode)
|
||||
{
|
||||
this->errorCode = errorCode;
|
||||
}
|
||||
void SetMessageIntegrity(const uint8_t* messageIntegrity)
|
||||
{
|
||||
this->messageIntegrity = messageIntegrity;
|
||||
}
|
||||
void SetFingerprint()
|
||||
{
|
||||
this->hasFingerprint = true;
|
||||
}
|
||||
const std::string& GetUsername() const
|
||||
{
|
||||
return this->username;
|
||||
}
|
||||
uint32_t GetPriority() const
|
||||
{
|
||||
return this->priority;
|
||||
}
|
||||
uint64_t GetIceControlling() const
|
||||
{
|
||||
return this->iceControlling;
|
||||
}
|
||||
uint64_t GetIceControlled() const
|
||||
{
|
||||
return this->iceControlled;
|
||||
}
|
||||
bool HasUseCandidate() const
|
||||
{
|
||||
return this->hasUseCandidate;
|
||||
}
|
||||
uint16_t GetErrorCode() const
|
||||
{
|
||||
return this->errorCode;
|
||||
}
|
||||
bool HasMessageIntegrity() const
|
||||
{
|
||||
return (this->messageIntegrity ? true : false);
|
||||
}
|
||||
bool HasFingerprint() const
|
||||
{
|
||||
return this->hasFingerprint;
|
||||
}
|
||||
Authentication CheckAuthentication(
|
||||
const std::string& localUsername, const std::string& localPassword);
|
||||
StunPacket* CreateSuccessResponse();
|
||||
StunPacket* CreateErrorResponse(uint16_t errorCode);
|
||||
void Authenticate(const std::string& password);
|
||||
void Serialize(uint8_t* buffer);
|
||||
|
||||
private:
|
||||
// Passed by argument.
|
||||
Class klass; // 2 bytes.
|
||||
Method method; // 2 bytes.
|
||||
const uint8_t* transactionId{ nullptr }; // 12 bytes.
|
||||
uint8_t* data{ nullptr }; // Pointer to binary data.
|
||||
size_t size{ 0u }; // The full message size (including header).
|
||||
// STUN attributes.
|
||||
std::string username; // Less than 513 bytes.
|
||||
uint32_t priority{ 0u }; // 4 bytes unsigned integer.
|
||||
uint64_t iceControlling{ 0u }; // 8 bytes unsigned integer.
|
||||
uint64_t iceControlled{ 0u }; // 8 bytes unsigned integer.
|
||||
bool hasUseCandidate{ false }; // 0 bytes.
|
||||
const uint8_t* messageIntegrity{ nullptr }; // 20 bytes.
|
||||
bool hasFingerprint{ false }; // 4 bytes.
|
||||
const struct sockaddr* xorMappedAddress{ nullptr }; // 8 or 20 bytes.
|
||||
uint16_t errorCode{ 0u }; // 4 bytes (no reason phrase).
|
||||
std::string password;
|
||||
//Comprehension-optional range (0x8000-0xFFFF)
|
||||
PASSWORD_ALGORITHMS = 0x8002,
|
||||
ALTERNATE_DOMAIN = 0x8003,
|
||||
SOFTWARE = 0x8022,
|
||||
ALTERNATE_SERVER = 0x8023,
|
||||
FINGERPRINT = 0x8028,
|
||||
ICE_CONTROLLED = 0x8029,
|
||||
ICE_CONTROLLING = 0x802A,
|
||||
GOOG_NETWORK_INFO = 0xC057,
|
||||
};
|
||||
|
||||
static const size_t ATTR_HEADER_SIZE = 4;
|
||||
static bool isComprehensionRequired(const uint8_t *data, size_t len);
|
||||
|
||||
using Ptr = std::shared_ptr<StunAttribute>;
|
||||
StunAttribute(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
|
||||
|
||||
#endif
|
||||
|
||||
256
webrtc/USAGE.md
Normal file
256
webrtc/USAGE.md
Normal 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代理推流
|
||||
118
webrtc/Utils.hpp
118
webrtc/Utils.hpp
@ -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
303
webrtc/WebRtcClient.cpp
Executable 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
105
webrtc/WebRtcClient.h
Executable 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 */
|
||||
@ -10,6 +10,8 @@
|
||||
|
||||
#include "WebRtcEchoTest.h"
|
||||
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
WebRtcEchoTest::Ptr WebRtcEchoTest::create(const EventPoller::Ptr &poller) {
|
||||
|
||||
@ -18,7 +18,7 @@ namespace mediakit {
|
||||
class WebRtcEchoTest : public WebRtcTransportImp {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<WebRtcEchoTest>;
|
||||
static Ptr create(const EventPoller::Ptr &poller);
|
||||
static Ptr create(const toolkit::EventPoller::Ptr &poller);
|
||||
|
||||
protected:
|
||||
///////WebRtcTransportImp override///////
|
||||
@ -31,7 +31,7 @@ protected:
|
||||
void onBeforeEncryptRtcp(const char *buf, int &len, void *ctx) override {};
|
||||
|
||||
private:
|
||||
WebRtcEchoTest(const EventPoller::Ptr &poller);
|
||||
WebRtcEchoTest(const toolkit::EventPoller::Ptr &poller);
|
||||
};
|
||||
|
||||
}// namespace mediakit
|
||||
|
||||
@ -1,330 +1,338 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT-like license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "WebRtcPlayer.h"
|
||||
|
||||
#include "Common/config.h"
|
||||
#include "Extension/Factory.h"
|
||||
#include "Util/base64.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
namespace Rtc {
|
||||
#define RTC_FIELD "rtc."
|
||||
const string kBfilter = RTC_FIELD "bfilter";
|
||||
static onceToken token([]() { mINI::Instance()[kBfilter] = 0; });
|
||||
} // namespace Rtc
|
||||
|
||||
H264BFrameFilter::H264BFrameFilter()
|
||||
: _last_seq(0)
|
||||
, _last_stamp(0)
|
||||
, _first_packet(true) {}
|
||||
|
||||
RtpPacket::Ptr H264BFrameFilter::processPacket(const RtpPacket::Ptr &packet) {
|
||||
if (!packet) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (isH264BFrame(packet)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto cur_stamp = packet->getStamp();
|
||||
auto cur_seq = packet->getSeq();
|
||||
|
||||
if (_first_packet) {
|
||||
_first_packet = false;
|
||||
_last_seq = cur_seq;
|
||||
_last_stamp = cur_stamp;
|
||||
}
|
||||
|
||||
// 处理时间戳连续性问题
|
||||
if (cur_stamp < _last_stamp) {
|
||||
return nullptr;
|
||||
}
|
||||
_last_stamp = cur_stamp;
|
||||
|
||||
// 处理 seq 连续性问题
|
||||
if (cur_seq > _last_seq + 4) {
|
||||
RtpHeader *header = packet->getHeader();
|
||||
_last_seq = (_last_seq + 1) & 0xFFFF;
|
||||
header->seq = htons(_last_seq);
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
bool H264BFrameFilter::isH264BFrame(const RtpPacket::Ptr &packet) const {
|
||||
uint8_t *payload = packet->getPayload();
|
||||
size_t payload_size = packet->getPayloadSize();
|
||||
|
||||
if (payload_size < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t nal_unit_type = payload[0] & 0x1F;
|
||||
switch (nal_unit_type) {
|
||||
case 24: // STAP-A
|
||||
return handleStapA(payload, payload_size);
|
||||
case 28: // FU-A
|
||||
return handleFua(payload, payload_size);
|
||||
default:
|
||||
if (nal_unit_type < 24) {
|
||||
return isBFrameByNalType(nal_unit_type, payload + 1, payload_size - 1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool H264BFrameFilter::handleStapA(const uint8_t *payload, size_t payload_size) const {
|
||||
size_t offset = 1;
|
||||
while (offset + 2 <= payload_size) {
|
||||
uint16_t nalu_size = (payload[offset] << 8) | payload[offset + 1];
|
||||
offset += 2;
|
||||
if (offset + nalu_size > payload_size || nalu_size < 1) {
|
||||
return false;
|
||||
}
|
||||
uint8_t original_nal_type = payload[offset] & 0x1F;
|
||||
if (original_nal_type < 24) {
|
||||
if (isBFrameByNalType(original_nal_type, payload + offset + 1, nalu_size - 1)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
offset += nalu_size;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool H264BFrameFilter::handleFua(const uint8_t *payload, size_t payload_size) const {
|
||||
if (payload_size < 2) {
|
||||
return false;
|
||||
}
|
||||
uint8_t fu_header = payload[1];
|
||||
uint8_t original_nal_type = fu_header & 0x1F;
|
||||
bool start_bit = fu_header & 0x80;
|
||||
if (start_bit) {
|
||||
return isBFrameByNalType(original_nal_type, payload + 2, payload_size - 2);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool H264BFrameFilter::isBFrameByNalType(uint8_t nal_type, const uint8_t *data, size_t size) const {
|
||||
if (size < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nal_type != NAL_NIDR && nal_type != NAL_PARTITION_A && nal_type != NAL_PARTITION_B && nal_type != NAL_PARTITION_C) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t slice_type = extractSliceType(data, size);
|
||||
return slice_type == H264SliceTypeB || slice_type == H264SliceTypeB1;
|
||||
}
|
||||
|
||||
int H264BFrameFilter::decodeExpGolomb(const uint8_t *data, size_t size, size_t &bitPos) const {
|
||||
if (bitPos >= size * 8)
|
||||
return -1;
|
||||
|
||||
int leadingZeroBits = 0;
|
||||
while (bitPos < size * 8 && !getBit(data, bitPos++)) {
|
||||
leadingZeroBits++;
|
||||
}
|
||||
|
||||
int result = (1 << leadingZeroBits) - 1;
|
||||
for (int i = 0; i < leadingZeroBits; i++) {
|
||||
if (bitPos < size * 8) {
|
||||
result += getBit(data, bitPos++) << (leadingZeroBits - i - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int H264BFrameFilter::getBit(const uint8_t *data, size_t pos) const {
|
||||
size_t byteIndex = pos / 8;
|
||||
size_t bitOffset = pos % 8;
|
||||
uint8_t byte = data[byteIndex];
|
||||
return (byte >> (7 - bitOffset)) & 0x01;
|
||||
}
|
||||
|
||||
uint8_t H264BFrameFilter::extractSliceType(const uint8_t *data, size_t size) const {
|
||||
size_t bitPos = 0;
|
||||
int first_mb_in_slice = decodeExpGolomb(data, size, bitPos);
|
||||
int slice_type = decodeExpGolomb(data, size, bitPos);
|
||||
|
||||
if (slice_type >= 0 && slice_type <= 9) {
|
||||
return static_cast<uint8_t>(slice_type);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
WebRtcPlayer::Ptr WebRtcPlayer::create(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info) {
|
||||
WebRtcPlayer::Ptr ret(new WebRtcPlayer(poller, src, info), [](WebRtcPlayer *ptr) {
|
||||
ptr->onDestory();
|
||||
delete ptr;
|
||||
});
|
||||
ret->onCreate();
|
||||
return ret;
|
||||
}
|
||||
|
||||
WebRtcPlayer::WebRtcPlayer(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info)
|
||||
: WebRtcTransportImp(poller) {
|
||||
_media_info = info;
|
||||
_play_src = src;
|
||||
CHECK(src);
|
||||
|
||||
GET_CONFIG(bool, direct_proxy, Rtsp::kDirectProxy);
|
||||
_send_config_frames_once = direct_proxy;
|
||||
|
||||
GET_CONFIG(bool, enable, Rtc::kBfilter);
|
||||
_bfliter_flag = enable;
|
||||
_is_h264 = false;
|
||||
_bfilter = std::make_shared<H264BFrameFilter>();
|
||||
}
|
||||
|
||||
void WebRtcPlayer::onStartWebRTC() {
|
||||
auto playSrc = _play_src.lock();
|
||||
if (!playSrc) {
|
||||
onShutdown(SockException(Err_shutdown, "rtsp media source was shutdown"));
|
||||
return;
|
||||
}
|
||||
WebRtcTransportImp::onStartWebRTC();
|
||||
if (canSendRtp()) {
|
||||
playSrc->pause(false);
|
||||
_reader = playSrc->getRing()->attach(getPoller(), true);
|
||||
weak_ptr<WebRtcPlayer> weak_self = static_pointer_cast<WebRtcPlayer>(shared_from_this());
|
||||
weak_ptr<Session> weak_session = static_pointer_cast<Session>(getSession());
|
||||
_reader->setGetInfoCB([weak_session]() {
|
||||
Any ret;
|
||||
ret.set(static_pointer_cast<Session>(weak_session.lock()));
|
||||
return ret;
|
||||
});
|
||||
_reader->setReadCB([weak_self](const RtspMediaSource::RingDataType &pkt) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strong_self->_send_config_frames_once && !pkt->empty()) {
|
||||
const auto &first_rtp = pkt->front();
|
||||
strong_self->sendConfigFrames(first_rtp->getSeq(), first_rtp->sample_rate, first_rtp->getStamp(), first_rtp->ntp_stamp);
|
||||
strong_self->_send_config_frames_once = false;
|
||||
}
|
||||
|
||||
size_t i = 0;
|
||||
pkt->for_each([&](const RtpPacket::Ptr &rtp) {
|
||||
if (strong_self->_bfliter_flag) {
|
||||
if (TrackVideo == rtp->type && strong_self->_is_h264) {
|
||||
auto rtp_filter = strong_self->_bfilter->processPacket(rtp);
|
||||
if (rtp_filter) {
|
||||
strong_self->onSendRtp(rtp_filter, ++i == pkt->size());
|
||||
}
|
||||
} else {
|
||||
strong_self->onSendRtp(rtp, ++i == pkt->size());
|
||||
}
|
||||
} else {
|
||||
strong_self->onSendRtp(rtp, ++i == pkt->size());
|
||||
}
|
||||
});
|
||||
});
|
||||
_reader->setDetachCB([weak_self]() {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
return;
|
||||
}
|
||||
strong_self->onShutdown(SockException(Err_shutdown, "rtsp ring buffer detached"));
|
||||
});
|
||||
|
||||
_reader->setMessageCB([weak_self](const toolkit::Any &data) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
return;
|
||||
}
|
||||
if (data.is<Buffer>()) {
|
||||
auto &buffer = data.get<Buffer>();
|
||||
// PPID 51: 文本string [AUTO-TRANSLATED:69a8cf81]
|
||||
// PPID 51: Text string
|
||||
// PPID 53: 二进制 [AUTO-TRANSLATED:faf00c3e]
|
||||
// PPID 53: Binary
|
||||
strong_self->sendDatachannel(0, 51, buffer.data(), buffer.size());
|
||||
} else {
|
||||
WarnL << "Send unknown message type to webrtc player: " << data.type_name();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
void WebRtcPlayer::onDestory() {
|
||||
auto duration = getDuration();
|
||||
auto bytes_usage = getBytesUsage();
|
||||
// 流量统计事件广播 [AUTO-TRANSLATED:6b0b1234]
|
||||
// Traffic statistics event broadcast
|
||||
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
|
||||
if (_reader && getSession()) {
|
||||
WarnL << "RTC播放器(" << _media_info.shortUrl() << ")结束播放,耗时(s):" << duration;
|
||||
if (bytes_usage >= iFlowThreshold * 1024) {
|
||||
NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, bytes_usage, duration, true, *getSession());
|
||||
}
|
||||
}
|
||||
WebRtcTransportImp::onDestory();
|
||||
}
|
||||
|
||||
void WebRtcPlayer::onRtcConfigure(RtcConfigure &configure) const {
|
||||
auto playSrc = _play_src.lock();
|
||||
if (!playSrc) {
|
||||
return;
|
||||
}
|
||||
WebRtcTransportImp::onRtcConfigure(configure);
|
||||
// 这是播放 [AUTO-TRANSLATED:d93c019e]
|
||||
// This is playing
|
||||
configure.audio.direction = configure.video.direction = RtpDirection::sendonly;
|
||||
configure.setPlayRtspInfo(playSrc->getSdp());
|
||||
}
|
||||
|
||||
void WebRtcPlayer::sendConfigFrames(uint32_t before_seq, uint32_t sample_rate, uint32_t timestamp, uint64_t ntp_timestamp) {
|
||||
auto play_src = _play_src.lock();
|
||||
if (!play_src) {
|
||||
return;
|
||||
}
|
||||
SdpParser parser(play_src->getSdp());
|
||||
auto video_sdp = parser.getTrack(TrackVideo);
|
||||
if (!video_sdp) {
|
||||
return;
|
||||
}
|
||||
auto video_track = dynamic_pointer_cast<VideoTrack>(Factory::getTrackBySdp(video_sdp));
|
||||
if (!video_track) {
|
||||
return;
|
||||
}
|
||||
_is_h264 = video_track->getCodecId() == CodecH264;
|
||||
auto frames = video_track->getConfigFrames();
|
||||
if (frames.empty()) {
|
||||
return;
|
||||
}
|
||||
auto encoder = mediakit::Factory::getRtpEncoderByCodecId(video_track->getCodecId(), 0);
|
||||
if (!encoder) {
|
||||
return;
|
||||
}
|
||||
|
||||
GET_CONFIG(uint32_t, video_mtu, Rtp::kVideoMtuSize);
|
||||
encoder->setRtpInfo(0, video_mtu, sample_rate, 0, 0, 0);
|
||||
|
||||
auto seq = before_seq - frames.size();
|
||||
for (const auto &frame : frames) {
|
||||
auto rtp = encoder->getRtpInfo().makeRtp(TrackVideo, frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize(), false, 0);
|
||||
auto header = rtp->getHeader();
|
||||
header->seq = htons(seq++);
|
||||
header->stamp = htonl(timestamp);
|
||||
rtp->ntp_stamp = ntp_timestamp;
|
||||
onSendRtp(rtp, false);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT-like license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "WebRtcPlayer.h"
|
||||
|
||||
#include "Common/config.h"
|
||||
#include "Extension/Factory.h"
|
||||
#include "Util/base64.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
namespace Rtc {
|
||||
#define RTC_FIELD "rtc."
|
||||
const string kBfilter = RTC_FIELD "bfilter";
|
||||
static onceToken token([]() { mINI::Instance()[kBfilter] = 0; });
|
||||
} // namespace Rtc
|
||||
|
||||
H264BFrameFilter::H264BFrameFilter()
|
||||
: _last_seq(0)
|
||||
, _last_stamp(0)
|
||||
, _first_packet(true) {}
|
||||
|
||||
RtpPacket::Ptr H264BFrameFilter::processPacket(const RtpPacket::Ptr &packet) {
|
||||
if (!packet) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (isH264BFrame(packet)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto cur_stamp = packet->getStamp();
|
||||
auto cur_seq = packet->getSeq();
|
||||
|
||||
if (_first_packet) {
|
||||
_first_packet = false;
|
||||
_last_seq = cur_seq;
|
||||
_last_stamp = cur_stamp;
|
||||
}
|
||||
|
||||
// 处理时间戳连续性问题
|
||||
if (cur_stamp < _last_stamp) {
|
||||
return nullptr;
|
||||
}
|
||||
_last_stamp = cur_stamp;
|
||||
|
||||
// 处理 seq 连续性问题
|
||||
if (cur_seq > _last_seq + 4) {
|
||||
RtpHeader *header = packet->getHeader();
|
||||
_last_seq = (_last_seq + 1) & 0xFFFF;
|
||||
header->seq = htons(_last_seq);
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
bool H264BFrameFilter::isH264BFrame(const RtpPacket::Ptr &packet) const {
|
||||
uint8_t *payload = packet->getPayload();
|
||||
size_t payload_size = packet->getPayloadSize();
|
||||
|
||||
if (payload_size < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t nal_unit_type = payload[0] & 0x1F;
|
||||
switch (nal_unit_type) {
|
||||
case 24: // STAP-A
|
||||
return handleStapA(payload, payload_size);
|
||||
case 28: // FU-A
|
||||
return handleFua(payload, payload_size);
|
||||
default:
|
||||
if (nal_unit_type < 24) {
|
||||
return isBFrameByNalType(nal_unit_type, payload + 1, payload_size - 1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool H264BFrameFilter::handleStapA(const uint8_t *payload, size_t payload_size) const {
|
||||
size_t offset = 1;
|
||||
while (offset + 2 <= payload_size) {
|
||||
uint16_t nalu_size = (payload[offset] << 8) | payload[offset + 1];
|
||||
offset += 2;
|
||||
if (offset + nalu_size > payload_size || nalu_size < 1) {
|
||||
return false;
|
||||
}
|
||||
uint8_t original_nal_type = payload[offset] & 0x1F;
|
||||
if (original_nal_type < 24) {
|
||||
if (isBFrameByNalType(original_nal_type, payload + offset + 1, nalu_size - 1)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
offset += nalu_size;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool H264BFrameFilter::handleFua(const uint8_t *payload, size_t payload_size) const {
|
||||
if (payload_size < 2) {
|
||||
return false;
|
||||
}
|
||||
uint8_t fu_header = payload[1];
|
||||
uint8_t original_nal_type = fu_header & 0x1F;
|
||||
bool start_bit = fu_header & 0x80;
|
||||
if (start_bit) {
|
||||
return isBFrameByNalType(original_nal_type, payload + 2, payload_size - 2);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool H264BFrameFilter::isBFrameByNalType(uint8_t nal_type, const uint8_t *data, size_t size) const {
|
||||
if (size < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nal_type != NAL_NIDR && nal_type != NAL_PARTITION_A && nal_type != NAL_PARTITION_B && nal_type != NAL_PARTITION_C) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t slice_type = extractSliceType(data, size);
|
||||
return slice_type == H264SliceTypeB || slice_type == H264SliceTypeB1;
|
||||
}
|
||||
|
||||
int H264BFrameFilter::decodeExpGolomb(const uint8_t *data, size_t size, size_t &bitPos) const {
|
||||
if (bitPos >= size * 8)
|
||||
return -1;
|
||||
|
||||
int leadingZeroBits = 0;
|
||||
while (bitPos < size * 8 && !getBit(data, bitPos++)) {
|
||||
leadingZeroBits++;
|
||||
}
|
||||
|
||||
int result = (1 << leadingZeroBits) - 1;
|
||||
for (int i = 0; i < leadingZeroBits; i++) {
|
||||
if (bitPos < size * 8) {
|
||||
result += getBit(data, bitPos++) << (leadingZeroBits - i - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int H264BFrameFilter::getBit(const uint8_t *data, size_t pos) const {
|
||||
size_t byteIndex = pos / 8;
|
||||
size_t bitOffset = pos % 8;
|
||||
uint8_t byte = data[byteIndex];
|
||||
return (byte >> (7 - bitOffset)) & 0x01;
|
||||
}
|
||||
|
||||
uint8_t H264BFrameFilter::extractSliceType(const uint8_t *data, size_t size) const {
|
||||
size_t bitPos = 0;
|
||||
int first_mb_in_slice = decodeExpGolomb(data, size, bitPos);
|
||||
int slice_type = decodeExpGolomb(data, size, bitPos);
|
||||
|
||||
if (slice_type >= 0 && slice_type <= 9) {
|
||||
return static_cast<uint8_t>(slice_type);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
WebRtcPlayer::Ptr WebRtcPlayer::create(const EventPoller::Ptr &poller,
|
||||
const RtspMediaSource::Ptr &src,
|
||||
const MediaInfo &info,
|
||||
WebRtcTransport::Role role,
|
||||
WebRtcTransport::SignalingProtocols signaling_protocols) {
|
||||
WebRtcPlayer::Ptr ret(new WebRtcPlayer(poller, src, info), [](WebRtcPlayer *ptr) {
|
||||
ptr->onDestory();
|
||||
delete ptr;
|
||||
});
|
||||
ret->setRole(role);
|
||||
ret->setSignalingProtocols(signaling_protocols);
|
||||
ret->onCreate();
|
||||
return ret;
|
||||
}
|
||||
|
||||
WebRtcPlayer::WebRtcPlayer(const EventPoller::Ptr &poller,
|
||||
const RtspMediaSource::Ptr &src,
|
||||
const MediaInfo &info) : WebRtcTransportImp(poller) {
|
||||
_media_info = info;
|
||||
_play_src = src;
|
||||
CHECK(src);
|
||||
|
||||
GET_CONFIG(bool, direct_proxy, Rtsp::kDirectProxy);
|
||||
_send_config_frames_once = direct_proxy;
|
||||
|
||||
GET_CONFIG(bool, enable, Rtc::kBfilter);
|
||||
_bfliter_flag = enable;
|
||||
_is_h264 = false;
|
||||
_bfilter = std::make_shared<H264BFrameFilter>();
|
||||
}
|
||||
|
||||
void WebRtcPlayer::onStartWebRTC() {
|
||||
auto playSrc = _play_src.lock();
|
||||
if (!playSrc) {
|
||||
onShutdown(SockException(Err_shutdown, "rtsp media source was shutdown"));
|
||||
return;
|
||||
}
|
||||
WebRtcTransportImp::onStartWebRTC();
|
||||
if (canSendRtp()) {
|
||||
playSrc->pause(false);
|
||||
_reader = playSrc->getRing()->attach(getPoller(), true);
|
||||
weak_ptr<WebRtcPlayer> weak_self = static_pointer_cast<WebRtcPlayer>(shared_from_this());
|
||||
weak_ptr<Session> weak_session = static_pointer_cast<Session>(getSession());
|
||||
_reader->setGetInfoCB([weak_session]() {
|
||||
Any ret;
|
||||
ret.set(static_pointer_cast<Session>(weak_session.lock()));
|
||||
return ret;
|
||||
});
|
||||
_reader->setReadCB([weak_self](const RtspMediaSource::RingDataType &pkt) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strong_self->_send_config_frames_once && !pkt->empty()) {
|
||||
const auto &first_rtp = pkt->front();
|
||||
strong_self->sendConfigFrames(first_rtp->getSeq(), first_rtp->sample_rate, first_rtp->getStamp(), first_rtp->ntp_stamp);
|
||||
strong_self->_send_config_frames_once = false;
|
||||
}
|
||||
|
||||
size_t i = 0;
|
||||
pkt->for_each([&](const RtpPacket::Ptr &rtp) {
|
||||
if (strong_self->_bfliter_flag) {
|
||||
if (TrackVideo == rtp->type && strong_self->_is_h264) {
|
||||
auto rtp_filter = strong_self->_bfilter->processPacket(rtp);
|
||||
if (rtp_filter) {
|
||||
strong_self->onSendRtp(rtp_filter, ++i == pkt->size());
|
||||
}
|
||||
} else {
|
||||
strong_self->onSendRtp(rtp, ++i == pkt->size());
|
||||
}
|
||||
} else {
|
||||
strong_self->onSendRtp(rtp, ++i == pkt->size());
|
||||
}
|
||||
});
|
||||
});
|
||||
_reader->setDetachCB([weak_self]() {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
return;
|
||||
}
|
||||
strong_self->onShutdown(SockException(Err_shutdown, "rtsp ring buffer detached"));
|
||||
});
|
||||
|
||||
_reader->setMessageCB([weak_self](const toolkit::Any &data) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
return;
|
||||
}
|
||||
if (data.is<Buffer>()) {
|
||||
auto &buffer = data.get<Buffer>();
|
||||
// PPID 51: 文本string [AUTO-TRANSLATED:69a8cf81]
|
||||
// PPID 51: Text string
|
||||
// PPID 53: 二进制 [AUTO-TRANSLATED:faf00c3e]
|
||||
// PPID 53: Binary
|
||||
strong_self->sendDatachannel(0, 51, buffer.data(), buffer.size());
|
||||
} else {
|
||||
WarnL << "Send unknown message type to webrtc player: " << data.type_name();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
void WebRtcPlayer::onDestory() {
|
||||
auto duration = getDuration();
|
||||
auto bytes_usage = getBytesUsage();
|
||||
// 流量统计事件广播 [AUTO-TRANSLATED:6b0b1234]
|
||||
// Traffic statistics event broadcast
|
||||
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
|
||||
if (_reader && getSession()) {
|
||||
WarnL << "RTC播放器(" << _media_info.shortUrl() << ")结束播放,耗时(s):" << duration;
|
||||
if (bytes_usage >= iFlowThreshold * 1024) {
|
||||
NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, bytes_usage, duration, true, *getSession());
|
||||
}
|
||||
}
|
||||
WebRtcTransportImp::onDestory();
|
||||
}
|
||||
|
||||
void WebRtcPlayer::onRtcConfigure(RtcConfigure &configure) const {
|
||||
auto playSrc = _play_src.lock();
|
||||
if (!playSrc) {
|
||||
return;
|
||||
}
|
||||
WebRtcTransportImp::onRtcConfigure(configure);
|
||||
// 这是播放 [AUTO-TRANSLATED:d93c019e]
|
||||
// This is playing
|
||||
configure.audio.direction = configure.video.direction = RtpDirection::sendonly;
|
||||
configure.setPlayRtspInfo(playSrc->getSdp());
|
||||
}
|
||||
|
||||
void WebRtcPlayer::sendConfigFrames(uint32_t before_seq, uint32_t sample_rate, uint32_t timestamp, uint64_t ntp_timestamp) {
|
||||
auto play_src = _play_src.lock();
|
||||
if (!play_src) {
|
||||
return;
|
||||
}
|
||||
SdpParser parser(play_src->getSdp());
|
||||
auto video_sdp = parser.getTrack(TrackVideo);
|
||||
if (!video_sdp) {
|
||||
return;
|
||||
}
|
||||
auto video_track = dynamic_pointer_cast<VideoTrack>(Factory::getTrackBySdp(video_sdp));
|
||||
if (!video_track) {
|
||||
return;
|
||||
}
|
||||
_is_h264 = video_track->getCodecId() == CodecH264;
|
||||
auto frames = video_track->getConfigFrames();
|
||||
if (frames.empty()) {
|
||||
return;
|
||||
}
|
||||
auto encoder = mediakit::Factory::getRtpEncoderByCodecId(video_track->getCodecId(), 0);
|
||||
if (!encoder) {
|
||||
return;
|
||||
}
|
||||
|
||||
GET_CONFIG(uint32_t, video_mtu, Rtp::kVideoMtuSize);
|
||||
encoder->setRtpInfo(0, video_mtu, sample_rate, 0, 0, 0);
|
||||
|
||||
auto seq = before_seq - frames.size();
|
||||
for (const auto &frame : frames) {
|
||||
auto rtp = encoder->getRtpInfo().makeRtp(TrackVideo, frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize(), false, 0);
|
||||
auto header = rtp->getHeader();
|
||||
header->seq = htons(seq++);
|
||||
header->stamp = htonl(timestamp);
|
||||
rtp->ntp_stamp = ntp_timestamp;
|
||||
onSendRtp(rtp, false);
|
||||
}
|
||||
}
|
||||
|
||||
}// namespace mediakit
|
||||
@ -1,164 +1,165 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT-like license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef ZLMEDIAKIT_WEBRTCPLAYER_H
|
||||
#define ZLMEDIAKIT_WEBRTCPLAYER_H
|
||||
|
||||
#include "Rtsp/RtspMediaSource.h"
|
||||
#include "WebRtcTransport.h"
|
||||
|
||||
namespace mediakit {
|
||||
/**
|
||||
* @brief H.264 B 帧过滤器
|
||||
* 用于从 H.264 RTP 流中移除 B 帧
|
||||
*/
|
||||
class H264BFrameFilter {
|
||||
public:
|
||||
/**
|
||||
* ISO_IEC_14496-10-AVC-2012
|
||||
* Table 7-6 – Name association to slice_type
|
||||
*/
|
||||
enum H264SliceType {
|
||||
H264SliceTypeP = 0,
|
||||
H264SliceTypeB = 1,
|
||||
H264SliceTypeI = 2,
|
||||
H264SliceTypeSP = 3,
|
||||
H264SliceTypeSI = 4,
|
||||
H264SliceTypeP1 = 5,
|
||||
H264SliceTypeB1 = 6,
|
||||
H264SliceTypeI1 = 7,
|
||||
H264SliceTypeSP1 = 8,
|
||||
H264SliceTypeSI1 = 9,
|
||||
};
|
||||
|
||||
enum H264NALUType {
|
||||
NAL_NIDR = 1,
|
||||
NAL_PARTITION_A = 2,
|
||||
NAL_PARTITION_B = 3,
|
||||
NAL_PARTITION_C = 4,
|
||||
NAL_IDR = 5,
|
||||
};
|
||||
|
||||
H264BFrameFilter();
|
||||
|
||||
~H264BFrameFilter() = default;
|
||||
|
||||
/**
|
||||
* @brief 处理单个 RTP 包,移除 B 帧
|
||||
* @param packet 输入的 RTP 包
|
||||
* @return 如果不是 B 帧则返回原包,否则返回 nullptr
|
||||
*/
|
||||
RtpPacket::Ptr processPacket(const RtpPacket::Ptr &packet);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 判断 RTP 包是否包含 H.264 的 B 帧
|
||||
* @param packet RTP 包
|
||||
* @return 如果是 B 帧返回 true,否则返回 false
|
||||
*/
|
||||
bool isH264BFrame(const RtpPacket::Ptr &packet) const;
|
||||
|
||||
/**
|
||||
* @brief 根据 NAL 类型和数据判断是否是 B 帧
|
||||
* @param nal_type NAL 单元类型
|
||||
* @param data NAL 单元数据(不含 NAL 头)
|
||||
* @param size 数据大小
|
||||
* @return 如果是 B 帧返回 true,否则返回 false
|
||||
*/
|
||||
bool isBFrameByNalType(uint8_t nal_type, const uint8_t *data, size_t size) const;
|
||||
|
||||
/**
|
||||
* @brief 解析指数哥伦布编码
|
||||
* @param data 数据缓冲区
|
||||
* @param size 缓冲区大小
|
||||
* @param bits_offset 位偏移量
|
||||
* @return 解析出的数值
|
||||
*/
|
||||
int decodeExpGolomb(const uint8_t *data, size_t size, size_t &bitPos) const;
|
||||
|
||||
/**
|
||||
* @brief 从比特流中读取位
|
||||
* @param data 数据缓冲区
|
||||
* @param size 缓冲区大小
|
||||
* @return 读取的位值(0 或 1)
|
||||
*/
|
||||
int getBit(const uint8_t *data, size_t size) const;
|
||||
|
||||
/**
|
||||
* @brief 提取切片类型值
|
||||
* @param data 数据缓冲区
|
||||
* @param size 缓冲区大小
|
||||
* @return 切片类型值
|
||||
*/
|
||||
uint8_t extractSliceType(const uint8_t *data, size_t size) const;
|
||||
|
||||
/**
|
||||
* @brief 处理FU-A分片
|
||||
* @param payload 数据缓冲区
|
||||
* @param payload_size 缓冲区大小
|
||||
* @return 如果是 B 帧返回 true,否则返回 false
|
||||
*/
|
||||
bool handleFua(const uint8_t *payload, size_t payload_size) const;
|
||||
|
||||
/**
|
||||
* @brief 处理 STAP-A 组合包
|
||||
* @param payload 数据缓冲区
|
||||
* @param payload_size 缓冲区大小
|
||||
* @return 如果是 B 帧返回 true,否则返回 false
|
||||
*/
|
||||
bool handleStapA(const uint8_t *payload, size_t payload_size) const;
|
||||
|
||||
|
||||
private:
|
||||
uint16_t _last_seq; // 维护输出流的序列号
|
||||
uint32_t _last_stamp; // 维护输出流的时间戳
|
||||
bool _first_packet; // 是否是第一个包的标记
|
||||
};
|
||||
|
||||
class WebRtcPlayer : public WebRtcTransportImp {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<WebRtcPlayer>;
|
||||
static Ptr create(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info);
|
||||
MediaInfo getMediaInfo() { return _media_info; }
|
||||
|
||||
protected:
|
||||
///////WebRtcTransportImp override///////
|
||||
void onStartWebRTC() override;
|
||||
void onDestory() override;
|
||||
void onRtcConfigure(RtcConfigure &configure) const override;
|
||||
|
||||
private:
|
||||
WebRtcPlayer(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info);
|
||||
|
||||
void sendConfigFrames(uint32_t before_seq, uint32_t sample_rate, uint32_t timestamp, uint64_t ntp_timestamp);
|
||||
|
||||
private:
|
||||
// 媒体相关元数据 [AUTO-TRANSLATED:f4cf8045]
|
||||
// Media related metadata
|
||||
MediaInfo _media_info;
|
||||
// 播放的rtsp源 [AUTO-TRANSLATED:9963eed1]
|
||||
// Playing rtsp source
|
||||
std::weak_ptr<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.
|
||||
bool _send_config_frames_once { false };
|
||||
|
||||
// 播放rtsp源的reader对象 [AUTO-TRANSLATED:7b305055]
|
||||
// Reader object for playing rtsp source
|
||||
RtspMediaSource::RingType::RingReader::Ptr _reader;
|
||||
|
||||
bool _is_h264 { false };
|
||||
bool _bfliter_flag { false };
|
||||
std::shared_ptr<H264BFrameFilter> _bfilter;
|
||||
};
|
||||
|
||||
}// namespace mediakit
|
||||
#endif // ZLMEDIAKIT_WEBRTCPLAYER_H
|
||||
/*
|
||||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT-like license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef ZLMEDIAKIT_WEBRTCPLAYER_H
|
||||
#define ZLMEDIAKIT_WEBRTCPLAYER_H
|
||||
|
||||
#include "WebRtcTransport.h"
|
||||
#include "Rtsp/RtspMediaSource.h"
|
||||
|
||||
namespace mediakit {
|
||||
/**
|
||||
* @brief H.264 B 帧过滤器
|
||||
* 用于从 H.264 RTP 流中移除 B 帧
|
||||
*/
|
||||
class H264BFrameFilter {
|
||||
public:
|
||||
/**
|
||||
* ISO_IEC_14496-10-AVC-2012
|
||||
* Table 7-6 – Name association to slice_type
|
||||
*/
|
||||
enum H264SliceType {
|
||||
H264SliceTypeP = 0,
|
||||
H264SliceTypeB = 1,
|
||||
H264SliceTypeI = 2,
|
||||
H264SliceTypeSP = 3,
|
||||
H264SliceTypeSI = 4,
|
||||
H264SliceTypeP1 = 5,
|
||||
H264SliceTypeB1 = 6,
|
||||
H264SliceTypeI1 = 7,
|
||||
H264SliceTypeSP1 = 8,
|
||||
H264SliceTypeSI1 = 9,
|
||||
};
|
||||
|
||||
enum H264NALUType {
|
||||
NAL_NIDR = 1,
|
||||
NAL_PARTITION_A = 2,
|
||||
NAL_PARTITION_B = 3,
|
||||
NAL_PARTITION_C = 4,
|
||||
NAL_IDR = 5,
|
||||
};
|
||||
|
||||
H264BFrameFilter();
|
||||
|
||||
~H264BFrameFilter() = default;
|
||||
|
||||
/**
|
||||
* @brief 处理单个 RTP 包,移除 B 帧
|
||||
* @param packet 输入的 RTP 包
|
||||
* @return 如果不是 B 帧则返回原包,否则返回 nullptr
|
||||
*/
|
||||
RtpPacket::Ptr processPacket(const RtpPacket::Ptr &packet);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 判断 RTP 包是否包含 H.264 的 B 帧
|
||||
* @param packet RTP 包
|
||||
* @return 如果是 B 帧返回 true,否则返回 false
|
||||
*/
|
||||
bool isH264BFrame(const RtpPacket::Ptr &packet) const;
|
||||
|
||||
/**
|
||||
* @brief 根据 NAL 类型和数据判断是否是 B 帧
|
||||
* @param nal_type NAL 单元类型
|
||||
* @param data NAL 单元数据(不含 NAL 头)
|
||||
* @param size 数据大小
|
||||
* @return 如果是 B 帧返回 true,否则返回 false
|
||||
*/
|
||||
bool isBFrameByNalType(uint8_t nal_type, const uint8_t *data, size_t size) const;
|
||||
|
||||
/**
|
||||
* @brief 解析指数哥伦布编码
|
||||
* @param data 数据缓冲区
|
||||
* @param size 缓冲区大小
|
||||
* @param bits_offset 位偏移量
|
||||
* @return 解析出的数值
|
||||
*/
|
||||
int decodeExpGolomb(const uint8_t *data, size_t size, size_t &bitPos) const;
|
||||
|
||||
/**
|
||||
* @brief 从比特流中读取位
|
||||
* @param data 数据缓冲区
|
||||
* @param size 缓冲区大小
|
||||
* @return 读取的位值(0 或 1)
|
||||
*/
|
||||
int getBit(const uint8_t *data, size_t size) const;
|
||||
|
||||
/**
|
||||
* @brief 提取切片类型值
|
||||
* @param data 数据缓冲区
|
||||
* @param size 缓冲区大小
|
||||
* @return 切片类型值
|
||||
*/
|
||||
uint8_t extractSliceType(const uint8_t *data, size_t size) const;
|
||||
|
||||
/**
|
||||
* @brief 处理FU-A分片
|
||||
* @param payload 数据缓冲区
|
||||
* @param payload_size 缓冲区大小
|
||||
* @return 如果是 B 帧返回 true,否则返回 false
|
||||
*/
|
||||
bool handleFua(const uint8_t *payload, size_t payload_size) const;
|
||||
|
||||
/**
|
||||
* @brief 处理 STAP-A 组合包
|
||||
* @param payload 数据缓冲区
|
||||
* @param payload_size 缓冲区大小
|
||||
* @return 如果是 B 帧返回 true,否则返回 false
|
||||
*/
|
||||
bool handleStapA(const uint8_t *payload, size_t payload_size) const;
|
||||
|
||||
|
||||
private:
|
||||
uint16_t _last_seq; // 维护输出流的序列号
|
||||
uint32_t _last_stamp; // 维护输出流的时间戳
|
||||
bool _first_packet; // 是否是第一个包的标记
|
||||
};
|
||||
|
||||
class WebRtcPlayer : public WebRtcTransportImp {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<WebRtcPlayer>;
|
||||
static Ptr create(const toolkit::EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info,
|
||||
WebRtcTransport::Role role, WebRtcTransport::SignalingProtocols signaling_protocols);
|
||||
MediaInfo getMediaInfo() { return _media_info; }
|
||||
|
||||
protected:
|
||||
///////WebRtcTransportImp override///////
|
||||
void onStartWebRTC() override;
|
||||
void onDestory() override;
|
||||
void onRtcConfigure(RtcConfigure &configure) const override;
|
||||
|
||||
private:
|
||||
WebRtcPlayer(const toolkit::EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info);
|
||||
|
||||
void sendConfigFrames(uint32_t before_seq, uint32_t sample_rate, uint32_t timestamp, uint64_t ntp_timestamp);
|
||||
|
||||
private:
|
||||
// 媒体相关元数据 [AUTO-TRANSLATED:f4cf8045]
|
||||
// Media related metadata
|
||||
MediaInfo _media_info;
|
||||
// 播放的rtsp源 [AUTO-TRANSLATED:9963eed1]
|
||||
// Playing rtsp source
|
||||
std::weak_ptr<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.
|
||||
bool _send_config_frames_once { false };
|
||||
|
||||
// 播放rtsp源的reader对象 [AUTO-TRANSLATED:7b305055]
|
||||
// Reader object for playing rtsp source
|
||||
RtspMediaSource::RingType::RingReader::Ptr _reader;
|
||||
|
||||
bool _is_h264 { false };
|
||||
bool _bfliter_flag { false };
|
||||
std::shared_ptr<H264BFrameFilter> _bfilter;
|
||||
};
|
||||
|
||||
}// namespace mediakit
|
||||
#endif // ZLMEDIAKIT_WEBRTCPLAYER_H
|
||||
|
||||
126
webrtc/WebRtcProxyPlayer.cpp
Executable file
126
webrtc/WebRtcProxyPlayer.cpp
Executable 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
56
webrtc/WebRtcProxyPlayer.h
Executable 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
43
webrtc/WebRtcProxyPlayerImp.h
Executable 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
92
webrtc/WebRtcProxyPusher.cpp
Executable 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
51
webrtc/WebRtcProxyPusher.h
Executable 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 */
|
||||
@ -1,169 +1,232 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT-like license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "WebRtcPusher.h"
|
||||
#include "Common/config.h"
|
||||
#include "Rtsp/RtspMediaSourceImp.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
WebRtcPusher::Ptr WebRtcPusher::create(const EventPoller::Ptr &poller,
|
||||
const RtspMediaSource::Ptr &src,
|
||||
const std::shared_ptr<void> &ownership,
|
||||
const MediaInfo &info,
|
||||
const ProtocolOption &option) {
|
||||
WebRtcPusher::Ptr ret(new WebRtcPusher(poller, src, ownership, info, option), [](WebRtcPusher *ptr) {
|
||||
ptr->onDestory();
|
||||
delete ptr;
|
||||
});
|
||||
ret->onCreate();
|
||||
return ret;
|
||||
}
|
||||
|
||||
WebRtcPusher::WebRtcPusher(const EventPoller::Ptr &poller,
|
||||
const RtspMediaSource::Ptr &src,
|
||||
const std::shared_ptr<void> &ownership,
|
||||
const MediaInfo &info,
|
||||
const ProtocolOption &option) : WebRtcTransportImp(poller) {
|
||||
_media_info = info;
|
||||
_push_src = src;
|
||||
_push_src_ownership = ownership;
|
||||
_continue_push_ms = option.continue_push_ms;
|
||||
CHECK(_push_src);
|
||||
}
|
||||
|
||||
bool WebRtcPusher::close(MediaSource &sender) {
|
||||
onShutdown(SockException(Err_shutdown, "close media: " + sender.getUrl()));
|
||||
// 主动关闭推流,那么不延时注销 [AUTO-TRANSLATED:ee7cc580]
|
||||
// Actively close the stream, then do not delay the logout
|
||||
_push_src = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
int WebRtcPusher::totalReaderCount(MediaSource &sender) {
|
||||
auto total_count = _push_src ? _push_src->totalReaderCount() : 0;
|
||||
if (_simulcast) {
|
||||
std::lock_guard<std::recursive_mutex> lock(_mtx);
|
||||
for (auto &src : _push_src_sim) {
|
||||
total_count += src.second->totalReaderCount();
|
||||
}
|
||||
}
|
||||
return total_count;
|
||||
}
|
||||
|
||||
MediaOriginType WebRtcPusher::getOriginType(MediaSource &sender) const {
|
||||
return MediaOriginType::rtc_push;
|
||||
}
|
||||
|
||||
string WebRtcPusher::getOriginUrl(MediaSource &sender) const {
|
||||
return _media_info.full_url;
|
||||
}
|
||||
|
||||
std::shared_ptr<SockInfo> WebRtcPusher::getOriginSock(MediaSource &sender) const {
|
||||
return static_pointer_cast<SockInfo>(getSession());
|
||||
}
|
||||
|
||||
toolkit::EventPoller::Ptr WebRtcPusher::getOwnerPoller(MediaSource &sender) {
|
||||
return getPoller();
|
||||
}
|
||||
|
||||
void WebRtcPusher::onRecvRtp(MediaTrack &track, const string &rid, RtpPacket::Ptr rtp) {
|
||||
if (!_simulcast) {
|
||||
assert(_push_src);
|
||||
_push_src->onWrite(rtp, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rtp->type == TrackAudio) {
|
||||
// 音频 [AUTO-TRANSLATED:a577d8e1]
|
||||
// Audio
|
||||
for (auto &pr : _push_src_sim) {
|
||||
pr.second->onWrite(rtp, false);
|
||||
}
|
||||
} else {
|
||||
// 视频 [AUTO-TRANSLATED:904730ac]
|
||||
// Video
|
||||
std::lock_guard<std::recursive_mutex> lock(_mtx);
|
||||
auto &src = _push_src_sim[rid];
|
||||
if (!src) {
|
||||
const auto& stream = _push_src->getMediaTuple().stream;
|
||||
auto src_imp = _push_src->clone(rid.empty() ? stream : stream + '_' + rid);
|
||||
_push_src_sim_ownership[rid] = src_imp->getOwnership();
|
||||
src_imp->setListener(static_pointer_cast<WebRtcPusher>(shared_from_this()));
|
||||
src = src_imp;
|
||||
}
|
||||
src->onWrite(std::move(rtp), false);
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtcPusher::onStartWebRTC() {
|
||||
WebRtcTransportImp::onStartWebRTC();
|
||||
_simulcast = _answer_sdp->supportSimulcast();
|
||||
if (canRecvRtp()) {
|
||||
_push_src->setSdp(_answer_sdp->toRtspSdp());
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtcPusher::onDestory() {
|
||||
auto duration = getDuration();
|
||||
auto bytes_usage = getBytesUsage();
|
||||
// 流量统计事件广播 [AUTO-TRANSLATED:6b0b1234]
|
||||
// Traffic statistics event broadcast
|
||||
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
|
||||
|
||||
if (getSession()) {
|
||||
WarnL << "RTC推流器(" << _media_info.shortUrl() << ")结束推流,耗时(s):" << duration;
|
||||
if (bytes_usage >= iFlowThreshold * 1024) {
|
||||
NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, bytes_usage, duration, false, *getSession());
|
||||
}
|
||||
}
|
||||
|
||||
if (_push_src && _continue_push_ms) {
|
||||
// 取消所有权 [AUTO-TRANSLATED:4895d8fa]
|
||||
// Cancel ownership
|
||||
_push_src_ownership = nullptr;
|
||||
// 延时10秒注销流 [AUTO-TRANSLATED:e1bb11f9]
|
||||
// Delay 10 seconds to log out the stream
|
||||
auto push_src = std::move(_push_src);
|
||||
getPoller()->doDelayTask(_continue_push_ms, [push_src]() { return 0; });
|
||||
}
|
||||
WebRtcTransportImp::onDestory();
|
||||
}
|
||||
|
||||
void WebRtcPusher::onRtcConfigure(RtcConfigure &configure) const {
|
||||
WebRtcTransportImp::onRtcConfigure(configure);
|
||||
// 这只是推流 [AUTO-TRANSLATED:f877bf98]
|
||||
// This is just pushing the stream
|
||||
configure.audio.direction = configure.video.direction = RtpDirection::recvonly;
|
||||
}
|
||||
|
||||
float WebRtcPusher::getLossRate(MediaSource &sender,TrackType type) {
|
||||
return WebRtcTransportImp::getLossRate(type);
|
||||
}
|
||||
|
||||
void WebRtcPusher::OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) {
|
||||
// 主动关闭推流,那么不等待重推 [AUTO-TRANSLATED:1ff514d7]
|
||||
// Actively close the stream, then do not wait for re-pushing
|
||||
_push_src = nullptr;
|
||||
WebRtcTransportImp::OnDtlsTransportClosed(dtlsTransport);
|
||||
}
|
||||
|
||||
void WebRtcPusher::onRtcpBye() {
|
||||
WebRtcTransportImp::onRtcpBye();
|
||||
}
|
||||
|
||||
void WebRtcPusher::onShutdown(const SockException &ex) {
|
||||
_push_src = nullptr;
|
||||
WebRtcTransportImp::onShutdown(ex);
|
||||
}
|
||||
|
||||
}// namespace mediakit
|
||||
/*
|
||||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT-like license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "WebRtcPusher.h"
|
||||
#include "Common/config.h"
|
||||
#include "Rtsp/RtspMediaSourceImp.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
WebRtcPusher::Ptr WebRtcPusher::create(const EventPoller::Ptr &poller,
|
||||
const RtspMediaSource::Ptr &src,
|
||||
const std::shared_ptr<void> &ownership,
|
||||
const MediaInfo &info,
|
||||
const ProtocolOption &option,
|
||||
WebRtcTransport::Role role,
|
||||
WebRtcTransport::SignalingProtocols signaling_protocols) {
|
||||
WebRtcPusher::Ptr pusher(new WebRtcPusher(poller, src, ownership, info, option), [](WebRtcPusher *ptr) {
|
||||
ptr->onDestory();
|
||||
delete ptr;
|
||||
});
|
||||
|
||||
pusher->setRole(role);
|
||||
pusher->setSignalingProtocols(signaling_protocols);
|
||||
pusher->onCreate();
|
||||
return pusher;
|
||||
}
|
||||
|
||||
WebRtcPusher::WebRtcPusher(const EventPoller::Ptr &poller,
|
||||
const RtspMediaSource::Ptr &src,
|
||||
const std::shared_ptr<void> &ownership,
|
||||
const MediaInfo &info,
|
||||
const ProtocolOption &option) : WebRtcTransportImp(poller) {
|
||||
_media_info = info;
|
||||
_push_src = src;
|
||||
_push_src_ownership = ownership;
|
||||
_continue_push_ms = option.continue_push_ms;
|
||||
CHECK(_push_src);
|
||||
}
|
||||
|
||||
bool WebRtcPusher::close(MediaSource &sender) {
|
||||
onShutdown(SockException(Err_shutdown, "close media: " + sender.getUrl()));
|
||||
// 主动关闭推流,那么不延时注销 [AUTO-TRANSLATED:ee7cc580]
|
||||
// Actively close the stream, then do not delay the logout
|
||||
_push_src = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
int WebRtcPusher::totalReaderCount(MediaSource &sender) {
|
||||
auto total_count = _push_src ? _push_src->totalReaderCount() : 0;
|
||||
if (_simulcast) {
|
||||
std::lock_guard<std::recursive_mutex> lock(_mtx);
|
||||
for (auto &src : _push_src_sim) {
|
||||
total_count += src.second->totalReaderCount();
|
||||
}
|
||||
}
|
||||
return total_count;
|
||||
}
|
||||
|
||||
MediaOriginType WebRtcPusher::getOriginType(MediaSource &sender) const {
|
||||
return MediaOriginType::rtc_push;
|
||||
}
|
||||
|
||||
string WebRtcPusher::getOriginUrl(MediaSource &sender) const {
|
||||
return _media_info.full_url;
|
||||
}
|
||||
|
||||
std::shared_ptr<SockInfo> WebRtcPusher::getOriginSock(MediaSource &sender) const {
|
||||
return static_pointer_cast<SockInfo>(getSession());
|
||||
}
|
||||
|
||||
toolkit::EventPoller::Ptr WebRtcPusher::getOwnerPoller(MediaSource &sender) {
|
||||
return getPoller();
|
||||
}
|
||||
|
||||
void WebRtcPusher::onRecvRtp(MediaTrack &track, const string &rid, RtpPacket::Ptr rtp) {
|
||||
if (!_simulcast) {
|
||||
assert(_push_src);
|
||||
_push_src->onWrite(rtp, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rtp->type == TrackAudio) {
|
||||
// 音频 [AUTO-TRANSLATED:a577d8e1]
|
||||
// Audio
|
||||
for (auto &pr : _push_src_sim) {
|
||||
pr.second->onWrite(rtp, false);
|
||||
}
|
||||
} else {
|
||||
// 视频 [AUTO-TRANSLATED:904730ac]
|
||||
// Video
|
||||
std::lock_guard<std::recursive_mutex> lock(_mtx);
|
||||
auto &src = _push_src_sim[rid];
|
||||
if (!src) {
|
||||
const auto& stream = _push_src->getMediaTuple().stream;
|
||||
auto src_imp = _push_src->clone(rid.empty() ? stream : stream + '_' + rid);
|
||||
_push_src_sim_ownership[rid] = src_imp->getOwnership();
|
||||
src_imp->setListener(static_pointer_cast<WebRtcPusher>(shared_from_this()));
|
||||
src = src_imp;
|
||||
}
|
||||
src->onWrite(std::move(rtp), false);
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtcPusher::onStartWebRTC() {
|
||||
WebRtcTransportImp::onStartWebRTC();
|
||||
_simulcast = _answer_sdp->supportSimulcast();
|
||||
if (canRecvRtp()) {
|
||||
_push_src->setSdp(_answer_sdp->toRtspSdp());
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtcPusher::onDestory() {
|
||||
auto duration = getDuration();
|
||||
auto bytes_usage = getBytesUsage();
|
||||
// 流量统计事件广播 [AUTO-TRANSLATED:6b0b1234]
|
||||
// Traffic statistics event broadcast
|
||||
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
|
||||
|
||||
if (getSession()) {
|
||||
WarnL << "RTC推流器(" << _media_info.shortUrl() << ")结束推流,耗时(s):" << duration;
|
||||
if (bytes_usage >= iFlowThreshold * 1024) {
|
||||
NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, bytes_usage, duration, false, *getSession());
|
||||
}
|
||||
}
|
||||
|
||||
if (_push_src && _continue_push_ms) {
|
||||
// 取消所有权 [AUTO-TRANSLATED:4895d8fa]
|
||||
// Cancel ownership
|
||||
_push_src_ownership = nullptr;
|
||||
// 延时10秒注销流 [AUTO-TRANSLATED:e1bb11f9]
|
||||
// Delay 10 seconds to log out the stream
|
||||
auto push_src = std::move(_push_src);
|
||||
getPoller()->doDelayTask(_continue_push_ms, [push_src]() { return 0; });
|
||||
}
|
||||
WebRtcTransportImp::onDestory();
|
||||
}
|
||||
|
||||
void WebRtcPusher::onRtcConfigure(RtcConfigure &configure) const {
|
||||
WebRtcTransportImp::onRtcConfigure(configure);
|
||||
// 这只是推流 [AUTO-TRANSLATED:f877bf98]
|
||||
// This is just pushing the stream
|
||||
configure.audio.direction = configure.video.direction = RtpDirection::recvonly;
|
||||
}
|
||||
|
||||
float WebRtcPusher::getLossRate(MediaSource &sender,TrackType type) {
|
||||
return WebRtcTransportImp::getLossRate(type);
|
||||
}
|
||||
|
||||
void WebRtcPusher::OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) {
|
||||
// 主动关闭推流,那么不等待重推 [AUTO-TRANSLATED:1ff514d7]
|
||||
// Actively close the stream, then do not wait for re-pushing
|
||||
_push_src = nullptr;
|
||||
WebRtcTransportImp::OnDtlsTransportClosed(dtlsTransport);
|
||||
}
|
||||
|
||||
void WebRtcPusher::onRtcpBye() {
|
||||
WebRtcTransportImp::onRtcpBye();
|
||||
}
|
||||
|
||||
void WebRtcPusher::onShutdown(const SockException &ex) {
|
||||
_push_src = nullptr;
|
||||
WebRtcTransportImp::onShutdown(ex);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
WebRtcPlayerClient::Ptr WebRtcPlayerClient::create(const EventPoller::Ptr &poller, WebRtcTransport::Role role,
|
||||
WebRtcTransport::SignalingProtocols signaling_protocols) {
|
||||
WebRtcPlayerClient::Ptr pusher(new WebRtcPlayerClient(poller), [](WebRtcPlayerClient *ptr) {
|
||||
ptr->onDestory();
|
||||
delete ptr;
|
||||
});
|
||||
|
||||
pusher->setRole(role);
|
||||
pusher->setSignalingProtocols(signaling_protocols);
|
||||
pusher->onCreate();
|
||||
return pusher;
|
||||
}
|
||||
|
||||
WebRtcPlayerClient::WebRtcPlayerClient(const EventPoller::Ptr &poller)
|
||||
: WebRtcTransportImp(poller) {
|
||||
_demuxer = std::make_shared<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
|
||||
|
||||
@ -1,87 +1,113 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT-like license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef ZLMEDIAKIT_WEBRTCPUSHER_H
|
||||
#define ZLMEDIAKIT_WEBRTCPUSHER_H
|
||||
|
||||
#include "WebRtcTransport.h"
|
||||
#include "Rtsp/RtspMediaSource.h"
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class WebRtcPusher : public WebRtcTransportImp, public MediaSourceEvent {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<WebRtcPusher>;
|
||||
static Ptr create(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src,
|
||||
const std::shared_ptr<void> &ownership, const MediaInfo &info, const ProtocolOption &option);
|
||||
|
||||
protected:
|
||||
///////WebRtcTransportImp override///////
|
||||
void onStartWebRTC() override;
|
||||
void onDestory() override;
|
||||
void onRtcConfigure(RtcConfigure &configure) const override;
|
||||
void onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) override;
|
||||
void onShutdown(const SockException &ex) override;
|
||||
void onRtcpBye() override;
|
||||
// // dtls相关的回调 //// [AUTO-TRANSLATED:31a1f32c]
|
||||
// // dtls related callbacks ////
|
||||
void OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) override;
|
||||
|
||||
protected:
|
||||
///////MediaSourceEvent override///////
|
||||
// 关闭 [AUTO-TRANSLATED:92392f02]
|
||||
// Close
|
||||
bool close(MediaSource &sender) override;
|
||||
// 播放总人数 [AUTO-TRANSLATED:c42a3161]
|
||||
// Total number of players
|
||||
int totalReaderCount(MediaSource &sender) override;
|
||||
// 获取媒体源类型 [AUTO-TRANSLATED:34290a69]
|
||||
// Get media source type
|
||||
MediaOriginType getOriginType(MediaSource &sender) const override;
|
||||
// 获取媒体源url或者文件路径 [AUTO-TRANSLATED:fa34d795]
|
||||
// Get media source url or file path
|
||||
std::string getOriginUrl(MediaSource &sender) const override;
|
||||
// 获取媒体源客户端相关信息 [AUTO-TRANSLATED:037ef910]
|
||||
// Get media source client related information
|
||||
std::shared_ptr<SockInfo> getOriginSock(MediaSource &sender) const override;
|
||||
// 由于支持断连续推,存在OwnerPoller变更的可能 [AUTO-TRANSLATED:1c863b40]
|
||||
// Due to support for discontinuous pushing, there is a possibility of OwnerPoller changes
|
||||
toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override;
|
||||
// 获取丢包率 [AUTO-TRANSLATED:ec61b378]
|
||||
// Get packet loss rate
|
||||
float getLossRate(MediaSource &sender,TrackType type) override;
|
||||
|
||||
private:
|
||||
WebRtcPusher(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src,
|
||||
const std::shared_ptr<void> &ownership, const MediaInfo &info, const ProtocolOption &option);
|
||||
|
||||
private:
|
||||
bool _simulcast = false;
|
||||
// 断连续推延时 [AUTO-TRANSLATED:13ad578a]
|
||||
// Discontinuous pushing delay
|
||||
uint32_t _continue_push_ms = 0;
|
||||
// 媒体相关元数据 [AUTO-TRANSLATED:f4cf8045]
|
||||
// Media related metadata
|
||||
MediaInfo _media_info;
|
||||
// 推流的rtsp源 [AUTO-TRANSLATED:4f976bca]
|
||||
// Rtsp source of the stream
|
||||
RtspMediaSource::Ptr _push_src;
|
||||
// 推流所有权 [AUTO-TRANSLATED:d0ddf5c7]
|
||||
// Stream ownership
|
||||
std::shared_ptr<void> _push_src_ownership;
|
||||
// 推流的rtsp源,支持simulcast [AUTO-TRANSLATED:44be9120]
|
||||
// Rtsp source of the stream, supports simulcast
|
||||
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
|
||||
/*
|
||||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT-like license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef ZLMEDIAKIT_WEBRTCPUSHER_H
|
||||
#define ZLMEDIAKIT_WEBRTCPUSHER_H
|
||||
|
||||
#include "WebRtcTransport.h"
|
||||
#include "Rtsp/RtspDemuxer.h"
|
||||
#include "Rtsp/RtspMediaSource.h"
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class WebRtcPusher : public WebRtcTransportImp, public MediaSourceEvent {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<WebRtcPusher>;
|
||||
static Ptr create(const toolkit::EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src,
|
||||
const std::shared_ptr<void> &ownership, const MediaInfo &info, const ProtocolOption &option,
|
||||
WebRtcTransport::Role role, WebRtcTransport::SignalingProtocols signaling_protocols);
|
||||
|
||||
protected:
|
||||
///////WebRtcTransportImp override///////
|
||||
void onStartWebRTC() override;
|
||||
void onDestory() override;
|
||||
void onRtcConfigure(RtcConfigure &configure) const override;
|
||||
void onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) override;
|
||||
void onShutdown(const toolkit::SockException &ex) override;
|
||||
void onRtcpBye() override;
|
||||
// // dtls相关的回调 //// [AUTO-TRANSLATED:31a1f32c]
|
||||
// // dtls related callbacks ////
|
||||
void OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) override;
|
||||
|
||||
protected:
|
||||
///////MediaSourceEvent override///////
|
||||
// 关闭 [AUTO-TRANSLATED:92392f02]
|
||||
// Close
|
||||
bool close(MediaSource &sender) override;
|
||||
// 播放总人数 [AUTO-TRANSLATED:c42a3161]
|
||||
// Total number of players
|
||||
int totalReaderCount(MediaSource &sender) override;
|
||||
// 获取媒体源类型 [AUTO-TRANSLATED:34290a69]
|
||||
// Get media source type
|
||||
MediaOriginType getOriginType(MediaSource &sender) const override;
|
||||
// 获取媒体源url或者文件路径 [AUTO-TRANSLATED:fa34d795]
|
||||
// Get media source url or file path
|
||||
std::string getOriginUrl(MediaSource &sender) const override;
|
||||
// 获取媒体源客户端相关信息 [AUTO-TRANSLATED:037ef910]
|
||||
// Get media source client related information
|
||||
std::shared_ptr<toolkit::SockInfo> getOriginSock(MediaSource &sender) const override;
|
||||
// 由于支持断连续推,存在OwnerPoller变更的可能 [AUTO-TRANSLATED:1c863b40]
|
||||
// Due to support for discontinuous pushing, there is a possibility of OwnerPoller changes
|
||||
toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override;
|
||||
// 获取丢包率 [AUTO-TRANSLATED:ec61b378]
|
||||
// Get packet loss rate
|
||||
float getLossRate(MediaSource &sender,TrackType type) override;
|
||||
|
||||
private:
|
||||
WebRtcPusher(const toolkit::EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src,
|
||||
const std::shared_ptr<void> &ownership, const MediaInfo &info, const ProtocolOption &option);
|
||||
|
||||
private:
|
||||
bool _simulcast = false;
|
||||
// 断连续推延时 [AUTO-TRANSLATED:13ad578a]
|
||||
// Discontinuous pushing delay
|
||||
uint32_t _continue_push_ms = 0;
|
||||
// 媒体相关元数据 [AUTO-TRANSLATED:f4cf8045]
|
||||
// Media related metadata
|
||||
MediaInfo _media_info;
|
||||
// 推流的rtsp源 [AUTO-TRANSLATED:4f976bca]
|
||||
// Rtsp source of the stream
|
||||
RtspMediaSource::Ptr _push_src;
|
||||
// 推流所有权 [AUTO-TRANSLATED:d0ddf5c7]
|
||||
// Stream ownership
|
||||
std::shared_ptr<void> _push_src_ownership;
|
||||
// 推流的rtsp源,支持simulcast [AUTO-TRANSLATED:44be9120]
|
||||
// Rtsp source of the stream, supports simulcast
|
||||
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;
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
@ -1,166 +1,163 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT-like license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "WebRtcSession.h"
|
||||
#include "Util/util.h"
|
||||
#include "Network/TcpServer.h"
|
||||
#include "Common/config.h"
|
||||
#include "IceServer.hpp"
|
||||
#include "WebRtcTransport.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
static string getUserName(const char *buf, size_t len) {
|
||||
if (!RTC::StunPacket::IsStun((const uint8_t *) buf, len)) {
|
||||
return "";
|
||||
}
|
||||
std::unique_ptr<RTC::StunPacket> packet(RTC::StunPacket::Parse((const uint8_t *) buf, len));
|
||||
if (!packet) {
|
||||
return "";
|
||||
}
|
||||
if (packet->GetClass() != RTC::StunPacket::Class::REQUEST ||
|
||||
packet->GetMethod() != RTC::StunPacket::Method::BINDING) {
|
||||
return "";
|
||||
}
|
||||
// 收到binding request请求 [AUTO-TRANSLATED:eff4d773]
|
||||
// Received binding request
|
||||
auto vec = split(packet->GetUsername(), ":");
|
||||
return vec[0];
|
||||
}
|
||||
|
||||
EventPoller::Ptr WebRtcSession::queryPoller(const Buffer::Ptr &buffer) {
|
||||
auto user_name = getUserName(buffer->data(), buffer->size());
|
||||
if (user_name.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto ret = WebRtcTransportManager::Instance().getItem(user_name);
|
||||
return ret ? ret->getPoller() : nullptr;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
WebRtcSession::WebRtcSession(const Socket::Ptr &sock) : Session(sock) {
|
||||
_over_tcp = sock->sockType() == SockNum::Sock_TCP;
|
||||
}
|
||||
|
||||
void WebRtcSession::attachServer(const Server &server) {
|
||||
_server = std::static_pointer_cast<toolkit::TcpServer>(const_cast<Server &>(server).shared_from_this());
|
||||
}
|
||||
|
||||
void WebRtcSession::onRecv_l(const char *data, size_t len) {
|
||||
if (_find_transport) {
|
||||
// 只允许寻找一次transport [AUTO-TRANSLATED:446fae53]
|
||||
// Only allow searching for transport once
|
||||
_find_transport = false;
|
||||
auto user_name = getUserName(data, len);
|
||||
auto transport = WebRtcTransportManager::Instance().getItem(user_name);
|
||||
CHECK(transport);
|
||||
|
||||
// WebRtcTransport在其他poller线程上,需要切换poller线程并重新创建WebRtcSession对象 [AUTO-TRANSLATED:7e5534cf]
|
||||
// WebRtcTransport is on another poller thread, need to switch poller thread and recreate WebRtcSession object
|
||||
if (!transport->getPoller()->isCurrentThread()) {
|
||||
auto sock = Socket::createSocket(transport->getPoller(), false);
|
||||
// 1、克隆socket(fd不变),切换poller线程到WebRtcTransport所在线程 [AUTO-TRANSLATED:f930bfab]
|
||||
// 1. Clone socket (fd remains unchanged), switch poller thread to the thread where WebRtcTransport is located
|
||||
sock->cloneSocket(*(getSock()));
|
||||
auto server = _server;
|
||||
std::string str(data, len);
|
||||
sock->getPoller()->async([sock, server, str](){
|
||||
auto strong_server = server.lock();
|
||||
if (strong_server) {
|
||||
auto session = static_pointer_cast<WebRtcSession>(strong_server->createSession(sock));
|
||||
// 2、创建新的WebRtcSession对象(绑定到WebRtcTransport所在线程),重新处理一遍ice binding request命令 [AUTO-TRANSLATED:c75203bb]
|
||||
// 2. Create a new WebRtcSession object (bound to the thread where WebRtcTransport is located), reprocess the ice binding request command
|
||||
session->onRecv_l(str.data(), str.size());
|
||||
}
|
||||
});
|
||||
// 3、销毁原先的socket和WebRtcSession(原先的对象跟WebRtcTransport不在同一条线程) [AUTO-TRANSLATED:a6d6d63f]
|
||||
// 3. Destroy the original socket and WebRtcSession (the original object is not on the same thread as WebRtcTransport)
|
||||
throw std::runtime_error("webrtc over tcp change poller: " + getPoller()->getThreadName() + " -> " + sock->getPoller()->getThreadName());
|
||||
}
|
||||
_transport = std::move(transport);
|
||||
InfoP(this);
|
||||
}
|
||||
_ticker.resetTime();
|
||||
CHECK(_transport);
|
||||
_transport->inputSockData((char *)data, len, this);
|
||||
}
|
||||
|
||||
void WebRtcSession::onRecv(const Buffer::Ptr &buffer) {
|
||||
if (_over_tcp) {
|
||||
input(buffer->data(), buffer->size());
|
||||
} else {
|
||||
onRecv_l(buffer->data(), buffer->size());
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtcSession::onError(const SockException &err) {
|
||||
// udp链接超时,但是rtc链接不一定超时,因为可能存在链接迁移的情况 [AUTO-TRANSLATED:aaa9672f]
|
||||
// UDP connection timeout, but RTC connection may not timeout, because there may be connection migration
|
||||
// 在udp链接迁移时,新的WebRtcSession对象将接管WebRtcTransport对象的生命周期 [AUTO-TRANSLATED:7e7d19df]
|
||||
// When UDP connection migrates, the new WebRtcSession object will take over the life cycle of the WebRtcTransport object
|
||||
// 本WebRtcSession对象将在超时后自动销毁 [AUTO-TRANSLATED:bc903a06]
|
||||
// This WebRtcSession object will be automatically destroyed after timeout
|
||||
WarnP(this) << err;
|
||||
|
||||
if (!_transport) {
|
||||
return;
|
||||
}
|
||||
auto self = static_pointer_cast<WebRtcSession>(shared_from_this());
|
||||
auto transport = std::move(_transport);
|
||||
getPoller()->async([transport, self]() mutable {
|
||||
// 延时减引用,防止使用transport对象时,销毁对象 [AUTO-TRANSLATED:09dd6609]
|
||||
// Delay decrementing the reference count to prevent the object from being destroyed when using the transport object
|
||||
transport->removeTuple(self.get());
|
||||
// 确保transport在Session对象前销毁,防止WebRtcTransport::onDestory()时获取不到Session对象 [AUTO-TRANSLATED:acd8bd77]
|
||||
// Ensure that the transport is destroyed before the Session object to prevent WebRtcTransport::onDestory() from not being able to get the Session object
|
||||
transport = nullptr;
|
||||
}, false);
|
||||
}
|
||||
|
||||
void WebRtcSession::onManager() {
|
||||
GET_CONFIG(float, timeoutSec, Rtc::kTimeOutSec);
|
||||
if (!_transport && _ticker.createdTime() > timeoutSec * 1000) {
|
||||
shutdown(SockException(Err_timeout, "illegal webrtc connection"));
|
||||
return;
|
||||
}
|
||||
if (_ticker.elapsedTime() > timeoutSec * 1000) {
|
||||
shutdown(SockException(Err_timeout, "webrtc connection timeout"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t WebRtcSession::onRecvHeader(const char *data, size_t len) {
|
||||
onRecv_l(data + 2, len - 2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *WebRtcSession::onSearchPacketTail(const char *data, size_t len) {
|
||||
if (len < 2) {
|
||||
// 数据不够 [AUTO-TRANSLATED:830a2785]
|
||||
// Not enough data
|
||||
return nullptr;
|
||||
}
|
||||
uint16_t length = (((uint8_t *)data)[0] << 8) | ((uint8_t *)data)[1];
|
||||
if (len < (size_t)(length + 2)) {
|
||||
// 数据不够 [AUTO-TRANSLATED:830a2785]
|
||||
// Not enough data
|
||||
return nullptr;
|
||||
}
|
||||
// 返回rtp包末尾 [AUTO-TRANSLATED:5134cf6f]
|
||||
// Return the end of the RTP packet
|
||||
return data + 2 + length;
|
||||
}
|
||||
|
||||
}// namespace mediakit
|
||||
|
||||
|
||||
/*
|
||||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT-like license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "WebRtcSession.h"
|
||||
#include "Util/util.h"
|
||||
#include "Network/TcpServer.h"
|
||||
#include "Common/config.h"
|
||||
#include "IceTransport.hpp"
|
||||
#include "WebRtcTransport.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
static string getUserName(const char *buf, size_t len) {
|
||||
if (!RTC::StunPacket::isStun((const uint8_t *) buf, len)) {
|
||||
return "";
|
||||
}
|
||||
auto packet = RTC::StunPacket::parse((const uint8_t *) buf, len);
|
||||
if (!packet) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// 收到binding request请求 [AUTO-TRANSLATED:eff4d773]
|
||||
// Received binding request
|
||||
auto vec = split(packet->getUsername(), ":");
|
||||
return vec[0];
|
||||
}
|
||||
|
||||
EventPoller::Ptr WebRtcSession::queryPoller(const Buffer::Ptr &buffer) {
|
||||
auto user_name = getUserName(buffer->data(), buffer->size());
|
||||
if (user_name.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto ret = WebRtcTransportManager::Instance().getItem(user_name);
|
||||
return ret ? ret->getPoller() : nullptr;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
WebRtcSession::WebRtcSession(const Socket::Ptr &sock) : Session(sock) {
|
||||
_over_tcp = sock->sockType() == SockNum::Sock_TCP;
|
||||
}
|
||||
|
||||
void WebRtcSession::attachServer(const Server &server) {
|
||||
_server = std::static_pointer_cast<toolkit::TcpServer>(const_cast<Server &>(server).shared_from_this());
|
||||
}
|
||||
|
||||
void WebRtcSession::onRecv_l(const char *data, size_t len) {
|
||||
if (_find_transport) {
|
||||
// 只允许寻找一次transport [AUTO-TRANSLATED:446fae53]
|
||||
// Only allow searching for transport once
|
||||
_find_transport = false;
|
||||
auto user_name = getUserName(data, len);
|
||||
auto transport = WebRtcTransportManager::Instance().getItem(user_name);
|
||||
CHECK(transport);
|
||||
|
||||
// WebRtcTransport在其他poller线程上,需要切换poller线程并重新创建WebRtcSession对象 [AUTO-TRANSLATED:7e5534cf]
|
||||
// WebRtcTransport is on another poller thread, need to switch poller thread and recreate WebRtcSession object
|
||||
if (!transport->getPoller()->isCurrentThread()) {
|
||||
auto sock = Socket::createSocket(transport->getPoller(), false);
|
||||
// 1、克隆socket(fd不变),切换poller线程到WebRtcTransport所在线程 [AUTO-TRANSLATED:f930bfab]
|
||||
// 1. Clone socket (fd remains unchanged), switch poller thread to the thread where WebRtcTransport is located
|
||||
sock->cloneSocket(*(getSock()));
|
||||
auto server = _server;
|
||||
std::string str(data, len);
|
||||
sock->getPoller()->async([sock, server, str](){
|
||||
auto strong_server = server.lock();
|
||||
if (strong_server) {
|
||||
auto session = static_pointer_cast<WebRtcSession>(strong_server->createSession(sock));
|
||||
// 2、创建新的WebRtcSession对象(绑定到WebRtcTransport所在线程),重新处理一遍ice binding request命令 [AUTO-TRANSLATED:c75203bb]
|
||||
// 2. Create a new WebRtcSession object (bound to the thread where WebRtcTransport is located), reprocess the ice binding request command
|
||||
session->onRecv_l(str.data(), str.size());
|
||||
}
|
||||
});
|
||||
// 3、销毁原先的socket和WebRtcSession(原先的对象跟WebRtcTransport不在同一条线程) [AUTO-TRANSLATED:a6d6d63f]
|
||||
// 3. Destroy the original socket and WebRtcSession (the original object is not on the same thread as WebRtcTransport)
|
||||
throw std::runtime_error("webrtc over tcp change poller: " + getPoller()->getThreadName() + " -> " + sock->getPoller()->getThreadName());
|
||||
}
|
||||
_transport = std::move(transport);
|
||||
InfoP(this);
|
||||
}
|
||||
_ticker.resetTime();
|
||||
CHECK(_transport);
|
||||
auto self = static_pointer_cast<WebRtcSession>(shared_from_this());
|
||||
_transport->inputSockData(data, len, self);
|
||||
}
|
||||
|
||||
void WebRtcSession::onRecv(const Buffer::Ptr &buffer) {
|
||||
if (_over_tcp) {
|
||||
input(buffer->data(), buffer->size());
|
||||
} else {
|
||||
onRecv_l(buffer->data(), buffer->size());
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtcSession::onError(const SockException &err) {
|
||||
// udp链接超时,但是rtc链接不一定超时,因为可能存在链接迁移的情况 [AUTO-TRANSLATED:aaa9672f]
|
||||
// UDP connection timeout, but RTC connection may not timeout, because there may be connection migration
|
||||
// 在udp链接迁移时,新的WebRtcSession对象将接管WebRtcTransport对象的生命周期 [AUTO-TRANSLATED:7e7d19df]
|
||||
// When UDP connection migrates, the new WebRtcSession object will take over the life cycle of the WebRtcTransport object
|
||||
// 本WebRtcSession对象将在超时后自动销毁 [AUTO-TRANSLATED:bc903a06]
|
||||
// This WebRtcSession object will be automatically destroyed after timeout
|
||||
WarnP(this) << err;
|
||||
|
||||
if (!_transport) {
|
||||
return;
|
||||
}
|
||||
auto self = static_pointer_cast<WebRtcSession>(shared_from_this());
|
||||
auto transport = std::move(_transport);
|
||||
getPoller()->async([transport, self]() mutable {
|
||||
// 延时减引用,防止使用transport对象时,销毁对象 [AUTO-TRANSLATED:09dd6609]
|
||||
// Delay decrementing the reference count to prevent the object from being destroyed when using the transport object
|
||||
transport->removePair(self.get());
|
||||
// 确保transport在Session对象前销毁,防止WebRtcTransport::onDestory()时获取不到Session对象 [AUTO-TRANSLATED:acd8bd77]
|
||||
// Ensure that the transport is destroyed before the Session object to prevent WebRtcTransport::onDestory() from not being able to get the Session object
|
||||
transport = nullptr;
|
||||
}, false);
|
||||
}
|
||||
|
||||
void WebRtcSession::onManager() {
|
||||
GET_CONFIG(float, timeoutSec, Rtc::kTimeOutSec);
|
||||
if (!_transport && _ticker.createdTime() > timeoutSec * 1000) {
|
||||
shutdown(SockException(Err_timeout, "illegal webrtc connection"));
|
||||
return;
|
||||
}
|
||||
if (_ticker.elapsedTime() > timeoutSec * 1000) {
|
||||
shutdown(SockException(Err_timeout, "webrtc connection timeout"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t WebRtcSession::onRecvHeader(const char *data, size_t len) {
|
||||
onRecv_l(data + 2, len - 2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *WebRtcSession::onSearchPacketTail(const char *data, size_t len) {
|
||||
if (len < 2) {
|
||||
// 数据不够 [AUTO-TRANSLATED:830a2785]
|
||||
// Not enough data
|
||||
return nullptr;
|
||||
}
|
||||
uint16_t length = (((uint8_t *)data)[0] << 8) | ((uint8_t *)data)[1];
|
||||
if (len < (size_t)(length + 2)) {
|
||||
// 数据不够 [AUTO-TRANSLATED:830a2785]
|
||||
// Not enough data
|
||||
return nullptr;
|
||||
}
|
||||
// 返回rtp包末尾 [AUTO-TRANSLATED:5134cf6f]
|
||||
// Return the end of the RTP packet
|
||||
return data + 2 + length;
|
||||
}
|
||||
|
||||
}// namespace mediakit
|
||||
|
||||
@ -21,18 +21,18 @@ namespace toolkit {
|
||||
}
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class WebRtcTransportImp;
|
||||
using namespace toolkit;
|
||||
|
||||
class WebRtcSession : public Session, public HttpRequestSplitter {
|
||||
class WebRtcSession : public toolkit::Session, public HttpRequestSplitter {
|
||||
public:
|
||||
WebRtcSession(const Socket::Ptr &sock);
|
||||
WebRtcSession(const toolkit::Socket::Ptr &sock);
|
||||
|
||||
void attachServer(const Server &server) override;
|
||||
void onRecv(const Buffer::Ptr &) override;
|
||||
void onError(const SockException &err) override;
|
||||
void attachServer(const toolkit::Server &server) override;
|
||||
void onRecv(const toolkit::Buffer::Ptr &) override;
|
||||
void onError(const toolkit::SockException &err) override;
|
||||
void onManager() override;
|
||||
static EventPoller::Ptr queryPoller(const Buffer::Ptr &buffer);
|
||||
static toolkit::EventPoller::Ptr queryPoller(const toolkit::Buffer::Ptr &buffer);
|
||||
|
||||
protected:
|
||||
WebRtcTransportImp::Ptr _transport;
|
||||
@ -47,7 +47,7 @@ private:
|
||||
private:
|
||||
bool _over_tcp = false;
|
||||
bool _find_transport = true;
|
||||
Ticker _ticker;
|
||||
toolkit::Ticker _ticker;
|
||||
std::weak_ptr<toolkit::TcpServer> _server;
|
||||
};
|
||||
|
||||
|
||||
49
webrtc/WebRtcSignalingMsg.cpp
Normal file
49
webrtc/WebRtcSignalingMsg.cpp
Normal 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
|
||||
58
webrtc/WebRtcSignalingMsg.h
Normal file
58
webrtc/WebRtcSignalingMsg.h
Normal 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
|
||||
687
webrtc/WebRtcSignalingPeer.cpp
Normal file
687
webrtc/WebRtcSignalingPeer.cpp
Normal 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
|
||||
141
webrtc/WebRtcSignalingPeer.h
Normal file
141
webrtc/WebRtcSignalingPeer.h
Normal 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
|
||||
488
webrtc/WebRtcSignalingSession.cpp
Normal file
488
webrtc/WebRtcSignalingSession.cpp
Normal 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
|
||||
85
webrtc/WebRtcSignalingSession.h
Normal file
85
webrtc/WebRtcSignalingSession.h
Normal 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
@ -1,400 +1,449 @@
|
||||
/*
|
||||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT-like license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "DtlsTransport.hpp"
|
||||
#include "IceServer.hpp"
|
||||
#include "SrtpSession.hpp"
|
||||
#include "StunPacket.hpp"
|
||||
#include "Sdp.h"
|
||||
#include "Util/mini.h"
|
||||
#include "Poller/EventPoller.h"
|
||||
#include "Network/Socket.h"
|
||||
#include "Network/Session.h"
|
||||
#include "Nack.h"
|
||||
#include "TwccContext.h"
|
||||
#include "SctpAssociation.hpp"
|
||||
#include "Rtcp/RtcpContext.h"
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
// RTC配置项目 [AUTO-TRANSLATED:65784416]
|
||||
// RTC configuration project
|
||||
namespace Rtc {
|
||||
extern const std::string kPort;
|
||||
extern const std::string kTcpPort;
|
||||
extern const std::string kTimeOutSec;
|
||||
}//namespace RTC
|
||||
|
||||
class WebRtcInterface {
|
||||
public:
|
||||
virtual ~WebRtcInterface() = default;
|
||||
virtual std::string getAnswerSdp(const std::string &offer) = 0;
|
||||
virtual const std::string& getIdentifier() const = 0;
|
||||
virtual const std::string& deleteRandStr() const { static std::string s_null; return s_null; }
|
||||
virtual void setIceCandidate(std::vector<SdpAttrCandidate> cands) {}
|
||||
virtual void setLocalIp(std::string localIp) {}
|
||||
virtual void setPreferredTcp(bool flag) {}
|
||||
};
|
||||
|
||||
class WebRtcException : public WebRtcInterface {
|
||||
public:
|
||||
WebRtcException(const SockException &ex) : _ex(ex) {};
|
||||
std::string getAnswerSdp(const std::string &offer) override {
|
||||
throw _ex;
|
||||
}
|
||||
const std::string &getIdentifier() const override {
|
||||
static std::string s_null;
|
||||
return s_null;
|
||||
}
|
||||
|
||||
private:
|
||||
SockException _ex;
|
||||
};
|
||||
|
||||
class WebRtcTransport : public WebRtcInterface, public RTC::DtlsTransport::Listener, public RTC::IceServer::Listener, public std::enable_shared_from_this<WebRtcTransport>
|
||||
#ifdef ENABLE_SCTP
|
||||
, public RTC::SctpAssociation::Listener
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
using Ptr = std::shared_ptr<WebRtcTransport>;
|
||||
WebRtcTransport(const EventPoller::Ptr &poller);
|
||||
|
||||
/**
|
||||
* 创建对象
|
||||
* Create object
|
||||
|
||||
* [AUTO-TRANSLATED:830344e4]
|
||||
*/
|
||||
virtual void onCreate();
|
||||
|
||||
/**
|
||||
* 销毁对象
|
||||
* Destroy object
|
||||
|
||||
* [AUTO-TRANSLATED:1016b97b]
|
||||
*/
|
||||
virtual void onDestory();
|
||||
|
||||
/**
|
||||
* 创建webrtc answer sdp
|
||||
* @param offer offer sdp
|
||||
* @return answer sdp
|
||||
* Create webrtc answer sdp
|
||||
* @param offer offer sdp
|
||||
* @return answer sdp
|
||||
|
||||
* [AUTO-TRANSLATED:d9b027d7]
|
||||
*/
|
||||
std::string getAnswerSdp(const std::string &offer) override final;
|
||||
|
||||
/**
|
||||
* 获取对象唯一id
|
||||
* Get object unique id
|
||||
|
||||
* [AUTO-TRANSLATED:9ad519c6]
|
||||
*/
|
||||
const std::string& getIdentifier() const override;
|
||||
const std::string& deleteRandStr() const override;
|
||||
|
||||
/**
|
||||
* socket收到udp数据
|
||||
* @param buf 数据指针
|
||||
* @param len 数据长度
|
||||
* @param tuple 数据来源
|
||||
* Socket receives udp data
|
||||
* @param buf data pointer
|
||||
* @param len data length
|
||||
* @param tuple data source
|
||||
|
||||
* [AUTO-TRANSLATED:1ee86069]
|
||||
*/
|
||||
void inputSockData(char *buf, int len, RTC::TransportTuple *tuple);
|
||||
|
||||
/**
|
||||
* 发送rtp
|
||||
* @param buf rtcp内容
|
||||
* @param len rtcp长度
|
||||
* @param flush 是否flush socket
|
||||
* @param ctx 用户指针
|
||||
* Send rtp
|
||||
* @param buf rtcp content
|
||||
* @param len rtcp length
|
||||
* @param flush whether to flush socket
|
||||
* @param ctx user pointer
|
||||
|
||||
* [AUTO-TRANSLATED:aa833695]
|
||||
*/
|
||||
void sendRtpPacket(const char *buf, int len, bool flush, void *ctx = nullptr);
|
||||
void sendRtcpPacket(const char *buf, int len, bool flush, void *ctx = nullptr);
|
||||
void sendDatachannel(uint16_t streamId, uint32_t ppid, const char *msg, size_t len);
|
||||
|
||||
const EventPoller::Ptr& getPoller() const;
|
||||
Session::Ptr getSession() const;
|
||||
|
||||
protected:
|
||||
// // dtls相关的回调 //// [AUTO-TRANSLATED:31a1f32c]
|
||||
// // dtls related callbacks ////
|
||||
void OnDtlsTransportConnecting(const RTC::DtlsTransport *dtlsTransport) override;
|
||||
void OnDtlsTransportConnected(const RTC::DtlsTransport *dtlsTransport,
|
||||
RTC::SrtpSession::CryptoSuite srtpCryptoSuite,
|
||||
uint8_t *srtpLocalKey,
|
||||
size_t srtpLocalKeyLen,
|
||||
uint8_t *srtpRemoteKey,
|
||||
size_t srtpRemoteKeyLen,
|
||||
std::string &remoteCert) override;
|
||||
|
||||
void OnDtlsTransportFailed(const RTC::DtlsTransport *dtlsTransport) override;
|
||||
void OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) override;
|
||||
void OnDtlsTransportSendData(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) override;
|
||||
void OnDtlsTransportApplicationDataReceived(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) override;
|
||||
|
||||
protected:
|
||||
// // ice相关的回调 /// [AUTO-TRANSLATED:30abf693]
|
||||
// // ice related callbacks ///
|
||||
void OnIceServerSendStunPacket(const RTC::IceServer *iceServer, const RTC::StunPacket *packet, RTC::TransportTuple *tuple) override;
|
||||
void OnIceServerConnected(const RTC::IceServer *iceServer) override;
|
||||
void OnIceServerCompleted(const RTC::IceServer *iceServer) override;
|
||||
void OnIceServerDisconnected(const RTC::IceServer *iceServer) override;
|
||||
|
||||
#ifdef ENABLE_SCTP
|
||||
void OnSctpAssociationConnecting(RTC::SctpAssociation* sctpAssociation) override;
|
||||
void OnSctpAssociationConnected(RTC::SctpAssociation* sctpAssociation) override;
|
||||
void OnSctpAssociationFailed(RTC::SctpAssociation* sctpAssociation) override;
|
||||
void OnSctpAssociationClosed(RTC::SctpAssociation* sctpAssociation) override;
|
||||
void OnSctpAssociationSendData(RTC::SctpAssociation* sctpAssociation, const uint8_t* data, size_t len) override;
|
||||
void OnSctpAssociationMessageReceived(RTC::SctpAssociation *sctpAssociation, uint16_t streamId, uint32_t ppid,
|
||||
const uint8_t *msg, size_t len) override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
virtual void onStartWebRTC() = 0;
|
||||
virtual void onRtcConfigure(RtcConfigure &configure) const;
|
||||
virtual void onCheckSdp(SdpType type, RtcSession &sdp) = 0;
|
||||
virtual void onSendSockData(Buffer::Ptr buf, bool flush = true, RTC::TransportTuple *tuple = nullptr) = 0;
|
||||
|
||||
virtual void onRtp(const char *buf, size_t len, uint64_t stamp_ms) = 0;
|
||||
virtual void onRtcp(const char *buf, size_t len) = 0;
|
||||
virtual void onShutdown(const SockException &ex) = 0;
|
||||
virtual void onBeforeEncryptRtp(const char *buf, int &len, void *ctx) = 0;
|
||||
virtual void onBeforeEncryptRtcp(const char *buf, int &len, void *ctx) = 0;
|
||||
virtual void onRtcpBye() = 0;
|
||||
|
||||
protected:
|
||||
void sendRtcpRemb(uint32_t ssrc, size_t bit_rate);
|
||||
void sendRtcpPli(uint32_t ssrc);
|
||||
|
||||
private:
|
||||
void sendSockData(const char *buf, size_t len, RTC::TransportTuple *tuple);
|
||||
void setRemoteDtlsFingerprint(const RtcSession &remote);
|
||||
|
||||
protected:
|
||||
RtcSession::Ptr _offer_sdp;
|
||||
RtcSession::Ptr _answer_sdp;
|
||||
std::shared_ptr<RTC::IceServer> _ice_server;
|
||||
|
||||
private:
|
||||
mutable std::string _delete_rand_str;
|
||||
std::string _identifier;
|
||||
EventPoller::Ptr _poller;
|
||||
std::shared_ptr<RTC::DtlsTransport> _dtls_transport;
|
||||
std::shared_ptr<RTC::SrtpSession> _srtp_session_send;
|
||||
std::shared_ptr<RTC::SrtpSession> _srtp_session_recv;
|
||||
Ticker _ticker;
|
||||
// 循环池 [AUTO-TRANSLATED:b7059f37]
|
||||
// Cycle pool
|
||||
ResourcePool<BufferRaw> _packet_pool;
|
||||
|
||||
#ifdef ENABLE_SCTP
|
||||
RTC::SctpAssociationImp::Ptr _sctp;
|
||||
#endif
|
||||
};
|
||||
|
||||
class RtpChannel;
|
||||
class MediaTrack {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<MediaTrack>;
|
||||
const RtcCodecPlan *plan_rtp;
|
||||
const RtcCodecPlan *plan_rtx;
|
||||
uint32_t offer_ssrc_rtp = 0;
|
||||
uint32_t offer_ssrc_rtx = 0;
|
||||
uint32_t answer_ssrc_rtp = 0;
|
||||
uint32_t answer_ssrc_rtx = 0;
|
||||
const RtcMedia *media;
|
||||
RtpExtContext::Ptr rtp_ext_ctx;
|
||||
|
||||
//for send rtp
|
||||
NackList nack_list;
|
||||
RtcpContext::Ptr rtcp_context_send;
|
||||
|
||||
//for recv rtp
|
||||
std::unordered_map<std::string/*rid*/, std::shared_ptr<RtpChannel> > rtp_channel;
|
||||
std::shared_ptr<RtpChannel> getRtpChannel(uint32_t ssrc) const;
|
||||
};
|
||||
|
||||
struct WrappedMediaTrack {
|
||||
MediaTrack::Ptr track;
|
||||
explicit WrappedMediaTrack(MediaTrack::Ptr ptr): track(ptr) {}
|
||||
virtual ~WrappedMediaTrack() {}
|
||||
virtual void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) = 0;
|
||||
};
|
||||
|
||||
struct WrappedRtxTrack: public WrappedMediaTrack {
|
||||
explicit WrappedRtxTrack(MediaTrack::Ptr ptr)
|
||||
: WrappedMediaTrack(std::move(ptr)) {}
|
||||
void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) override;
|
||||
};
|
||||
|
||||
class WebRtcTransportImp;
|
||||
|
||||
struct WrappedRtpTrack : public WrappedMediaTrack {
|
||||
explicit WrappedRtpTrack(MediaTrack::Ptr ptr, TwccContext& twcc, WebRtcTransportImp& t)
|
||||
: WrappedMediaTrack(std::move(ptr))
|
||||
, _twcc_ctx(twcc)
|
||||
, _transport(t) {}
|
||||
TwccContext& _twcc_ctx;
|
||||
WebRtcTransportImp& _transport;
|
||||
void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) override;
|
||||
};
|
||||
|
||||
class WebRtcTransportImp : public WebRtcTransport {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<WebRtcTransportImp>;
|
||||
~WebRtcTransportImp() override;
|
||||
|
||||
uint64_t getBytesUsage() const;
|
||||
uint64_t getDuration() const;
|
||||
bool canSendRtp() const;
|
||||
bool canRecvRtp() const;
|
||||
void onSendRtp(const RtpPacket::Ptr &rtp, bool flush, bool rtx = false);
|
||||
|
||||
void createRtpChannel(const std::string &rid, uint32_t ssrc, MediaTrack &track);
|
||||
void removeTuple(RTC::TransportTuple* tuple);
|
||||
void safeShutdown(const SockException &ex);
|
||||
|
||||
void setPreferredTcp(bool flag) override;
|
||||
void setLocalIp(std::string local_ip) override;
|
||||
void setIceCandidate(std::vector<SdpAttrCandidate> cands) override;
|
||||
|
||||
protected:
|
||||
void OnIceServerSelectedTuple(const RTC::IceServer *iceServer, RTC::TransportTuple *tuple) override;
|
||||
WebRtcTransportImp(const EventPoller::Ptr &poller);
|
||||
void OnDtlsTransportApplicationDataReceived(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) override;
|
||||
void onStartWebRTC() override;
|
||||
void onSendSockData(Buffer::Ptr buf, bool flush = true, RTC::TransportTuple *tuple = nullptr) override;
|
||||
void onCheckSdp(SdpType type, RtcSession &sdp) override;
|
||||
void onRtcConfigure(RtcConfigure &configure) const override;
|
||||
|
||||
void onRtp(const char *buf, size_t len, uint64_t stamp_ms) override;
|
||||
void onRtcp(const char *buf, size_t len) override;
|
||||
void onBeforeEncryptRtp(const char *buf, int &len, void *ctx) override;
|
||||
void onBeforeEncryptRtcp(const char *buf, int &len, void *ctx) override {};
|
||||
void onCreate() override;
|
||||
void onDestory() override;
|
||||
void onShutdown(const SockException &ex) override;
|
||||
virtual void onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) {}
|
||||
void updateTicker();
|
||||
float getLossRate(TrackType type);
|
||||
void onRtcpBye() override;
|
||||
|
||||
private:
|
||||
void onSortedRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp);
|
||||
void onSendNack(MediaTrack &track, const FCI_NACK &nack, uint32_t ssrc);
|
||||
void onSendTwcc(uint32_t ssrc, const std::string &twcc_fci);
|
||||
|
||||
void registerSelf();
|
||||
void unregisterSelf();
|
||||
void unrefSelf();
|
||||
void onCheckAnswer(RtcSession &sdp);
|
||||
|
||||
private:
|
||||
bool _preferred_tcp = false;
|
||||
uint16_t _rtx_seq[2] = {0, 0};
|
||||
// 用掉的总流量 [AUTO-TRANSLATED:713b61c9]
|
||||
// Total traffic used
|
||||
uint64_t _bytes_usage = 0;
|
||||
// 保持自我强引用 [AUTO-TRANSLATED:c2dc228f]
|
||||
// Keep self strong reference
|
||||
Ptr _self;
|
||||
// 检测超时的定时器 [AUTO-TRANSLATED:a58e1388]
|
||||
// Timeout detection timer
|
||||
Timer::Ptr _timer;
|
||||
// 刷新计时器 [AUTO-TRANSLATED:61eb11e5]
|
||||
// Refresh timer
|
||||
Ticker _alive_ticker;
|
||||
// pli rtcp计时器 [AUTO-TRANSLATED:a1a5fd18]
|
||||
// pli rtcp timer
|
||||
Ticker _pli_ticker;
|
||||
// twcc rtcp发送上下文对象 [AUTO-TRANSLATED:aef6476a]
|
||||
// twcc rtcp send context object
|
||||
TwccContext _twcc_ctx;
|
||||
// 根据发送rtp的track类型获取相关信息 [AUTO-TRANSLATED:ff31c272]
|
||||
// Get relevant information based on the track type of the sent rtp
|
||||
MediaTrack::Ptr _type_to_track[2];
|
||||
// 根据rtcp的ssrc获取相关信息,收发rtp和rtx的ssrc都会记录 [AUTO-TRANSLATED:6c57cd48]
|
||||
// Get relevant information based on the ssrc of the rtcp, the ssrc of sending and receiving rtp and rtx will be recorded
|
||||
std::unordered_map<uint32_t/*ssrc*/, MediaTrack::Ptr> _ssrc_to_track;
|
||||
// 根据接收rtp的pt获取相关信息 [AUTO-TRANSLATED:39e56d7d]
|
||||
// Get relevant information based on the pt of the received rtp
|
||||
std::unordered_map<uint8_t/*pt*/, std::unique_ptr<WrappedMediaTrack>> _pt_to_track;
|
||||
std::vector<SdpAttrCandidate> _cands;
|
||||
// http访问时的host ip [AUTO-TRANSLATED:e8fe6957]
|
||||
// Host ip for http access
|
||||
std::string _local_ip;
|
||||
};
|
||||
|
||||
class WebRtcTransportManager {
|
||||
public:
|
||||
friend class WebRtcTransportImp;
|
||||
static WebRtcTransportManager &Instance();
|
||||
WebRtcTransportImp::Ptr getItem(const std::string &key);
|
||||
|
||||
private:
|
||||
WebRtcTransportManager() = default;
|
||||
void addItem(const std::string &key, const WebRtcTransportImp::Ptr &ptr);
|
||||
void removeItem(const std::string &key);
|
||||
|
||||
private:
|
||||
mutable std::mutex _mtx;
|
||||
std::unordered_map<std::string, std::weak_ptr<WebRtcTransportImp> > _map;
|
||||
};
|
||||
|
||||
class WebRtcArgs : public std::enable_shared_from_this<WebRtcArgs> {
|
||||
public:
|
||||
virtual ~WebRtcArgs() = default;
|
||||
virtual variant operator[](const std::string &key) const = 0;
|
||||
};
|
||||
|
||||
using onCreateWebRtc = std::function<void(const WebRtcInterface &rtc)>;
|
||||
class WebRtcPluginManager {
|
||||
public:
|
||||
using Plugin = std::function<void(Session &sender, const WebRtcArgs &args, const onCreateWebRtc &cb)>;
|
||||
using Listener = std::function<void(Session &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(Session &sender, const std::string &type, const WebRtcArgs &args, const onCreateWebRtc &cb);
|
||||
|
||||
private:
|
||||
WebRtcPluginManager() = default;
|
||||
|
||||
private:
|
||||
mutable std::mutex _mtx_creator;
|
||||
Listener _listener;
|
||||
std::unordered_map<std::string, Plugin> _map_creator;
|
||||
};
|
||||
|
||||
}// namespace mediakit
|
||||
/*
|
||||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT-like license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef ZLMEDIAKIT_WEBRTC_TRANSPORT_H
|
||||
#define ZLMEDIAKIT_WEBRTC_TRANSPORT_H
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include "DtlsTransport.hpp"
|
||||
#include "IceTransport.hpp"
|
||||
#include "SrtpSession.hpp"
|
||||
#include "StunPacket.hpp"
|
||||
#include "Sdp.h"
|
||||
#include "Util/mini.h"
|
||||
#include "Poller/EventPoller.h"
|
||||
#include "Network/Socket.h"
|
||||
#include "Network/Session.h"
|
||||
#include "Nack.h"
|
||||
#include "TwccContext.h"
|
||||
#include "SctpAssociation.hpp"
|
||||
#include "Rtcp/RtcpContext.h"
|
||||
#include "Rtsp/RtspMediaSource.h"
|
||||
|
||||
using namespace RTC;
|
||||
namespace mediakit {
|
||||
|
||||
// ICE transport policy enum
|
||||
enum class IceTransportPolicy {
|
||||
kAll = 0, // 不限制,支持所有连接类型(默认)
|
||||
kRelayOnly = 1, // 仅支持Relay转发
|
||||
kP2POnly = 2 // 仅支持P2P直连
|
||||
};
|
||||
|
||||
// RTC配置项目 [AUTO-TRANSLATED:65784416]
|
||||
// RTC configuration project
|
||||
namespace Rtc {
|
||||
extern const std::string kPort;
|
||||
extern const std::string kTcpPort;
|
||||
extern const std::string kTimeOutSec;
|
||||
extern const std::string kSignalingPort;
|
||||
extern const std::string kSignalingSslPort;
|
||||
extern const std::string kIcePort;
|
||||
extern const std::string kIceTcpPort;
|
||||
extern const std::string kEnableTurn;
|
||||
extern const std::string kIceTransportPolicy;
|
||||
extern const std::string kIceUfrag;
|
||||
extern const std::string kIcePwd;
|
||||
extern const std::string kExternIP;
|
||||
extern const std::string kInterfaces;
|
||||
}//namespace RTC
|
||||
|
||||
class WebRtcInterface {
|
||||
public:
|
||||
virtual ~WebRtcInterface() = default;
|
||||
virtual std::string getAnswerSdp(const std::string &offer) = 0;
|
||||
virtual std::string createOfferSdp() = 0;
|
||||
virtual void setAnswerSdp(const std::string &answer) = 0;
|
||||
virtual const std::string& getIdentifier() const = 0;
|
||||
virtual const std::string& deleteRandStr() const { static std::string s_null; return s_null; }
|
||||
virtual void setIceCandidate(std::vector<SdpAttrCandidate> cands) {}
|
||||
virtual void setLocalIp(std::string localIp) {}
|
||||
virtual void setPreferredTcp(bool flag) {}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
class WebRtcException : public WebRtcInterface {
|
||||
public:
|
||||
WebRtcException(const toolkit::SockException &ex) : _ex(ex) {};
|
||||
|
||||
std::string createOfferSdp() override {
|
||||
throw _ex;
|
||||
}
|
||||
|
||||
std::string getAnswerSdp(const std::string &offer) override {
|
||||
throw _ex;
|
||||
}
|
||||
|
||||
void setAnswerSdp(const std::string &answer) override {
|
||||
throw _ex;
|
||||
}
|
||||
|
||||
void gatheringCandidate(IceServerInfo::Ptr ice_server, onGatheringCandidateCB cb = nullptr) override {
|
||||
throw _ex;
|
||||
}
|
||||
|
||||
const std::string &getIdentifier() const override {
|
||||
static std::string s_null;
|
||||
return s_null;
|
||||
}
|
||||
|
||||
private:
|
||||
toolkit::SockException _ex;
|
||||
};
|
||||
|
||||
class WebRtcTransport : public WebRtcInterface, public RTC::DtlsTransport::Listener, public IceTransport::Listener, public std::enable_shared_from_this<WebRtcTransport>
|
||||
#ifdef ENABLE_SCTP
|
||||
, public RTC::SctpAssociation::Listener
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
enum class Role {
|
||||
NONE = 0,
|
||||
CLIENT,
|
||||
PEER,
|
||||
};
|
||||
static const char* RoleStr(Role role);
|
||||
|
||||
enum class SignalingProtocols {
|
||||
Invalid = -1,
|
||||
WHEP_WHIP = 0,
|
||||
WEBSOCKET = 1, //FOR P2P
|
||||
};
|
||||
static const char* SignalingProtocolsStr(SignalingProtocols protocol);
|
||||
|
||||
using WeakPtr = std::weak_ptr<WebRtcTransport>;
|
||||
using Ptr = std::shared_ptr<WebRtcTransport>;
|
||||
WebRtcTransport(const toolkit::EventPoller::Ptr &poller);
|
||||
|
||||
virtual void onCreate();
|
||||
|
||||
virtual void onDestory();
|
||||
|
||||
std::string getAnswerSdp(const std::string &offer) override;
|
||||
void setAnswerSdp(const std::string &answer) override;
|
||||
|
||||
const RtcSession::Ptr& answerSdp() const {
|
||||
return _answer_sdp;
|
||||
}
|
||||
|
||||
std::string createOfferSdp() override;
|
||||
|
||||
const std::string& getIdentifier() const override;
|
||||
const std::string& deleteRandStr() const override;
|
||||
|
||||
void inputSockData(const char *buf, int len, const toolkit::SocketHelper::Ptr& socket, struct sockaddr *addr = nullptr, int addr_len = 0);
|
||||
void inputSockData(const char *buf, int len, const IceTransport::Pair::Ptr& pair = nullptr);
|
||||
void sendRtpPacket(const char *buf, int len, bool flush, void *ctx = nullptr);
|
||||
void sendRtcpPacket(const char *buf, int len, bool flush, void *ctx = nullptr);
|
||||
void sendDatachannel(uint16_t streamId, uint32_t ppid, const char *msg, size_t len);
|
||||
|
||||
const toolkit::EventPoller::Ptr &getPoller() const { return _poller; }
|
||||
void setPoller(toolkit::EventPoller::Ptr poller) { _poller = std::move(poller); }
|
||||
|
||||
toolkit::Session::Ptr getSession() const;
|
||||
void removePair(const toolkit::SocketHelper *socket);
|
||||
|
||||
Role getRole() const { return _role; }
|
||||
void setRole(Role role) { _role = role; }
|
||||
|
||||
SignalingProtocols getSignalingProtocols() const { return _signaling_protocols; }
|
||||
void setSignalingProtocols(SignalingProtocols signaling_protocols) { _signaling_protocols = signaling_protocols; }
|
||||
|
||||
float getTimeOutSec();
|
||||
|
||||
void getTransportInfo(const std::function<void(Json::Value)> &callback) const;
|
||||
|
||||
void setOnShutdown(std::function<void(const toolkit::SockException &ex)> cb);
|
||||
|
||||
void gatheringCandidate(IceServerInfo::Ptr ice_server, onGatheringCandidateCB cb = nullptr) override;
|
||||
void connectivityCheck(SdpAttrCandidate candidate_attr, const std::string &ufrag, const std::string &pwd);
|
||||
void connectivityCheckForSFU();
|
||||
|
||||
void setOnStartWebRTC(std::function<void()> on_start);
|
||||
|
||||
protected:
|
||||
// DtlsTransport::Listener; dtls相关的回调
|
||||
void OnDtlsTransportConnecting(const RTC::DtlsTransport *dtlsTransport) override;
|
||||
void OnDtlsTransportConnected(const RTC::DtlsTransport *dtlsTransport,
|
||||
RTC::SrtpSession::CryptoSuite srtpCryptoSuite,
|
||||
uint8_t *srtpLocalKey,
|
||||
size_t srtpLocalKeyLen,
|
||||
uint8_t *srtpRemoteKey,
|
||||
size_t srtpRemoteKeyLen,
|
||||
std::string &remoteCert) override;
|
||||
void OnDtlsTransportFailed(const RTC::DtlsTransport *dtlsTransport) override;
|
||||
void OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) override;
|
||||
void OnDtlsTransportSendData(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) override;
|
||||
void OnDtlsTransportApplicationDataReceived(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) override;
|
||||
|
||||
protected:
|
||||
// ice相关的回调; IceTransport::Listener.
|
||||
void onIceTransportRecvData(const toolkit::Buffer::Ptr& buffer, const IceTransport::Pair::Ptr& pair) override;
|
||||
void onIceTransportGatheringCandidate(const IceTransport::Pair::Ptr& pair, const CandidateInfo& candidate) override;
|
||||
void onIceTransportCompleted() override;
|
||||
void onIceTransportDisconnected() override;
|
||||
|
||||
// SctpAssociation::Listener
|
||||
#ifdef ENABLE_SCTP
|
||||
void OnSctpAssociationConnecting(RTC::SctpAssociation* sctpAssociation) override;
|
||||
void OnSctpAssociationConnected(RTC::SctpAssociation* sctpAssociation) override;
|
||||
void OnSctpAssociationFailed(RTC::SctpAssociation* sctpAssociation) override;
|
||||
void OnSctpAssociationClosed(RTC::SctpAssociation* sctpAssociation) override;
|
||||
void OnSctpAssociationSendData(RTC::SctpAssociation* sctpAssociation, const uint8_t* data, size_t len) override;
|
||||
void OnSctpAssociationMessageReceived(RTC::SctpAssociation *sctpAssociation, uint16_t streamId, uint32_t ppid,
|
||||
const uint8_t *msg, size_t len) override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
virtual void onStartWebRTC() = 0;
|
||||
virtual void onRtcConfigure(RtcConfigure &configure) const;
|
||||
virtual void onCheckSdp(SdpType type, RtcSession &sdp) = 0;
|
||||
virtual void onSendSockData(toolkit::Buffer::Ptr buf, bool flush = true, const IceTransport::Pair::Ptr& pair = nullptr) = 0;
|
||||
|
||||
virtual void onRtp(const char *buf, size_t len, uint64_t stamp_ms) = 0;
|
||||
virtual void onRtcp(const char *buf, size_t len) = 0;
|
||||
virtual void onShutdown(const toolkit::SockException &ex);
|
||||
virtual void onBeforeEncryptRtp(const char *buf, int &len, void *ctx) = 0;
|
||||
virtual void onBeforeEncryptRtcp(const char *buf, int &len, void *ctx) = 0;
|
||||
virtual void onRtcpBye() = 0;
|
||||
|
||||
protected:
|
||||
void sendRtcpRemb(uint32_t ssrc, size_t bit_rate);
|
||||
void sendRtcpPli(uint32_t ssrc);
|
||||
|
||||
private:
|
||||
void sendSockData(const char *buf, size_t len, const IceTransport::Pair::Ptr& pair = nullptr);
|
||||
void setRemoteDtlsFingerprint(SdpType type, const RtcSession &remote);
|
||||
|
||||
protected:
|
||||
SignalingProtocols _signaling_protocols = SignalingProtocols::WHEP_WHIP;
|
||||
Role _role = Role::PEER;
|
||||
RtcSession::Ptr _offer_sdp;
|
||||
RtcSession::Ptr _answer_sdp;
|
||||
|
||||
IceAgent::Ptr _ice_agent;
|
||||
onGatheringCandidateCB _on_gathering_candidate = nullptr;
|
||||
|
||||
private:
|
||||
mutable std::string _delete_rand_str;
|
||||
std::string _identifier;
|
||||
toolkit::EventPoller::Ptr _poller;
|
||||
DtlsTransport::Ptr _dtls_transport;
|
||||
SrtpSession::Ptr _srtp_session_send;
|
||||
SrtpSession::Ptr _srtp_session_recv;
|
||||
toolkit::Ticker _ticker;
|
||||
// 循环池 [AUTO-TRANSLATED:b7059f37]
|
||||
// Cycle pool
|
||||
toolkit::ResourcePool<toolkit::BufferRaw> _packet_pool;
|
||||
|
||||
//超时功能实现
|
||||
toolkit::Ticker _recv_ticker;
|
||||
std::shared_ptr<toolkit::Timer> _check_timer;
|
||||
std::function<void()> _on_start;
|
||||
std::function<void(const toolkit::SockException &ex)> _on_shutdown;
|
||||
|
||||
#ifdef ENABLE_SCTP
|
||||
RTC::SctpAssociationImp::Ptr _sctp;
|
||||
#endif
|
||||
};
|
||||
|
||||
class RtpChannel;
|
||||
class MediaTrack {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<MediaTrack>;
|
||||
const RtcCodecPlan *plan_rtp;
|
||||
const RtcCodecPlan *plan_rtx;
|
||||
uint32_t offer_ssrc_rtp = 0;
|
||||
uint32_t offer_ssrc_rtx = 0;
|
||||
uint32_t answer_ssrc_rtp = 0;
|
||||
uint32_t answer_ssrc_rtx = 0;
|
||||
const RtcMedia *media;
|
||||
RtpExtContext::Ptr rtp_ext_ctx;
|
||||
|
||||
//for send rtp
|
||||
NackList nack_list;
|
||||
RtcpContext::Ptr rtcp_context_send;
|
||||
|
||||
//for recv rtp
|
||||
std::unordered_map<std::string/*rid*/, std::shared_ptr<RtpChannel> > rtp_channel;
|
||||
std::shared_ptr<RtpChannel> getRtpChannel(uint32_t ssrc) const;
|
||||
};
|
||||
|
||||
struct WrappedMediaTrack {
|
||||
MediaTrack::Ptr track;
|
||||
explicit WrappedMediaTrack(MediaTrack::Ptr ptr): track(ptr) {}
|
||||
virtual ~WrappedMediaTrack() {}
|
||||
virtual void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) = 0;
|
||||
};
|
||||
|
||||
struct WrappedRtxTrack: public WrappedMediaTrack {
|
||||
explicit WrappedRtxTrack(MediaTrack::Ptr ptr)
|
||||
: WrappedMediaTrack(std::move(ptr)) {}
|
||||
void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) override;
|
||||
};
|
||||
|
||||
class WebRtcTransportImp;
|
||||
|
||||
struct WrappedRtpTrack : public WrappedMediaTrack {
|
||||
explicit WrappedRtpTrack(MediaTrack::Ptr ptr, TwccContext& twcc, WebRtcTransportImp& t)
|
||||
: WrappedMediaTrack(std::move(ptr))
|
||||
, _twcc_ctx(twcc)
|
||||
, _transport(t) {}
|
||||
TwccContext& _twcc_ctx;
|
||||
WebRtcTransportImp& _transport;
|
||||
void inputRtp(const char *buf, size_t len, uint64_t stamp_ms, RtpHeader *rtp) override;
|
||||
};
|
||||
|
||||
class WebRtcTransportImp : public WebRtcTransport {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<WebRtcTransportImp>;
|
||||
~WebRtcTransportImp() override;
|
||||
|
||||
uint64_t getBytesUsage() const;
|
||||
uint64_t getDuration() const;
|
||||
bool canSendRtp() const;
|
||||
bool canRecvRtp() const;
|
||||
bool canSendRtp(const RtcMedia& media) const;
|
||||
bool canRecvRtp(const RtcMedia& media) const;
|
||||
void onSendRtp(const RtpPacket::Ptr &rtp, bool flush, bool rtx = false);
|
||||
|
||||
void createRtpChannel(const std::string &rid, uint32_t ssrc, MediaTrack &track);
|
||||
void safeShutdown(const toolkit::SockException &ex);
|
||||
|
||||
void setPreferredTcp(bool flag) override;
|
||||
void setLocalIp(std::string local_ip) override;
|
||||
void setIceCandidate(std::vector<SdpAttrCandidate> cands) override;
|
||||
|
||||
protected:
|
||||
|
||||
// // ice相关的回调 /// [AUTO-TRANSLATED:30abf693]
|
||||
// // ice related callbacks ///
|
||||
|
||||
WebRtcTransportImp(const toolkit::EventPoller::Ptr &poller);
|
||||
void OnDtlsTransportApplicationDataReceived(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) override;
|
||||
void onStartWebRTC() override;
|
||||
void onSendSockData(toolkit::Buffer::Ptr buf, bool flush = true, const IceTransport::Pair::Ptr& pair = nullptr) override;
|
||||
void onCheckSdp(SdpType type, RtcSession &sdp) override;
|
||||
void onRtcConfigure(RtcConfigure &configure) const override;
|
||||
|
||||
void onRtp(const char *buf, size_t len, uint64_t stamp_ms) override;
|
||||
void onRtcp(const char *buf, size_t len) override;
|
||||
void onBeforeEncryptRtp(const char *buf, int &len, void *ctx) override;
|
||||
void onBeforeEncryptRtcp(const char *buf, int &len, void *ctx) override {};
|
||||
void onCreate() override;
|
||||
void onDestory() override;
|
||||
void onShutdown(const toolkit::SockException &ex) override;
|
||||
virtual void onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) {}
|
||||
void updateTicker();
|
||||
float getLossRate(TrackType type);
|
||||
void onRtcpBye() override;
|
||||
|
||||
private:
|
||||
void onSortedRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp);
|
||||
void onSendNack(MediaTrack &track, const FCI_NACK &nack, uint32_t ssrc);
|
||||
void onSendTwcc(uint32_t ssrc, const std::string &twcc_fci);
|
||||
|
||||
void registerSelf();
|
||||
void unregisterSelf();
|
||||
void unrefSelf();
|
||||
void onCheckAnswer(RtcSession &sdp);
|
||||
|
||||
private:
|
||||
bool _preferred_tcp = false;
|
||||
uint16_t _rtx_seq[2] = {0, 0};
|
||||
// 用掉的总流量 [AUTO-TRANSLATED:713b61c9]
|
||||
// Total traffic used
|
||||
uint64_t _bytes_usage = 0;
|
||||
// 保持自我强引用 [AUTO-TRANSLATED:c2dc228f]
|
||||
// Keep self strong reference
|
||||
Ptr _self;
|
||||
// 检测超时的定时器 [AUTO-TRANSLATED:a58e1388]
|
||||
// Timeout detection timer
|
||||
toolkit::Timer::Ptr _timer;
|
||||
// 刷新计时器 [AUTO-TRANSLATED:61eb11e5]
|
||||
// Refresh timer
|
||||
toolkit::Ticker _alive_ticker;
|
||||
// pli rtcp计时器 [AUTO-TRANSLATED:a1a5fd18]
|
||||
// pli rtcp timer
|
||||
toolkit::Ticker _pli_ticker;
|
||||
|
||||
toolkit::Ticker _rtcp_sr_send_ticker;
|
||||
toolkit::Ticker _rtcp_rr_send_ticker;
|
||||
|
||||
// twcc rtcp发送上下文对象 [AUTO-TRANSLATED:aef6476a]
|
||||
// twcc rtcp send context object
|
||||
TwccContext _twcc_ctx;
|
||||
// 根据发送rtp的track类型获取相关信息 [AUTO-TRANSLATED:ff31c272]
|
||||
// Get relevant information based on the track type of the sent rtp
|
||||
MediaTrack::Ptr _type_to_track[2];
|
||||
// 根据rtcp的ssrc获取相关信息,收发rtp和rtx的ssrc都会记录 [AUTO-TRANSLATED:6c57cd48]
|
||||
// Get relevant information based on the ssrc of the rtcp, the ssrc of sending and receiving rtp and rtx will be recorded
|
||||
std::unordered_map<uint32_t/*ssrc*/, MediaTrack::Ptr> _ssrc_to_track;
|
||||
// 根据接收rtp的pt获取相关信息 [AUTO-TRANSLATED:39e56d7d]
|
||||
// Get relevant information based on the pt of the received rtp
|
||||
std::unordered_map<uint8_t/*pt*/, std::unique_ptr<WrappedMediaTrack>> _pt_to_track;
|
||||
std::vector<SdpAttrCandidate> _cands;
|
||||
// http访问时的host ip [AUTO-TRANSLATED:e8fe6957]
|
||||
// Host ip for http access
|
||||
std::string _local_ip;
|
||||
};
|
||||
|
||||
class WebRtcTransportManager {
|
||||
public:
|
||||
friend class WebRtcTransportImp;
|
||||
static WebRtcTransportManager &Instance();
|
||||
WebRtcTransportImp::Ptr getItem(const std::string &key);
|
||||
|
||||
private:
|
||||
WebRtcTransportManager() = default;
|
||||
void addItem(const std::string &key, const WebRtcTransportImp::Ptr &ptr);
|
||||
void removeItem(const std::string &key);
|
||||
|
||||
private:
|
||||
mutable std::mutex _mtx;
|
||||
std::unordered_map<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
|
||||
|
||||
@ -1,13 +1,6 @@
|
||||
# 致谢与声明
|
||||
本文件夹下部分文件提取自[MediaSoup](https://github.com/versatica/mediasoup) ,分别为:
|
||||
|
||||
- ice相关功能:
|
||||
- IceServer.cpp
|
||||
- IceServer.hpp
|
||||
- StunPacket.cpp
|
||||
- StunPacket.hpp
|
||||
- Utils.hpp
|
||||
|
||||
- dtls相关功能:
|
||||
- DtlsTransport.cpp
|
||||
- DtlsTransport.hpp
|
||||
|
||||
132
webrtc/webrtcSignal.txt
Normal file
132
webrtc/webrtcSignal.txt
Normal 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",
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user