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

- 增加客户端模式,支持主动拉流、推流:
   - addStreamProxy接口新增支持whep主动拉流,拉流地址目前只兼容zlm的whep url。
   - addStreamPusherProxy接口新增支持whip主动推流,推流地址目前只兼容zlm的whip url。
   - 以上推流url格式为webrtc[s]://server_host:server_port/app/stream_id?key=value, 内部会自动转换为http[s]://server_host:server_port/index/api/[whip/whep]?app=app&stream=stream_id&key=value。

- 增加WebRtc p2p 模式:
  - 增加 ICE FULL模式。
  - 增加STUN/TURN 服务器。
  - 增加websocket 信令。
  - 增加P2P代理拉流。

---------

Co-authored-by: xia-chu <771730766@qq.com>
Co-authored-by: mtdxc <mtdxc@126.com>
Co-authored-by: cqm <cqm@97kid.com>
This commit is contained in:
baigao-X 2025-09-20 16:23:30 +08:00 committed by GitHub
parent 97d2a1fb08
commit 3fb43c5fef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
72 changed files with 16912 additions and 10319 deletions

View File

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

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

View File

@ -472,6 +472,17 @@ if(ENABLE_SRT)
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_SRT)
endif()
if(ENABLE_WEBRTC)
# srtp
find_package(SRTP QUIET)
if(SRTP_FOUND AND ENABLE_OPENSSL)
message(STATUS "found library: ${SRTP_LIBRARIES}, ENABLE_WEBRTC defined")
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_WEBRTC)
else()
set(ENABLE_WEBRTC OFF)
message(WARNING "srtp 未找到, WebRTC 相关功能打开失败")
endif()
endif()
# ----------------------------------------------------------------------------
# Solution folders:
# ----------------------------------------------------------------------------

View File

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

View File

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

View File

@ -346,11 +346,30 @@ udp_recv_socket_buffer=4194304
merge_frame=1
[rtc]
#webrtc 信令服务器端口
signalingPort=3000
signalingSslPort=3001
#STUN/TURN服务器端口
icePort=3478
iceTcpPort=3478
#STUN/TURN端口是否使能TURN服务
enableTurn=1
#ICE传输策略0=不限制(默认)1=仅支持Relay转发2=仅支持P2P直连
iceTransportPolicy=0
#STUN/TURN 服务Ice密码
iceUfrag=ZLMediaKit
icePwd=ZLMediaKit
#TURN服务分配端口池
portRange=50000-65000
#rtc播放推流、播放超时时间
timeoutSec=15
#本机对rtc客户端的可见ip作为服务器时一般为公网ip可有多个用','分开当置空时会自动获取网卡ip
#同时支持环境变量,以$开头,如"$EXTERN_IP"; 请参考https://github.com/ZLMediaKit/ZLMediaKit/pull/1786
externIP=
#当指定了interfaces,ICE服务器会使用指定网卡bind socket
#以解决公网IP使用弹性公网IP配置实现(部署机器无法bind该公网ip的问题)
#支持环境变量,以$开头,如"$PRIVATE_IP"
interfaces=
#rtc udp服务器监听端口号所有rtc客户端将通过该端口传输stun/dtls/srtp/srtcp数据
#该端口是多线程的,同时支持客户端网络切换导致的连接迁移
#需要注意的是如果服务器在nat内需要做端口映射时必须确保外网映射端口跟该端口一致

View File

@ -2732,6 +2732,154 @@
}
},
"response": []
},
{
"name": "WebRTC-注册到信令服务器(addWebrtcRoomKeeper)",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{ZLMediaKit_URL}}/index/api/addWebrtcRoomKeeper?secret={{ZLMediaKit_secret}}&server_host=127.0.0.1&server_port=3000&room_id=peer_1",
"host": [
"{{ZLMediaKit_URL}}"
],
"path": [
"index",
"api",
"addWebrtcRoomKeeper"
],
"query": [
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}"
},
{
"key": "server_host",
"value": "127.0.0.1",
"description": "要注册到的信令服务器地址"
},
{
"key": "server_port",
"value": "3000",
"description": "要注册到的信令服务器端口"
},
{
"key": "room_id",
"value": "peer_1",
"description": "要注册到的roomid"
}
]
}
},
"response": []
},
{
"name": "WebRTC-从信令服务器注销(delWebrtcRoomKeeper)",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{ZLMediaKit_URL}}/index/api/delWebrtcRoomKeeper?secret={{ZLMediaKit_secret}}&room_key=",
"host": [
"{{ZLMediaKit_URL}}"
],
"path": [
"index",
"api",
"delWebrtcRoomKeeper"
],
"query": [
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}"
},
{
"key": "room_key",
"value": ""
}
]
}
},
"response": []
},
{
"name": "WebRTC-Peer查看注册信息(listWebrtcRoomKeepers)",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{ZLMediaKit_URL}}/index/api/listWebrtcRoomKeepers?secret={{ZLMediaKit_secret}}",
"host": [
"{{ZLMediaKit_URL}}"
],
"path": [
"index",
"api",
"listWebrtcRoomKeepers"
],
"query": [
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}"
}
]
}
},
"response": []
},
{
"name": "WebRTC-信令服务器查看注册信息(listWebrtcRooms)",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{ZLMediaKit_URL}}/index/api/listWebrtcRooms?secret={{ZLMediaKit_secret}}",
"host": [
"{{ZLMediaKit_URL}}"
],
"path": [
"index",
"api",
"listWebrtcRooms"
],
"query": [
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}"
}
]
}
},
"response": []
},
{
"name": "WebRTC-查看WebRTCProxyPlayer连接信息(getWebrtcProxyPlayerInfo)",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{ZLMediaKit_URL}}/index/api/getWebrtcProxyPlayerInfo?secret={{ZLMediaKit_secret}}&key=__defaultVhost__/live/test",
"host": [
"{{ZLMediaKit_URL}}"
],
"path": [
"index",
"api",
"getWebrtcProxyPlayerInfo"
],
"query": [
{
"key": "secret",
"value": "{{ZLMediaKit_secret}}"
},
{
"key": "key",
"value": "__defaultVhost__/live/test"
}
]
}
},
"response": []
}
],
"event": [

View File

@ -57,6 +57,10 @@
#include "../webrtc/WebRtcPlayer.h"
#include "../webrtc/WebRtcPusher.h"
#include "../webrtc/WebRtcEchoTest.h"
#include "../webrtc/WebRtcSignalingPeer.h"
#include "../webrtc/WebRtcSignalingSession.h"
#include "../webrtc/WebRtcProxyPlayer.h"
#include "../webrtc/WebRtcProxyPlayerImp.h"
#endif
#if defined(ENABLE_VERSION)
@ -314,84 +318,6 @@ static inline void addHttpListener(){
});
}
template <typename Type>
class ServiceController {
public:
using Pointer = std::shared_ptr<Type>;
void clear() {
decltype(_map) copy;
{
std::lock_guard<std::recursive_mutex> lck(_mtx);
copy.swap(_map);
}
}
size_t erase(const std::string &key) {
Pointer erase_ptr;
{
std::lock_guard<std::recursive_mutex> lck(_mtx);
auto itr = _map.find(key);
if (itr != _map.end()) {
erase_ptr = itr->second;
_map.erase(itr);
return 1;
}
}
return 0;
}
size_t size() {
std::lock_guard<std::recursive_mutex> lck(_mtx);
return _map.size();
}
Pointer find(const std::string &key) const {
std::lock_guard<std::recursive_mutex> lck(_mtx);
auto it = _map.find(key);
if (it == _map.end()) {
return nullptr;
}
return it->second;
}
void for_each(const std::function<void(const std::string&, const Pointer&)>& cb) {
std::lock_guard<std::recursive_mutex> lck(_mtx);
auto it = _map.begin();
while (it != _map.end()) {
cb(it->first, it->second);
it++;
}
}
template<class ..._Args>
Pointer make(const std::string &key, _Args&& ...__args) {
// assert(!find(key));
auto server = std::make_shared<Type>(std::forward<_Args>(__args)...);
std::lock_guard<std::recursive_mutex> lck(_mtx);
auto it = _map.emplace(key, server);
assert(it.second);
return server;
}
template<class ..._Args>
Pointer makeWithAction(const std::string &key, function<void(Pointer)> action, _Args&& ...__args) {
// assert(!find(key));
auto server = std::make_shared<Type>(std::forward<_Args>(__args)...);
action(server);
std::lock_guard<std::recursive_mutex> lck(_mtx);
auto it = _map.emplace(key, server);
assert(it.second);
return server;
}
private:
std::unordered_map<std::string, Pointer> _map;
mutable std::recursive_mutex _mtx;
};
// 拉流代理器列表 [AUTO-TRANSLATED:6dcfb11f]
// Pull stream proxy list
static ServiceController<PlayerProxy> s_player_proxy;
@ -2127,35 +2053,6 @@ void installWebApi() {
});
#ifdef ENABLE_WEBRTC
class WebRtcArgsImp : public WebRtcArgs {
public:
WebRtcArgsImp(const ArgsString &args, std::string session_id)
: _args(args)
, _session_id(std::move(session_id)) {}
~WebRtcArgsImp() override = default;
toolkit::variant operator[](const string &key) const override {
if (key == "url") {
return getUrl();
}
return _args[key];
}
private:
string getUrl() const{
auto &allArgs = _args;
CHECK_ARGS("app", "stream");
string auth = _args["Authorization"]; // Authorization Bearer
return StrPrinter << "rtc://" << _args["Host"] << "/" << _args["app"] << "/" << _args["stream"] << "?"
<< _args.parser.params() + "&session=" + _session_id + (auth.empty() ? "" : ("&Authorization=" + auth));
}
private:
ArgsString _args;
std::string _session_id;
};
api_regist("/index/api/webrtc",[](API_ARGS_STRING_ASYNC){
CHECK_ARGS("type");
auto type = allArgs["type"];
@ -2163,7 +2060,7 @@ void installWebApi() {
CHECK(!offer.empty(), "http body(webrtc offer sdp) is empty");
auto &session = static_cast<Session&>(sender);
auto args = std::make_shared<WebRtcArgsImp>(allArgs, sender.getIdentifier());
auto args = std::make_shared<WebRtcArgsImp<std::string>>(allArgs, sender.getIdentifier());
WebRtcPluginManager::Instance().negotiateSdp(session, type, *args, [invoker, val, offer, headerOut](const WebRtcInterface &exchanger) mutable {
auto &handler = const_cast<WebRtcInterface &>(exchanger);
try {
@ -2186,7 +2083,7 @@ void installWebApi() {
auto &session = static_cast<Session&>(sender);
auto location = std::string(session.overSsl() ? "https://" : "http://") + allArgs["host"] + delete_webrtc_url;
auto args = std::make_shared<WebRtcArgsImp>(allArgs, sender.getIdentifier());
auto args = std::make_shared<WebRtcArgsImp<std::string>>(allArgs, sender.getIdentifier());
WebRtcPluginManager::Instance().negotiateSdp(session, type, *args, [invoker, offer, headerOut, location](const WebRtcInterface &exchanger) mutable {
auto &handler = const_cast<WebRtcInterface &>(exchanger);
try {
@ -2220,6 +2117,103 @@ void installWebApi() {
obj->safeShutdown(SockException(Err_shutdown, "deleted by http api"));
invoker(200, headerOut, "");
});
// 获取WebRTCProxyPlayer 连接信息
api_regist("/index/api/getWebrtcProxyPlayerInfo", [](API_ARGS_MAP_ASYNC) {
CHECK_SECRET();
CHECK_ARGS("key");
auto player_proxy = s_player_proxy.find(allArgs["key"]);
if (!player_proxy) {
throw ApiRetException("Stream proxy not found", API::NotFound);
}
auto media_player = player_proxy->getDelegate();
if (!media_player) {
throw ApiRetException("Media player not found", API::OtherFailed);
}
auto webrtc_player_imp = std::dynamic_pointer_cast<WebRtcProxyPlayerImp>(media_player);
if (!webrtc_player_imp) {
throw ApiRetException("Stream proxy is not WebRTC type", API::OtherFailed);
}
auto webrtc_transport = webrtc_player_imp->getWebRtcTransport();
if (!webrtc_transport) {
throw ApiRetException("WebRTC transport not available", API::OtherFailed);
}
std::string stream_key = allArgs["key"];
webrtc_transport->getTransportInfo([val, headerOut, invoker, stream_key](Json::Value transport_info) mutable {
transport_info["stream_key"] = stream_key;
if (transport_info.isMember("error")) {
Json::Value error_val;
error_val["code"] = API::OtherFailed;
error_val["msg"] = transport_info["error"].asString();
invoker(200, headerOut, error_val.toStyledString());
return;
}
// 成功返回结果
Json::Value success_val;
success_val["code"] = API::Success;
success_val["msg"] = "success";
success_val["data"] = transport_info;
invoker(200, headerOut, success_val.toStyledString());
});
});
api_regist("/index/api/addWebrtcRoomKeeper",[](API_ARGS_MAP_ASYNC){
CHECK_SECRET();
CHECK_ARGS("server_host", "server_port", "room_id", "ssl");
//server_host: 信令服务器host
//server_post: 信令服务器host
//room_id: 注册的id,信令服务器会对该id进行唯一性检查
addWebrtcRoomKeeper(allArgs["server_host"], allArgs["server_port"], allArgs["room_id"], allArgs["ssl"],
[val, headerOut, invoker](const SockException &ex, const string &key) mutable {
if (ex) {
val["code"] = API::OtherFailed;
val["msg"] = ex.what();
} else {
val["msg"] = "success";
val["data"]["room_key"] = key;
}
invoker(200, headerOut, val.toStyledString());
});
});
api_regist("/index/api/delWebrtcRoomKeeper",[](API_ARGS_MAP_ASYNC){
CHECK_SECRET();
CHECK_ARGS("room_key");
delWebrtcRoomKeeper(allArgs["room_key"],
[val, headerOut, invoker](const SockException &ex) mutable {
if (ex) {
val["code"] = API::OtherFailed;
val["msg"] = ex.what();
}
invoker(200, headerOut, val.toStyledString());
});
});
api_regist("/index/api/listWebrtcRoomKeepers", [](API_ARGS_MAP) {
CHECK_SECRET();
listWebrtcRoomKeepers([&val](const std::string& key, const WebRtcSignalingPeer::Ptr& p) {
Json::Value item = ToJson(p);
item["room_key"] = key;
val["data"].append(item);
});
});
api_regist("/index/api/listWebrtcRooms", [](API_ARGS_MAP) {
CHECK_SECRET();
listWebrtcRooms([&val](const std::string& key, const WebRtcSignalingSession::Ptr& p) {
Json::Value item = ToJson(p);
item["room_id"] = key;
val["data"].append(item);
});
});
#endif
#if defined(ENABLE_VERSION)

View File

@ -19,6 +19,10 @@
#include "Http/HttpSession.h"
#include "Common/MultiMediaSourceMuxer.h"
#if defined(ENABLE_WEBRTC)
#include "webrtc/WebRtcTransport.h"
#endif
// 配置文件路径 [AUTO-TRANSLATED:8a373c2f]
// Configuration file path
extern std::string g_ini_file;
@ -110,6 +114,11 @@ std::string getValue(const mediakit::Parser &parser, const Key &key) {
return getValue(parser.getHeader(), key);
}
template<typename Key>
std::string getValue(mediakit::Parser &parser, const Key &key) {
return getValue((const mediakit::Parser &) parser, key);
}
template<typename Args, typename Key>
std::string getValue(const mediakit::Parser &parser, Args &args, const Key &key) {
auto ret = getValue(args, key);
@ -145,6 +154,14 @@ public:
toolkit::variant operator[](const Key &key) const {
return (toolkit::variant)getValue(parser, args, key);
}
const Args& getArgs() const {
return args;
}
const mediakit::Parser &getParser() const {
return parser;
}
};
using ArgsMap = HttpAllArgs<ApiArgsType>;
@ -225,4 +242,123 @@ void getStatisticJson(const std::function<void(Json::Value &val)> &cb);
void addStreamProxy(const mediakit::MediaTuple &tuple, const std::string &url, int retry_count,
const mediakit::ProtocolOption &option, int rtp_type, float timeout_sec, const toolkit::mINI &args,
const std::function<void(const toolkit::SockException &ex, const std::string &key)> &cb);
template <typename Type>
class ServiceController {
public:
using Pointer = std::shared_ptr<Type>;
std::unordered_map<std::string, Pointer> _map;
mutable std::recursive_mutex _mtx;
void clear() {
decltype(_map) copy;
{
std::lock_guard<std::recursive_mutex> lck(_mtx);
copy.swap(_map);
}
}
size_t erase(const std::string &key) {
Pointer erase_ptr;
{
std::lock_guard<std::recursive_mutex> lck(_mtx);
auto itr = _map.find(key);
if (itr != _map.end()) {
erase_ptr = std::move(itr->second);
_map.erase(itr);
return 1;
}
}
return 0;
}
size_t size() {
std::lock_guard<std::recursive_mutex> lck(_mtx);
return _map.size();
}
Pointer find(const std::string &key) const {
std::lock_guard<std::recursive_mutex> lck(_mtx);
auto it = _map.find(key);
if (it == _map.end()) {
return nullptr;
}
return it->second;
}
void for_each(const std::function<void(const std::string&, const Pointer&)>& cb) {
std::lock_guard<std::recursive_mutex> lck(_mtx);
auto it = _map.begin();
while (it != _map.end()) {
cb(it->first, it->second);
it++;
}
}
template<class ..._Args>
Pointer make(const std::string &key, _Args&& ...__args) {
// assert(!find(key));
auto server = std::make_shared<Type>(std::forward<_Args>(__args)...);
std::lock_guard<std::recursive_mutex> lck(_mtx);
auto it = _map.emplace(key, server);
assert(it.second);
return server;
}
template<class ..._Args>
Pointer makeWithAction(const std::string &key, std::function<void(Pointer)> action, _Args&& ...__args) {
// assert(!find(key));
auto server = std::make_shared<Type>(std::forward<_Args>(__args)...);
action(server);
std::lock_guard<std::recursive_mutex> lck(_mtx);
auto it = _map.emplace(key, server);
assert(it.second);
return server;
}
template<class ..._Args>
Pointer emplace(const std::string &key, _Args&& ...__args) {
// assert(!find(key));
auto server = std::static_pointer_cast<Type>(std::forward<_Args>(__args)...);
std::lock_guard<std::recursive_mutex> lck(_mtx);
auto it = _map.emplace(key, server);
assert(it.second);
return server;
}
};
#if defined(ENABLE_WEBRTC)
template <typename Args>
class WebRtcArgsImp : public mediakit::WebRtcArgs {
public:
WebRtcArgsImp(const HttpAllArgs<Args> &args, std::string session_id)
: _args(args)
, _session_id(std::move(session_id)) {}
~WebRtcArgsImp() override = default;
toolkit::variant operator[](const std::string &key) const override {
if (key == "url") {
return getUrl();
}
return _args[key];
}
private:
std::string getUrl() const {
auto &allArgs = _args;
CHECK_ARGS("app", "stream");
return StrPrinter << RTC_SCHEMA << "://" << (_args["Host"].empty() ? DEFAULT_VHOST : _args["Host"].data()) << "/" << _args["app"] << "/"
<< _args["stream"] << "?" << _args.getParser().params() + "&session=" + _session_id;
}
private:
HttpAllArgs<Args> _args;
std::string _session_id;
};
#endif
#endif //ZLMEDIAKIT_WEBAPI_H

View File

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

View File

@ -30,7 +30,7 @@ namespace mediakit {
* [AUTO-TRANSLATED:f214f734]
*/
#if !defined(ENABLE_VERSION)
const char kServerName[] = "ZLMediaKit-8.0(build in " __DATE__ " " __TIME__ ")";
const char kServerName[] = "ZLMediaKit-9.0(build in " __DATE__ " " __TIME__ ")";
#else
const char kServerName[] = "ZLMediaKit(git hash:" COMMIT_HASH "/" COMMIT_TIME ",branch:" BRANCH_NAME ",build time:" BUILD_TIME ")";
#endif

View File

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

View File

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

View File

@ -18,7 +18,9 @@
#ifdef ENABLE_SRT
#include "Srt/SrtPlayerImp.h"
#endif // ENABLE_SRT
#ifdef ENABLE_WEBRTC
#include "../webrtc/WebRtcProxyPlayerImp.h"
#endif // ENABLE_WEBRTC
using namespace std;
using namespace toolkit;
@ -84,6 +86,11 @@ PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &in_poller, cons
return PlayerBase::Ptr(new SrtPlayerImp(poller), release_func);
}
#endif//ENABLE_SRT
#ifdef ENABLE_WEBRTC
if ((strcasecmp("webrtc", prefix.data()) == 0 || strcasecmp("webrtcs", prefix.data()) == 0)) {
return PlayerBase::Ptr(new WebRtcProxyPlayerImp(poller), release_func);
}
#endif//ENABLE_WEBRTC
throw std::invalid_argument("not supported play schema:" + url_in);
}

View File

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

View File

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

View File

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

View File

@ -27,6 +27,11 @@ struct MediaTuple {
std::string shortUrl() const {
return vhost + '/' + app + '/' + stream;
}
MediaTuple() = default;
MediaTuple(std::string vhost, std::string app, std::string stream, std::string params = "")
: vhost(std::move(vhost)), app(std::move(app)), stream(std::move(stream)), params(std::move(params)) {
}
};
class RecordInfo: public MediaTuple {

View File

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

View File

@ -33,6 +33,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#include "Util/SSLUtil.h"
using namespace std;
using namespace toolkit;
#define LOG_OPENSSL_ERROR(desc) \
do \
@ -650,6 +651,8 @@ namespace RTC
void DtlsTransport::Run(Role localRole)
{
DebugL << ((localRole == RTC::DtlsTransport::Role::SERVER)? "Server" : "Client");
MS_TRACE();
MS_ASSERT(

View File

@ -28,13 +28,13 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#include <vector>
#include "Poller/Timer.h"
#include "Poller/EventPoller.h"
using namespace toolkit;
namespace RTC
{
class DtlsTransport : public std::enable_shared_from_this<DtlsTransport>
{
public:
using Ptr = std::shared_ptr<DtlsTransport>;
enum class DtlsState
{
NEW = 1,
@ -175,7 +175,7 @@ namespace RTC
static std::vector<SrtpCryptoSuiteMapEntry> srtpCryptoSuites;
public:
DtlsTransport(EventPoller::Ptr poller, Listener* listener);
DtlsTransport(toolkit::EventPoller::Ptr poller, Listener* listener);
~DtlsTransport();
public:
@ -230,14 +230,14 @@ namespace RTC
private:
DtlsEnvironment::Ptr env;
EventPoller::Ptr poller;
toolkit::EventPoller::Ptr poller;
// Passed by argument.
Listener* listener{ nullptr };
// Allocated by this.
SSL* ssl{ nullptr };
BIO* sslBioFromNetwork{ nullptr }; // The BIO from which ssl reads.
BIO* sslBioToNetwork{ nullptr }; // The BIO in which ssl writes.
Timer::Ptr timer;
toolkit::Timer::Ptr timer;
// Others.
DtlsState state{ DtlsState::NEW };
Role localRole{ Role::NONE };

View File

@ -1,528 +0,0 @@
/**
ISC License
Copyright © 2015, Iñaki Baz Castillo <ibc@aliax.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#define MS_CLASS "RTC::IceServer"
// #define MS_LOG_DEV_LEVEL 3
#include <utility>
#include "IceServer.hpp"
namespace RTC
{
/* Static. */
/* Instance methods. */
IceServer::IceServer(Listener* listener, const std::string& usernameFragment, const std::string& password)
: listener(listener), usernameFragment(usernameFragment), password(password)
{
MS_TRACE();
}
void IceServer::ProcessStunPacket(RTC::StunPacket* packet, RTC::TransportTuple* tuple)
{
MS_TRACE();
// Must be a Binding method.
if (packet->GetMethod() != RTC::StunPacket::Method::BINDING)
{
if (packet->GetClass() == RTC::StunPacket::Class::REQUEST)
{
MS_WARN_TAG(
ice,
"unknown method %#.3x in STUN Request => 400",
static_cast<unsigned int>(packet->GetMethod()));
// Reply 400.
RTC::StunPacket* response = packet->CreateErrorResponse(400);
response->Serialize(StunSerializeBuffer);
this->listener->OnIceServerSendStunPacket(this, response, tuple);
delete response;
}
else
{
MS_WARN_TAG(
ice,
"ignoring STUN Indication or Response with unknown method %#.3x",
static_cast<unsigned int>(packet->GetMethod()));
}
return;
}
// Must use FINGERPRINT (optional for ICE STUN indications).
if (!packet->HasFingerprint() && packet->GetClass() != RTC::StunPacket::Class::INDICATION)
{
if (packet->GetClass() == RTC::StunPacket::Class::REQUEST)
{
MS_WARN_TAG(ice, "STUN Binding Request without FINGERPRINT => 400");
// Reply 400.
RTC::StunPacket* response = packet->CreateErrorResponse(400);
response->Serialize(StunSerializeBuffer);
this->listener->OnIceServerSendStunPacket(this, response, tuple);
delete response;
}
else
{
MS_WARN_TAG(ice, "ignoring STUN Binding Response without FINGERPRINT");
}
return;
}
switch (packet->GetClass())
{
case RTC::StunPacket::Class::REQUEST:
{
// USERNAME, MESSAGE-INTEGRITY and PRIORITY are required.
if (!packet->HasMessageIntegrity() || (packet->GetPriority() == 0u) || packet->GetUsername().empty())
{
MS_WARN_TAG(ice, "mising required attributes in STUN Binding Request => 400");
// Reply 400.
RTC::StunPacket* response = packet->CreateErrorResponse(400);
response->Serialize(StunSerializeBuffer);
this->listener->OnIceServerSendStunPacket(this, response, tuple);
delete response;
return;
}
// Check authentication.
switch (packet->CheckAuthentication(this->usernameFragment, this->password))
{
case RTC::StunPacket::Authentication::OK:
{
if (!this->oldPassword.empty())
{
MS_DEBUG_TAG(ice, "new ICE credentials applied");
this->oldUsernameFragment.clear();
this->oldPassword.clear();
}
break;
}
case RTC::StunPacket::Authentication::UNAUTHORIZED:
{
// We may have changed our usernameFragment and password, so check
// the old ones.
// clang-format off
if (
!this->oldUsernameFragment.empty() &&
!this->oldPassword.empty() &&
packet->CheckAuthentication(this->oldUsernameFragment, this->oldPassword) == RTC::StunPacket::Authentication::OK
)
// clang-format on
{
MS_DEBUG_TAG(ice, "using old ICE credentials");
break;
}
MS_WARN_TAG(ice, "wrong authentication in STUN Binding Request => 401");
// Reply 401.
RTC::StunPacket* response = packet->CreateErrorResponse(401);
response->Serialize(StunSerializeBuffer);
this->listener->OnIceServerSendStunPacket(this, response, tuple);
delete response;
return;
}
case RTC::StunPacket::Authentication::BAD_REQUEST:
{
MS_WARN_TAG(ice, "cannot check authentication in STUN Binding Request => 400");
// Reply 400.
RTC::StunPacket* response = packet->CreateErrorResponse(400);
response->Serialize(StunSerializeBuffer);
this->listener->OnIceServerSendStunPacket(this, response, tuple);
delete response;
return;
}
}
#if 0
// The remote peer must be ICE controlling.
if (packet->GetIceControlled())
{
MS_WARN_TAG(ice, "peer indicates ICE-CONTROLLED in STUN Binding Request => 487");
// Reply 487 (Role Conflict).
RTC::StunPacket* response = packet->CreateErrorResponse(487);
response->Serialize(StunSerializeBuffer);
this->listener->OnIceServerSendStunPacket(this, response, tuple);
delete response;
return;
}
#endif
//MS_DEBUG_DEV(
// "processing STUN Binding Request [Priority:%" PRIu32 ", UseCandidate:%s]",
// static_cast<uint32_t>(packet->GetPriority()),
// packet->HasUseCandidate() ? "true" : "false");
// Create a success response.
RTC::StunPacket* response = packet->CreateSuccessResponse();
sockaddr_storage peerAddr;
socklen_t addr_len = sizeof(peerAddr);
getpeername(tuple->getSock()->rawFD(), (struct sockaddr *)&peerAddr, &addr_len);
// Add XOR-MAPPED-ADDRESS.
response->SetXorMappedAddress((struct sockaddr *)&peerAddr);
// Authenticate the response.
if (this->oldPassword.empty())
response->Authenticate(this->password);
else
response->Authenticate(this->oldPassword);
// Send back.
response->Serialize(StunSerializeBuffer);
this->listener->OnIceServerSendStunPacket(this, response, tuple);
delete response;
// Handle the tuple.
HandleTuple(tuple, packet->HasUseCandidate());
break;
}
case RTC::StunPacket::Class::INDICATION:
{
MS_DEBUG_TAG(ice, "STUN Binding Indication processed");
break;
}
case RTC::StunPacket::Class::SUCCESS_RESPONSE:
{
MS_DEBUG_TAG(ice, "STUN Binding Success Response processed");
break;
}
case RTC::StunPacket::Class::ERROR_RESPONSE:
{
MS_DEBUG_TAG(ice, "STUN Binding Error Response processed");
break;
}
}
}
bool IceServer::IsValidTuple(const RTC::TransportTuple* tuple) const
{
MS_TRACE();
return HasTuple(tuple) != nullptr;
}
void IceServer::RemoveTuple(RTC::TransportTuple* tuple)
{
MS_TRACE();
RTC::TransportTuple* removedTuple{ nullptr };
// Find the removed tuple.
auto it = this->tuples.begin();
for (; it != this->tuples.end(); ++it)
{
RTC::TransportTuple* storedTuple = *it;
if (storedTuple == tuple)
{
removedTuple = storedTuple;
break;
}
}
// If not found, ignore.
if (!removedTuple)
return;
// Remove from the list of tuples.
this->tuples.erase(it);
// If this is not the selected tuple, stop here.
if (removedTuple != this->selectedTuple)
return;
// Otherwise this was the selected tuple.
this->selectedTuple = nullptr;
// Mark the first tuple as selected tuple (if any).
if (!this->tuples.empty())
{
SetSelectedTuple(this->tuples.front());
}
// Or just emit 'disconnected'.
else
{
// Update state.
this->state = IceState::DISCONNECTED;
// Notify the listener.
this->listener->OnIceServerDisconnected(this);
}
}
void IceServer::ForceSelectedTuple(const RTC::TransportTuple* tuple)
{
MS_TRACE();
MS_ASSERT(
this->selectedTuple, "cannot force the selected tuple if there was not a selected tuple");
auto* storedTuple = HasTuple(tuple);
MS_ASSERT(
storedTuple,
"cannot force the selected tuple if the given tuple was not already a valid tuple");
// Mark it as selected tuple.
SetSelectedTuple(storedTuple);
}
void IceServer::HandleTuple(RTC::TransportTuple* tuple, bool hasUseCandidate)
{
MS_TRACE();
switch (this->state)
{
case IceState::NEW:
{
// There should be no tuples.
MS_ASSERT(
this->tuples.empty(), "state is 'new' but there are %zu tuples", this->tuples.size());
// There shouldn't be a selected tuple.
MS_ASSERT(!this->selectedTuple, "state is 'new' but there is selected tuple");
if (!hasUseCandidate)
{
MS_DEBUG_TAG(ice, "transition from state 'new' to 'connected'");
// Store the tuple.
auto* storedTuple = AddTuple(tuple);
// Mark it as selected tuple.
SetSelectedTuple(storedTuple);
// Update state.
this->state = IceState::CONNECTED;
// Notify the listener.
this->listener->OnIceServerConnected(this);
}
else
{
MS_DEBUG_TAG(ice, "transition from state 'new' to 'completed'");
// Store the tuple.
auto* storedTuple = AddTuple(tuple);
// Mark it as selected tuple.
SetSelectedTuple(storedTuple);
// Update state.
this->state = IceState::COMPLETED;
// Notify the listener.
this->listener->OnIceServerCompleted(this);
}
break;
}
case IceState::DISCONNECTED:
{
// There should be no tuples.
MS_ASSERT(
this->tuples.empty(),
"state is 'disconnected' but there are %zu tuples",
this->tuples.size());
// There shouldn't be a selected tuple.
MS_ASSERT(!this->selectedTuple, "state is 'disconnected' but there is selected tuple");
if (!hasUseCandidate)
{
MS_DEBUG_TAG(ice, "transition from state 'disconnected' to 'connected'");
// Store the tuple.
auto* storedTuple = AddTuple(tuple);
// Mark it as selected tuple.
SetSelectedTuple(storedTuple);
// Update state.
this->state = IceState::CONNECTED;
// Notify the listener.
this->listener->OnIceServerConnected(this);
}
else
{
MS_DEBUG_TAG(ice, "transition from state 'disconnected' to 'completed'");
// Store the tuple.
auto* storedTuple = AddTuple(tuple);
// Mark it as selected tuple.
SetSelectedTuple(storedTuple);
// Update state.
this->state = IceState::COMPLETED;
// Notify the listener.
this->listener->OnIceServerCompleted(this);
}
break;
}
case IceState::CONNECTED:
{
// There should be some tuples.
MS_ASSERT(!this->tuples.empty(), "state is 'connected' but there are no tuples");
// There should be a selected tuple.
MS_ASSERT(this->selectedTuple, "state is 'connected' but there is not selected tuple");
if (!hasUseCandidate)
{
// If a new tuple store it.
if (!HasTuple(tuple))
AddTuple(tuple);
}
else
{
MS_DEBUG_TAG(ice, "transition from state 'connected' to 'completed'");
auto* storedTuple = HasTuple(tuple);
// If a new tuple store it.
if (!storedTuple)
storedTuple = AddTuple(tuple);
// Mark it as selected tuple.
SetSelectedTuple(storedTuple);
// Update state.
this->state = IceState::COMPLETED;
// Notify the listener.
this->listener->OnIceServerCompleted(this);
}
break;
}
case IceState::COMPLETED:
{
// There should be some tuples.
MS_ASSERT(!this->tuples.empty(), "state is 'completed' but there are no tuples");
// There should be a selected tuple.
MS_ASSERT(this->selectedTuple, "state is 'completed' but there is not selected tuple");
if (!hasUseCandidate)
{
// If a new tuple store it.
if (!HasTuple(tuple))
AddTuple(tuple);
}
else
{
auto* storedTuple = HasTuple(tuple);
// If a new tuple store it.
if (!storedTuple)
storedTuple = AddTuple(tuple);
// Mark it as selected tuple.
SetSelectedTuple(storedTuple);
}
break;
}
}
}
inline RTC::TransportTuple* IceServer::AddTuple(RTC::TransportTuple* tuple)
{
MS_TRACE();
// Add the new tuple at the beginning of the list.
this->tuples.push_front(tuple);
// Return the address of the inserted tuple.
return tuple;
}
inline RTC::TransportTuple* IceServer::HasTuple(const RTC::TransportTuple* tuple) const
{
MS_TRACE();
// If there is no selected tuple yet then we know that the tuples list
// is empty.
if (!this->selectedTuple)
return nullptr;
// Check the current selected tuple.
if (selectedTuple == tuple)
return this->selectedTuple;
// Otherwise check other stored tuples.
for (const auto& it : this->tuples)
{
auto& storedTuple = it;
if (storedTuple == tuple)
return storedTuple;
}
return nullptr;
}
inline void IceServer::SetSelectedTuple(RTC::TransportTuple* storedTuple)
{
MS_TRACE();
// If already the selected tuple do nothing.
if (storedTuple == this->selectedTuple)
return;
this->selectedTuple = storedTuple;
this->lastSelectedTuple = std::static_pointer_cast<RTC::TransportTuple>(storedTuple->shared_from_this());
// Notify the listener.
this->listener->OnIceServerSelectedTuple(this, this->selectedTuple);
}
} // namespace RTC

View File

@ -1,138 +0,0 @@
/**
ISC License
Copyright © 2015, Iñaki Baz Castillo <ibc@aliax.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef MS_RTC_ICE_SERVER_HPP
#define MS_RTC_ICE_SERVER_HPP
#include "StunPacket.hpp"
#include "Network/Session.h"
#include "logger.h"
#include "Utils.hpp"
#include <list>
#include <string>
#include <functional>
#include <memory>
namespace RTC
{
using TransportTuple = toolkit::Session;
class IceServer
{
public:
enum class IceState
{
NEW = 1,
CONNECTED,
COMPLETED,
DISCONNECTED
};
public:
class Listener
{
public:
virtual ~Listener() = default;
public:
/**
* These callbacks are guaranteed to be called before ProcessStunPacket()
* returns, so the given pointers are still usable.
*/
virtual void OnIceServerSendStunPacket(
const RTC::IceServer* iceServer, const RTC::StunPacket* packet, RTC::TransportTuple* tuple) = 0;
virtual void OnIceServerSelectedTuple(
const RTC::IceServer* iceServer, RTC::TransportTuple* tuple) = 0;
virtual void OnIceServerConnected(const RTC::IceServer* iceServer) = 0;
virtual void OnIceServerCompleted(const RTC::IceServer* iceServer) = 0;
virtual void OnIceServerDisconnected(const RTC::IceServer* iceServer) = 0;
};
public:
IceServer(Listener* listener, const std::string& usernameFragment, const std::string& password);
public:
void ProcessStunPacket(RTC::StunPacket* packet, RTC::TransportTuple* tuple);
const std::string& GetUsernameFragment() const
{
return this->usernameFragment;
}
const std::string& GetPassword() const
{
return this->password;
}
IceState GetState() const
{
return this->state;
}
RTC::TransportTuple* GetSelectedTuple(bool try_last_tuple = false) const
{
return try_last_tuple ? this->lastSelectedTuple.lock().get() : this->selectedTuple;
}
void SetUsernameFragment(const std::string& usernameFragment)
{
this->oldUsernameFragment = this->usernameFragment;
this->usernameFragment = usernameFragment;
}
void SetPassword(const std::string& password)
{
this->oldPassword = this->password;
this->password = password;
}
bool IsValidTuple(const RTC::TransportTuple* tuple) const;
void RemoveTuple(RTC::TransportTuple* tuple);
// This should be just called in 'connected' or completed' state
// and the given tuple must be an already valid tuple.
void ForceSelectedTuple(const RTC::TransportTuple* tuple);
const std::list<RTC::TransportTuple *>& GetTuples() const { return tuples; }
private:
void HandleTuple(RTC::TransportTuple* tuple, bool hasUseCandidate);
/**
* Store the given tuple and return its stored address.
*/
RTC::TransportTuple* AddTuple(RTC::TransportTuple* tuple);
/**
* If the given tuple exists return its stored address, nullptr otherwise.
*/
RTC::TransportTuple* HasTuple(const RTC::TransportTuple* tuple) const;
/**
* Set the given tuple as the selected tuple.
* NOTE: The given tuple MUST be already stored within the list.
*/
void SetSelectedTuple(RTC::TransportTuple* storedTuple);
private:
// Passed by argument.
Listener* listener{ nullptr };
// Others.
std::string usernameFragment;
std::string password;
std::string oldUsernameFragment;
std::string oldPassword;
IceState state{ IceState::NEW };
std::list<RTC::TransportTuple *> tuples;
RTC::TransportTuple *selectedTuple { nullptr };
std::weak_ptr<RTC::TransportTuple> lastSelectedTuple;
//最大不超过mtu
static constexpr size_t StunSerializeBufferSize{ 1600 };
uint8_t StunSerializeBuffer[StunSerializeBufferSize];
};
} // namespace RTC
#endif

139
webrtc/IceSession.cpp Normal file
View File

@ -0,0 +1,139 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "IceSession.hpp"
#include "Util/util.h"
#include "Common/config.h"
#include "WebRtcTransport.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
static IceSession::Ptr queryIceTransport(uint8_t *data, size_t size) {
auto packet = RTC::StunPacket::parse((const uint8_t *)data, size);
if (!packet) {
WarnL << "parse stun error";
return nullptr;
}
auto username = packet->getUsername();
return IceSessionManager::Instance().getItem(username);
}
//////////// IceSession //////////////////////////
IceSession::IceSession(const Socket::Ptr &sock) : Session(sock) {
TraceL << getIdentifier();
_over_tcp = sock->sockType() == SockNum::Sock_TCP;
GET_CONFIG(string, iceUfrag, Rtc::kIceUfrag);
GET_CONFIG(string, icePwd, Rtc::kIcePwd);
_ice_transport = std::make_shared<IceServer>(this, iceUfrag, icePwd, getPoller());
_ice_transport->initialize();
}
IceSession::~IceSession() {
TraceL << getIdentifier();
}
EventPoller::Ptr IceSession::queryPoller(const Buffer::Ptr &buffer) {
auto transport = queryIceTransport((uint8_t *)buffer->data(), buffer->size());
return transport ? transport->getPoller() : nullptr;
}
void IceSession::onRecv(const Buffer::Ptr &buffer) {
// TraceL;
if (_over_tcp) {
input(buffer->data(), buffer->size());
}
else{
onRecv_l(buffer->data(), buffer->size());
}
}
void IceSession::onRecv_l(const char* buffer, size_t size) {
if (!_session_pair) {
_session_pair = std::make_shared<IceTransport::Pair>(shared_from_this());
}
_ice_transport->processSocketData((const uint8_t *)buffer, size, _session_pair);
}
void IceSession::onError(const SockException &err) {
InfoL;
// 消除循环引用
_session_pair = nullptr;
}
void IceSession::onManager() {
}
ssize_t IceSession::onRecvHeader(const char *data, size_t len) {
onRecv_l(data + 2, len - 2);
return 0;
}
const char *IceSession::onSearchPacketTail(const char *data, size_t len) {
if (len < 2) {
// Not enough data
return nullptr;
}
uint16_t length = (((uint8_t *)data)[0] << 8) | ((uint8_t *)data)[1];
if (len < (size_t)(length + 2)) {
// Not enough data
return nullptr;
}
// Return the end of the RTP packet
return data + 2 + length;
}
void IceSession::onIceTransportRecvData(const toolkit::Buffer::Ptr& buffer, const IceTransport::Pair::Ptr& pair) {
_ice_transport->processSocketData((const uint8_t *)buffer->data(), buffer->size(), pair);
}
void IceSession::onIceTransportGatheringCandidate(const IceTransport::Pair::Ptr& pair, const CandidateInfo& candidate) {
DebugL << candidate.dumpString();
}
void IceSession::onIceTransportDisconnected() {
InfoL << getIdentifier();
}
void IceSession::onIceTransportCompleted() {
InfoL << getIdentifier();
}
//////////// IceSessionManager //////////////////////////
IceSessionManager &IceSessionManager::Instance() {
static IceSessionManager s_instance;
return s_instance;
}
void IceSessionManager::addItem(const std::string& key, const IceSession::Ptr &ptr) {
std::lock_guard<std::mutex> lck(_mtx);
_map[key] = ptr;
}
IceSession::Ptr IceSessionManager::getItem(const std::string& key) {
assert(!key.empty());
std::lock_guard<std::mutex> lck(_mtx);
auto it = _map.find(key);
if (it == _map.end()) {
return nullptr;
}
return it->second.lock();
}
void IceSessionManager::removeItem(const std::string& key) {
std::lock_guard<std::mutex> lck(_mtx);
_map.erase(key);
}
}// namespace mediakit

70
webrtc/IceSession.hpp Normal file
View File

@ -0,0 +1,70 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_WEBRTC_ICE_SESSION_H
#define ZLMEDIAKIT_WEBRTC_ICE_SESSION_H
#include "Network/Session.h"
#include "IceTransport.hpp"
#include "Http/HttpRequestSplitter.h"
namespace mediakit {
class IceSession : public toolkit::Session, public RTC::IceTransport::Listener, public HttpRequestSplitter {
public:
using Ptr = std::shared_ptr<IceSession>;
using WeakPtr = std::weak_ptr<IceSession>;
IceSession(const toolkit::Socket::Ptr &sock);
~IceSession() override;
static toolkit::EventPoller::Ptr queryPoller(const toolkit::Buffer::Ptr &buffer);
//// Session override////
// void attachServer(const Server &server) override;
void onRecv(const toolkit::Buffer::Ptr &) override;
void onError(const toolkit::SockException &err) override;
void onManager() override;
// ice related callbacks ///
void onIceTransportRecvData(const toolkit::Buffer::Ptr& buffer, const RTC::IceTransport::Pair::Ptr& pair) override;
void onIceTransportGatheringCandidate(const RTC::IceTransport::Pair::Ptr& pair, const RTC::CandidateInfo& candidate) override;
void onIceTransportDisconnected() override;
void onIceTransportCompleted() override;
//// HttpRequestSplitter override ////
ssize_t onRecvHeader(const char *data, size_t len) override;
const char *onSearchPacketTail(const char *data, size_t len) override;
void onRecv_l(const char *data, size_t len);
protected:
bool _over_tcp = false;
RTC::IceTransport::Pair::Ptr _session_pair = nullptr;
RTC::IceServer::Ptr _ice_transport;
};
class IceSessionManager {
public:
static IceSessionManager &Instance();
IceSession::Ptr getItem(const std::string& key);
void addItem(const std::string& key, const IceSession::Ptr &ptr);
void removeItem(const std::string& key);
private:
IceSessionManager() = default;
private:
std::mutex _mtx;
std::unordered_map<std::string, std::weak_ptr<IceSession>> _map;
};
}// namespace mediakit
#endif //ZLMEDIAKIT_WEBRTC_ICE_SESSION_H

2026
webrtc/IceTransport.cpp Normal file

File diff suppressed because it is too large Load Diff

754
webrtc/IceTransport.hpp Normal file
View File

@ -0,0 +1,754 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_WEBRTC_ICE_TRANSPORT_HPP
#define ZLMEDIAKIT_WEBRTC_ICE_TRANSPORT_HPP
#include <map>
#include <list>
#include <string>
#include <memory>
#include <algorithm>
#include <functional>
#include <unordered_map>
#include "json/json.h"
#include "Util/Byte.hpp"
#include "Poller/Timer.h"
#include "Poller/EventPoller.h"
#include "Network/Socket.h"
#include "Network/UdpClient.h"
#include "Network/Session.h"
#include "logger.h"
#include "StunPacket.hpp"
namespace RTC {
uint64_t calCandidatePairPriority(uint32_t G, uint32_t D);
class CandidateAddr {
public:
bool operator==(const CandidateAddr& rhs) const {
return ((_host == rhs._host) && (_port == rhs._port));
}
bool operator!=(const CandidateAddr& rhs) const {
return !(*this == rhs);
}
std::string dumpString() const {
return _host + ":" + std::to_string(_port);
}
public:
std::string _host;
uint16_t _port = 0;
};
class CandidateTuple {
public:
using Ptr = std::shared_ptr<CandidateTuple>;
CandidateTuple() = default;
virtual ~CandidateTuple() = default;
enum class AddressType {
HOST = 1,
SRFLX, //server reflexive
PRFLX, //peer reflexive
RELAY,
};
enum class SecureType {
NOT_SECURE = 1,
SECURE,
};
enum class TransportType {
UDP = 1,
TCP,
};
bool operator<(const CandidateTuple& rhs) const {
return (_priority < rhs._priority);
}
bool operator==(const CandidateTuple& rhs) const {
return ((_addr == rhs._addr)
&& (_priority == rhs._priority)
&& (_transport == rhs._transport) && (_secure == rhs._secure));
}
struct ClassHash {
std::size_t operator()(const CandidateTuple& t) const {
std::string str = t._addr._host + std::to_string(t._addr._port) +
std::to_string((uint32_t)t._transport) + std::to_string((uint32_t)t._secure);
return std::hash<std::string>()(str);
}
};
struct ClassEqual {
bool operator()(const CandidateTuple& a, const CandidateTuple& b) const {
return a == b;
}
};
public:
CandidateAddr _addr;
uint32_t _priority = 0;
TransportType _transport = TransportType::UDP;
SecureType _secure = SecureType::NOT_SECURE;
std::string _ufrag;
std::string _pwd;
};
class CandidateInfo : public CandidateTuple {
public:
using Ptr = std::shared_ptr<CandidateInfo>;
CandidateInfo() = default;
virtual ~CandidateInfo() = default;
enum class AddressType {
INVALID = 0,
HOST = 1,
SRFLX, // server reflx
PRFLX, // peer reflx
RELAY,
};
enum class State {
Frozen = 1, //尚未check,并还不需要check
Waiting, //尚未发送check,但也不是Frozen
InProgress, //已经发起check,但是仍在进行中
Succeeded, //check success
Failed, //check failed
};
bool operator==(const CandidateInfo& rhs) const {
return CandidateTuple::operator==(rhs) && (_type == rhs._type);
}
std::string getAddressTypeStr() const {
return getAddressTypeStr(_type);
}
// 获取候选者地址类型字符串的静态函数
static std::string getAddressTypeStr(CandidateInfo::AddressType type) {
switch (type) {
case CandidateInfo::AddressType::HOST: return "host";
case CandidateInfo::AddressType::SRFLX: return "srflx";
case CandidateInfo::AddressType::PRFLX: return "reflx";
case CandidateInfo::AddressType::RELAY: return "relay";
default: return "invalid";
}
}
static std::string getStateStr(State state) {
switch (state) {
case State::Frozen: return "frozen";
case State::Waiting: return "waiting";
case State::InProgress: return "in_progress";
case State::Succeeded: return "succeeded";
case State::Failed: return "failed";
default: break;
}
return "unknown";
}
std::string dumpString() const {
return getAddressTypeStr() + " " + _addr.dumpString();
}
public:
AddressType _type = AddressType::HOST;
CandidateAddr _base_addr;
};
// ice stun/turn服务器配置
// 格式为: (stun/turn)[s]:host:port[?transport=(tcp/udp)], 默认udp模式
// 例如:
// stun:stun.l.google.com:19302 → 谷歌的 STUN 服务器UDP
// turn:turn.example.com:3478?transport=tcp → 使用 TCP 的 TURN 服务器。
// turns:turn.example.com:5349 → 使用 TLS 的 TURN 服务器。
class IceServerInfo : public CandidateTuple {
public:
using Ptr = std::shared_ptr<IceServerInfo>;
IceServerInfo() = default;
virtual ~IceServerInfo() = default;
IceServerInfo(const std::string &url) { parse(url); }
void parse(const std::string &url);
enum class SchemaType {
TURN = 1,
STUN,
};
public:
std::string _full_url;
std::string _param_strs;
SchemaType _schema = SchemaType::TURN;
};
class IceTransport : public std::enable_shared_from_this<IceTransport> {
public:
using Ptr = std::shared_ptr<IceTransport>;
class Pair {
public:
using Ptr = std::shared_ptr<Pair>;
Pair() = default;
Pair(toolkit::SocketHelper::Ptr socket) : _socket(std::move(socket)) {}
Pair(toolkit::SocketHelper::Ptr socket, std::string peer_host, uint16_t peer_port,
std::shared_ptr<sockaddr_storage> relayed_addr = nullptr) :
_socket(std::move(socket)), _peer_host(std::move(peer_host)), _peer_port(peer_port), _relayed_addr(std::move(relayed_addr)) {
}
Pair(Pair &that) {
_socket = that._socket;
_peer_host = that._peer_host;
_peer_port = that._peer_port;
_relayed_addr = nullptr;
if (that._relayed_addr) {
_relayed_addr = std::make_shared<sockaddr_storage>();
memcpy(_relayed_addr.get(), that._relayed_addr.get(), sizeof(sockaddr_storage));
}
}
virtual ~Pair() = default;
void get_peer_addr(sockaddr_storage &peer_addr) const {
if (!_peer_host.empty()) {
peer_addr = toolkit::SockUtil::make_sockaddr(_peer_host.data(), _peer_port);
} else {
auto addr = _socket->get_peer_addr();
if (addr->sa_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)addr)->sin6_addr)) {
memset(&peer_addr, 0, sizeof(peer_addr));
// 转换IPv6 v4mapped地址为IPv4地址
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr;
struct sockaddr_in *addr4 = (struct sockaddr_in *)&peer_addr;
addr4->sin_family = AF_INET;
addr4->sin_port = addr6->sin6_port;
memcpy(&addr4->sin_addr, &addr6->sin6_addr.s6_addr[12], 4);
} else {
memcpy(&peer_addr, addr, toolkit::SockUtil::get_sock_len(addr));
}
}
}
bool get_relayed_addr(sockaddr_storage &peerAddr) const {
if (!_relayed_addr) {
return false;
}
memcpy(&peerAddr, _relayed_addr.get(), sizeof(peerAddr));
return true;
}
std::string get_local_ip() const { return _socket->get_local_ip(); }
uint16_t get_local_port() const { return _socket->get_local_port(); }
std::string get_peer_ip() const { return !_peer_host.empty() ? _peer_host : _socket->get_peer_ip(); }
uint16_t get_peer_port() const { return !_peer_host.empty() ? _peer_port : _socket->get_peer_port(); }
std::string get_relayed_ip() const { return _relayed_addr ? toolkit::SockUtil::inet_ntoa((const struct sockaddr *)_relayed_addr.get()) : ""; }
uint16_t get_relayed_port() const { return _relayed_addr ? toolkit::SockUtil::inet_port((const struct sockaddr *)_relayed_addr.get()) : 0; }
static bool is_same_relayed_addr(Pair *a, Pair *b) {
if (a->_relayed_addr && b->_relayed_addr) {
return toolkit::SockUtil::is_same_addr(
reinterpret_cast<const struct sockaddr *>(a->_relayed_addr.get()), reinterpret_cast<const struct sockaddr *>(b->_relayed_addr.get()));
}
return (a->_relayed_addr == b->_relayed_addr);
}
static bool is_same(Pair* a, Pair* b) {
// FIXME: a->_socket == b->_socket条件成立后后面get_peer_ip和get_peer_port一定相同
if ((a->_socket == b->_socket)
&& (a->get_peer_ip() == b->get_peer_ip())
&& (a->get_peer_port() == b->get_peer_port())
&& (is_same_relayed_addr(a, b))) {
return true;
}
return false;
}
std::string dumpString(uint8_t flag) const {
toolkit::_StrPrinter sp;
static const char* fStr[] = { "<-", "->", "<->" };
sp << (_socket ? (_socket->getSock()->sockType() == toolkit::SockNum::Sock_TCP ? "tcp " : "udp ") : "")
<< get_local_ip() << ":" << get_local_port() << fStr[flag] << get_peer_ip() << ":" << get_peer_port();
if (_relayed_addr && flag == 2) {
sp << " relay " << get_relayed_ip() << ":" << get_relayed_port();
}
return sp;
}
public:
toolkit::SocketHelper::Ptr _socket;
//对端host:port 地址因为多个pair会复用一个socket对象因此可能会和_socket的创建bind信息不一致
std::string _peer_host;
uint16_t _peer_port;
//中继后地址用于实现TURN转发地址当该地址不为空时该地址为真正的peer地址,_peer_host和_peer_port表示中继地址
std::shared_ptr<sockaddr_storage> _relayed_addr;
};
class Listener {
public:
virtual ~Listener() = default;
public:
virtual void onIceTransportRecvData(const toolkit::Buffer::Ptr& buffer, const Pair::Ptr& pair) = 0;
virtual void onIceTransportGatheringCandidate(const Pair::Ptr&, const CandidateInfo&) = 0;
virtual void onIceTransportDisconnected() = 0;
virtual void onIceTransportCompleted() = 0;
};
public:
using MsgHandler = std::function<void(const StunPacket::Ptr&, const Pair::Ptr&)>;
struct RequestInfo {
StunPacket::Ptr _request; // 原始请求包
MsgHandler _handler; // 响应处理函数
Pair::Ptr _pair; // 发送对
uint64_t _send_time; // 首次发送时间(毫秒)
uint64_t _next_timeout; // 下次超时时间(毫秒)
uint32_t _retry_count; // 当前重传次数
uint32_t _rto = 500; // 当前RTO值(毫秒) 初始RTO 500ms
RequestInfo(StunPacket::Ptr req, MsgHandler h, Pair::Ptr p)
: _request(std::move(req))
, _handler(std::move(h))
, _pair(std::move(p))
, _retry_count(0) {
_send_time = toolkit::getCurrentMillisecond();
_next_timeout = _send_time + _rto;
}
};
IceTransport(Listener* listener, std::string ufrag, std::string password, toolkit::EventPoller::Ptr poller);
virtual ~IceTransport() {}
virtual void initialize();
const toolkit::EventPoller::Ptr& getPoller() const { return _poller; }
const std::string& getIdentifier() const { return _identifier; }
const std::string& getUfrag() const { return _ufrag; }
const std::string& getPassword() const { return _password; }
void setUfrag(std::string ufrag) { _ufrag = std::move(ufrag); }
void setPassword(std::string password) { _password = std::move(password); }
virtual bool processSocketData(const uint8_t* data, size_t len, const Pair::Ptr& pair);
virtual void sendSocketData(const toolkit::Buffer::Ptr& buf, const Pair::Ptr& pair, bool flush = true);
void sendSocketData_l(const toolkit::Buffer::Ptr& buf, const Pair::Ptr& pair, bool flush = true);
protected:
virtual void processStunPacket(const StunPacket::Ptr& packet, const Pair::Ptr& pair);
virtual void processRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair);
virtual void processResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair);
virtual bool processChannelData(const uint8_t* data, size_t len, const Pair::Ptr& pair);
virtual StunPacket::Authentication checkRequestAuthentication(const StunPacket::Ptr& packet, const Pair::Ptr& pair);
StunPacket::Authentication checkResponseAuthentication(const StunPacket::Ptr& request, const StunPacket::Ptr& packet, const Pair::Ptr& pair);
void processUnauthorizedResponse(const StunPacket::Ptr& response, const StunPacket::Ptr& request, const Pair::Ptr& pair, MsgHandler handler);
virtual void handleBindingRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair);
virtual void handleChannelData(uint16_t channel_number, const char* data, size_t len, const Pair::Ptr& pair) {};
void sendChannelData(uint16_t channel_number, const toolkit::Buffer::Ptr &buffer, const Pair::Ptr& pair);
virtual void sendUnauthorizedResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair);
void sendErrorResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair, StunAttrErrorCode::Code errorCode);
void sendRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair, MsgHandler handler);
void sendPacket(const StunPacket::Ptr& packet, const Pair::Ptr& pair);
// For permissions
bool hasPermission(const sockaddr_storage& addr);
void addPermission(const sockaddr_storage& addr);
// For Channel Bind
bool hasChannelBind(uint16_t channel_number);
bool hasChannelBind(const sockaddr_storage& addr, uint16_t& channel_number);
void addChannelBind(uint16_t channel_number, const sockaddr_storage& addr);
toolkit::SocketHelper::Ptr createSocket(CandidateTuple::TransportType type, const std::string &peer_host, uint16_t peer_port, const std::string &local_ip, uint16_t local_port = 0);
toolkit::SocketHelper::Ptr createUdpSocket(const std::string &target_host, uint16_t peer_port, const std::string &local_ip, uint16_t local_port);
void checkRequestTimeouts();
void retransmitRequest(const std::string& transaction_id, RequestInfo& req_info);
protected:
std::string _identifier;
toolkit::EventPoller::Ptr _poller;
Listener* _listener = nullptr;
std::unordered_map<std::string /*transcation ID*/, RequestInfo> _response_handlers;
std::unordered_map<std::pair<StunPacket::Class, StunPacket::Method>, MsgHandler, StunPacket::ClassMethodHash> _request_handlers;
// for local
std::string _ufrag;
std::string _password;
// For permissions
std::unordered_map<sockaddr_storage /*peer ip:port*/, uint64_t /* create or fresh time*/,
toolkit::SockUtil::SockAddrHash, toolkit::SockUtil::SockAddrEqual> _permissions;
// For Channel Bind
std::unordered_map<uint16_t /*channel number*/, sockaddr_storage /*peer ip:port*/> _channel_bindings;
std::unordered_map<uint16_t /*channel number*/, uint64_t /*bind or fresh time*/> _channel_binding_times;
// For STUN request retry
std::shared_ptr<toolkit::Timer> _retry_timer;
};
class IceServer : public IceTransport {
public:
using Ptr = std::shared_ptr<IceServer>;
using WeakPtr = std::weak_ptr<IceServer>;
IceServer(Listener* listener, std::string ufrag, std::string password, toolkit::EventPoller::Ptr poller);
virtual ~IceServer() {}
bool processSocketData(const uint8_t* data, size_t len, const Pair::Ptr& pair) override;
void relayForwordingData(const toolkit::Buffer::Ptr& buffer, const sockaddr_storage& peer_addr);
void relayBackingData(const toolkit::Buffer::Ptr& buffer, const Pair::Ptr& pair, const sockaddr_storage& peer_addr);
protected:
void processRelayPacket(const toolkit::Buffer::Ptr &buffer, const Pair::Ptr& pair);
void handleAllocateRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair);
void handleRefreshRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair);
void handleCreatePermissionRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair);
void handleChannelBindRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair);
void handleSendIndication(const StunPacket::Ptr& packet, const Pair::Ptr& pair);
void handleChannelData(uint16_t channel_number, const char* data, size_t len, const Pair::Ptr& pair) override;
StunPacket::Authentication checkRequestAuthentication(const StunPacket::Ptr& packet, const Pair::Ptr& pair) override;
void sendDataIndication(const sockaddr_storage& peer_addr, const toolkit::Buffer::Ptr &buffer, const Pair::Ptr& pair);
void sendUnauthorizedResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair) override;
toolkit::SocketHelper::Ptr allocateRelayed(const Pair::Ptr& pair);
toolkit::SocketHelper::Ptr createRelayedUdpSocket(const std::string &peer_host, uint16_t peer_port, const std::string &local_ip, uint16_t local_port);
protected:
std::vector<toolkit::BufferLikeString> _nonce_list;
std::unordered_map<sockaddr_storage /*peer ip:port*/, std::pair<std::shared_ptr<uint16_t> /* port */, Pair::Ptr /*relayed_pairs*/>,
toolkit::SockUtil::SockAddrHash, toolkit::SockUtil::SockAddrEqual> _relayed_pairs;
Pair::Ptr _session_pair;
};
class IceAgent : public IceTransport {
public:
using Ptr = std::shared_ptr<IceAgent>;
// 候选者对信息结构
struct CandidatePair {
Pair::Ptr _local_pair; // 本地候选者对
CandidateInfo _remote_candidate; // 远程候选者信息
CandidateInfo _local_candidate; // 本地候选者信息
uint64_t _priority; // 候选者对优先级64位符合RFC 8445
CandidateInfo::State _state; // 连通性检查状态
bool _nominated = false;
CandidatePair(Pair::Ptr local_pair, CandidateInfo remote, CandidateInfo local)
: _local_pair(std::move(local_pair))
, _remote_candidate(std::move(remote))
, _local_candidate(std::move(local))
, _state(CandidateInfo::State::Frozen) {
_priority = calCandidatePairPriority(local._priority, remote._priority);
}
std::string dumpString() const {
return "local " + _local_candidate.dumpString() + " <-> remote " + _remote_candidate.dumpString();
}
// 比较操作符,用于优先级排序(高优先级在前)
bool operator<(const CandidatePair& other) const {
return _priority > other._priority;
}
};
enum class State {
//checklist state and ice session state
Running = 1, //正在进行候选地址的连通性检测
Nominated, //发起提名,等待应答
Completed, //所有候选地址完成验证,且至少有一路连接检测成功
Failed, //所有候选地址检测失败,连接不可用
};
static const char* stateToString(State state) {
switch (state) {
case State::Running: return "Running";
case State::Completed: return "Completed";
case State::Failed: return "Failed";
default: return "Unknown";
}
}
enum class Role {
Controlling = 1,
Controlled,
};
enum class Implementation {
Lite = 1,
Full,
};
IceAgent(Listener* listener, Implementation implementation, Role role,
std::string ufrag, std::string password, toolkit::EventPoller::Ptr poller);
virtual ~IceAgent() {}
void setIceServer(IceServerInfo::Ptr ice_server) {
_ice_server = std::move(ice_server);
}
void gatheringCandidate(const CandidateTuple::Ptr& candidate_tuple, bool gathering_rflx, bool gathering_realy);
void connectivityCheck(CandidateInfo& candidate);
void nominated(const Pair::Ptr& pair, CandidateTuple& candidate);
void sendSocketData(const toolkit::Buffer::Ptr& buf, const Pair::Ptr& pair, bool flush = true) override;
IceAgent::Implementation getImplementation() const {
return _implementation;
}
void setgetImplementation(IceAgent::Implementation implementation) {
InfoL << (uint32_t)implementation;
_implementation = implementation;
}
IceAgent::Role getRole() const {
return _role;
}
void setRole(IceAgent::Role role) {
InfoL << (uint32_t)role;
_role = role;
}
IceAgent::State getState() const {
return _state;
}
void setState(IceAgent::State state) {
InfoL << stateToString(state);
_state = state;
}
Pair::Ptr getSelectedPair(bool try_last = false) const {
return try_last ? _last_selected_pair.lock() : _selected_pair;
}
bool setSelectedPair(const Pair::Ptr& pair);
void removePair(const toolkit::SocketHelper *socket);
std::vector<Pair::Ptr> getPairs() const;
// 获取checklist信息用于API查询
Json::Value getChecklistInfo() const;
protected:
void gatheringSrflxCandidate(const Pair::Ptr& pair);
void gatheringRealyCandidate(const Pair::Ptr& pair);
void localRelayedConnectivityCheck(CandidateInfo& candidate);
void connectivityCheck(const Pair::Ptr& pair, CandidateTuple& candidate);
void tryTriggerredCheck(const Pair::Ptr& pair);
void sendBindRequest(const Pair::Ptr& pair, MsgHandler handler);
void sendBindRequest(const Pair::Ptr& pair, CandidateTuple& candidate, bool use_candidate, MsgHandler handler);
void sendAllocateRequest(const Pair::Ptr& pair);
void sendCreatePermissionRequest(const Pair::Ptr& pair, const sockaddr_storage& peer_addr);
void sendChannelBindRequest(const Pair::Ptr& pair, uint16_t channel_number, const sockaddr_storage& peer_addr);
void processRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair) override;
void handleBindingRequest(const StunPacket::Ptr& packet, const Pair::Ptr& pair) override;
void handleGatheringCandidateResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair);
void handleConnectivityCheckResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair, CandidateTuple& candidate);
void handleNominatedResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair, CandidateTuple& candidate);
void handleAllocateResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair);
void handleCreatePermissionResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair, const sockaddr_storage& peer_addr);
void handleChannelBindResponse(const StunPacket::Ptr& packet, const Pair::Ptr& pair, uint16_t channel_number, const sockaddr_storage& peer_addr);
void handleDataIndication(const StunPacket::Ptr& packet, const Pair::Ptr& pair);
void handleChannelData(uint16_t channel_number, const char* data, size_t len, const Pair::Ptr& pair) override;
void onGatheringCandidate(const Pair::Ptr& pair, CandidateInfo& candidate);
void onConnected(const Pair::Ptr& pair);
void onCompleted(const Pair::Ptr& pair);
void refreshPermissions();
void refreshChannelBindings();
void sendSendIndication(const sockaddr_storage& peer_addr, const toolkit::Buffer::Ptr& buffer, const Pair::Ptr& pair);
void sendRealyPacket(const toolkit::Buffer::Ptr& buffer, const Pair::Ptr& pair, bool flush);
private:
CandidateInfo getLocalCandidateInfo(const Pair::Ptr& local_pair);
void addToChecklist(const Pair::Ptr& local_pair, CandidateInfo& remote_candidate);
protected:
IceServerInfo::Ptr _ice_server;
std::shared_ptr<toolkit::Timer> _refresh_timer;
// for candidate
Implementation _implementation = Implementation::Full;
Role _role = Role::Controlling; //ice role
uint64_t _tiebreaker = 0; // 8 bytes unsigned integer.
State _state = IceAgent::State::Running; //ice session state
Pair::Ptr _selected_pair;
Pair::Ptr _nominated_pair;
StunPacket::Ptr _nominated_response;
std::weak_ptr<Pair> _last_selected_pair;
// 双向索引的候选地址管理结构
struct SocketCandidateManager {
// socket -> candidates 的一对多映射
std::unordered_map<toolkit::SocketHelper::Ptr, std::vector<CandidateInfo>> socket_to_candidates;
// candidate -> socket 的映射(用于快速查找)
std::unordered_map<CandidateInfo, toolkit::SocketHelper::Ptr, CandidateTuple::ClassHash, CandidateTuple::ClassEqual> candidate_to_socket;
// 按类型分组的socket列表方便遍历
std::vector<toolkit::SocketHelper::Ptr> _host_sockets; // HOST类型socket
std::vector<toolkit::SocketHelper::Ptr> _relay_sockets; // RELAY类型socket
bool _has_relayed_candidate = false;
// 添加映射关系带5元组重复检查
bool addMapping(toolkit::SocketHelper::Ptr socket, const CandidateInfo& candidate) {
// 检查5元组是否已存在
if (candidate_to_socket.find(candidate) != candidate_to_socket.end()) {
return false; // 已存在相同的5元组
}
socket_to_candidates[socket].push_back(candidate);
candidate_to_socket[candidate] = socket;
// 按类型分组
if (candidate._type != CandidateInfo::AddressType::RELAY) {
addHostSocket(std::move(socket));
} else if (candidate._type == CandidateInfo::AddressType::RELAY) {
addRelaySocket(std::move(socket));
}
return true;
}
// 获取socket对应的所有candidates
std::vector<CandidateInfo> getCandidates(const toolkit::SocketHelper::Ptr& socket) const {
auto it = socket_to_candidates.find(socket);
return (it != socket_to_candidates.end()) ? it->second : std::vector<CandidateInfo>();
}
// 获取candidate对应的socket
toolkit::SocketHelper::Ptr getSocket(const CandidateInfo& candidate) const {
auto it = candidate_to_socket.find(candidate);
return (it != candidate_to_socket.end()) ? it->second : nullptr;
}
// 获取所有socket便于遍历
std::vector<toolkit::SocketHelper::Ptr> getAllSockets() const {
std::vector<toolkit::SocketHelper::Ptr> result;
result.reserve(_host_sockets.size() + _relay_sockets.size());
result.insert(result.end(), _host_sockets.begin(), _host_sockets.end());
result.insert(result.end(), _relay_sockets.begin(), _relay_sockets.end());
return result;
}
// 获取所有candidates便于遍历
std::vector<CandidateInfo> getAllCandidates() const {
std::vector<CandidateInfo> result;
for (auto& pair : candidate_to_socket) {
result.push_back(pair.first);
}
return result;
}
// 直接添加host socket
void addHostSocket(toolkit::SocketHelper::Ptr socket) {
if (std::find(_host_sockets.begin(), _host_sockets.end(), socket) == _host_sockets.end()) {
_host_sockets.emplace_back(std::move(socket));
}
}
// 直接添加relay socket
void addRelaySocket(toolkit::SocketHelper::Ptr socket) {
if (std::find(_relay_sockets.begin(), _relay_sockets.end(), socket) == _relay_sockets.end()) {
_relay_sockets.emplace_back(std::move(socket));
}
}
// 获取host sockets
const std::vector<toolkit::SocketHelper::Ptr>& getHostSockets() const {
return _host_sockets;
}
// 获取relay sockets
const std::vector<toolkit::SocketHelper::Ptr>& getRelaySockets() const {
return _relay_sockets;
}
// 移除host socket
void removeHostSocket(const toolkit::SocketHelper::Ptr& socket) {
auto it = std::find(_host_sockets.begin(), _host_sockets.end(), socket);
if (it != _host_sockets.end()) {
_host_sockets.erase(it);
}
}
// 移除relay socket
void removeRelaySocket(const toolkit::SocketHelper::Ptr& socket) {
auto it = std::find(_relay_sockets.begin(), _relay_sockets.end(), socket);
if (it != _relay_sockets.end()) {
_relay_sockets.erase(it);
}
}
// 清空host sockets
void clearHostSockets() {
_host_sockets.clear();
}
// 清空relay sockets
void clearRelaySockets() {
_relay_sockets.clear();
}
// 获取host socket数量
size_t getHostSocketCount() const {
return _host_sockets.size();
}
// 获取relay socket数量
size_t getRelaySocketCount() const {
return _relay_sockets.size();
}
};
//for GATHERING_CANDIDATE
SocketCandidateManager _socket_candidate_manager; //local candidates
//for CONNECTIVITY_CHECK
using CandidateSet = std::unordered_set<CandidateInfo, CandidateTuple::ClassHash, CandidateTuple::ClassEqual>;
CandidateSet _remote_candidates;
//TODO:当前仅支持多数据流复用一个checklist
std::vector<std::shared_ptr<CandidatePair>> _check_list;
std::vector<std::shared_ptr<CandidatePair>> _valid_list;
std::shared_ptr<CandidatePair> _select_candidate_pair;
};
} // namespace RTC
#endif //ZLMEDIAKIT_WEBRTC_ICE_TRANSPORT_HPP

210
webrtc/RtpMap.h Normal file
View File

@ -0,0 +1,210 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_RTPMAP_H
#define ZLMEDIAKIT_RTPMAP_H
#include <set>
#include <map>
#include <string>
#include <vector>
#include <memory>
#include <iostream>
#include <iomanip>
#include <cassert>
#include "Extension/Frame.h"
namespace mediakit {
class RtpMap {
public:
using Ptr = std::shared_ptr<RtpMap>;
RtpMap(std::string code_name, uint8_t payload, uint32_t clock_rate)
: _code_name(std::move(code_name))
, _payload(payload)
, _clock_rate(clock_rate) {}
virtual ~RtpMap() = default;
virtual TrackType getType() = 0;
const std::map<std::string /*key*/, std::string /*value*/> &getFmtp() const { return _fmtp; }
const std::string &getCodeName() const { return _code_name; }
uint8_t getPayload() const { return _payload; }
uint32_t getClockRate() const { return _clock_rate; }
protected:
std::map<std::string /*key*/, std::string /*value*/> _fmtp;
std::string _code_name;
uint8_t _payload;
uint32_t _clock_rate;
};
class VideoRtpMap : public RtpMap {
public:
VideoRtpMap(std::string code_name, uint8_t payload, uint32_t clock_rate)
: RtpMap(std::move(code_name), payload, clock_rate) {};
TrackType getType() override { return TrackVideo; }
};
class AudioRtpMap : public RtpMap {
public:
AudioRtpMap( std::string code_name, uint8_t payload, uint32_t clock_rate)
: RtpMap(std::move(code_name), payload, clock_rate) {};
TrackType getType() override { return TrackAudio; };
};
#define H264_PROFILE_IDC_MAP(XX) \
XX(PROFILE_H264_BASELINE, 66, "baseline") \
XX(PROFILE_H264_MAIN, 77, "main") \
XX(PROFILE_H264_HIGH, 100, "high") \
XX(PROFILE_H264_HIGH10, 110, "high10") \
XX(PROFILE_H264_HIGH422, 122, "high422") \
XX(PROFILE_H264_HIGH444, 244, "high444") \
typedef enum {
H264ProfileIdcInvalid = -1,
#define XX(name, value, str) name = value,
H264_PROFILE_IDC_MAP(XX)
#undef XX
H264ProfileIdcMax
} H264ProfileIdc;
#define H264_PROFILE_LEVEL_MAP(XX) \
XX(10) \
XX(20) \
XX(30) \
XX(31) \
XX(40) \
XX(41) \
XX(50) \
XX(51)
typedef enum {
H264ProfileLevelInvalid = -1,
#define XX(value) H264_PROFILE_LEVEL_##value = value,
H264_PROFILE_LEVEL_MAP(XX)
#undef XX
H264ProfileLevelMax
} H264ProfileLevel;
class H264RtpMap : public VideoRtpMap {
public:
H264RtpMap(uint8_t payload, uint32_t clock_rate, H264ProfileIdc profile_idc)
: VideoRtpMap("H264", payload, clock_rate)
, _profile_idc(profile_idc) {
_fmtp.emplace("level-asymmetry-allowed", "1");
_fmtp.emplace("packetization-mode", "1");
toolkit::_StrPrinter printer;
printer << std::setw(2) << std::setfill('0') << std::hex << _profile_idc;
printer << std::setw(2) << std::setfill('0') << std::hex << _profile_iop;
printer << std::setw(2) << std::setfill('0') << std::hex << _profile_level;
_fmtp.emplace("profile-level-id", printer);
};
private:
H264ProfileIdc _profile_idc;
int _profile_iop = 0;
H264ProfileLevel _profile_level = H264_PROFILE_LEVEL_31;
};
#define H265_PROFILE_IDC_MAP(XX) \
XX(PROFILE_H265_MAIN, 1, "main") \
XX(PROFILE_H265_MAIN10, 2, "main10") \
XX(PROFILE_H265_MAINSTILL, 3, "mainstill") \
XX(PROFILE_H265_RANGE_EXTS, 4, "RangeExtensions") \
XX(PROFILE_H265_HIGH_THROUGHPUT, 5, "HighThroughput") \
XX(PROFILE_H265_MULTIVIEW, 6, "MultiviewMain") \
XX(PROFILE_H265_SCALABLE_MAIN, 7, "ScalableMain") \
XX(PROFILE_H265_3DMAIN, 8, "3dMain") \
XX(PROFILE_H265_SCREEN, 9, "ScreenContentCoding") \
XX(PROFILE_H265_SCALABLE_RANGE_EXTENSIONS, 10, "ScalableRangeExtensions") \
XX(PROFILE_H265_HIGH_SCREEN, 11, "HighThroughputScreenContentCoding")
typedef enum {
H265ProfileIdcInvalid = -1,
#define XX(name, value, str) name = value,
H265_PROFILE_IDC_MAP(XX)
#undef XX
H265ProfileIdcMax
} H265ProfileIdc;
#define H265_PROFILE_LEVEL_MAP(XX) \
XX(30) \
XX(60) \
XX(63) \
XX(90) \
XX(93) \
XX(120) \
XX(123) \
XX(150) \
XX(153) \
XX(156) \
XX(180) \
XX(183) \
XX(186)
typedef enum {
H265ProfileLevelInvalid = -1,
#define XX(value) H265_PROFILE_LEVEL_##value = value,
H265_PROFILE_LEVEL_MAP(XX)
#undef XX
H265ProfileLevelMax
} H265ProfileLevel;
class H265RtpMap : public VideoRtpMap {
public:
H265RtpMap(uint8_t payload, uint32_t clock_rate, H265ProfileIdc profile_idc)
: VideoRtpMap("H265", payload, clock_rate)
, _profile_idc(profile_idc) {
_fmtp.emplace("level-asymmetry-allowed", "1");
_fmtp.emplace("packetization-mode", "1");
_fmtp.emplace("profile-id", std::to_string(_profile_idc));
_fmtp.emplace("tier-flag", std::to_string(_tier_flag));
_fmtp.emplace("level-id", std::to_string(_profile_level));
}
private:
H265ProfileIdc _profile_idc;
int _tier_flag = 0; // 0: main tier; 1: high tier
H265ProfileLevel _profile_level = H265_PROFILE_LEVEL_30;
};
class VP9RtpMap : public VideoRtpMap {
public:
VP9RtpMap(uint8_t payload, uint32_t clock_rate, int profile_id)
: VideoRtpMap("VP9", payload, clock_rate)
, _profile_id(profile_id) {
_fmtp.emplace("profile-id", std::to_string(_profile_id));
};
private:
int _profile_id = 1; // 0-3
};
class AV1RtpMap : public VideoRtpMap {
public:
AV1RtpMap(uint8_t payload, uint32_t clock_rate, int profile_id)
: VideoRtpMap("AV1", payload, clock_rate)
, _profile_id(profile_id) {
// a=fmtp:45 level-idx=5;profile=0;tier=0
_fmtp.emplace("profile-id", std::to_string(_profile_id));
};
private:
int _profile_id = 0; // 0-2
};
} // namespace mediakit
#endif // ZLMEDIAKIT_RTPMAP_H

View File

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

View File

@ -1083,6 +1083,7 @@ RtcSessionSdp::Ptr RtcSession::toRtcSessionSdp() const {
sdp.addItem(std::make_shared<SdpConnection>(connection));
}
sdp.addAttr(std::make_shared<SdpAttrGroup>(group));
sdp.addAttr(std::make_shared<SdpAttrExtmapAllowMixed>());
sdp.addAttr(std::make_shared<SdpAttrMsidSemantic>(msid_semantic));
bool ice_lite = false;
@ -1329,6 +1330,10 @@ void RtcMedia::checkValid() const {
// 非simulcast时检查有没有指定rtp ssrc [AUTO-TRANSLATED:e2d53f8a]
// When not simulcast, check if the RTP SSRC is specified
CHECK(!rtp_rtx_ssrc.empty() || !send_rtp);
for (auto ssrc : rtp_rtx_ssrc) {
InfoL << "ssrc:" << ssrc.cname << "," << ssrc.msid;
}
}
#if 0
@ -1573,6 +1578,26 @@ void RtcConfigure::enableREMB(bool enable, TrackType type) {
}
}
shared_ptr<RtcSession> RtcConfigure::createOffer() const {
shared_ptr<RtcSession> ret = std::make_shared<RtcSession>();
ret->version = 0;
ret->origin.session_id = std::to_string(makeRandNum());
ret->origin.session_version = std::to_string(1);
ret->session_name = "-";
createMediaOffer(ret);
// 设置音视频端口复用 [AUTO-TRANSLATED:ffe27d17]
// Set audio and video port multiplexing
for (auto &m : ret->media) {
// The remote end has rejected (port 0) the m-section, so it should not be putting its mid in the group attribute.
if (m.port) {
ret->group.mids.emplace_back(m.mid);
}
}
return ret;
}
shared_ptr<RtcSession> RtcConfigure::createAnswer(const RtcSession &offer) const {
shared_ptr<RtcSession> ret = std::make_shared<RtcSession>();
ret->version = offer.version;
@ -1634,6 +1659,156 @@ static DtlsRole mathDtlsRole(DtlsRole role) {
}
}
void RtcConfigure::createMediaOffer(const std::shared_ptr<RtcSession> &ret) const {
int index = 0;
if (video.direction != RtpDirection::sendonly || _rtsp_video_plan) {
createMediaOfferEach(ret, TrackVideo, index++);
}
if (audio.direction != RtpDirection::sendonly || _rtsp_audio_plan) {
createMediaOfferEach(ret, TrackAudio, index++);
}
// createMediaOfferEach(ret, TrackApplication, index++);
}
void RtcConfigure::createMediaOfferEach(const std::shared_ptr<RtcSession> &ret, TrackType type, int index) const {
// rtpmap
static std::multimap<CodecId, RtpMap::Ptr> audio_list_ref, video_list_ref;
static toolkit::onceToken token([]() {
audio_list_ref.emplace(CodecG711U, make_shared<AudioRtpMap>("PCMU", 0, 8000));
audio_list_ref.emplace(CodecG711A, make_shared<AudioRtpMap>("PCMA", 8, 8000));
audio_list_ref.emplace(CodecOpus, make_shared<AudioRtpMap>("opus", 111, 48000));
audio_list_ref.emplace(CodecAAC, make_shared<AudioRtpMap>("mpeg4-generic", 96, 48000));
video_list_ref.emplace(CodecH264, make_shared<H264RtpMap>(102, 90000, PROFILE_H264_BASELINE));
video_list_ref.emplace(CodecH264, make_shared<H264RtpMap>(104, 90000, PROFILE_H264_MAIN));
video_list_ref.emplace(CodecH264, make_shared<H264RtpMap>(106, 90000, PROFILE_H264_HIGH));
video_list_ref.emplace(CodecH265, make_shared<H265RtpMap>(120, 90000, PROFILE_H265_MAIN));
video_list_ref.emplace(CodecH265, make_shared<H265RtpMap>(124, 90000, PROFILE_H265_MAIN10));
video_list_ref.emplace(CodecH265, make_shared<H265RtpMap>(126, 90000, PROFILE_H265_SCREEN));
video_list_ref.emplace(CodecAV1, make_shared<AV1RtpMap>(35, 90000, 0));
video_list_ref.emplace(CodecVP8, make_shared<VideoRtpMap>("VP8", 96, 90000));
video_list_ref.emplace(CodecVP9, make_shared<VP9RtpMap>(98, 90000, 0));
video_list_ref.emplace(CodecVP9, make_shared<VP9RtpMap>(100, 90000, 2));
});
bool check_profile = true;
bool check_codec = true;
const RtcTrackConfigure *cfg_ptr = nullptr;
std::multimap<CodecId, RtpMap::Ptr>* rtpMap = nullptr;
switch (type) {
case TrackAudio: cfg_ptr = &audio; rtpMap = &audio_list_ref; break;
case TrackVideo: cfg_ptr = &video; rtpMap = &video_list_ref; break;
case TrackApplication: cfg_ptr = &application; break;
default: return;
}
auto &configure = *cfg_ptr;
if (type == TrackApplication) {
RtcMedia media;
media.role = DtlsRole::active;
media.ice_ufrag = configure.ice_ufrag;
media.ice_pwd = configure.ice_pwd;
media.fingerprint = configure.fingerprint;
// media.ice_lite = configure.ice_lite;
media.ice_lite = false;
#ifdef ENABLE_SCTP
media.candidate = configure.candidate;
#else
media.port = 9; //占位符,表示后续协商分配
WarnL << "answer sdp忽略application mline, 请安装usrsctp后再测试datachannel功能";
#endif
ret->media.emplace_back(media);
return;
}
RtcMedia media;
media.type = type;
media.mid = to_string(index);
media.proto = "UDP/TLS/RTP/SAVPF";
media.port = 9;//占位符,表示后续协商分配
// media.addr = ;
// media.bandwidth = ;
// media.rtcp_addr = ;
media.rtcp_mux = true;
media.rtcp_rsize = true;
media.ice_trickle = true;
media.ice_renomination = configure.ice_renomination;
media.ice_ufrag = configure.ice_ufrag;
media.ice_pwd = configure.ice_pwd;
media.fingerprint = configure.fingerprint;
// media.ice_lite = configure.ice_lite;
media.ice_lite = false;
// candidate offer不生成candidate反正也是错的
// media.candidate = configure.candidate;
// copy simulicast setting
// media.rtp_rids =;
// media.rtp_ssrc_sim = ;
media.role = DtlsRole::active;
// 如果codec匹配失败那么禁用该track [AUTO-TRANSLATED:037de9a8]
// If the codec matching fails, then disable the track
media.direction = configure.direction;
//extmap
for (auto extmap : cfg_ptr->extmap) {
#if 0
if (extmap.second != media.direction) {
continue;
}
#endif
SdpAttrExtmap attrExtmap;
attrExtmap.direction = extmap.second;
attrExtmap.id = (uint8_t)extmap.first;
attrExtmap.ext = RtpExt::getExtUrl(extmap.first);
media.extmap.push_back(attrExtmap);
}
//rtpmap
for (auto codec : cfg_ptr->preferred_codec) {
if (!rtpMap) continue;
auto range = rtpMap->equal_range(codec);
for (auto it = range.first; it != range.second; ++it) {
auto rtpmap = it->second;
RtcCodecPlan plan;
plan.codec = rtpmap->getCodeName();
plan.pt = rtpmap->getPayload();
plan.sample_rate = rtpmap->getClockRate();
plan.rtcp_fb = cfg_ptr->rtcp_fb;
auto fmtp = rtpmap->getFmtp();
for (const auto& pair : fmtp) {
plan.fmtp.emplace(pair);
}
media.plan.push_back(plan);
// add video rtx plan
if (rtpmap->getType() == TrackVideo) {
// a=rtpmap:108 rtx/90000
// a=fmtp:108 apt=107
RtcCodecPlan rtx;
rtx.codec = "rtx";
rtx.pt = rtpmap->getPayload() + 1;
rtx.sample_rate = rtpmap->getClockRate();
rtx.fmtp["apt"] = std::to_string(rtpmap->getPayload());
media.plan.push_back(rtx);
}
}
}
//msid
if (media.direction != RtpDirection::recvonly) {
RtcSSRC ssrc;
ssrc.ssrc = (uint32_t)makeRandNum();
ssrc.rtx_ssrc = (uint32_t)makeRandNum();
ssrc.cname = makeRandStr(16);
ssrc.msid = makeRandStr(36) + " " + makeUuidStr();
media.rtp_rtx_ssrc.push_back(ssrc);
}
ret->media.emplace_back(media);
}
void RtcConfigure::matchMedia(const std::shared_ptr<RtcSession> &ret, const RtcMedia &offer_media) const {
bool check_profile = true;
bool check_codec = true;

View File

@ -16,7 +16,7 @@
#include <string>
#include <vector>
#include "RtpExt.h"
#include "assert.h"
#include "RtpMap.h"
#include "Extension/Frame.h"
#include "Common/Parser.h"
@ -744,6 +744,7 @@ public:
void setDefaultSetting(std::string ice_ufrag, std::string ice_pwd, RtpDirection direction, const SdpAttrFingerprint &fingerprint);
void addCandidate(const SdpAttrCandidate &candidate, TrackType type = TrackInvalid);
std::shared_ptr<RtcSession> createOffer() const;
std::shared_ptr<RtcSession> createAnswer(const RtcSession &offer) const;
void setPlayRtspInfo(const std::string &sdp);
@ -752,6 +753,8 @@ public:
void enableREMB(bool enable = true, TrackType type = TrackInvalid);
private:
void createMediaOffer(const std::shared_ptr<RtcSession> &ret) const;
void createMediaOfferEach(const std::shared_ptr<RtcSession> &ret, TrackType type, int index) const;
void matchMedia(const std::shared_ptr<RtcSession> &ret, const RtcMedia &media) const;
bool onCheckCodecProfile(const RtcCodecPlan &plan, CodecId codec) const;
void onSelectPlan(RtcCodecPlan &plan, CodecId codec) const;

View File

@ -19,7 +19,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#ifndef MS_RTC_SRTP_SESSION_HPP
#define MS_RTC_SRTP_SESSION_HPP
#include "Utils.hpp"
#include "Util/Byte.hpp"
#include <memory>
@ -31,6 +31,7 @@ class DepLibSRTP;
class SrtpSession {
public:
using Ptr = std::shared_ptr<SrtpSession>;
enum class CryptoSuite {
NONE = 0,
AES_CM_128_HMAC_SHA1_80 = 1,

File diff suppressed because it is too large Load Diff

View File

@ -1,37 +1,481 @@
/**
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.
/*
* 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 MS_RTC_STUN_PACKET_HPP
#define MS_RTC_STUN_PACKET_HPP
#ifndef ZLMEDIAKIT_WEBRTC_STUN_PACKET_HPP
#define ZLMEDIAKIT_WEBRTC_STUN_PACKET_HPP
#include "logger.h"
#include "Utils.hpp"
#include <string>
#include "Util/Byte.hpp"
#include "Network/Buffer.h"
#include "Network/sockutil.h"
namespace RTC
{
class StunPacket
{
namespace RTC {
// reference https://rcf-editor.org/rfc/rfc8489
// reference https://rcf-editor.org/rfc/rfc8656
// reference https://rcf-editor.org/rfc/rfc8445
//////////// Attribute //////////////////////////
// reference https://rcf-editor.org/rfc/rfc8489
/*
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Value (variable) ....
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 4: Format of STUN Attributes
reference https://www.rfc-editor.org/rfc/rfc8489.html#section-14
*/
class StunAttribute {
public:
// Attribute type.
enum class Type : uint16_t {
MAPPED_ADDRESS = 0x0001,
RESPONSE_ADDRESS = 0x0002, // Reserved; was RESPONSE-ADDRESS prior to [RFC5389]
CHANGE_REQUEST = 0x0003, // Reserved; was CHANGE-REQUEST prior to [RFC5389]
CHANGED_ADDRESS = 0x0005, // Reserved; was CHANGED-ADDRESS prior to [RFC5389]
USERNAME = 0x0006,
PASSWORD = 0x0005, // Reserved; was PASSWORD prior to [RFC5389]
MESSAGE_INTEGRITY = 0x0008,
ERROR_CODE = 0x0009,
UNKNOWN_ATTRIBUTES = 0x000A,
REFLECTED_FROM = 0x000B, // Reserved; was REFLECTED-FROM prior to [RFC5389]
CHANNEL_NUMBER = 0x000C, // [RFC5766]
LIFETIME = 0x000D, // [RFC5766]
BANDWIDTH = 0x0010, // Reserved; [RFC5766]
XOR_PEER_ADDRESS = 0x0012, // [RFC5766]
DATA = 0x0013, // [RFC5766]
REALM = 0x0014,
NONCE = 0x0015,
XOR_RELAYED_ADDRESS = 0x0016, // [RFC5766]
EVEN_PORT = 0x0018, // [RFC5766]
REQUESTED_TRANSPORT = 0x0019, // [RFC5766]
DONT_FRAGMENT = 0x001A, // [RFC5766]
MESSAGE_INTEGRITY_SHA256 = 0x001C,
USERHASH = 0x001E,
PASSWORD_ALGORITHM = 0x001D,
XOR_MAPPED_ADDRESS = 0x0020,
TIMER_VAL = 0x0021, // Reserved; [RFC5766]
RESERVATION_TOKEN = 0x0022, // [RFC5766]
PRIORITY = 0x0024,
USE_CANDIDATE = 0x0025,
//Comprehension-optional range (0x8000-0xFFFF)
PASSWORD_ALGORITHMS = 0x8002,
ALTERNATE_DOMAIN = 0x8003,
SOFTWARE = 0x8022,
ALTERNATE_SERVER = 0x8023,
FINGERPRINT = 0x8028,
ICE_CONTROLLED = 0x8029,
ICE_CONTROLLING = 0x802A,
GOOG_NETWORK_INFO = 0xC057,
};
static const size_t ATTR_HEADER_SIZE = 4;
static bool isComprehensionRequired(const uint8_t *data, size_t len);
using Ptr = std::shared_ptr<StunAttribute>;
StunAttribute(StunAttribute::Type type) : _type(type) {}
virtual ~StunAttribute() = default;
char *data() { return _data ? _data->data() : nullptr; }
char *body() { return _data ? _data->data() + ATTR_HEADER_SIZE : nullptr; }
size_t size() const { return _data ? _data->size() : 0; }
Type type() const { return _type; }
virtual bool loadFromData(const uint8_t *buf, size_t len) = 0;
virtual bool storeToData() = 0;
// virtual std::string dump() = 0;
protected:
const uint8_t * loadHeader(const uint8_t *buf);
uint8_t * storeHeader();
protected:
Type _type;
uint16_t _length;
toolkit::BufferRaw::Ptr _data;
};
/*
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0| Family | Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Address (32 bits or 128 bits) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 5: Format of MAPPED-ADDRESS Attribute
reference https://www.rfc-editor.org/rfc/rfc8489.html#page-37
*/
class StunAttrMappedAddress : public StunAttribute {
public:
using Ptr = std::shared_ptr<StunAttrMappedAddress>;
static constexpr Type TYPE = StunAttribute::Type::MAPPED_ADDRESS;
StunAttrMappedAddress() : StunAttribute(TYPE) {};
virtual ~StunAttrMappedAddress() = default;
bool loadFromData(const uint8_t *buf, size_t len) override;
bool storeToData() override;
// std::string dump() override;
};
class StunAttrUserName : public StunAttribute {
public:
using Ptr = std::shared_ptr<StunAttrUserName>;
static constexpr Type TYPE = StunAttribute::Type::USERNAME;
StunAttrUserName() : StunAttribute(TYPE) {};
virtual ~StunAttrUserName() = default;
bool loadFromData(const uint8_t *buf, size_t len) override;
bool storeToData() override;
// std::string dump() override;
void setUsername(std::string username) { _username = std::move(username); }
const std::string& getUsername() const { return _username; }
private:
std::string _username;
};
class StunAttrMessageIntegrity : public StunAttribute {
public:
using Ptr = std::shared_ptr<StunAttrMessageIntegrity>;
static constexpr Type TYPE = StunAttribute::Type::MESSAGE_INTEGRITY;
StunAttrMessageIntegrity() : StunAttribute(TYPE) {};
virtual ~StunAttrMessageIntegrity() = default;
bool loadFromData(const uint8_t *buf, size_t len) override;
bool storeToData() override;
// std::string dump() override;
void setHmac(std::string hmac) { _hmac = std::move(hmac); }
const std::string &getHmac() const { return _hmac; }
private:
std::string _hmac;
};
class StunAttrErrorCode : public StunAttribute {
public:
using Ptr = std::shared_ptr<StunAttrErrorCode>;
static constexpr Type TYPE = StunAttribute::Type::ERROR_CODE;
StunAttrErrorCode() : StunAttribute(TYPE) {};
virtual ~StunAttrErrorCode() = default;
enum class Code : uint16_t {
Invalid = 0, //
TryAlternate = 300, //尝试备用服务器
BadRequest = 400,
Unauthorized = 401,
Forbidden = 403, //禁止
RequestTimedOut = 408, //请求超时(客户端认为此事务已经失败)
UnknownAttribute = 420,
AllocationMismatch = 438,
StaleNonce = 438, //NONCE 不再有效,客户端应使用响应中的NONCE重试
AddressFamilyNotSupported = 440, //不支持的协议簇
WrongCredentials = 441, //凭据错误
UnsupportedTransportAddress = 442, //不支持的传输地址
AllocationQuotaReached = 486, //alloction 达到上限,客户端应该至少等待一分钟后重新尝试创建
RoleConflict = 487, //角色冲突
ServerError = 500, //服务器临时错误,客户端应重试
InsuficientCapacity = 508, //容量不足,没有更多可用的中继传输地址
};
bool loadFromData(const uint8_t *buf, size_t len) override;
bool storeToData() override;
// std::string dump() override;
void setErrorCode(Code error_code) { _error_code = error_code; }
Code getErrorCode() const { return _error_code; }
private:
Code _error_code;
};
class StunAttrChannelNumber : public StunAttribute {
public:
using Ptr = std::shared_ptr<StunAttrChannelNumber>;
static constexpr Type TYPE = StunAttribute::Type::CHANNEL_NUMBER;
StunAttrChannelNumber() : StunAttribute(TYPE) {};
virtual ~StunAttrChannelNumber() = default;
bool loadFromData(const uint8_t *buf, size_t len) override;
bool storeToData() override;
void setChannelNumber(uint16_t channel_number) { _channel_number = channel_number; }
uint16_t getChannelNumber() const { return _channel_number; }
private:
uint16_t _channel_number;
};
class StunAttrLifeTime : public StunAttribute {
public:
using Ptr = std::shared_ptr<StunAttrLifeTime>;
static constexpr Type TYPE = StunAttribute::Type::LIFETIME;
StunAttrLifeTime() : StunAttribute(TYPE) {};
~StunAttrLifeTime() = default;
bool loadFromData(const uint8_t *buf, size_t len) override;
bool storeToData() override;
// std::string dump() override;
void setLifetime(uint32_t lifetime) { _lifetime = lifetime; }
uint32_t getLifetime() const { return _lifetime; }
private:
uint32_t _lifetime;
};
/*
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 0| Family | X-Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| X-Address (Variable)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 6: Format of XOR-MAPPED-ADDRESS Attribute
reference https://www.rfc-editor.org/rfc/rfc8489.html#page-38
*/
class StunAttrXorPeerAddress : public StunAttribute {
public:
using Ptr = std::shared_ptr<StunAttrXorPeerAddress>;
static constexpr Type TYPE = StunAttribute::Type::XOR_PEER_ADDRESS;
StunAttrXorPeerAddress(std::string transaction_id)
: StunAttribute(TYPE)
, _transaction_id(std::move(transaction_id)) {}
virtual ~StunAttrXorPeerAddress() = default;
bool loadFromData(const uint8_t *buf, size_t len) override;
bool storeToData() override;
// std::string dump() override;
void setAddr(const struct sockaddr_storage &addr) { _addr = addr; }
const struct sockaddr_storage& getAddr() const { return _addr; }
std::string getIp() const { return toolkit::SockUtil::inet_ntoa((struct sockaddr *)&_addr); }
uint16_t getPort() const { return toolkit::SockUtil::inet_port((struct sockaddr *)&_addr); }
protected:
struct sockaddr_storage _addr;
std::string _transaction_id;
};
class StunAttrData : public StunAttribute {
public:
using Ptr = std::shared_ptr<StunAttrData>;
static constexpr Type TYPE = StunAttribute::Type::DATA;
StunAttrData() : StunAttribute(TYPE) {};
virtual ~StunAttrData() = default;
bool loadFromData(const uint8_t *buf, size_t len) override;
bool storeToData() override;
void setData(std::string data) { _data_content = std::move(data); }
void setData(const char *data, int size) { _data_content.assign(data, size); }
const std::string &getData() const { return _data_content; }
private:
std::string _data_content;
};
class StunAttrRealm : public StunAttribute {
public:
using Ptr = std::shared_ptr<StunAttrRealm>;
static constexpr Type TYPE = StunAttribute::Type::REALM;
StunAttrRealm() : StunAttribute(TYPE) {};
virtual ~StunAttrRealm() = default;
bool loadFromData(const uint8_t *buf, size_t len) override;
bool storeToData() override;
// std::string dump() override;
void setRealm(std::string realm) { _realm = std::move(realm); }
const std::string &getRealm() const { return _realm; }
private:
// 长度小于128字符
std::string _realm;
};
class StunAttrNonce : public StunAttribute {
public:
using Ptr = std::shared_ptr<StunAttrNonce>;
static constexpr Type TYPE = StunAttribute::Type::NONCE;
StunAttrNonce() : StunAttribute(TYPE) {};
virtual ~StunAttrNonce() = default;
bool loadFromData(const uint8_t *buf, size_t len) override;
bool storeToData() override;
// std::string dump() override;
void setNonce(std::string nonce) { _nonce = std::move(nonce); }
const std::string& getNonce() const { return _nonce; }
private:
// 长度小于128字符
std::string _nonce;
};
class StunAttrXorRelayedAddress : public StunAttrXorPeerAddress {
public:
using Ptr = std::shared_ptr<StunAttrXorRelayedAddress>;
static constexpr Type TYPE = StunAttribute::Type::XOR_RELAYED_ADDRESS;
StunAttrXorRelayedAddress(std::string transaction_id) : StunAttrXorPeerAddress(std::move(transaction_id)) {
_type = TYPE;
}
virtual ~StunAttrXorRelayedAddress() = default;
};
class StunAttrXorMappedAddress : public StunAttrXorPeerAddress {
public:
using Ptr = std::shared_ptr<StunAttrXorPeerAddress>;
static constexpr Type TYPE = StunAttribute::Type::XOR_MAPPED_ADDRESS;
StunAttrXorMappedAddress(std::string transaction_id) : StunAttrXorPeerAddress(std::move(transaction_id)) {
_type = TYPE;
}
virtual ~StunAttrXorMappedAddress() = default;
};
/*
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Protocol | RFFU |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
reference https://www.rfc-editor.org/rfc/rfc5766.html#section-14.7
*/
class StunAttrRequestedTransport : public StunAttribute {
public:
using Ptr = std::shared_ptr<StunAttrRequestedTransport>;
static constexpr Type TYPE = StunAttribute::Type::REQUESTED_TRANSPORT;
StunAttrRequestedTransport() : StunAttribute(TYPE) {};
virtual ~StunAttrRequestedTransport() = default;
bool loadFromData(const uint8_t *buf, size_t len) override;
bool storeToData() override;
// std::string dump() override;
enum class Protocol : uint8_t {
// This specification only allows the use of codepoint 17 (User Datagram Protocol).
UDP = 0x11,
};
void setProtocol(Protocol protocol) { _protocol = protocol; }
Protocol getProtocol() const { return _protocol; }
private:
Protocol _protocol = Protocol::UDP;
};
class StunAttrPriority : public StunAttribute {
public:
using Ptr = std::shared_ptr<StunAttrPriority>;
static constexpr Type TYPE = StunAttribute::Type::PRIORITY;
StunAttrPriority() : StunAttribute(TYPE) {};
virtual ~StunAttrPriority() = default;
bool loadFromData(const uint8_t *buf, size_t len) override;
bool storeToData() override;
// std::string dump() override;
void setPriority(uint64_t priority) { _priority = priority; }
uint64_t getPriority() const { return _priority; }
private:
uint32_t _priority;
};
class StunAttrUseCandidate : public StunAttribute {
public:
using Ptr = std::shared_ptr<StunAttrUseCandidate>;
static constexpr Type TYPE = StunAttribute::Type::USE_CANDIDATE;
StunAttrUseCandidate() : StunAttribute(TYPE) {};
virtual ~StunAttrUseCandidate() = default;
bool loadFromData(const uint8_t *buf, size_t len) override;
bool storeToData() override;
// std::string dump() override;
};
class StunAttrFingerprint : public StunAttribute {
public:
using Ptr = std::shared_ptr<StunAttrFingerprint>;
static constexpr Type TYPE = StunAttribute::Type::FINGERPRINT;
StunAttrFingerprint() : StunAttribute(TYPE) {};
virtual ~StunAttrFingerprint() = default;
bool loadFromData(const uint8_t *buf, size_t len) override;
bool storeToData() override;
// std::string dump() override;
void setFingerprint(uint32_t fingerprint) { _fingerprint = fingerprint; }
uint32_t getFingerprint() const { return _fingerprint; }
private:
uint32_t _fingerprint;
};
class StunAttrIceControlled : public StunAttribute {
public:
using Ptr = std::shared_ptr<StunAttrIceControlled>;
static constexpr Type TYPE = StunAttribute::Type::ICE_CONTROLLED;
StunAttrIceControlled() : StunAttribute(TYPE) {};
virtual ~StunAttrIceControlled() = default;
bool loadFromData(const uint8_t *buf, size_t len) override;
bool storeToData() override;
// std::string dump() override;
void setTiebreaker(uint64_t tiebreaker) { _tiebreaker = tiebreaker; }
uint64_t getTiebreaker() const { return _tiebreaker; }
private:
uint64_t _tiebreaker = 0; // 8 bytes unsigned integer.
};
class StunAttrIceControlling : public StunAttribute {
public:
using Ptr = std::shared_ptr<StunAttrIceControlling>;
static constexpr Type TYPE = StunAttribute::Type::ICE_CONTROLLING;
StunAttrIceControlling() : StunAttribute(TYPE) {};
virtual ~StunAttrIceControlling() = default;
bool loadFromData(const uint8_t *buf, size_t len) override;
bool storeToData() override;
// std::string dump() override;
void setTiebreaker(uint64_t tiebreaker) { _tiebreaker = tiebreaker; }
uint64_t getTiebreaker() const { return _tiebreaker; }
private:
uint64_t _tiebreaker = 0; // 8 bytes unsigned integer.
};
//////////// STUN //////////////////////////
/*
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0| STUN Message Type | Message Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Magic Cookie |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Transaction ID (96 bits) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 2: Format of STUN Message Header
reference https://www.rfc-editor.org/rfc/rfc8489.html#section-5 */
class StunPacket : public toolkit::Buffer {
public:
using Ptr = std::shared_ptr<StunPacket>;
// STUN message class.
enum class Class : uint16_t
{
enum class Class : uint8_t {
REQUEST = 0,
INDICATION = 1,
SUCCESS_RESPONSE = 2,
@ -39,175 +483,207 @@ namespace RTC
};
// STUN message method.
enum class Method : uint16_t
{
BINDING = 1
};
enum class Method : uint16_t {
BINDING = 0x001,
// Attribute type.
enum class Attribute : uint16_t
{
MAPPED_ADDRESS = 0x0001,
USERNAME = 0x0006,
MESSAGE_INTEGRITY = 0x0008,
ERROR_CODE = 0x0009,
UNKNOWN_ATTRIBUTES = 0x000A,
REALM = 0x0014,
NONCE = 0x0015,
XOR_MAPPED_ADDRESS = 0x0020,
PRIORITY = 0x0024,
USE_CANDIDATE = 0x0025,
SOFTWARE = 0x8022,
ALTERNATE_SERVER = 0x8023,
FINGERPRINT = 0x8028,
ICE_CONTROLLED = 0x8029,
ICE_CONTROLLING = 0x802A
//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
{
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
struct EnumClassHash {
template <typename T>
std::size_t operator()(T t) const {
return static_cast<std::size_t>(t);
}
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;
};
struct ClassMethodHash {
bool operator()(std::pair<StunPacket::Class, StunPacket::Method> key) const {
std::size_t h = 0;
h ^= std::hash<uint8_t>()((uint8_t)key.first) << 1;
h ^= std::hash<uint8_t>()((uint8_t)key.second) << 2;
return h;
}
};
static const size_t HEADER_SIZE = 20;
static const uint8_t _magicCookie[];
static bool isStun(const uint8_t *data, size_t len);
static Class getClass(const uint8_t *data, size_t len);
static Method getMethod(const uint8_t *data, size_t len);
static StunPacket::Ptr parse(const uint8_t *data, size_t len);
static std::string mappingClassEnum2Str(Class klass);
static std::string mappingMethodEnum2Str(Method method);
StunPacket(Class klass, Method method, const char* transId = nullptr);
virtual ~StunPacket();
Class getClass() const { return _klass; }
Method getMethod() const { return _method; }
std::string getClassStr() const { return StrPrinter << mappingClassEnum2Str(_klass) << "(" << (uint32_t)_klass << ")"; }
std::string getMethodStr() const { return StrPrinter << mappingMethodEnum2Str(_method) << "(" << (uint32_t)_method << ")"; }
std::string dumpString(bool transId = false) const;
const std::string& getTransactionId() const { return _transaction_id; }
void setUfrag(std::string ufrag) { _ufrag = std::move(ufrag); }
const std::string& getUfrag() const { return _ufrag; }
void setPassword(std::string password) { _password = std::move(password); }
const std::string& getPassword() const { return _password; }
void setPeerUfrag(std::string peer_ufrag) { _peer_ufrag = std::move(peer_ufrag); }
const std::string& getPeerUfrag() const { return _peer_ufrag; }
void setPeerPassword(std::string peer_password) { _peer_password = std::move(peer_password); }
const std::string& getPeerPassword() const { return _peer_password; }
void setNeedMessageIntegrity(bool flag) { _need_message_integrity = flag; }
bool getNeedMessageIntegrity() const { return _need_message_integrity; }
void setNeedFingerprint(bool flag) { _need_fingerprint = flag; }
bool getNeedFingerprint() const { return _need_fingerprint; }
void refreshTransactionId() { _transaction_id = toolkit::makeRandStr(12, false); }
void addAttribute(StunAttribute::Ptr attr);
void removeAttribute(StunAttribute::Type type);
bool hasAttribute(StunAttribute::Type type) const;
StunAttribute::Ptr getAttribute(StunAttribute::Type type) const;
template <typename T>
std::shared_ptr<T> getAttribute() const {
auto attr = getAttribute(T::TYPE);
if (attr) {
return std::dynamic_pointer_cast<T>(attr);
}
return nullptr;
}
std::string getUsername() const;
uint64_t getPriority() const;
StunAttrErrorCode::Code getErrorCode() const;
Authentication checkAuthentication(const std::string &ufrag, const std::string &password) const;
void serialize();
StunPacket::Ptr createSuccessResponse() const;
StunPacket::Ptr createErrorResponse(StunAttrErrorCode::Code errorCode) const;
///////Buffer override///////
char *data() const override;
size_t size() const override;
private:
bool loadFromData(const uint8_t *buf, size_t len);
// attribute
bool loadAttrMessage(const uint8_t *buf, size_t len);
bool storeAttrMessage();
size_t getAttrSize() const;
protected:
Class _klass;
Method _method;
std::string _transaction_id; // 12 bytes/96bits.
std::map<StunAttribute::Type, StunAttribute::Ptr> _attribute_map;
toolkit::BufferRaw::Ptr _data;
std::string _ufrag;
std::string _password;
std::string _peer_ufrag;
std::string _peer_password;
size_t _message_integrity_data_len = 0; //MESSAGE_INTEGRITY属性之前的字段
bool _need_message_integrity = true;
bool _need_fingerprint = true;
};
class BindingPacket : public StunPacket {
public:
BindingPacket() : StunPacket(Class::REQUEST, Method::BINDING) {};
virtual ~BindingPacket() {};
};
class SuccessResponsePacket : public StunPacket {
public:
SuccessResponsePacket(Method method, const std::string& transaction_id);
virtual ~SuccessResponsePacket() {};
};
class ErrorResponsePacket : public StunPacket {
public:
ErrorResponsePacket(Method method, const std::string& transaction_id, StunAttrErrorCode::Code error_code);
virtual ~ErrorResponsePacket() {};
};
//////////// TURN //////////////////////////
class TurnPacket : public StunPacket {
public:
TurnPacket(Class klass, Method method) : StunPacket(klass, method) {}
virtual ~TurnPacket() {};
};
class AllocatePacket : public TurnPacket {
public:
AllocatePacket() : TurnPacket(Class::REQUEST, Method::ALLOCATE) {};
virtual ~AllocatePacket() {};
};
class RefreshPacket : public TurnPacket {
public:
RefreshPacket() : TurnPacket(Class::REQUEST, Method::REFRESH) {};
virtual ~RefreshPacket() {};
};
class CreatePermissionPacket : public TurnPacket {
public:
CreatePermissionPacket() : TurnPacket(Class::REQUEST, Method::CREATEPERMISSION) {};
virtual ~CreatePermissionPacket() {};
};
class ChannelBindPacket : public TurnPacket {
public:
ChannelBindPacket() : TurnPacket(Class::REQUEST, Method::CHANNELBIND) {};
virtual ~ChannelBindPacket() {};
};
class SendIndicationPacket : public TurnPacket {
public:
SendIndicationPacket() : TurnPacket(Class::INDICATION, Method::SEND) {};
virtual ~SendIndicationPacket() {};
};
class DataIndicationPacket : public TurnPacket {
public:
DataIndicationPacket() : TurnPacket(Class::INDICATION, Method::DATA) {};
virtual ~DataIndicationPacket() {};
};
class DataPacket : public TurnPacket {
public:
DataPacket() : TurnPacket(Class::INDICATION, Method::DATA) {};
virtual ~DataPacket() {};
};
} // namespace RTC
#endif

256
webrtc/USAGE.md Normal file
View File

@ -0,0 +1,256 @@
# WebRTC 使用说明
## WebRTC 架构
### 1. SFU 模式架构 (WHIP/WHEP)
SFU 模式通过服务器中继媒体流,支持多路复用和转码:
```
WebRTC SFU 模式 (WHIP/WHEP)
推流端 (WHIP) 拉流端 (WHEP)
+----------------+ +-----------------+
| Encoder | | Player |
| (Browser/ZLM) | | (Browser/ZLM) |
+----------------+ +-----------------+
| |
| WHIP Protocol | WHEP Protocol
| (WebRTC ingest) | (WebRTC playback)
| |
v v
+-------------------------------------------------------------------+
| ZLMediaKit Server |
+-------------------------------------------------------------------+
- WHIP: WebRTC-HTTP Ingestion Protocol (推流)
- WHEP: WebRTC-HTTP Egress Protocol (拉流)
```
### 2. P2P 模式架构
P2P 模式允许客户端之间直接建立连接,减少服务器负载:
基于Websocket的自定义信令协议
```
WebRTt WC P2P 模式
客户端 A 客户端 B
+------------+ +-------------+
| Browser/ZLM| | Browser/ZLM |
+------------+ +-------------+
| |
| 1. 信令交换 (SDP Offer/Answer) |
| 2. ICE Candidate 交换 |
+---------------- -----+-----------------------+
| | |
| +-----------------------+ |
| | ZLMediaKit Server | |
| | 信令服务器 (WebSocket) | |
| | STUN 服务器 | |
| | TURN 服务器 | |
| +-----------------------+ |
| |
+-----------------------------------------------+
直接P2P连接
```
## HTTP API 接口
### 1. WebRTC 房间管理
#### `/index/api/addWebrtcRoomKeeper`
添加WebRTC到指定信令服务器用于在信令服务器中维持房间连接。
**请求参数:**
- `secret`: 接口访问密钥
- `server_host`: 信令服务器主机地址
- `server_port`: 信令服务器端口
- `room_id`: 房间ID信令服务器会对该ID进行唯一性检查
#### `/index/api/delWebrtcRoomKeeper`
删除指定的信令服务器。
**请求参数:**
- `secret`: 接口访问密钥
- `room_key`: 房间保持器的唯一标识符
#### `/index/api/listWebrtcRoomKeepers`
列出所有信令服务器。
**请求参数:**
- `secret`: 接口访问密钥
### 2. WebRTC 房间会话管理
#### `/index/api/listWebrtcRooms`
列出所有活跃的WebRTC Peer会话信息。
**请求参数:**
- `secret`: 接口访问密钥
### 3. WebRTC 推流和拉流接口
ZLMediaKit 支持通过标准的流代理接口来创建WebRTC推流和拉流支持两种信令模式
##### `/index/api/addStreamProxy` - WebRTC 拉流代理
通过此接口可以创建WebRTC拉流代理支持两种信令协议模式。
**请求参数:**
- `secret`: 接口访问密钥
- `vhost`: 虚拟主机名,默认为 `__defaultVhost__`
- `app`: 应用名
- `stream`: 流ID
- `url`: WebRTC源URL支持两种格式
**WebRTC URL 格式:**
1. **WHIP/WHEP 模式 (SFU)** - 标准HTTP信令协议:
```
# HTTP
webrtc://server_host:server_port/app/stream_id?signaling_protocols=0
# HTTPS (暂未实现)
webrtcs://server_host:server_port/app/stream_id?signaling_protocols=0
```
2. **WebSocket P2P 模式** - 自定义信令协议:
```
# WebSocket
webrtc://signaling_server_host:signaling_server_port/app/stream_id?signaling_protocols=1&peer_room_id=target_room_id
# WebSocket Secure (暂未实现)
webrtcs://signaling_server_host:signaling_server_port/app/stream_id?signaling_protocols=1&peer_room_id=target_room_id
```
**请求示例:**
```bash
# WHIP/WHEP 模式拉流
curl -X POST "http://127.0.0.1/index/api/addStreamProxy" \
-d "secret=your_secret" \
-d "vhost=__defaultVhost__" \
-d "app=live" \
-d "stream=test" \
-d "url=webrtc://source.server.com:80/live/source_stream?signaling_protocols=0"
# P2P 模式拉流
curl -X POST "http://127.0.0.1/index/api/addStreamProxy" \
-d "secret=your_secret" \
-d "vhost=__defaultVhost__" \
-d "app=live" \
-d "stream=test" \
-d "url=webrtc://signaling.server.com:3000/live/source_stream??signaling_protocols=1%26peer_room_id=target_room_id"
```
#### `/index/api/addStreamPusherProxy` - WebRTC 推流代理 (暂未实现)
通过此接口可以创建WebRTC推流代理将现有流推送到WebRTC目标服务器。
**请求参数:**
- `secret`: 接口访问密钥
- `schema`: 源流协议 (如: rtmp, rtsp, hls等)
- `vhost`: 虚拟主机名
- `app`: 应用名
- `stream`: 源流ID
- `dst_url`: WebRTC目标推流URL
**WebRTC 推流 URL 格式:**
1. **WHIP 模式 (SFU)** - 推流到支持WHIP的服务器:
```
# HTTP
webrtc://target_server:port/app/stream_id?signaling_protocols=0
# HTTPS (暂未实现)
webrtcs://target_server:port/app/stream_id?signaling_protocols=0
```
2. **WebSocket P2P 模式** - 推流到P2P房间
```
# WebSocket
webrtc://signaling_server:port/app/stream_id?signaling_protocols=1&peer_room_id=target_room
# WebSocket Secure
webrtcs://signaling_server:port/app/stream_id?signaling_protocols=1&peer_room_id=target_room
```
**请求示例:**
```bash
# 将RTSP流推送到WHIP服务器
curl -X POST "http://127.0.0.1/index/api/addStreamPusherProxy" \
-d "secret=your_secret" \
-d "schema=rtsp" \
-d "vhost=__defaultVhost__" \
-d "app=live" \
-d "stream=test" \
-d "dst_url=webrtc://target.server.com:80/live/target_stream?signaling_protocols=0"
# 将RTSP流推送到P2P房间
curl -X POST "http://127.0.0.1/index/api/addStreamPusherProxy" \
-d "secret=your_secret" \
-d "schema=rtsp" \
-d "vhost=__defaultVhost__" \
-d "app=live" \
-d "stream=test" \
-d "dst_url=webrtc://signaling.server.com:3000/live/room_stream?signaling_protocols=1%26peer_room_id=target_room_id"
```
#### URL 参数说明
- `signaling_protocols`: 信令协议类型
- `0`: WHIP/WHEP 模式(默认)
- **协议**: 基于HTTP的标准WebRTC信令协议
- **应用场景**: SFU选择性转发单元模式适合广播和多人会议
- `1`: WebSocket P2P 模式
- **协议**: 基于WebSocket的自定义信令协议
- **应用场景**: 点对点直连,适合低延迟通话和私人通信
- `peer_room_id`: P2P模式下的目标房间ID仅P2P模式需要
### 4. WebRTC 代理播放器信息查询
#### `/index/api/getWebrtcProxyPlayerInfo`
获取WebRTC代理播放器的连接信息和状态。
**请求参数:**
- `secret`: 接口访问密钥
- `key`: 代理播放器标识符
## WebRTC 相关配置项
`config.ini` 中的 `[rtc]` 配置段:
``` ini
[rtc]
#webrtc 信令服务器端口
signalingPort=3000
#STUN/TURN服务器端口
icePort=3478
#STUN/TURN端口是否使能TURN服务
enableTurn=1
#TURN服务分配端口池
portRange=50000-65000
#ICE传输策略0=不限制(默认)1=仅支持Relay转发2=仅支持P2P直连
iceTransportPolicy=0
#STUN/TURN 服务Ice密码
iceUfrag=ZLMediaKit
icePwd=ZLMediaKit
```
## Examples
- [zlm_peerconnection](https://gitee.com/libwebrtc_develop/libwebrtc/tree/feature-zlm/examples/zlm_peerconnection)
一个基于libwebrtc 实现的zlm p2p 代理拉流简单示例
## 注意事项
1. **防火墙配置**: 确保 WebRTC 相关端口已开放
- 信令端口: 3000 (默认)
- STUN/TURN 端口: 3478 (默认)
- TURN Alloc 端口范围: 50000-65000(默认)
## 暂未实现的功能:
- Webrtc信令服务的安全校验
- 自定义外部STUN/TURN 服务器的配置
- webrtc代理推流

View File

@ -1,118 +0,0 @@
/**
ISC License
Copyright © 2015, Iñaki Baz Castillo <ibc@aliax.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef MS_UTILS_HPP
#define MS_UTILS_HPP
#if defined(_WIN32)
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment (lib, "Ws2_32.lib")
#else
#include <arpa/inet.h>
#endif // defined(_WIN32)
#include <cinttypes>// PRIu64, etc
#include <cstddef>// size_t
#include <cstdint>// uint8_t, etc
namespace Utils {
class Byte {
public:
/**
* Getters below get value in Host Byte Order.
* Setters below set value in Network Byte Order.
*/
static uint8_t Get1Byte(const uint8_t *data, size_t i);
static uint16_t Get2Bytes(const uint8_t *data, size_t i);
static uint32_t Get3Bytes(const uint8_t *data, size_t i);
static uint32_t Get4Bytes(const uint8_t *data, size_t i);
static uint64_t Get8Bytes(const uint8_t *data, size_t i);
static void Set1Byte(uint8_t *data, size_t i, uint8_t value);
static void Set2Bytes(uint8_t *data, size_t i, uint16_t value);
static void Set3Bytes(uint8_t *data, size_t i, uint32_t value);
static void Set4Bytes(uint8_t *data, size_t i, uint32_t value);
static void Set8Bytes(uint8_t *data, size_t i, uint64_t value);
static uint16_t PadTo4Bytes(uint16_t size);
static uint32_t PadTo4Bytes(uint32_t size);
};
/* Inline static methods. */
inline uint8_t Byte::Get1Byte(const uint8_t *data, size_t i) { return data[i]; }
inline uint16_t Byte::Get2Bytes(const uint8_t *data, size_t i) {
return uint16_t{data[i + 1]} | uint16_t{data[i]} << 8;
}
inline uint32_t Byte::Get3Bytes(const uint8_t *data, size_t i) {
return uint32_t{data[i + 2]} | uint32_t{data[i + 1]} << 8 | uint32_t{data[i]} << 16;
}
inline uint32_t Byte::Get4Bytes(const uint8_t *data, size_t i) {
return uint32_t{data[i + 3]} | uint32_t{data[i + 2]} << 8 | uint32_t{data[i + 1]} << 16 |
uint32_t{data[i]} << 24;
}
inline uint64_t Byte::Get8Bytes(const uint8_t *data, size_t i) {
return uint64_t{Byte::Get4Bytes(data, i)} << 32 | Byte::Get4Bytes(data, i + 4);
}
inline void Byte::Set1Byte(uint8_t *data, size_t i, uint8_t value) { data[i] = value; }
inline void Byte::Set2Bytes(uint8_t *data, size_t i, uint16_t value) {
data[i + 1] = static_cast<uint8_t>(value);
data[i] = static_cast<uint8_t>(value >> 8);
}
inline void Byte::Set3Bytes(uint8_t *data, size_t i, uint32_t value) {
data[i + 2] = static_cast<uint8_t>(value);
data[i + 1] = static_cast<uint8_t>(value >> 8);
data[i] = static_cast<uint8_t>(value >> 16);
}
inline void Byte::Set4Bytes(uint8_t *data, size_t i, uint32_t value) {
data[i + 3] = static_cast<uint8_t>(value);
data[i + 2] = static_cast<uint8_t>(value >> 8);
data[i + 1] = static_cast<uint8_t>(value >> 16);
data[i] = static_cast<uint8_t>(value >> 24);
}
inline void Byte::Set8Bytes(uint8_t *data, size_t i, uint64_t value) {
data[i + 7] = static_cast<uint8_t>(value);
data[i + 6] = static_cast<uint8_t>(value >> 8);
data[i + 5] = static_cast<uint8_t>(value >> 16);
data[i + 4] = static_cast<uint8_t>(value >> 24);
data[i + 3] = static_cast<uint8_t>(value >> 32);
data[i + 2] = static_cast<uint8_t>(value >> 40);
data[i + 1] = static_cast<uint8_t>(value >> 48);
data[i] = static_cast<uint8_t>(value >> 56);
}
inline uint16_t Byte::PadTo4Bytes(uint16_t size) {
// If size is not multiple of 32 bits then pad it.
if (size & 0x03)
return (size & 0xFFFC) + 4;
else
return size;
}
}// namespace Utils
#endif

303
webrtc/WebRtcClient.cpp Executable file
View File

@ -0,0 +1,303 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "Network/TcpClient.h"
#include "Common/config.h"
#include "Common/Parser.h"
#include "WebRtcClient.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
// # WebRTCUrl format
// ## whep/whip over http sfu: webrtc://server_host:server_port/{{app}}/{{streamid}}
// ## whep/whip over https sfu: webrtcs://server_host:server_port/{{app}}/{{streamid}}
// ## websocket p2p: webrtc://{{signaling_server_host}}:{{signaling_server_port}}/{{app}}/{{streamid}}?room_id={{peer_room_id}}
// ## websockets p2p: webrtcs://{{signaling_server_host}}:{{signaling_server_port}}/{{app}}/{{streamid}}?room_id={{peer_room_id}}
void WebRTCUrl::parse(const string &strUrl, bool isPlayer) {
DebugL << "url: " << strUrl;
_full_url = strUrl;
auto url = strUrl;
auto pos = url.find("?");
if (pos != string::npos) {
_params = url.substr(pos + 1);
url.erase(pos);
}
auto schema_pos = url.find("://");
if (schema_pos != string::npos) {
auto schema = url.substr(0, schema_pos);
_is_ssl = strcasecmp(schema.data(), "webrtcs") == 0;
} else {
schema_pos = -3;
}
// set default port
_port = _is_ssl ? 443 : 80;
auto split_vec = split(url.substr(schema_pos + 3), "/");
if (split_vec.size() > 0) {
splitUrl(split_vec[0], _host, _port);
_vhost = _host;
if (_vhost == "localhost" || isIP(_vhost.data())) {
// 如果访问的是localhost或ip那么则为默认虚拟主机
_vhost = DEFAULT_VHOST;
}
}
if (split_vec.size() > 1) {
_app = split_vec[1];
}
if (split_vec.size() > 2) {
string stream_id;
for (size_t i = 2; i < split_vec.size(); ++i) {
stream_id.append(split_vec[i] + "/");
}
if (stream_id.back() == '/') {
stream_id.pop_back();
}
_stream = stream_id;
}
// for vhost
auto kv = Parser::parseArgs(_params);
auto it = kv.find(VHOST_KEY);
if (it != kv.end()) {
_vhost = it->second;
}
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
if (!enableVhost || _vhost.empty()) {
// 如果关闭虚拟主机或者虚拟主机为空,则设置虚拟主机为默认
_vhost = DEFAULT_VHOST;
}
// for peer_room_id
it = kv.find("peer_room_id");
if (it != kv.end()) {
_peer_room_id = it->second;
}
it = kv.find("signaling_protocols");
if (it != kv.end()) {
_signaling_protocols = (WebRtcTransport::SignalingProtocols)(stoi(it->second));
}
auto suffix = _host + ":" + to_string(_port);
suffix += (isPlayer ? "/index/api/whep" : "/index/api/whip");
suffix += "?app=" + _app + "&stream=" + _stream;
if (!_params.empty()) {
suffix += "&" + _params;
}
if (_is_ssl) {
_negotiate_url = StrPrinter << "https://" << suffix << endl;
} else {
_negotiate_url = StrPrinter << "http://" << suffix << endl;
}
}
//////////// WebRtcClient //////////////////////////
WebRtcClient::WebRtcClient(toolkit::EventPoller::Ptr poller) {
DebugL;
_poller = poller ? std::move(poller) : EventPollerPool::Instance().getPoller();
}
WebRtcClient::~WebRtcClient() {
doBye();
DebugL;
}
void WebRtcClient::startConnect() {
DebugL;
doNegotiate();
}
void WebRtcClient::connectivityCheck() {
DebugL;
return _transport->connectivityCheckForSFU();
}
void WebRtcClient::onNegotiateFinish() {
DebugL;
_is_negotiate_finished = true;
if (WebRtcTransport::SignalingProtocols::WEBSOCKET == _url._signaling_protocols) {
// P2P模式需要gathering candidates
gatheringCandidate(_peer->getIceServer());
} else if (WebRtcTransport::SignalingProtocols::WHEP_WHIP == _url._signaling_protocols) {
// SFU模式不会存在IP不通的情况 answer中就携带了candidates, 直接进行connectivityCheck
connectivityCheck();
}
}
void WebRtcClient::doNegotiate() {
DebugL;
switch (_url._signaling_protocols) {
case WebRtcTransport::SignalingProtocols::WHEP_WHIP: return doNegotiateWhepOrWhip();
case WebRtcTransport::SignalingProtocols::WEBSOCKET: return doNegotiateWebsocket();
default: throw std::invalid_argument(StrPrinter << "not support signaling_protocols: " << (int)_url._signaling_protocols);
}
}
void WebRtcClient::doNegotiateWhepOrWhip() {
DebugL << _url._negotiate_url;
weak_ptr<WebRtcClient> weak_self = static_pointer_cast<WebRtcClient>(shared_from_this());
auto offer_sdp = _transport->createOfferSdp();
DebugL << "send offer:\n" << offer_sdp;
_negotiate = make_shared<HttpRequester>();
_negotiate->setMethod("POST");
_negotiate->setBody(std::move(offer_sdp));
_negotiate->startRequester(_url._negotiate_url, [weak_self](const toolkit::SockException &ex, const Parser &response) {
auto strong_self = weak_self.lock();
if (!strong_self) {
return;
}
if (ex) {
WarnL << "network err:" << ex;
strong_self->onResult(ex);
return;
}
DebugL << "status:" << response.status() << "\r\n"
<< "Location:\r\n"
<< response.getHeader()["Location"] << "\r\nrecv answer:\n"
<< response.content();
strong_self->_url._delete_url = response.getHeader()["Location"];
if ("201" != response.status()) {
strong_self->onResult(SockException(Err_other, response.content()));
return;
}
strong_self->_transport->setAnswerSdp(response.content());
strong_self->onNegotiateFinish();
}, getTimeOutSec());
}
void WebRtcClient::doNegotiateWebsocket() {
DebugL;
#if 0
//TODO: 当前暂将每一路呼叫都使用一个独立的peer_connection,不复用
_peer = getWebrtcRoomKeeper(_url._host, _url._port);
if (_peer) {
checkIn();
return;
}
#endif
// 未注册的,先增加注册流程,并在此次播放结束后注销
InfoL << (StrPrinter << "register to signaling server " << _url._host << "::" << _url._port << " first");
auto room_id = "ringing_" + makeRandStr(16);
_peer = make_shared<WebRtcSignalingPeer>(_url._host, _url._port, _url._is_ssl, room_id);
weak_ptr<WebRtcClient> weak_self = static_pointer_cast<WebRtcClient>(shared_from_this());
_peer->setOnConnect([weak_self](const SockException &ex) {
if (auto strong_self = weak_self.lock()) {
if (ex) {
strong_self->onResult(ex);
return;
}
auto cb = [weak_self](const SockException &ex, const string &key) {
if (auto strong_self = weak_self.lock()) {
strong_self->checkIn();
}
};
strong_self->_peer->regist(cb);
}
});
_peer->connect();
}
void WebRtcClient::checkIn() {
DebugL;
weak_ptr<WebRtcClient> weak_self = static_pointer_cast<WebRtcClient>(shared_from_this());
auto tuple = MediaTuple(_url._vhost, _url._app, _url._stream, _url._params);
_peer->checkIn(_url._peer_room_id, tuple, _transport->getIdentifier(), _transport->createOfferSdp(), isPlayer(),
[weak_self](const SockException &ex, const std::string &answer) {
auto strong_self = weak_self.lock();
if (!strong_self) {
return;
}
if (ex) {
WarnL << "network err:" << ex;
strong_self->onResult(ex);
return;
}
strong_self->_transport->setAnswerSdp(answer);
strong_self->onNegotiateFinish();
}, getTimeOutSec());
}
void WebRtcClient::checkOut() {
DebugL;
auto tuple = MediaTuple(_url._vhost, _url._app, _url._stream);
if (_peer) {
_peer->checkOut(_url._peer_room_id);
_peer->unregist([](const SockException &ex) {});
}
}
void WebRtcClient::candidate(const std::string &candidate, const std::string &ufrag, const std::string &pwd) {
_peer->candidate(_transport->getIdentifier(), candidate, ufrag, pwd);
}
void WebRtcClient::gatheringCandidate(IceServerInfo::Ptr ice_server) {
DebugL;
std::weak_ptr<WebRtcClient> weak_self = std::static_pointer_cast<WebRtcClient>(shared_from_this());
_transport->gatheringCandidate(ice_server, [weak_self](const std::string& transport_identifier, const std::string& candidate,
const std::string& ufrag, const std::string& pwd) {
auto strong_self = weak_self.lock();
if (!strong_self) {
return;
}
strong_self->candidate(candidate, ufrag, pwd);
});
}
void WebRtcClient::doBye() {
DebugL;
if (!_is_negotiate_finished) {
return;
}
switch (_url._signaling_protocols) {
case WebRtcTransport::SignalingProtocols::WHEP_WHIP: return doByeWhepOrWhip();
case WebRtcTransport::SignalingProtocols::WEBSOCKET: return checkOut();
default: throw std::invalid_argument(StrPrinter << "not support signaling_protocols: " << (int)_url._signaling_protocols);
}
_is_negotiate_finished = false;
}
void WebRtcClient::doByeWhepOrWhip() {
DebugL;
if (!_negotiate) {
return;
}
_negotiate->setMethod("DELETE");
_negotiate->setBody("");
_negotiate->startRequester(_url._delete_url, [](const toolkit::SockException &ex, const Parser &response) {
if (ex) {
WarnL << "network err:" << ex;
return;
}
DebugL << "status:" << response.status();
}, getTimeOutSec());
}
float WebRtcClient::getTimeOutSec() {
GET_CONFIG(uint32_t, timeout, Rtc::kTimeOutSec);
if (timeout <= 0) {
WarnL << "config rtc. " << Rtc::kTimeOutSec << ": " << timeout << " not vaild";
return 5.0;
}
return (float)timeout;
}
} /* namespace mediakit */

105
webrtc/WebRtcClient.h Executable file
View File

@ -0,0 +1,105 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_WEBRTC_CLIENT_H
#define ZLMEDIAKIT_WEBRTC_CLIENT_H
#include "Network/Socket.h"
#include "Poller/Timer.h"
#include "Util/TimeTicker.h"
#include "Http/HttpRequester.h"
#include "Sdp.h"
#include "WebRtcTransport.h"
#include "WebRtcSignalingPeer.h"
#include <memory>
#include <string>
namespace mediakit {
// 解析webrtc 信令url的工具类
class WebRTCUrl {
public:
bool _is_ssl;
std::string _full_url;
std::string _negotiate_url; // for whep or whip
std::string _delete_url; // for whep or whip
std::string _target_secret;
std::string _params;
std::string _host;
uint16_t _port;
std::string _vhost;
std::string _app;
std::string _stream;
WebRtcTransport::SignalingProtocols _signaling_protocols = WebRtcTransport::SignalingProtocols::WHEP_WHIP;
std::string _peer_room_id; // peer room_id
public:
void parse(const std::string &url, bool isPlayer);
private:
};
namespace Rtc {
typedef enum {
Signaling_Invalid = -1,
Signaling_WHEP_WHIP = 0,
Signaling_WEBSOCKET = 1,
} eSignalingProtocols;
} // namespace Rtc
// 实现了webrtc代理功能
class WebRtcClient : public std::enable_shared_from_this<WebRtcClient> {
public:
using Ptr = std::shared_ptr<WebRtcClient>;
WebRtcClient(toolkit::EventPoller::Ptr poller);
virtual ~WebRtcClient();
const toolkit::EventPoller::Ptr &getPoller() const { return _poller; }
void setPoller(toolkit::EventPoller::Ptr poller) { _poller = std::move(poller); }
// 获取WebRTC transport用于API查询
const WebRtcTransport::Ptr &getWebRtcTransport() const { return _transport; }
protected:
virtual bool isPlayer() = 0;
virtual void startConnect();
virtual void onResult(const toolkit::SockException &ex) = 0;
virtual void onNegotiateFinish();
virtual float getTimeOutSec();
void doNegotiate();
void doNegotiateWebsocket();
void doNegotiateWhepOrWhip();
void checkIn();
void doBye();
void doByeWhepOrWhip();
void checkOut();
void gatheringCandidate(IceServerInfo::Ptr ice_server);
void connectivityCheck();
void candidate(const std::string &candidate, const std::string &ufrag, const std::string &pwd);
protected:
toolkit::EventPoller::Ptr _poller;
// for _negotiate_sdp
WebRTCUrl _url;
HttpRequester::Ptr _negotiate = nullptr;
WebRtcSignalingPeer::Ptr _peer = nullptr;
WebRtcTransport::Ptr _transport = nullptr;
bool _is_negotiate_finished = false;
private:
std::map<std::string /*candidate key*/, toolkit::SocketHelper::Ptr> _socket_map;
};
} /*namespace mediakit */
#endif /* ZLMEDIAKIT_WEBRTC_CLIENT_H */

View File

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

View File

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

View File

@ -15,6 +15,7 @@
#include "Util/base64.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
@ -167,17 +168,24 @@ uint8_t H264BFrameFilter::extractSliceType(const uint8_t *data, size_t size) con
return -1;
}
WebRtcPlayer::Ptr WebRtcPlayer::create(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info) {
WebRtcPlayer::Ptr WebRtcPlayer::create(const EventPoller::Ptr &poller,
const RtspMediaSource::Ptr &src,
const MediaInfo &info,
WebRtcTransport::Role role,
WebRtcTransport::SignalingProtocols signaling_protocols) {
WebRtcPlayer::Ptr ret(new WebRtcPlayer(poller, src, info), [](WebRtcPlayer *ptr) {
ptr->onDestory();
delete ptr;
});
ret->setRole(role);
ret->setSignalingProtocols(signaling_protocols);
ret->onCreate();
return ret;
}
WebRtcPlayer::WebRtcPlayer(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info)
: WebRtcTransportImp(poller) {
WebRtcPlayer::WebRtcPlayer(const EventPoller::Ptr &poller,
const RtspMediaSource::Ptr &src,
const MediaInfo &info) : WebRtcTransportImp(poller) {
_media_info = info;
_play_src = src;
CHECK(src);

View File

@ -11,8 +11,8 @@
#ifndef ZLMEDIAKIT_WEBRTCPLAYER_H
#define ZLMEDIAKIT_WEBRTCPLAYER_H
#include "Rtsp/RtspMediaSource.h"
#include "WebRtcTransport.h"
#include "Rtsp/RtspMediaSource.h"
namespace mediakit {
/**
@ -125,7 +125,8 @@ private:
class WebRtcPlayer : public WebRtcTransportImp {
public:
using Ptr = std::shared_ptr<WebRtcPlayer>;
static Ptr create(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info);
static Ptr create(const toolkit::EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info,
WebRtcTransport::Role role, WebRtcTransport::SignalingProtocols signaling_protocols);
MediaInfo getMediaInfo() { return _media_info; }
protected:
@ -135,7 +136,7 @@ protected:
void onRtcConfigure(RtcConfigure &configure) const override;
private:
WebRtcPlayer(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info);
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);

126
webrtc/WebRtcProxyPlayer.cpp Executable file
View File

@ -0,0 +1,126 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "WebRtcProxyPlayer.h"
#include "WebRtcProxyPlayerImp.h"
#include "WebRtcPusher.h"
#include "Common/config.h"
#include "Http/HlsPlayer.h"
#include "Rtsp/RtspMediaSourceImp.h"
using namespace toolkit;
using namespace std;
namespace mediakit {
WebRtcProxyPlayer::WebRtcProxyPlayer(const EventPoller::Ptr &poller)
: WebRtcClient(poller) {
DebugL;
}
WebRtcProxyPlayer::~WebRtcProxyPlayer(void) {
DebugL;
}
void WebRtcProxyPlayer::play(const string &strUrl) {
DebugL;
try {
_url.parse(strUrl, isPlayer());
} catch (std::exception &ex) {
onResult(SockException(Err_other, StrPrinter << "illegal webrtc url:" << ex.what()));
return;
}
startConnect();
}
void WebRtcProxyPlayer::teardown() {
DebugL;
doBye();
}
void WebRtcProxyPlayer::pause(bool bPause) {
DebugL;
}
void WebRtcProxyPlayer::speed(float speed) {
DebugL;
}
float WebRtcProxyPlayer::getTimeOutSec() {
auto timeoutMS = (*this)[Client::kTimeoutMS].as<uint64_t>();
return (float)timeoutMS / (float)1000;
}
void WebRtcProxyPlayer::onNegotiateFinish() {
DebugL;
onResult(SockException(Err_success, "webrtc play success"));
WebRtcClient::onNegotiateFinish();
}
///////////////////////////////////////////////////
// WebRtcProxyPlayerImp
void WebRtcProxyPlayerImp::startConnect() {
DebugL;
MediaInfo info(_url._full_url);
ProtocolOption option;
std::weak_ptr<WebRtcProxyPlayerImp> weak_self = std::static_pointer_cast<WebRtcProxyPlayerImp>(shared_from_this());
_transport = WebRtcPlayerClient::create(getPoller(), WebRtcTransport::Role::CLIENT, _url._signaling_protocols);
_transport->setOnShutdown([weak_self](const SockException &ex) {
auto strong_self = weak_self.lock();
if (!strong_self) {
return;
}
strong_self->onResult(ex);
});
WebRtcClient::startConnect();
}
void WebRtcProxyPlayerImp::onResult(const SockException &ex) {
if (!ex) {
// 播放成功
_benchmark_mode = (*this)[Client::kBenchmarkMode].as<int>();
WebRtcPlayerClient::Ptr transport = std::dynamic_pointer_cast<WebRtcPlayerClient>(_transport);
auto media_src = dynamic_pointer_cast<RtspMediaSource>(_media_src);
transport->setMediaSource(media_src);
std::weak_ptr<WebRtcProxyPlayerImp> weak_self = std::static_pointer_cast<WebRtcProxyPlayerImp>(shared_from_this());
if (!ex) {
transport->setOnStartWebRTC([weak_self, ex]() {
if (auto strong_self = weak_self.lock()) {
strong_self->onPlayResult(ex);
}
});
}
} else {
WarnL << ex.getErrCode() << " " << ex.what();
if (ex.getErrCode() == Err_shutdown) {
// 主动shutdown的不触发回调
return;
}
if (!_is_negotiate_finished) {
onPlayResult(ex);
} else {
onShutdown(ex);
}
}
}
std::vector<Track::Ptr> WebRtcProxyPlayerImp::getTracks(bool ready /*= true*/) const {
auto transport = static_pointer_cast<WebRtcPlayerClient>(_transport);
return transport ? transport->getTracks(ready) : Super::getTracks(ready);
}
void WebRtcProxyPlayerImp::addTrackCompleted() {
}
} /* namespace mediakit */

56
webrtc/WebRtcProxyPlayer.h Executable file
View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_WEBRTC_PROXY_PLAYER_H
#define ZLMEDIAKIT_WEBRTC_PROXY_PLAYER_H
#include "Network/Socket.h"
#include "Player/PlayerBase.h"
#include "Poller/Timer.h"
#include "Util/TimeTicker.h"
#include "WebRtcClient.h"
#include <memory>
#include <string>
namespace mediakit {
// 实现了webrtc代理拉流功能
class WebRtcProxyPlayer
: public PlayerBase , public WebRtcClient {
public:
using Ptr = std::shared_ptr<WebRtcProxyPlayer>;
WebRtcProxyPlayer(const toolkit::EventPoller::Ptr &poller);
~WebRtcProxyPlayer() override;
//// PlayerBase override////
void play(const std::string &strUrl) override;
void teardown() override;
void pause(bool pause) override;
void speed(float speed) override;
protected:
//// WebRtcClient override////
bool isPlayer() override {return true;}
float getTimeOutSec() override;
void onNegotiateFinish() override;
protected:
//是否为性能测试模式
bool _benchmark_mode = false;
//超时功能实现
toolkit::Ticker _recv_ticker;
std::shared_ptr<toolkit::Timer> _check_timer;
};
} /* namespace mediakit */
#endif /* ZLMEDIAKIT_WEBRTC_PROXY_PLAYER_H */

43
webrtc/WebRtcProxyPlayerImp.h Executable file
View File

@ -0,0 +1,43 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_WEBRTC_PROXY_PLAYER_IMP_H
#define ZLMEDIAKIT_WEBRTC_PROXY_PLAYER_IMP_H
#include "WebRtcProxyPlayer.h"
namespace mediakit {
class WebRtcProxyPlayerImp
: public PlayerImp<WebRtcProxyPlayer, PlayerBase>
, private TrackListener {
public:
using Ptr = std::shared_ptr<WebRtcProxyPlayerImp>;
using Super = PlayerImp<WebRtcProxyPlayer, PlayerBase>;
WebRtcProxyPlayerImp(const toolkit::EventPoller::Ptr &poller) : Super(poller) {}
~WebRtcProxyPlayerImp() override { DebugL; }
private:
//// WebRtcProxyPlayer override////
void startConnect() override;
//// PlayerBase override////
void onResult(const toolkit::SockException &ex) override;
std::vector<Track::Ptr> getTracks(bool ready = true) const override;
//// TrackListener override////
bool addTrack(const Track::Ptr &track) override { return true; }
void addTrackCompleted() override;
};
} /* namespace mediakit */
#endif /* ZLMEDIAKIT_WEBRTC_PROXY_PLAYER_IMP_H */

92
webrtc/WebRtcProxyPusher.cpp Executable file
View File

@ -0,0 +1,92 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "WebRtcProxyPusher.h"
#include "Common/config.h"
#include "Http/HlsPlayer.h"
#include "Rtsp/RtspMediaSourceImp.h"
#include "WebRtcPlayer.h"
using namespace toolkit;
using namespace std;
namespace mediakit {
WebRtcProxyPusher::WebRtcProxyPusher(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src)
: WebRtcClient(poller) {
_push_src = src;
DebugL;
}
WebRtcProxyPusher::~WebRtcProxyPusher(void) {
teardown();
DebugL;
}
void WebRtcProxyPusher::publish(const string &strUrl) {
DebugL;
try {
_url.parse(strUrl, isPlayer());
} catch (std::exception &ex) {
onResult(SockException(Err_other, StrPrinter << "illegal webrtc url:" << ex.what()));
return;
}
startConnect();
}
void WebRtcProxyPusher::teardown() {
DebugL;
_transport = nullptr;
}
void WebRtcProxyPusher::onResult(const SockException &ex) {
DebugL << ex;
if (!ex) {
onPublishResult(ex);
} else {
if (!_is_negotiate_finished) {
onPublishResult(ex);
} else {
onShutdown(ex);
}
}
}
float WebRtcProxyPusher::getTimeOutSec() {
auto timeoutMS = (*this)[Client::kTimeoutMS].as<uint64_t>();
return (float)timeoutMS / (float)1000;
}
void WebRtcProxyPusher::startConnect() {
DebugL;
MediaInfo info(_url._full_url);
info.schema = "rtc";
auto src = _push_src.lock();
if (!src) {
onResult(SockException(Err_other, "media source released"));
return;
}
std::weak_ptr<WebRtcProxyPusher> weak_self = std::static_pointer_cast<WebRtcProxyPusher>(shared_from_this());
_transport = WebRtcPlayer::create(getPoller(), src, info, WebRtcTransport::Role::CLIENT, _url._signaling_protocols);
_transport->setOnShutdown([weak_self](const SockException &ex) {
if (auto strong_self = weak_self.lock()) {
strong_self->onResult(ex);
}
});
_transport->setOnStartWebRTC([weak_self]() {
if (auto strong_self = weak_self.lock()) {
strong_self->onResult(SockException());
}
});
WebRtcClient::startConnect();
}
} /* namespace mediakit */

51
webrtc/WebRtcProxyPusher.h Executable file
View File

@ -0,0 +1,51 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_WEBRTC_PROXY_PUSHER_H
#define ZLMEDIAKIT_WEBRTC_PROXY_PUSHER_H
#include "Network/Socket.h"
#include "Pusher/PusherBase.h"
#include "Poller/Timer.h"
#include "Util/TimeTicker.h"
#include "WebRtcClient.h"
#include <memory>
#include <string>
namespace mediakit {
// 实现了webrtc代理拉流功能
class WebRtcProxyPusher
: public PusherBase , public WebRtcClient {
public:
using Ptr = std::shared_ptr<WebRtcProxyPusher>;
WebRtcProxyPusher(const toolkit::EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src);
~WebRtcProxyPusher() override;
//// PusherBase override////
void publish(const std::string &url) override;
void teardown() override;
protected:
//// WebRtcClient override////
void startConnect() override;
bool isPlayer() override { return false; }
void onResult(const toolkit::SockException &ex) override;
float getTimeOutSec() override;
protected:
std::weak_ptr<RtspMediaSource> _push_src;
};
using WebRtcProxyPusherImp = PusherImp<WebRtcProxyPusher, PusherBase>;
} /* namespace mediakit */
#endif /* ZLMEDIAKIT_WEBRTC_PROXY_PUSHER_H */

View File

@ -13,6 +13,7 @@
#include "Rtsp/RtspMediaSourceImp.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
@ -20,13 +21,18 @@ WebRtcPusher::Ptr WebRtcPusher::create(const EventPoller::Ptr &poller,
const RtspMediaSource::Ptr &src,
const std::shared_ptr<void> &ownership,
const MediaInfo &info,
const ProtocolOption &option) {
WebRtcPusher::Ptr ret(new WebRtcPusher(poller, src, ownership, info, option), [](WebRtcPusher *ptr) {
const ProtocolOption &option,
WebRtcTransport::Role role,
WebRtcTransport::SignalingProtocols signaling_protocols) {
WebRtcPusher::Ptr pusher(new WebRtcPusher(poller, src, ownership, info, option), [](WebRtcPusher *ptr) {
ptr->onDestory();
delete ptr;
});
ret->onCreate();
return ret;
pusher->setRole(role);
pusher->setSignalingProtocols(signaling_protocols);
pusher->onCreate();
return pusher;
}
WebRtcPusher::WebRtcPusher(const EventPoller::Ptr &poller,
@ -166,4 +172,61 @@ void WebRtcPusher::onShutdown(const SockException &ex) {
WebRtcTransportImp::onShutdown(ex);
}
////////////////////////////////////////////////////////////////////////////////////////
WebRtcPlayerClient::Ptr WebRtcPlayerClient::create(const EventPoller::Ptr &poller, WebRtcTransport::Role role,
WebRtcTransport::SignalingProtocols signaling_protocols) {
WebRtcPlayerClient::Ptr pusher(new WebRtcPlayerClient(poller), [](WebRtcPlayerClient *ptr) {
ptr->onDestory();
delete ptr;
});
pusher->setRole(role);
pusher->setSignalingProtocols(signaling_protocols);
pusher->onCreate();
return pusher;
}
WebRtcPlayerClient::WebRtcPlayerClient(const EventPoller::Ptr &poller)
: WebRtcTransportImp(poller) {
_demuxer = std::make_shared<RtspDemuxer>();
}
void WebRtcPlayerClient::onRecvRtp(MediaTrack &track, const string &rid, RtpPacket::Ptr rtp) {
auto key_pos = _demuxer->inputRtp(rtp);
if (_push_src) {
_push_src->onWrite(rtp, key_pos);
}
}
void WebRtcPlayerClient::onStartWebRTC() {
WebRtcTransportImp::onStartWebRTC();
CHECK(!_answer_sdp->supportSimulcast());
auto sdp = _answer_sdp->toRtspSdp();
if (canRecvRtp()) {
if (_push_src) {
_push_src->setSdp(sdp);
}
_demuxer->loadSdp(sdp);
}
}
void WebRtcPlayerClient::onRtcConfigure(RtcConfigure &configure) const {
WebRtcTransportImp::onRtcConfigure(configure);
// 这只是推流 [AUTO-TRANSLATED:f877bf98]
// This is just pushing the stream
configure.audio.direction = configure.video.direction = RtpDirection::recvonly;
}
vector<Track::Ptr> WebRtcPlayerClient::getTracks(bool ready) const {
return _demuxer->getTracks(ready);
}
void WebRtcPlayerClient::setMediaSource(RtspMediaSource::Ptr src) {
_push_src = std::move(src);
if (_push_src && canRecvRtp()) {
_push_src->setSdp(_answer_sdp->toRtspSdp());
}
}
}// namespace mediakit

View File

@ -12,6 +12,7 @@
#define ZLMEDIAKIT_WEBRTCPUSHER_H
#include "WebRtcTransport.h"
#include "Rtsp/RtspDemuxer.h"
#include "Rtsp/RtspMediaSource.h"
namespace mediakit {
@ -19,8 +20,9 @@ namespace mediakit {
class WebRtcPusher : public WebRtcTransportImp, public MediaSourceEvent {
public:
using Ptr = std::shared_ptr<WebRtcPusher>;
static Ptr create(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src,
const std::shared_ptr<void> &ownership, const MediaInfo &info, const ProtocolOption &option);
static Ptr create(const toolkit::EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src,
const std::shared_ptr<void> &ownership, const MediaInfo &info, const ProtocolOption &option,
WebRtcTransport::Role role, WebRtcTransport::SignalingProtocols signaling_protocols);
protected:
///////WebRtcTransportImp override///////
@ -28,7 +30,7 @@ protected:
void onDestory() override;
void onRtcConfigure(RtcConfigure &configure) const override;
void onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) override;
void onShutdown(const SockException &ex) override;
void onShutdown(const toolkit::SockException &ex) override;
void onRtcpBye() override;
// // dtls相关的回调 //// [AUTO-TRANSLATED:31a1f32c]
// // dtls related callbacks ////
@ -50,7 +52,7 @@ protected:
std::string getOriginUrl(MediaSource &sender) const override;
// 获取媒体源客户端相关信息 [AUTO-TRANSLATED:037ef910]
// Get media source client related information
std::shared_ptr<SockInfo> getOriginSock(MediaSource &sender) const override;
std::shared_ptr<toolkit::SockInfo> getOriginSock(MediaSource &sender) const override;
// 由于支持断连续推存在OwnerPoller变更的可能 [AUTO-TRANSLATED:1c863b40]
// Due to support for discontinuous pushing, there is a possibility of OwnerPoller changes
toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override;
@ -59,7 +61,7 @@ protected:
float getLossRate(MediaSource &sender,TrackType type) override;
private:
WebRtcPusher(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src,
WebRtcPusher(const toolkit::EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src,
const std::shared_ptr<void> &ownership, const MediaInfo &info, const ProtocolOption &option);
private:
@ -83,5 +85,29 @@ private:
std::unordered_map<std::string/*rid*/, std::shared_ptr<void> > _push_src_sim_ownership;
};
class WebRtcPlayerClient : public WebRtcTransportImp {
public:
using Ptr = std::shared_ptr<WebRtcPlayerClient>;
static Ptr create(const toolkit::EventPoller::Ptr &poller, WebRtcTransport::Role role, WebRtcTransport::SignalingProtocols signaling_protocols);
void setMediaSource(RtspMediaSource::Ptr src);
std::vector<Track::Ptr> getTracks(bool ready) const;
protected:
///////WebRtcTransportImp override///////
void onStartWebRTC() override;
void onRtcConfigure(RtcConfigure &configure) const override;
void onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) override;
private:
WebRtcPlayerClient(const toolkit::EventPoller::Ptr &poller);
private:
RtspDemuxer::Ptr _demuxer;
// 推流的rtsp源 [AUTO-TRANSLATED:4f976bca]
// Rtsp source of the stream
RtspMediaSource::Ptr _push_src;
};
}// namespace mediakit
#endif //ZLMEDIAKIT_WEBRTCPUSHER_H

View File

@ -12,28 +12,26 @@
#include "Util/util.h"
#include "Network/TcpServer.h"
#include "Common/config.h"
#include "IceServer.hpp"
#include "IceTransport.hpp"
#include "WebRtcTransport.h"
using namespace std;
using namespace toolkit;
namespace mediakit {
static string getUserName(const char *buf, size_t len) {
if (!RTC::StunPacket::IsStun((const uint8_t *) buf, len)) {
if (!RTC::StunPacket::isStun((const uint8_t *) buf, len)) {
return "";
}
std::unique_ptr<RTC::StunPacket> packet(RTC::StunPacket::Parse((const uint8_t *) buf, len));
auto packet = RTC::StunPacket::parse((const uint8_t *) buf, len);
if (!packet) {
return "";
}
if (packet->GetClass() != RTC::StunPacket::Class::REQUEST ||
packet->GetMethod() != RTC::StunPacket::Method::BINDING) {
return "";
}
// 收到binding request请求 [AUTO-TRANSLATED:eff4d773]
// Received binding request
auto vec = split(packet->GetUsername(), ":");
auto vec = split(packet->getUsername(), ":");
return vec[0];
}
@ -92,7 +90,8 @@ void WebRtcSession::onRecv_l(const char *data, size_t len) {
}
_ticker.resetTime();
CHECK(_transport);
_transport->inputSockData((char *)data, len, this);
auto self = static_pointer_cast<WebRtcSession>(shared_from_this());
_transport->inputSockData(data, len, self);
}
void WebRtcSession::onRecv(const Buffer::Ptr &buffer) {
@ -120,7 +119,7 @@ void WebRtcSession::onError(const SockException &err) {
getPoller()->async([transport, self]() mutable {
// 延时减引用防止使用transport对象时销毁对象 [AUTO-TRANSLATED:09dd6609]
// Delay decrementing the reference count to prevent the object from being destroyed when using the transport object
transport->removeTuple(self.get());
transport->removePair(self.get());
// 确保transport在Session对象前销毁防止WebRtcTransport::onDestory()时获取不到Session对象 [AUTO-TRANSLATED:acd8bd77]
// Ensure that the transport is destroyed before the Session object to prevent WebRtcTransport::onDestory() from not being able to get the Session object
transport = nullptr;
@ -162,5 +161,3 @@ const char *WebRtcSession::onSearchPacketTail(const char *data, size_t len) {
}
}// namespace mediakit

View File

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

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "WebRtcSignalingMsg.h"
namespace mediakit {
namespace Rtc {
// WebRTC 信令消息键名和值常量定义
const char* const CLASS_KEY = "class";
const char* const CLASS_VALUE_REQUEST = "request";
const char* const CLASS_VALUE_INDICATION = "indication"; // 指示类型,不需要应答
const char* const CLASS_VALUE_ACCEPT = "accept"; // 作为CLASS_VALUE_REQUEST的应答
const char* const CLASS_VALUE_REJECT = "reject"; // 作为CLASS_VALUE_REQUEST的应答
const char* const METHOD_KEY = "method";
const char* const METHOD_VALUE_REGISTER = "register"; // 注册
const char* const METHOD_VALUE_UNREGISTER = "unregister"; // 注销
const char* const METHOD_VALUE_CALL = "call"; // 呼叫(取流或推流)
const char* const METHOD_VALUE_BYE = "bye"; // 挂断
const char* const METHOD_VALUE_CANDIDATE = "candidate";
const char* const TRANSACTION_ID_KEY = "transaction_id"; // 消息id,每条消息拥有一个唯一的id
const char* const ROOM_ID_KEY = "room_id";
const char* const GUEST_ID_KEY = "guest_id"; // 每个独立的会话会拥有一个唯一的guest_id
const char* const SENDER_KEY = "sender";
const char* const TYPE_KEY = "type";
const char* const TYPE_VALUE_PLAY = "play"; // 拉流
const char* const TYPE_VALUE_PUSH = "push"; // 推流
const char* const REASON_KEY = "reason";
const char* const CALL_VHOST_KEY = "vhost";
const char* const CALL_APP_KEY = "app";
const char* const CALL_STREAM_KEY = "stream";
const char* const SDP_KEY = "sdp";
const char* const ICE_SERVERS_KEY = "ice_servers";
const char* const CANDIDATE_KEY = "candidate";
const char* const URL_KEY = "url";
const char* const UFRAG_KEY = "ufrag";
const char* const PWD_KEY = "pwd";
} // namespace Rtc
} // namespace mediakit

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_WEBRTC_SIGNALING_MSG_H
#define ZLMEDIAKIT_WEBRTC_SIGNALING_MSG_H
#include "server/WebApi.h"
namespace mediakit {
namespace Rtc {
#define SIGNALING_MSG_ARGS const HttpAllArgs<Json::Value>& allArgs
// WebRTC 信令消息键名和值常量声明
extern const char* const CLASS_KEY;
extern const char* const CLASS_VALUE_REQUEST;
extern const char* const CLASS_VALUE_INDICATION; // 指示类型,不需要应答
extern const char* const CLASS_VALUE_ACCEPT; // 作为CLASS_VALUE_REQUEST的应答
extern const char* const CLASS_VALUE_REJECT; // 作为CLASS_VALUE_REQUEST的应答
extern const char* const METHOD_KEY;
extern const char* const METHOD_VALUE_REGISTER; // 注册
extern const char* const METHOD_VALUE_UNREGISTER; // 注销
extern const char* const METHOD_VALUE_CALL; // 呼叫(取流或推流)
extern const char* const METHOD_VALUE_BYE; // 挂断
extern const char* const METHOD_VALUE_CANDIDATE;
extern const char* const TRANSACTION_ID_KEY; // 消息id,每条消息拥有一个唯一的id
extern const char* const ROOM_ID_KEY;
extern const char* const GUEST_ID_KEY; // 每个独立的会话会拥有一个唯一的guest_id
extern const char* const SENDER_KEY;
extern const char* const TYPE_KEY;
extern const char* const TYPE_VALUE_PLAY; // 拉流
extern const char* const TYPE_VALUE_PUSH; // 推流
extern const char* const REASON_KEY;
extern const char* const CALL_VHOST_KEY;
extern const char* const CALL_APP_KEY;
extern const char* const CALL_STREAM_KEY;
extern const char* const SDP_KEY;
extern const char* const ICE_SERVERS_KEY;
extern const char* const CANDIDATE_KEY;
extern const char* const URL_KEY;
extern const char* const UFRAG_KEY;
extern const char* const PWD_KEY;
} // namespace Rtc
} // namespace mediakit
//
#endif //ZLMEDIAKIT_WEBRTC_SIGNALING_PEER_H

View File

@ -0,0 +1,687 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "WebRtcSignalingPeer.h"
#include "WebRtcSignalingMsg.h"
#include "Util/util.h"
#include "Common/config.h"
#include "json/value.h"
using namespace std;
using namespace toolkit;
using namespace mediakit::Rtc;
namespace mediakit {
// 注册到的信令服务器列表
// 不允许注册到同一个服务器地址
static ServiceController<WebRtcSignalingPeer> s_room_keepers;
static inline string getRoomKeepersKey(const string &host, uint16_t &port) {
return host + ":" + std::to_string(port);
}
void addWebrtcRoomKeeper(const string &host, uint16_t port, const std::string& room_id, bool ssl,
const function<void(const SockException &ex, const string &key)> &cb) {
DebugL;
auto key = getRoomKeepersKey(host, port);
if (s_room_keepers.find(key)) {
//已经发起注册了
cb(SockException(Err_success), key);
return;
}
auto peer = s_room_keepers.make(key, host, port, ssl, room_id);
peer->setOnShutdown([key] (const SockException &ex) {
InfoL << "webrtc peer shutdown, key: " << key << ", " << ex.what();
s_room_keepers.erase(key);
});
peer->setOnConnect([peer, cb] (const SockException &ex) {
peer->regist(cb);
});
peer->connect();
}
void delWebrtcRoomKeeper(const std::string &key, const std::function<void(const SockException &ex)> &cb) {
auto peer = s_room_keepers.find(key);
if (!peer) {
return cb(SockException(Err_other, "room_key not exist"));
}
peer->unregist(cb);
s_room_keepers.erase(key);
}
void listWebrtcRoomKeepers(const std::function<void(const std::string& key, const WebRtcSignalingPeer::Ptr& p)> &cb) {
s_room_keepers.for_each(cb);
}
Json::Value ToJson(const WebRtcSignalingPeer::Ptr& p) {
return p->makeInfoJson();
}
WebRtcSignalingPeer::Ptr getWebrtcRoomKeeper(const string &host, uint16_t port) {
return s_room_keepers.find(getRoomKeepersKey(host, port));
}
//////////// WebRtcSignalingPeer //////////////////////////
WebRtcSignalingPeer::WebRtcSignalingPeer(const std::string &host, uint16_t port, bool ssl, const std::string &room_id, const EventPoller::Ptr &poller)
: WebSocketClient<TcpClient>(poller)
, _room_id(room_id) {
TraceL;
// TODO: not support wss now
_ws_url = StrPrinter << (ssl ? "wss://" : "ws://") + host << ":" << port << "/signaling";
_room_key = getRoomKeepersKey(host, port);
}
WebRtcSignalingPeer::~WebRtcSignalingPeer() {
DebugL << "room_id: " << _room_id;
}
void WebRtcSignalingPeer::connect() {
DebugL;
startWebSocket(_ws_url);
}
void WebRtcSignalingPeer::regist(const function<void(const SockException &ex, const string &key)> &cb) {
DebugL;
std::weak_ptr<WebRtcSignalingPeer> weak_self = std::static_pointer_cast<WebRtcSignalingPeer>(shared_from_this());
getPoller()->async([weak_self, cb]() mutable {
if (auto strong_self = weak_self.lock()) {
strong_self->sendRegisterRequest(std::move(cb));
}
});
}
void WebRtcSignalingPeer::unregist(const function<void(const SockException &ex)> &cb) {
DebugL;
auto trigger = [cb](const SockException &ex, std::string msg) { cb(ex); };
std::weak_ptr<WebRtcSignalingPeer> weak_self = std::static_pointer_cast<WebRtcSignalingPeer>(shared_from_this());
getPoller()->async([weak_self, trigger]() mutable {
if (auto strong_self = weak_self.lock()) {
strong_self->sendUnregisterRequest(std::move(trigger));
}
});
}
void WebRtcSignalingPeer::checkIn(const std::string& peer_room_id, const MediaTuple &tuple, const std::string& identifier,
const std::string& offer, bool is_play,
const function<void(const SockException &ex, const std::string& answer)> &cb, float timeout_sec) {
DebugL;
std::weak_ptr<WebRtcSignalingPeer> weak_self = std::static_pointer_cast<WebRtcSignalingPeer>(shared_from_this());
getPoller()->async([=] () mutable {
TraceL;
if (auto strong_self = weak_self.lock()) {
auto guest_id = strong_self->_room_id + "_" + makeRandStr(16);
strong_self->_tours.emplace(peer_room_id, std::make_pair(guest_id, identifier));
auto trigger = ([cb, peer_room_id, weak_self](const SockException &ex, const std::string &msg) {
auto strong_self = weak_self.lock();
if (ex && strong_self) {
strong_self->_tours.erase(peer_room_id);
}
return cb(ex, msg);
});
strong_self->sendCallRequest(peer_room_id, guest_id, tuple, offer, is_play, std::move(trigger));
}
});
}
void WebRtcSignalingPeer::checkOut(const std::string& peer_room_id) {
DebugL;
std::weak_ptr<WebRtcSignalingPeer> weak_self = std::static_pointer_cast<WebRtcSignalingPeer>(shared_from_this());
getPoller()->async([=] () {
TraceL;
if (auto strong_self = weak_self.lock()) {
auto it = strong_self->_tours.find(peer_room_id);
if (it != strong_self->_tours.end()) {
auto &guest_id = it->second.first;
strong_self->sendByeIndication(peer_room_id, guest_id);
strong_self->_tours.erase(it);
}
}
});
}
void WebRtcSignalingPeer::candidate(const std::string& transport_identifier, const std::string& candidate, const std::string& ice_ufrag, const std::string& ice_pwd) {
std::weak_ptr<WebRtcSignalingPeer> weak_self = std::static_pointer_cast<WebRtcSignalingPeer>(shared_from_this());
getPoller()->async([=] () {
if (auto strong_self = weak_self.lock()) {
strong_self->sendCandidateIndication(transport_identifier, candidate, ice_ufrag, ice_pwd);
}
});
}
void WebRtcSignalingPeer::processOffer(SIGNALING_MSG_ARGS, WebRtcInterface &transport) {
try {
auto sdp = transport.getAnswerSdp((const std::string )allArgs[SDP_KEY]);
auto tuple = MediaTuple(allArgs[CALL_VHOST_KEY], allArgs[CALL_APP_KEY], allArgs[CALL_STREAM_KEY]);
answer(allArgs[GUEST_ID_KEY], tuple, transport.getIdentifier(), sdp, allArgs[TYPE_KEY] == TYPE_VALUE_PLAY, allArgs[TRANSACTION_ID_KEY]);
std::weak_ptr<WebRtcSignalingPeer> weak_self = std::static_pointer_cast<WebRtcSignalingPeer>(shared_from_this());
transport.gatheringCandidate(_ice_server, [weak_self](const std::string& transport_identifier,
const std::string& candidate, const std::string& ufrag, const std::string& pwd) {
if (auto strong_self = weak_self.lock()) {
strong_self->candidate(transport_identifier, candidate, ufrag, pwd);
}
});
} catch (std::exception &ex) {
Json::Value body;
body[METHOD_KEY] = allArgs[METHOD_KEY];
body[ROOM_ID_KEY] = allArgs[ROOM_ID_KEY];
body[GUEST_ID_KEY] = allArgs[GUEST_ID_KEY];
body[CALL_VHOST_KEY] = allArgs[CALL_VHOST_KEY];
body[CALL_APP_KEY] = allArgs[CALL_APP_KEY];
body[CALL_STREAM_KEY] = allArgs[CALL_STREAM_KEY];
body[TYPE_KEY] = allArgs[TYPE_KEY];
sendRefusesResponse(body, allArgs[TRANSACTION_ID_KEY], ex.what());
}
}
void WebRtcSignalingPeer::answer(const std::string& guest_id, const MediaTuple &tuple, const std::string& identifier, const std::string& sdp, bool is_play, const std::string& transaction_id) {
_peer_guests.emplace(guest_id, identifier);
sendCallAccept(guest_id, tuple, sdp, is_play, transaction_id);
}
void WebRtcSignalingPeer::setOnConnect(function<void(const SockException &ex)> cb) {
_on_connect = cb ? std::move(cb) : [](const SockException &) {};
}
void WebRtcSignalingPeer::onConnect(const SockException &ex) {
TraceL;
if (_on_connect) {
_on_connect(ex);
}
if (!ex) {
createResponseExpiredTimer();
}
}
void WebRtcSignalingPeer::setOnShutdown(function<void(const SockException &ex)> cb) {
_on_shutdown = cb ? std::move(cb) : [](const SockException &) {};
}
void WebRtcSignalingPeer::onShutdown(const SockException &ex) {
TraceL;
if (_on_shutdown) {
_on_shutdown(ex);
}
}
void WebRtcSignalingPeer::onRecv(const Buffer::Ptr &buffer) {
TraceL << "recv msg:\r\n" << buffer->data();
Json::Value args;
Json::Reader reader;
reader.parse(buffer->data(), args);
Parser parser;
HttpAllArgs<decltype(args)> allArgs(parser, args);
CHECK_ARGS(METHOD_KEY, TRANSACTION_ID_KEY);
using MsgHandler = void (WebRtcSignalingPeer::*)(SIGNALING_MSG_ARGS);
static std::unordered_map<std::pair<std::string /*class*/, std::string /*method*/>, MsgHandler, ClassMethodHash> s_msg_handlers;
static onceToken token([]() {
s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_ACCEPT, METHOD_VALUE_REGISTER), &WebRtcSignalingPeer::handleRegisterAccept);
s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_REJECT, METHOD_VALUE_REGISTER), &WebRtcSignalingPeer::handleRegisterReject);
s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_ACCEPT, METHOD_VALUE_UNREGISTER), &WebRtcSignalingPeer::handleUnregisterAccept);
s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_REJECT, METHOD_VALUE_UNREGISTER), &WebRtcSignalingPeer::handleUnregisterReject);
s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_REQUEST, METHOD_VALUE_CALL), &WebRtcSignalingPeer::handleCallRequest);
s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_ACCEPT, METHOD_VALUE_CALL), &WebRtcSignalingPeer::handleCallAccept);
s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_REJECT, METHOD_VALUE_CALL), &WebRtcSignalingPeer::handleCallReject);
s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_INDICATION, METHOD_VALUE_CANDIDATE), &WebRtcSignalingPeer::handleCandidateIndication);
s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_INDICATION, METHOD_VALUE_BYE), &WebRtcSignalingPeer::handleByeIndication);
});
auto it = s_msg_handlers.find(std::make_pair(allArgs[CLASS_KEY], allArgs[METHOD_KEY]));
if (it == s_msg_handlers.end()) {
WarnL << "unsupported class: "<< allArgs[CLASS_KEY] << ", method: " << allArgs[METHOD_KEY] << ", ignore";
return;
}
return (this->*(it->second))(allArgs);
}
void WebRtcSignalingPeer::onError(const SockException &err) {
WarnL << "room_id: " << _room_id;
s_room_keepers.erase(_room_key);
// 除非对端显式的发送了注销执行,否则因为网络异常导致的会话中断不影响已经进行通信的webrtc会话,仅作移除
}
bool WebRtcSignalingPeer::responseFilter(SIGNALING_MSG_ARGS, ResponseTrigger& trigger) {
if (allArgs[CLASS_KEY] != CLASS_VALUE_ACCEPT && allArgs[CLASS_KEY] != CLASS_VALUE_REJECT) {
return false;
}
for (auto &pr : _response_list) {
auto &transaction_id = pr.first;
// mismatch transaction_id
if (transaction_id != allArgs[TRANSACTION_ID_KEY] && !transaction_id.empty()) {
continue;
}
auto &handle = pr.second;
if (allArgs[METHOD_KEY] != handle.method) {
WarnL << "recv response method: " << allArgs[METHOD_KEY] << " mismatch request method: " << handle.method;
return false;
}
trigger = std::move(handle.cb);
_response_list.erase(transaction_id);
return true;
}
return false;
}
void WebRtcSignalingPeer::sendRegisterRequest(ResponseTrigger trigger) {
TraceL;
Json::Value body;
body[CLASS_KEY] = CLASS_VALUE_REQUEST;
body[METHOD_KEY] = METHOD_VALUE_REGISTER;
body[ROOM_ID_KEY] = getRoomId();
sendRequest(body, std::move(trigger));
}
void WebRtcSignalingPeer::handleRegisterAccept(SIGNALING_MSG_ARGS) {
TraceL;
ResponseTrigger trigger;
if (!responseFilter(allArgs, trigger)) {
return;
}
auto jsonArgs = allArgs.getArgs();
auto ice_servers = jsonArgs[ICE_SERVERS_KEY];
if (ice_servers.type() != Json::ValueType::arrayValue) {
_StrPrinter msg;
msg << "illegal \"" << ICE_SERVERS_KEY << "\" point";
WarnL << msg;
trigger(SockException(Err_other, msg), getRoomKey());
return;
}
if (ice_servers.empty()) {
_StrPrinter msg;
msg << "no ice server found in \"" << ICE_SERVERS_KEY << "\" point";
WarnL << msg;
trigger(SockException(Err_other, msg), getRoomKey());
return;
}
for (auto &ice_server : ice_servers) {
// only support 1 ice_server now
auto url = ice_server[URL_KEY].asString();
_ice_server = std::make_shared<RTC::IceServerInfo>(url);
_ice_server->_ufrag = ice_server[UFRAG_KEY].asString();
_ice_server->_pwd = ice_server[PWD_KEY].asString();
}
trigger(SockException(Err_success), getRoomKey());
}
void WebRtcSignalingPeer::handleRegisterReject(SIGNALING_MSG_ARGS) {
TraceL;
ResponseTrigger trigger;
if (!responseFilter(allArgs, trigger)) {
return;
}
auto ex = SockException(Err_other, StrPrinter << "register refuses by server, reason: " << allArgs[REASON_KEY]);
trigger(ex, getRoomKey());
onShutdown(ex);
}
void WebRtcSignalingPeer::sendUnregisterRequest(ResponseTrigger trigger) {
TraceL;
Json::Value body;
body[CLASS_KEY] = CLASS_VALUE_REQUEST;
body[METHOD_KEY] = METHOD_VALUE_UNREGISTER;
body[ROOM_ID_KEY] = _room_id;
sendRequest(body, std::move(trigger));
}
void WebRtcSignalingPeer::handleUnregisterAccept(SIGNALING_MSG_ARGS) {
ResponseTrigger trigger;
if (!responseFilter(allArgs, trigger)) {
return;
}
trigger(SockException(Err_success), getRoomKey());
}
void WebRtcSignalingPeer::handleUnregisterReject(SIGNALING_MSG_ARGS) {
ResponseTrigger trigger;
if (!responseFilter(allArgs, trigger)) {
return;
}
auto ex = SockException(Err_other, StrPrinter << "unregister refuses by server, reason: " << allArgs[REASON_KEY]);
trigger(ex, getRoomKey());
}
void WebRtcSignalingPeer::sendCallRequest(const std::string& peer_room_id, const std::string& guest_id, const MediaTuple &tuple, const std::string& sdp, bool is_play, ResponseTrigger trigger) {
DebugL;
Json::Value body;
body[CLASS_KEY] = CLASS_VALUE_REQUEST;
body[METHOD_KEY] = METHOD_VALUE_CALL;
body[TYPE_KEY] = is_play? TYPE_VALUE_PLAY : TYPE_VALUE_PUSH;
body[GUEST_ID_KEY] = guest_id; //our guest id
body[ROOM_ID_KEY] = peer_room_id;
body[CALL_VHOST_KEY] = tuple.vhost;
body[CALL_APP_KEY] = tuple.app;
body[CALL_STREAM_KEY] = tuple.stream;
body[SDP_KEY] = sdp;
sendRequest(body, std::move(trigger));
}
void WebRtcSignalingPeer::sendCallAccept(const std::string& peer_guest_id, const MediaTuple &tuple, const std::string& sdp, bool is_play, const std::string& transaction_id) {
DebugL;
Json::Value body;
body[CLASS_KEY] = CLASS_VALUE_ACCEPT;
body[METHOD_KEY] = METHOD_VALUE_CALL;
body[TRANSACTION_ID_KEY] = transaction_id;
body[TYPE_KEY] = is_play? TYPE_VALUE_PLAY : TYPE_VALUE_PUSH;
body[GUEST_ID_KEY] = peer_guest_id;
body[ROOM_ID_KEY] = _room_id; //our room id
body[CALL_VHOST_KEY] = tuple.vhost;
body[CALL_APP_KEY] = tuple.app;
body[CALL_STREAM_KEY] = tuple.stream;
body[SDP_KEY] = sdp;
sendPacket(body);
}
void WebRtcSignalingPeer::handleCallRequest(SIGNALING_MSG_ARGS) {
DebugL;
CHECK_ARGS(GUEST_ID_KEY, ROOM_ID_KEY, CALL_VHOST_KEY, CALL_APP_KEY, CALL_STREAM_KEY, TYPE_KEY);
if (allArgs[ROOM_ID_KEY] != getRoomId()) {
WarnL << "target room_id: " << allArgs[ROOM_ID_KEY] << "mismatch our room_id: " << getRoomId();
return;
}
auto args = std::make_shared<WebRtcArgsImp<Json::Value>>(allArgs, allArgs[GUEST_ID_KEY]);
std::weak_ptr<WebRtcSignalingPeer> weak_self = std::static_pointer_cast<WebRtcSignalingPeer>(shared_from_this());
WebRtcPluginManager::Instance().negotiateSdp(*this, allArgs[TYPE_KEY], *args, [allArgs, weak_self](const WebRtcInterface &exchanger) mutable {
if (auto strong_self = weak_self.lock()) {
strong_self->processOffer(allArgs, const_cast<WebRtcInterface &>(exchanger));
}
});
}
void WebRtcSignalingPeer::handleCallAccept(SIGNALING_MSG_ARGS) {
DebugL;
ResponseTrigger trigger;
if (!responseFilter(allArgs, trigger)) {
return;
}
CHECK_ARGS(GUEST_ID_KEY, ROOM_ID_KEY, CALL_VHOST_KEY, CALL_APP_KEY, CALL_STREAM_KEY, TYPE_KEY);
auto room_id = allArgs[ROOM_ID_KEY];
auto it = _tours.find(room_id);
if (it == _tours.end()) {
WarnL << "not found room_id: " << room_id << " in tours";
return;
}
auto &guest_id = it->second.first;
if (allArgs[GUEST_ID_KEY] != guest_id) {
WarnL << "guest_id: " << allArgs[GUEST_ID_KEY] << "mismatch our guest_id: " << guest_id;
return;
}
trigger(SockException(Err_success), allArgs[SDP_KEY]);
}
void WebRtcSignalingPeer::handleCallReject(SIGNALING_MSG_ARGS) {
DebugL;
ResponseTrigger trigger;
if (!responseFilter(allArgs, trigger)) {
return;
}
CHECK_ARGS(GUEST_ID_KEY, ROOM_ID_KEY, CALL_VHOST_KEY, CALL_APP_KEY, CALL_STREAM_KEY, TYPE_KEY);
auto room_id = allArgs[ROOM_ID_KEY];
auto it = _tours.find(room_id);
if (it == _tours.end()) {
WarnL << "not found room_id: " << room_id << " in tours";
return;
}
auto &guest_id = it->second.first;
if (allArgs[GUEST_ID_KEY] != guest_id) {
WarnL << "guest_id: " << allArgs[GUEST_ID_KEY] << "mismatch our guest_id: " << guest_id;
return;
}
_tours.erase(room_id);
trigger(SockException(Err_other, StrPrinter << "call refuses by server, reason: " << allArgs[REASON_KEY]), "");
}
void WebRtcSignalingPeer::handleCandidateIndication(SIGNALING_MSG_ARGS) {
DebugL;
CHECK_ARGS(GUEST_ID_KEY, ROOM_ID_KEY, CANDIDATE_KEY, UFRAG_KEY, PWD_KEY);
std::string identifier;
std::string room_id = allArgs[ROOM_ID_KEY];
std::string guest_id = allArgs[GUEST_ID_KEY];
//作为被叫
if (room_id == getRoomId()) {
auto it = _peer_guests.find(guest_id);
if (it == _peer_guests.end()) {
WarnL << "not found guest_id: " << guest_id;
return;
}
identifier = it->second;
} else {
//作为主叫
for (auto it : _tours) {
if (room_id != it.first) {
continue;
}
auto info = it.second;
if (guest_id != info.first) {
break;
}
identifier = info.second;
}
}
TraceL << "recv remote candidate: " << allArgs[CANDIDATE_KEY];
if (identifier.empty()) {
WarnL << "target room_id: " << room_id << " not match our room_id: " << getRoomId()
<< ", and target guest_id: " << guest_id << " not match";
return;
}
auto transport = WebRtcTransportManager::Instance().getItem(identifier);
if (transport) {
SdpAttrCandidate candidate_attr;
candidate_attr.parse(allArgs[CANDIDATE_KEY]);
transport->connectivityCheck(candidate_attr, allArgs[UFRAG_KEY], allArgs[PWD_KEY]);
}
}
void WebRtcSignalingPeer::handleByeIndication(SIGNALING_MSG_ARGS) {
DebugL;
CHECK_ARGS(GUEST_ID_KEY, ROOM_ID_KEY);
if (allArgs[ROOM_ID_KEY] != getRoomId()) {
WarnL << "target room_id: " << allArgs[ROOM_ID_KEY] << "not match our room_id: " << getRoomId();
return;
}
auto it = _peer_guests.find(allArgs[GUEST_ID_KEY]);
if (it == _peer_guests.end()) {
WarnL << "not found guest_id: " << allArgs[GUEST_ID_KEY];
return;
}
auto identifier = it->second;
_peer_guests.erase(it);
auto obj = WebRtcTransportManager::Instance().getItem(identifier);
if (obj) {
obj->safeShutdown(SockException(Err_shutdown, "deleted by websocket signaling server"));
}
}
void WebRtcSignalingPeer::sendByeIndication(const std::string& peer_room_id, const std::string &guest_id) {
DebugL;
Json::Value body;
body[CLASS_KEY] = CLASS_VALUE_INDICATION;
body[METHOD_KEY] = METHOD_VALUE_BYE;
body[GUEST_ID_KEY] = guest_id; //our guest id
body[ROOM_ID_KEY] = peer_room_id;
body[SENDER_KEY] = guest_id;
sendIndication(body);
}
void WebRtcSignalingPeer::sendCandidateIndication(const std::string& transport_identifier, const std::string& candidate, const std::string& ice_ufrag, const std::string& ice_pwd) {
TraceL;
Json::Value body;
body[CLASS_KEY] = CLASS_VALUE_INDICATION;
body[METHOD_KEY] = METHOD_VALUE_CANDIDATE;
body[CANDIDATE_KEY] = candidate;
body[UFRAG_KEY] = ice_ufrag;
body[PWD_KEY] = ice_pwd;
//作为被叫
for (auto &pr : _peer_guests) {
if (pr.second == transport_identifier) {
body[ROOM_ID_KEY] = _room_id;
body[GUEST_ID_KEY] = pr.first; //peer_guest_id
body[SENDER_KEY] = _room_id;
return sendIndication(body);
}
}
//作为主叫
for (auto &pr : _tours) {
auto &info = pr.second;
if (info.second == transport_identifier) {
body[ROOM_ID_KEY] = pr.first; //peer room id
body[GUEST_ID_KEY] = info.first; //our_guest_id
body[SENDER_KEY] = info.first;
return sendIndication(body);
}
}
}
void WebRtcSignalingPeer::sendAcceptResponse(const std::string& method, const std::string& transaction_id, const std::string& room_id,
const std::string& guest_id, const std::string& reason) {
// TODO
}
void WebRtcSignalingPeer::sendRefusesResponse(Json::Value &body, const std::string& transaction_id, const std::string& reason) {
body[CLASS_KEY] = CLASS_VALUE_REJECT;
body[REASON_KEY] = reason;
sendResponse(body, transaction_id);
}
void WebRtcSignalingPeer::sendRequest(Json::Value& body, ResponseTrigger trigger, float seconds) {
auto transaction_id = makeRandStr(32);
body[TRANSACTION_ID_KEY] = transaction_id;
ResponseTuple tuple;
tuple.ttl_ms = seconds * 1000;
tuple.method = body[METHOD_KEY].asString();
tuple.cb = std::move(trigger);
_response_list.emplace(std::move(transaction_id), std::move(tuple));
sendPacket(body);
}
void WebRtcSignalingPeer::sendIndication(Json::Value &body) {
auto transaction_id = makeRandStr(32);
body[TRANSACTION_ID_KEY] = transaction_id;
sendPacket(body);
}
void WebRtcSignalingPeer::sendResponse(Json::Value &body, const std::string& transaction_id) {
body[TRANSACTION_ID_KEY] = transaction_id;
sendPacket(body);
}
void WebRtcSignalingPeer::sendPacket(Json::Value& body) {
auto msg = body.toStyledString();
DebugL << "send msg: " << msg;
SockSender::send(std::move(msg));
}
Json::Value WebRtcSignalingPeer::makeInfoJson() {
Json::Value item;
item["room_id"] = getRoomId();
item["room_key"] = getRoomKey();
Json::Value peer_guests_obj(Json::arrayValue);
auto peer_guests = _peer_guests;
for(auto &guest : peer_guests) {
Json::Value obj;
obj["guest_id"] = guest.first;
obj["transport_identifier"] = guest.second;
peer_guests_obj.append(std::move(obj));
}
item["guests"] = std::move(peer_guests_obj);
Json::Value tours_obj(Json::arrayValue);
auto tours = _tours;
for(auto &tour : tours){
Json::Value obj;
obj["room_id"] = tour.first;
obj["guest_id"] = tour.second.first;
obj["transport_identifier"] = tour.second.second;
tours_obj.append(std::move(obj));
}
item["tours"] = std::move(tours_obj);
return item;
}
void WebRtcSignalingPeer::createResponseExpiredTimer() {
std::weak_ptr<WebRtcSignalingPeer> weak_self = std::static_pointer_cast<WebRtcSignalingPeer>(shared_from_this());
_expire_timer = std::make_shared<Timer>(0.2, [weak_self]() {
if (auto strong_self = weak_self.lock()) {
strong_self->checkResponseExpired();
return true; // 继续定时器
}
return false;
}, getPoller());
}
void WebRtcSignalingPeer::checkResponseExpired() {
//FIXME: 移动到专门的超时timer中处理
#if 0
// 设置计时器以检测 offer 响应超时
_offer_timeout_timer = std::make_shared<Timer>(
timeout_sec,
[this, cb, peer_room_id]() {
_tours.erase(peer_room_id);
return false; // 停止计时器
},
getPoller()
);
#endif
for (auto it = _response_list.begin(); it != _response_list.end();) {
auto &tuple = it->second;
if (!tuple.expired()) {
++it;
continue;
}
// over time
WarnL << "transaction_id: " << it->first << ", method: " << tuple.method << " recv response over time";
tuple.cb(SockException(Err_timeout, "recv response timeout"), "");
it = _response_list.erase(it);
}
}
}// namespace mediakit

View File

@ -0,0 +1,141 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_WEBRTC_SIGNALING_PEER_H
#define ZLMEDIAKIT_WEBRTC_SIGNALING_PEER_H
#include <chrono>
#include "Poller/Timer.h"
#include "Network/Session.h"
#include "Http/WebSocketClient.h"
#include "webrtc/WebRtcSignalingMsg.h"
#include "webrtc/WebRtcTransport.h"
namespace mediakit {
class WebRtcSignalingPeer : public WebSocketClient<toolkit::TcpClient> {
public:
struct ClassMethodHash {
bool operator()(std::pair<std::string /*class*/, std::string /*method*/> key) const {
std::size_t h = 0;
h ^= std::hash<std::string>()(key.first) << 0;
h ^= std::hash<std::string>()(key.second) << 1;
return h;
}
};
using Ptr = std::shared_ptr<WebRtcSignalingPeer>;
WebRtcSignalingPeer(const std::string &host, uint16_t port, bool ssl, const std::string &room_id, const toolkit::EventPoller::Ptr &poller = nullptr);
virtual ~WebRtcSignalingPeer();
void connect();
void regist(const std::function<void(const toolkit::SockException &ex, const std::string &key)> &cb);
void unregist(const std::function<void(const toolkit::SockException &ex)> &cb);
void checkIn(const std::string& peer_room_id, const MediaTuple &tuple, const std::string& identifier,
const std::string& offer, bool is_play, const std::function<void(const toolkit::SockException &ex, const std::string& answer)> &cb, float timeout_sec);
void checkOut(const std::string& peer_room_id);
void candidate(const std::string& transport_identifier, const std::string& candidate, const std::string& ice_ufrag, const std::string& ice_pwd);
void processOffer(SIGNALING_MSG_ARGS, WebRtcInterface &transport);
void answer(const std::string& guest_id, const MediaTuple &tuple, const std::string& identifier, const std::string& sdp, bool is_play, const std::string& transaction_id);
const std::string& getRoomKey() const {
return _room_key;
}
const std::string& getRoomId() const {
return _room_id;
}
const RTC::IceServerInfo::Ptr& getIceServer() const {
return _ice_server;
}
//// TcpClient override////
void setOnConnect(std::function<void(const toolkit::SockException &ex)> cb);
void onConnect(const toolkit::SockException &ex) override;
void setOnShutdown(std::function<void(const toolkit::SockException &ex)> cb);
void onShutdown(const toolkit::SockException &ex);
void onRecv(const toolkit::Buffer::Ptr &) override;
void onError(const toolkit::SockException &err) override;
Json::Value makeInfoJson();
protected:
void checkResponseExpired();
void createResponseExpiredTimer();
using ResponseTrigger = std::function<void(const toolkit::SockException &ex, std::string /*msg*/)>;
struct ResponseTuple {
toolkit::Ticker ticker;
uint32_t ttl_ms;
std::string method;
ResponseTrigger cb;
bool expired() {
return ticker.elapsedTime() > ttl_ms;
}
};
bool responseFilter(SIGNALING_MSG_ARGS, ResponseTrigger& trigger);
void sendRegisterRequest(ResponseTrigger trigger);
void handleRegisterAccept(SIGNALING_MSG_ARGS);
void handleRegisterReject(SIGNALING_MSG_ARGS);
void sendUnregisterRequest(ResponseTrigger trigger);
void handleUnregisterAccept(SIGNALING_MSG_ARGS);
void handleUnregisterReject(SIGNALING_MSG_ARGS);
void sendCallRequest(const std::string& peer_room_id, const std::string& guest_id, const MediaTuple &tuple, const std::string& sdp, bool is_play, ResponseTrigger trigger);
void sendCallAccept(const std::string& peer_guest_id, const MediaTuple &tuple, const std::string& sdp, bool is_play, const std::string& transaction_id);
void handleCallRequest(SIGNALING_MSG_ARGS);
void handleCallAccept(SIGNALING_MSG_ARGS);
void handleCallReject(SIGNALING_MSG_ARGS);
void sendCandidateIndication(const std::string& transport_identifier, const std::string& candidate, const std::string& ice_ufrag, const std::string& ice_pwd);
void handleCandidateIndication(SIGNALING_MSG_ARGS);
void sendByeIndication(const std::string& peer_room_id, const std::string &guest_id);
void handleByeIndication(SIGNALING_MSG_ARGS);
void sendAcceptResponse(const std::string& method, const std::string& transaction_id, const std::string& room_id, const std::string& guest_id, const std::string& reason);
void sendRefusesResponse(Json::Value &body, const std::string& transaction_id, const std::string& reason);
void sendIndication(Json::Value &body);
void sendRequest(Json::Value& body, ResponseTrigger trigger, float seconds = 10);
void sendResponse(Json::Value &body, const std::string& transaction_id);
void sendPacket(Json::Value& body);
private:
toolkit::Timer::Ptr _expire_timer;
std::string _ws_url;
std::string _room_key;
std::string _room_id;
std::unordered_map<std::string /*peer_guest_id*/, std::string /*transport_identifier*/> _peer_guests; //作为被叫
std::unordered_map<std::string /*peer_room_id*/, std::pair<std::string /*guest_id*/, std::string /*transport_identifier*/>> _tours; //作为主叫
RTC::IceServerInfo::Ptr _ice_server;
std::unordered_map<std::string /*transcation ID*/, ResponseTuple> _response_list;
std::function<void(const toolkit::SockException &ex)> _on_connect;
std::function<void(const toolkit::SockException &ex)> _on_shutdown;
toolkit::Timer::Ptr _offer_timeout_timer = nullptr;
};
void addWebrtcRoomKeeper(const std::string &host, uint16_t port, const std::string& room_id, bool ssl,
const std::function<void(const toolkit::SockException &ex, const std::string &key)> &cb);
void delWebrtcRoomKeeper(const std::string &key, const std::function<void(const toolkit::SockException &ex)> &cb);
void listWebrtcRoomKeepers(const std::function<void(const std::string& key, const WebRtcSignalingPeer::Ptr& p)> &cb);
Json::Value ToJson(const WebRtcSignalingPeer::Ptr& p);
WebRtcSignalingPeer::Ptr getWebrtcRoomKeeper(const std::string &host, uint16_t port);
} // namespace mediakit
#endif // ZLMEDIAKIT_WEBRTC_SIGNALING_PEER_H

View File

@ -0,0 +1,488 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "Util/util.h"
#include "Common/config.h"
#include "WebRtcTransport.h"
#include "WebRtcSignalingMsg.h"
#include "WebRtcSignalingSession.h"
using namespace std;
using namespace toolkit;
using namespace mediakit::Rtc;
namespace mediakit {
// 注册上来的peer列表
static std::atomic<uint32_t> s_room_idx_generate { 1 };
static ServiceController<WebRtcSignalingSession> s_rooms;
void listWebrtcRooms(const std::function<void(const std::string &key, const WebRtcSignalingSession::Ptr &p)> &cb) {
s_rooms.for_each(cb);
}
Json::Value ToJson(const WebRtcSignalingSession::Ptr &p) {
return p->makeInfoJson();
}
WebRtcSignalingSession::Ptr getWebrtcRoomKeeper(const string &room_id) {
return s_rooms.find(room_id);
}
//////////// WebRtcSignalingSession //////////////////////////
WebRtcSignalingSession::WebRtcSignalingSession(const Socket::Ptr &sock) : Session(sock) {
DebugL;
}
WebRtcSignalingSession::~WebRtcSignalingSession() {
DebugL << "room_id: " << _room_id;
}
void WebRtcSignalingSession::onRecv(const Buffer::Ptr &buffer) {
DebugL << "recv msg:\r\n" << buffer->data();
Json::Value args;
Json::Reader reader;
reader.parse(buffer->data(), args);
Parser parser;
HttpAllArgs<decltype(args)> allArgs(parser, args);
using MsgHandler = void (WebRtcSignalingSession::*)(SIGNALING_MSG_ARGS);
static std::unordered_map<std::pair<std::string /*class*/, std::string /*method*/>, MsgHandler, ClassMethodHash> s_msg_handlers;
static onceToken token([]() {
s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_REQUEST, METHOD_VALUE_REGISTER), &WebRtcSignalingSession::handleRegisterRequest);
s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_REQUEST, METHOD_VALUE_UNREGISTER), &WebRtcSignalingSession::handleUnregisterRequest);
s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_REQUEST, METHOD_VALUE_CALL), &WebRtcSignalingSession::handleCallRequest);
s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_ACCEPT, METHOD_VALUE_CALL), &WebRtcSignalingSession::handleCallAccept);
s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_REJECT, METHOD_VALUE_CALL), &WebRtcSignalingSession::handleCallReject);
s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_INDICATION, METHOD_VALUE_BYE), &WebRtcSignalingSession::handleByeIndication);
s_msg_handlers.emplace(std::make_pair(CLASS_VALUE_INDICATION, METHOD_VALUE_CANDIDATE), &WebRtcSignalingSession::handleCandidateIndication);
});
try {
CHECK_ARGS(CLASS_KEY, METHOD_KEY, TRANSACTION_ID_KEY);
auto it = s_msg_handlers.find(std::make_pair(allArgs[CLASS_KEY], allArgs[METHOD_KEY]));
if (it == s_msg_handlers.end()) {
WarnL << " not support class: " << allArgs[CLASS_KEY] << ", method: " << allArgs[METHOD_KEY] << ", ignore";
return;
}
(this->*(it->second))(allArgs);
} catch (std::exception &ex) {
WarnL << "process msg fail: " << ex.what();
}
}
void WebRtcSignalingSession::onError(const SockException &err) {
WarnL << "room_id: " << _room_id;
notifyByeIndication();
s_rooms.erase(_room_id);
}
void WebRtcSignalingSession::onManager() {
// Websocket会话会自行定时发送PING/PONG 消息,并进行超时自己管理,该对象暂时不需要心跳超时处理
}
void WebRtcSignalingSession::handleRegisterRequest(SIGNALING_MSG_ARGS) {
DebugL;
std::string room_id;
Json::Value body;
body[METHOD_KEY] = METHOD_VALUE_REGISTER;
// 如果客户端没有提供 room_id服务端自动分配一个
if (allArgs[ROOM_ID_KEY].empty()) {
auto idx = s_room_idx_generate.fetch_add(1);
room_id = std::to_string(idx) + "_" + makeRandStr(16);
DebugL << "auto generated room_id: " << room_id;
} else {
room_id = allArgs[ROOM_ID_KEY];
if (s_rooms.find(room_id)) {
// 已经注册了
body[ROOM_ID_KEY] = room_id;
return sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], "room id conflict");
}
}
body[ROOM_ID_KEY] = room_id;
_room_id = room_id;
s_rooms.emplace(_room_id, shared_from_this());
sendRegisterAccept(body, allArgs[TRANSACTION_ID_KEY]);
}
void WebRtcSignalingSession::handleUnregisterRequest(SIGNALING_MSG_ARGS) {
DebugL;
CHECK_ARGS(ROOM_ID_KEY);
Json::Value body;
body[METHOD_KEY] = METHOD_VALUE_UNREGISTER;
body[ROOM_ID_KEY] = allArgs[ROOM_ID_KEY];
if (_room_id.empty()) {
return sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], "unregistered");
}
if (allArgs[ROOM_ID_KEY] != getRoomId()) {
return sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], StrPrinter << "room_id: \"" << allArgs[ROOM_ID_KEY] << "\" not match room_id:" << getRoomId());
}
sendAcceptResponse(body, allArgs[TRANSACTION_ID_KEY]);
// 同时主动向所有连接的对端会话发送bye
notifyByeIndication();
if (s_rooms.find(_room_id)) {
s_rooms.erase(_room_id);
}
}
void WebRtcSignalingSession::handleCallRequest(SIGNALING_MSG_ARGS) {
DebugL;
CHECK_ARGS(TRANSACTION_ID_KEY, GUEST_ID_KEY, ROOM_ID_KEY, CALL_VHOST_KEY, CALL_APP_KEY, CALL_STREAM_KEY, TYPE_KEY, SDP_KEY);
Json::Value body;
body[METHOD_KEY] = METHOD_VALUE_CALL;
body[ROOM_ID_KEY] = allArgs[ROOM_ID_KEY];
body[GUEST_ID_KEY] = allArgs[GUEST_ID_KEY];
body[CALL_VHOST_KEY] = allArgs[CALL_VHOST_KEY];
body[CALL_APP_KEY] = allArgs[CALL_APP_KEY];
body[CALL_STREAM_KEY] = allArgs[CALL_STREAM_KEY];
body[TYPE_KEY] = allArgs[TYPE_KEY];
if (_room_id.empty()) {
return sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], "should register first");
}
auto peer_id = allArgs[ROOM_ID_KEY];
auto session = getWebrtcRoomKeeper(peer_id);
if (!session) {
return sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], StrPrinter << "room_id: \"" << peer_id << "\" unregistered");
}
_tours.emplace(allArgs[GUEST_ID_KEY], peer_id);
// forwardOffer
weak_ptr<WebRtcSignalingSession> sender_ptr = static_pointer_cast<WebRtcSignalingSession>(shared_from_this());
session->forwardCallRequest(sender_ptr, allArgs);
}
void WebRtcSignalingSession::handleCallAccept(SIGNALING_MSG_ARGS) {
DebugL;
CHECK_ARGS(GUEST_ID_KEY, ROOM_ID_KEY, CALL_VHOST_KEY, CALL_APP_KEY, CALL_STREAM_KEY);
Json::Value body;
body[ROOM_ID_KEY] = allArgs[ROOM_ID_KEY];
if (_room_id.empty()) {
return sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], "should register first");
}
auto it = _guests.find(allArgs[GUEST_ID_KEY]);
if (it == _guests.end()) {
WarnL << "guest_id: \"" << allArgs[GUEST_ID_KEY] << "\" not register";
return;
}
auto session = it->second.lock();
if (!session) {
WarnL << "guest_id: \"" << allArgs[GUEST_ID_KEY] << "\" leave alreadly";
return;
}
session->forwardCallAccept(allArgs);
}
void WebRtcSignalingSession::handleByeIndication(SIGNALING_MSG_ARGS) {
DebugL;
CHECK_ARGS(GUEST_ID_KEY, ROOM_ID_KEY);
auto guest_id = allArgs[GUEST_ID_KEY];
Json::Value body;
body[METHOD_KEY] = METHOD_VALUE_BYE;
body[ROOM_ID_KEY] = allArgs[ROOM_ID_KEY];
body[GUEST_ID_KEY] = guest_id;
if (_room_id.empty()) {
return sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], "should register first");
}
if (allArgs[ROOM_ID_KEY] == getRoomId()) {
// 作为被叫方,接收bye
auto it = _guests.find(guest_id);
if (it == _guests.end()) {
WarnL << "guest_id: \"" << guest_id << "\" not register";
return;
}
auto session = it->second.lock();
if (!session) {
WarnL << "guest_id: \"" << guest_id << "\" leave alreadly";
return;
}
_guests.erase(guest_id);
session->forwardBye(allArgs);
} else {
// 作为主叫方接受bye
auto session = getWebrtcRoomKeeper(allArgs[ROOM_ID_KEY]);
if (!session) {
WarnL << "room_id: \"" << allArgs[ROOM_ID_KEY] << "\" not register";
return;
}
_tours.erase(guest_id);
session->forwardBye(allArgs);
}
}
void WebRtcSignalingSession::handleCandidateIndication(SIGNALING_MSG_ARGS) {
DebugL;
CHECK_ARGS(TRANSACTION_ID_KEY, GUEST_ID_KEY, ROOM_ID_KEY, CANDIDATE_KEY, UFRAG_KEY, PWD_KEY);
Json::Value body;
body[METHOD_KEY] = METHOD_VALUE_CANDIDATE;
body[ROOM_ID_KEY] = allArgs[ROOM_ID_KEY];
if (_room_id.empty()) {
sendRejectResponse(body, allArgs[TRANSACTION_ID_KEY], "should register first");
} else {
handleOtherMsg(allArgs);
}
}
void WebRtcSignalingSession::handleOtherMsg(SIGNALING_MSG_ARGS) {
DebugL;
if (allArgs[ROOM_ID_KEY] == getRoomId()) {
// 作为被叫方,接收bye
auto guest_id = allArgs[GUEST_ID_KEY];
auto it = _guests.find(guest_id);
if (it == _guests.end()) {
WarnL << "guest_id: \"" << guest_id << "\" not register";
return;
}
auto session = it->second.lock();
if (!session) {
WarnL << "guest_id: \"" << guest_id << "\" leave alreadly";
return;
}
session->forwardPacket(allArgs);
} else {
// 作为主叫方接受bye
auto session = getWebrtcRoomKeeper(allArgs[ROOM_ID_KEY]);
if (!session) {
WarnL << "room_id: \"" << allArgs[ROOM_ID_KEY] << "\" not register";
return;
}
session->forwardPacket(allArgs);
}
}
void WebRtcSignalingSession::notifyByeIndication() {
DebugL;
Json::Value allArgs;
allArgs[CLASS_KEY] = CLASS_VALUE_INDICATION;
allArgs[METHOD_KEY] = METHOD_VALUE_BYE;
allArgs[REASON_KEY] = "peer unregister";
// 作为被叫方
for (auto it : _guests) {
auto session = it.second.lock();
if (session) {
allArgs[TRANSACTION_ID_KEY] = makeRandStr(32);
allArgs[GUEST_ID_KEY] = it.first;
allArgs[ROOM_ID_KEY] = getRoomId();
session->forwardBye(allArgs);
}
}
// 作为主叫方
for (auto it : _tours) {
auto guest_id = it.first;
auto peer_room_id = it.second;
auto session = getWebrtcRoomKeeper(peer_room_id);
if (session) {
allArgs[TRANSACTION_ID_KEY] = makeRandStr(32);
allArgs[GUEST_ID_KEY] = guest_id;
allArgs[ROOM_ID_KEY] = peer_room_id;
session->forwardBye(allArgs);
}
}
}
void WebRtcSignalingSession::forwardCallRequest(WebRtcSignalingSession::WeakPtr sender, SIGNALING_MSG_ARGS) {
DebugL;
WeakPtr weak_self = std::static_pointer_cast<WebRtcSignalingSession>(shared_from_this());
getPoller()->async([weak_self, sender, allArgs]() {
if (auto strong_self = weak_self.lock()) {
strong_self->_guests.emplace(allArgs[GUEST_ID_KEY], sender);
strong_self->sendPacket(allArgs.getArgs());
}
});
}
void WebRtcSignalingSession::forwardCallAccept(SIGNALING_MSG_ARGS) {
DebugL;
WeakPtr weak_self = std::static_pointer_cast<WebRtcSignalingSession>(shared_from_this());
getPoller()->async([weak_self, allArgs]() {
if (auto strong_self = weak_self.lock()) {
strong_self->sendPacket(allArgs.getArgs());
}
});
}
void WebRtcSignalingSession::forwardBye(SIGNALING_MSG_ARGS) {
DebugL;
WeakPtr weak_self = std::static_pointer_cast<WebRtcSignalingSession>(shared_from_this());
getPoller()->async([weak_self, allArgs]() {
if (auto strong_self = weak_self.lock()) {
if (allArgs[ROOM_ID_KEY] == strong_self->getRoomId()) {
// 作为被叫
strong_self->_guests.erase(allArgs[GUEST_ID_KEY]);
} else {
// 作为主叫
strong_self->_tours.erase(allArgs[GUEST_ID_KEY]);
}
strong_self->sendPacket(allArgs.getArgs());
}
});
}
void WebRtcSignalingSession::forwardBye(Json::Value allArgs) {
DebugL;
WeakPtr weak_self = std::static_pointer_cast<WebRtcSignalingSession>(shared_from_this());
getPoller()->async([weak_self, allArgs]() {
if (auto strong_self = weak_self.lock()) {
if (allArgs[ROOM_ID_KEY] == strong_self->getRoomId()) {
// 作为被叫
strong_self->_guests.erase(allArgs[GUEST_ID_KEY].asString());
} else {
// 作为主叫
strong_self->_tours.erase(allArgs[GUEST_ID_KEY].asString());
}
strong_self->sendPacket(allArgs);
}
});
}
void WebRtcSignalingSession::forwardPacket(SIGNALING_MSG_ARGS) {
WeakPtr weak_self = std::static_pointer_cast<WebRtcSignalingSession>(shared_from_this());
getPoller()->async([weak_self, allArgs]() {
if (auto strong_self = weak_self.lock()) {
strong_self->sendPacket(allArgs.getArgs());
}
});
}
void WebRtcSignalingSession::sendRegisterAccept(Json::Value& body, const std::string& transaction_id) {
DebugL;
body[CLASS_KEY] = CLASS_VALUE_ACCEPT;
Json::Value ice_server;
GET_CONFIG(uint16_t, icePort, Rtc::kIcePort);
GET_CONFIG(bool, enable_turn, Rtc::kEnableTurn);
GET_CONFIG(string, iceUfrag, Rtc::kIceUfrag);
GET_CONFIG(string, icePwd, Rtc::kIcePwd);
GET_CONFIG_FUNC(std::vector<std::string>, extern_ips, Rtc::kExternIP, [](string str) {
std::vector<std::string> ret;
if (str.length()) {
ret = split(str, ",");
}
translateIPFromEnv(ret);
return ret;
});
// 如果配置了extern_ips, 则选择第一个作为turn服务器的ip
// 如果没配置获取网卡接口
std::string extern_ip;
if (!extern_ips.empty()) {
extern_ip = extern_ips.front();
} else {
extern_ip = SockUtil::get_local_ip();
}
// TODO: support multi extern ip
// TODO: support third stun/turn server
std::string url;
// SUPPORT:
// stun:host:port?transport=udp
// turn:host:port?transport=udp
// NOT SUPPORT NOW TODO:
// turns:host:port?transport=udp
// turn:host:port?transport=tcp
// turns:host:port?transport=tcp
// stuns:host:port?transport=udp
// stuns:host:port?transport=udp
// stun:host:port?transport=tcp
if (enable_turn) {
url = "turn:" + extern_ip + ":" + std::to_string(icePort) + "?transport=udp";
} else {
url = "stun:" + extern_ip + ":" + std::to_string(icePort) + "?transport=udp";
}
ice_server[URL_KEY] = url;
ice_server[UFRAG_KEY] = iceUfrag;
ice_server[PWD_KEY] = icePwd;
Json::Value ice_servers;
ice_servers.append(ice_server);
body[ICE_SERVERS_KEY] = ice_servers;
sendAcceptResponse(body, transaction_id);
}
void WebRtcSignalingSession::sendAcceptResponse(Json::Value &body, const std::string &transaction_id) {
TraceL;
body[CLASS_KEY] = CLASS_VALUE_ACCEPT;
return sendResponse(body, transaction_id);
}
void WebRtcSignalingSession::sendRejectResponse(Json::Value &body, const std::string &transaction_id, const std::string &reason) {
DebugL;
body[CLASS_KEY] = CLASS_VALUE_REJECT;
body[REASON_KEY] = reason;
return sendResponse(body, transaction_id);
}
void WebRtcSignalingSession::sendResponse(Json::Value &body, const std::string &transaction_id) {
DebugL;
body[TRANSACTION_ID_KEY] = transaction_id;
return sendPacket(body);
}
void WebRtcSignalingSession::sendPacket(const Json::Value &body) {
auto msg = body.toStyledString();
TraceL << "send msg: " << msg;
SockSender::send(msg);
}
Json::Value WebRtcSignalingSession::makeInfoJson() {
Json::Value item;
item["room_id"] = getRoomId();
Json::Value tours_obj(Json::arrayValue);
auto tours = _tours;
for (auto &tour : tours) {
Json::Value obj;
obj["guest_id"] = tour.first;
obj["room_id"] = tour.second;
tours_obj.append(std::move(obj));
}
item["tours"] = std::move(tours_obj);
Json::Value guests_obj(Json::arrayValue);
auto guests = _guests;
for (auto &guest : guests) {
Json::Value obj;
obj["guest_id"] = guest.first;
guests_obj.append(std::move(obj));
}
item["guests"] = std::move(guests_obj);
return item;
}
} // namespace mediakit

View File

@ -0,0 +1,85 @@
/*
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
*
* Use of this source code is governed by MIT-like license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_WEBRTC_SIGNALING_SESSION_H
#define ZLMEDIAKIT_WEBRTC_SIGNALING_SESSION_H
#include "Network/Session.h"
#include "Http/WebSocketSession.h"
#include "webrtc/WebRtcSignalingMsg.h"
namespace mediakit {
// webrtc 信令, 基于websocket实现
class WebRtcSignalingSession : public toolkit::Session {
public:
struct ClassMethodHash {
bool operator()(std::pair<std::string /*class*/, std::string /*method*/> key) const {
std::size_t h = 0;
h ^= std::hash<std::string>()(key.first) << 0;
h ^= std::hash<std::string>()(key.second) << 1;
return h;
}
};
using Ptr = std::shared_ptr<WebRtcSignalingSession>;
using WeakPtr = std::weak_ptr<WebRtcSignalingSession>;
WebRtcSignalingSession(const toolkit::Socket::Ptr &sock);
virtual ~WebRtcSignalingSession();
Json::Value makeInfoJson();
std::string getRoomId() { return _room_id; };
//// Session override////
void onRecv(const toolkit::Buffer::Ptr &) override;
void onError(const toolkit::SockException &err) override;
void onManager() override;
protected:
void handleRegisterRequest(SIGNALING_MSG_ARGS);
void handleUnregisterRequest(SIGNALING_MSG_ARGS);
void handleCallRequest(SIGNALING_MSG_ARGS);
void handleCallAccept(SIGNALING_MSG_ARGS);
#define handleCallReject handleCallAccept
void handleByeIndication(SIGNALING_MSG_ARGS);
void handleCandidateIndication(SIGNALING_MSG_ARGS);
void handleOtherMsg(SIGNALING_MSG_ARGS);
void notifyByeIndication();
void forwardCallRequest(WebRtcSignalingSession::WeakPtr sender, SIGNALING_MSG_ARGS);
void forwardCallAccept(SIGNALING_MSG_ARGS);
void forwardBye(SIGNALING_MSG_ARGS);
void forwardBye(Json::Value allArgs);
void forwardPacket(SIGNALING_MSG_ARGS);
void sendRegisterAccept(Json::Value& body, const std::string& transaction_id);
void sendAcceptResponse(Json::Value &body, const std::string& transaction_id);
void sendRejectResponse(Json::Value &body, const std::string& transaction_id, const std::string& reason);
void sendResponse(Json::Value &body, const std::string& transaction_id);
void sendPacket(const Json::Value &body);
private:
std::string _room_id; //
std::unordered_map<std::string /*guest id*/, std::string /*peer_room_id*/> _tours; //作为主叫
std::unordered_map<std::string /*peer_guest_id*/, WebRtcSignalingSession::WeakPtr /*session*/> _guests; //作为被叫
};
using WebRtcWebcosktSignalingSession = WebSocketSession<WebRtcSignalingSession, HttpSession>;
using WebRtcWebcosktSignalSslSession = WebSocketSession<WebRtcSignalingSession, HttpsSession>;
void listWebrtcRooms(const std::function<void(const std::string& key, const WebRtcSignalingSession::Ptr& p)> &cb);
Json::Value ToJson(const WebRtcSignalingSession::Ptr& p);
WebRtcSignalingSession::Ptr getWebrtcRoomKeeper(const std::string &room_id);
}// namespace mediakit
#endif //ZLMEDIAKIT_WEBRTC_SIGNALING_SESSION_H

View File

@ -9,6 +9,9 @@
*/
#include <iostream>
#include <functional>
#include <algorithm>
#include <cctype>
#include <srtp2/srtp.h>
#include "Util/base64.h"
#include "Network/sockutil.h"
@ -35,6 +38,7 @@
using namespace std;
using namespace toolkit;
namespace mediakit {
// RTC配置项目 [AUTO-TRANSLATED:19940011]
@ -47,6 +51,7 @@ const string kTimeOutSec = RTC_FIELD "timeoutSec";
// 服务器外网ip [AUTO-TRANSLATED:23283ba6]
// Server external network ip
const string kExternIP = RTC_FIELD "externIP";
const string kInterfaces = RTC_FIELD "interfaces";
// 设置remb比特率非0时关闭twcc并开启remb。该设置在rtc推流时有效可以控制推流画质 [AUTO-TRANSLATED:412801db]
// Set remb bitrate, when it is not 0, turn off twcc and turn on remb. This setting is valid when rtc pushes the stream, and can control the pushing stream quality
const string kRembBitRate = RTC_FIELD "rembBitRate";
@ -54,6 +59,18 @@ const string kRembBitRate = RTC_FIELD "rembBitRate";
// webrtc single-port udp server
const string kPort = RTC_FIELD "port";
const string kTcpPort = RTC_FIELD "tcpPort";
// webrtc SignalingServerPort udp server
const string kSignalingPort = RTC_FIELD "signalingPort";
const string kSignalingSslPort = RTC_FIELD "signalingSslPort";
// webrtc iceServer udp server
const string kIcePort = RTC_FIELD "icePort";
const string kIceTcpPort = RTC_FIELD "iceTcpPort";
// webrtc enable turn or only enable stun
const string kEnableTurn = RTC_FIELD "enableTurn";
// webrtc ice ufrag and pwd [AUTO-TRANSLATED:2f0d1b3c]
const string kIceUfrag = RTC_FIELD "iceUfrag";
const string kIcePwd = RTC_FIELD "icePwd";
const string kIceTransportPolicy = RTC_FIELD "iceTransportPolicy";
// 比特率设置 [AUTO-TRANSLATED:2c75f5bc]
// Bitrate setting
@ -68,6 +85,7 @@ const string kDataChannelEcho = RTC_FIELD "datachannel_echo";
static onceToken token([]() {
mINI::Instance()[kTimeOutSec] = 15;
mINI::Instance()[kExternIP] = "";
mINI::Instance()[kInterfaces] = "";
mINI::Instance()[kRembBitRate] = 0;
mINI::Instance()[kPort] = 8000;
mINI::Instance()[kTcpPort] = 8000;
@ -77,27 +95,21 @@ static onceToken token([]() {
mINI::Instance()[kMinBitrate] = 0;
mINI::Instance()[kDataChannelEcho] = true;
mINI::Instance()[kSignalingPort] = 3000;
mINI::Instance()[kSignalingSslPort] = 3001;
mINI::Instance()[kIcePort] = 3478;
mINI::Instance()[kIceTcpPort] = 3478;
mINI::Instance()[kEnableTurn] = 1;
mINI::Instance()[kIceTransportPolicy] = 0; // 默认值:不限制(kAll)
mINI::Instance()[kIceUfrag] = "ZLMediaKit";
mINI::Instance()[kIcePwd] = "ZLMediaKit";
});
} // namespace RTC
} // namespace Rtc
static atomic<uint64_t> s_key { 0 };
static void translateIPFromEnv(std::vector<std::string> &v) {
for (auto iter = v.begin(); iter != v.end();) {
if (start_with(*iter, "$")) {
auto ip = toolkit::getEnv(*iter);
if (ip.empty()) {
iter = v.erase(iter);
} else {
*iter++ = ip;
}
} else {
++iter;
}
}
}
static std::string getServerPrefix() {
// stun_user_name格式: base64(ip+udp_port+tcp_port) + _ + number [AUTO-TRANSLATED:cc3c5902]
// stun_user_name format: base64(ip+udp_port+tcp_port) + _ + number
@ -127,15 +139,133 @@ static std::string getServerPrefix() {
return ret;
}
const char* sockTypeStr(Session* session) {
if (session) {
switch (session->getSock()->sockType()) {
case SockNum::Sock_TCP: return "tcp";
case SockNum::Sock_UDP: return "udp";
static std::string mappingCandidateTypeEnum2Str(CandidateInfo::AddressType type) {
switch (type) {
case CandidateInfo::AddressType::HOST: return "host";
case CandidateInfo::AddressType::SRFLX: return "srflx";
case CandidateInfo::AddressType::PRFLX: return "prflx";
case CandidateInfo::AddressType::RELAY: return "relay";
default: break;
}
return "invalid";
}
static CandidateInfo::AddressType mappingCandidateTypeStr2Enum(const std::string &type) {
if (strcasecmp(type.c_str(), "host") == 0) {
return CandidateInfo::AddressType::HOST;
}
if (strcasecmp(type.c_str(), "srflx") == 0) {
return CandidateInfo::AddressType::SRFLX;
}
if (strcasecmp(type.c_str(), "prflx") == 0) {
return CandidateInfo::AddressType::PRFLX;
}
if (strcasecmp(type.c_str(), "relay") == 0) {
return CandidateInfo::AddressType::RELAY;
}
return CandidateInfo::AddressType::INVALID;
}
// 根据RFC 5245标准计算foundation
// 1. IP地址类型IPv4/IPv6
// 2. 传输协议UDP/TCP
// 3. 候选类型host/srflx/prflx/relay
// 4. STUN/TURN服务器地址对于srflx和relay类型
static std::string calculateFoundation(const std::string& ip, const std::string& proto, const std::string& type, const std::string& stun_server = "") {
// 将协议和类型转换为小写以确保一致性
std::string proto_lower = proto;
std::string type_lower = type;
std::transform(proto_lower.begin(), proto_lower.end(), proto_lower.begin(), ::tolower);
std::transform(type_lower.begin(), type_lower.end(), type_lower.begin(), ::tolower);
std::string foundation_base = type_lower + "-" + ip + "-" + proto_lower;
// 对于server reflexive和relay候选需要包含STUN/TURN服务器地址
if ((type_lower == "srflx" || type_lower == "relay") && !stun_server.empty()) {
foundation_base += "-" + stun_server;
}
std::hash<std::string> hasher;
size_t hash_value = hasher(foundation_base);
char foundation_str[9];
snprintf(foundation_str, sizeof(foundation_str), "%08x", (unsigned int)(hash_value & 0xFFFFFFFF));
return foundation_str;
}
static SdpAttrCandidate::Ptr makeIceCandidate(std::string ip, uint16_t port, uint32_t priority = 100,
const std::string &proto = "udp", const std::string &type = "host",
const std::string &base_host = "", uint16_t base_port = 0, const std::string &stun_server = "") {
auto candidate = std::make_shared<SdpAttrCandidate>();
candidate->foundation = calculateFoundation(ip, proto, type, stun_server);
candidate->component = 1;
candidate->transport = proto;
candidate->priority = priority;
candidate->address = std::move(ip);
candidate->port = port;
candidate->type = type;
if (strcasecmp(proto.c_str(), "tcp") == 0) {
candidate->type += " tcptype passive";
}
if (type != "host" && !base_host.empty() && base_port > 0) {
candidate->arr.emplace_back("raddr", base_host);
candidate->arr.emplace_back("rport", std::to_string(base_port));
}
return candidate;
}
static CandidateInfo::Ptr makeCandidateInfoBySdpAttr(const SdpAttrCandidate& candidate_attr, const std::string& ufrag, const std::string& pwd) {
auto candidate = std::make_shared<CandidateInfo>();
candidate->_type = mappingCandidateTypeStr2Enum(candidate_attr.type);
candidate->_priority = candidate_attr.priority;
candidate->_addr._host = candidate_attr.address;
candidate->_addr._port = candidate_attr.port;
candidate->_base_addr._host = candidate->_addr._host;
candidate->_base_addr._port = candidate->_addr._port;
candidate->_priority = candidate_attr.priority;
candidate->_ufrag = ufrag;
candidate->_pwd = pwd;
if (CandidateInfo::AddressType::HOST == candidate->_type) {
candidate->_base_addr = candidate->_addr;
} else {
for (auto &pr : candidate_attr.arr) {
if (pr.first == "raddr") {
candidate->_base_addr._host = pr.second;
}
if (pr.first == "rport") {
candidate->_base_addr._port = atoi(pr.second.data());
}
}
}
if (strcasecmp(candidate_attr.transport.c_str(), "udp") == 0) {
candidate->_transport = CandidateTuple::TransportType::UDP;
candidate->_secure = CandidateTuple::SecureType::NOT_SECURE;
} else if (strcasecmp(candidate_attr.transport.c_str(), "tcp") == 0) {
candidate->_transport = CandidateTuple::TransportType::TCP;
candidate->_secure = CandidateTuple::SecureType::NOT_SECURE;
}
return candidate;
}
const char* WebRtcTransport::SignalingProtocolsStr(SignalingProtocols protocol) {
switch (protocol) {
case SignalingProtocols::WHEP_WHIP: return "whep_whip";
case SignalingProtocols::WEBSOCKET: return "websocket";
default: return "invalid";
}
}
const char* WebRtcTransport::RoleStr(Role role) {
switch (role) {
case Role::CLIENT: return "client";
case Role::PEER: return "peer";
default: return "none";
}
return "unknown";
}
WebRtcTransport::WebRtcTransport(const EventPoller::Ptr &poller) {
@ -147,7 +277,18 @@ WebRtcTransport::WebRtcTransport(const EventPoller::Ptr &poller) {
void WebRtcTransport::onCreate() {
_dtls_transport = std::make_shared<RTC::DtlsTransport>(_poller, this);
_ice_server = std::make_shared<RTC::IceServer>(this, _identifier, makeRandStr(24));
IceAgent::Role role = IceAgent::Role::Controlling;
IceAgent::Implementation implementation = IceAgent::Implementation::Full;
if (_role == Role::PEER) {
role = IceAgent::Role::Controlled;
if (_signaling_protocols == SignalingProtocols::WHEP_WHIP) {
implementation = IceAgent::Implementation::Lite;
}
}
_ice_agent = std::make_shared<RTC::IceAgent>(this, implementation, role, _identifier, makeRandStr(24), getPoller());
_ice_agent->initialize();
}
void WebRtcTransport::onDestory() {
@ -155,11 +296,7 @@ void WebRtcTransport::onDestory() {
_sctp = nullptr;
#endif
_dtls_transport = nullptr;
_ice_server = nullptr;
}
const EventPoller::Ptr &WebRtcTransport::getPoller() const {
return _poller;
_ice_agent = nullptr;
}
const string &WebRtcTransport::getIdentifier() const {
@ -173,38 +310,130 @@ const std::string& WebRtcTransport::deleteRandStr() const {
return _delete_rand_str;
}
void WebRtcTransport::getTransportInfo(const std::function<void(Json::Value)>& callback) const {
if (!callback) {
return;
}
std::weak_ptr<const WebRtcTransport> weak_self = shared_from_this();
_poller->async([weak_self, callback]() {
Json::Value result;
auto strong_self = weak_self.lock();
if (!strong_self) {
result["error"] = "Transport object destroyed";
callback(std::move(result));
return;
}
try {
result["transport_id"] = strong_self->_identifier;
result["role"] = RoleStr(strong_self->_role);
result["signaling_protocol"] = SignalingProtocolsStr(strong_self->_signaling_protocols);
result["has_offer_sdp"] = (strong_self->_offer_sdp != nullptr);
result["has_answer_sdp"] = (strong_self->_answer_sdp != nullptr);
result["dtls_state"] = strong_self->_dtls_transport? "connected" : "disconnected";
result["srtp_send_ready"] = (strong_self->_srtp_session_send != nullptr);
result["srtp_recv_ready"] = (strong_self->_srtp_session_recv != nullptr);
// ICE 连接检查列表信息
if (strong_self->_ice_agent) {
Json::Value ice_info = strong_self->_ice_agent->getChecklistInfo();
result["ice_checklists"] = ice_info;
} else {
result["ice_checklists"] = Json::nullValue;
}
} catch (const std::exception& ex) {
result["error"] = std::string("Exception occurred: ") + ex.what();
}
callback(std::move(result));
});
}
void WebRtcTransport::gatheringCandidate(IceServerInfo::Ptr ice_server, onGatheringCandidateCB cb) {
_on_gathering_candidate = std::move(cb);
_ice_agent->setIceServer(ice_server);
return _ice_agent->gatheringCandidate(ice_server, true, ice_server->_schema == IceServerInfo::SchemaType::TURN);
}
void WebRtcTransport::connectivityCheck(SdpAttrCandidate candidate_attr, const std::string& ufrag, const std::string& pwd) {
DebugL;
auto candidate = makeCandidateInfoBySdpAttr(candidate_attr, ufrag, pwd);
return _ice_agent->connectivityCheck(*candidate);
}
void WebRtcTransport::connectivityCheckForSFU() {
DebugL;
// Connectivity Checks 连通性测试
auto answer_sdp = answerSdp();
// TODO: 暂不支持每个媒体源,RTP,RTCP独立的candidates
for (auto &media : answer_sdp->media) {
for (auto &item : media.candidate) {
auto candidate = makeCandidateInfoBySdpAttr(item, media.ice_ufrag, media.ice_pwd);
_ice_agent->gatheringCandidate(candidate, false, false);
_ice_agent->connectivityCheck(*candidate);
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void WebRtcTransport::OnIceServerSendStunPacket(
const RTC::IceServer *iceServer, const RTC::StunPacket *packet, RTC::TransportTuple *tuple) {
sendSockData((char *)packet->GetData(), packet->GetSize(), tuple);
}
void WebRtcTransportImp::OnIceServerSelectedTuple(const RTC::IceServer *iceServer, RTC::TransportTuple *tuple) {
InfoL << getIdentifier() << " select tuple " << sockTypeStr(tuple) << " " << tuple->get_peer_ip() << ":" << tuple->get_peer_port();
tuple->setSendFlushFlag(false);
unrefSelf();
}
void WebRtcTransport::OnIceServerConnected(const RTC::IceServer *iceServer) {
void WebRtcTransport::onIceTransportCompleted() {
InfoL << getIdentifier();
if (!_answer_sdp) {
onShutdown(SockException(Err_other, "answer sdp not ready"));
return;
}
void WebRtcTransport::OnIceServerCompleted(const RTC::IceServer *iceServer) {
InfoL << getIdentifier();
if (_answer_sdp->media[0].role == DtlsRole::passive) {
_recv_ticker.resetTime();
auto timeout = getTimeOutSec();
weak_ptr<WebRtcTransport> weakSelf = static_pointer_cast<WebRtcTransport>(shared_from_this());
_check_timer = std::make_shared<Timer>(timeout / 2, [weakSelf, timeout]() {
auto strongSelf = weakSelf.lock();
if (!strongSelf) {
return false;
}
if (strongSelf->_recv_ticker.elapsedTime() > timeout * 1000) {
// 接收媒体数据包超时
strongSelf->onShutdown(SockException(Err_timeout, "webrtc data receive timeout"));
return false;
}
return true;
}, getPoller());
if ((getRole() == Role::PEER && _answer_sdp->media[0].role == DtlsRole::passive)
|| (getRole() == Role::CLIENT && _answer_sdp->media[0].role == DtlsRole::active)) {
_dtls_transport->Run(RTC::DtlsTransport::Role::SERVER);
} else {
_dtls_transport->Run(RTC::DtlsTransport::Role::CLIENT);
}
}
void WebRtcTransport::OnIceServerDisconnected(const RTC::IceServer *iceServer) {
void WebRtcTransport::onIceTransportDisconnected() {
InfoL << getIdentifier();
}
void WebRtcTransport::onIceTransportGatheringCandidate(const IceTransport::Pair::Ptr &pair, const CandidateInfo &candidate) {
InfoL << getIdentifier() << " get local candidate type " << candidate.dumpString();
if (_on_gathering_candidate) {
auto type = mappingCandidateTypeEnum2Str(candidate._type);
auto sdpAttrCandidate = makeIceCandidate(candidate._addr._host, candidate._addr._port, candidate._priority, "udp", type, candidate._base_addr._host, candidate._base_addr._port);
_on_gathering_candidate(getIdentifier(), sdpAttrCandidate->toString(), candidate._ufrag, candidate._pwd);
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void WebRtcTransport::setOnStartWebRTC(std::function<void()> on_start) {
_on_start = std::move(on_start);
}
void WebRtcTransport::OnDtlsTransportConnected(
const RTC::DtlsTransport *dtlsTransport, RTC::SrtpSession::CryptoSuite srtpCryptoSuite, uint8_t *srtpLocalKey,
size_t srtpLocalKeyLen, uint8_t *srtpRemoteKey, size_t srtpRemoteKeyLen, std::string &remoteCert) {
@ -218,6 +447,9 @@ void WebRtcTransport::OnDtlsTransportConnected(
_sctp->TransportConnected();
#endif
onStartWebRTC();
if (_on_start) {
_on_start();
}
}
#pragma pack(push, 1)
@ -231,13 +463,12 @@ struct DtlsHeader {
};
#pragma pack(pop)
void WebRtcTransport::OnDtlsTransportSendData(
const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) {
void WebRtcTransport::OnDtlsTransportSendData(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) {
size_t offset = 0;
while (offset < len) {
auto *header = reinterpret_cast<const DtlsHeader *>(data + offset);
auto length = ntohs(header->length) + offsetof(DtlsHeader, payload);
sendSockData((char *)data + offset, length, nullptr);
sendSockData((char *)data + offset, length);
offset += length;
}
}
@ -348,15 +579,37 @@ void WebRtcTransport::sendDatachannel(uint16_t streamId, uint32_t ppid, const ch
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void WebRtcTransport::sendSockData(const char *buf, size_t len, RTC::TransportTuple *tuple) {
void WebRtcTransport::sendSockData(const char *buf, size_t len, const IceTransport::Pair::Ptr &pair) {
auto pkt = _packet_pool.obtain2();
pkt->assign(buf, len);
onSendSockData(std::move(pkt), true, tuple ? tuple : _ice_server->GetSelectedTuple());
onSendSockData(std::move(pkt), true, pair);
}
Session::Ptr WebRtcTransport::getSession() const {
auto tuple = _ice_server ? _ice_server->GetSelectedTuple(true) : nullptr;
return tuple ? static_pointer_cast<Session>(tuple->shared_from_this()) : nullptr;
auto pair = _ice_agent->getSelectedPair();
return pair ? static_pointer_cast<Session>(pair->_socket->shared_from_this()) : nullptr;
}
void WebRtcTransport::removePair(const SocketHelper *socket) {
_ice_agent->removePair(socket);
}
void WebRtcTransport::setOnShutdown(function<void(const SockException &ex)> cb) {
_on_shutdown = cb ? std::move(cb) : [](const SockException &) {};
}
void WebRtcTransport::onShutdown(const SockException &ex) {
TraceL << ex;
if (_on_shutdown) {
_on_shutdown(ex);
}
if (_ice_agent) {
for (auto &pair : _ice_agent->getPairs()) {
if (pair->_socket) {
pair->_socket->shutdown(ex);
}
}
}
}
void WebRtcTransport::sendRtcpRemb(uint32_t ssrc, size_t bit_rate) {
@ -384,22 +637,21 @@ string getFingerprint(const string &algorithm_str, const std::shared_ptr<RTC::Dt
throw std::invalid_argument(StrPrinter << "不支持的加密算法:" << algorithm_str);
}
void WebRtcTransport::setRemoteDtlsFingerprint(const RtcSession &remote) {
void WebRtcTransport::setRemoteDtlsFingerprint(SdpType type, const RtcSession &remote) {
// 设置远端dtls签名 [AUTO-TRANSLATED:746d5f9c]
// Set remote dtls signature
auto &media = (type == SdpType::answer) ? _answer_sdp->media[0] : _offer_sdp->media[0];
RTC::DtlsTransport::Fingerprint remote_fingerprint;
remote_fingerprint.algorithm
= RTC::DtlsTransport::GetFingerprintAlgorithm(_offer_sdp->media[0].fingerprint.algorithm);
remote_fingerprint.value = _offer_sdp->media[0].fingerprint.hash;
remote_fingerprint.algorithm = RTC::DtlsTransport::GetFingerprintAlgorithm(media.fingerprint.algorithm);
remote_fingerprint.value = media.fingerprint.hash;
_dtls_transport->SetRemoteFingerprint(remote_fingerprint);
}
void WebRtcTransport::onRtcConfigure(RtcConfigure &configure) const {
SdpAttrFingerprint fingerprint;
fingerprint.algorithm = _offer_sdp->media[0].fingerprint.algorithm;
fingerprint.algorithm = _offer_sdp ? _offer_sdp->media[0].fingerprint.algorithm : "sha-256";
fingerprint.hash = getFingerprint(fingerprint.algorithm, _dtls_transport);
configure.setDefaultSetting(
_ice_server->GetUsernameFragment(), _ice_server->GetPassword(), RtpDirection::sendrecv, fingerprint);
configure.setDefaultSetting(_ice_agent->getUfrag(), _ice_agent->getPassword(), RtpDirection::sendrecv, fingerprint);
// 开启remb后关闭twcc因为开启twcc后remb无效 [AUTO-TRANSLATED:8a8feca2]
// Turn off twcc after turning on remb, because remb is invalid after turning on twcc
@ -421,6 +673,18 @@ static void setSdpBitrate(RtcSession &sdp) {
}
}
std::string WebRtcTransport::createOfferSdp() {
try {
RtcConfigure configure;
onRtcConfigure(configure);
_offer_sdp = configure.createOffer();
return _offer_sdp->toString();
} catch (exception &ex) {
onShutdown(SockException(Err_shutdown, ex.what()));
throw;
}
}
std::string WebRtcTransport::getAnswerSdp(const string &offer) {
try {
// // 解析offer sdp //// [AUTO-TRANSLATED:87c1f337]
@ -429,7 +693,7 @@ std::string WebRtcTransport::getAnswerSdp(const string &offer) {
_offer_sdp->loadFrom(offer);
onCheckSdp(SdpType::offer, *_offer_sdp);
_offer_sdp->checkValid();
setRemoteDtlsFingerprint(*_offer_sdp);
setRemoteDtlsFingerprint(SdpType::offer, *_offer_sdp);
// // sdp 配置 //// [AUTO-TRANSLATED:718a72e2]
// // sdp configuration ////
@ -449,18 +713,39 @@ std::string WebRtcTransport::getAnswerSdp(const string &offer) {
}
}
static bool isDtls(char *buf) {
void WebRtcTransport::setAnswerSdp(const std::string &answer) {
try {
_answer_sdp = std::make_shared<RtcSession>();
_answer_sdp->loadFrom(answer);
onCheckSdp(SdpType::answer, *_answer_sdp);
_answer_sdp->checkValid();
setRemoteDtlsFingerprint(SdpType::answer, *_answer_sdp);
} catch (exception &ex) {
onShutdown(SockException(Err_shutdown, ex.what()));
throw;
}
}
static bool isDtls(const char *buf) {
return ((*buf > 19) && (*buf < 64));
}
void WebRtcTransport::inputSockData(char *buf, int len, RTC::TransportTuple *tuple) {
if (RTC::StunPacket::IsStun((const uint8_t *)buf, len)) {
std::unique_ptr<RTC::StunPacket> packet(RTC::StunPacket::Parse((const uint8_t *)buf, len));
if (!packet) {
WarnL << "parse stun error";
return;
void WebRtcTransport::inputSockData(const char *buf, int len, const SocketHelper::Ptr& socket, struct sockaddr *addr, int addr_len) {
IceTransport::Pair::Ptr pair;
if (addr != nullptr) {
auto peer_host = SockUtil::inet_ntoa(addr);
auto peer_port = SockUtil::inet_port(addr);
pair = std::make_shared<IceTransport::Pair>(socket, std::move(peer_host), peer_port);
} else {
pair = std::make_shared<IceTransport::Pair>(socket);
}
_ice_server->ProcessStunPacket(packet.get(), tuple);
return inputSockData(buf, len, pair);
}
void WebRtcTransport::inputSockData(const char *buf, int len, const IceTransport::Pair::Ptr& pair) {
// DebugL;
_recv_ticker.resetTime();
if (_ice_agent->processSocketData((const uint8_t *)buf, len, pair)) {
return;
}
if (isDtls(buf)) {
@ -469,7 +754,7 @@ void WebRtcTransport::inputSockData(char *buf, int len, RTC::TransportTuple *tup
}
if (isRtp(buf, len)) {
if (!_srtp_session_recv) {
WarnL << "received rtp packet when dtls not completed from:" << tuple->get_peer_ip();
WarnL << "received rtp packet when dtls not completed from:" << pair->get_peer_ip();
return;
}
if (_srtp_session_recv->DecryptSrtp((uint8_t *)buf, &len)) {
@ -479,7 +764,7 @@ void WebRtcTransport::inputSockData(char *buf, int len, RTC::TransportTuple *tup
}
if (isRtcp(buf, len)) {
if (!_srtp_session_recv) {
WarnL << "received rtcp packet when dtls not completed from:" << tuple->get_peer_ip();
WarnL << "received rtcp packet when dtls not completed from:" << pair->get_peer_ip();
return;
}
if (_srtp_session_recv->DecryptSrtcp((uint8_t *)buf, &len)) {
@ -566,38 +851,26 @@ void WebRtcTransportImp::onDestory() {
unregisterSelf();
}
void WebRtcTransportImp::onSendSockData(Buffer::Ptr buf, bool flush, RTC::TransportTuple *tuple) {
if (tuple == nullptr) {
tuple = _ice_server->GetSelectedTuple();
if (!tuple) {
WarnL << "send data failed:" << buf->size();
return;
}
}
// 一次性发送一帧的rtp数据提高网络io性能 [AUTO-TRANSLATED:fbab421e]
// Send one frame of rtp data at a time to improve network io performance
if (tuple->getSock()->sockType() == SockNum::Sock_TCP) {
// 增加tcp两字节头 [AUTO-TRANSLATED:62159f79]
// Add two-byte header to tcp
auto len = buf->size();
char tcp_len[2] = { 0 };
tcp_len[0] = (len >> 8) & 0xff;
tcp_len[1] = len & 0xff;
tuple->SockSender::send(tcp_len, 2);
}
tuple->send(std::move(buf));
if (flush) {
tuple->flushAll();
}
void WebRtcTransportImp::onSendSockData(Buffer::Ptr buf, bool flush, const IceTransport::Pair::Ptr& pair) {
return _ice_agent->sendSocketData(buf, pair, flush);
}
///////////////////////////////////////////////////////////////////
bool WebRtcTransportImp::canSendRtp(const RtcMedia& m) const {
return (getRole() == WebRtcTransport::Role::PEER && m.direction == RtpDirection::sendonly)
|| (getRole() == WebRtcTransport::Role::CLIENT && m.direction == RtpDirection::recvonly)
|| (m.direction == RtpDirection::sendrecv);
}
bool WebRtcTransportImp::canRecvRtp(const RtcMedia& m) const {
return (getRole() == WebRtcTransport::Role::PEER && m.direction == RtpDirection::recvonly)
|| (getRole() == WebRtcTransport::Role::CLIENT && m.direction == RtpDirection::sendonly)
|| (m.direction == RtpDirection::sendrecv);
}
bool WebRtcTransportImp::canSendRtp() const {
for (auto &m : _answer_sdp->media) {
if (m.direction == RtpDirection::sendrecv || m.direction == RtpDirection::sendonly) {
if (canSendRtp(m)) {
return true;
}
}
@ -606,7 +879,7 @@ bool WebRtcTransportImp::canSendRtp() const {
bool WebRtcTransportImp::canRecvRtp() const {
for (auto &m : _answer_sdp->media) {
if (m.direction == RtpDirection::sendrecv || m.direction == RtpDirection::recvonly) {
if (canRecvRtp(m)) {
return true;
}
}
@ -633,7 +906,7 @@ void WebRtcTransportImp::onStartWebRTC() {
track->rtcp_context_send = std::make_shared<RtcpContextForSend>();
// rtp track type --> MediaTrack
if (m_answer.direction == RtpDirection::sendonly || m_answer.direction == RtpDirection::sendrecv) {
if (canSendRtp(m_answer)) {
// 该类型的track 才支持发送 [AUTO-TRANSLATED:b7c1e631]
// This type of track supports sending
_type_to_track[m_answer.type] = track;
@ -763,25 +1036,6 @@ void WebRtcTransportImp::onCheckSdp(SdpType type, RtcSession &sdp) {
}
}
SdpAttrCandidate::Ptr
makeIceCandidate(std::string ip, uint16_t port, uint32_t priority = 100, std::string proto = "udp") {
auto candidate = std::make_shared<SdpAttrCandidate>();
// rtp端口 [AUTO-TRANSLATED:b0addb27]
// rtp port
candidate->component = 1;
candidate->transport = proto;
candidate->foundation = proto + "candidate";
// 优先级单candidate时随便 [AUTO-TRANSLATED:7c85d820]
// Priority, random when there is only one candidate
candidate->priority = priority;
candidate->address = std::move(ip);
candidate->port = port;
candidate->type = "host";
if (proto == "tcp") {
candidate->type += " tcptype passive";
}
return candidate;
}
void WebRtcTransportImp::onRtcConfigure(RtcConfigure &configure) const {
WebRtcTransport::onRtcConfigure(configure);
@ -792,6 +1046,9 @@ void WebRtcTransportImp::onRtcConfigure(RtcConfigure &configure) const {
return;
}
//P2P的不直接在answer中返回candication
if (getSignalingProtocols() == SignalingProtocols::WHEP_WHIP) {
GET_CONFIG(uint16_t, local_udp_port, Rtc::kPort);
GET_CONFIG(uint16_t, local_tcp_port, Rtc::kTcpPort);
// 添加接收端口candidate信息 [AUTO-TRANSLATED:cc9a6a90]
@ -806,18 +1063,27 @@ void WebRtcTransportImp::onRtcConfigure(RtcConfigure &configure) const {
});
if (extern_ips.empty()) {
std::string local_ip = _local_ip.empty() ? SockUtil::get_local_ip() : _local_ip;
if (local_udp_port) { configure.addCandidate(*makeIceCandidate(local_ip, local_udp_port, 120, "udp")); }
if (local_tcp_port) { configure.addCandidate(*makeIceCandidate(local_ip, local_tcp_port, _preferred_tcp ? 125 : 115, "tcp")); }
if (local_udp_port) {
configure.addCandidate(*makeIceCandidate(local_ip, local_udp_port, 120, "udp"));
}
if (local_tcp_port) {
configure.addCandidate(*makeIceCandidate(local_ip, local_tcp_port, _preferred_tcp ? 125 : 115, "tcp"));
}
} else {
const uint32_t delta = 10;
uint32_t priority = 100 + delta * extern_ips.size();
for (auto ip : extern_ips) {
if (local_udp_port) { configure.addCandidate(*makeIceCandidate(ip, local_udp_port, priority, "udp")); }
if (local_tcp_port) { configure.addCandidate(*makeIceCandidate(ip, local_tcp_port, priority - (_preferred_tcp ? -5 : 5), "tcp")); }
if (local_udp_port) {
configure.addCandidate(*makeIceCandidate(ip, local_udp_port, priority, "udp"));
}
if (local_tcp_port) {
configure.addCandidate(*makeIceCandidate(ip, local_tcp_port, priority - (_preferred_tcp ? -5 : 5), "tcp"));
}
priority -= delta;
}
}
}
}
void WebRtcTransportImp::setPreferredTcp(bool flag) {
_preferred_tcp = flag;
@ -863,7 +1129,9 @@ public:
}
Buffer::Ptr createRtcpRR(RtcpHeader *sr, uint32_t ssrc) {
if (sr) {
_rtcp_context.onRtcp(sr);
}
return _rtcp_context.createRtcpRR(ssrc, getSSRC());
}
@ -951,6 +1219,7 @@ void WebRtcTransportImp::onRtcp(const char *buf, size_t len) {
rtp_chn->setNtpStamp(sr->rtpts, sr->getNtpUnixStampMS());
auto rr = rtp_chn->createRtcpRR(sr, track->answer_ssrc_rtp);
sendRtcpPacket(rr->data(), rr->size(), true);
_rtcp_rr_send_ticker.resetTime();
}
} else {
WarnL << "未识别的sr rtcp包:" << rtcp->dumpString();
@ -969,6 +1238,7 @@ void WebRtcTransportImp::onRtcp(const char *buf, size_t len) {
track->rtcp_context_send->onRtcp(rtcp);
auto sr = track->rtcp_context_send->createRtcpSR(track->answer_ssrc_rtp);
sendRtcpPacket(sr->data(), sr->size(), true);
_rtcp_sr_send_ticker.resetTime();
} else {
WarnL << "未识别的rr rtcp包:" << rtcp->dumpString();
}
@ -1078,6 +1348,24 @@ void WebRtcTransportImp::onRtp(const char *buf, size_t len, uint64_t stamp_ms) {
WarnL << "unknown rtp pt:" << (int)rtp->pt;
return;
}
if (_rtcp_rr_send_ticker.elapsedTime() > 5000) {
_rtcp_rr_send_ticker.resetTime();
auto ssrc = ntohl(rtp->ssrc);
auto track_it = _ssrc_to_track.find(ssrc);
if (track_it != _ssrc_to_track.end()) {
auto &track = track_it->second;
auto rtp_chn = track->getRtpChannel(ssrc);
if (rtp_chn) {
auto rr = rtp_chn->createRtcpRR(nullptr, track->answer_ssrc_rtp);
if (rr && rr->size() > 0) {
sendRtcpPacket(rr->data(), rr->size(), true);
}
}
}
}
it->second->inputRtp(buf, len, stamp_ms, rtp);
}
@ -1216,6 +1504,16 @@ void WebRtcTransportImp::onSendRtp(const RtpPacket::Ptr &rtp, bool flush, bool r
pair<bool /*rtx*/, MediaTrack *> ctx { rtx, track.get() };
sendRtpPacket(rtp->data() + RtpPacket::kRtpTcpHeaderSize, rtp->size() - RtpPacket::kRtpTcpHeaderSize, flush, &ctx);
_bytes_usage += rtp->size() - RtpPacket::kRtpTcpHeaderSize;
if (_rtcp_sr_send_ticker.elapsedTime() > 5000) {
_rtcp_sr_send_ticker.resetTime();
if (track->rtcp_context_send) {
auto sr = track->rtcp_context_send->createRtcpSR(track->answer_ssrc_rtp);
if (sr && sr->size() > 0) {
sendRtcpPacket(sr->data(), sr->size(), true);
}
}
}
}
void WebRtcTransportImp::onBeforeEncryptRtp(const char *buf, int &len, void *ctx) {
@ -1274,15 +1572,8 @@ void WebRtcTransportImp::safeShutdown(const SockException &ex) {
void WebRtcTransportImp::onShutdown(const SockException &ex) {
WarnL << ex;
WebRtcTransport::onShutdown(ex);
unrefSelf();
for (auto &tuple : _ice_server->GetTuples()) {
tuple->shutdown(ex);
}
}
void WebRtcTransportImp::removeTuple(RTC::TransportTuple *tuple) {
InfoL << getIdentifier() << " remove tuple " << tuple->get_peer_ip() << ":" << tuple->get_peer_port();
this->_ice_server->RemoveTuple(tuple);
}
uint64_t WebRtcTransportImp::getBytesUsage() const {
@ -1307,6 +1598,7 @@ void WebRtcTransportImp::unrefSelf() {
}
void WebRtcTransportImp::unregisterSelf() {
DebugL;
unrefSelf();
WebRtcTransportManager::Instance().removeItem(getIdentifier());
}
@ -1350,19 +1642,18 @@ void WebRtcPluginManager::registerPlugin(const string &type, Plugin cb) {
_map_creator[type] = std::move(cb);
}
void WebRtcPluginManager::setListener(Listener cb) {
lock_guard<mutex> lck(_mtx_creator);
_listener = std::move(cb);
}
void WebRtcPluginManager::negotiateSdp(Session &sender, const string &type, const WebRtcArgs &args, const onCreateWebRtc &cb_in) {
void WebRtcPluginManager::negotiateSdp(SocketHelper& sender, const string &type, const WebRtcArgs &args, const onCreateWebRtc &cb_in) {
onCreateWebRtc cb;
lock_guard<mutex> lck(_mtx_creator);
if (_listener) {
auto listener = _listener;
auto args_ptr = args.shared_from_this();
auto sender_ptr = static_pointer_cast<Session>(sender.shared_from_this());
auto sender_ptr = static_pointer_cast<SocketHelper>(sender.shared_from_this());
cb = [listener, sender_ptr, type, args_ptr, cb_in](const WebRtcInterface &rtc) {
listener(*sender_ptr, type, *args_ptr, rtc);
cb_in(rtc);
@ -1379,11 +1670,11 @@ void WebRtcPluginManager::negotiateSdp(Session &sender, const string &type, cons
it->second(sender, args, cb);
}
void echo_plugin(Session &sender, const WebRtcArgs &args, const onCreateWebRtc &cb) {
void echo_plugin(SocketHelper& sender, const WebRtcArgs &args, const onCreateWebRtc &cb) {
cb(*WebRtcEchoTest::create(EventPollerPool::Instance().getPoller()));
}
void push_plugin(Session &sender, const WebRtcArgs &args, const onCreateWebRtc &cb) {
void push_plugin(SocketHelper& sender, const WebRtcArgs &args, const onCreateWebRtc &cb) {
MediaInfo info(args["url"]);
Broadcast::PublishAuthInvoker invoker = [cb, info](const string &err, const ProtocolOption &option) mutable {
if (!err.empty()) {
@ -1427,7 +1718,8 @@ void push_plugin(Session &sender, const WebRtcArgs &args, const onCreateWebRtc &
push_src_ownership = push_src->getOwnership();
push_src->setProtocolOption(option);
}
auto rtc = WebRtcPusher::create(EventPollerPool::Instance().getPoller(), push_src, push_src_ownership, info, option);
auto rtc = WebRtcPusher::create(EventPollerPool::Instance().getPoller(), push_src, push_src_ownership, info, option,
WebRtcTransport::Role::PEER, WebRtcTransport::SignalingProtocols::WHEP_WHIP);
push_src->setListener(rtc);
cb(*rtc);
};
@ -1442,7 +1734,8 @@ void push_plugin(Session &sender, const WebRtcArgs &args, const onCreateWebRtc &
}
}
void play_plugin(Session &sender, const WebRtcArgs &args, const onCreateWebRtc &cb) {
void play_plugin(SocketHelper &sender, const WebRtcArgs &args, const onCreateWebRtc &cb) {
MediaInfo info(args["url"]);
auto session_ptr = static_pointer_cast<Session>(sender.shared_from_this());
Broadcast::AuthInvoker invoker = [cb, info, session_ptr](const string &err) mutable {
@ -1463,7 +1756,8 @@ void play_plugin(Session &sender, const WebRtcArgs &args, const onCreateWebRtc &
// 还原成rtc目的是为了hook时识别哪种播放协议 [AUTO-TRANSLATED:fe8dd2dc]
// Restore to RTC, the purpose is to identify which playback protocol during hooking
info.schema = "rtc";
auto rtc = WebRtcPlayer::create(EventPollerPool::Instance().getPoller(), src, info);
auto rtc = WebRtcPlayer::create(EventPollerPool::Instance().getPoller(), src, info,
WebRtcTransport::Role::PEER, WebRtcTransport::SignalingProtocols::WHEP_WHIP);
cb(*rtc);
});
};
@ -1529,6 +1823,15 @@ static void setWebRtcArgs(const WebRtcArgs &args, WebRtcInterface &rtc) {
}
}
float WebRtcTransport::getTimeOutSec() {
GET_CONFIG(uint32_t, timeout, Rtc::kTimeOutSec);
if (timeout <= 0) {
WarnL << "config rtc. " << Rtc::kTimeOutSec << ": " << timeout << " not vaild";
return 5;
}
return (float)timeout;
}
static onceToken s_rtc_auto_register([]() {
#if !defined (NDEBUG)
// debug模式才开启echo插件 [AUTO-TRANSLATED:48fcb116]
@ -1537,9 +1840,28 @@ static onceToken s_rtc_auto_register([]() {
#endif
WebRtcPluginManager::Instance().registerPlugin("push", push_plugin);
WebRtcPluginManager::Instance().registerPlugin("play", play_plugin);
WebRtcPluginManager::Instance().setListener([](Session &sender, const std::string &type, const WebRtcArgs &args, const WebRtcInterface &rtc) {
WebRtcPluginManager::Instance().setListener([](SocketHelper& sender, const std::string &type, const WebRtcArgs &args, const WebRtcInterface &rtc) {
setWebRtcArgs(args, const_cast<WebRtcInterface&>(rtc));
});
});
void WebRtcTransport::onIceTransportRecvData(const toolkit::Buffer::Ptr& buffer, const IceTransport::Pair::Ptr& pair) {
return inputSockData(buffer->data(), buffer->size(), pair);
}
void translateIPFromEnv(std::vector<std::string> &v) {
for (auto iter = v.begin(); iter != v.end();) {
if (start_with(*iter, "$")) {
auto ip = toolkit::getEnv(*iter);
if (ip.empty()) {
iter = v.erase(iter);
} else {
*iter++ = ip;
}
} else {
++iter;
}
}
}
}// namespace mediakit

View File

@ -8,12 +8,14 @@
* 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 <functional>
#include "DtlsTransport.hpp"
#include "IceServer.hpp"
#include "IceTransport.hpp"
#include "SrtpSession.hpp"
#include "StunPacket.hpp"
#include "Sdp.h"
@ -25,127 +27,153 @@
#include "TwccContext.h"
#include "SctpAssociation.hpp"
#include "Rtcp/RtcpContext.h"
#include "Rtsp/RtspMediaSource.h"
using namespace RTC;
namespace mediakit {
// ICE transport policy enum
enum class IceTransportPolicy {
kAll = 0, // 不限制,支持所有连接类型(默认)
kRelayOnly = 1, // 仅支持Relay转发
kP2POnly = 2 // 仅支持P2P直连
};
// RTC配置项目 [AUTO-TRANSLATED:65784416]
// RTC configuration project
namespace Rtc {
extern const std::string kPort;
extern const std::string kTcpPort;
extern const std::string kTimeOutSec;
extern const std::string kSignalingPort;
extern const std::string kSignalingSslPort;
extern const std::string kIcePort;
extern const std::string kIceTcpPort;
extern const std::string kEnableTurn;
extern const std::string kIceTransportPolicy;
extern const std::string kIceUfrag;
extern const std::string kIcePwd;
extern const std::string kExternIP;
extern const std::string kInterfaces;
}//namespace RTC
class WebRtcInterface {
public:
virtual ~WebRtcInterface() = default;
virtual std::string getAnswerSdp(const std::string &offer) = 0;
virtual std::string createOfferSdp() = 0;
virtual void setAnswerSdp(const std::string &answer) = 0;
virtual const std::string& getIdentifier() const = 0;
virtual const std::string& deleteRandStr() const { static std::string s_null; return s_null; }
virtual void setIceCandidate(std::vector<SdpAttrCandidate> cands) {}
virtual void setLocalIp(std::string localIp) {}
virtual void setPreferredTcp(bool flag) {}
using onGatheringCandidateCB = std::function<void(const std::string& transport_identifier, const std::string& candidate, const std::string& ufrag, const std::string& pwd)>;
virtual void gatheringCandidate(IceServerInfo::Ptr ice_server, onGatheringCandidateCB cb = nullptr) = 0;
};
class WebRtcException : public WebRtcInterface {
public:
WebRtcException(const SockException &ex) : _ex(ex) {};
WebRtcException(const toolkit::SockException &ex) : _ex(ex) {};
std::string createOfferSdp() override {
throw _ex;
}
std::string getAnswerSdp(const std::string &offer) override {
throw _ex;
}
void setAnswerSdp(const std::string &answer) override {
throw _ex;
}
void gatheringCandidate(IceServerInfo::Ptr ice_server, onGatheringCandidateCB cb = nullptr) override {
throw _ex;
}
const std::string &getIdentifier() const override {
static std::string s_null;
return s_null;
}
private:
SockException _ex;
toolkit::SockException _ex;
};
class WebRtcTransport : public WebRtcInterface, public RTC::DtlsTransport::Listener, public RTC::IceServer::Listener, public std::enable_shared_from_this<WebRtcTransport>
class WebRtcTransport : public WebRtcInterface, public RTC::DtlsTransport::Listener, public IceTransport::Listener, public std::enable_shared_from_this<WebRtcTransport>
#ifdef ENABLE_SCTP
, public RTC::SctpAssociation::Listener
#endif
{
public:
enum class Role {
NONE = 0,
CLIENT,
PEER,
};
static const char* RoleStr(Role role);
enum class SignalingProtocols {
Invalid = -1,
WHEP_WHIP = 0,
WEBSOCKET = 1, //FOR P2P
};
static const char* SignalingProtocolsStr(SignalingProtocols protocol);
using WeakPtr = std::weak_ptr<WebRtcTransport>;
using Ptr = std::shared_ptr<WebRtcTransport>;
WebRtcTransport(const EventPoller::Ptr &poller);
WebRtcTransport(const toolkit::EventPoller::Ptr &poller);
/**
*
* Create object
* [AUTO-TRANSLATED:830344e4]
*/
virtual void onCreate();
/**
*
* Destroy object
* [AUTO-TRANSLATED:1016b97b]
*/
virtual void onDestory();
/**
* webrtc answer sdp
* @param offer offer sdp
* @return answer sdp
* Create webrtc answer sdp
* @param offer offer sdp
* @return answer sdp
std::string getAnswerSdp(const std::string &offer) override;
void setAnswerSdp(const std::string &answer) override;
* [AUTO-TRANSLATED:d9b027d7]
*/
std::string getAnswerSdp(const std::string &offer) override final;
const RtcSession::Ptr& answerSdp() const {
return _answer_sdp;
}
/**
* id
* Get object unique id
std::string createOfferSdp() override;
* [AUTO-TRANSLATED:9ad519c6]
*/
const std::string& getIdentifier() const override;
const std::string& deleteRandStr() const override;
/**
* socket收到udp数据
* @param buf
* @param len
* @param tuple
* Socket receives udp data
* @param buf data pointer
* @param len data length
* @param tuple data source
* [AUTO-TRANSLATED:1ee86069]
*/
void inputSockData(char *buf, int len, RTC::TransportTuple *tuple);
/**
* rtp
* @param buf rtcp内容
* @param len rtcp长度
* @param flush flush socket
* @param ctx
* Send rtp
* @param buf rtcp content
* @param len rtcp length
* @param flush whether to flush socket
* @param ctx user pointer
* [AUTO-TRANSLATED:aa833695]
*/
void inputSockData(const char *buf, int len, const toolkit::SocketHelper::Ptr& socket, struct sockaddr *addr = nullptr, int addr_len = 0);
void inputSockData(const char *buf, int len, const IceTransport::Pair::Ptr& pair = nullptr);
void sendRtpPacket(const char *buf, int len, bool flush, void *ctx = nullptr);
void sendRtcpPacket(const char *buf, int len, bool flush, void *ctx = nullptr);
void sendDatachannel(uint16_t streamId, uint32_t ppid, const char *msg, size_t len);
const EventPoller::Ptr& getPoller() const;
Session::Ptr getSession() const;
const toolkit::EventPoller::Ptr &getPoller() const { return _poller; }
void setPoller(toolkit::EventPoller::Ptr poller) { _poller = std::move(poller); }
toolkit::Session::Ptr getSession() const;
void removePair(const toolkit::SocketHelper *socket);
Role getRole() const { return _role; }
void setRole(Role role) { _role = role; }
SignalingProtocols getSignalingProtocols() const { return _signaling_protocols; }
void setSignalingProtocols(SignalingProtocols signaling_protocols) { _signaling_protocols = signaling_protocols; }
float getTimeOutSec();
void getTransportInfo(const std::function<void(Json::Value)> &callback) const;
void setOnShutdown(std::function<void(const toolkit::SockException &ex)> cb);
void gatheringCandidate(IceServerInfo::Ptr ice_server, onGatheringCandidateCB cb = nullptr) override;
void connectivityCheck(SdpAttrCandidate candidate_attr, const std::string &ufrag, const std::string &pwd);
void connectivityCheckForSFU();
void setOnStartWebRTC(std::function<void()> on_start);
protected:
// // dtls相关的回调 //// [AUTO-TRANSLATED:31a1f32c]
// // dtls related callbacks ////
// DtlsTransport::Listener; dtls相关的回调
void OnDtlsTransportConnecting(const RTC::DtlsTransport *dtlsTransport) override;
void OnDtlsTransportConnected(const RTC::DtlsTransport *dtlsTransport,
RTC::SrtpSession::CryptoSuite srtpCryptoSuite,
@ -154,20 +182,19 @@ protected:
uint8_t *srtpRemoteKey,
size_t srtpRemoteKeyLen,
std::string &remoteCert) override;
void OnDtlsTransportFailed(const RTC::DtlsTransport *dtlsTransport) override;
void OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) override;
void OnDtlsTransportSendData(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) override;
void OnDtlsTransportApplicationDataReceived(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) override;
protected:
// // ice相关的回调 /// [AUTO-TRANSLATED:30abf693]
// // ice related callbacks ///
void OnIceServerSendStunPacket(const RTC::IceServer *iceServer, const RTC::StunPacket *packet, RTC::TransportTuple *tuple) override;
void OnIceServerConnected(const RTC::IceServer *iceServer) override;
void OnIceServerCompleted(const RTC::IceServer *iceServer) override;
void OnIceServerDisconnected(const RTC::IceServer *iceServer) override;
// ice相关的回调; IceTransport::Listener.
void onIceTransportRecvData(const toolkit::Buffer::Ptr& buffer, const IceTransport::Pair::Ptr& pair) override;
void onIceTransportGatheringCandidate(const IceTransport::Pair::Ptr& pair, const CandidateInfo& candidate) override;
void onIceTransportCompleted() override;
void onIceTransportDisconnected() override;
// SctpAssociation::Listener
#ifdef ENABLE_SCTP
void OnSctpAssociationConnecting(RTC::SctpAssociation* sctpAssociation) override;
void OnSctpAssociationConnected(RTC::SctpAssociation* sctpAssociation) override;
@ -182,11 +209,11 @@ protected:
virtual void onStartWebRTC() = 0;
virtual void onRtcConfigure(RtcConfigure &configure) const;
virtual void onCheckSdp(SdpType type, RtcSession &sdp) = 0;
virtual void onSendSockData(Buffer::Ptr buf, bool flush = true, RTC::TransportTuple *tuple = nullptr) = 0;
virtual void onSendSockData(toolkit::Buffer::Ptr buf, bool flush = true, const IceTransport::Pair::Ptr& pair = nullptr) = 0;
virtual void onRtp(const char *buf, size_t len, uint64_t stamp_ms) = 0;
virtual void onRtcp(const char *buf, size_t len) = 0;
virtual void onShutdown(const SockException &ex) = 0;
virtual void onShutdown(const toolkit::SockException &ex);
virtual void onBeforeEncryptRtp(const char *buf, int &len, void *ctx) = 0;
virtual void onBeforeEncryptRtcp(const char *buf, int &len, void *ctx) = 0;
virtual void onRtcpBye() = 0;
@ -196,25 +223,35 @@ protected:
void sendRtcpPli(uint32_t ssrc);
private:
void sendSockData(const char *buf, size_t len, RTC::TransportTuple *tuple);
void setRemoteDtlsFingerprint(const RtcSession &remote);
void sendSockData(const char *buf, size_t len, const IceTransport::Pair::Ptr& pair = nullptr);
void setRemoteDtlsFingerprint(SdpType type, const RtcSession &remote);
protected:
SignalingProtocols _signaling_protocols = SignalingProtocols::WHEP_WHIP;
Role _role = Role::PEER;
RtcSession::Ptr _offer_sdp;
RtcSession::Ptr _answer_sdp;
std::shared_ptr<RTC::IceServer> _ice_server;
IceAgent::Ptr _ice_agent;
onGatheringCandidateCB _on_gathering_candidate = nullptr;
private:
mutable std::string _delete_rand_str;
std::string _identifier;
EventPoller::Ptr _poller;
std::shared_ptr<RTC::DtlsTransport> _dtls_transport;
std::shared_ptr<RTC::SrtpSession> _srtp_session_send;
std::shared_ptr<RTC::SrtpSession> _srtp_session_recv;
Ticker _ticker;
toolkit::EventPoller::Ptr _poller;
DtlsTransport::Ptr _dtls_transport;
SrtpSession::Ptr _srtp_session_send;
SrtpSession::Ptr _srtp_session_recv;
toolkit::Ticker _ticker;
// 循环池 [AUTO-TRANSLATED:b7059f37]
// Cycle pool
ResourcePool<BufferRaw> _packet_pool;
toolkit::ResourcePool<toolkit::BufferRaw> _packet_pool;
//超时功能实现
toolkit::Ticker _recv_ticker;
std::shared_ptr<toolkit::Timer> _check_timer;
std::function<void()> _on_start;
std::function<void(const toolkit::SockException &ex)> _on_shutdown;
#ifdef ENABLE_SCTP
RTC::SctpAssociationImp::Ptr _sctp;
@ -277,22 +314,26 @@ public:
uint64_t getDuration() const;
bool canSendRtp() const;
bool canRecvRtp() const;
bool canSendRtp(const RtcMedia& media) const;
bool canRecvRtp(const RtcMedia& media) const;
void onSendRtp(const RtpPacket::Ptr &rtp, bool flush, bool rtx = false);
void createRtpChannel(const std::string &rid, uint32_t ssrc, MediaTrack &track);
void removeTuple(RTC::TransportTuple* tuple);
void safeShutdown(const SockException &ex);
void safeShutdown(const toolkit::SockException &ex);
void setPreferredTcp(bool flag) override;
void setLocalIp(std::string local_ip) override;
void setIceCandidate(std::vector<SdpAttrCandidate> cands) override;
protected:
void OnIceServerSelectedTuple(const RTC::IceServer *iceServer, RTC::TransportTuple *tuple) override;
WebRtcTransportImp(const EventPoller::Ptr &poller);
// // ice相关的回调 /// [AUTO-TRANSLATED:30abf693]
// // ice related callbacks ///
WebRtcTransportImp(const toolkit::EventPoller::Ptr &poller);
void OnDtlsTransportApplicationDataReceived(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) override;
void onStartWebRTC() override;
void onSendSockData(Buffer::Ptr buf, bool flush = true, RTC::TransportTuple *tuple = nullptr) override;
void onSendSockData(toolkit::Buffer::Ptr buf, bool flush = true, const IceTransport::Pair::Ptr& pair = nullptr) override;
void onCheckSdp(SdpType type, RtcSession &sdp) override;
void onRtcConfigure(RtcConfigure &configure) const override;
@ -302,7 +343,7 @@ protected:
void onBeforeEncryptRtcp(const char *buf, int &len, void *ctx) override {};
void onCreate() override;
void onDestory() override;
void onShutdown(const SockException &ex) override;
void onShutdown(const toolkit::SockException &ex) override;
virtual void onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) {}
void updateTicker();
float getLossRate(TrackType type);
@ -329,13 +370,17 @@ private:
Ptr _self;
// 检测超时的定时器 [AUTO-TRANSLATED:a58e1388]
// Timeout detection timer
Timer::Ptr _timer;
toolkit::Timer::Ptr _timer;
// 刷新计时器 [AUTO-TRANSLATED:61eb11e5]
// Refresh timer
Ticker _alive_ticker;
toolkit::Ticker _alive_ticker;
// pli rtcp计时器 [AUTO-TRANSLATED:a1a5fd18]
// pli rtcp timer
Ticker _pli_ticker;
toolkit::Ticker _pli_ticker;
toolkit::Ticker _rtcp_sr_send_ticker;
toolkit::Ticker _rtcp_rr_send_ticker;
// twcc rtcp发送上下文对象 [AUTO-TRANSLATED:aef6476a]
// twcc rtcp send context object
TwccContext _twcc_ctx;
@ -373,20 +418,20 @@ private:
class WebRtcArgs : public std::enable_shared_from_this<WebRtcArgs> {
public:
virtual ~WebRtcArgs() = default;
virtual variant operator[](const std::string &key) const = 0;
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(Session &sender, const WebRtcArgs &args, const onCreateWebRtc &cb)>;
using Listener = std::function<void(Session &sender, const std::string &type, const WebRtcArgs &args, const WebRtcInterface &rtc)>;
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(Session &sender, const std::string &type, const WebRtcArgs &args, const onCreateWebRtc &cb);
void negotiateSdp(toolkit::SocketHelper& sender, const std::string &type, const WebRtcArgs &args, const onCreateWebRtc &cb);
private:
WebRtcPluginManager() = default;
@ -397,4 +442,8 @@ private:
std::unordered_map<std::string, Plugin> _map_creator;
};
void translateIPFromEnv(std::vector<std::string> &v);
}// namespace mediakit
#endif // ZLMEDIAKIT_WEBRTC_TRANSPORT_H

View File

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

132
webrtc/webrtcSignal.txt Normal file
View File

@ -0,0 +1,132 @@
webrtc websocket 信令
# register 注册
``` json
#client/peer --> server
{
"class" : "request",
"method" : "register",
"transaction_id" : "HFaq5Jp2agKfDjizOT5jGpiPtOQ8yays"
"room_id" : "room_1",
}
#server --> client/peer
#success
#支持turn
{
"class" : "accept",
"method" : "register",
"transaction_id" : "HFaq5Jp2agKfDjizOT5jGpiPtOQ8yays"
"room_id" : "room_1",
"ice_servers" : [ {
"pwd" : "ZLMediaKit",
"ufrag" : "ZLMediaKit",
"url" : "turn:10.9.120.61:3478?transport=udp"
}
],
}
#不支持turn
{
"class" : "accept",
"method" : "register",
"transaction_id" : "HFaq5Jp2agKfDjizOT5jGpiPtOQ8yays"
"room_id" : "room_1",
"ice_servers" : [ {
"pwd" : "ZLMediaKit",
"ufrag" : "ZLMediaKit",
"url" : "stun:10.9.120.61:3478?transport=udp"
}
],
}
```
#fail
``` json
{
"class" : "reject",
"method" : "register",
"transaction_id" : "2DiOjTulA4Glp9Si7yHdQypibAn2LPaX"
"reason" : "alreadly register",
"room_id" : "room_1",
}
```
# unregister 注销
# client --> server
```json
{
"class" : "request",
"method" : "unregister",
"transaction_id" : "0Xbgr86OIacWvjJIc03EsxH3QIF1ou8m"
"room_id" : "room_1",
}
```
# server --> client
# success
``` json
{
"class" : "accept",
"method" : "unregister",
"room_id" : "room1",
"transaction_id" : "0Xbgr86OIacWvjJIc03EsxH3QIF1ou8m"
}
```
# 呼叫
# client --> server,server -透传-> peer
{
"class" : "request",
"method" : "call",
"transaction_id" : "qUpN8C49bGiyOHk6WNanAFq2viSkk6HC",
"guest_id" : "guest1_EDuVWIxLUMlDDKDa",
"room_id" : "room_1",
"type" : "play",
"vhost" : "__defaultVhost__",
"app" : "live",
"stream" : "test",
"sdp" : "v=0\r\no=- 7040255305116218076 1 IN IP4 0.0.0.0\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0 1\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS\r\nm=video 9 UDP/TLS/RTP/SAVPF 102 124 123 102 124 123 35 98 100 96\r\nc=IN IP4 0.0.0.0\r\na=ice-ufrag:rBIAAR9AH0A=_1\r\na=ice-pwd:V1WhKKOK9jrhmLPmZemhcO5h\r\na=ice-options:trickle\r\na=fingerprint:sha-256 B4:51:C0:D2:0E:60:70:C2:CD:40:3A:8E:33:EB:FC:67:F6:29:72:89:AC:23:48:90:A0:D7:C0:07:44:7B:F1:79\r\na=setup:active\r\na=mid:0\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\na=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\na=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\na=extmap:10 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07\r\na=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r\na=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\na=extmap:14 urn:ietf:params:rtp-hdrext:toffset\r\na=recvonly\r\na=rtcp-mux\r\na=rtcp-rsize\r\na=rtpmap:102 H264/90000\r\na=rtcp-fb:102 ccm fir\r\na=rtcp-fb:102 goog-remb\r\na=rtcp-fb:102 nack\r\na=rtcp-fb:102 nack pli\r\na=rtcp-fb:102 transport-cc\r\na=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f\r\na=rtpmap:124 H264/90000\r\na=rtcp-fb:124 ccm fir\r\na=rtcp-fb:124 goog-remb\r\na=rtcp-fb:124 nack\r\na=rtcp-fb:124 nack pli\r\na=rtcp-fb:124 transport-cc\r\na=fmtp:124 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f\r\na=rtpmap:123 H264/90000\r\na=rtcp-fb:123 ccm fir\r\na=rtcp-fb:123 goog-remb\r\na=rtcp-fb:123 nack\r\na=rtcp-fb:123 nack pli\r\na=rtcp-fb:123 transport-cc\r\na=fmtp:123 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f\r\na=rtpmap:102 H264/90000\r\na=rtcp-fb:102 ccm fir\r\na=rtcp-fb:102 goog-remb\r\na=rtcp-fb:102 nack\r\na=rtcp-fb:102 nack pli\r\na=rtcp-fb:102 transport-cc\r\na=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f\r\na=rtpmap:124 H264/90000\r\na=rtcp-fb:124 ccm fir\r\na=rtcp-fb:124 goog-remb\r\na=rtcp-fb:124 nack\r\na=rtcp-fb:124 nack pli\r\na=rtcp-fb:124 transport-cc\r\na=fmtp:124 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f\r\na=rtpmap:123 H264/90000\r\na=rtcp-fb:123 ccm fir\r\na=rtcp-fb:123 goog-remb\r\na=rtcp-fb:123 nack\r\na=rtcp-fb:123 nack pli\r\na=rtcp-fb:123 transport-cc\r\na=fmtp:123 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f\r\na=rtpmap:35 AV1/90000\r\na=rtcp-fb:35 ccm fir\r\na=rtcp-fb:35 goog-remb\r\na=rtcp-fb:35 nack\r\na=rtcp-fb:35 nack pli\r\na=rtcp-fb:35 transport-cc\r\na=rtpmap:98 VP9/90000\r\na=rtcp-fb:98 ccm fir\r\na=rtcp-fb:98 goog-remb\r\na=rtcp-fb:98 nack\r\na=rtcp-fb:98 nack pli\r\na=rtcp-fb:98 transport-cc\r\na=fmtp:98 profile-id==0\r\na=rtpmap:100 VP9/90000\r\na=rtcp-fb:100 ccm fir\r\na=rtcp-fb:100 goog-remb\r\na=rtcp-fb:100 nack\r\na=rtcp-fb:100 nack pli\r\na=rtcp-fb:100 transport-cc\r\na=fmtp:100 profile-id==2\r\na=rtpmap:96 VP8/90000\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 goog-remb\r\na=rtcp-fb:96 nack\r\na=rtcp-fb:96 nack pli\r\na=rtcp-fb:96 transport-cc\r\nm=audio 9 UDP/TLS/RTP/SAVPF 0 8 111 96\r\nc=IN IP4 0.0.0.0\r\na=ice-ufrag:rBIAAR9AH0A=_1\r\na=ice-pwd:V1WhKKOK9jrhmLPmZemhcO5h\r\na=ice-options:trickle\r\na=fingerprint:sha-256 B4:51:C0:D2:0E:60:70:C2:CD:40:3A:8E:33:EB:FC:67:F6:29:72:89:AC:23:48:90:A0:D7:C0:07:44:7B:F1:79\r\na=setup:active\r\na=mid:1\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\na=extmap:9 urn:ietf:params:rtp-hdrext:csrc-audio-level\r\na=recvonly\r\na=rtcp-mux\r\na=rtcp-rsize\r\na=rtpmap:0 PCMU/8000\r\na=rtcp-fb:0 goog-remb\r\na=rtcp-fb:0 transport-cc\r\na=rtpmap:8 PCMA/8000\r\na=rtcp-fb:8 goog-remb\r\na=rtcp-fb:8 transport-cc\r\na=rtpmap:111 opus/48000\r\na=rtcp-fb:111 goog-remb\r\na=rtcp-fb:111 transport-cc\r\na=rtpmap:96 mpeg4-generic/48000\r\na=rtcp-fb:96 goog-remb\r\na=rtcp-fb:96 transport-cc\r\n"
}
#peer->server, server -透传->client
{
"class" : "accept",
"method" : "call",
"transaction_id" : "qUpN8C49bGiyOHk6WNanAFq2viSkk6HC",
"guest_id" : "guest1_EDuVWIxLUMlDDKDa",
"room_id" : "room1",
"vhost" : "__defaultVhost__",
"app" : "live",
"stream" : "test",
"type" : "play",
"sdp" : "v=0\r\no=- 7040255305116218076 1 IN IP4 10.9.120.61\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0 1\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS\r\na=ice-lite\r\nm=video 28000 UDP/TLS/RTP/SAVPF 102\r\nc=IN IP4 10.9.120.61\r\na=rtcp:28000 IN IP4 10.9.120.61\r\na=ice-ufrag:rBIAAW1gbWA=_1\r\na=ice-pwd:NmPNJgMbz9z2kH3g97yZFbCn\r\na=ice-options:trickle\r\na=fingerprint:sha-256 B4:51:C0:D2:0E:60:70:C2:CD:40:3A:8E:33:EB:FC:67:F6:29:72:89:AC:23:48:90:A0:D7:C0:07:44:7B:F1:79\r\na=setup:passive\r\na=mid:0\r\na=ice-lite\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\na=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\na=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\na=extmap:10 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07\r\na=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r\na=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\na=extmap:14 urn:ietf:params:rtp-hdrext:toffset\r\na=sendonly\r\na=rtcp-mux\r\na=rtpmap:102 H264/90000\r\na=rtcp-fb:102 ccm fir\r\na=rtcp-fb:102 goog-remb\r\na=rtcp-fb:102 nack\r\na=rtcp-fb:102 nack pli\r\na=rtcp-fb:102 transport-cc\r\na=fmtp:102 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f\r\na=msid:zlmediakit-mslabel zlmediakit-label-0\r\na=ssrc:1 cname:zlmediakit-rtp\r\na=ssrc:1 msid:zlmediakit-mslabel zlmediakit-label-0\r\na=ssrc:1 mslabel:zlmediakit-mslabel\r\na=ssrc:1 label:zlmediakit-label-0\r\nm=audio 28000 UDP/TLS/RTP/SAVPF 0\r\nc=IN IP4 10.9.120.61\r\na=rtcp:28000 IN IP4 10.9.120.61\r\na=ice-ufrag:rBIAAW1gbWA=_1\r\na=ice-pwd:NmPNJgMbz9z2kH3g97yZFbCn\r\na=ice-options:trickle\r\na=fingerprint:sha-256 B4:51:C0:D2:0E:60:70:C2:CD:40:3A:8E:33:EB:FC:67:F6:29:72:89:AC:23:48:90:A0:D7:C0:07:44:7B:F1:79\r\na=setup:passive\r\na=mid:1\r\na=ice-lite\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\na=extmap:9 urn:ietf:params:rtp-hdrext:csrc-audio-level\r\na=inactive\r\na=rtcp-mux\r\na=rtpmap:0 PCMU/8000/1\r\na=rtcp-fb:0 goog-remb\r\na=rtcp-fb:0 transport-cc\r\na=msid:zlmediakit-mslabel zlmediakit-label-1\r\na=ssrc:2 cname:zlmediakit-rtp\r\na=ssrc:2 msid:zlmediakit-mslabel zlmediakit-label-1\r\na=ssrc:2 mslabel:zlmediakit-mslabel\r\na=ssrc:2 label:zlmediakit-label-1\r\n"
}
# candidate
```peer--> server -透传-> peer
{
"class" : "indication",
"method" : "candidate",
"transaction_id" : "7oEa2vcYvps7aZ1g9UGIPoFf5PrTl2N9",
"guest_id" : "guest1_n9WyhNMR42EvkOvE",
"room_id" : "room_1",
"candidate" : "7e0de214 1 udp 2113955071 192.168.1.1 46411 typ host",
"ufrag" : "rBIAAW1gbWA=_1"
"pwd" : "gDNJZM0uVLlnNnthaE41KXOp",
}
# bye
```
{
"class" : "indication",
"method" : "bye",
"transaction_id" : "86RdplPz21Ow9DwR1gvXjsAdmh30TAf3"
"guest_id" : "guest1_n9WyhNMR42EvkOvE",
"reason" : "peer unregister",
"room_id" : "room_1",
}
```