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