Commit 307be433 by ziyue

Merge branch 'dev' of https://github.com/xia-chu/ZLMediaKit into dev_test

parents 8717cd2f 0eecb3be
...@@ -46,3 +46,4 @@ ...@@ -46,3 +46,4 @@
/3rdpart/media-server/.idea/ /3rdpart/media-server/.idea/
/ios/ /ios/
/cmake-build-* /cmake-build-*
/3rdpart/ZLToolKit/cmake-build-mq/
...@@ -12,19 +12,22 @@ ...@@ -12,19 +12,22 @@
#define ZLMEDIAKIT_ASSERT_H #define ZLMEDIAKIT_ASSERT_H
#include <stdio.h> #include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
extern void Assert_Throw(int failed, const char *exp, const char *func, const char *file, int line);
#ifdef __cplusplus
}
#endif
#define CHECK(exp) Assert_Throw(!(exp), #exp, __FUNCTION__, __FILE__, __LINE__)
#ifndef NDEBUG #ifndef NDEBUG
#ifdef assert #ifdef assert
#undef assert #undef assert
#endif//assert #endif//assert
#ifdef __cplusplus
extern "C" {
#endif
extern void Assert_Throw(int failed, const char *exp, const char *func, const char *file, int line);
#ifdef __cplusplus
}
#endif
#define assert(exp) Assert_Throw(!(exp), #exp, __FUNCTION__, __FILE__, __LINE__); #define assert(exp) Assert_Throw(!(exp), #exp, __FUNCTION__, __FILE__, __LINE__);
#else #else
#define assert(e) ((void)0) #define assert(e) ((void)0)
......
...@@ -55,6 +55,7 @@ option(ENABLE_TESTS "Enable Tests" true) ...@@ -55,6 +55,7 @@ option(ENABLE_TESTS "Enable Tests" true)
option(ENABLE_SERVER "Enable Server" true) option(ENABLE_SERVER "Enable Server" true)
option(ENABLE_MEM_DEBUG "Enable Memory Debug" false) option(ENABLE_MEM_DEBUG "Enable Memory Debug" false)
option(ENABLE_ASAN "Enable Address Sanitize" false) option(ENABLE_ASAN "Enable Address Sanitize" false)
option(ENABLE_WEBRTC "Enable WebRTC" true)
if (ENABLE_MEM_DEBUG) if (ENABLE_MEM_DEBUG)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,-wrap,free -Wl,-wrap,malloc -Wl,-wrap,realloc -Wl,-wrap,calloc") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,-wrap,free -Wl,-wrap,malloc -Wl,-wrap,realloc -Wl,-wrap,calloc")
...@@ -86,6 +87,10 @@ if (OPENSSL_FOUND AND ENABLE_OPENSSL) ...@@ -86,6 +87,10 @@ if (OPENSSL_FOUND AND ENABLE_OPENSSL)
include_directories(${OPENSSL_INCLUDE_DIR}) include_directories(${OPENSSL_INCLUDE_DIR})
add_definitions(-DENABLE_OPENSSL) add_definitions(-DENABLE_OPENSSL)
list(APPEND LINK_LIB_LIST ${OPENSSL_LIBRARIES}) list(APPEND LINK_LIB_LIST ${OPENSSL_LIBRARIES})
if (CMAKE_SYSTEM_NAME MATCHES "Linux" AND OPENSSL_USE_STATIC_LIBS)
list(APPEND LINK_LIB_LIST dl)
endif()
else() else()
message(WARNING "openssl未找到,rtmp将不支持flash播放器,https/wss/rtsps/rtmps也将失效") message(WARNING "openssl未找到,rtmp将不支持flash播放器,https/wss/rtsps/rtmps也将失效")
endif () endif ()
...@@ -224,6 +229,24 @@ if(ENABLE_API) ...@@ -224,6 +229,24 @@ if(ENABLE_API)
add_subdirectory(api) add_subdirectory(api)
endif() endif()
if (ENABLE_WEBRTC)
#查找srtp是否安装
find_package(SRTP QUIET)
if (SRTP_FOUND)
message(STATUS "found library:${SRTP_LIBRARIES}")
include_directories(${SRTP_INCLUDE_DIRS})
list(APPEND LINK_LIB_LIST ${SRTP_LIBRARIES})
add_definitions(-DENABLE_WEBRTC)
include_directories(./webrtc)
file(GLOB SRC_WEBRTC_LIST ./webrtc/*.cpp ./webrtc/*.h ./webrtc/*.hpp)
add_library(webrtc ${SRC_WEBRTC_LIST})
list(APPEND LINK_LIB_LIST webrtc)
else ()
message(WARNING "srtp未找到, webrtc相关功能打开失败")
endif ()
endif ()
if (NOT IOS) if (NOT IOS)
#测试程序 #测试程序
if(ENABLE_TESTS) if(ENABLE_TESTS)
......
############################################################################
# FindSRTP.txt
# Copyright (C) 2014 Belledonne Communications, Grenoble France
#
############################################################################
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
############################################################################
#
# - Find the SRTP include file and library
#
# SRTP_FOUND - system has SRTP
# SRTP_INCLUDE_DIRS - the SRTP include directory
# SRTP_LIBRARIES - The libraries needed to use SRTP
set(_SRTP_ROOT_PATHS
${CMAKE_INSTALL_PREFIX}
)
find_path(SRTP_INCLUDE_DIRS
NAMES srtp2/srtp.h
HINTS _SRTP_ROOT_PATHS ${SRTP_PREFIX}
PATH_SUFFIXES include
)
if(SRTP_INCLUDE_DIRS)
set(HAVE_SRTP_SRTP_H 1)
endif()
find_library(SRTP_LIBRARIES
NAMES srtp2
HINTS ${_SRTP_ROOT_PATHS} ${SRTP_PREFIX}
PATH_SUFFIXES bin lib
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(SRTP
DEFAULT_MSG
SRTP_INCLUDE_DIRS SRTP_LIBRARIES HAVE_SRTP_SRTP_H
)
mark_as_advanced(SRTP_INCLUDE_DIRS SRTP_LIBRARIES HAVE_SRTP_SRTP_H)
\ No newline at end of file
...@@ -140,7 +140,7 @@ charSet=utf-8 ...@@ -140,7 +140,7 @@ charSet=utf-8
#http链接超时时间 #http链接超时时间
keepAliveSecond=30 keepAliveSecond=30
#http请求体最大字节数,如果post的body太大,则不适合缓存body在内存 #http请求体最大字节数,如果post的body太大,则不适合缓存body在内存
maxReqSize=4096 maxReqSize=40960
#404网页内容,用户可以自定义404网页 #404网页内容,用户可以自定义404网页
notFound=<html><head><title>404 Not Found</title></head><body bgcolor="white"><center><h1>您访问的资源不存在!</h1></center><hr><center>ZLMediaKit-4.0</center></body></html> notFound=<html><head><title>404 Not Found</title></head><body bgcolor="white"><center><h1>您访问的资源不存在!</h1></center><hr><center>ZLMediaKit-4.0</center></body></html>
#http服务器监听端口 #http服务器监听端口
...@@ -210,13 +210,23 @@ port=10000 ...@@ -210,13 +210,23 @@ port=10000
#rtp超时时间,单位秒 #rtp超时时间,单位秒
timeoutSec=15 timeoutSec=15
[rtc]
#rtc播放推流、播放超时时间
timeoutSec=15
#本机对rtc客户端的可见ip,作为服务器时一般为公网ip,置空时,会自动获取网卡ip
externIP=
#设置remb比特率,非0时关闭twcc并开启remb。该设置在rtc推流时有效,可以控制推流画质
rembBitRate=1000000
[rtsp] [rtsp]
#rtsp专有鉴权方式是采用base64还是md5方式 #rtsp专有鉴权方式是采用base64还是md5方式
authBasic=0 authBasic=0
#rtsp拉流代理是否是直接代理模式 #rtsp拉流、推流代理是否是直接代理模式
#直接代理后支持任意编码格式,但是会导致GOP缓存无法定位到I帧,可能会导致开播花屏 #直接代理后支持任意编码格式,但是会导致GOP缓存无法定位到I帧,可能会导致开播花屏
#并且如果是tcp方式拉流,如果rtp大于mtu会导致无法使用udp方式代理 #并且如果是tcp方式拉流,如果rtp大于mtu会导致无法使用udp方式代理
#假定您的拉流源地址不是264或265或AAC,那么你可以使用直接代理的方式来支持rtsp代理 #假定您的拉流源地址不是264或265或AAC,那么你可以使用直接代理的方式来支持rtsp代理
#如果你是rtsp推拉流,但是rtc播放,也建议关闭直接代理模式,
#因为直接代理时,rtp中可能没有sps pps,会导致rtc无法播放
#默认开启rtsp直接代理,rtmp由于没有这些问题,是强制开启直接代理的 #默认开启rtsp直接代理,rtmp由于没有这些问题,是强制开启直接代理的
directProxy=1 directProxy=1
#rtsp必须在此时间内完成握手,否则服务器会断开链接,单位秒 #rtsp必须在此时间内完成握手,否则服务器会断开链接,单位秒
......
...@@ -2,10 +2,12 @@ ...@@ -2,10 +2,12 @@
%global use_devtoolset 0 %global use_devtoolset 0
%bcond_without faac %bcond_without faac
%bcond_without x264 %bcond_without x264
%bcond_without webrtc
%else %else
%global use_devtoolset 1 %global use_devtoolset 1
%bcond_with faac %bcond_with faac
%bcond_with x264 %bcond_with x264
%bcond_with webrtc
%endif %endif
%bcond_without openssl %bcond_without openssl
...@@ -22,8 +24,12 @@ URL: https://github.com/xia-chu/ZLMediaKit ...@@ -22,8 +24,12 @@ URL: https://github.com/xia-chu/ZLMediaKit
Source0: %{name}-%{version}.tar.xz Source0: %{name}-%{version}.tar.xz
%if %{with openssl} %if %{with openssl}
%if 0%{?rhel} <= 7 && %{with webrtc}
BuildRequires: openssl11-devel
%else
BuildRequires: openssl-devel BuildRequires: openssl-devel
%endif %endif
%endif
%if %{with mysql} %if %{with mysql}
BuildRequires: mysql-devel BuildRequires: mysql-devel
...@@ -37,6 +43,10 @@ BuildRequires: faac-devel ...@@ -37,6 +43,10 @@ BuildRequires: faac-devel
BuildRequires: x264-devel BuildRequires: x264-devel
%endif %endif
%if %{with webrtc}
BuildRequires: libsrtp-devel >= 2.0
%endif
%if 0%{?use_devtoolset} %if 0%{?use_devtoolset}
BuildRequires: devtoolset-8-gcc-c++ BuildRequires: devtoolset-8-gcc-c++
%endif %endif
...@@ -88,6 +98,10 @@ pushd %{_target_platform} ...@@ -88,6 +98,10 @@ pushd %{_target_platform}
-DENABLE_MYSQL:BOOL=%{with mysql} \ -DENABLE_MYSQL:BOOL=%{with mysql} \
-DENABLE_FAAC:BOOL=%{with faac} \ -DENABLE_FAAC:BOOL=%{with faac} \
-DENABLE_X264:BOOL=%{with x264} \ -DENABLE_X264:BOOL=%{with x264} \
-DENABLE_WEBRTC:BOOL=%{with webrtc} \
%if %{with webrtc} && 0%{?rhel} <= 7
-DOPENSSL_ROOT_DIR:STRING="/usr/lib64/openssl11;/usr/include/openssl11" \
%endif
-DENABLE_MP4:BOOL=ON \ -DENABLE_MP4:BOOL=ON \
-DENABLE_RTPPROXY:BOOL=ON \ -DENABLE_RTPPROXY:BOOL=ON \
-DENABLE_API:BOOL=ON \ -DENABLE_API:BOOL=ON \
......
...@@ -48,4 +48,8 @@ else() ...@@ -48,4 +48,8 @@ else()
install(TARGETS MediaServer DESTINATION ${INSTALL_PATH_EXECUTABLE}) install(TARGETS MediaServer DESTINATION ${INSTALL_PATH_EXECUTABLE})
endif() endif()
target_link_libraries(MediaServer jsoncpp ${LINK_LIB_LIST}) if (CMAKE_SYSTEM_NAME MATCHES "Linux")
target_link_libraries(MediaServer -Wl,--start-group jsoncpp ${LINK_LIB_LIST} -Wl,--end-group)
else ()
target_link_libraries(MediaServer jsoncpp ${LINK_LIB_LIST})
endif ()
...@@ -37,6 +37,9 @@ ...@@ -37,6 +37,9 @@
#if defined(ENABLE_RTPPROXY) #if defined(ENABLE_RTPPROXY)
#include "Rtp/RtpServer.h" #include "Rtp/RtpServer.h"
#endif #endif
#ifdef ENABLE_WEBRTC
#include "../webrtc/WebRtcTransport.h"
#endif
using namespace toolkit; using namespace toolkit;
using namespace mediakit; using namespace mediakit;
...@@ -126,6 +129,26 @@ static HttpApi toApi(const function<void(API_ARGS_JSON)> &cb) { ...@@ -126,6 +129,26 @@ static HttpApi toApi(const function<void(API_ARGS_JSON)> &cb) {
}); });
} }
static HttpApi toApi(const function<void(API_ARGS_STRING_ASYNC)> &cb) {
return [cb](const Parser &parser, const HttpSession::HttpResponseInvoker &invoker, SockInfo &sender) {
GET_CONFIG(string, charSet, Http::kCharSet);
HttpSession::KeyValue headerOut;
headerOut["Content-Type"] = string("application/json; charset=") + charSet;
Json::Value val;
val["code"] = API::Success;
cb(sender, parser.getHeader(), headerOut, parser, val, invoker);
};
}
static HttpApi toApi(const function<void(API_ARGS_STRING)> &cb) {
return toApi([cb](API_ARGS_STRING_ASYNC) {
cb(API_ARGS_VALUE);
invoker(200, headerOut, val.toStyledString());
});
}
void api_regist(const string &api_path, const function<void(API_ARGS_MAP)> &func) { void api_regist(const string &api_path, const function<void(API_ARGS_MAP)> &func) {
s_map_api.emplace(api_path, toApi(func)); s_map_api.emplace(api_path, toApi(func));
} }
...@@ -142,6 +165,14 @@ void api_regist(const string &api_path, const function<void(API_ARGS_JSON_ASYNC) ...@@ -142,6 +165,14 @@ void api_regist(const string &api_path, const function<void(API_ARGS_JSON_ASYNC)
s_map_api.emplace(api_path, toApi(func)); s_map_api.emplace(api_path, toApi(func));
} }
void api_regist(const string &api_path, const function<void(API_ARGS_STRING)> &func){
s_map_api.emplace(api_path, toApi(func));
}
void api_regist(const string &api_path, const function<void(API_ARGS_STRING_ASYNC)> &func){
s_map_api.emplace(api_path, toApi(func));
}
//获取HTTP请求中url参数、content参数 //获取HTTP请求中url参数、content参数
static ApiArgsType getAllArgs(const Parser &parser) { static ApiArgsType getAllArgs(const Parser &parser) {
ApiArgsType allArgs; ApiArgsType allArgs;
...@@ -380,8 +411,15 @@ void installWebApi() { ...@@ -380,8 +411,15 @@ void installWebApi() {
int changed = API::Success; int changed = API::Success;
for (auto &pr : allArgs) { for (auto &pr : allArgs) {
if (ini.find(pr.first) == ini.end()) { if (ini.find(pr.first) == ini.end()) {
#if 1
//没有这个key //没有这个key
continue; continue;
#else
// 新增配置选项,为了动态添加多个ffmpeg cmd 模板
ini[pr.first] = pr.second;
// 防止changed变化
continue;
#endif
} }
if (ini[pr.first] == pr.second) { if (ini[pr.first] == pr.second) {
continue; continue;
...@@ -1161,6 +1199,90 @@ void installWebApi() { ...@@ -1161,6 +1199,90 @@ void installWebApi() {
#endif #endif
}); });
#ifdef ENABLE_WEBRTC
api_regist("/index/api/webrtc",[](API_ARGS_STRING_ASYNC){
CHECK_ARGS("app", "stream");
auto offer_sdp = allArgs.Content();
auto type = allArgs.getUrlArgs()["type"];
MediaInfo info(StrPrinter << "rtc://" << headerIn["Host"] << "/" << allArgs.getUrlArgs()["app"] << "/" << allArgs.getUrlArgs()["stream"] << "?" << allArgs.Params());
//设置返回类型
headerOut["Content-Type"] = HttpFileManager::getContentType(".json");
//设置跨域
headerOut["Access-Control-Allow-Origin"] = "*";
if (type.empty() || !strcasecmp(type.data(), "play")) {
Broadcast::AuthInvoker authInvoker = [invoker, offer_sdp, val, info, headerOut](const string &err) mutable {
try {
auto src = dynamic_pointer_cast<RtspMediaSource>(MediaSource::find(RTSP_SCHEMA, info._vhost, info._app, info._streamid));
if (!src) {
throw runtime_error("流不存在");
}
if (!err.empty()) {
throw runtime_error(StrPrinter << "播放鉴权失败:" << err);
}
auto rtc = WebRtcTransportImp::create(EventPollerPool::Instance().getPoller());
rtc->attach(src, info, true);
val["sdp"] = rtc->getAnswerSdp(offer_sdp);
val["type"] = "answer";
invoker(200, headerOut, val.toStyledString());
} catch (std::exception &ex) {
val["code"] = API::Exception;
val["msg"] = ex.what();
invoker(200, headerOut, val.toStyledString());
}
};
//广播通用播放url鉴权事件
auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, info, authInvoker, sender);
if (!flag) {
//该事件无人监听,默认不鉴权
authInvoker("");
}
return;
}
if (!strcasecmp(type.data(), "push")) {
Broadcast::PublishAuthInvoker authInvoker = [invoker, offer_sdp, val, info, headerOut](const string &err, bool enableHls, bool enableMP4) mutable {
try {
auto src = dynamic_pointer_cast<RtspMediaSource>(MediaSource::find(RTSP_SCHEMA, info._vhost, info._app, info._streamid));
if (src) {
throw std::runtime_error("已经在推流");
}
if (!err.empty()) {
throw runtime_error(StrPrinter << "推流鉴权失败:" << err);
}
auto push_src = std::make_shared<RtspMediaSourceImp>(info._vhost, info._app, info._streamid);
push_src->setProtocolTranslation(enableHls, enableMP4);
auto rtc = WebRtcTransportImp::create(EventPollerPool::Instance().getPoller());
push_src->setListener(rtc);
rtc->attach(push_src, info, false);
val["sdp"] = rtc->getAnswerSdp(offer_sdp);
val["type"] = "answer";
invoker(200, headerOut, val.toStyledString());
} catch (std::exception &ex) {
val["code"] = API::Exception;
val["msg"] = ex.what();
invoker(200, headerOut, val.toStyledString());
}
};
//rtsp推流需要鉴权
auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPublish, info, authInvoker, sender);
if (!flag) {
//该事件无人监听,默认不鉴权
GET_CONFIG(bool, toHls, General::kPublishToHls);
GET_CONFIG(bool, toMP4, General::kPublishToMP4);
authInvoker("", toHls, toMP4);
}
return;
}
throw ApiRetException("不支持该类型", API::InvalidArgs);
});
#endif
////////////以下是注册的Hook API//////////// ////////////以下是注册的Hook API////////////
api_regist("/index/hook/on_publish",[](API_ARGS_MAP){ api_regist("/index/hook/on_publish",[](API_ARGS_MAP){
//开始推流事件 //开始推流事件
......
...@@ -85,6 +85,8 @@ using ApiArgsType = map<string, variant, StrCaseCompare>; ...@@ -85,6 +85,8 @@ using ApiArgsType = map<string, variant, StrCaseCompare>;
#define API_ARGS_MAP_ASYNC API_ARGS_MAP, const HttpSession::HttpResponseInvoker &invoker #define API_ARGS_MAP_ASYNC API_ARGS_MAP, const HttpSession::HttpResponseInvoker &invoker
#define API_ARGS_JSON SockInfo &sender, HttpSession::KeyValue &headerIn, HttpSession::KeyValue &headerOut, Json::Value &allArgs, Json::Value &val #define API_ARGS_JSON SockInfo &sender, HttpSession::KeyValue &headerIn, HttpSession::KeyValue &headerOut, Json::Value &allArgs, Json::Value &val
#define API_ARGS_JSON_ASYNC API_ARGS_JSON, const HttpSession::HttpResponseInvoker &invoker #define API_ARGS_JSON_ASYNC API_ARGS_JSON, const HttpSession::HttpResponseInvoker &invoker
#define API_ARGS_STRING SockInfo &sender, HttpSession::KeyValue &headerIn, HttpSession::KeyValue &headerOut, const Parser &allArgs, Json::Value &val
#define API_ARGS_STRING_ASYNC API_ARGS_STRING, const HttpSession::HttpResponseInvoker &invoker
#define API_ARGS_VALUE sender, headerIn, headerOut, allArgs, val #define API_ARGS_VALUE sender, headerIn, headerOut, allArgs, val
//注册http请求参数是map<string, variant, StrCaseCompare>类型的http api //注册http请求参数是map<string, variant, StrCaseCompare>类型的http api
...@@ -97,6 +99,11 @@ void api_regist(const string &api_path, const function<void(API_ARGS_JSON)> &fun ...@@ -97,6 +99,11 @@ void api_regist(const string &api_path, const function<void(API_ARGS_JSON)> &fun
//注册http请求参数是Json::Value类型,但是可以异步回复的的http api //注册http请求参数是Json::Value类型,但是可以异步回复的的http api
void api_regist(const string &api_path, const function<void(API_ARGS_JSON_ASYNC)> &func); void api_regist(const string &api_path, const function<void(API_ARGS_JSON_ASYNC)> &func);
//注册http请求参数是http原始请求信息的http api
void api_regist(const string &api_path, const function<void(API_ARGS_STRING)> &func);
//注册http请求参数是http原始请求信息的异步回复的http api
void api_regist(const string &api_path, const function<void(API_ARGS_STRING_ASYNC)> &func);
template<typename Args, typename First> template<typename Args, typename First>
bool checkArgs(Args &&args, First &&first) { bool checkArgs(Args &&args, First &&first) {
return !args[first].empty(); return !args[first].empty();
...@@ -107,6 +114,16 @@ bool checkArgs(Args &&args, First &&first, KeyTypes &&...keys) { ...@@ -107,6 +114,16 @@ bool checkArgs(Args &&args, First &&first, KeyTypes &&...keys) {
return !args[first].empty() && checkArgs(std::forward<Args>(args), std::forward<KeyTypes>(keys)...); return !args[first].empty() && checkArgs(std::forward<Args>(args), std::forward<KeyTypes>(keys)...);
} }
template<typename First>
bool checkArgs(const Parser &args, First &&first) {
return !args.getUrlArgs()[first].empty();
}
template<typename First, typename ...KeyTypes>
bool checkArgs(const Parser &args, First &&first, KeyTypes &&...keys) {
return !args.getUrlArgs()[first].empty() && checkArgs(args, std::forward<KeyTypes>(keys)...);
}
//检查http参数是否为空的宏 //检查http参数是否为空的宏
#define CHECK_ARGS(...) \ #define CHECK_ARGS(...) \
if(!checkArgs(allArgs,##__VA_ARGS__)){ \ if(!checkArgs(allArgs,##__VA_ARGS__)){ \
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include "Rtp/RtpServer.h" #include "Rtp/RtpServer.h"
#include "WebApi.h" #include "WebApi.h"
#include "WebHook.h" #include "WebHook.h"
#include "../webrtc/Sdp.h"
#if defined(ENABLE_VERSION) #if defined(ENABLE_VERSION)
#include "Version.h" #include "Version.h"
......
...@@ -35,6 +35,7 @@ string getOriginTypeString(MediaOriginType type){ ...@@ -35,6 +35,7 @@ string getOriginTypeString(MediaOriginType type){
SWITCH_CASE(ffmpeg_pull); SWITCH_CASE(ffmpeg_pull);
SWITCH_CASE(mp4_vod); SWITCH_CASE(mp4_vod);
SWITCH_CASE(device_chn); SWITCH_CASE(device_chn);
SWITCH_CASE(rtc_push);
default : return "unknown"; default : return "unknown";
} }
} }
......
...@@ -45,7 +45,8 @@ enum class MediaOriginType : uint8_t { ...@@ -45,7 +45,8 @@ enum class MediaOriginType : uint8_t {
pull, pull,
ffmpeg_pull, ffmpeg_pull,
mp4_vod, mp4_vod,
device_chn device_chn,
rtc_push,
}; };
string getOriginTypeString(MediaOriginType type); string getOriginTypeString(MediaOriginType type);
......
...@@ -205,7 +205,7 @@ public: ...@@ -205,7 +205,7 @@ public:
if (bitrate) { if (bitrate) {
_printer << "b=AS:" << bitrate << "\r\n"; _printer << "b=AS:" << bitrate << "\r\n";
} }
_printer << "a=rtpmap:" << payload_type << " mpeg4-generic/" << sample_rate << "/" << channels << "\r\n"; _printer << "a=rtpmap:" << payload_type << " " << getCodecName() << "/" << sample_rate << "/" << channels << "\r\n";
string configStr; string configStr;
char buf[4] = {0}; char buf[4] = {0};
......
...@@ -69,7 +69,7 @@ CommonRtpEncoder::CommonRtpEncoder(CodecId codec, uint32_t ssrc, uint32_t mtu_si ...@@ -69,7 +69,7 @@ CommonRtpEncoder::CommonRtpEncoder(CodecId codec, uint32_t ssrc, uint32_t mtu_si
} }
void CommonRtpEncoder::inputFrame(const Frame::Ptr &frame){ void CommonRtpEncoder::inputFrame(const Frame::Ptr &frame){
auto stamp = frame->dts(); auto stamp = frame->pts();
auto ptr = frame->data() + frame->prefixSize(); auto ptr = frame->data() + frame->prefixSize();
auto len = frame->size() - frame->prefixSize(); auto len = frame->size() - frame->prefixSize();
auto remain_size = len; auto remain_size = len;
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
#include "Frame.h" #include "Frame.h"
#include "H264.h" #include "H264.h"
#include "H265.h" #include "H265.h"
#include "Common/Parser.h"
using namespace std; using namespace std;
using namespace toolkit; using namespace toolkit;
...@@ -106,30 +106,50 @@ Frame::Ptr Frame::getCacheAbleFrame(const Frame::Ptr &frame){ ...@@ -106,30 +106,50 @@ Frame::Ptr Frame::getCacheAbleFrame(const Frame::Ptr &frame){
return std::make_shared<FrameCacheAble>(frame); return std::make_shared<FrameCacheAble>(frame);
} }
#define SWITCH_CASE(codec_id) case codec_id : return #codec_id TrackType getTrackType(CodecId codecId) {
const char *getCodecName(CodecId codecId) {
switch (codecId) { switch (codecId) {
SWITCH_CASE(CodecH264); #define XX(name, type, value, str) case name : return type;
SWITCH_CASE(CodecH265); CODEC_MAP(XX)
SWITCH_CASE(CodecAAC); #undef XX
SWITCH_CASE(CodecG711A); default : return TrackInvalid;
SWITCH_CASE(CodecG711U);
SWITCH_CASE(CodecOpus);
SWITCH_CASE(CodecL16);
default : return "unknown codec";
} }
} }
TrackType getTrackType(CodecId codecId){ const char *getCodecName(CodecId codec) {
switch (codecId){ switch (codec) {
case CodecH264: #define XX(name, type, value, str) case name : return str;
case CodecH265: return TrackVideo; CODEC_MAP(XX)
case CodecAAC: #undef XX
case CodecG711A: default : return "invalid";
case CodecG711U: }
case CodecOpus: }
case CodecL16: return TrackAudio;
default: return TrackInvalid; #define XX(name, type, value, str) {str, name},
static map<string, CodecId, StrCaseCompare> codec_map = {CODEC_MAP(XX)};
#undef XX
CodecId getCodecId(const string &str){
auto it = codec_map.find(str);
return it == codec_map.end() ? CodecInvalid : it->second;
}
static map<string, TrackType, StrCaseCompare> track_str_map = {
{"video", TrackVideo},
{"audio", TrackAudio},
{"application", TrackApplication}
};
TrackType getTrackType(const string &str) {
auto it = track_str_map.find(str);
return it == track_str_map.end() ? TrackInvalid : it->second;
}
const char* getTrackString(TrackType type){
switch (type) {
case TrackVideo : return "video";
case TrackAudio : return "audio";
case TrackApplication : return "application";
default: return "invalid";
} }
} }
...@@ -171,16 +191,23 @@ bool FrameMerger::willFlush(const Frame::Ptr &frame) const{ ...@@ -171,16 +191,23 @@ bool FrameMerger::willFlush(const Frame::Ptr &frame) const{
} }
switch (frame->getCodecId()) { switch (frame->getCodecId()) {
case CodecH264 : { case CodecH264 : {
if (H264_TYPE(frame->data()[frame->prefixSize()]) == H264Frame::NAL_B_P) { auto type = H264_TYPE(frame->data()[frame->prefixSize()]);
//如果是264的b/p帧,那么也刷新输出 if ((frame->data()[frame->prefixSize()+1]&0x80) !=0 && type >=H264Frame::NAL_B_P && type<=H264Frame::NAL_IDR ) {// sei aud pps sps 不判断
//264 新一帧的开始,刷新输出
return true; return true;
}else{
// 不刷新输出
return false;
} }
break; break;
} }
case CodecH265 : { case CodecH265 : {
if (H265_TYPE(frame->data()[frame->prefixSize()]) == H265Frame::NAL_TRAIL_R) { auto type = H265_TYPE(frame->data()[frame->prefixSize()]);
//如果是265的TRAIL_R帧,那么也刷新输出 if ((type>=H265Frame::NAL_TRAIL_R &&type<= H265Frame::NAL_RSV_IRAP_VCL23) && ( (frame->data()[frame->prefixSize()+2]>>7 & 0x01) != 0)) {
//first_slice_segment_in_pic_flag is frame start
return true; return true;
}else{
return false;
} }
break; break;
} }
...@@ -217,8 +244,33 @@ void FrameMerger::doMerge(BufferLikeString &merged, const Frame::Ptr &frame) con ...@@ -217,8 +244,33 @@ void FrameMerger::doMerge(BufferLikeString &merged, const Frame::Ptr &frame) con
default: /*不可达*/ assert(0); break; default: /*不可达*/ assert(0); break;
} }
} }
bool FrameMerger::shouldDrop(const Frame::Ptr &frame) const{
switch (frame->getCodecId()) {
case CodecH264:{
auto type = H264_TYPE(frame->data()[frame->prefixSize()]);
if(type == H264Frame::NAL_SEI || type == H264Frame::NAL_AUD){
// 防止吧AUD或者SEI当成一帧
return true;
}
break;
}
case CodecH265: {
//如果是新的一帧,前面的缓存需要输出
auto type = H265_TYPE(frame->data()[frame->prefixSize()]);
if(type == H265Frame::NAL_AUD || type == H265Frame::NAL_SEI_PREFIX || type == H265Frame::NAL_SEI_SUFFIX){
// 防止吧AUD或者SEI当成一帧
return true;
}
break;
}
default: break;
}
return false;
}
void FrameMerger::inputFrame(const Frame::Ptr &frame, const onOutput &cb) { void FrameMerger::inputFrame(const Frame::Ptr &frame, const onOutput &cb) {
if(shouldDrop(frame)){
return;
}
if (willFlush(frame)) { if (willFlush(frame)) {
Frame::Ptr back = _frameCached.back(); Frame::Ptr back = _frameCached.back();
Buffer::Ptr merged_frame = back; Buffer::Ptr merged_frame = back;
......
...@@ -22,25 +22,51 @@ using namespace toolkit; ...@@ -22,25 +22,51 @@ using namespace toolkit;
namespace mediakit{ namespace mediakit{
typedef enum { typedef enum {
CodecInvalid = -1,
CodecH264 = 0,
CodecH265,
CodecAAC,
CodecG711A,
CodecG711U,
CodecOpus,
CodecL16,
CodecMax = 0x7FFF
} CodecId;
typedef enum {
TrackInvalid = -1, TrackInvalid = -1,
TrackVideo = 0, TrackVideo = 0,
TrackAudio, TrackAudio,
TrackTitle, TrackTitle,
TrackMax = 3 TrackApplication,
TrackMax
} TrackType; } TrackType;
#define CODEC_MAP(XX) \
XX(CodecH264, TrackVideo, 0, "H264") \
XX(CodecH265, TrackVideo, 1, "H265") \
XX(CodecAAC, TrackAudio, 2, "mpeg4-generic") \
XX(CodecG711A, TrackAudio, 3, "PCMA") \
XX(CodecG711U, TrackAudio, 4, "PCMU") \
XX(CodecOpus, TrackAudio, 5, "opus") \
XX(CodecL16, TrackAudio, 6, "L16") \
XX(CodecVP8, TrackVideo, 7, "VP8") \
XX(CodecVP9, TrackVideo, 8, "VP9") \
XX(CodecAV1, TrackVideo, 9, "AV1X")
typedef enum {
CodecInvalid = -1,
#define XX(name, type, value, str) name = value,
CODEC_MAP(XX)
#undef XX
CodecMax
} CodecId;
/**
* 字符串转媒体类型转
*/
TrackType getTrackType(const string &str);
/**
* 媒体类型转字符串
*/
const char* getTrackString(TrackType type);
/**
* 根据SDP中描述获取codec_id
* @param str
* @return
*/
CodecId getCodecId(const string &str);
/** /**
* 获取编码器名称 * 获取编码器名称
*/ */
...@@ -449,6 +475,7 @@ public: ...@@ -449,6 +475,7 @@ public:
private: private:
bool willFlush(const Frame::Ptr &frame) const; bool willFlush(const Frame::Ptr &frame) const;
void doMerge(BufferLikeString &buffer, const Frame::Ptr &frame) const; void doMerge(BufferLikeString &buffer, const Frame::Ptr &frame) const;
bool shouldDrop(const Frame::Ptr &frame) const;
private: private:
int _type; int _type;
......
...@@ -33,7 +33,7 @@ public: ...@@ -33,7 +33,7 @@ public:
if (bitrate) { if (bitrate) {
_printer << "b=AS:" << bitrate << "\r\n"; _printer << "b=AS:" << bitrate << "\r\n";
} }
_printer << "a=rtpmap:" << payload_type << (codecId == CodecG711A ? " PCMA/" : " PCMU/") << sample_rate << "/" << channels << "\r\n"; _printer << "a=rtpmap:" << payload_type << " " << getCodecName() << "/" << sample_rate << "/" << channels << "\r\n";
_printer << "a=control:trackID=" << (int)TrackAudio << "\r\n"; _printer << "a=control:trackID=" << (int)TrackAudio << "\r\n";
} }
......
...@@ -181,17 +181,15 @@ void H264Track::inputFrame_l(const Frame::Ptr &frame){ ...@@ -181,17 +181,15 @@ void H264Track::inputFrame_l(const Frame::Ptr &frame){
_pps = string(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize()); _pps = string(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize());
break; break;
} }
case H264Frame::NAL_IDR: {
insertConfigFrame(frame);
VideoTrack::inputFrame(frame);
break;
}
case H264Frame::NAL_AUD: { case H264Frame::NAL_AUD: {
//忽略AUD帧; //忽略AUD帧;
break; break;
} }
default: default:
if (frame->keyFrame()) {
insertConfigFrame(frame);
}
VideoTrack::inputFrame(frame); VideoTrack::inputFrame(frame);
break; break;
} }
...@@ -235,7 +233,7 @@ public: ...@@ -235,7 +233,7 @@ public:
if (bitrate) { if (bitrate) {
_printer << "b=AS:" << bitrate << "\r\n"; _printer << "b=AS:" << bitrate << "\r\n";
} }
_printer << "a=rtpmap:" << payload_type << " H264/" << 90000 << "\r\n"; _printer << "a=rtpmap:" << payload_type << " " << getCodecName() << "/" << 90000 << "\r\n";
_printer << "a=fmtp:" << payload_type << " packetization-mode=1; profile-level-id="; _printer << "a=fmtp:" << payload_type << " packetization-mode=1; profile-level-id=";
char strTemp[1024]; char strTemp[1024];
...@@ -281,7 +279,8 @@ Sdp::Ptr H264Track::getSdp() { ...@@ -281,7 +279,8 @@ Sdp::Ptr H264Track::getSdp() {
//////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////
bool H264Frame::keyFrame() const { bool H264Frame::keyFrame() const {
return H264_TYPE(_buffer[_prefix_size]) == H264Frame::NAL_IDR; //多slice 一帧的情况下检查 first_mb_in_slice 是否为0 表示其为一帧的开始
return H264_TYPE(_buffer[_prefix_size]) == H264Frame::NAL_IDR && (_buffer[_prefix_size + 1] & 0x80);
} }
bool H264Frame::configFrame() const { bool H264Frame::configFrame() const {
......
...@@ -183,7 +183,7 @@ void H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) { ...@@ -183,7 +183,7 @@ void H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
} }
} }
if(_lastPacket && (_lastPacket->time_stamp != frame->dts() || type == H264Frame::NAL_B_P)) { if(_lastPacket && (_lastPacket->time_stamp != frame->dts() || ((pcData[1]&0x80) != 0 && type>=H264Frame::NAL_B_P && type<=H264Frame::NAL_IDR))) {
RtmpCodec::inputRtmp(_lastPacket); RtmpCodec::inputRtmp(_lastPacket);
_lastPacket = nullptr; _lastPacket = nullptr;
} }
...@@ -214,10 +214,6 @@ void H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) { ...@@ -214,10 +214,6 @@ void H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
_lastPacket->buffer.append((char *) &size, 4); _lastPacket->buffer.append((char *) &size, 4);
_lastPacket->buffer.append(pcData, iLen); _lastPacket->buffer.append(pcData, iLen);
_lastPacket->body_size = _lastPacket->buffer.size(); _lastPacket->body_size = _lastPacket->buffer.size();
if (type == H264Frame::NAL_B_P) {
RtmpCodec::inputRtmp(_lastPacket);
_lastPacket = nullptr;
}
} }
void H264RtmpEncoder::makeVideoConfigPkt() { void H264RtmpEncoder::makeVideoConfigPkt() {
......
...@@ -184,21 +184,39 @@ H264RtpEncoder::H264RtpEncoder(uint32_t ssrc, uint32_t mtu, uint32_t sample_rate ...@@ -184,21 +184,39 @@ H264RtpEncoder::H264RtpEncoder(uint32_t ssrc, uint32_t mtu, uint32_t sample_rate
: RtpInfo(ssrc, mtu, sample_rate, pt, interleaved) { : RtpInfo(ssrc, mtu, sample_rate, pt, interleaved) {
} }
void H264RtpEncoder::inputFrame(const Frame::Ptr &frame) { void H264RtpEncoder::insertConfigFrame(uint32_t pts){
auto ptr = frame->data() + frame->prefixSize(); if (!_sps || !_pps) {
auto len = frame->size() - frame->prefixSize(); return;
auto pts = frame->pts(); }
auto nal_type = H264_TYPE(ptr[0]); //gop缓存从sps开始,sps、pps后面还有时间戳相同的关键帧,所以mark bit为false
packRtp(_sps->data() + _sps->prefixSize(), _sps->size() - _sps->prefixSize(), pts, false, true);
packRtp(_pps->data() + _pps->prefixSize(), _pps->size() - _pps->prefixSize(), pts, false, false);
}
void H264RtpEncoder::packRtp(const char *ptr, size_t len, uint32_t pts, bool is_mark, bool gop_pos){
if (len + 3 <= getMaxSize()) {
//STAP-A模式打包小于MTU
packRtpStapA(ptr, len, pts, is_mark, gop_pos);
} else {
//STAP-A模式打包会大于MTU,所以采用FU-A模式
packRtpFu(ptr, len, pts, is_mark, gop_pos);
}
}
void H264RtpEncoder::packRtpFu(const char *ptr, size_t len, uint32_t pts, bool is_mark, bool gop_pos){
auto packet_size = getMaxSize() - 2; auto packet_size = getMaxSize() - 2;
if (len <= packet_size + 1) {
//小于FU-A打包最小字节长度要求,采用STAP-A模式
packRtpStapA(ptr, len, pts, is_mark, gop_pos);
return;
}
//末尾5bit为nalu type,固定为28(FU-A) //末尾5bit为nalu type,固定为28(FU-A)
auto fu_char_0 = (ptr[0] & (~0x1F)) | 28; auto fu_char_0 = (ptr[0] & (~0x1F)) | 28;
auto fu_char_1 = nal_type; auto fu_char_1 = H264_TYPE(ptr[0]);
FuFlags *fu_flags = (FuFlags *) (&fu_char_1); FuFlags *fu_flags = (FuFlags *) (&fu_char_1);
fu_flags->start_bit = 1; fu_flags->start_bit = 1;
//超过MTU则按照FU-A模式打包
if (len > packet_size + 1) {
size_t offset = 1; size_t offset = 1;
while (!fu_flags->end_bit) { while (!fu_flags->end_bit) {
if (!fu_flags->start_bit && len <= offset + packet_size) { if (!fu_flags->start_bit && len <= offset + packet_size) {
...@@ -208,7 +226,7 @@ void H264RtpEncoder::inputFrame(const Frame::Ptr &frame) { ...@@ -208,7 +226,7 @@ void H264RtpEncoder::inputFrame(const Frame::Ptr &frame) {
} }
//传入nullptr先不做payload的内存拷贝 //传入nullptr先不做payload的内存拷贝
auto rtp = makeRtp(getTrackType(), nullptr, packet_size + 2, fu_flags->end_bit, pts); auto rtp = makeRtp(getTrackType(), nullptr, packet_size + 2, fu_flags->end_bit && is_mark, pts);
//rtp payload 负载部分 //rtp payload 负载部分
uint8_t *payload = rtp->getPayload(); uint8_t *payload = rtp->getPayload();
//FU-A 第1个字节 //FU-A 第1个字节
...@@ -218,19 +236,57 @@ void H264RtpEncoder::inputFrame(const Frame::Ptr &frame) { ...@@ -218,19 +236,57 @@ void H264RtpEncoder::inputFrame(const Frame::Ptr &frame) {
//H264 数据 //H264 数据
memcpy(payload + 2, (uint8_t *) ptr + offset, packet_size); memcpy(payload + 2, (uint8_t *) ptr + offset, packet_size);
//输入到rtp环形缓存 //输入到rtp环形缓存
RtpCodec::inputRtp(rtp, fu_flags->start_bit && nal_type == H264Frame::NAL_IDR); RtpCodec::inputRtp(rtp, gop_pos);
offset += packet_size; offset += packet_size;
fu_flags->start_bit = 0; fu_flags->start_bit = 0;
} }
} else { }
//如果帧长度不超过mtu, 则按照Single NAL unit packet per H.264 方式打包
makeH264Rtp(ptr, len, false, false, pts); void H264RtpEncoder::packRtpStapA(const char *ptr, size_t len, uint32_t pts, bool is_mark, bool gop_pos){
//如果帧长度不超过mtu,为了兼容性 webrtc,采用STAP-A模式打包
auto rtp = makeRtp(getTrackType(), nullptr, len + 3, is_mark, pts);
uint8_t *payload = rtp->getPayload();
//STAP-A
payload[0] = (ptr[0] & (~0x1F)) | 24;
payload[1] = (len >> 8) & 0xFF;
payload[2] = len & 0xff;
memcpy(payload + 3, (uint8_t *) ptr, len);
RtpCodec::inputRtp(rtp, gop_pos);
}
void H264RtpEncoder::inputFrame(const Frame::Ptr &frame) {
auto ptr = frame->data() + frame->prefixSize();
switch (H264_TYPE(ptr[0])) {
case H264Frame::NAL_AUD:
case H264Frame::NAL_SEI : {
return;
} }
case H264Frame::NAL_SPS: {
_sps = Frame::getCacheAbleFrame(frame);
return;
}
case H264Frame::NAL_PPS: {
_pps = Frame::getCacheAbleFrame(frame);
return;
}
default: break;
}
if (_last_frame) {
//如果时间戳发生了变化,那么markbit才置true
inputFrame_l(_last_frame, _last_frame->pts() != frame->pts());
}
_last_frame = Frame::getCacheAbleFrame(frame);
} }
void H264RtpEncoder::makeH264Rtp(const void* data, size_t len, bool mark, bool gop_pos, uint32_t uiStamp) { void H264RtpEncoder::inputFrame_l(const Frame::Ptr &frame, bool is_mark){
RtpCodec::inputRtp(makeRtp(getTrackType(), data, len, mark, uiStamp), gop_pos); if (frame->keyFrame()) {
//保证每一个关键帧前都有SPS与PPS
insertConfigFrame(frame->pts());
}
packRtp(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize(), frame->pts(), is_mark, false);
} }
}//namespace mediakit }//namespace mediakit
...@@ -82,7 +82,16 @@ public: ...@@ -82,7 +82,16 @@ public:
void inputFrame(const Frame::Ptr &frame) override; void inputFrame(const Frame::Ptr &frame) override;
private: private:
void makeH264Rtp(const void *pData, size_t uiLen, bool bMark, bool gop_pos, uint32_t uiStamp); void insertConfigFrame(uint32_t pts);
void inputFrame_l(const Frame::Ptr &frame, bool is_mark);
void packRtp(const char *data, size_t len, uint32_t pts, bool is_mark, bool gop_pos);
void packRtpFu(const char *data, size_t len, uint32_t pts, bool is_mark, bool gop_pos);
void packRtpStapA(const char *data, size_t len, uint32_t pts, bool is_mark, bool gop_pos);
private:
Frame::Ptr _sps;
Frame::Ptr _pps;
Frame::Ptr _last_frame;
}; };
}//namespace mediakit{ }//namespace mediakit{
......
...@@ -64,10 +64,11 @@ bool H265Frame::configFrame() const { ...@@ -64,10 +64,11 @@ bool H265Frame::configFrame() const {
} }
bool H265Frame::isKeyFrame(int type, const char *ptr) { bool H265Frame::isKeyFrame(int type, const char *ptr) {
if (!ptr || type != NAL_IDR_W_RADL) { if(ptr){
return type >= NAL_BLA_W_LP && type <= NAL_RSV_IRAP_VCL23; return (((*((uint8_t *) ptr + 2)) >> 7) & 0x01) == 1 && (type == NAL_IDR_N_LP || type == NAL_IDR_W_RADL);
} }
return (((*((uint8_t *) ptr + 2)) >> 7) & 0x01) == 1; return false;
} }
H265Frame::H265Frame(){ H265Frame::H265Frame(){
...@@ -254,7 +255,7 @@ public: ...@@ -254,7 +255,7 @@ public:
if (bitrate) { if (bitrate) {
_printer << "b=AS:" << bitrate << "\r\n"; _printer << "b=AS:" << bitrate << "\r\n";
} }
_printer << "a=rtpmap:" << payload_type << " H265/" << 90000 << "\r\n"; _printer << "a=rtpmap:" << payload_type << " " << getCodecName() << "/" << 90000 << "\r\n";
_printer << "a=fmtp:" << payload_type << " "; _printer << "a=fmtp:" << payload_type << " ";
_printer << "sprop-vps="; _printer << "sprop-vps=";
_printer << encodeBase64(strVPS) << "; "; _printer << encodeBase64(strVPS) << "; ";
......
...@@ -165,11 +165,11 @@ void H265RtmpEncoder::inputFrame(const Frame::Ptr &frame) { ...@@ -165,11 +165,11 @@ void H265RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
} }
} }
if(type == H265Frame::NAL_SEI_PREFIX || type == H265Frame::NAL_SEI_SUFFIX){ if(type == H265Frame::NAL_SEI_PREFIX || type == H265Frame::NAL_SEI_SUFFIX || type == H265Frame::NAL_AUD){
return; return;// 防止sei aud 作为一帧
} }
if (_lastPacket && (_lastPacket->time_stamp != frame->dts() || type == H265Frame::NAL_TRAIL_R)) { if (_lastPacket && (_lastPacket->time_stamp != frame->dts() || (type >=H264Frame::NAL_B_P && type<=H264Frame::NAL_IDR && (pcData[2]>>7 &0x01) !=0))) {
RtmpCodec::inputRtmp(_lastPacket); RtmpCodec::inputRtmp(_lastPacket);
_lastPacket = nullptr; _lastPacket = nullptr;
} }
......
...@@ -33,7 +33,7 @@ public: ...@@ -33,7 +33,7 @@ public:
if (bitrate) { if (bitrate) {
_printer << "b=AS:" << bitrate << "\r\n"; _printer << "b=AS:" << bitrate << "\r\n";
} }
_printer << "a=rtpmap:" << payload_type << " L16/" << sample_rate << "/" << channels << "\r\n"; _printer << "a=rtpmap:" << payload_type << " " << getCodecName() << "/" << sample_rate << "/" << channels << "\r\n";
_printer << "a=control:trackID=" << (int)TrackAudio << "\r\n"; _printer << "a=control:trackID=" << (int)TrackAudio << "\r\n";
} }
......
...@@ -31,7 +31,7 @@ public: ...@@ -31,7 +31,7 @@ public:
if (bitrate) { if (bitrate) {
_printer << "b=AS:" << bitrate << "\r\n"; _printer << "b=AS:" << bitrate << "\r\n";
} }
_printer << "a=rtpmap:" << payload_type << " opus/" << sample_rate << "/" << channels << "\r\n"; _printer << "a=rtpmap:" << payload_type << " " << getCodecName() << "/" << sample_rate << "/" << channels << "\r\n";
_printer << "a=control:trackID=" << (int)TrackAudio << "\r\n"; _printer << "a=control:trackID=" << (int)TrackAudio << "\r\n";
} }
......
...@@ -9,8 +9,10 @@ ...@@ -9,8 +9,10 @@
*/ */
#include <stddef.h> #include <stddef.h>
#include <assert.h>
#include "Rtcp.h" #include "Rtcp.h"
#include "Util/logger.h" #include "Util/logger.h"
#include "RtcpFCI.h"
namespace mediakit { namespace mediakit {
...@@ -32,8 +34,26 @@ const char *sdesTypeToStr(SdesType type){ ...@@ -32,8 +34,26 @@ const char *sdesTypeToStr(SdesType type){
} }
} }
const char *psfbTypeToStr(PSFBType type) {
switch (type){
#define SWITCH_CASE(key, value) case PSFBType::key : return #value "(" #key ")";
PSFB_TYPE_MAP(SWITCH_CASE)
#undef SWITCH_CASE
default: return "unknown payload-specific fb message fmt type";
}
}
const char *rtpfbTypeToStr(RTPFBType type) {
switch (type){
#define SWITCH_CASE(key, value) case RTPFBType::key : return #value "(" #key ")";
RTPFB_TYPE_MAP(SWITCH_CASE)
#undef SWITCH_CASE
default: return "unknown transport layer feedback messages fmt type";
}
}
static size_t alignSize(size_t bytes) { static size_t alignSize(size_t bytes) {
return (size_t)((bytes + 3) / 4) << 2; return (size_t)((bytes + 3) >> 2 ) << 2;
} }
static void setupHeader(RtcpHeader *rtcp, RtcpType type, size_t report_count, size_t total_bytes) { static void setupHeader(RtcpHeader *rtcp, RtcpType type, size_t report_count, size_t total_bytes) {
...@@ -45,23 +65,46 @@ static void setupHeader(RtcpHeader *rtcp, RtcpType type, size_t report_count, si ...@@ -45,23 +65,46 @@ static void setupHeader(RtcpHeader *rtcp, RtcpType type, size_t report_count, si
//items总个数 //items总个数
rtcp->report_count = report_count; rtcp->report_count = report_count;
rtcp->pt = (uint8_t) type; rtcp->pt = (uint8_t) type;
//不包含rtcp头的长度 rtcp->setSize(total_bytes);
rtcp->length = htons((uint16_t)((total_bytes / 4) - 1));
} }
///////////////////////////////////////////////////////////////////////////// static void setupPadding(RtcpHeader *rtcp, size_t padding_size) {
if (padding_size) {
void RtcpHeader::net2Host() { rtcp->padding = 1;
length = ntohs(length); ((uint8_t *) rtcp)[rtcp->getSize() - 1] = padding_size & 0xFF;
} else {
rtcp->padding = 0;
}
} }
/////////////////////////////////////////////////////////////////////////////
string RtcpHeader::dumpHeader() const{ string RtcpHeader::dumpHeader() const{
_StrPrinter printer; _StrPrinter printer;
printer << "version:" << version << "\r\n"; printer << "version:" << version << "\r\n";
if (padding) {
printer << "padding:" << padding << " " << getPaddingSize() << "\r\n";
} else {
printer << "padding:" << padding << "\r\n"; printer << "padding:" << padding << "\r\n";
}
switch ((RtcpType)pt) {
case RtcpType::RTCP_RTPFB : {
printer << "report_count:" << rtpfbTypeToStr((RTPFBType) report_count) << "\r\n";
break;
}
case RtcpType::RTCP_PSFB : {
printer << "report_count:" << psfbTypeToStr((PSFBType) report_count) << "\r\n";
break;
}
default : {
printer << "report_count:" << report_count << "\r\n"; printer << "report_count:" << report_count << "\r\n";
break;
}
}
printer << "pt:" << rtcpTypeToStr((RtcpType)pt) << "\r\n"; printer << "pt:" << rtcpTypeToStr((RtcpType)pt) << "\r\n";
printer << "length:" << length << "\r\n"; printer << "size:" << getSize() << "\r\n";
printer << "--------\r\n"; printer << "--------\r\n";
return std::move(printer); return std::move(printer);
} }
...@@ -82,10 +125,39 @@ string RtcpHeader::dumpString() const { ...@@ -82,10 +125,39 @@ string RtcpHeader::dumpString() const {
RtcpSdes *rtcp = (RtcpSdes *)this; RtcpSdes *rtcp = (RtcpSdes *)this;
return rtcp->dumpString(); return rtcp->dumpString();
} }
default: return StrPrinter << dumpHeader() << hexdump((char *)this + sizeof(*this), length << 2);
case RtcpType::RTCP_RTPFB:
case RtcpType::RTCP_PSFB: {
RtcpFB *rtcp = (RtcpFB *)this;
return rtcp->dumpString();
}
case RtcpType::RTCP_BYE: {
RtcpBye *rtcp = (RtcpBye *)this;
return rtcp->dumpString();
}
default: return StrPrinter << dumpHeader() << hexdump((char *)this + sizeof(*this), getSize() - sizeof(*this));
} }
} }
size_t RtcpHeader::getSize() const {
//加上rtcp头长度
return (1 + ntohs(length)) << 2;
}
size_t RtcpHeader::getPaddingSize() const{
if (!padding) {
return 0;
}
return ((uint8_t *) this)[getSize() - 1];
}
void RtcpHeader::setSize(size_t size) {
//不包含rtcp头的长度
length = htons((uint16_t)((size >> 2) - 1));
}
void RtcpHeader::net2Host(size_t len){ void RtcpHeader::net2Host(size_t len){
switch ((RtcpType)pt) { switch ((RtcpType)pt) {
case RtcpType::RTCP_SR: { case RtcpType::RTCP_SR: {
...@@ -105,6 +177,20 @@ void RtcpHeader::net2Host(size_t len){ ...@@ -105,6 +177,20 @@ void RtcpHeader::net2Host(size_t len){
sdes->net2Host(len); sdes->net2Host(len);
break; break;
} }
case RtcpType::RTCP_RTPFB:
case RtcpType::RTCP_PSFB: {
RtcpFB *fb = (RtcpFB *)this;
fb->net2Host(len);
break;
}
case RtcpType::RTCP_BYE: {
RtcpBye *bye = (RtcpBye *)this;
bye->net2Host(len);
break;
}
default: throw std::runtime_error(StrPrinter << "未处理的rtcp包:" << rtcpTypeToStr((RtcpType) this->pt)); default: throw std::runtime_error(StrPrinter << "未处理的rtcp包:" << rtcpTypeToStr((RtcpType) this->pt));
} }
} }
...@@ -115,13 +201,17 @@ vector<RtcpHeader *> RtcpHeader::loadFromBytes(char *data, size_t len){ ...@@ -115,13 +201,17 @@ vector<RtcpHeader *> RtcpHeader::loadFromBytes(char *data, size_t len){
char *ptr = data; char *ptr = data;
while (remain > (ssize_t) sizeof(RtcpHeader)) { while (remain > (ssize_t) sizeof(RtcpHeader)) {
RtcpHeader *rtcp = (RtcpHeader *) ptr; RtcpHeader *rtcp = (RtcpHeader *) ptr;
auto rtcp_len = (1 + ntohs(rtcp->length)) << 2; auto rtcp_len = rtcp->getSize();
if (remain < (ssize_t)rtcp_len) {
WarnL << "非法的rtcp包,声明的长度超过实际数据长度";
break;
}
try { try {
rtcp->net2Host(rtcp_len); rtcp->net2Host(rtcp_len);
ret.emplace_back(rtcp); ret.emplace_back(rtcp);
} catch (std::exception &ex) { } catch (std::exception &ex) {
//不能处理的rtcp包,或者无法解析的rtcp包,忽略掉 //不能处理的rtcp包,或者无法解析的rtcp包,忽略掉
WarnL << ex.what(); WarnL << ex.what() << ",长度为:" << rtcp_len;
} }
ptr += rtcp_len; ptr += rtcp_len;
remain -= rtcp_len; remain -= rtcp_len;
...@@ -133,7 +223,6 @@ class BufferRtcp : public Buffer { ...@@ -133,7 +223,6 @@ class BufferRtcp : public Buffer {
public: public:
BufferRtcp(std::shared_ptr<RtcpHeader> rtcp) { BufferRtcp(std::shared_ptr<RtcpHeader> rtcp) {
_rtcp = std::move(rtcp); _rtcp = std::move(rtcp);
_size = (htons(_rtcp->length) + 1) << 2;
} }
~BufferRtcp() override {} ~BufferRtcp() override {}
...@@ -143,11 +232,10 @@ public: ...@@ -143,11 +232,10 @@ public:
} }
size_t size() const override { size_t size() const override {
return _size; return _rtcp->getSize();
} }
private: private:
std::size_t _size;
std::shared_ptr<RtcpHeader> _rtcp; std::shared_ptr<RtcpHeader> _rtcp;
}; };
...@@ -158,9 +246,11 @@ Buffer::Ptr RtcpHeader::toBuffer(std::shared_ptr<RtcpHeader> rtcp) { ...@@ -158,9 +246,11 @@ Buffer::Ptr RtcpHeader::toBuffer(std::shared_ptr<RtcpHeader> rtcp) {
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
std::shared_ptr<RtcpSR> RtcpSR::create(size_t item_count) { std::shared_ptr<RtcpSR> RtcpSR::create(size_t item_count) {
auto bytes = alignSize(sizeof(RtcpSR) - sizeof(ReportItem) + item_count * sizeof(ReportItem)); auto real_size = sizeof(RtcpSR) - sizeof(ReportItem) + item_count * sizeof(ReportItem);
auto bytes = alignSize(real_size);
auto ptr = (RtcpSR *) new char[bytes]; auto ptr = (RtcpSR *) new char[bytes];
setupHeader(ptr, RtcpType::RTCP_SR, item_count, bytes); setupHeader(ptr, RtcpType::RTCP_SR, item_count, bytes);
setupPadding(ptr, bytes - real_size);
return std::shared_ptr<RtcpSR>(ptr, [](RtcpSR *ptr) { return std::shared_ptr<RtcpSR>(ptr, [](RtcpSR *ptr) {
delete[] (char *) ptr; delete[] (char *) ptr;
}); });
...@@ -202,21 +292,17 @@ if (size < kMinSize) { \ ...@@ -202,21 +292,17 @@ if (size < kMinSize) { \
throw std::out_of_range(StrPrinter << rtcpTypeToStr((RtcpType)pt) << " 长度不足:" << size << " < " << kMinSize); \ throw std::out_of_range(StrPrinter << rtcpTypeToStr((RtcpType)pt) << " 长度不足:" << size << " < " << kMinSize); \
} }
#define CHECK_LENGTH(size, item_count) \ #define CHECK_REPORT_COUNT(item_count) \
/*修正个数,防止getItemList时内存越界*/ \ /*修正个数,防止getItemList时内存越界*/ \
if (report_count != item_count) { \ if (report_count != item_count) { \
WarnL << rtcpTypeToStr((RtcpType)pt) << " report_count 字段不正确,已修正为:" << (int)report_count << " -> " << item_count; \ WarnL << rtcpTypeToStr((RtcpType)pt) << " report_count 字段不正确,已修正为:" << (int)report_count << " -> " << item_count; \
report_count = item_count; \ report_count = item_count; \
} \
if ((size_t) (length + 1) << 2 != size) { \
WarnL << rtcpTypeToStr((RtcpType)pt) << " length字段不正确:" << (size_t) (length + 1) << 2 << " != " << size; \
} }
void RtcpSR::net2Host(size_t size) { void RtcpSR::net2Host(size_t size) {
static const size_t kMinSize = sizeof(RtcpSR) - sizeof(items); static const size_t kMinSize = sizeof(RtcpSR) - sizeof(items);
CHECK_MIN_SIZE(size, kMinSize); CHECK_MIN_SIZE(size, kMinSize);
RtcpHeader::net2Host();
ssrc = ntohl(ssrc); ssrc = ntohl(ssrc);
ntpmsw = ntohl(ntpmsw); ntpmsw = ntohl(ntpmsw);
ntplsw = ntohl(ntplsw); ntplsw = ntohl(ntplsw);
...@@ -231,7 +317,7 @@ void RtcpSR::net2Host(size_t size) { ...@@ -231,7 +317,7 @@ void RtcpSR::net2Host(size_t size) {
++ptr; ++ptr;
++item_count; ++item_count;
} }
CHECK_LENGTH(size, item_count); CHECK_REPORT_COUNT(item_count);
} }
vector<ReportItem*> RtcpSR::getItemList(){ vector<ReportItem*> RtcpSR::getItemList(){
...@@ -272,9 +358,11 @@ void ReportItem::net2Host() { ...@@ -272,9 +358,11 @@ void ReportItem::net2Host() {
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
std::shared_ptr<RtcpRR> RtcpRR::create(size_t item_count) { std::shared_ptr<RtcpRR> RtcpRR::create(size_t item_count) {
auto bytes = alignSize(sizeof(RtcpRR) - sizeof(ReportItem) + item_count * sizeof(ReportItem)); auto real_size = sizeof(RtcpRR) - sizeof(ReportItem) + item_count * sizeof(ReportItem);
auto bytes = alignSize(real_size);
auto ptr = (RtcpRR *) new char[bytes]; auto ptr = (RtcpRR *) new char[bytes];
setupHeader(ptr, RtcpType::RTCP_RR, item_count, bytes); setupHeader(ptr, RtcpType::RTCP_RR, item_count, bytes);
setupPadding(ptr, bytes - real_size);
return std::shared_ptr<RtcpRR>(ptr, [](RtcpRR *ptr) { return std::shared_ptr<RtcpRR>(ptr, [](RtcpRR *ptr) {
delete[] (char *) ptr; delete[] (char *) ptr;
}); });
...@@ -296,7 +384,6 @@ string RtcpRR::dumpString() const{ ...@@ -296,7 +384,6 @@ string RtcpRR::dumpString() const{
void RtcpRR::net2Host(size_t size) { void RtcpRR::net2Host(size_t size) {
static const size_t kMinSize = sizeof(RtcpRR) - sizeof(items); static const size_t kMinSize = sizeof(RtcpRR) - sizeof(items);
CHECK_MIN_SIZE(size, kMinSize); CHECK_MIN_SIZE(size, kMinSize);
RtcpHeader::net2Host();
ssrc = ntohl(ssrc); ssrc = ntohl(ssrc);
ReportItem *ptr = &items; ReportItem *ptr = &items;
...@@ -306,7 +393,7 @@ void RtcpRR::net2Host(size_t size) { ...@@ -306,7 +393,7 @@ void RtcpRR::net2Host(size_t size) {
++ptr; ++ptr;
++item_count; ++item_count;
} }
CHECK_LENGTH(size, item_count); CHECK_REPORT_COUNT(item_count);
} }
vector<ReportItem*> RtcpRR::getItemList() { vector<ReportItem*> RtcpRR::getItemList() {
...@@ -326,7 +413,7 @@ void SdesItem::net2Host() { ...@@ -326,7 +413,7 @@ void SdesItem::net2Host() {
} }
size_t SdesItem::totalBytes() const{ size_t SdesItem::totalBytes() const{
return alignSize(minSize() + length); return alignSize(minSize() + txt_len);
} }
size_t SdesItem::minSize() { size_t SdesItem::minSize() {
...@@ -337,30 +424,32 @@ string SdesItem::dumpString() const{ ...@@ -337,30 +424,32 @@ string SdesItem::dumpString() const{
_StrPrinter printer; _StrPrinter printer;
printer << "ssrc:" << ssrc << "\r\n"; printer << "ssrc:" << ssrc << "\r\n";
printer << "type:" << sdesTypeToStr((SdesType) type) << "\r\n"; printer << "type:" << sdesTypeToStr((SdesType) type) << "\r\n";
printer << "length:" << (int) length << "\r\n"; printer << "txt_len:" << (int) txt_len << "\r\n";
printer << "text:" << (length ? string(&text, length) : "") << "\r\n"; printer << "text:" << (txt_len ? string(text, txt_len) : "") << "\r\n";
return std::move(printer); return std::move(printer);
} }
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
std::shared_ptr<RtcpSdes> RtcpSdes::create(const std::initializer_list<string> &item_text) { std::shared_ptr<RtcpSdes> RtcpSdes::create(const std::vector<string> &item_text) {
size_t item_total_size = 0; size_t item_total_size = 0;
for (auto &text : item_text) { for (auto &text : item_text) {
//统计所有SdesItem对象占用的空间 //统计所有SdesItem对象占用的空间
item_total_size += alignSize(SdesItem::minSize() + (0xFF & text.size())); item_total_size += alignSize(SdesItem::minSize() + (0xFF & text.size()));
} }
auto bytes = alignSize(sizeof(RtcpSdes) - sizeof(SdesItem) + item_total_size); auto real_size = sizeof(RtcpSdes) - sizeof(SdesItem) + item_total_size;
auto bytes = alignSize(real_size);
auto ptr = (RtcpSdes *) new char[bytes]; auto ptr = (RtcpSdes *) new char[bytes];
auto item_ptr = &ptr->items; auto item_ptr = &ptr->items;
for (auto &text : item_text) { for (auto &text : item_text) {
item_ptr->length = (0xFF & text.size()); item_ptr->txt_len = (0xFF & text.size());
//确保赋值\0为RTCP_SDES_END //确保赋值\0为RTCP_SDES_END
memcpy(&(item_ptr->text), text.data(), item_ptr->length + 1); memcpy(item_ptr->text, text.data(), item_ptr->txt_len + 1);
item_ptr = (SdesItem *) ((char *) item_ptr + item_ptr->totalBytes()); item_ptr = (SdesItem *) ((char *) item_ptr + item_ptr->totalBytes());
} }
setupHeader(ptr, RtcpType::RTCP_SDES, item_text.size(), bytes); setupHeader(ptr, RtcpType::RTCP_SDES, item_text.size(), bytes);
setupPadding(ptr, bytes - real_size);
return std::shared_ptr<RtcpSdes>(ptr, [](RtcpSdes *ptr) { return std::shared_ptr<RtcpSdes>(ptr, [](RtcpSdes *ptr) {
delete [] (char *) ptr; delete [] (char *) ptr;
}); });
...@@ -381,7 +470,6 @@ string RtcpSdes::dumpString() const { ...@@ -381,7 +470,6 @@ string RtcpSdes::dumpString() const {
void RtcpSdes::net2Host(size_t size) { void RtcpSdes::net2Host(size_t size) {
static const size_t kMinSize = sizeof(RtcpSdes) - sizeof(items); static const size_t kMinSize = sizeof(RtcpSdes) - sizeof(items);
CHECK_MIN_SIZE(size, kMinSize); CHECK_MIN_SIZE(size, kMinSize);
RtcpHeader::net2Host();
SdesItem *ptr = &items; SdesItem *ptr = &items;
int item_count = 0; int item_count = 0;
for(int i = 0; i < (int)report_count && (char *)(ptr) + SdesItem::minSize() <= (char *)(this) + size; ++i){ for(int i = 0; i < (int)report_count && (char *)(ptr) + SdesItem::minSize() <= (char *)(this) + size; ++i){
...@@ -389,7 +477,7 @@ void RtcpSdes::net2Host(size_t size) { ...@@ -389,7 +477,7 @@ void RtcpSdes::net2Host(size_t size) {
ptr = (SdesItem *) ((char *) ptr + ptr->totalBytes()); ptr = (SdesItem *) ((char *) ptr + ptr->totalBytes());
++item_count; ++item_count;
} }
CHECK_LENGTH(size, item_count); CHECK_REPORT_COUNT(item_count);
} }
vector<SdesItem *> RtcpSdes::getItemList() { vector<SdesItem *> RtcpSdes::getItemList() {
...@@ -402,4 +490,202 @@ vector<SdesItem *> RtcpSdes::getItemList() { ...@@ -402,4 +490,202 @@ vector<SdesItem *> RtcpSdes::getItemList() {
return ret; return ret;
} }
////////////////////////////////////////////////////////////////////
std::shared_ptr<RtcpFB> RtcpFB::create_l(RtcpType type, int fmt, const void *fci, size_t fci_len) {
if (!fci) {
fci_len = 0;
}
auto real_size = sizeof(RtcpFB) + fci_len;
auto bytes = alignSize(real_size);
auto ptr = (RtcpFB *) new char[bytes];
if (fci && fci_len) {
memcpy((char *)ptr + sizeof(RtcpFB), fci, fci_len);
}
setupHeader(ptr, type, fmt, bytes);
setupPadding(ptr, bytes - real_size);
return std::shared_ptr<RtcpFB>((RtcpFB *) ptr, [](RtcpFB *ptr) {
delete[] (char *) ptr;
});
}
std::shared_ptr<RtcpFB> RtcpFB::create(PSFBType fmt, const void *fci, size_t fci_len) {
return RtcpFB::create_l(RtcpType::RTCP_PSFB, (int)fmt, fci, fci_len);
}
std::shared_ptr<RtcpFB> RtcpFB::create(RTPFBType fmt, const void *fci, size_t fci_len) {
return RtcpFB::create_l(RtcpType::RTCP_RTPFB, (int)fmt, fci, fci_len);
}
const void *RtcpFB::getFciPtr() const {
return (uint8_t *) &ssrc_media + sizeof(ssrc_media);
}
size_t RtcpFB::getFciSize() const {
auto fci_len = (ssize_t) getSize() - getPaddingSize() - sizeof(RtcpFB);
CHECK(fci_len >= 0);
return fci_len;
}
string RtcpFB::dumpString() const {
_StrPrinter printer;
printer << RtcpHeader::dumpHeader();
printer << "ssrc:" << ssrc << "\r\n";
printer << "ssrc_media:" << ssrc_media << "\r\n";
switch ((RtcpType) pt) {
case RtcpType::RTCP_PSFB : {
switch ((PSFBType) report_count) {
case PSFBType::RTCP_PSFB_SLI : {
auto &fci = getFci<FCI_SLI>();
printer << "fci:" << psfbTypeToStr((PSFBType) report_count) << " " << fci.dumpString();
break;
}
case PSFBType::RTCP_PSFB_PLI : {
getFciSize();
printer << "fci:" << psfbTypeToStr((PSFBType) report_count);
break;
}
case PSFBType::RTCP_PSFB_FIR : {
auto &fci = getFci<FCI_FIR>();
printer << "fci:" << psfbTypeToStr((PSFBType) report_count) << " " << fci.dumpString();
break;
}
case PSFBType::RTCP_PSFB_REMB : {
auto &fci = getFci<FCI_REMB>();
printer << "fci:" << psfbTypeToStr((PSFBType) report_count) << " " << fci.dumpString();
break;
}
default:{
printer << "fci:" << psfbTypeToStr((PSFBType) report_count) << " " << hexdump(getFciPtr(), getFciSize());
break;
}
}
break;
}
case RtcpType::RTCP_RTPFB : {
switch ((RTPFBType) report_count) {
case RTPFBType::RTCP_RTPFB_NACK : {
auto &fci = getFci<FCI_NACK>();
printer << "fci:" << rtpfbTypeToStr((RTPFBType) report_count) << " " << fci.dumpString();
break;
}
case RTPFBType::RTCP_RTPFB_TWCC : {
auto &fci = getFci<FCI_TWCC>();
printer << "fci:" << rtpfbTypeToStr((RTPFBType) report_count) << " " << fci.dumpString(getFciSize());
break;
}
default: {
printer << "fci:" << rtpfbTypeToStr((RTPFBType) report_count) << " " << hexdump(getFciPtr(), getFciSize());
break;
}
}
break;
}
default: /*不可达*/ assert(0); break;
}
return std::move(printer);
}
void RtcpFB::net2Host(size_t size) {
static const size_t kMinSize = sizeof(RtcpFB);
CHECK_MIN_SIZE(size, kMinSize);
ssrc = ntohl(ssrc);
ssrc_media = ntohl(ssrc_media);
}
////////////////////////////////////////////////////////////////////
std::shared_ptr<RtcpBye> RtcpBye::create(const std::vector<uint32_t> &ssrcs, const string &reason) {
assert(reason.size() <= 0xFF);
auto real_size = sizeof(RtcpHeader) + sizeof(uint32_t) * ssrcs.size() + 1 + reason.size();
auto bytes = alignSize(real_size);
auto ptr = (RtcpBye *) new char[bytes];
setupHeader(ptr, RtcpType::RTCP_BYE, ssrcs.size(), bytes);
setupPadding(ptr, bytes - real_size);
auto ssrc_ptr = ((RtcpBye *) ptr)->ssrc;
for (auto ssrc : ssrcs) {
*ssrc_ptr = htonl(ssrc);
++ssrc_ptr;
}
if (!reason.empty()) {
uint8_t *reason_len_ptr = (uint8_t *) ptr + sizeof(RtcpHeader) + sizeof(uint32_t) * ssrcs.size();
*reason_len_ptr = reason.size() & 0xFF;
memcpy(reason_len_ptr + 1, reason.data(), *reason_len_ptr);
}
return std::shared_ptr<RtcpBye>(ptr, [](RtcpBye *ptr) {
delete[] (char *) ptr;
});
}
vector<uint32_t *> RtcpBye::getSSRC() {
vector<uint32_t *> ret;
auto ssrc_ptr = ssrc;
for (size_t i = 0; i < report_count; ++i) {
ret.emplace_back(ssrc_ptr);
ssrc_ptr += 1;
}
return ret;
}
string RtcpBye::getReason() const {
auto *reason_len_ptr = &reason_len + sizeof(ssrc) * (report_count - 1);
if (reason_len_ptr + 1 >= (uint8_t *) this + getSize()) {
return "";
}
return string((char *) reason_len_ptr + 1, *reason_len_ptr);
}
string RtcpBye::dumpString() const {
_StrPrinter printer;
printer << RtcpHeader::dumpHeader();
for(auto ssrc : ((RtcpBye *)this)->getSSRC()) {
printer << "ssrc:" << *ssrc << "\r\n";
}
printer << "reason:" << getReason();
return std::move(printer);
}
void RtcpBye::net2Host(size_t size) {
static const size_t kMinSize = sizeof(RtcpHeader);
CHECK_MIN_SIZE(size, kMinSize);
auto ssrc_ptr = ssrc;
size_t offset = kMinSize;
size_t i = 0;
for (; i < report_count && offset + sizeof(ssrc) <= size; ++i) {
*ssrc_ptr = ntohl(*ssrc_ptr);
ssrc_ptr += 1;
offset += sizeof(ssrc);
}
//修正ssrc个数
CHECK_REPORT_COUNT(i);
if (offset < size) {
uint8_t *reason_len_ptr = &reason_len + sizeof(ssrc) * (report_count - 1);
if (reason_len_ptr + 1 + *reason_len_ptr > (uint8_t *) this + size) {
WarnL << "invalid rtcp bye reason length";
//修正reason_len长度
*reason_len_ptr = ((uint8_t *) this + size - reason_len_ptr - 1) & 0xFF;
}
}
}
#if 0
#include "Util/onceToken.h"
static toolkit::onceToken token([](){
auto bye = RtcpBye::create({1,2,3,4,5,6}, "this is a bye reason");
auto buffer = RtcpHeader::toBuffer(bye);
auto rtcps = RtcpHeader::loadFromBytes(buffer->data(), buffer->size());
for(auto rtcp : rtcps){
std::cout << rtcp->dumpString() << std::endl;
}
});
#endif
}//namespace mediakit }//namespace mediakit
\ No newline at end of file
...@@ -25,8 +25,7 @@ namespace mediakit { ...@@ -25,8 +25,7 @@ namespace mediakit {
#pragma pack(push, 1) #pragma pack(push, 1)
#endif // defined(_WIN32) #endif // defined(_WIN32)
//https://datatracker.ietf.org/doc/rfc3550 //http://www.networksorcery.com/enp/protocol/rtcp.htm
#define RTCP_PT_MAP(XX) \ #define RTCP_PT_MAP(XX) \
XX(RTCP_FIR, 192) \ XX(RTCP_FIR, 192) \
XX(RTCP_NACK, 193) \ XX(RTCP_NACK, 193) \
...@@ -44,6 +43,7 @@ namespace mediakit { ...@@ -44,6 +43,7 @@ namespace mediakit {
XX(RTCP_RSI, 209) \ XX(RTCP_RSI, 209) \
XX(RTCP_TOKEN, 210) XX(RTCP_TOKEN, 210)
//https://tools.ietf.org/html/rfc3550#section-6.5
#define SDES_TYPE_MAP(XX) \ #define SDES_TYPE_MAP(XX) \
XX(RTCP_SDES_END, 0) \ XX(RTCP_SDES_END, 0) \
XX(RTCP_SDES_CNAME, 1) \ XX(RTCP_SDES_CNAME, 1) \
...@@ -55,6 +55,63 @@ namespace mediakit { ...@@ -55,6 +55,63 @@ namespace mediakit {
XX(RTCP_SDES_NOTE, 7) \ XX(RTCP_SDES_NOTE, 7) \
XX(RTCP_SDES_PRIVATE, 8) XX(RTCP_SDES_PRIVATE, 8)
//https://datatracker.ietf.org/doc/rfc4585/?include_text=1
//6.3. Payload-Specific Feedback Messages
//
// Payload-Specific FB messages are identified by the value PT=PSFB as
// RTCP message type.
//
// Three payload-specific FB messages are defined so far plus an
// application layer FB message. They are identified by means of the
// FMT parameter as follows:
//
// 0: unassigned
// 1: Picture Loss Indication (PLI)
// 2: Slice Loss Indication (SLI)
// 3: Reference Picture Selection Indication (RPSI)
// 4: FIR https://tools.ietf.org/html/rfc5104#section-4.3.1.1
// 5: TSTR https://tools.ietf.org/html/rfc5104#section-4.3.2.1
// 6: TSTN https://tools.ietf.org/html/rfc5104#section-4.3.2.1
// 7: VBCM https://tools.ietf.org/html/rfc5104#section-4.3.4.1
// 8-14: unassigned
// 15: REMB / Application layer FB (AFB) message, https://tools.ietf.org/html/draft-alvestrand-rmcat-remb-03
// 16-30: unassigned
// 31: reserved for future expansion of the sequence number space
#define PSFB_TYPE_MAP(XX) \
XX(RTCP_PSFB_PLI, 1) \
XX(RTCP_PSFB_SLI, 2) \
XX(RTCP_PSFB_RPSI, 3) \
XX(RTCP_PSFB_FIR, 4) \
XX(RTCP_PSFB_TSTR, 5)\
XX(RTCP_PSFB_TSTN, 6)\
XX(RTCP_PSFB_VBCM, 7) \
XX(RTCP_PSFB_REMB, 15)
//https://tools.ietf.org/html/rfc4585#section-6.2
//6.2. Transport Layer Feedback Messages
//
// Transport layer FB messages are identified by the value RTPFB as RTCP
// message type.
//
// A single general purpose transport layer FB message is defined in
// this document: Generic NACK. It is identified by means of the FMT
// parameter as follows:
//
// 0: unassigned
// 1: Generic NACK
// 2: reserved https://tools.ietf.org/html/rfc5104#section-4.2
// 3: TMMBR https://tools.ietf.org/html/rfc5104#section-4.2.1.1
// 4: TMMBN https://tools.ietf.org/html/rfc5104#section-4.2.2.1
// 5-14: unassigned
// 15 transport-cc https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01
// 16-30: unassigned
// 31: reserved for future expansion of the identifier number space
#define RTPFB_TYPE_MAP(XX) \
XX(RTCP_RTPFB_NACK, 1) \
XX(RTCP_RTPFB_TMMBR, 3) \
XX(RTCP_RTPFB_TMMBN, 4) \
XX(RTCP_RTPFB_TWCC, 15)
//rtcp类型枚举 //rtcp类型枚举
enum class RtcpType : uint8_t { enum class RtcpType : uint8_t {
#define XX(key, value) key = value, #define XX(key, value) key = value,
...@@ -69,6 +126,20 @@ enum class SdesType : uint8_t { ...@@ -69,6 +126,20 @@ enum class SdesType : uint8_t {
#undef XX #undef XX
}; };
//psfb类型枚举
enum class PSFBType : uint8_t {
#define XX(key, value) key = value,
PSFB_TYPE_MAP(XX)
#undef XX
};
//rtpfb类型枚举
enum class RTPFBType : uint8_t {
#define XX(key, value) key = value,
RTPFB_TYPE_MAP(XX)
#undef XX
};
/** /**
* RtcpType转描述字符串 * RtcpType转描述字符串
*/ */
...@@ -79,6 +150,16 @@ const char *rtcpTypeToStr(RtcpType type); ...@@ -79,6 +150,16 @@ const char *rtcpTypeToStr(RtcpType type);
*/ */
const char *sdesTypeToStr(SdesType type); const char *sdesTypeToStr(SdesType type);
/**
* psfb枚举转描述字符串
*/
const char *psfbTypeToStr(PSFBType type);
/**
* rtpfb枚举转描述字符串
*/
const char *rtpfbTypeToStr(RTPFBType type);
class RtcpHeader { class RtcpHeader {
public: public:
#if __BYTE_ORDER == __BIG_ENDIAN #if __BYTE_ORDER == __BIG_ENDIAN
...@@ -91,13 +172,15 @@ public: ...@@ -91,13 +172,15 @@ public:
#else #else
//reception report count //reception report count
uint32_t report_count: 5; uint32_t report_count: 5;
//padding,固定为0 //padding,末尾是否有追加填充
uint32_t padding: 1; uint32_t padding: 1;
//版本号,固定为2 //版本号,固定为2
uint32_t version: 2; uint32_t version: 2;
#endif #endif
//rtcp类型,RtcpType //rtcp类型,RtcpType
uint32_t pt: 8; uint32_t pt: 8;
private:
//长度 //长度
uint32_t length: 16; uint32_t length: 16;
...@@ -124,11 +207,23 @@ public: ...@@ -124,11 +207,23 @@ public:
*/ */
string dumpString() const; string dumpString() const;
protected:
/** /**
* 网络字节序转换为主机字节序 * 根据length字段获取rtcp总长度
*/ */
void net2Host(); size_t getSize() const;
/**
* 后面追加padding数据长度
*/
size_t getPaddingSize() const;
/**
* 设置rtcp length字段
* @param size rtcp总长度,单位字节
*/
void setSize(size_t size);
protected:
/** /**
* 打印字段详情 * 打印字段详情
...@@ -389,9 +484,9 @@ public: ...@@ -389,9 +484,9 @@ public:
//SdesType //SdesType
uint8_t type; uint8_t type;
//text长度股,可以为0 //text长度股,可以为0
uint8_t length; uint8_t txt_len;
//不定长 //不定长
char text; char text[1];
//最后以RTCP_SDES_END结尾 //最后以RTCP_SDES_END结尾
//只字段为占位字段,不代表真实位置 //只字段为占位字段,不代表真实位置
uint8_t end; uint8_t end;
...@@ -434,7 +529,7 @@ public: ...@@ -434,7 +529,7 @@ public:
* @param item_text SdesItem列表,只赋值length和text部分 * @param item_text SdesItem列表,只赋值length和text部分
* @return SDES包 * @return SDES包
*/ */
static std::shared_ptr<RtcpSdes> create(const std::initializer_list<string> &item_text); static std::shared_ptr<RtcpSdes> create(const std::vector<string> &item_text);
/** /**
* 获取SdesItem对象指针列表 * 获取SdesItem对象指针列表
...@@ -456,6 +551,142 @@ private: ...@@ -456,6 +551,142 @@ private:
void net2Host(size_t size); void net2Host(size_t size);
} PACKED; } PACKED;
// https://tools.ietf.org/html/rfc4585#section-6.1
// 6.1. Common Packet Format for Feedback Messages
//
// All FB messages MUST use a common packet format that is depicted in
// Figure 3:
//
// 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
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |V=2|P| FMT | PT | length |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | SSRC of packet sender |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | SSRC of media source |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// : Feedback Control Information (FCI) :
// : :
// rtcpfb和psfb的数据结构一致
class RtcpFB : public RtcpHeader {
public:
friend class RtcpHeader;
uint32_t ssrc;
uint32_t ssrc_media;
public:
/**
* 创建psfb类型的反馈包
*/
static std::shared_ptr<RtcpFB> create(PSFBType fmt, const void *fci = nullptr, size_t fci_len = 0);
/**
* 创建rtpfb类型的反馈包
*/
static std::shared_ptr<RtcpFB> create(RTPFBType fmt, const void *fci = nullptr, size_t fci_len = 0);
/**
* fci转换成某对象指针
* @tparam Type 对象类型
* @return 对象指针
*/
template<typename Type>
const Type& getFci() const{
auto fci_data = getFciPtr();
auto fci_len = getFciSize();
Type *fci = (Type *) fci_data;
fci->check(fci_len);
return *fci;
}
/**
* 获取fci指针
*/
const void *getFciPtr() const;
/**
* 获取fci数据长度
*/
size_t getFciSize() const;
private:
/**
* 打印字段详情
* 使用net2Host转换成主机字节序后才可使用此函数
*/
string dumpString() const;
/**
* 网络字节序转换为主机字节序
* @param size 字节长度,防止内存越界
*/
void net2Host(size_t size);
private:
static std::shared_ptr<RtcpFB> create_l(RtcpType type, int fmt, const void *fci, size_t fci_len);
} PACKED;
//BYE
/*
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P| SC | PT=BYE=203 | length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC/CSRC |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
: ... :
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
(opt) | length | reason for leaving ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
class RtcpBye : public RtcpHeader {
public:
friend class RtcpHeader;
/* 变长,根据count决定有多少个ssrc */
uint32_t ssrc[1];
/** 中间可能有若干个 ssrc **/
/* 可选 */
uint8_t reason_len;
char reason[1];
public:
/**
* 创建bye包
* @param ssrc ssrc列表
* @param reason 原因
* @return rtcp bye包
*/
static std::shared_ptr<RtcpBye> create(const std::vector<uint32_t> &ssrc, const string &reason);
/**
* 获取ssrc列表
*/
vector<uint32_t *> getSSRC();
/**
* 获取原因
*/
string getReason() const;
private:
/**
* 打印字段详情
* 使用net2Host转换成主机字节序后才可使用此函数
*/
string dumpString() const;
/**
* 网络字节序转换为主机字节序
* @param size 字节长度,防止内存越界
*/
void net2Host(size_t size);
} PACKED;
#if defined(_WIN32) #if defined(_WIN32)
#pragma pack(pop) #pragma pack(pop)
#endif // defined(_WIN32) #endif // defined(_WIN32)
......
/*
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
*
* Use of this source code is governed by MIT 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 "RtcpFCI.h"
#include "Util/logger.h"
using namespace toolkit;
namespace mediakit {
void FCI_SLI::check(size_t size){
CHECK(size >= kSize);
}
FCI_SLI::FCI_SLI(uint16_t first, uint16_t number, uint8_t pic_id) {
//13 bits
first &= 0x1FFF;
//13 bits
number &= 0x1FFF;
//6 bits
pic_id &= 0x3F;
data = (first << 19) | (number << 6) | pic_id;
data = htonl(data);
}
uint16_t FCI_SLI::getFirst() const {
return ntohl(data) >> 19;
}
uint16_t FCI_SLI::getNumber() const {
return (ntohl(data) >> 6) & 0x1FFF;
}
uint8_t FCI_SLI::getPicID() const {
return ntohl(data) & 0x3F;
}
string FCI_SLI::dumpString() const {
return StrPrinter << "First:" << getFirst() << ", Number:" << getNumber() << ", PictureID:" << (int)getPicID();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FCI_FIR::check(size_t size){
CHECK(size >= kSize);
}
uint32_t FCI_FIR::getSSRC() const{
return ntohl(ssrc);
}
uint8_t FCI_FIR::getSeq() const{
return seq_number;
}
uint32_t FCI_FIR::getReserved() const{
return (reserved[0] << 16) | (reserved[1] << 8) | reserved[2];
}
string FCI_FIR::dumpString() const {
return StrPrinter << "ssrc:" << getSSRC() << ", seq_number:" << (int)getSeq() << ", reserved:" << getReserved();
}
FCI_FIR::FCI_FIR(uint32_t ssrc, uint8_t seq_number, uint32_t reserved) {
this->ssrc = htonl(ssrc);
this->seq_number = seq_number;
this->reserved[0] = (reserved >> 16) & 0xFF;
this->reserved[1] = (reserved >> 8) & 0xFF;
this->reserved[2] = reserved & 0xFF;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static const char kRembMagic[] = "REMB";
void FCI_REMB::check(size_t size){
CHECK(size >= kSize);
CHECK(memcmp(magic, kRembMagic, sizeof(magic)) == 0);
auto num_ssrc = bitrate[0];
auto expect_size = kSize + 4 * num_ssrc;
CHECK(size >= expect_size);
}
string FCI_REMB::create(const vector<uint32_t> &ssrcs, uint32_t bitrate) {
CHECK(ssrcs.size() > 0 && ssrcs.size() <= 0xFF);
string ret;
ret.resize(kSize + ssrcs.size() * 4);
FCI_REMB *thiz = (FCI_REMB *) ret.data();
memcpy(thiz->magic, kRembMagic, sizeof(magic));
/* bitrate --> BR Exp/BR Mantissa */
uint8_t b = 0;
uint8_t exp = 0;
uint32_t mantissa = 0;
for (b = 0; b < 32; b++) {
if (bitrate <= ((uint32_t) 0x3FFFF << b)) {
exp = b;
break;
}
}
if (b > 31) {
b = 31;
}
mantissa = bitrate >> b;
//Num SSRC (8 bits)
thiz->bitrate[0] = ssrcs.size() & 0xFF;
//BR Exp (6 bits)/BR Mantissa (18 bits)
thiz->bitrate[1] = (uint8_t) ((exp << 2) + ((mantissa >> 16) & 0x03));
//BR Mantissa (18 bits)
thiz->bitrate[2] = (uint8_t) (mantissa >> 8);
//BR Mantissa (18 bits)
thiz->bitrate[3] = (uint8_t) (mantissa);
//设置ssrc列表
auto ptr = thiz->ssrc_feedback;
for (auto ssrc : ssrcs) {
*(ptr++) = htonl(ssrc);
}
return ret;
}
uint32_t FCI_REMB::getBitRate() const {
uint8_t exp = (bitrate[1] >> 2) & 0x3F;
uint32_t mantissa = (bitrate[1] & 0x03) << 16;
mantissa += (bitrate[2] << 8);
mantissa += (bitrate[3]);
return mantissa << exp;
}
vector<uint32_t> FCI_REMB::getSSRC() {
vector<uint32_t> ret;
auto num_ssrc = bitrate[0];
auto ptr = ssrc_feedback;
while (num_ssrc--) {
ret.emplace_back(ntohl(*ptr++));
}
return ret;
}
string FCI_REMB::dumpString() const {
_StrPrinter printer;
printer << "bitrate:" << getBitRate() << ", ssrc:";
for (auto &ssrc : ((FCI_REMB *) this)->getSSRC()) {
printer << ssrc << " ";
}
return std::move(printer);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
FCI_NACK::FCI_NACK(uint16_t pid_h, const vector<bool> &type) {
uint16_t blp_h = 0;
int i = kBitSize;
for (auto item : type) {
--i;
if (item) {
blp_h |= (1 << i);
}
}
blp = htons(blp_h);
pid = htons(pid_h);
}
void FCI_NACK::check(size_t size){
CHECK(size >= kSize);
}
uint16_t FCI_NACK::getPid() const {
return ntohs(pid);
}
uint16_t FCI_NACK::getBlp() const {
return ntohs(blp);
}
vector<bool> FCI_NACK::getBitArray() const {
vector<bool> ret;
ret.resize(kBitSize + 1);
//nack第一个包丢包
ret[0] = true;
auto blp_h = getBlp();
for (size_t i = 0; i < kBitSize; ++i) {
ret[i + 1] = blp_h & (1 << (kBitSize - i - 1));
}
return ret;
}
string FCI_NACK::dumpString() const {
_StrPrinter printer;
printer << "pid:" << getPid() << ",blp:" << getBlp() << ",bit array:";
for (auto flag : getBitArray()) {
printer << flag << " ";
}
return std::move(printer);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class RunLengthChunk {
public:
static size_t constexpr kSize = 2;
// 0 1
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |T| S | Run Length |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#if __BYTE_ORDER == __BIG_ENDIAN
uint16_t type: 1;
uint16_t symbol: 2;
uint16_t run_length_high: 5;
#else
// Run Length 高5位
uint16_t run_length_high: 5;
//参考SymbolStatus定义
uint16_t symbol: 2;
//固定为0
uint16_t type: 1;
#endif
// Run Length 低8位
uint16_t run_length_low: 8;
//获取Run Length
uint16_t getRunLength() const;
//构造函数
RunLengthChunk(SymbolStatus status, uint16_t run_length);
//打印本对象
string dumpString() const;
} PACKED;
RunLengthChunk::RunLengthChunk(SymbolStatus status, uint16_t run_length) {
type = 0;
symbol = (uint8_t)status & 0x03;
run_length_high = (run_length >> 8) & 0x1F;
run_length_low = run_length & 0xFF;
}
uint16_t RunLengthChunk::getRunLength() const {
CHECK(type == 0);
return run_length_high << 8 | run_length_low;
}
string RunLengthChunk::dumpString() const{
_StrPrinter printer;
printer << "run length chunk, symbol:" << (int)symbol << ", run length:" << getRunLength();
return std::move(printer);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class StatusVecChunk {
public:
static size_t constexpr kSize = 2;
// 0 1
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |T|S| symbol list |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#if __BYTE_ORDER == __BIG_ENDIAN
uint16_t type: 1;
uint16_t symbol: 1;
uint16_t symbol_list_high: 6;
#else
// symbol_list 高6位
uint16_t symbol_list_high: 6;
//symbol_list中元素是1个还是2个bit
uint16_t symbol: 1;
//固定为1
uint16_t type: 1;
#endif
// symbol_list 低8位
uint16_t symbol_list_low: 8;
//获取symbollist
vector<SymbolStatus> getSymbolList() const;
//构造函数
StatusVecChunk(const vector<SymbolStatus> &status);
//打印本对象
string dumpString() const;
} PACKED;
StatusVecChunk::StatusVecChunk(const vector<SymbolStatus> &status) {
uint16_t value = 0;
type = 1;
if (status.size() == 14) {
symbol = 0;
} else if (status.size() == 7) {
symbol = 1;
} else {
//非法
CHECK(0);
}
int i = 13;
for (auto &item : status) {
CHECK(item <= SymbolStatus::reserved);
if (!symbol) {
CHECK(item <= SymbolStatus::small_delta);
value |= (int) item << i;
--i;
} else {
value |= (int) item << (i - 1);
i -= 2;
}
}
symbol_list_low = value & 0xFF;
symbol_list_high = (value >> 8 ) & 0x1F;
}
vector<SymbolStatus> StatusVecChunk::getSymbolList() const {
CHECK(type == 1);
vector<SymbolStatus> ret;
auto thiz = ntohs(*((uint16_t *) this));
if (symbol == 0) {
//s = 0 时,表示symbollist的每一个bit能表示一个数据包的到达状态
for (int i = 13; i >= 0; --i) {
SymbolStatus status = (SymbolStatus) ((bool) (thiz & (1 << i)));
ret.emplace_back(status);
}
} else {
//s = 1 时,表示symbollist每两个bit表示一个数据包的状态
for (int i = 12; i >= 0; i -= 2) {
SymbolStatus status = (SymbolStatus) ((thiz & (3 << i)) >> i);
ret.emplace_back(status);
}
}
return ret;
}
string StatusVecChunk::dumpString() const {
_StrPrinter printer;
printer << "status vector chunk, symbol:" << (int) symbol << ", symbol list:";
auto vec = getSymbolList();
for (auto &item : vec) {
printer << (int) item << " ";
}
return std::move(printer);
}
///////////////////////////////////////////////////////
void FCI_TWCC::check(size_t size){
CHECK(size >= kSize);
}
uint16_t FCI_TWCC::getBaseSeq() const {
return ntohs(base_seq);
}
uint16_t FCI_TWCC::getPacketCount() const {
return ntohs(pkt_status_count);
}
uint32_t FCI_TWCC::getReferenceTime() const {
uint32_t ret = 0;
ret |= ref_time[0] << 16;
ret |= ref_time[1] << 8;
ret |= ref_time[2];
return ret;
}
//3.1.5. Receive Delta
//
// Deltas are represented as multiples of 250us:
//
// o If the "Packet received, small delta" symbol has been appended to
// the status list, an 8-bit unsigned receive delta will be appended
// to recv delta list, representing a delta in the range [0, 63.75]
// ms.
//
// o If the "Packet received, large or negative delta" symbol has been
// appended to the status list, a 16-bit signed receive delta will be
// appended to recv delta list, representing a delta in the range
// [-8192.0, 8191.75] ms.
//
// o If the delta exceeds even the larger limits, a new feedback
// message must be used, where the 24-bit base receive delta can
// cover very large gaps.
//
// The smaller receive delta upper bound of 63.75 ms means that this is
// only viable at about 1000/25.5 ~= 16 packets per second and above.
// With a packet size of 1200 bytes/packet that amounts to a bitrate of
// about 150 kbit/s.
//
// The 0.25 ms resolution means that up to 4000 packets per second can
// be represented. With a 1200 bytes/packet payload, that amounts to
// 38.4 Mbit/s payload bandwidth.
static int16_t getRecvDelta(SymbolStatus status, uint8_t *&ptr, const uint8_t *end){
int16_t delta = 0;
switch (status) {
case SymbolStatus::not_received : {
//丢包, recv delta为0个字节
break;
}
case SymbolStatus::small_delta : {
CHECK(ptr + 1 <= end);
//时间戳增量小于256, recv delta为1个字节
delta = *ptr;
ptr += 1;
break;
}
case SymbolStatus::large_delta : {
CHECK(ptr + 2 <= end);
//时间戳增量256~65535间,recv delta为2个字节
delta = *ptr << 8 | *(ptr + 1);
ptr += 2;
break;
}
case SymbolStatus::reserved : {
//没有时间戳
break;
}
default:
//这个逻辑分支不可达到
CHECK(0);
break;
}
return delta;
}
map<uint16_t, std::pair<SymbolStatus, uint32_t/*stamp*/> > FCI_TWCC::getPacketChunkList(size_t total_size) const {
map<uint16_t, std::pair<SymbolStatus, uint32_t> > ret;
auto ptr = (uint8_t *) this + kSize;
auto end = (uint8_t *) this + total_size;
CHECK(ptr < end);
auto seq = getBaseSeq();
auto rtp_count = getPacketCount();
for (uint8_t i = 0; i < rtp_count;) {
CHECK(ptr + RunLengthChunk::kSize <= end);
RunLengthChunk *chunk = (RunLengthChunk *) ptr;
if (!chunk->type) {
//RunLengthChunk
for (auto j = 0; j < chunk->getRunLength(); ++j) {
ret.emplace(seq++, std::make_pair((SymbolStatus) chunk->symbol, 0));
if (++i >= rtp_count) {
break;
}
}
} else {
//StatusVecChunk
StatusVecChunk *chunk = (StatusVecChunk *) ptr;
for (auto &symbol : chunk->getSymbolList()) {
ret.emplace(seq++, std::make_pair(symbol, 0));
if (++i >= rtp_count) {
break;
}
}
}
ptr += 2;
}
for (auto &pr : ret) {
CHECK(ptr <= end);
pr.second.second = 250 * getRecvDelta(pr.second.first, ptr, end);
}
return ret;
}
string FCI_TWCC::dumpString(size_t total_size) const {
_StrPrinter printer;
auto map = getPacketChunkList(total_size);
printer << "twcc fci, base_seq:" << getBaseSeq() << ", pkt_status_count:" << getPacketCount() << ", ref time:" << getReferenceTime() << ", fb count:" << (int)fb_pkt_count << "\n";
for (auto &pr : map) {
printer << "rtp seq:" << pr.first <<", packet status:" << (int)(pr.second.first) << ", delta:" << pr.second.second << "\n";
}
return std::move(printer);
}
}//namespace mediakit
#if 1
using namespace mediakit;
void testFCI() {
{
FCI_SLI fci(8191, 0, 63);
InfoL << hexdump(&fci, FCI_SLI::kSize) << fci.dumpString();
}
{
FCI_FIR fci(123456, 139, 456789);
InfoL << hexdump(&fci, FCI_FIR::kSize) << fci.dumpString();
}
{
auto str = FCI_REMB::create({1234, 2345, 5678}, 4 * 1024 * 1024);
FCI_REMB *ptr = (FCI_REMB *) str.data();
InfoL << hexdump(str.data(), str.size()) << ptr->dumpString();
}
{
FCI_NACK nack(1234, vector<bool>({1, 0, 0, 0, 1, 0, 1, 0, 1, 0}));
InfoL << hexdump(&nack, FCI_NACK::kSize) << nack.dumpString();
}
{
RunLengthChunk chunk(SymbolStatus::large_delta, 8024);
InfoL << hexdump(&chunk, RunLengthChunk::kSize) << chunk.dumpString();
}
auto lam = [](const initializer_list<int> &lst) {
vector<SymbolStatus> ret;
for (auto &num : lst) {
ret.emplace_back((SymbolStatus) num);
}
return ret;
};
{
StatusVecChunk chunk(lam({0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1}));
InfoL << hexdump(&chunk, StatusVecChunk::kSize) << chunk.dumpString();
}
{
StatusVecChunk chunk(lam({0, 1, 2, 2, 0, 1, 2}));
InfoL << hexdump(&chunk, StatusVecChunk::kSize) << chunk.dumpString();
}
}
#endif
\ No newline at end of file
/*
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
*
* Use of this source code is governed by MIT 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_RTCPFCI_H
#define ZLMEDIAKIT_RTCPFCI_H
#include "Rtcp.h"
#include "assert.h"
namespace mediakit {
/////////////////////////////////////////// PSFB ////////////////////////////////////////////////////
//PSFB fmt = 2
//https://tools.ietf.org/html/rfc4585#section-6.3.2.2
// 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
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | First | Number | PictureID |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//First: 13 bits
// The macroblock (MB) address of the first lost macroblock. The MB
// numbering is done such that the macroblock in the upper left
// corner of the picture is considered macroblock number 1 and the
// number for each macroblock increases from left to right and then
// from top to bottom in raster-scan order (such that if there is a
// total of N macroblocks in a picture, the bottom right macroblock
// is considered macroblock number N).
//
// Number: 13 bits
// The number of lost macroblocks, in scan order as discussed above.
//
// PictureID: 6 bits
// The six least significant bits of the codec-specific identifier
// that is used to reference the picture in which the loss of the
// macroblock(s) has occurred. For many video codecs, the PictureID
// is identical to the Temporal Reference.
class FCI_SLI {
public:
static size_t constexpr kSize = 4;
FCI_SLI(uint16_t first, uint16_t number, uint8_t pic_id);
void check(size_t size);
uint16_t getFirst() const;
uint16_t getNumber() const;
uint8_t getPicID() const;
string dumpString() const;
private:
uint32_t data;
} PACKED;
//PSFB fmt = 3
//https://tools.ietf.org/html/rfc4585#section-6.3.3.2
// 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
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | PB |0| Payload Type| Native RPSI bit string |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | defined per codec ... | Padding (0) |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
class FCI_RPSI {
public:
//The number of unused bits required to pad the length of the RPSI
// message to a multiple of 32 bits.
uint8_t pb;
#if __BYTE_ORDER == __BIG_ENDIAN
//0: 1 bit
// MUST be set to zero upon transmission and ignored upon reception.
uint8_t zero : 1;
//Payload Type: 7 bits
// Indicates the RTP payload type in the context of which the native
// RPSI bit string MUST be interpreted.
uint8_t pt : 7;
#else
uint8_t pt: 7;
uint8_t zero: 1;
#endif
// Native RPSI bit string: variable length
// The RPSI information as natively defined by the video codec.
char bit_string[5];
//Padding: #PB bits
// A number of bits set to zero to fill up the contents of the RPSI
// message to the next 32-bit boundary. The number of padding bits
// MUST be indicated by the PB field.
uint8_t padding;
static size_t constexpr kSize = 8;
} PACKED;
//PSFB fmt = 4
//https://tools.ietf.org/html/rfc5104#section-4.3.1.1
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | SSRC |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Seq nr. | Reserved |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
class FCI_FIR {
public:
static size_t constexpr kSize = 8;
FCI_FIR(uint32_t ssrc, uint8_t seq_number, uint32_t reserved = 0);
void check(size_t size);
uint32_t getSSRC() const;
uint8_t getSeq() const;
uint32_t getReserved() const;
string dumpString() const;
private:
uint32_t ssrc;
uint8_t seq_number;
uint8_t reserved[3];
} PACKED;
//PSFB fmt = 5
//https://tools.ietf.org/html/rfc5104#section-4.3.2.1
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | SSRC |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Seq nr. | Reserved | Index |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
class FCI_TSTR {
public:
static size_t constexpr kSize = 8;
void check(size_t size) {
CHECK(size == kSize);
}
private:
uint8_t data[kSize];
} PACKED;
//PSFB fmt = 6
//https://tools.ietf.org/html/rfc5104#section-4.3.2.1
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | SSRC |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Seq nr. | Reserved | Index |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
class FCI_TSTN : public FCI_TSTR{
} PACKED;
//PSFB fmt = 7
//https://tools.ietf.org/html/rfc5104#section-4.3.4.1
//0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | SSRC |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Seq nr. |0| Payload Type| Length |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | VBCM Octet String.... | Padding |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
class FCI_VBCM {
public:
static size_t constexpr kSize = 12;
void check(size_t size) {
CHECK(size == kSize);
}
private:
uint8_t data[kSize];
} PACKED;
//PSFB fmt = 15
//https://tools.ietf.org/html/draft-alvestrand-rmcat-remb-03
// 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
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Unique identifier 'R' 'E' 'M' 'B' |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Num SSRC | BR Exp | BR Mantissa |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | SSRC feedback |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ... |
// Num SSRC (8 bits): Number of SSRCs in this message.
//
// BR Exp (6 bits): The exponential scaling of the mantissa for the
// maximum total media bit rate value, ignoring all packet
// overhead. The value is an unsigned integer [0..63], as
// in RFC 5104 section 4.2.2.1.
//
// BR Mantissa (18 bits): The mantissa of the maximum total media bit
// rate (ignoring all packet overhead) that the sender of
// the REMB estimates. The BR is the estimate of the
// traveled path for the SSRCs reported in this message.
// The value is an unsigned integer in number of bits per
// second.
//
// SSRC feedback (32 bits) Consists of one or more SSRC entries which
// this feedback message applies to.
class FCI_REMB {
public:
static size_t constexpr kSize = 8;
static string create(const std::vector<uint32_t> &ssrcs, uint32_t bitrate);
void check(size_t size);
string dumpString() const;
uint32_t getBitRate() const;
vector<uint32_t> getSSRC();
private:
//Unique identifier 'R' 'E' 'M' 'B'
char magic[4];
//Num SSRC (8 bits)/BR Exp (6 bits)/ BR Mantissa (18 bits)
uint8_t bitrate[4];
// SSRC feedback (32 bits) Consists of one or more SSRC entries which
// this feedback message applies to.
uint32_t ssrc_feedback[1];
} PACKED;
/////////////////////////////////////////// RTPFB ////////////////////////////////////////////////////
//RTPFB fmt = 1
//https://tools.ietf.org/html/rfc4585#section-6.2.1
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | PID | BLP |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
class FCI_NACK {
public:
static constexpr size_t kSize = 4;
static constexpr size_t kBitSize = 16;
FCI_NACK(uint16_t pid_h, const vector<bool> &type);
void check(size_t size);
uint16_t getPid() const;
uint16_t getBlp() const;
//返回丢包列表,总长度17,第一个包必丢
vector<bool> getBitArray() const;
string dumpString() const;
private:
// The PID field is used to specify a lost packet. The PID field
// refers to the RTP sequence number of the lost packet.
uint16_t pid;
// bitmask of following lost packets (BLP): 16 bits
uint16_t blp;
} PACKED;
//RTPFB fmt = 3
//https://tools.ietf.org/html/rfc5104#section-4.2.1.1
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | SSRC |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | MxTBR Exp | MxTBR Mantissa |Measured Overhead|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
class FCI_TMMBR {
public:
static size_t constexpr kSize = 8;
void check(size_t size) {
CHECK(size == kSize);
}
private:
//SSRC (32 bits): The SSRC value of the media sender that is
// requested to obey the new maximum bit rate.
uint32_t ssrc;
// MxTBR Exp (6 bits): The exponential scaling of the mantissa for the
// maximum total media bit rate value. The value is an
// unsigned integer [0..63].
// MxTBR Mantissa (17 bits): The mantissa of the maximum total media
// bit rate value as an unsigned integer.
// Measured Overhead (9 bits): The measured average packet overhead
// value in bytes. The measurement SHALL be done according
// to the description in section 4.2.1.2. The value is an
// unsigned integer [0..511].
uint32_t max_tbr;
} PACKED;
//RTPFB fmt = 4
// https://tools.ietf.org/html/rfc5104#section-4.2.2.1
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | SSRC |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | MxTBR Exp | MxTBR Mantissa |Measured Overhead|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
class FCI_TMMBN : public FCI_TMMBR{
public:
} PACKED;
enum class SymbolStatus : uint8_t{
//Packet not received
not_received = 0,
//Packet received, small delta (所谓small detal是指能用一个字节表示的数值)
small_delta = 1,
// Packet received, large ornegative delta (large即是能用两个字节表示的数值)
large_delta = 2,
//Reserved
reserved = 3
};
//RTPFB fmt = 15
//https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#section-3.1
//https://zhuanlan.zhihu.com/p/206656654
// 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
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | base sequence number | packet status count |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | reference time | fb pkt. count |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | packet chunk | packet chunk |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// . .
// . .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | packet chunk | recv delta | recv delta |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// . .
// . .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | recv delta | recv delta | zero padding |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
class FCI_TWCC{
public:
static size_t constexpr kSize = 8;
void check(size_t size);
string dumpString(size_t total_size) const;
uint16_t getBaseSeq() const;
uint32_t getReferenceTime() const;
uint16_t getPacketCount() const;
map<uint16_t, std::pair<SymbolStatus, uint32_t/*recv delta 微秒*/> > getPacketChunkList(size_t total_size) const;
private:
//base sequence number,基础序号,本次反馈的第一个包的序号;也就是RTP扩展头的序列号
uint16_t base_seq;
//packet status count, 包个数,本次反馈包含多少个包的状态;从基础序号开始算
uint16_t pkt_status_count;
//reference time,基准时间,绝对时间;计算该包中每个媒体包的到达时间都要基于这个基准时间计算
uint8_t ref_time[3];
//feedback packet count,反馈包号,本包是第几个transport-cc包,每次加1 |
uint8_t fb_pkt_count;
} PACKED;
} //namespace mediakit
#endif //ZLMEDIAKIT_RTCPFCI_H
...@@ -463,6 +463,9 @@ void RtmpSession::onRtmpChunk(RtmpPacket::Ptr packet) { ...@@ -463,6 +463,9 @@ void RtmpSession::onRtmpChunk(RtmpPacket::Ptr packet) {
std::string type = dec.load<std::string>(); std::string type = dec.load<std::string>();
if (type == "@setDataFrame") { if (type == "@setDataFrame") {
setMetaData(dec); setMetaData(dec);
} else if (type == "onMetaData") {
//兼容某些不规范的推流器
_publisher_metadata = dec.load<AMFValue>();
} else { } else {
TraceP(this) << "unknown notify:" << type; TraceP(this) << "unknown notify:" << type;
} }
......
...@@ -21,7 +21,7 @@ using namespace toolkit; ...@@ -21,7 +21,7 @@ using namespace toolkit;
namespace mediakit { namespace mediakit {
template<typename T, typename SEQ = uint16_t, size_t kMax = 256, size_t kMin = 10> template<typename T, typename SEQ = uint16_t, size_t kMax = 1024, size_t kMin = 32>
class PacketSortor { class PacketSortor {
public: public:
PacketSortor() = default; PacketSortor() = default;
......
...@@ -458,18 +458,27 @@ size_t RtpHeader::getExtSize() const { ...@@ -458,18 +458,27 @@ size_t RtpHeader::getExtSize() const {
return 0; return 0;
} }
auto ext_ptr = &payload + getCsrcSize(); auto ext_ptr = &payload + getCsrcSize();
uint16_t reserved = AV_RB16(ext_ptr); //uint16_t reserved = AV_RB16(ext_ptr);
//每个ext占用4字节 //每个ext占用4字节
return AV_RB16(ext_ptr + 2) << 2; return AV_RB16(ext_ptr + 2) << 2;
} }
uint16_t RtpHeader::getExtReserved() const{
//rtp有ext
if (!ext) {
return 0;
}
auto ext_ptr = &payload + getCsrcSize();
return AV_RB16(ext_ptr);
}
uint8_t *RtpHeader::getExtData() { uint8_t *RtpHeader::getExtData() {
if (!ext) { if (!ext) {
return nullptr; return nullptr;
} }
auto ext_ptr = &payload + getCsrcSize(); auto ext_ptr = &payload + getCsrcSize();
//多出的4个字节分别为reserved、ext_len //多出的4个字节分别为reserved、ext_len
return ext_ptr + 4 + getExtSize(); return ext_ptr + 4;
} }
size_t RtpHeader::getPayloadOffset() const { size_t RtpHeader::getPayloadOffset() const {
...@@ -521,6 +530,10 @@ RtpHeader* RtpPacket::getHeader(){ ...@@ -521,6 +530,10 @@ RtpHeader* RtpPacket::getHeader(){
return (RtpHeader*)(data() + RtpPacket::kRtpTcpHeaderSize); return (RtpHeader*)(data() + RtpPacket::kRtpTcpHeaderSize);
} }
string RtpPacket::dumpString() const{
return ((RtpPacket *) this)->getHeader()->dumpString(size() - RtpPacket::kRtpTcpHeaderSize);
}
uint16_t RtpPacket::getSeq(){ uint16_t RtpPacket::getSeq(){
return ntohs(getHeader()->seq); return ntohs(getHeader()->seq);
} }
......
...@@ -119,6 +119,8 @@ public: ...@@ -119,6 +119,8 @@ public:
//返回ext字段字节长度 //返回ext字段字节长度
size_t getExtSize() const; size_t getExtSize() const;
//返回ext reserved值
uint16_t getExtReserved() const;
//返回ext段首地址,不存在时返回nullptr //返回ext段首地址,不存在时返回nullptr
uint8_t *getExtData(); uint8_t *getExtData();
...@@ -150,7 +152,11 @@ public: ...@@ -150,7 +152,11 @@ public:
kRtpTcpHeaderSize = 4 kRtpTcpHeaderSize = 4
}; };
//获取rtp头
RtpHeader* getHeader(); RtpHeader* getHeader();
//打印调试信息
string dumpString() const;
//主机字节序的seq //主机字节序的seq
uint16_t getSeq(); uint16_t getSeq();
//主机字节序的时间戳,已经转换为毫秒 //主机字节序的时间戳,已经转换为毫秒
......
...@@ -56,8 +56,12 @@ public: ...@@ -56,8 +56,12 @@ public:
//需要解复用rtp //需要解复用rtp
key_pos = _demuxer->inputRtp(rtp); key_pos = _demuxer->inputRtp(rtp);
} }
GET_CONFIG(bool, directProxy, Rtsp::kDirectProxy);
if (directProxy) {
//直接代理模式才直接使用原始rtp
RtspMediaSource::onWrite(std::move(rtp), key_pos); RtspMediaSource::onWrite(std::move(rtp), key_pos);
} }
}
/** /**
* 获取观看总人数,包括(hls/rtsp/rtmp) * 获取观看总人数,包括(hls/rtsp/rtmp)
...@@ -72,8 +76,10 @@ public: ...@@ -72,8 +76,10 @@ public:
* @param enableMP4 是否mp4录制 * @param enableMP4 是否mp4录制
*/ */
void setProtocolTranslation(bool enableHls,bool enableMP4){ void setProtocolTranslation(bool enableHls,bool enableMP4){
//不重复生成rtsp GET_CONFIG(bool, directProxy, Rtsp::kDirectProxy);
_muxer = std::make_shared<MultiMediaSourceMuxer>(getVhost(), getApp(), getId(), _demuxer->getDuration(), false, true, enableHls, enableMP4); //开启直接代理模式时,rtsp直接代理,不重复产生;但是有些rtsp推流端,由于sdp中已有sps pps,rtp中就不再包括sps pps,
//导致rtc无法播放,所以在rtsp推流rtc播放时,建议关闭直接代理模式
_muxer = std::make_shared<MultiMediaSourceMuxer>(getVhost(), getApp(), getId(), _demuxer->getDuration(), !directProxy, true, enableHls, enableMP4);
_muxer->setMediaListener(getListener()); _muxer->setMediaListener(getListener());
_muxer->setTrackListener(static_pointer_cast<RtspMediaSourceImp>(shared_from_this())); _muxer->setTrackListener(static_pointer_cast<RtspMediaSourceImp>(shared_from_this()));
//让_muxer对象拦截一部分事件(比如说录像相关事件) //让_muxer对象拦截一部分事件(比如说录像相关事件)
......
/*
* Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved.
*
* This file is part of ZLToolKit(https://github.com/xia-chu/ZLToolKit).
*
* Use of this source code is governed by MIT 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 <iostream>
#include "Util/logger.h"
#include "Rtcp/RtcpFCI.h"
using namespace std;
using namespace toolkit;
using namespace mediakit;
extern void testFCI();
int main() {
Logger::Instance().add(std::make_shared<ConsoleChannel>());
testFCI();
return 0;
}
/*
* Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved.
*
* This file is part of ZLToolKit(https://github.com/xia-chu/ZLToolKit).
*
* Use of this source code is governed by MIT 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 <iostream>
#include "Util/logger.h"
#include "Rtcp/RtcpFCI.h"
#include "../webrtc/WebRtcTransport.h"
using namespace std;
using namespace toolkit;
using namespace mediakit;
extern void testFCI();
int main() {
Logger::Instance().add(std::make_shared<ConsoleChannel>());
srand((unsigned) time(NULL));
NackContext ctx;
for (int i = 1; i < 1000; ++i) {
if (i % (1 + (rand() % 30)) == 0) {
DebugL << "drop:" << i;
} else {
ctx.received(i);
}
}
sleep(1);
return 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::DtlsTransport"
// #define MS_LOG_DEV_LEVEL 3
#include "DtlsTransport.hpp"
#include "logger.h"
#include <openssl/asn1.h>
#include <openssl/bn.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/rsa.h>
#include <cstdio> // std::sprintf(), std::fopen()
#include <cstring> // std::memcpy(), std::strcmp()
#include "Util/util.h"
#define LOG_OPENSSL_ERROR(desc) \
do \
{ \
if (ERR_peek_error() == 0) \
MS_ERROR("OpenSSL error [desc:'%s']", desc); \
else \
{ \
int64_t err; \
while ((err = ERR_get_error()) != 0) \
{ \
MS_ERROR("OpenSSL error [desc:'%s', error:'%s']", desc, ERR_error_string(err, nullptr)); \
} \
ERR_clear_error(); \
} \
} while (false)
/* Static methods for OpenSSL callbacks. */
inline static int onSslCertificateVerify(int /*preverifyOk*/, X509_STORE_CTX* /*ctx*/)
{
MS_TRACE();
// Always valid since DTLS certificates are self-signed.
return 1;
}
inline static unsigned int onSslDtlsTimer(SSL* /*ssl*/, unsigned int timerUs)
{
if (timerUs == 0)
return 100000;
else if (timerUs >= 4000000)
return 4000000;
else
return 2 * timerUs;
}
namespace RTC
{
/* Static. */
// clang-format off
static constexpr int DtlsMtu{ 1350 };
// AES-HMAC: http://tools.ietf.org/html/rfc3711
static constexpr size_t SrtpMasterKeyLength{ 16 };
static constexpr size_t SrtpMasterSaltLength{ 14 };
static constexpr size_t SrtpMasterLength{ SrtpMasterKeyLength + SrtpMasterSaltLength };
// AES-GCM: http://tools.ietf.org/html/rfc7714
static constexpr size_t SrtpAesGcm256MasterKeyLength{ 32 };
static constexpr size_t SrtpAesGcm256MasterSaltLength{ 12 };
static constexpr size_t SrtpAesGcm256MasterLength{ SrtpAesGcm256MasterKeyLength + SrtpAesGcm256MasterSaltLength };
static constexpr size_t SrtpAesGcm128MasterKeyLength{ 16 };
static constexpr size_t SrtpAesGcm128MasterSaltLength{ 12 };
static constexpr size_t SrtpAesGcm128MasterLength{ SrtpAesGcm128MasterKeyLength + SrtpAesGcm128MasterSaltLength };
// clang-format on
/* Class variables. */
// clang-format off
std::map<std::string, DtlsTransport::FingerprintAlgorithm> DtlsTransport::string2FingerprintAlgorithm =
{
{ "sha-1", DtlsTransport::FingerprintAlgorithm::SHA1 },
{ "sha-224", DtlsTransport::FingerprintAlgorithm::SHA224 },
{ "sha-256", DtlsTransport::FingerprintAlgorithm::SHA256 },
{ "sha-384", DtlsTransport::FingerprintAlgorithm::SHA384 },
{ "sha-512", DtlsTransport::FingerprintAlgorithm::SHA512 }
};
std::map<DtlsTransport::FingerprintAlgorithm, std::string> DtlsTransport::fingerprintAlgorithm2String =
{
{ DtlsTransport::FingerprintAlgorithm::SHA1, "sha-1" },
{ DtlsTransport::FingerprintAlgorithm::SHA224, "sha-224" },
{ DtlsTransport::FingerprintAlgorithm::SHA256, "sha-256" },
{ DtlsTransport::FingerprintAlgorithm::SHA384, "sha-384" },
{ DtlsTransport::FingerprintAlgorithm::SHA512, "sha-512" }
};
std::map<std::string, DtlsTransport::Role> DtlsTransport::string2Role =
{
{ "auto", DtlsTransport::Role::AUTO },
{ "client", DtlsTransport::Role::CLIENT },
{ "server", DtlsTransport::Role::SERVER }
};
std::vector<DtlsTransport::SrtpCryptoSuiteMapEntry> DtlsTransport::srtpCryptoSuites =
{
{ RTC::SrtpSession::CryptoSuite::AEAD_AES_256_GCM, "SRTP_AEAD_AES_256_GCM" },
{ RTC::SrtpSession::CryptoSuite::AEAD_AES_128_GCM, "SRTP_AEAD_AES_128_GCM" },
{ RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80, "SRTP_AES128_CM_SHA1_80" },
{ RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32, "SRTP_AES128_CM_SHA1_32" }
};
// clang-format on
INSTANCE_IMP(DtlsTransport::DtlsEnvironment);
/* Class methods. */
DtlsTransport::DtlsEnvironment::DtlsEnvironment()
{
MS_TRACE();
// Generate a X509 certificate and private key (unless PEM files are provided).
if (true /*
Settings::configuration.dtlsCertificateFile.empty() ||
Settings::configuration.dtlsPrivateKeyFile.empty()*/)
{
GenerateCertificateAndPrivateKey();
}
else
{
ReadCertificateAndPrivateKeyFromFiles();
}
// Create a global SSL_CTX.
CreateSslCtx();
// Generate certificate fingerprints.
GenerateFingerprints();
}
DtlsTransport::DtlsEnvironment::~DtlsEnvironment()
{
MS_TRACE();
if (privateKey)
EVP_PKEY_free(privateKey);
if (certificate)
X509_free(certificate);
if (sslCtx)
SSL_CTX_free(sslCtx);
}
void DtlsTransport::DtlsEnvironment::GenerateCertificateAndPrivateKey()
{
MS_TRACE();
int ret{ 0 };
EC_KEY* ecKey{ nullptr };
X509_NAME* certName{ nullptr };
std::string subject =
std::string("mediasoup") + std::to_string(rand() % 999999 + 100000);
// Create key with curve.
ecKey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
if (!ecKey)
{
LOG_OPENSSL_ERROR("EC_KEY_new_by_curve_name() failed");
goto error;
}
EC_KEY_set_asn1_flag(ecKey, OPENSSL_EC_NAMED_CURVE);
// NOTE: This can take some time.
ret = EC_KEY_generate_key(ecKey);
if (ret == 0)
{
LOG_OPENSSL_ERROR("EC_KEY_generate_key() failed");
goto error;
}
// Create a private key object.
privateKey = EVP_PKEY_new();
if (!privateKey)
{
LOG_OPENSSL_ERROR("EVP_PKEY_new() failed");
goto error;
}
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
ret = EVP_PKEY_assign_EC_KEY(privateKey, ecKey);
if (ret == 0)
{
LOG_OPENSSL_ERROR("EVP_PKEY_assign_EC_KEY() failed");
goto error;
}
// The EC key now belongs to the private key, so don't clean it up separately.
ecKey = nullptr;
// Create the X509 certificate.
certificate = X509_new();
if (!certificate)
{
LOG_OPENSSL_ERROR("X509_new() failed");
goto error;
}
// Set version 3 (note that 0 means version 1).
X509_set_version(certificate, 2);
// Set serial number (avoid default 0).
ASN1_INTEGER_set(
X509_get_serialNumber(certificate),
static_cast<uint64_t>(rand() % 999999 + 100000));
// Set valid period.
X509_gmtime_adj(X509_get_notBefore(certificate), -315360000); // -10 years.
X509_gmtime_adj(X509_get_notAfter(certificate), 315360000); // 10 years.
// Set the public key for the certificate using the key.
ret = X509_set_pubkey(certificate, privateKey);
if (ret == 0)
{
LOG_OPENSSL_ERROR("X509_set_pubkey() failed");
goto error;
}
// Set certificate fields.
certName = X509_get_subject_name(certificate);
if (!certName)
{
LOG_OPENSSL_ERROR("X509_get_subject_name() failed");
goto error;
}
X509_NAME_add_entry_by_txt(
certName, "O", MBSTRING_ASC, reinterpret_cast<const uint8_t*>(subject.c_str()), -1, -1, 0);
X509_NAME_add_entry_by_txt(
certName, "CN", MBSTRING_ASC, reinterpret_cast<const uint8_t*>(subject.c_str()), -1, -1, 0);
// It is self-signed so set the issuer name to be the same as the subject.
ret = X509_set_issuer_name(certificate, certName);
if (ret == 0)
{
LOG_OPENSSL_ERROR("X509_set_issuer_name() failed");
goto error;
}
// Sign the certificate with its own private key.
ret = X509_sign(certificate, privateKey, EVP_sha1());
if (ret == 0)
{
LOG_OPENSSL_ERROR("X509_sign() failed");
goto error;
}
return;
error:
if (ecKey)
EC_KEY_free(ecKey);
if (privateKey)
EVP_PKEY_free(privateKey); // NOTE: This also frees the EC key.
if (certificate)
X509_free(certificate);
MS_THROW_ERROR("DTLS certificate and private key generation failed");
}
void DtlsTransport::DtlsEnvironment::ReadCertificateAndPrivateKeyFromFiles()
{
#if 0
MS_TRACE();
FILE* file{ nullptr };
file = fopen(Settings::configuration.dtlsCertificateFile.c_str(), "r");
if (!file)
{
MS_ERROR("error reading DTLS certificate file: %s", std::strerror(errno));
goto error;
}
certificate = PEM_read_X509(file, nullptr, nullptr, nullptr);
if (!certificate)
{
LOG_OPENSSL_ERROR("PEM_read_X509() failed");
goto error;
}
fclose(file);
file = fopen(Settings::configuration.dtlsPrivateKeyFile.c_str(), "r");
if (!file)
{
MS_ERROR("error reading DTLS private key file: %s", std::strerror(errno));
goto error;
}
privateKey = PEM_read_PrivateKey(file, nullptr, nullptr, nullptr);
if (!privateKey)
{
LOG_OPENSSL_ERROR("PEM_read_PrivateKey() failed");
goto error;
}
fclose(file);
return;
error:
MS_THROW_ERROR("error reading DTLS certificate and private key PEM files");
#endif
}
void DtlsTransport::DtlsEnvironment::CreateSslCtx()
{
MS_TRACE();
std::string dtlsSrtpCryptoSuites;
int ret;
/* Set the global DTLS context. */
// Both DTLS 1.0 and 1.2 (requires OpenSSL >= 1.1.0).
sslCtx = SSL_CTX_new(DTLS_method());
if (!sslCtx)
{
LOG_OPENSSL_ERROR("SSL_CTX_new() failed");
goto error;
}
ret = SSL_CTX_use_certificate(sslCtx, certificate);
if (ret == 0)
{
LOG_OPENSSL_ERROR("SSL_CTX_use_certificate() failed");
goto error;
}
ret = SSL_CTX_use_PrivateKey(sslCtx, privateKey);
if (ret == 0)
{
LOG_OPENSSL_ERROR("SSL_CTX_use_PrivateKey() failed");
goto error;
}
ret = SSL_CTX_check_private_key(sslCtx);
if (ret == 0)
{
LOG_OPENSSL_ERROR("SSL_CTX_check_private_key() failed");
goto error;
}
// Set options.
SSL_CTX_set_options(
sslCtx,
SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_TICKET | SSL_OP_SINGLE_ECDH_USE |
SSL_OP_NO_QUERY_MTU);
// Don't use sessions cache.
SSL_CTX_set_session_cache_mode(sslCtx, SSL_SESS_CACHE_OFF);
// Read always as much into the buffer as possible.
// NOTE: This is the default for DTLS, but a bug in non latest OpenSSL
// versions makes this call required.
SSL_CTX_set_read_ahead(sslCtx, 1);
SSL_CTX_set_verify_depth(sslCtx, 4);
// Require certificate from peer.
SSL_CTX_set_verify(
sslCtx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, onSslCertificateVerify);
// Set SSL info callback.
SSL_CTX_set_info_callback(sslCtx, [](const SSL* ssl, int where, int ret){
static_cast<RTC::DtlsTransport*>(SSL_get_ex_data(ssl, 0))->OnSslInfo(where, ret);
});
// Set ciphers.
ret = SSL_CTX_set_cipher_list(
sslCtx, "DEFAULT:!NULL:!aNULL:!SHA256:!SHA384:!aECDH:!AESGCM+AES256:!aPSK");
if (ret == 0)
{
LOG_OPENSSL_ERROR("SSL_CTX_set_cipher_list() failed");
goto error;
}
// Enable ECDH ciphers.
// DOC: http://en.wikibooks.org/wiki/OpenSSL/Diffie-Hellman_parameters
// NOTE: https://code.google.com/p/chromium/issues/detail?id=406458
// NOTE: https://bugs.ruby-lang.org/issues/12324
// For OpenSSL >= 1.0.2.
SSL_CTX_set_ecdh_auto(sslCtx, 1);
// Set the "use_srtp" DTLS extension.
for (auto it = DtlsTransport::srtpCryptoSuites.begin();
it != DtlsTransport::srtpCryptoSuites.end();
++it)
{
if (it != DtlsTransport::srtpCryptoSuites.begin())
dtlsSrtpCryptoSuites += ":";
SrtpCryptoSuiteMapEntry* cryptoSuiteEntry = std::addressof(*it);
dtlsSrtpCryptoSuites += cryptoSuiteEntry->name;
}
MS_DEBUG_2TAGS(dtls, srtp, "setting SRTP cryptoSuites for DTLS: %s", dtlsSrtpCryptoSuites.c_str());
// NOTE: This function returns 0 on success.
ret = SSL_CTX_set_tlsext_use_srtp(sslCtx, dtlsSrtpCryptoSuites.c_str());
if (ret != 0)
{
MS_ERROR(
"SSL_CTX_set_tlsext_use_srtp() failed when entering '%s'", dtlsSrtpCryptoSuites.c_str());
LOG_OPENSSL_ERROR("SSL_CTX_set_tlsext_use_srtp() failed");
goto error;
}
return;
error:
if (sslCtx)
{
SSL_CTX_free(sslCtx);
sslCtx = nullptr;
}
MS_THROW_ERROR("SSL context creation failed");
}
void DtlsTransport::DtlsEnvironment::GenerateFingerprints()
{
MS_TRACE();
for (auto& kv : DtlsTransport::string2FingerprintAlgorithm)
{
const std::string& algorithmString = kv.first;
FingerprintAlgorithm algorithm = kv.second;
uint8_t binaryFingerprint[EVP_MAX_MD_SIZE];
unsigned int size{ 0 };
char hexFingerprint[(EVP_MAX_MD_SIZE * 3) + 1];
const EVP_MD* hashFunction;
int ret;
switch (algorithm)
{
case FingerprintAlgorithm::SHA1:
hashFunction = EVP_sha1();
break;
case FingerprintAlgorithm::SHA224:
hashFunction = EVP_sha224();
break;
case FingerprintAlgorithm::SHA256:
hashFunction = EVP_sha256();
break;
case FingerprintAlgorithm::SHA384:
hashFunction = EVP_sha384();
break;
case FingerprintAlgorithm::SHA512:
hashFunction = EVP_sha512();
break;
default:
MS_THROW_ERROR("unknown algorithm");
}
ret = X509_digest(certificate, hashFunction, binaryFingerprint, &size);
if (ret == 0)
{
MS_ERROR("X509_digest() failed");
MS_THROW_ERROR("Fingerprints generation failed");
}
// Convert to hexadecimal format in uppercase with colons.
for (unsigned int i{ 0 }; i < size; ++i)
{
std::sprintf(hexFingerprint + (i * 3), "%.2X:", binaryFingerprint[i]);
}
hexFingerprint[(size * 3) - 1] = '\0';
MS_DEBUG_TAG(dtls, "%-7s fingerprint: %s", algorithmString.c_str(), hexFingerprint);
// Store it in the vector.
DtlsTransport::Fingerprint fingerprint;
fingerprint.algorithm = DtlsTransport::GetFingerprintAlgorithm(algorithmString);
fingerprint.value = hexFingerprint;
localFingerprints.push_back(fingerprint);
}
}
/* Instance methods. */
DtlsTransport::DtlsTransport(EventPoller::Ptr poller,Listener* listener) : poller(std::move(poller)), listener(listener)
{
MS_TRACE();
env = DtlsEnvironment::Instance().shared_from_this();
/* Set SSL. */
this->ssl = SSL_new(env->sslCtx);
if (!this->ssl)
{
LOG_OPENSSL_ERROR("SSL_new() failed");
goto error;
}
// Set this as custom data.
SSL_set_ex_data(this->ssl, 0, static_cast<void*>(this));
this->sslBioFromNetwork = BIO_new(BIO_s_mem());
if (!this->sslBioFromNetwork)
{
LOG_OPENSSL_ERROR("BIO_new() failed");
SSL_free(this->ssl);
goto error;
}
this->sslBioToNetwork = BIO_new(BIO_s_mem());
if (!this->sslBioToNetwork)
{
LOG_OPENSSL_ERROR("BIO_new() failed");
BIO_free(this->sslBioFromNetwork);
SSL_free(this->ssl);
goto error;
}
SSL_set_bio(this->ssl, this->sslBioFromNetwork, this->sslBioToNetwork);
// Set the MTU so that we don't send packets that are too large with no fragmentation.
SSL_set_mtu(this->ssl, DtlsMtu);
DTLS_set_link_mtu(this->ssl, DtlsMtu);
// Set callback handler for setting DTLS timer interval.
DTLS_set_timer_cb(this->ssl, onSslDtlsTimer);
return;
error:
// NOTE: At this point SSL_set_bio() was not called so we must free BIOs as
// well.
if (this->sslBioFromNetwork)
BIO_free(this->sslBioFromNetwork);
if (this->sslBioToNetwork)
BIO_free(this->sslBioToNetwork);
if (this->ssl)
SSL_free(this->ssl);
// NOTE: If this is not catched by the caller the program will abort, but
// this should never happen.
MS_THROW_ERROR("DtlsTransport instance creation failed");
}
DtlsTransport::~DtlsTransport()
{
MS_TRACE();
if (IsRunning())
{
// Send close alert to the peer.
SSL_shutdown(this->ssl);
SendPendingOutgoingDtlsData();
}
if (this->ssl)
{
SSL_free(this->ssl);
this->ssl = nullptr;
this->sslBioFromNetwork = nullptr;
this->sslBioToNetwork = nullptr;
}
// Close the DTLS timer.
this->timer = nullptr;
}
void DtlsTransport::Dump() const
{
MS_TRACE();
std::string state{ "new" };
std::string role{ "none " };
switch (this->state)
{
case DtlsState::CONNECTING:
state = "connecting";
break;
case DtlsState::CONNECTED:
state = "connected";
break;
case DtlsState::FAILED:
state = "failed";
break;
case DtlsState::CLOSED:
state = "closed";
break;
default:;
}
switch (this->localRole)
{
case Role::AUTO:
role = "auto";
break;
case Role::SERVER:
role = "server";
break;
case Role::CLIENT:
role = "client";
break;
default:;
}
MS_DUMP("<DtlsTransport>");
MS_DUMP(" state : %s", state.c_str());
MS_DUMP(" role : %s", role.c_str());
MS_DUMP(" handshake done: : %s", this->handshakeDone ? "yes" : "no");
MS_DUMP("</DtlsTransport>");
}
void DtlsTransport::Run(Role localRole)
{
MS_TRACE();
MS_ASSERT(
localRole == Role::CLIENT || localRole == Role::SERVER,
"local DTLS role must be 'client' or 'server'");
Role previousLocalRole = this->localRole;
if (localRole == previousLocalRole)
{
MS_ERROR("same local DTLS role provided, doing nothing");
return;
}
// If the previous local DTLS role was 'client' or 'server' do reset.
if (previousLocalRole == Role::CLIENT || previousLocalRole == Role::SERVER)
{
MS_DEBUG_TAG(dtls, "resetting DTLS due to local role change");
Reset();
}
// Update local role.
this->localRole = localRole;
// Set state and notify the listener.
this->state = DtlsState::CONNECTING;
this->listener->OnDtlsTransportConnecting(this);
switch (this->localRole)
{
case Role::CLIENT:
{
MS_DEBUG_TAG(dtls, "running [role:client]");
SSL_set_connect_state(this->ssl);
SSL_do_handshake(this->ssl);
SendPendingOutgoingDtlsData();
SetTimeout();
break;
}
case Role::SERVER:
{
MS_DEBUG_TAG(dtls, "running [role:server]");
SSL_set_accept_state(this->ssl);
SSL_do_handshake(this->ssl);
break;
}
default:
{
MS_ABORT("invalid local DTLS role");
}
}
}
bool DtlsTransport::SetRemoteFingerprint(Fingerprint fingerprint)
{
MS_TRACE();
MS_ASSERT(
fingerprint.algorithm != FingerprintAlgorithm::NONE, "no fingerprint algorithm provided");
this->remoteFingerprint = fingerprint;
// The remote fingerpring may have been set after DTLS handshake was done,
// so we may need to process it now.
if (this->handshakeDone && this->state != DtlsState::CONNECTED)
{
MS_DEBUG_TAG(dtls, "handshake already done, processing it right now");
return ProcessHandshake();
}
return true;
}
void DtlsTransport::ProcessDtlsData(const uint8_t* data, size_t len)
{
MS_TRACE();
int written;
int read;
if (!IsRunning())
{
MS_ERROR("cannot process data while not running");
return;
}
// Write the received DTLS data into the sslBioFromNetwork.
written =
BIO_write(this->sslBioFromNetwork, static_cast<const void*>(data), static_cast<int>(len));
if (written != static_cast<int>(len))
{
MS_WARN_TAG(
dtls,
"OpenSSL BIO_write() wrote less (%zu bytes) than given data (%zu bytes)",
static_cast<size_t>(written),
len);
}
// Must call SSL_read() to process received DTLS data.
read = SSL_read(this->ssl, static_cast<void*>(DtlsTransport::sslReadBuffer), SslReadBufferSize);
// Send data if it's ready.
SendPendingOutgoingDtlsData();
// Check SSL status and return if it is bad/closed.
if (!CheckStatus(read))
return;
// Set/update the DTLS timeout.
if (!SetTimeout())
return;
// Application data received. Notify to the listener.
if (read > 0)
{
// It is allowed to receive DTLS data even before validating remote fingerprint.
if (!this->handshakeDone)
{
MS_WARN_TAG(dtls, "ignoring application data received while DTLS handshake not done");
return;
}
// Notify the listener.
this->listener->OnDtlsTransportApplicationDataReceived(
this, (uint8_t*)DtlsTransport::sslReadBuffer, static_cast<size_t>(read));
}
}
void DtlsTransport::SendApplicationData(const uint8_t* data, size_t len)
{
MS_TRACE();
// We cannot send data to the peer if its remote fingerprint is not validated.
if (this->state != DtlsState::CONNECTED)
{
MS_WARN_TAG(dtls, "cannot send application data while DTLS is not fully connected");
return;
}
if (len == 0)
{
MS_WARN_TAG(dtls, "ignoring 0 length data");
return;
}
int written;
written = SSL_write(this->ssl, static_cast<const void*>(data), static_cast<int>(len));
if (written < 0)
{
LOG_OPENSSL_ERROR("SSL_write() failed");
if (!CheckStatus(written))
return;
}
else if (written != static_cast<int>(len))
{
MS_WARN_TAG(
dtls, "OpenSSL SSL_write() wrote less (%d bytes) than given data (%zu bytes)", written, len);
}
// Send data.
SendPendingOutgoingDtlsData();
}
void DtlsTransport::Reset()
{
MS_TRACE();
int ret;
if (!IsRunning())
return;
MS_WARN_TAG(dtls, "resetting DTLS transport");
// Stop the DTLS timer.
this->timer = nullptr;
// We need to reset the SSL instance so we need to "shutdown" it, but we
// don't want to send a Close Alert to the peer, so just don't call
// SendPendingOutgoingDTLSData().
SSL_shutdown(this->ssl);
this->localRole = Role::NONE;
this->state = DtlsState::NEW;
this->handshakeDone = false;
this->handshakeDoneNow = false;
// Reset SSL status.
// NOTE: For this to properly work, SSL_shutdown() must be called before.
// NOTE: This may fail if not enough DTLS handshake data has been received,
// but we don't care so just clear the error queue.
ret = SSL_clear(this->ssl);
if (ret == 0)
ERR_clear_error();
}
inline bool DtlsTransport::CheckStatus(int returnCode)
{
MS_TRACE();
int err;
bool wasHandshakeDone = this->handshakeDone;
err = SSL_get_error(this->ssl, returnCode);
switch (err)
{
case SSL_ERROR_NONE:
break;
case SSL_ERROR_SSL:
LOG_OPENSSL_ERROR("SSL status: SSL_ERROR_SSL");
break;
case SSL_ERROR_WANT_READ:
break;
case SSL_ERROR_WANT_WRITE:
MS_WARN_TAG(dtls, "SSL status: SSL_ERROR_WANT_WRITE");
break;
case SSL_ERROR_WANT_X509_LOOKUP:
MS_DEBUG_TAG(dtls, "SSL status: SSL_ERROR_WANT_X509_LOOKUP");
break;
case SSL_ERROR_SYSCALL:
LOG_OPENSSL_ERROR("SSL status: SSL_ERROR_SYSCALL");
break;
case SSL_ERROR_ZERO_RETURN:
break;
case SSL_ERROR_WANT_CONNECT:
MS_WARN_TAG(dtls, "SSL status: SSL_ERROR_WANT_CONNECT");
break;
case SSL_ERROR_WANT_ACCEPT:
MS_WARN_TAG(dtls, "SSL status: SSL_ERROR_WANT_ACCEPT");
break;
default:
MS_WARN_TAG(dtls, "SSL status: unknown error");
}
// Check if the handshake (or re-handshake) has been done right now.
if (this->handshakeDoneNow)
{
this->handshakeDoneNow = false;
this->handshakeDone = true;
// Stop the timer.
this->timer = nullptr;
// Process the handshake just once (ignore if DTLS renegotiation).
if (!wasHandshakeDone && this->remoteFingerprint.algorithm != FingerprintAlgorithm::NONE)
return ProcessHandshake();
return true;
}
// Check if the peer sent close alert or a fatal error happened.
else if (((SSL_get_shutdown(this->ssl) & SSL_RECEIVED_SHUTDOWN) != 0) || err == SSL_ERROR_SSL || err == SSL_ERROR_SYSCALL)
{
if (this->state == DtlsState::CONNECTED)
{
MS_DEBUG_TAG(dtls, "disconnected");
Reset();
// Set state and notify the listener.
this->state = DtlsState::CLOSED;
this->listener->OnDtlsTransportClosed(this);
}
else
{
MS_WARN_TAG(dtls, "connection failed");
Reset();
// Set state and notify the listener.
this->state = DtlsState::FAILED;
this->listener->OnDtlsTransportFailed(this);
}
return false;
}
else
{
return true;
}
}
inline void DtlsTransport::SendPendingOutgoingDtlsData()
{
MS_TRACE();
if (BIO_eof(this->sslBioToNetwork))
return;
int64_t read;
char* data{ nullptr };
read = BIO_get_mem_data(this->sslBioToNetwork, &data); // NOLINT
if (read <= 0)
return;
MS_DEBUG_DEV("%" PRIu64 " bytes of DTLS data ready to sent to the peer", read);
// Notify the listener.
this->listener->OnDtlsTransportSendData(
this, reinterpret_cast<uint8_t*>(data), static_cast<size_t>(read));
// Clear the BIO buffer.
// NOTE: the (void) avoids the -Wunused-value warning.
(void)BIO_reset(this->sslBioToNetwork);
}
inline bool DtlsTransport::SetTimeout()
{
MS_TRACE();
MS_ASSERT(
this->state == DtlsState::CONNECTING || this->state == DtlsState::CONNECTED,
"invalid DTLS state");
int64_t ret;
struct timeval dtlsTimeout{ 0, 0 };
uint64_t timeoutMs;
// NOTE: If ret == 0 then ignore the value in dtlsTimeout.
// NOTE: No DTLSv_1_2_get_timeout() or DTLS_get_timeout() in OpenSSL 1.1.0-dev.
ret = DTLSv1_get_timeout(this->ssl, static_cast<void*>(&dtlsTimeout)); // NOLINT
if (ret == 0)
return true;
timeoutMs = (dtlsTimeout.tv_sec * static_cast<uint64_t>(1000)) + (dtlsTimeout.tv_usec / 1000);
if (timeoutMs == 0)
{
return true;
}
else if (timeoutMs < 30000)
{
MS_DEBUG_DEV("DTLS timer set in %" PRIu64 "ms", timeoutMs);
weak_ptr<DtlsTransport> weak_self = shared_from_this();
this->timer = std::make_shared<Timer>(timeoutMs / 1000.0f, [weak_self](){
auto strong_self = weak_self.lock();
if(strong_self){
strong_self->OnTimer();
}
return true;
}, this->poller);
return true;
}
// NOTE: Don't start the timer again if the timeout is greater than 30 seconds.
else
{
MS_WARN_TAG(dtls, "DTLS timeout too high (%" PRIu64 "ms), resetting DLTS", timeoutMs);
Reset();
// Set state and notify the listener.
this->state = DtlsState::FAILED;
this->listener->OnDtlsTransportFailed(this);
return false;
}
}
inline bool DtlsTransport::ProcessHandshake()
{
MS_TRACE();
MS_ASSERT(this->handshakeDone, "handshake not done yet");
MS_ASSERT(
this->remoteFingerprint.algorithm != FingerprintAlgorithm::NONE, "remote fingerprint not set");
// Validate the remote fingerprint.
if (!CheckRemoteFingerprint())
{
Reset();
// Set state and notify the listener.
this->state = DtlsState::FAILED;
this->listener->OnDtlsTransportFailed(this);
return false;
}
// Get the negotiated SRTP crypto suite.
RTC::SrtpSession::CryptoSuite srtpCryptoSuite = GetNegotiatedSrtpCryptoSuite();
if (srtpCryptoSuite != RTC::SrtpSession::CryptoSuite::NONE)
{
// Extract the SRTP keys (will notify the listener with them).
ExtractSrtpKeys(srtpCryptoSuite);
return true;
}
// NOTE: We assume that "use_srtp" DTLS extension is required even if
// there is no audio/video.
MS_WARN_2TAGS(dtls, srtp, "SRTP crypto suite not negotiated");
Reset();
// Set state and notify the listener.
this->state = DtlsState::FAILED;
this->listener->OnDtlsTransportFailed(this);
return false;
}
inline bool DtlsTransport::CheckRemoteFingerprint()
{
MS_TRACE();
MS_ASSERT(
this->remoteFingerprint.algorithm != FingerprintAlgorithm::NONE, "remote fingerprint not set");
X509* certificate;
uint8_t binaryFingerprint[EVP_MAX_MD_SIZE];
unsigned int size{ 0 };
char hexFingerprint[(EVP_MAX_MD_SIZE * 3) + 1];
const EVP_MD* hashFunction;
int ret;
certificate = SSL_get_peer_certificate(this->ssl);
if (!certificate)
{
MS_WARN_TAG(dtls, "no certificate was provided by the peer");
return false;
}
switch (this->remoteFingerprint.algorithm)
{
case FingerprintAlgorithm::SHA1:
hashFunction = EVP_sha1();
break;
case FingerprintAlgorithm::SHA224:
hashFunction = EVP_sha224();
break;
case FingerprintAlgorithm::SHA256:
hashFunction = EVP_sha256();
break;
case FingerprintAlgorithm::SHA384:
hashFunction = EVP_sha384();
break;
case FingerprintAlgorithm::SHA512:
hashFunction = EVP_sha512();
break;
default:
MS_ABORT("unknown algorithm");
}
// Compare the remote fingerprint with the value given via signaling.
ret = X509_digest(certificate, hashFunction, binaryFingerprint, &size);
if (ret == 0)
{
MS_ERROR("X509_digest() failed");
X509_free(certificate);
return false;
}
// Convert to hexadecimal format in uppercase with colons.
for (unsigned int i{ 0 }; i < size; ++i)
{
std::sprintf(hexFingerprint + (i * 3), "%.2X:", binaryFingerprint[i]);
}
hexFingerprint[(size * 3) - 1] = '\0';
if (this->remoteFingerprint.value != hexFingerprint)
{
MS_WARN_TAG(
dtls,
"fingerprint in the remote certificate (%s) does not match the announced one (%s)",
hexFingerprint,
this->remoteFingerprint.value.c_str());
X509_free(certificate);
return false;
}
MS_DEBUG_TAG(dtls, "valid remote fingerprint");
// Get the remote certificate in PEM format.
BIO* bio = BIO_new(BIO_s_mem());
// Ensure the underlying BUF_MEM structure is also freed.
// NOTE: Avoid stupid "warning: value computed is not used [-Wunused-value]" since
// BIO_set_close() always returns 1.
(void)BIO_set_close(bio, BIO_CLOSE);
ret = PEM_write_bio_X509(bio, certificate);
if (ret != 1)
{
LOG_OPENSSL_ERROR("PEM_write_bio_X509() failed");
X509_free(certificate);
BIO_free(bio);
return false;
}
BUF_MEM* mem;
BIO_get_mem_ptr(bio, &mem); // NOLINT[cppcoreguidelines-pro-type-cstyle-cast]
if (!mem || !mem->data || mem->length == 0u)
{
LOG_OPENSSL_ERROR("BIO_get_mem_ptr() failed");
X509_free(certificate);
BIO_free(bio);
return false;
}
this->remoteCert = std::string(mem->data, mem->length);
X509_free(certificate);
BIO_free(bio);
return true;
}
inline void DtlsTransport::ExtractSrtpKeys(RTC::SrtpSession::CryptoSuite srtpCryptoSuite)
{
MS_TRACE();
size_t srtpKeyLength{ 0 };
size_t srtpSaltLength{ 0 };
size_t srtpMasterLength{ 0 };
switch (srtpCryptoSuite)
{
case RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80:
case RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32:
{
srtpKeyLength = SrtpMasterKeyLength;
srtpSaltLength = SrtpMasterSaltLength;
srtpMasterLength = SrtpMasterLength;
break;
}
case RTC::SrtpSession::CryptoSuite::AEAD_AES_256_GCM:
{
srtpKeyLength = SrtpAesGcm256MasterKeyLength;
srtpSaltLength = SrtpAesGcm256MasterSaltLength;
srtpMasterLength = SrtpAesGcm256MasterLength;
break;
}
case RTC::SrtpSession::CryptoSuite::AEAD_AES_128_GCM:
{
srtpKeyLength = SrtpAesGcm128MasterKeyLength;
srtpSaltLength = SrtpAesGcm128MasterSaltLength;
srtpMasterLength = SrtpAesGcm128MasterLength;
break;
}
default:
{
MS_ABORT("unknown SRTP crypto suite");
}
}
auto* srtpMaterial = new uint8_t[srtpMasterLength * 2];
uint8_t* srtpLocalKey{ nullptr };
uint8_t* srtpLocalSalt{ nullptr };
uint8_t* srtpRemoteKey{ nullptr };
uint8_t* srtpRemoteSalt{ nullptr };
auto* srtpLocalMasterKey = new uint8_t[srtpMasterLength];
auto* srtpRemoteMasterKey = new uint8_t[srtpMasterLength];
int ret;
ret = SSL_export_keying_material(
this->ssl, srtpMaterial, srtpMasterLength * 2, "EXTRACTOR-dtls_srtp", 19, nullptr, 0, 0);
MS_ASSERT(ret != 0, "SSL_export_keying_material() failed");
switch (this->localRole)
{
case Role::SERVER:
{
srtpRemoteKey = srtpMaterial;
srtpLocalKey = srtpRemoteKey + srtpKeyLength;
srtpRemoteSalt = srtpLocalKey + srtpKeyLength;
srtpLocalSalt = srtpRemoteSalt + srtpSaltLength;
break;
}
case Role::CLIENT:
{
srtpLocalKey = srtpMaterial;
srtpRemoteKey = srtpLocalKey + srtpKeyLength;
srtpLocalSalt = srtpRemoteKey + srtpKeyLength;
srtpRemoteSalt = srtpLocalSalt + srtpSaltLength;
break;
}
default:
{
MS_ABORT("no DTLS role set");
}
}
// Create the SRTP local master key.
std::memcpy(srtpLocalMasterKey, srtpLocalKey, srtpKeyLength);
std::memcpy(srtpLocalMasterKey + srtpKeyLength, srtpLocalSalt, srtpSaltLength);
// Create the SRTP remote master key.
std::memcpy(srtpRemoteMasterKey, srtpRemoteKey, srtpKeyLength);
std::memcpy(srtpRemoteMasterKey + srtpKeyLength, srtpRemoteSalt, srtpSaltLength);
// Set state and notify the listener.
this->state = DtlsState::CONNECTED;
this->listener->OnDtlsTransportConnected(
this,
srtpCryptoSuite,
srtpLocalMasterKey,
srtpMasterLength,
srtpRemoteMasterKey,
srtpMasterLength,
this->remoteCert);
delete[] srtpMaterial;
delete[] srtpLocalMasterKey;
delete[] srtpRemoteMasterKey;
}
inline RTC::SrtpSession::CryptoSuite DtlsTransport::GetNegotiatedSrtpCryptoSuite()
{
MS_TRACE();
RTC::SrtpSession::CryptoSuite negotiatedSrtpCryptoSuite = RTC::SrtpSession::CryptoSuite::NONE;
// Ensure that the SRTP crypto suite has been negotiated.
// NOTE: This is a OpenSSL type.
SRTP_PROTECTION_PROFILE* sslSrtpCryptoSuite = SSL_get_selected_srtp_profile(this->ssl);
if (!sslSrtpCryptoSuite)
return negotiatedSrtpCryptoSuite;
// Get the negotiated SRTP crypto suite.
for (auto& srtpCryptoSuite : DtlsTransport::srtpCryptoSuites)
{
SrtpCryptoSuiteMapEntry* cryptoSuiteEntry = std::addressof(srtpCryptoSuite);
if (std::strcmp(sslSrtpCryptoSuite->name, cryptoSuiteEntry->name) == 0)
{
MS_DEBUG_2TAGS(dtls, srtp, "chosen SRTP crypto suite: %s", cryptoSuiteEntry->name);
negotiatedSrtpCryptoSuite = cryptoSuiteEntry->cryptoSuite;
}
}
MS_ASSERT(
negotiatedSrtpCryptoSuite != RTC::SrtpSession::CryptoSuite::NONE,
"chosen SRTP crypto suite is not an available one");
return negotiatedSrtpCryptoSuite;
}
inline void DtlsTransport::OnSslInfo(int where, int ret)
{
MS_TRACE();
int w = where & -SSL_ST_MASK;
const char* role;
if ((w & SSL_ST_CONNECT) != 0)
role = "client";
else if ((w & SSL_ST_ACCEPT) != 0)
role = "server";
else
role = "undefined";
if ((where & SSL_CB_LOOP) != 0)
{
MS_DEBUG_TAG(dtls, "[role:%s, action:'%s']", role, SSL_state_string_long(this->ssl));
}
else if ((where & SSL_CB_ALERT) != 0)
{
const char* alertType;
switch (*SSL_alert_type_string(ret))
{
case 'W':
alertType = "warning";
break;
case 'F':
alertType = "fatal";
break;
default:
alertType = "undefined";
}
if ((where & SSL_CB_READ) != 0)
{
MS_WARN_TAG(dtls, "received DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret));
}
else if ((where & SSL_CB_WRITE) != 0)
{
MS_DEBUG_TAG(dtls, "sending DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret));
}
else
{
MS_DEBUG_TAG(dtls, "DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret));
}
}
else if ((where & SSL_CB_EXIT) != 0)
{
if (ret == 0)
MS_DEBUG_TAG(dtls, "[role:%s, failed:'%s']", role, SSL_state_string_long(this->ssl));
else if (ret < 0)
MS_DEBUG_TAG(dtls, "role: %s, waiting:'%s']", role, SSL_state_string_long(this->ssl));
}
else if ((where & SSL_CB_HANDSHAKE_START) != 0)
{
MS_DEBUG_TAG(dtls, "DTLS handshake start");
}
else if ((where & SSL_CB_HANDSHAKE_DONE) != 0)
{
MS_DEBUG_TAG(dtls, "DTLS handshake done");
this->handshakeDoneNow = true;
}
// NOTE: checking SSL_get_shutdown(this->ssl) & SSL_RECEIVED_SHUTDOWN here upon
// receipt of a close alert does not work (the flag is set after this callback).
}
inline void DtlsTransport::OnTimer()
{
MS_TRACE();
// Workaround for https://github.com/openssl/openssl/issues/7998.
if (this->handshakeDone)
{
MS_DEBUG_DEV("handshake is done so return");
return;
}
DTLSv1_handle_timeout(this->ssl);
// If required, send DTLS data.
SendPendingOutgoingDtlsData();
// Set the DTLS timer again.
SetTimeout();
}
} // namespace RTC
/**
ISC License
Copyright © 2015, Iñaki Baz Castillo <ibc@aliax.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef MS_RTC_DTLS_TRANSPORT_HPP
#define MS_RTC_DTLS_TRANSPORT_HPP
#include "SrtpSession.hpp"
#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <map>
#include <string>
#include <vector>
#include "Poller/Timer.h"
#include "Poller/EventPoller.h"
using namespace toolkit;
namespace RTC
{
class DtlsTransport : public std::enable_shared_from_this<DtlsTransport>
{
public:
enum class DtlsState
{
NEW = 1,
CONNECTING,
CONNECTED,
FAILED,
CLOSED
};
public:
enum class Role
{
NONE = 0,
AUTO = 1,
CLIENT,
SERVER
};
public:
enum class FingerprintAlgorithm
{
NONE = 0,
SHA1 = 1,
SHA224,
SHA256,
SHA384,
SHA512
};
public:
struct Fingerprint
{
FingerprintAlgorithm algorithm{ FingerprintAlgorithm::NONE };
std::string value;
};
private:
struct SrtpCryptoSuiteMapEntry
{
RTC::SrtpSession::CryptoSuite cryptoSuite;
const char* name;
};
class DtlsEnvironment : public std::enable_shared_from_this<DtlsEnvironment>
{
public:
using Ptr = std::shared_ptr<DtlsEnvironment>;
~DtlsEnvironment();
static DtlsEnvironment& Instance();
private:
DtlsEnvironment();
void GenerateCertificateAndPrivateKey();
void ReadCertificateAndPrivateKeyFromFiles();
void CreateSslCtx();
void GenerateFingerprints();
public:
X509* certificate{ nullptr };
EVP_PKEY* privateKey{ nullptr };
SSL_CTX* sslCtx{ nullptr };
std::vector<Fingerprint> localFingerprints;
};
public:
class Listener
{
public:
// DTLS is in the process of negotiating a secure connection. Incoming
// media can flow through.
// NOTE: The caller MUST NOT call any method during this callback.
virtual void OnDtlsTransportConnecting(const RTC::DtlsTransport* dtlsTransport) = 0;
// DTLS has completed negotiation of a secure connection (including DTLS-SRTP
// and remote fingerprint verification). Outgoing media can now flow through.
// NOTE: The caller MUST NOT call any method during this callback.
virtual void OnDtlsTransportConnected(
const RTC::DtlsTransport* dtlsTransport,
RTC::SrtpSession::CryptoSuite srtpCryptoSuite,
uint8_t* srtpLocalKey,
size_t srtpLocalKeyLen,
uint8_t* srtpRemoteKey,
size_t srtpRemoteKeyLen,
std::string& remoteCert) = 0;
// The DTLS connection has been closed as the result of an error (such as a
// DTLS alert or a failure to validate the remote fingerprint).
virtual void OnDtlsTransportFailed(const RTC::DtlsTransport* dtlsTransport) = 0;
// The DTLS connection has been closed due to receipt of a close_notify alert.
virtual void OnDtlsTransportClosed(const RTC::DtlsTransport* dtlsTransport) = 0;
// Need to send DTLS data to the peer.
virtual void OnDtlsTransportSendData(
const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) = 0;
// DTLS application data received.
virtual void OnDtlsTransportApplicationDataReceived(
const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) = 0;
};
public:
static Role StringToRole(const std::string& role)
{
auto it = DtlsTransport::string2Role.find(role);
if (it != DtlsTransport::string2Role.end())
return it->second;
else
return DtlsTransport::Role::NONE;
}
static FingerprintAlgorithm GetFingerprintAlgorithm(const std::string& fingerprint)
{
auto it = DtlsTransport::string2FingerprintAlgorithm.find(fingerprint);
if (it != DtlsTransport::string2FingerprintAlgorithm.end())
return it->second;
else
return DtlsTransport::FingerprintAlgorithm::NONE;
}
static std::string& GetFingerprintAlgorithmString(FingerprintAlgorithm fingerprint)
{
auto it = DtlsTransport::fingerprintAlgorithm2String.find(fingerprint);
return it->second;
}
static bool IsDtls(const uint8_t* data, size_t len)
{
// clang-format off
return (
// Minimum DTLS record length is 13 bytes.
(len >= 13) &&
// DOC: https://tools.ietf.org/html/draft-ietf-avtcore-rfc5764-mux-fixes
(data[0] > 19 && data[0] < 64)
);
// clang-format on
}
private:
static std::map<std::string, Role> string2Role;
static std::map<std::string, FingerprintAlgorithm> string2FingerprintAlgorithm;
static std::map<FingerprintAlgorithm, std::string> fingerprintAlgorithm2String;
static std::vector<SrtpCryptoSuiteMapEntry> srtpCryptoSuites;
public:
DtlsTransport(EventPoller::Ptr poller, Listener* listener);
~DtlsTransport();
public:
void Dump() const;
void Run(Role localRole);
std::vector<Fingerprint>& GetLocalFingerprints() const
{
return env->localFingerprints;
}
bool SetRemoteFingerprint(Fingerprint fingerprint);
void ProcessDtlsData(const uint8_t* data, size_t len);
DtlsState GetState() const
{
return this->state;
}
Role GetLocalRole() const
{
return this->localRole;
}
void SendApplicationData(const uint8_t* data, size_t len);
private:
bool IsRunning() const
{
switch (this->state)
{
case DtlsState::NEW:
return false;
case DtlsState::CONNECTING:
case DtlsState::CONNECTED:
return true;
case DtlsState::FAILED:
case DtlsState::CLOSED:
return false;
}
// Make GCC 4.9 happy.
return false;
}
void Reset();
bool CheckStatus(int returnCode);
void SendPendingOutgoingDtlsData();
bool SetTimeout();
bool ProcessHandshake();
bool CheckRemoteFingerprint();
void ExtractSrtpKeys(RTC::SrtpSession::CryptoSuite srtpCryptoSuite);
RTC::SrtpSession::CryptoSuite GetNegotiatedSrtpCryptoSuite();
private:
void OnSslInfo(int where, int ret);
void OnTimer();
private:
DtlsEnvironment::Ptr env;
EventPoller::Ptr poller;
// Passed by argument.
Listener* listener{ nullptr };
// Allocated by this.
SSL* ssl{ nullptr };
BIO* sslBioFromNetwork{ nullptr }; // The BIO from which ssl reads.
BIO* sslBioToNetwork{ nullptr }; // The BIO in which ssl writes.
Timer::Ptr timer;
// Others.
DtlsState state{ DtlsState::NEW };
Role localRole{ Role::NONE };
Fingerprint remoteFingerprint;
bool handshakeDone{ false };
bool handshakeDoneNow{ false };
std::string remoteCert;
//最大不超过mtu
static constexpr int SslReadBufferSize{ 2000 };
uint8_t sslReadBuffer[SslReadBufferSize];
};
} // namespace RTC
#endif
/**
ISC License
Copyright © 2015, Iñaki Baz Castillo <ibc@aliax.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#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();
// Add XOR-MAPPED-ADDRESS.
response->SetXorMappedAddress(tuple);
// 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 = std::addressof(*it);
if (memcmp(storedTuple, tuple, sizeof (RTC::TransportTuple)) == 0)
{
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.begin() != this->tuples.end())
{
SetSelectedTuple(std::addressof(*this->tuples.begin()));
}
// 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);
auto* storedTuple = std::addressof(*this->tuples.begin());
// Return the address of the inserted tuple.
return storedTuple;
}
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 (memcmp(selectedTuple, tuple, sizeof (RTC::TransportTuple)) == 0)
return this->selectedTuple;
// Otherwise check other stored tuples.
for (const auto& it : this->tuples)
{
auto* storedTuple = const_cast<RTC::TransportTuple*>(std::addressof(it));
if (memcmp(storedTuple, tuple, sizeof (RTC::TransportTuple)) == 0)
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;
// Notify the listener.
this->listener->OnIceServerSelectedTuple(this, this->selectedTuple);
}
} // namespace RTC
/**
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 "logger.h"
#include "Utils.hpp"
#include <list>
#include <string>
#include <functional>
#include <memory>
using _TransportTuple = struct sockaddr;
namespace RTC
{
using TransportTuple = _TransportTuple;
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() const
{
return 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);
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 };
//最大不超过mtu
static constexpr size_t StunSerializeBufferSize{ 1600 };
uint8_t StunSerializeBuffer[StunSerializeBufferSize];
};
} // namespace RTC
#endif
/*
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
*
* Use of this source code is governed by MIT license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "RtpExt.h"
#include "Sdp.h"
#if defined(_WIN32)
#pragma pack(push, 1)
#endif // defined(_WIN32)
//https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01
//https://tools.ietf.org/html/rfc5285
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | 0xBE | 0xDE | length=3 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID | L=0 | data | ID | L=1 | data...
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// ...data | 0 (pad) | 0 (pad) | ID | L=3 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | data |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
class RtpExtOneByte {
public:
static constexpr uint16_t kMinSize = 1;
size_t getSize() const;
uint8_t getId() const;
void setId(uint8_t id);
uint8_t* getData();
private:
#if __BYTE_ORDER == __BIG_ENDIAN
uint8_t id: 4;
uint8_t len: 4;
#else
uint8_t len: 4;
uint8_t id: 4;
#endif
uint8_t data[1];
} PACKED;
//0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | 0x100 |appbits| length=3 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID | L=0 | ID | L=1 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | data | 0 (pad) | ID | L=4 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | data |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
class RtpExtTwoByte {
public:
static constexpr uint16_t kMinSize = 2;
size_t getSize() const;
uint8_t getId() const;
void setId(uint8_t id);
uint8_t* getData();
private:
uint8_t id;
uint8_t len;
uint8_t data[1];
} PACKED;
#if defined(_WIN32)
#pragma pack(pop)
#endif // defined(_WIN32)
//////////////////////////////////////////////////////////////////
size_t RtpExtOneByte::getSize() const {
return len + 1;
}
uint8_t RtpExtOneByte::getId() const {
return id;
}
void RtpExtOneByte::setId(uint8_t in) {
id = in & 0x0F;
}
uint8_t *RtpExtOneByte::getData() {
return data;
}
//////////////////////////////////////////////////////////////////
size_t RtpExtTwoByte::getSize() const {
return len;
}
uint8_t RtpExtTwoByte::getId() const {
return id;
}
void RtpExtTwoByte::setId(uint8_t in) {
id = in;
}
uint8_t *RtpExtTwoByte::getData() {
return data;
}
//////////////////////////////////////////////////////////////////
static constexpr uint16_t kOneByteHeader = 0xBEDE;
static constexpr uint16_t kTwoByteHeader = 0x1000;
template<typename Type>
static bool isOneByteExt(){
return false;
}
template<>
bool isOneByteExt<RtpExtOneByte>(){
return true;
}
template<typename Type>
void appendExt(map<uint8_t, RtpExt> &ret, uint8_t *ptr, const uint8_t *end) {
while (ptr < end) {
auto ext = reinterpret_cast<Type *>(ptr);
if (ext->getId() == (uint8_t) RtpExtType::padding) {
//padding,忽略
++ptr;
continue;
}
//15类型的rtp ext为保留
CHECK(ext->getId() < (uint8_t) RtpExtType::reserved);
CHECK(reinterpret_cast<uint8_t *>(ext) + Type::kMinSize <= end);
CHECK(ext->getData() + ext->getSize() <= end);
ret.emplace(ext->getId(), RtpExt(ext, isOneByteExt<Type>(), reinterpret_cast<char *>(ext->getData()), ext->getSize()));
ptr += Type::kMinSize + ext->getSize();
}
}
RtpExt::RtpExt(void *ext, bool one_byte_ext, const char *str, size_t size) {
_ext = ext;
_one_byte_ext = one_byte_ext;
_data = str;
_size = size;
}
const char *RtpExt::data() const {
return _data;
}
size_t RtpExt::size() const {
return _size;
}
const char& RtpExt::operator[](size_t pos) const{
CHECK(pos < _size);
return _data[pos];
}
RtpExt::operator std::string() const{
return string(_data, _size);
}
map<uint8_t/*id*/, RtpExt/*data*/> RtpExt::getExtValue(const RtpHeader *header) {
map<uint8_t, RtpExt> ret;
assert(header);
auto ext_size = header->getExtSize();
if (!ext_size) {
return ret;
}
auto reserved = header->getExtReserved();
auto ptr = const_cast<RtpHeader *>(header)->getExtData();
auto end = ptr + ext_size;
if (reserved == kOneByteHeader) {
appendExt<RtpExtOneByte>(ret, ptr, end);
return ret;
}
if ((reserved & 0xFFF0) >> 4 == kTwoByteHeader) {
appendExt<RtpExtTwoByte>(ret, ptr, end);
return ret;
}
return ret;
}
#define RTP_EXT_MAP(XX) \
XX(ssrc_audio_level, "urn:ietf:params:rtp-hdrext:ssrc-audio-level") \
XX(abs_send_time, "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time") \
XX(transport_cc, "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01") \
XX(sdes_mid, "urn:ietf:params:rtp-hdrext:sdes:mid") \
XX(sdes_rtp_stream_id, "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id") \
XX(sdes_repaired_rtp_stream_id, "urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id") \
XX(video_timing, "http://www.webrtc.org/experiments/rtp-hdrext/video-timing") \
XX(color_space, "http://www.webrtc.org/experiments/rtp-hdrext/color-space") \
XX(csrc_audio_level, "urn:ietf:params:rtp-hdrext:csrc-audio-level") \
XX(framemarking, "http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07") \
XX(video_content_type, "http://www.webrtc.org/experiments/rtp-hdrext/video-content-type") \
XX(playout_delay, "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay") \
XX(video_orientation, "urn:3gpp:video-orientation") \
XX(toffset, "urn:ietf:params:rtp-hdrext:toffset") \
XX(encrypt, "urn:ietf:params:rtp-hdrext:encrypt")
#define XX(type, url) {RtpExtType::type , url},
static map<RtpExtType/*id*/, string/*ext*/> s_type_to_url = {RTP_EXT_MAP(XX)};
#undef XX
#define XX(type, url) {url, RtpExtType::type},
static unordered_map<string/*ext*/, RtpExtType/*id*/> s_url_to_type = {RTP_EXT_MAP(XX)};
#undef XX
RtpExtType RtpExt::getExtType(const string &url) {
auto it = s_url_to_type.find(url);
if (it == s_url_to_type.end()) {
throw std::invalid_argument(string("未识别的rtp ext url类型:") + url);
}
return it->second;
}
const string &RtpExt::getExtUrl(RtpExtType type) {
auto it = s_type_to_url.find(type);
if (it == s_type_to_url.end()) {
throw std::invalid_argument(string("未识别的rtp ext类型:") + to_string((int) type));
}
return it->second;
}
const char *RtpExt::getExtName(RtpExtType type) {
#define XX(type, url) case RtpExtType::type: return #type;
switch (type) {
RTP_EXT_MAP(XX)
default: return "unknown ext type";
}
#undef XX
}
string RtpExt::dumpString() const {
_StrPrinter printer;
switch (_type) {
case RtpExtType::ssrc_audio_level : {
bool vad;
printer << "audio level:" << (int) getAudioLevel(&vad) << ", vad:" << vad;
break;
}
case RtpExtType::abs_send_time : {
printer << "abs send time:" << getAbsSendTime();
break;
}
case RtpExtType::transport_cc : {
printer << "twcc seq:" << getTransportCCSeq();
break;
}
case RtpExtType::sdes_mid : {
printer << "sdes mid:" << getSdesMid();
break;
}
case RtpExtType::sdes_rtp_stream_id : {
printer << "rtp stream id:" << getRtpStreamId();
break;
}
case RtpExtType::sdes_repaired_rtp_stream_id : {
printer << "rtp repaired stream id:" << getRepairedRtpStreamId();
break;
}
case RtpExtType::video_timing : {
uint8_t flags;
uint16_t encode_start, encode_finish, packetization_complete, last_pkt_left_pacer, reserved_net0, reserved_net1;
getVideoTiming(flags, encode_start, encode_finish, packetization_complete, last_pkt_left_pacer,
reserved_net0, reserved_net1);
printer << "video timing, flags:" << (int) flags
<< ",encode:" << encode_start << "-" << encode_finish
<< ",packetization_complete:" << packetization_complete
<< ",last_pkt_left_pacer:" << last_pkt_left_pacer
<< ",reserved_net0:" << reserved_net0
<< ",reserved_net1:" << reserved_net1;
break;
}
case RtpExtType::video_content_type : {
printer << "video content type:" << (int)getVideoContentType();
break;
}
case RtpExtType::video_orientation : {
bool camera_bit, flip_bit, first_rotation, second_rotation;
getVideoOrientation(camera_bit, flip_bit, first_rotation, second_rotation);
printer << "video orientation:" << camera_bit << "-" << flip_bit << "-" << first_rotation << "-" << second_rotation;
break;
}
case RtpExtType::playout_delay : {
uint16_t min_delay, max_delay;
getPlayoutDelay(min_delay, max_delay);
printer << "playout delay:" << min_delay << "-" << max_delay;
break;
}
case RtpExtType::toffset : {
printer << "toffset:" << getTransmissionOffset();
break;
}
case RtpExtType::framemarking : {
printer << "framemarking tid:" << (int)getFramemarkingTID();
break;
}
default: {
printer << getExtName(_type) << ", hex:" << hexdump(data(), size());
break;
}
}
return std::move(printer);
}
//https://tools.ietf.org/html/rfc6464
// 0 1
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID | len=0 |V| level |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Figure 1: Sample Audio Level Encoding Using the
// One-Byte Header Format
//
//
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID | len=1 |V| level | 0 (pad) |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Figure 2: Sample Audio Level Encoding Using the
// Two-Byte Header Format
uint8_t RtpExt::getAudioLevel(bool *vad) const{
CHECK(_type == RtpExtType::ssrc_audio_level && size() >= 1);
auto &byte = (*this)[0];
if (vad) {
*vad = byte & 0x80;
}
return byte & 0x7F;
}
//http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
//Wire format: 1-byte extension, 3 bytes of data. total 4 bytes extra per packet (plus shared 4 bytes for all extensions present: 2 byte magic word 0xBEDE, 2 byte # of extensions). Will in practice replace the “toffset” extension so we should see no long term increase in traffic as a result.
//
//Encoding: Timestamp is in seconds, 24 bit 6.18 fixed point, yielding 64s wraparound and 3.8us resolution (one increment for each 477 bytes going out on a 1Gbps interface).
//
//Relation to NTP timestamps: abs_send_time_24 = (ntp_timestamp_64 >> 14) & 0x00ffffff ; NTP timestamp is 32 bits for whole seconds, 32 bits fraction of second.
//
//Notes: Packets are time stamped when going out, preferably close to metal. Intermediate RTP relays (entities possibly altering the stream) should remove the extension or set its own timestamp.
uint32_t RtpExt::getAbsSendTime() const {
CHECK(_type == RtpExtType::abs_send_time && size() >= 3);
uint32_t ret = 0;
ret |= (*this)[0] << 16;
ret |= (*this)[1] << 8;
ret |= (*this)[2];
return ret;
}
//https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | 0xBE | 0xDE | length=1 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID | L=1 |transport-wide sequence number | zero padding |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
uint16_t RtpExt::getTransportCCSeq() const {
CHECK(_type == RtpExtType::transport_cc && size() >= 2);
uint16_t ret;
ret = (*this)[0] << 8;
ret |= (*this)[1];
return ret;
}
//https://tools.ietf.org/html/draft-ietf-avtext-sdes-hdr-ext-07
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID | len | SDES Item text value ... |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
string RtpExt::getSdesMid() const {
CHECK(_type == RtpExtType::sdes_mid && size() >= 1);
return *this;
}
//https://tools.ietf.org/html/draft-ietf-avtext-rid-06
//用于simulcast
//3.1. RTCP 'RtpStreamId' SDES Extension
//
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |RtpStreamId=TBD| length | RtpStreamId ...
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
//
// The RtpStreamId payload is UTF-8 encoded and is not null-terminated.
//
// RFC EDITOR NOTE: Please replace TBD with the assigned SDES
// identifier value.
//3.2. RTCP 'RepairedRtpStreamId' SDES Extension
//
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |Repaired...=TBD| length | RepairRtpStreamId ...
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
//
// The RepairedRtpStreamId payload is UTF-8 encoded and is not null-
// terminated.
//
// RFC EDITOR NOTE: Please replace TBD with the assigned SDES
// identifier value.
string RtpExt::getRtpStreamId() const {
CHECK(_type == RtpExtType::sdes_rtp_stream_id && size() >= 1);
return *this;
}
string RtpExt::getRepairedRtpStreamId() const {
CHECK(_type == RtpExtType::sdes_repaired_rtp_stream_id && size() >= 1);
return *this;
}
//http://www.webrtc.org/experiments/rtp-hdrext/video-timing
//Wire format: 1-byte extension, 13 bytes of data. Total 14 bytes extra per packet (plus 1-3 padding byte in some cases, plus shared 4 bytes for all extensions present: 2 byte magic word 0xBEDE, 2 byte # of extensions).
//
//First byte is a flags field. Defined flags:
//
//0x01 - extension is set due to timer.
//0x02 - extension is set because the frame is larger than usual.
//Both flags may be set at the same time. All remaining 6 bits are reserved and should be ignored.
//
//Next, 6 timestamps are stored as 16-bit values in big-endian order, representing delta from the capture time of a packet in ms. Timestamps are, in order:
//
//Encode start.
//Encode finish.
//Packetization complete.
//Last packet left the pacer.
//Reserved for network.
//Reserved for network (2).
void RtpExt::getVideoTiming(uint8_t &flags,
uint16_t &encode_start,
uint16_t &encode_finish,
uint16_t &packetization_complete,
uint16_t &last_pkt_left_pacer,
uint16_t &reserved_net0,
uint16_t &reserved_net1) const {
CHECK(_type == RtpExtType::video_timing && size() >= 13);
flags = (*this)[0];
encode_start = (*this)[1] << 8 | (*this)[2];
encode_finish = (*this)[3] << 8 | (*this)[4];
packetization_complete = (*this)[5] << 8 | (*this)[6];
last_pkt_left_pacer = (*this)[7] << 8 | (*this)[8];
reserved_net0 = (*this)[9] << 8 | (*this)[10];
reserved_net1 = (*this)[11] << 8 | (*this)[12];
}
//http://www.webrtc.org/experiments/rtp-hdrext/color-space
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID | L = 3 | primaries | transfer | matrix |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |range+chr.sit. |
// +-+-+-+-+-+-+-+-+
//http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
//Values:
//0x00: Unspecified. Default value. Treated the same as an absence of an extension.
//0x01: Screenshare. Video stream is of a screenshare type.
//0x02: 摄像头?
//Notes: Extension shoud be present only in the last packet of key-frames.
// If attached to other packets it should be ignored.
// If extension is absent, Unspecified value is assumed.
uint8_t RtpExt::getVideoContentType() const {
CHECK(_type == RtpExtType::video_content_type && size() >= 1);
return (*this)[0];
}
//http://www.3gpp.org/ftp/Specs/html-info/26114.htm
void RtpExt::getVideoOrientation(bool &camera_bit, bool &flip_bit, bool &first_rotation, bool &second_rotation) const {
CHECK(_type == RtpExtType::video_orientation && size() >= 1);
uint8_t byte = (*this)[0];
camera_bit = (byte & 0x08) >> 3;
flip_bit = (byte & 0x04) >> 2;
first_rotation = (byte & 0x02) >> 1;
second_rotation = byte & 0x01;
}
//http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
//+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//| ID | len=2 | MIN delay | MAX delay |
//+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
void RtpExt::getPlayoutDelay(uint16_t &min_delay, uint16_t &max_delay) const {
CHECK(_type == RtpExtType::playout_delay && size() >= 3);
uint32_t bytes = (*this)[0] << 16 | (*this)[1] << 8 | (*this)[2];
min_delay = (bytes & 0x00FFF000) >> 12;
max_delay = bytes & 0x00000FFF;
}
//urn:ietf:params:rtp-hdrext:toffset
//https://tools.ietf.org/html/rfc5450
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID | len=2 | transmission offset |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
uint32_t RtpExt::getTransmissionOffset() const {
CHECK(_type == RtpExtType::toffset && size() >= 3);
return (*this)[0] << 16 | (*this)[1] << 8 | (*this)[2];
}
//http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID=? | L=2 |S|E|I|D|B| TID | LID | TL0PICIDX |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
uint8_t RtpExt::getFramemarkingTID() const {
CHECK(_type == RtpExtType::framemarking && size() >= 3);
return (*this)[0] & 0x07;
}
void RtpExt::setExtId(uint8_t ext_id) {
assert(ext_id > (int) RtpExtType::padding && ext_id <= (int) RtpExtType::reserved && _ext);
if (_one_byte_ext) {
auto ptr = reinterpret_cast<RtpExtOneByte *>(_ext);
ptr->setId(ext_id);
} else {
auto ptr = reinterpret_cast<RtpExtTwoByte *>(_ext);
ptr->setId(ext_id);
}
}
void RtpExt::clearExt(){
assert(_ext);
if (_one_byte_ext) {
auto ptr = reinterpret_cast<RtpExtOneByte *>(_ext);
memset(ptr, (int) RtpExtType::padding, RtpExtOneByte::kMinSize + ptr->getSize());
} else {
auto ptr = reinterpret_cast<RtpExtTwoByte *>(_ext);
memset(ptr, (int) RtpExtType::padding, RtpExtTwoByte::kMinSize + ptr->getSize());
}
}
void RtpExt::setType(RtpExtType type) {
_type = type;
}
RtpExtType RtpExt::getType() const {
return _type;
}
\ No newline at end of file
/*
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
*
* Use of this source code is governed by MIT 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_RTPEXT_H
#define ZLMEDIAKIT_RTPEXT_H
#include <stdint.h>
#include <map>
#include <string>
#include "Common/macros.h"
#include "Rtsp/Rtsp.h"
using namespace std;
using namespace mediakit;
enum class RtpExtType : uint8_t {
padding = 0,
ssrc_audio_level = 1,
abs_send_time = 2,
transport_cc = 3,
sdes_mid = 4,
sdes_rtp_stream_id = 5,
sdes_repaired_rtp_stream_id = 6,
video_timing = 7,
color_space = 8,
//for firefox
csrc_audio_level = 9,
//svc ?
framemarking = 10,
video_content_type = 11,
playout_delay = 12,
video_orientation = 13,
toffset = 14,
reserved = 15,
// e2e ?
encrypt = reserved
};
class RtcMedia;
//使用次对象的方法前需保证RtpHeader内存未释放
class RtpExt {
public:
template<typename Type>
friend void appendExt(map<uint8_t, RtpExt> &ret, uint8_t *ptr, const uint8_t *end);
~RtpExt() = default;
static map<uint8_t/*id*/, RtpExt/*data*/> getExtValue(const RtpHeader *header);
static RtpExtType getExtType(const string &url);
static const string& getExtUrl(RtpExtType type);
static const char *getExtName(RtpExtType type);
void setType(RtpExtType type);
RtpExtType getType() const;
string dumpString() const;
uint8_t getAudioLevel(bool *vad) const;
uint32_t getAbsSendTime() const;
uint16_t getTransportCCSeq() const;
string getSdesMid() const;
string getRtpStreamId() const;
string getRepairedRtpStreamId() const;
void getVideoTiming(uint8_t &flags,
uint16_t &encode_start,
uint16_t &encode_finish,
uint16_t &packetization_complete,
uint16_t &last_pkt_left_pacer,
uint16_t &reserved_net0,
uint16_t &reserved_net1) const;
uint8_t getVideoContentType() const;
void getVideoOrientation(bool &camera_bit,
bool &flip_bit,
bool &first_rotation,
bool &second_rotation) const;
void getPlayoutDelay(uint16_t &min_delay, uint16_t &max_delay) const;
uint32_t getTransmissionOffset() const;
uint8_t getFramemarkingTID() const;
void setExtId(uint8_t ext_id);
void clearExt();
private:
RtpExt(void *ptr, bool one_byte_ext, const char *str, size_t size);
const char *data() const;
size_t size() const;
const char& operator[](size_t pos) const;
operator std::string() const;
private:
void *_ext = nullptr;
const char *_data;
size_t _size;
bool _one_byte_ext = true;
RtpExtType _type = RtpExtType::padding;
};
#endif //ZLMEDIAKIT_RTPEXT_H
/*
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
*
* Use of this source code is governed by MIT license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#include "Sdp.h"
#include "Rtsp/Rtsp.h"
#include <cinttypes>
using namespace mediakit;
using onCreateSdpItem = function<SdpItem::Ptr(const string &key, const string &value)>;
static map<string, onCreateSdpItem, StrCaseCompare> sdpItemCreator;
template <typename Item>
void registerSdpItem(){
onCreateSdpItem func = [](const string &key, const string &value) {
auto ret = std::make_shared<Item>();
ret->parse(value);
return ret;
};
Item item;
sdpItemCreator.emplace(item.getKey(), std::move(func));
}
class DirectionInterface {
public:
virtual RtpDirection getDirection() const = 0;
};
class SdpDirectionSendonly : public SdpItem, public DirectionInterface{
public:
const char* getKey() const override { return getRtpDirectionString(getDirection());}
RtpDirection getDirection() const override {return RtpDirection::sendonly;}
};
class SdpDirectionRecvonly : public SdpItem, public DirectionInterface{
public:
const char* getKey() const override { return getRtpDirectionString(getDirection());}
RtpDirection getDirection() const override {return RtpDirection::recvonly;}
};
class SdpDirectionSendrecv : public SdpItem, public DirectionInterface{
public:
const char* getKey() const override { return getRtpDirectionString(getDirection());}
RtpDirection getDirection() const override {return RtpDirection::sendrecv;}
};
class SdpDirectionInactive : public SdpItem, public DirectionInterface{
public:
const char* getKey() const override { return getRtpDirectionString(getDirection());}
RtpDirection getDirection() const override {return RtpDirection::inactive;}
};
class DirectionInterfaceImp : public SdpItem, public DirectionInterface{
public:
DirectionInterfaceImp(RtpDirection direct){
direction = direct;
}
const char* getKey() const override { return getRtpDirectionString(getDirection());}
RtpDirection getDirection() const override {return direction;}
private:
RtpDirection direction;
};
static bool registerAllItem(){
registerSdpItem<SdpString<'v'> >();
registerSdpItem<SdpString<'s'> >();
registerSdpItem<SdpString<'i'> >();
registerSdpItem<SdpString<'u'> >();
registerSdpItem<SdpString<'e'> >();
registerSdpItem<SdpString<'p'> >();
registerSdpItem<SdpString<'z'> >();
registerSdpItem<SdpString<'k'> >();
registerSdpItem<SdpString<'r'> >();
registerSdpItem<SdpTime>();
registerSdpItem<SdpOrigin>();
registerSdpItem<SdpConnection>();
registerSdpItem<SdpBandwidth>();
registerSdpItem<SdpMedia>();
registerSdpItem<SdpAttr>();
registerSdpItem<SdpAttrGroup>();
registerSdpItem<SdpAttrMsidSemantic>();
registerSdpItem<SdpAttrRtcp>();
registerSdpItem<SdpAttrIceUfrag>();
registerSdpItem<SdpAttrIcePwd>();
registerSdpItem<SdpAttrIceOption>();
registerSdpItem<SdpAttrFingerprint>();
registerSdpItem<SdpAttrSetup>();
registerSdpItem<SdpAttrMid>();
registerSdpItem<SdpAttrExtmap>();
registerSdpItem<SdpAttrRtpMap>();
registerSdpItem<SdpAttrRtcpFb>();
registerSdpItem<SdpAttrFmtp>();
registerSdpItem<SdpAttrSSRC>();
registerSdpItem<SdpAttrSSRCGroup>();
registerSdpItem<SdpAttrSctpMap>();
registerSdpItem<SdpAttrCandidate>();
registerSdpItem<SdpDirectionSendonly>();
registerSdpItem<SdpDirectionRecvonly>();
registerSdpItem<SdpDirectionSendrecv>();
registerSdpItem<SdpDirectionInactive>();
registerSdpItem<SdpAttrMsid>();
registerSdpItem<SdpAttrExtmapAllowMixed>();
registerSdpItem<SdpAttrRid>();
registerSdpItem<SdpAttrSimulcast>();
return true;
}
static map<string, DtlsRole, StrCaseCompare> dtls_role_map = {
{"active", DtlsRole::active},
{"passive", DtlsRole::passive},
{"actpass", DtlsRole::actpass}
};
DtlsRole getDtlsRole(const string &str) {
auto it = dtls_role_map.find(str);
return it == dtls_role_map.end() ? DtlsRole::invalid : it->second;
}
const char* getDtlsRoleString(DtlsRole role){
switch (role) {
case DtlsRole::active : return "active";
case DtlsRole::passive : return "passive";
case DtlsRole::actpass : return "actpass";
default: return "invalid";
}
}
static map<string, RtpDirection, StrCaseCompare> direction_map = {
{"sendonly", RtpDirection::sendonly},
{"recvonly", RtpDirection::recvonly},
{"sendrecv", RtpDirection::sendrecv},
{"inactive", RtpDirection::inactive}
};
RtpDirection getRtpDirection(const string &str) {
auto it = direction_map.find(str);
return it == direction_map.end() ? RtpDirection::invalid : it->second;
}
const char* getRtpDirectionString(RtpDirection val){
switch (val) {
case RtpDirection::sendonly : return "sendonly";
case RtpDirection::recvonly : return "recvonly";
case RtpDirection::sendrecv : return "sendrecv";
case RtpDirection::inactive : return "inactive";
default: return "invalid";
}
}
//////////////////////////////////////////////////////////////////////////////////////////
string RtcSdpBase::toString() const {
_StrPrinter printer;
for (auto &item : items) {
printer << item->getKey() << "=" << item->toString() << "\r\n";
}
return std::move(printer);
}
RtpDirection RtcSdpBase::getDirection() const{
for (auto &item : items) {
auto attr = dynamic_pointer_cast<SdpAttr>(item);
if (attr) {
auto dir = dynamic_pointer_cast<DirectionInterface>(attr->detail);
if (dir) {
return dir->getDirection();
}
}
}
return RtpDirection::invalid;
}
SdpItem::Ptr RtcSdpBase::getItem(char key_c, const char *attr_key) const {
for (auto item : items) {
string key(1, key_c);
if (strcasecmp(item->getKey(), key.data()) == 0) {
if (!attr_key) {
return item;
}
auto attr = dynamic_pointer_cast<SdpAttr>(item);
if (attr && !strcasecmp(attr->detail->getKey() , attr_key)) {
return attr->detail;
}
}
}
return SdpItem::Ptr();
}
int RtcSdpBase::getVersion() const {
return atoi(getStringItem('v').data());
}
SdpOrigin RtcSdpBase::getOrigin() const {
return getItemClass<SdpOrigin>('o');
}
string RtcSdpBase::getSessionName() const {
return getStringItem('s');
}
string RtcSdpBase::getSessionInfo() const {
return getStringItem('i');
}
SdpTime RtcSdpBase::getSessionTime() const{
return getItemClass<SdpTime>('t');
}
SdpConnection RtcSdpBase::getConnection() const {
return getItemClass<SdpConnection>('c');
}
SdpBandwidth RtcSdpBase::getBandwidth() const {
return getItemClass<SdpBandwidth>('b');
}
string RtcSdpBase::getUri() const {
return getStringItem('u');
}
string RtcSdpBase::getEmail() const {
return getStringItem('e');
}
string RtcSdpBase::getPhone() const {
return getStringItem('p');
}
string RtcSdpBase::getTimeZone() const {
return getStringItem('z');
}
string RtcSdpBase::getEncryptKey() const {
return getStringItem('k');
}
string RtcSdpBase::getRepeatTimes() const {
return getStringItem('r');
}
//////////////////////////////////////////////////////////////////////
void RtcSessionSdp::parse(const string &str) {
static auto flag = registerAllItem();
RtcSdpBase *media = nullptr;
auto lines = split(str, "\n");
for(auto &line : lines){
trim(line);
if(line.size() < 3 || line[1] != '='){
continue;
}
auto key = line.substr(0, 1);
auto value = line.substr(2);
if (!strcasecmp(key.data(), "m")) {
medias.emplace_back(RtcSdpBase());
media = &medias.back();
}
SdpItem::Ptr item;
auto it = sdpItemCreator.find(key);
if (it != sdpItemCreator.end()) {
item = it->second(key, value);
} else {
item = std::make_shared<SdpCommon>(key);
item->parse(value);
}
if (media) {
media->items.push_back(std::move(item));
} else {
items.push_back(std::move(item));
}
}
}
string RtcSessionSdp::toString() const {
_StrPrinter printer;
printer << RtcSdpBase::toString();
for (auto &media : medias) {
printer << media.toString();
}
return std::move(printer);
}
//////////////////////////////////////////////////////////////////////////////////////////
#define SDP_THROW() throw std::invalid_argument(StrPrinter << "解析sdp " << getKey() << " 字段失败:" << str)
void SdpTime::parse(const string &str) {
if (sscanf(str.data(), "%" SCNu64 " %" SCNu64, &start, &stop) != 2) {
SDP_THROW();
}
}
string SdpTime::toString() const {
if (value.empty()) {
value = to_string(start) + " " + to_string(stop);
}
return SdpItem::toString();
}
void SdpOrigin::parse(const string &str) {
auto vec = split(str, " ");
if (vec.size() != 6) {
SDP_THROW();
}
username = vec[0];
session_id = vec[1];
session_version = vec[2];
nettype = vec[3];
addrtype = vec[4];
address = vec[5];
}
string SdpOrigin::toString() const {
if (value.empty()) {
value = username + " " + session_id + " " + session_version + " " + nettype + " " + addrtype + " " + address;
}
return SdpItem::toString();
}
void SdpConnection::parse(const string &str) {
auto vec = split(str, " ");
if (vec.size() != 3) {
SDP_THROW();
}
nettype = vec[0];
addrtype = vec[1];
address = vec[2];
}
string SdpConnection::toString() const {
if (value.empty()) {
value = nettype + " " + addrtype + " " + address;
}
return SdpItem::toString();
}
void SdpBandwidth::parse(const string &str) {
auto vec = split(str, ":");
if (vec.size() != 2) {
SDP_THROW();
}
bwtype = vec[0];
bandwidth = atoi(vec[1].data());
}
string SdpBandwidth::toString() const {
if (value.empty()) {
value = bwtype + ":" + to_string(bandwidth);
}
return SdpItem::toString();
}
void SdpMedia::parse(const string &str) {
auto vec = split(str, " ");
if (vec.size() < 4) {
SDP_THROW();
}
type = getTrackType(vec[0]);
if (type == TrackInvalid) {
SDP_THROW();
}
port = atoi(vec[1].data());
proto = vec[2];
for (size_t i = 3; i < vec.size(); ++i) {
auto pt = atoi(vec[i].data());
if (type != TrackApplication && pt > 0xFF) {
SDP_THROW();
}
fmts.emplace_back(pt);
}
}
string SdpMedia::toString() const {
if (value.empty()) {
value = string(getTrackString(type)) + " " + to_string(port) + " " + proto;
for (auto fmt : fmts) {
value += ' ';
value += to_string(fmt);
}
}
return SdpItem::toString();
}
void SdpAttr::parse(const string &str) {
auto pos = str.find(':');
auto key = pos == string::npos ? str : str.substr(0, pos);
auto value = pos == string::npos ? string() : str.substr(pos + 1);
auto it = sdpItemCreator.find(key);
if (it != sdpItemCreator.end()) {
detail = it->second(key, value);
} else {
detail = std::make_shared<SdpCommon>(key);
detail->parse(value);
}
}
string SdpAttr::toString() const {
if (value.empty()) {
auto detail_value = detail->toString();
if (detail_value.empty()) {
value = detail->getKey();
} else {
value = string(detail->getKey()) + ":" + detail_value;
}
}
return SdpItem::toString();
}
void SdpAttrGroup::parse(const string &str) {
auto vec = split(str, " ");
if (vec.size() < 2) {
SDP_THROW();
}
type = vec[0];
vec.erase(vec.begin());
mids = std::move(vec);
}
string SdpAttrGroup::toString() const {
if (value.empty()) {
value = type;
for (auto mid : mids) {
value += ' ';
value += mid;
}
}
return SdpItem::toString();
}
void SdpAttrMsidSemantic::parse(const string &str) {
auto vec = split(str, " ");
if (vec.size() < 1) {
SDP_THROW();
}
msid = vec[0];
token = vec.size() > 1 ? vec[1] : "";
}
string SdpAttrMsidSemantic::toString() const {
if (value.empty()) {
if (token.empty()) {
value = string(" ") + msid;
} else {
value = string(" ") + msid + " " + token;
}
}
return SdpItem::toString();
}
void SdpAttrRtcp::parse(const string &str) {
auto vec = split(str, " ");
if (vec.size() != 4) {
SDP_THROW();
}
port = atoi(vec[0].data());
nettype = vec[1];
addrtype = vec[2];
address = vec[3];
}
string SdpAttrRtcp::toString() const {
if (value.empty()) {
value = to_string(port) + " " + nettype + " " + addrtype + " " + address;
}
return SdpItem::toString();
}
void SdpAttrIceOption::parse(const string &str){
auto vec = split(str, " ");
for (auto &v : vec) {
if (!strcasecmp(v.data(), "trickle")) {
trickle = true;
continue;
}
if (!strcasecmp(v.data(), "renomination")) {
renomination = true;
continue;
}
}
}
string SdpAttrIceOption::toString() const{
if (value.empty()) {
if (trickle && renomination) {
value = "trickle renomination";
} else if (trickle) {
value = "trickle";
} else if (renomination) {
value = "renomination";
}
}
return value;
}
void SdpAttrFingerprint::parse(const string &str) {
auto vec = split(str, " ");
if (vec.size() != 2) {
SDP_THROW();
}
algorithm = vec[0];
hash = vec[1];
}
string SdpAttrFingerprint::toString() const {
if (value.empty()) {
value = algorithm + " " + hash;
}
return SdpItem::toString();
}
void SdpAttrSetup::parse(const string &str) {
role = getDtlsRole(str);
if (role == DtlsRole::invalid) {
SDP_THROW();
}
}
string SdpAttrSetup::toString() const {
if (value.empty()) {
value = getDtlsRoleString(role);
}
return SdpItem::toString();
}
void SdpAttrExtmap::parse(const string &str) {
char buf[128] = {0};
char direction_buf[32] = {0};
if (sscanf(str.data(), "%" SCNd8 "/%31[^ ] %127s", &id, direction_buf, buf) != 3) {
if (sscanf(str.data(), "%" SCNd8 " %127s", &id, buf) != 2) {
SDP_THROW();
}
direction = RtpDirection::sendrecv;
} else {
direction = getRtpDirection(direction_buf);
}
ext = buf;
}
string SdpAttrExtmap::toString() const {
if (value.empty()) {
if(direction == RtpDirection::invalid || direction == RtpDirection::sendrecv){
value = to_string((int)id) + " " + ext;
} else {
value = to_string((int)id) + "/" + getRtpDirectionString(direction) + " " + ext;
}
}
return SdpItem::toString();
}
void SdpAttrRtpMap::parse(const string &str) {
char buf[32] = {0};
if (sscanf(str.data(), "%" SCNu8 " %31[^/]/%" SCNd32 "/%" SCNd32, &pt, buf, &sample_rate, &channel) != 4) {
if (sscanf(str.data(), "%" SCNu8 " %31[^/]/%" SCNd32, &pt, buf, &sample_rate) != 3) {
SDP_THROW();
}
if (getTrackType(getCodecId(buf)) == TrackAudio) {
//未指定通道数时,且为音频时,那么通道数默认为1
channel = 1;
}
}
codec = buf;
}
string SdpAttrRtpMap::toString() const {
if (value.empty()) {
value = to_string(pt) + " " + codec + "/" + to_string(sample_rate);
if (channel) {
value += '/';
value += to_string(channel);
}
}
return SdpItem::toString();
}
void SdpAttrRtcpFb::parse(const string &str_in) {
auto str = str_in + "\n";
char rtcp_type_buf[32] = {0};
if (2 != sscanf(str.data(), "%" SCNu8 " %31[^\n]", &pt, rtcp_type_buf)) {
SDP_THROW();
}
rtcp_type = rtcp_type_buf;
}
string SdpAttrRtcpFb::toString() const {
if (value.empty()) {
value = to_string(pt) + " " + rtcp_type;
}
return SdpItem::toString();
}
void SdpAttrFmtp::parse(const string &str) {
auto pos = str.find(' ');
if (pos == string::npos) {
SDP_THROW();
}
pt = atoi(str.substr(0, pos).data());
auto vec = split(str.substr(pos + 1), ";");
for (auto &item : vec) {
trim(item);
auto pos = item.find('=');
if(pos == string::npos){
fmtp.emplace(std::make_pair(item, ""));
} else {
fmtp.emplace(std::make_pair(item.substr(0, pos), item.substr(pos + 1)));
}
}
if (fmtp.empty()) {
SDP_THROW();
}
}
string SdpAttrFmtp::toString() const {
if (value.empty()) {
value = to_string(pt);
int i = 0;
for (auto &pr : fmtp) {
value += (i++ ? ';' : ' ');
value += pr.first + "=" + pr.second;
}
}
return SdpItem::toString();
}
void SdpAttrSSRC::parse(const string &str_in) {
auto str = str_in + '\n';
char attr_buf[32] = {0};
char attr_val_buf[128] = {0};
if (3 == sscanf(str.data(), "%" SCNu32 " %31[^:]:%127[^\n]", &ssrc, attr_buf, attr_val_buf)) {
attribute = attr_buf;
attribute_value = attr_val_buf;
} else if (2 == sscanf(str.data(), "%" SCNu32 " %31s[^\n]", &ssrc, attr_buf)) {
attribute = attr_buf;
} else {
SDP_THROW();
}
}
string SdpAttrSSRC::toString() const {
if (value.empty()) {
value = to_string(ssrc) + ' ';
value += attribute;
if (!attribute_value.empty()) {
value += ':';
value += attribute_value;
}
}
return SdpItem::toString();
}
void SdpAttrSSRCGroup::parse(const string &str) {
auto vec = split(str, " ");
if (vec.size() >= 3) {
type = std::move(vec[0]);
CHECK(isFID() || isSIM());
vec.erase(vec.begin());
for (auto ssrc : vec) {
ssrcs.emplace_back((uint32_t)atoll(ssrc.data()));
}
} else {
SDP_THROW();
}
}
string SdpAttrSSRCGroup::toString() const {
if (value.empty()) {
value = type;
//最少要求2个ssrc
CHECK(ssrcs.size() >= 2);
for (auto &ssrc : ssrcs) {
value += ' ';
value += to_string(ssrc);
}
}
return SdpItem::toString();
}
void SdpAttrSctpMap::parse(const string &str) {
char subtypes_buf[64] = {0};
if (3 == sscanf(str.data(), "%" SCNu16 " %63[^ ] %" SCNd32, &port, subtypes_buf, &streams)) {
subtypes = subtypes_buf;
} else {
SDP_THROW();
}
}
string SdpAttrSctpMap::toString() const {
if (value.empty()) {
value = to_string(port);
value += ' ';
value += subtypes;
value += ' ';
value += to_string(streams);
}
return SdpItem::toString();
}
void SdpAttrCandidate::parse(const string &str) {
char foundation_buf[40] = {0};
char transport_buf[16] = {0};
char address_buf[32] = {0};
char type_buf[16] = {0};
// https://datatracker.ietf.org/doc/html/rfc5245#section-15.1
if (7 != sscanf(str.data(), "%32[^ ] %" SCNu32 " %15[^ ] %" SCNu32 " %31[^ ] %" SCNu16 " typ %15[^ ]",
foundation_buf, &component, transport_buf, &priority, address_buf, &port, type_buf)) {
SDP_THROW();
}
foundation = foundation_buf;
transport = transport_buf;
address = address_buf;
type = type_buf;
auto pos = str.find(type);
if (pos != string::npos) {
auto remain = str.substr(pos + type.size());
trim(remain);
if (!remain.empty()) {
auto vec = split(remain, " ");
string key;
for (auto &item : vec) {
if (key.empty()) {
key = item;
} else {
arr.emplace_back(std::make_pair(std::move(key), std::move(item)));
}
}
}
}
}
string SdpAttrCandidate::toString() const {
if (value.empty()) {
value = foundation + " " + to_string(component) + " " + transport + " " + to_string(priority) +
" " + address + " " + to_string(port) + " typ " + type;
for (auto &pr : arr) {
value += ' ';
value += pr.first;
value += ' ';
value += pr.second;
}
}
return SdpItem::toString();
}
void SdpAttrSimulcast::parse(const string &str) {
//https://www.meetecho.com/blog/simulcast-janus-ssrc/
//a=simulcast:send/recv q;h;f
//a=simulcast:send/recv [rid=]q;h;f
//a=simulcast: recv h;m;l
//
auto vec = split(str, " ");
if (vec.size() != 2) {
SDP_THROW();
}
direction = vec[0];
rids = split(vec[1], ";");
}
string SdpAttrSimulcast::toString() const {
if (value.empty()) {
value = direction + " ";
bool first = true;
for (auto &rid : rids) {
if (first) {
first = false;
} else {
value += ';';
}
value += rid;
}
}
return SdpItem::toString();
}
void SdpAttrRid::parse(const string &str) {
auto vec = split(str, " ");
CHECK(vec.size() >= 2);
rid = vec[0];
direction = vec[1];
}
string SdpAttrRid::toString() const {
if (value.empty()) {
value = rid + " " + direction;
}
return SdpItem::toString();
}
void RtcSession::loadFrom(const string &str, bool check) {
RtcSessionSdp sdp;
sdp.parse(str);
version = sdp.getVersion();
origin = sdp.getOrigin();
session_name = sdp.getSessionName();
session_info = sdp.getSessionInfo();
connection = sdp.getConnection();
bandwidth = sdp.getBandwidth();
time = sdp.getSessionTime();
msid_semantic = sdp.getItemClass<SdpAttrMsidSemantic>('a', "msid-semantic");
for (auto &media : sdp.medias) {
auto mline = media.getItemClass<SdpMedia>('m');
switch (mline.type) {
case TrackVideo:
case TrackAudio:
case TrackApplication: break;
default: throw std::invalid_argument(StrPrinter << "不识别的media类型:" << mline.toString());
}
this->media.emplace_back();
auto &rtc_media = this->media.back();
rtc_media.type = mline.type;
rtc_media.mid = media.getStringItem('a', "mid");
rtc_media.proto = mline.proto;
rtc_media.type = mline.type;
rtc_media.port = mline.port;
rtc_media.addr = media.getItemClass<SdpConnection>('c');
rtc_media.ice_ufrag = media.getStringItem('a', "ice-ufrag");
rtc_media.ice_pwd = media.getStringItem('a', "ice-pwd");
rtc_media.role = media.getItemClass<SdpAttrSetup>('a', "setup").role;
rtc_media.fingerprint = media.getItemClass<SdpAttrFingerprint>('a', "fingerprint");
if (rtc_media.fingerprint.empty()) {
rtc_media.fingerprint = sdp.getItemClass<SdpAttrFingerprint>('a', "fingerprint");
}
rtc_media.ice_lite = media.getItem('a', "ice-lite").operator bool();
auto ice_options = media.getItemClass<SdpAttrIceOption>('a', "ice-options");
rtc_media.ice_trickle = ice_options.trickle;
rtc_media.ice_renomination = ice_options.renomination;
rtc_media.candidate = media.getAllItem<SdpAttrCandidate>('a', "candidate");
if (mline.type == TrackType::TrackApplication) {
rtc_media.sctp_port = atoi(media.getStringItem('a', "sctp-port").data());
rtc_media.sctpmap = media.getItemClass<SdpAttrSctpMap>('a', "sctpmap");
continue;
}
rtc_media.rtcp_addr = media.getItemClass<SdpAttrRtcp>('a', "rtcp");
rtc_media.direction = media.getDirection();
rtc_media.extmap = media.getAllItem<SdpAttrExtmap>('a', "extmap");
rtc_media.rtcp_mux = media.getItem('a', "rtcp-mux").operator bool();
rtc_media.rtcp_rsize = media.getItem('a', "rtcp-rsize").operator bool();
map<uint32_t, RtcSSRC> rtc_ssrc_map;
auto ssrc_attr = media.getAllItem<SdpAttrSSRC>('a', "ssrc");
for (auto &ssrc : ssrc_attr) {
auto &rtc_ssrc = rtc_ssrc_map[ssrc.ssrc];
rtc_ssrc.ssrc = ssrc.ssrc;
if (!strcasecmp(ssrc.attribute.data(), "cname")) {
rtc_ssrc.cname = ssrc.attribute_value;
continue;
}
if (!strcasecmp(ssrc.attribute.data(), "msid")) {
rtc_ssrc.msid = ssrc.attribute_value;
continue;
}
if (!strcasecmp(ssrc.attribute.data(), "mslabel")) {
rtc_ssrc.mslabel = ssrc.attribute_value;
continue;
}
if (!strcasecmp(ssrc.attribute.data(), "label")) {
rtc_ssrc.label = ssrc.attribute_value;
continue;
}
}
auto ssrc_groups = media.getAllItem<SdpAttrSSRCGroup>('a', "ssrc-group");
SdpAttrSSRCGroup *ssrc_group_sim = nullptr;
SdpAttrSSRCGroup *ssrc_group_fid = nullptr;
for (auto &group : ssrc_groups) {
if (group.isFID()) {
ssrc_group_fid = &group;
} else if (group.isSIM()) {
ssrc_group_sim = &group;
}
}
if (ssrc_group_fid) {
//指定了ssrc-group:FID字段
for (auto ssrc : ssrc_group_fid->ssrcs) {
auto it = rtc_ssrc_map.find(ssrc);
if (it == rtc_ssrc_map.end()) {
throw std::invalid_argument("a=ssrc-group:FID字段指定的ssrc未找到");
}
rtc_media.rtp_rtx_ssrc.emplace_back(it->second);
}
CHECK(rtc_media.rtp_rtx_ssrc.size() == 2);
} else {
auto simulcast = media.getItemClass<SdpAttrSimulcast>('a', "simulcast");
if (simulcast.empty()) {
//没有指定ssrc-group:FID字段,也不是simulcast,那么只有1个或0个ssrc
if (rtc_ssrc_map.size() == 1) {
rtc_media.rtp_rtx_ssrc.emplace_back(rtc_ssrc_map.begin()->second);
} else if (rtc_ssrc_map.size() > 1) {
throw std::invalid_argument("sdp中不存在a=ssrc-group:FID字段,但是ssrc却大于1个");
}
} else {
//开启simulcast
rtc_media.rtp_rids = simulcast.rids;
//simulcast最少要求2种方案
CHECK(rtc_media.rtp_rids.size() >= 2);
}
}
if (ssrc_group_sim) {
//指定了a=ssrc-group:SIM
for (auto ssrc : ssrc_group_sim->ssrcs) {
auto it = rtc_ssrc_map.find(ssrc);
if (it == rtc_ssrc_map.end()) {
throw std::invalid_argument("a=ssrc-group:SIM字段指定的ssrc未找到");
}
rtc_media.rtp_ssrc_sim.emplace_back(it->second);
}
} else if (!rtc_media.rtp_rids.empty()) {
//未指定a=ssrc-group:SIM,但是指定了a=simulcast,且可能指定了ssrc
for (auto &attr : ssrc_attr) {
rtc_media.rtp_ssrc_sim.emplace_back(rtc_ssrc_map[attr.ssrc]);
}
}
auto rtpmap_arr = media.getAllItem<SdpAttrRtpMap>('a', "rtpmap");
auto rtcpfb_arr = media.getAllItem<SdpAttrRtcpFb>('a', "rtcp-fb");
auto fmtp_aar = media.getAllItem<SdpAttrFmtp>('a', "fmtp");
//方便根据pt查找rtpmap,一个pt必有一条
map<uint8_t, SdpAttrRtpMap &> rtpmap_map;
//方便根据pt查找rtcp-fb,一个pt可能有多条或0条
multimap<uint8_t, SdpAttrRtcpFb &> rtcpfb_map;
//方便根据pt查找fmtp,一个pt最多一条
map<uint8_t, SdpAttrFmtp &> fmtp_map;
for (auto &rtpmap : rtpmap_arr) {
if (!rtpmap_map.emplace(rtpmap.pt, rtpmap).second) {
//添加失败,有多条
throw std::invalid_argument(StrPrinter << "该pt存在多条a=rtpmap:" << rtpmap.pt);
}
}
for (auto &rtpfb : rtcpfb_arr) {
rtcpfb_map.emplace(rtpfb.pt, rtpfb);
}
for (auto &fmtp : fmtp_aar) {
if (!fmtp_map.emplace(fmtp.pt, fmtp).second) {
//添加失败,有多条
throw std::invalid_argument(StrPrinter << "该pt存在多条a=fmtp:" << fmtp.pt);
}
}
for (auto &pt : mline.fmts) {
//遍历所有编码方案的pt
rtc_media.plan.emplace_back();
auto &plan = rtc_media.plan.back();
auto rtpmap_it = rtpmap_map.find(pt);
if (rtpmap_it == rtpmap_map.end()) {
plan.pt = pt;
plan.codec = RtpPayload::getName(pt);
plan.sample_rate = RtpPayload::getClockRate(pt);
plan.channel = RtpPayload::getAudioChannel(pt);
} else {
plan.pt = rtpmap_it->second.pt;
plan.codec = rtpmap_it->second.codec;
plan.sample_rate = rtpmap_it->second.sample_rate;
plan.channel = rtpmap_it->second.channel;
}
auto fmtp_it = fmtp_map.find(pt);
if (fmtp_it != fmtp_map.end()) {
plan.fmtp = fmtp_it->second.fmtp;
}
for (auto rtpfb_it = rtcpfb_map.find(pt);
rtpfb_it != rtcpfb_map.end() && rtpfb_it->second.pt == pt; ++rtpfb_it) {
plan.rtcp_fb.emplace(rtpfb_it->second.rtcp_type);
}
}
}
group = sdp.getItemClass<SdpAttrGroup>('a', "group");
if (check) {
checkValid();
}
}
std::shared_ptr<SdpItem> wrapSdpAttr(SdpItem::Ptr item){
auto ret = std::make_shared<SdpAttr>();
ret->detail = std::move(item);
return ret;
}
static void toRtsp(vector <SdpItem::Ptr> &items) {
for (auto it = items.begin(); it != items.end();) {
switch ((*it)->getKey()[0]) {
case 'v':
case 'o':
case 's':
case 'i':
case 't':
case 'c':
case 'b':{
++it;
break;
}
case 'm': {
auto m = dynamic_pointer_cast<SdpMedia>(*it);
CHECK(m);
m->proto = "RTP/AVP";
++it;
break;
}
case 'a': {
auto attr = dynamic_pointer_cast<SdpAttr>(*it);
CHECK(attr);
if (!strcasecmp(attr->detail->getKey(), "rtpmap")
|| !strcasecmp(attr->detail->getKey(), "fmtp")) {
++it;
break;
}
}
default: {
it = items.erase(it);
break;
}
}
}
}
string RtcSession::toRtspSdp() const{
checkValid();
RtcSession copy = *this;
copy.media.clear();
for (auto &m : media) {
switch (m.type) {
case TrackAudio:
case TrackVideo: {
copy.media.emplace_back(m);
copy.media.back().plan.resize(1);
break;
}
default:
continue;
}
}
copy.session_name = "zlmediakit rtsp stream from webrtc";
auto sdp = copy.toRtcSessionSdp();
toRtsp(sdp->items);
int i = 0;
for (auto &m : sdp->medias) {
toRtsp(m.items);
m.items.push_back(wrapSdpAttr(std::make_shared<SdpCommon>("control", string("trackID=") + to_string(i++))));
}
return sdp->toString();
}
RtcSessionSdp::Ptr RtcSession::toRtcSessionSdp() const{
RtcSessionSdp::Ptr ret = std::make_shared<RtcSessionSdp>();
auto &sdp = *ret;
sdp.items.emplace_back(std::make_shared<SdpString<'v'> >(to_string(version)));
sdp.items.emplace_back(std::make_shared<SdpOrigin>(origin));
sdp.items.emplace_back(std::make_shared<SdpString<'s'> >(session_name));
if (!session_info.empty()) {
sdp.items.emplace_back(std::make_shared<SdpString<'i'> >(session_info));
}
sdp.items.emplace_back(std::make_shared<SdpTime>(time));
if(connection.empty()){
sdp.items.emplace_back(std::make_shared<SdpConnection>(connection));
}
if (!bandwidth.empty()) {
sdp.items.emplace_back(std::make_shared<SdpBandwidth>(bandwidth));
}
sdp.items.emplace_back(wrapSdpAttr(std::make_shared<SdpAttrGroup>(group)));
sdp.items.emplace_back(wrapSdpAttr(std::make_shared<SdpAttrMsidSemantic>(msid_semantic)));
for (auto &m : media) {
sdp.medias.emplace_back();
auto &sdp_media = sdp.medias.back();
auto mline = std::make_shared<SdpMedia>();
mline->type = m.type;
mline->port = m.port;
mline->proto = m.proto;
for (auto &p : m.plan) {
mline->fmts.emplace_back(p.pt);
}
if (m.type == TrackApplication) {
mline->fmts.emplace_back(m.sctp_port);
}
sdp_media.items.emplace_back(std::move(mline));
sdp_media.items.emplace_back(std::make_shared<SdpConnection>(m.addr));
if (!m.rtcp_addr.empty()) {
sdp_media.items.emplace_back(wrapSdpAttr(std::make_shared<SdpAttrRtcp>(m.rtcp_addr)));
}
sdp_media.items.emplace_back(wrapSdpAttr(std::make_shared<SdpAttrIceUfrag>(m.ice_ufrag)));
sdp_media.items.emplace_back(wrapSdpAttr(std::make_shared<SdpAttrIcePwd>(m.ice_pwd)));
if (m.ice_trickle || m.ice_renomination) {
auto attr = std::make_shared<SdpAttrIceOption>();
attr->trickle = m.ice_trickle;
attr->renomination = m.ice_renomination;
sdp_media.items.emplace_back(wrapSdpAttr(attr));
}
sdp_media.items.emplace_back(wrapSdpAttr(std::make_shared<SdpAttrFingerprint>(m.fingerprint)));
sdp_media.items.emplace_back(wrapSdpAttr(std::make_shared<SdpAttrSetup>(m.role)));
sdp_media.items.emplace_back(wrapSdpAttr(std::make_shared<SdpAttrMid>(m.mid)));
if (m.ice_lite) {
sdp_media.items.emplace_back(wrapSdpAttr(std::make_shared<SdpCommon>("ice-lite")));
}
for (auto &ext : m.extmap) {
sdp_media.items.emplace_back(wrapSdpAttr(std::make_shared<SdpAttrExtmap>(ext)));
}
if (m.direction != RtpDirection::invalid) {
sdp_media.items.emplace_back(wrapSdpAttr(std::make_shared<DirectionInterfaceImp>(m.direction)));
}
if (m.rtcp_mux) {
sdp_media.items.emplace_back(wrapSdpAttr(std::make_shared<SdpCommon>("rtcp-mux")));
}
if (m.rtcp_rsize) {
sdp_media.items.emplace_back(wrapSdpAttr(std::make_shared<SdpCommon>("rtcp-rsize")));
}
if(m.type != TrackApplication) {
for (auto &p : m.plan) {
auto rtp_map = std::make_shared<SdpAttrRtpMap>();
rtp_map->pt = p.pt;
rtp_map->codec = p.codec;
rtp_map->sample_rate = p.sample_rate;
rtp_map->channel = p.channel;
//添加a=rtpmap
sdp_media.items.emplace_back(wrapSdpAttr(std::move(rtp_map)));
for (auto &fb : p.rtcp_fb) {
auto rtcp_fb = std::make_shared<SdpAttrRtcpFb>();
rtcp_fb->pt = p.pt;
rtcp_fb->rtcp_type = fb;
//添加a=rtcp-fb
sdp_media.items.emplace_back(wrapSdpAttr(std::move(rtcp_fb)));
}
if (!p.fmtp.empty()) {
auto fmtp = std::make_shared<SdpAttrFmtp>();
fmtp->pt = p.pt;
fmtp->fmtp = p.fmtp;
//添加a=fmtp
sdp_media.items.emplace_back(wrapSdpAttr(std::move(fmtp)));
}
}
{
//添加a=msid字段
if (!m.rtp_rtx_ssrc.empty()) {
auto msid = std::make_shared<SdpAttrMsid>();
if (!m.rtp_rtx_ssrc[0].msid.empty()) {
msid->parse(m.rtp_rtx_ssrc[0].msid);
} else {
msid->parse("mslabel label");
}
sdp_media.items.emplace_back(wrapSdpAttr(std::move(msid)));
}
}
static auto addSSRCItem = [](const RtcSSRC &rtp_ssrc, vector<SdpItem::Ptr> &items) {
CHECK(!rtp_ssrc.empty());
SdpAttrSSRC ssrc;
ssrc.ssrc = rtp_ssrc.ssrc;
ssrc.attribute = "cname";
ssrc.attribute_value = rtp_ssrc.cname;
items.emplace_back(wrapSdpAttr(std::make_shared<SdpAttrSSRC>(ssrc)));
if (!rtp_ssrc.msid.empty()) {
ssrc.attribute = "msid";
ssrc.attribute_value = rtp_ssrc.msid;
items.emplace_back(wrapSdpAttr(std::make_shared<SdpAttrSSRC>(ssrc)));
}
if (!rtp_ssrc.mslabel.empty()) {
ssrc.attribute = "mslabel";
ssrc.attribute_value = rtp_ssrc.mslabel;
items.emplace_back(wrapSdpAttr(std::make_shared<SdpAttrSSRC>(ssrc)));
}
if (!rtp_ssrc.label.empty()) {
ssrc.attribute = "label";
ssrc.attribute_value = rtp_ssrc.label;
items.emplace_back(wrapSdpAttr(std::make_shared<SdpAttrSSRC>(ssrc)));
}
};
{
auto group = std::make_shared<SdpAttrSSRCGroup>();
for (auto &ssrc : m.rtp_rtx_ssrc) {
//添加a=ssrc字段
addSSRCItem(ssrc, sdp_media.items);
group->ssrcs.emplace_back(ssrc.ssrc);
}
if (group->ssrcs.size() >= 2) {
group->type = "FID";
//生成a=ssrc-group:FID字段
sdp_media.items.emplace_back(wrapSdpAttr(std::move(group)));
}
}
{
if (m.rtp_ssrc_sim.size() >= 2) {
//simulcast 要求 2~3路
auto group = std::make_shared<SdpAttrSSRCGroup>();
for (auto &ssrc : m.rtp_ssrc_sim) {
//添加simulcast的ssrc
addSSRCItem(ssrc, sdp_media.items);
group->ssrcs.emplace_back(ssrc.ssrc);
}
//添加a=ssrc-group:SIM字段
group->type = "SIM";
sdp_media.items.emplace_back(wrapSdpAttr(std::move(group)));
}
if (m.rtp_rids.size() >= 2) {
auto simulcast = std::make_shared<SdpAttrSimulcast>();
simulcast->direction = "recv";
simulcast->rids = m.rtp_rids;
sdp_media.items.emplace_back(wrapSdpAttr(std::move(simulcast)));
for (auto &rid : m.rtp_rids) {
auto attr_rid = std::make_shared<SdpAttrRid>();
attr_rid->rid = rid;
attr_rid->direction = "recv";
sdp_media.items.emplace_back(wrapSdpAttr(std::move(attr_rid)));
}
}
}
} else {
sdp_media.items.emplace_back(wrapSdpAttr(std::make_shared<SdpAttrSctpMap>(m.sctpmap)));
sdp_media.items.emplace_back(wrapSdpAttr(std::make_shared<SdpCommon>("sctp-port", to_string(m.sctp_port))));
}
for (auto &cand : m.candidate) {
sdp_media.items.emplace_back(wrapSdpAttr(std::make_shared<SdpAttrCandidate>(cand)));
}
}
return ret;
}
string RtcSession::toString() const{
checkValid();
return toRtcSessionSdp()->toString();
}
string RtcCodecPlan::getFmtp(const char *key) const{
for (auto &item : fmtp) {
if (strcasecmp(item.first.data(), key) == 0) {
return item.second;
}
}
return "";
}
const RtcCodecPlan *RtcMedia::getPlan(uint8_t pt) const{
for (auto &item : plan) {
if (item.pt == pt) {
return &item;
}
}
return nullptr;
}
const RtcCodecPlan *RtcMedia::getPlan(const char *codec) const{
for (auto &item : plan) {
if (strcasecmp(item.codec.data(), codec) == 0) {
return &item;
}
}
return nullptr;
}
const RtcCodecPlan *RtcMedia::getRelatedRtxPlan(uint8_t pt) const{
for (auto &item : plan) {
if (strcasecmp(item.codec.data(), "rtx") == 0) {
auto apt = atoi(item.getFmtp("apt").data());
if (pt == apt) {
return &item;
}
}
}
return nullptr;
}
uint32_t RtcMedia::getRtpSSRC() const {
if (rtp_rtx_ssrc.size()) {
return rtp_rtx_ssrc[0].ssrc;
}
return 0;
}
uint32_t RtcMedia::getRtxSSRC() const {
if (rtp_rtx_ssrc.size() > 1) {
return rtp_rtx_ssrc[1].ssrc;
}
return 0;
}
void RtcMedia::checkValid() const{
CHECK(type != TrackInvalid);
CHECK(!mid.empty());
CHECK(!proto.empty());
CHECK(direction != RtpDirection::invalid || type == TrackApplication);
CHECK(!plan.empty() || type == TrackApplication );
}
void RtcMedia::checkValidSSRC() const {
bool send_rtp = (direction == RtpDirection::sendonly || direction == RtpDirection::sendrecv);
if (rtp_rids.empty() && rtp_ssrc_sim.empty()) {
//非simulcast时,检查有没有指定rtp ssrc
CHECK(!rtp_rtx_ssrc.empty() || !send_rtp);
}
#if 0
//todo 发现Firefox(88.0)在mac平台下,开启rtx后没有指定ssrc
auto rtx_plan = getPlan("rtx");
if (rtx_plan) {
//开启rtx后必须指定rtx_ssrc
CHECK(rtp_rtx_ssrc.size() >= 2 || !send_rtp);
}
#endif
}
void RtcSession::checkValid() const{
CHECK(version == 0);
CHECK(!origin.empty());
CHECK(!session_name.empty());
CHECK(!msid_semantic.empty());
CHECK(!media.empty());
CHECK(group.mids.size() <= media.size());
for (auto &item : media) {
item.checkValid();
}
}
void RtcSession::checkValidSSRC() const{
for (auto &item : media) {
item.checkValidSSRC();
}
}
const RtcMedia *RtcSession::getMedia(TrackType type) const{
for(auto &m : media){
if(m.type == type){
return &m;
}
}
return nullptr;
}
bool RtcSession::haveSSRC() const {
for (auto &m : media) {
if (!m.rtp_rtx_ssrc.empty()) {
return true;
}
}
return false;
}
bool RtcSession::supportRtcpFb(const string &name, TrackType type) const {
auto media = getMedia(type);
if (!media) {
return false;
}
auto &ref = media->plan[0].rtcp_fb;
return ref.find(name) != ref.end();
}
string const SdpConst::kTWCCRtcpFb = "transport-cc";
string const SdpConst::kRembRtcpFb = "goog-remb";
void RtcConfigure::RtcTrackConfigure::enableTWCC(bool enable){
if (!enable) {
rtcp_fb.erase(SdpConst::kTWCCRtcpFb);
extmap.erase(RtpExtType::transport_cc);
} else {
rtcp_fb.emplace(SdpConst::kTWCCRtcpFb);
extmap.emplace(RtpExtType::transport_cc);
}
}
void RtcConfigure::RtcTrackConfigure::enableREMB(bool enable){
if (!enable) {
rtcp_fb.erase(SdpConst::kRembRtcpFb);
extmap.erase(RtpExtType::abs_send_time);
} else {
rtcp_fb.emplace(SdpConst::kRembRtcpFb);
extmap.emplace(RtpExtType::abs_send_time);
}
}
void RtcConfigure::RtcTrackConfigure::setDefaultSetting(TrackType type){
enable = true;
rtcp_mux = true;
rtcp_rsize = false;
group_bundle = true;
support_rtx = true;
support_red = false;
support_ulpfec = false;
ice_lite = true;
ice_trickle = true;
ice_renomination = false;
switch (type) {
case TrackAudio: {
//此处调整偏好的编码格式优先级
preferred_codec = {CodecAAC, CodecG711U, CodecG711A, CodecOpus};
rtcp_fb = {SdpConst::kTWCCRtcpFb, SdpConst::kRembRtcpFb};
extmap = {
RtpExtType::ssrc_audio_level,
RtpExtType::csrc_audio_level,
RtpExtType::abs_send_time,
RtpExtType::transport_cc,
//rtx重传rtp时,忽略sdes_mid类型的rtp ext,实测发现Firefox在接收rtx时,如果存在sdes_mid的ext,将导致无法播放
//RtpExtType::sdes_mid,
RtpExtType::sdes_rtp_stream_id,
RtpExtType::sdes_repaired_rtp_stream_id
};
break;
}
case TrackVideo: {
//此处调整偏好的编码格式优先级
preferred_codec = {CodecH264, CodecH265, CodecAV1};
rtcp_fb = {SdpConst::kTWCCRtcpFb, SdpConst::kRembRtcpFb, "nack", "ccm fir", "nack pli"};
extmap = {
RtpExtType::abs_send_time,
RtpExtType::transport_cc,
//rtx重传rtp时,忽略sdes_mid类型的rtp ext,实测发现Firefox在接收rtx时,如果存在sdes_mid的ext,将导致无法播放
//RtpExtType::sdes_mid,
RtpExtType::sdes_rtp_stream_id,
RtpExtType::sdes_repaired_rtp_stream_id,
RtpExtType::video_timing,
RtpExtType::color_space,
RtpExtType::video_content_type,
RtpExtType::playout_delay,
RtpExtType::video_orientation,
RtpExtType::toffset,
RtpExtType::framemarking
};
break;
}
case TrackApplication: {
enable = false;
break;
}
default: break;
}
}
void RtcConfigure::setDefaultSetting(string ice_ufrag,
string ice_pwd,
RtpDirection direction,
const SdpAttrFingerprint &fingerprint) {
video.setDefaultSetting(TrackVideo);
audio.setDefaultSetting(TrackAudio);
application.setDefaultSetting(TrackApplication);
video.ice_ufrag = audio.ice_ufrag = application.ice_ufrag = ice_ufrag;
video.ice_pwd = audio.ice_pwd = application.ice_pwd = ice_pwd;
video.direction = audio.direction = application.direction = direction;
video.fingerprint = audio.fingerprint = application.fingerprint = fingerprint;
}
void RtcConfigure::addCandidate(const SdpAttrCandidate &candidate, TrackType type) {
switch (type) {
case TrackAudio: {
audio.candidate.emplace_back(candidate);
break;
}
case TrackVideo: {
video.candidate.emplace_back(candidate);
break;
}
case TrackApplication: {
application.candidate.emplace_back(candidate);
break;
}
default: {
if (audio.group_bundle) {
audio.candidate.emplace_back(candidate);
}
if (video.group_bundle) {
video.candidate.emplace_back(candidate);
}
if (application.group_bundle) {
application.candidate.emplace_back(candidate);
}
break;
}
}
}
void RtcConfigure::enableTWCC(bool enable, TrackType type){
switch (type) {
case TrackAudio: {
audio.enableTWCC(enable);
break;
}
case TrackVideo: {
video.enableTWCC(enable);
break;
}
case TrackApplication: {
application.enableTWCC(enable);
break;
}
default: {
audio.enableTWCC(enable);
video.enableTWCC(enable);
application.enableTWCC(enable);
break;
}
}
}
void RtcConfigure::enableREMB(bool enable, TrackType type){
switch (type) {
case TrackAudio: {
audio.enableREMB(enable);
break;
}
case TrackVideo: {
video.enableREMB(enable);
break;
}
case TrackApplication: {
application.enableREMB(enable);
break;
}
default: {
audio.enableREMB(enable);
video.enableREMB(enable);
application.enableREMB(enable);
break;
}
}
}
shared_ptr<RtcSession> RtcConfigure::createAnswer(const RtcSession &offer){
shared_ptr<RtcSession> ret = std::make_shared<RtcSession>();
ret->version = offer.version;
//todo 此处设置会话id与会话地址,貌似没什么作用
ret->origin = offer.origin;
ret->session_name = offer.session_name;
ret->msid_semantic = offer.msid_semantic;
matchMedia(ret, TrackAudio, offer.media, audio);
matchMedia(ret, TrackVideo, offer.media, video);
matchMedia(ret, TrackApplication, offer.media, application);
if (ret->media.empty()) {
throw std::invalid_argument("生成的answer sdp中媒体个数为0");
}
//设置音视频端口复用
if (!offer.group.mids.empty()) {
for (auto &m : ret->media) {
ret->group.mids.emplace_back(m.mid);
}
}
return ret;
}
void RtcConfigure::matchMedia(shared_ptr<RtcSession> &ret, TrackType type, const vector<RtcMedia> &medias, const RtcTrackConfigure &configure){
if (!configure.enable) {
return;
}
bool check_profile = true;
bool check_codec = true;
RETRY:
for (auto &codec : configure.preferred_codec) {
for (auto &offer_media : medias) {
if (offer_media.type != type) {
continue;
}
if (offer_media.ice_lite && configure.ice_lite) {
WarnL << "answer sdp配置为ice_lite模式,与offer sdp中的ice_lite模式冲突";
continue;
}
const RtcCodecPlan *selected_plan = nullptr;
for (auto &plan : offer_media.plan) {
//先检查编码格式是否为偏好
if (check_codec && getCodecId(plan.codec) != codec) {
continue;
}
//命中偏好的编码格式,然后检查规格
if (check_profile && !onCheckCodecProfile(plan, codec)) {
continue;
}
//找到中意的codec
selected_plan = &plan;
break;
}
if (!selected_plan) {
//offer中该媒体的所有的codec都不支持
continue;
}
RtcMedia answer_media;
answer_media.type = offer_media.type;
answer_media.mid = offer_media.mid;
answer_media.proto = offer_media.proto;
//todo(此处设置rtp端口,貌似没什么作用)
answer_media.port = offer_media.port;
//todo(此处设置rtp的ip地址,貌似没什么作用)
answer_media.addr = offer_media.addr;
//todo(此处设置rtcp地址,貌似没什么作用)
answer_media.rtcp_addr = offer_media.rtcp_addr;
answer_media.rtcp_mux = offer_media.rtcp_mux && configure.rtcp_mux;
answer_media.rtcp_rsize = offer_media.rtcp_rsize && configure.rtcp_rsize;
answer_media.ice_trickle = offer_media.ice_trickle && configure.ice_trickle;
answer_media.ice_renomination = offer_media.ice_renomination && configure.ice_renomination;
answer_media.ice_ufrag = configure.ice_ufrag;
answer_media.ice_pwd = configure.ice_pwd;
answer_media.fingerprint = configure.fingerprint;
answer_media.ice_lite = configure.ice_lite;
answer_media.candidate = configure.candidate;
answer_media.rtp_rids = offer_media.rtp_rids;
answer_media.rtp_ssrc_sim = offer_media.rtp_ssrc_sim;
switch (offer_media.role) {
case DtlsRole::actpass :
case DtlsRole::active : {
answer_media.role = DtlsRole::passive;
break;
}
case DtlsRole::passive : {
answer_media.role = DtlsRole::active;
break;
}
default: continue;
}
switch (offer_media.direction) {
case RtpDirection::sendonly : {
if (configure.direction != RtpDirection::recvonly &&
configure.direction != RtpDirection::sendrecv) {
//我们不支持接收
continue;
}
answer_media.direction = RtpDirection::recvonly;
break;
}
case RtpDirection::recvonly : {
if (configure.direction != RtpDirection::sendonly &&
configure.direction != RtpDirection::sendrecv) {
//我们不支持发送
continue;
}
answer_media.direction = RtpDirection::sendonly;
break;
}
case RtpDirection::sendrecv : {
//对方支持发送接收,那么最终能力根据配置来决定
answer_media.direction = configure.direction;
break;
}
default: continue;
}
//添加媒体plan
answer_media.plan.emplace_back(*selected_plan);
onSelectPlan(answer_media.plan.back(), codec);
set<uint8_t> pt_selected = {selected_plan->pt};
//添加rtx,red,ulpfec plan
if (configure.support_red || configure.support_rtx || configure.support_ulpfec) {
for (auto &plan : offer_media.plan) {
if (!strcasecmp(plan.codec.data(), "rtx")) {
if (configure.support_rtx && atoi(plan.getFmtp("apt").data()) == selected_plan->pt) {
answer_media.plan.emplace_back(plan);
pt_selected.emplace(plan.pt);
}
continue;
}
if (!strcasecmp(plan.codec.data(), "red")) {
if (configure.support_red) {
answer_media.plan.emplace_back(plan);
pt_selected.emplace(plan.pt);
}
continue;
}
if (!strcasecmp(plan.codec.data(), "ulpfec")) {
if (configure.support_ulpfec) {
answer_media.plan.emplace_back(plan);
pt_selected.emplace(plan.pt);
}
continue;
}
}
}
//对方和我方都支持的扩展,那么我们才支持
for (auto &ext : offer_media.extmap) {
if (configure.extmap.find(RtpExt::getExtType(ext.ext)) != configure.extmap.end()) {
answer_media.extmap.emplace_back(ext);
}
}
auto &rtcp_fb_ref = answer_media.plan[0].rtcp_fb;
rtcp_fb_ref.clear();
//对方和我方都支持的rtcpfb,那么我们才支持
for (auto &fp : selected_plan->rtcp_fb) {
if (configure.rtcp_fb.find(fp) != configure.rtcp_fb.end()) {
//对方该rtcp被我们支持
rtcp_fb_ref.emplace(fp);
}
}
#if 0
//todo 此处为添加无效的plan,webrtc sdp通过调节plan pt顺序选择匹配的codec,意味着后面的codec其实放在sdp中是无意义的
for (auto &plan : offer_media.plan) {
if (pt_selected.find(plan.pt) == pt_selected.end()) {
answer_media.plan.emplace_back(plan);
}
}
#endif
ret->media.emplace_back(answer_media);
return;
}
}
if (check_profile) {
//如果是由于检查profile导致匹配失败,那么重试一次,且不检查profile
check_profile = false;
goto RETRY;
}
if (check_codec) {
//如果是由于检查codec导致匹配失败,那么重试一次,且不检查codec
check_codec = false;
goto RETRY;
}
}
void RtcConfigure::setPlayRtspInfo(const string &sdp){
RtcSession session;
session.loadFrom(sdp, false);
for (auto &m : session.media) {
switch (m.type) {
case TrackVideo : {
_rtsp_video_plan = std::make_shared<RtcCodecPlan>(m.plan[0]);
video.preferred_codec.clear();
video.preferred_codec.emplace_back(getCodecId(_rtsp_video_plan->codec));
break;
}
case TrackAudio : {
_rtsp_audio_plan = std::make_shared<RtcCodecPlan>(m.plan[0]);
audio.preferred_codec.clear();
audio.preferred_codec.emplace_back(getCodecId(_rtsp_audio_plan->codec));
break;
}
default: break;
}
}
}
static const string kProfile{"profile-level-id"};
static const string kMode{"packetization-mode"};
bool RtcConfigure::onCheckCodecProfile(const RtcCodecPlan &plan, CodecId codec){
if (_rtsp_audio_plan && codec == getCodecId(_rtsp_audio_plan->codec)) {
if (plan.sample_rate != _rtsp_audio_plan->sample_rate || plan.channel != _rtsp_audio_plan->channel) {
//音频采样率和通道数必须相同
return false;
}
return true;
}
if (_rtsp_video_plan && codec == CodecH264 && getCodecId(_rtsp_video_plan->codec) == CodecH264) {
//h264时,profile-level-id
if (strcasecmp(_rtsp_video_plan->fmtp[kProfile].data(), const_cast<RtcCodecPlan &>(plan).fmtp[kProfile].data())) {
//profile-level-id 不匹配
return false;
}
return true;
}
return true;
}
void RtcConfigure::onSelectPlan(RtcCodecPlan &plan, CodecId codec){
if (_rtsp_video_plan && codec == CodecH264 && getCodecId(_rtsp_video_plan->codec) == CodecH264) {
//h264时,设置packetization-mod为一致
auto mode = _rtsp_video_plan->fmtp[kMode];
plan.fmtp[kMode] = mode.empty() ? "0" : mode;
}
}
/*
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
*
* Use of this source code is governed by MIT license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#ifndef ZLMEDIAKIT_SDP_H
#define ZLMEDIAKIT_SDP_H
#include <string>
#include <vector>
#include "RtpExt.h"
#include "assert.h"
#include "Extension/Frame.h"
#include "Common/Parser.h"
using namespace std;
using namespace mediakit;
//https://datatracker.ietf.org/doc/rfc4566/?include_text=1
//https://blog.csdn.net/aggresss/article/details/109850434
//https://aggresss.blog.csdn.net/article/details/106436703
//Session description
// v= (protocol version)
// o= (originator and session identifier)
// s= (session name)
// i=* (session information)
// u=* (URI of description)
// e=* (email address)
// p=* (phone number)
// c=* (connection information -- not required if included in
// all media)
// b=* (zero or more bandwidth information lines)
// One or more time descriptions ("t=" and "r=" lines; see below)
// z=* (time zone adjustments)
// k=* (encryption key)
// a=* (zero or more session attribute lines)
// Zero or more media descriptions
//
// Time description
// t= (time the session is active)
// r=* (zero or more repeat times)
//
// Media description, if present
// m= (media name and transport address)
// i=* (media title)
// c=* (connection information -- optional if included at
// session level)
// b=* (zero or more bandwidth information lines)
// k=* (encryption key)
// a=* (zero or more media attribute lines)
enum class RtpDirection {
invalid = -1,
//只发送
sendonly,
//只接收
recvonly,
//同时发送接收
sendrecv,
//禁止发送数据
inactive
};
enum class DtlsRole {
invalid = -1,
//客户端
active,
//服务端
passive,
//既可作做客户端也可以做服务端
actpass,
};
enum class SdpType {
invalid = -1,
offer,
answer
};
DtlsRole getDtlsRole(const string &str);
const char* getDtlsRoleString(DtlsRole role);
RtpDirection getRtpDirection(const string &str);
const char* getRtpDirectionString(RtpDirection val);
class SdpItem {
public:
using Ptr = std::shared_ptr<SdpItem>;
virtual ~SdpItem() = default;
virtual void parse(const string &str) {
value = str;
}
virtual string toString() const {
return value;
}
virtual const char* getKey() const = 0;
void reset() {
value.clear();
}
protected:
mutable string value;
};
template <char KEY>
class SdpString : public SdpItem{
public:
SdpString() = default;
SdpString(string val) {value = std::move(val);}
// *=*
const char* getKey() const override { static string key(1, KEY); return key.data();}
};
class SdpCommon : public SdpItem {
public:
string key;
SdpCommon(string key) { this->key = std::move(key); }
SdpCommon(string key, string val) {
this->key = std::move(key);
this->value = std::move(val);
}
const char* getKey() const override { return key.data();}
};
class SdpTime : public SdpItem{
public:
//5.9. Timing ("t=")
// t=<start-time> <stop-time>
uint64_t start {0};
uint64_t stop {0};
void parse(const string &str) override;
string toString() const override;
const char* getKey() const override { return "t";}
};
class SdpOrigin : public SdpItem{
public:
// 5.2. Origin ("o=")
// o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5
// o=<username> <sess-id> <sess-version> <nettype> <addrtype> <unicast-address>
string username {"-"};
string session_id;
string session_version;
string nettype {"IN"};
string addrtype {"IP4"};
string address {"0.0.0.0"};
void parse(const string &str) override;
string toString() const override;
const char* getKey() const override { return "o";}
bool empty() const {
return username.empty() || session_id.empty() || session_version.empty()
|| nettype.empty() || addrtype.empty() || address.empty();
}
};
class SdpConnection : public SdpItem {
public:
// 5.7. Connection Data ("c=")
// c=IN IP4 224.2.17.12/127
// c=<nettype> <addrtype> <connection-address>
string nettype {"IN"};
string addrtype {"IP4"};
string address {"0.0.0.0"};
void parse(const string &str) override;
string toString() const override;
const char* getKey() const override { return "c";}
bool empty() const {return address.empty();}
};
class SdpBandwidth : public SdpItem {
public:
//5.8. Bandwidth ("b=")
//b=<bwtype>:<bandwidth>
//AS、CT
string bwtype {"AS"};
uint32_t bandwidth {0};
void parse(const string &str) override;
string toString() const override;
const char* getKey() const override { return "b";}
bool empty() const {return bandwidth == 0;}
};
class SdpMedia : public SdpItem {
public:
// 5.14. Media Descriptions ("m=")
// m=<media> <port> <proto> <fmt> ...
TrackType type;
uint16_t port;
//RTP/AVP:应用场景为视频/音频的 RTP 协议。参考 RFC 3551
//RTP/SAVP:应用场景为视频/音频的 SRTP 协议。参考 RFC 3711
//RTP/AVPF: 应用场景为视频/音频的 RTP 协议,支持 RTCP-based Feedback。参考 RFC 4585
//RTP/SAVPF: 应用场景为视频/音频的 SRTP 协议,支持 RTCP-based Feedback。参考 RFC 5124
string proto;
vector<uint32_t> fmts;
void parse(const string &str) override;
string toString() const override;
const char* getKey() const override { return "m";}
};
class SdpAttr : public SdpItem{
public:
using Ptr = std::shared_ptr<SdpAttr>;
//5.13. Attributes ("a=")
//a=<attribute>
//a=<attribute>:<value>
SdpItem::Ptr detail;
void parse(const string &str) override;
string toString() const override;
const char* getKey() const override { return "a";}
};
class SdpAttrGroup : public SdpItem{
public:
//a=group:BUNDLE line with all the 'mid' identifiers part of the
// BUNDLE group is included at the session-level.
//a=group:LS session level attribute MUST be included wth the 'mid'
// identifiers that are part of the same lip sync group.
string type {"BUNDLE"};
vector<string> mids;
void parse(const string &str) override ;
string toString() const override ;
const char* getKey() const override { return "group";}
};
class SdpAttrMsidSemantic : public SdpItem {
public:
//https://tools.ietf.org/html/draft-alvestrand-rtcweb-msid-02#section-3
//3. The Msid-Semantic Attribute
//
// In order to fully reproduce the semantics of the SDP and SSRC
// grouping frameworks, a session-level attribute is defined for
// signalling the semantics associated with an msid grouping.
//
// This OPTIONAL attribute gives the message ID and its group semantic.
// a=msid-semantic: examplefoo LS
//
//
// The ABNF of msid-semantic is:
//
// msid-semantic-attr = "msid-semantic:" " " msid token
// token = <as defined in RFC 4566>
//
// The semantic field may hold values from the IANA registries
// "Semantics for the "ssrc-group" SDP Attribute" and "Semantics for the
// "group" SDP Attribute".
//a=msid-semantic: WMS 616cfbb1-33a3-4d8c-8275-a199d6005549
string msid{"WMS"};
string token;
void parse(const string &str) override;
string toString() const override;
const char* getKey() const override { return "msid-semantic";}
bool empty() const {
return msid.empty();
}
};
class SdpAttrRtcp : public SdpItem {
public:
// a=rtcp:9 IN IP4 0.0.0.0
uint16_t port{0};
string nettype {"IN"};
string addrtype {"IP4"};
string address {"0.0.0.0"};
void parse(const string &str) override;;
string toString() const override;
const char* getKey() const override { return "rtcp";}
bool empty() const {
return address.empty() || !port;
}
};
class SdpAttrIceUfrag : public SdpItem {
public:
SdpAttrIceUfrag() = default;
SdpAttrIceUfrag(string str) {value = std::move(str);}
//a=ice-ufrag:sXJ3
const char* getKey() const override { return "ice-ufrag";}
};
class SdpAttrIcePwd : public SdpItem {
public:
SdpAttrIcePwd() = default;
SdpAttrIcePwd(string str) {value = std::move(str);}
//a=ice-pwd:yEclOTrLg1gEubBFefOqtmyV
const char* getKey() const override { return "ice-pwd";}
};
class SdpAttrIceOption : public SdpItem {
public:
//a=ice-options:trickle
bool trickle{false};
bool renomination{false};
void parse(const string &str) override;
string toString() const override;
const char* getKey() const override { return "ice-options";}
};
class SdpAttrFingerprint : public SdpItem {
public:
//a=fingerprint:sha-256 22:14:B5:AF:66:12:C7:C7:8D:EF:4B:DE:40:25:ED:5D:8F:17:54:DD:88:33:C0:13:2E:FD:1A:FA:7E:7A:1B:79
string algorithm;
string hash;
void parse(const string &str) override;
string toString() const override;
const char* getKey() const override { return "fingerprint";}
bool empty() const { return algorithm.empty() || hash.empty(); }
};
class SdpAttrSetup : public SdpItem {
public:
//a=setup:actpass
SdpAttrSetup() = default;
SdpAttrSetup(DtlsRole r) { role = r; }
DtlsRole role{DtlsRole::actpass};
void parse(const string &str) override;
string toString() const override;
const char* getKey() const override { return "setup";}
};
class SdpAttrMid : public SdpItem {
public:
SdpAttrMid() = default;
SdpAttrMid(string val) { value = std::move(val); }
//a=mid:audio
const char* getKey() const override { return "mid";}
};
class SdpAttrExtmap : public SdpItem {
public:
//https://aggresss.blog.csdn.net/article/details/106436703
//a=extmap:1[/sendonly] urn:ietf:params:rtp-hdrext:ssrc-audio-level
uint8_t id;
RtpDirection direction{RtpDirection::invalid};
string ext;
void parse(const string &str) override;
string toString() const override;
const char* getKey() const override { return "extmap";}
};
class SdpAttrRtpMap : public SdpItem {
public:
//a=rtpmap:111 opus/48000/2
uint8_t pt;
string codec;
uint32_t sample_rate;
uint32_t channel {0};
void parse(const string &str) override;
string toString() const override;
const char* getKey() const override { return "rtpmap";}
};
class SdpAttrRtcpFb : public SdpItem {
public:
//a=rtcp-fb:98 nack pli
//a=rtcp-fb:120 nack 支持 nack 重传,nack (Negative-Acknowledgment) 。
//a=rtcp-fb:120 nack pli 支持 nack 关键帧重传,PLI (Picture Loss Indication) 。
//a=rtcp-fb:120 ccm fir 支持编码层关键帧请求,CCM (Codec Control Message),FIR (Full Intra Request ),通常与 nack pli 有同样的效果,但是 nack pli 是用于重传时的关键帧请求。
//a=rtcp-fb:120 goog-remb 支持 REMB (Receiver Estimated Maximum Bitrate) 。
//a=rtcp-fb:120 transport-cc 支持 TCC (Transport Congest Control) 。
uint8_t pt;
string rtcp_type;
void parse(const string &str) override;
string toString() const override;
const char* getKey() const override { return "rtcp-fb";}
};
class SdpAttrFmtp : public SdpItem {
public:
//fmtp:96 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
uint8_t pt;
map<string/*key*/, string/*value*/, StrCaseCompare> fmtp;
void parse(const string &str) override;
string toString() const override;
const char* getKey() const override { return "fmtp";}
};
class SdpAttrSSRC : public SdpItem {
public:
//a=ssrc:3245185839 cname:Cx4i/VTR51etgjT7
//a=ssrc:3245185839 msid:cb373bff-0fea-4edb-bc39-e49bb8e8e3b9 0cf7e597-36a2-4480-9796-69bf0955eef5
//a=ssrc:3245185839 mslabel:cb373bff-0fea-4edb-bc39-e49bb8e8e3b9
//a=ssrc:3245185839 label:0cf7e597-36a2-4480-9796-69bf0955eef5
//a=ssrc:<ssrc-id> <attribute>
//a=ssrc:<ssrc-id> <attribute>:<value>
//cname 是必须的,msid/mslabel/label 这三个属性都是 WebRTC 自创的,或者说 Google 自创的,可以参考 https://tools.ietf.org/html/draft-ietf-mmusic-msid-17,
// 理解它们三者的关系需要先了解三个概念:RTP stream / MediaStreamTrack / MediaStream :
//一个 a=ssrc 代表一个 RTP stream ;
//一个 MediaStreamTrack 通常包含一个或多个 RTP stream,例如一个视频 MediaStreamTrack 中通常包含两个 RTP stream,一个用于常规传输,一个用于 nack 重传;
//一个 MediaStream 通常包含一个或多个 MediaStreamTrack ,例如 simulcast 场景下,一个 MediaStream 通常会包含三个不同编码质量的 MediaStreamTrack ;
//这种标记方式并不被 Firefox 认可,在 Firefox 生成的 SDP 中一个 a=ssrc 通常只有一行,例如:
//a=ssrc:3245185839 cname:Cx4i/VTR51etgjT7
uint32_t ssrc;
string attribute;
string attribute_value;
void parse(const string &str) override;
string toString() const override;
const char* getKey() const override { return "ssrc";}
};
class SdpAttrSSRCGroup : public SdpItem {
public:
//a=ssrc-group 定义参考 RFC 5576(https://tools.ietf.org/html/rfc5576) ,用于描述多个 ssrc 之间的关联,常见的有两种:
//a=ssrc-group:FID 2430709021 3715850271
// FID (Flow Identification) 最初用在 FEC 的关联中,WebRTC 中通常用于关联一组常规 RTP stream 和 重传 RTP stream 。
//a=ssrc-group:SIM 360918977 360918978 360918980
// 在 Chrome 独有的 SDP munging 风格的 simulcast 中使用,将三组编码质量由低到高的 MediaStreamTrack 关联在一起。
string type{"FID"};
vector<uint32_t> ssrcs;
bool isFID() const { return type == "FID"; }
bool isSIM() const { return type == "SIM"; }
void parse(const string &str) override;
string toString() const override;
const char* getKey() const override { return "ssrc-group";}
};
class SdpAttrSctpMap : public SdpItem {
public:
//https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-05
//a=sctpmap:5000 webrtc-datachannel 1024
//a=sctpmap: sctpmap-number media-subtypes [streams]
uint16_t port;
string subtypes;
uint32_t streams;
void parse(const string &str) override;
string toString() const override;
const char* getKey() const override { return "sctpmap";}
};
class SdpAttrCandidate : public SdpItem {
public:
using Ptr = std::shared_ptr<SdpAttrCandidate>;
//https://tools.ietf.org/html/rfc5245
//15.1. "candidate" Attribute
//a=candidate:4 1 udp 2 192.168.1.7 58107 typ host
//a=candidate:<foundation> <component-id> <transport> <priority> <address> <port> typ <cand-type>
string foundation;
//传输媒体的类型,1代表RTP;2代表 RTCP。
uint32_t component;
string transport {"udp"};
uint32_t priority;
string address;
uint16_t port;
string type;
vector<std::pair<string, string> > arr;
void parse(const string &str) override;
string toString() const override;
const char* getKey() const override { return "candidate";}
};
class SdpAttrMsid : public SdpItem{
public:
const char* getKey() const override { return "msid";}
};
class SdpAttrExtmapAllowMixed : public SdpItem{
public:
const char* getKey() const override { return "extmap-allow-mixed";}
};
class SdpAttrSimulcast : public SdpItem{
public:
//https://www.meetecho.com/blog/simulcast-janus-ssrc/
//https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-14
const char* getKey() const override { return "simulcast";}
void parse(const string &str) override;
string toString() const override;
bool empty() const { return rids.empty(); }
string direction;
vector<string> rids;
};
class SdpAttrRid : public SdpItem{
public:
void parse(const string &str) override;
string toString() const override;
const char* getKey() const override { return "rid";}
string direction;
string rid;
};
class RtcSdpBase {
public:
vector<SdpItem::Ptr> items;
public:
virtual ~RtcSdpBase() = default;
virtual string toString() const;
int getVersion() const;
SdpOrigin getOrigin() const;
string getSessionName() const;
string getSessionInfo() const;
SdpTime getSessionTime() const;
SdpConnection getConnection() const;
SdpBandwidth getBandwidth() const;
string getUri() const;
string getEmail() const;
string getPhone() const;
string getTimeZone() const;
string getEncryptKey() const;
string getRepeatTimes() const;
RtpDirection getDirection() const;
template<typename cls>
cls getItemClass(char key, const char *attr_key = nullptr) const{
auto item = dynamic_pointer_cast<cls>(getItem(key, attr_key));
if (!item) {
return cls();
}
return *item;
}
string getStringItem(char key, const char *attr_key = nullptr) const{
auto item = getItem(key, attr_key);
if (!item) {
return "";
}
return item->toString();
}
SdpItem::Ptr getItem(char key, const char *attr_key = nullptr) const;
template<typename cls>
vector<cls> getAllItem(char key_c, const char *attr_key = nullptr) const {
vector<cls> ret;
for (auto item : items) {
string key(1, key_c);
if (strcasecmp(item->getKey(), key.data()) == 0) {
if (!attr_key) {
auto c = dynamic_pointer_cast<cls>(item);
if (c) {
ret.emplace_back(*c);
}
} else {
auto attr = dynamic_pointer_cast<SdpAttr>(item);
if (attr && !strcasecmp(attr->detail->getKey(), attr_key)) {
auto c = dynamic_pointer_cast<cls>(attr->detail);
if (c) {
ret.emplace_back(*c);
}
}
}
}
}
return ret;
}
};
class RtcSessionSdp : public RtcSdpBase{
public:
using Ptr = std::shared_ptr<RtcSessionSdp>;
vector<RtcSdpBase> medias;
void parse(const string &str);
string toString() const override;
};
//////////////////////////////////////////////////////////////////
//ssrc相关信息
class RtcSSRC{
public:
uint32_t ssrc {0};
string cname;
string msid;
string mslabel;
string label;
bool empty() const {return ssrc == 0 && cname.empty();}
};
//rtc传输编码方案
class RtcCodecPlan{
public:
using Ptr = shared_ptr<RtcCodecPlan>;
uint8_t pt;
string codec;
uint32_t sample_rate;
//音频时有效
uint32_t channel = 0;
//rtcp反馈
set<string> rtcp_fb;
map<string/*key*/, string/*value*/, StrCaseCompare> fmtp;
string getFmtp(const char *key) const;
};
//rtc 媒体描述
class RtcMedia{
public:
TrackType type{TrackType::TrackInvalid};
string mid;
uint16_t port{0};
SdpConnection addr;
string proto;
RtpDirection direction{RtpDirection::invalid};
vector<RtcCodecPlan> plan;
//////// rtp ////////
vector<RtcSSRC> rtp_rtx_ssrc;
//////// simulcast ////////
vector<RtcSSRC> rtp_ssrc_sim;
vector<string> rtp_rids;
//////// rtcp ////////
bool rtcp_mux{false};
bool rtcp_rsize{false};
SdpAttrRtcp rtcp_addr;
//////// ice ////////
bool ice_trickle{false};
bool ice_lite{false};
bool ice_renomination{false};
string ice_ufrag;
string ice_pwd;
std::vector<SdpAttrCandidate> candidate;
//////// dtls ////////
DtlsRole role{DtlsRole::invalid};
SdpAttrFingerprint fingerprint;
//////// extmap ////////
vector<SdpAttrExtmap> extmap;
//////// sctp ////////////
SdpAttrSctpMap sctpmap;
uint32_t sctp_port{0};
void checkValid() const;
//offer sdp,如果指定了发送rtp,那么应该指定ssrc
void checkValidSSRC() const;
const RtcCodecPlan *getPlan(uint8_t pt) const;
const RtcCodecPlan *getPlan(const char *codec) const;
const RtcCodecPlan *getRelatedRtxPlan(uint8_t pt) const;
uint32_t getRtpSSRC() const;
uint32_t getRtxSSRC() const;
};
class RtcSession{
public:
using Ptr = std::shared_ptr<RtcSession>;
uint32_t version;
SdpOrigin origin;
string session_name;
string session_info;
SdpTime time;
SdpConnection connection;
SdpBandwidth bandwidth;
SdpAttrMsidSemantic msid_semantic;
vector<RtcMedia> media;
SdpAttrGroup group;
void loadFrom(const string &sdp, bool check = true);
void checkValid() const;
//offer sdp,如果指定了发送rtp,那么应该指定ssrc
void checkValidSSRC() const;
string toString() const;
string toRtspSdp() const;
const RtcMedia *getMedia(TrackType type) const;
bool haveSSRC() const;
bool supportRtcpFb(const string &name, TrackType type = TrackType::TrackVideo) const;
private:
RtcSessionSdp::Ptr toRtcSessionSdp() const;
};
class RtcConfigure {
public:
using Ptr = std::shared_ptr<RtcConfigure>;
class RtcTrackConfigure {
public:
bool enable;
bool rtcp_mux;
bool rtcp_rsize;
bool group_bundle;
bool support_rtx;
bool support_red;
bool support_ulpfec;
bool ice_lite;
bool ice_trickle;
bool ice_renomination;
string ice_ufrag;
string ice_pwd;
RtpDirection direction{RtpDirection::invalid};
SdpAttrFingerprint fingerprint;
set<string> rtcp_fb;
set<RtpExtType> extmap;
vector<CodecId> preferred_codec;
vector<SdpAttrCandidate> candidate;
void setDefaultSetting(TrackType type);
void enableTWCC(bool enable = true);
void enableREMB(bool enable = true);
};
RtcTrackConfigure video;
RtcTrackConfigure audio;
RtcTrackConfigure application;
void setDefaultSetting(string ice_ufrag,
string ice_pwd,
RtpDirection direction,
const SdpAttrFingerprint &fingerprint);
void addCandidate(const SdpAttrCandidate &candidate, TrackType type = TrackInvalid);
shared_ptr<RtcSession> createAnswer(const RtcSession &offer);
void setPlayRtspInfo(const string &sdp);
void enableTWCC(bool enable = true, TrackType type = TrackInvalid);
void enableREMB(bool enable = true, TrackType type = TrackInvalid);
private:
void matchMedia(shared_ptr<RtcSession> &ret, TrackType type, const vector<RtcMedia> &medias, const RtcTrackConfigure &configure);
bool onCheckCodecProfile(const RtcCodecPlan &plan, CodecId codec);
void onSelectPlan(RtcCodecPlan &plan, CodecId codec);
private:
RtcCodecPlan::Ptr _rtsp_video_plan;
RtcCodecPlan::Ptr _rtsp_audio_plan;
};
class SdpConst {
public:
static string const kTWCCRtcpFb;
static string const kRembRtcpFb;
private:
SdpConst() = delete;
~SdpConst() = delete;
};
#endif //ZLMEDIAKIT_SDP_H
/**
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::SrtpSession"
// #define MS_LOG_DEV_LEVEL 3
#include "SrtpSession.hpp"
#include <cstring> // std::memset(), std::memcpy()
#include "logger.h"
#include "Util/util.h"
#include "Util/logger.h"
using namespace toolkit;
namespace RTC
{
/* Static. */
static std::vector<const char *> errors = {
// From 0 (srtp_err_status_ok) to 24 (srtp_err_status_pfkey_err).
"success (srtp_err_status_ok)",
"unspecified failure (srtp_err_status_fail)",
"unsupported parameter (srtp_err_status_bad_param)",
"couldn't allocate memory (srtp_err_status_alloc_fail)",
"couldn't deallocate memory (srtp_err_status_dealloc_fail)",
"couldn't initialize (srtp_err_status_init_fail)",
"can’t process as much data as requested (srtp_err_status_terminus)",
"authentication failure (srtp_err_status_auth_fail)",
"cipher failure (srtp_err_status_cipher_fail)",
"replay check failed (bad index) (srtp_err_status_replay_fail)",
"replay check failed (index too old) (srtp_err_status_replay_old)",
"algorithm failed test routine (srtp_err_status_algo_fail)",
"unsupported operation (srtp_err_status_no_such_op)",
"no appropriate context found (srtp_err_status_no_ctx)",
"unable to perform desired validation (srtp_err_status_cant_check)",
"can’t use key any more (srtp_err_status_key_expired)",
"error in use of socket (srtp_err_status_socket_err)",
"error in use POSIX signals (srtp_err_status_signal_err)",
"nonce check failed (srtp_err_status_nonce_bad)",
"couldn’t read data (srtp_err_status_read_fail)",
"couldn’t write data (srtp_err_status_write_fail)",
"error parsing data (srtp_err_status_parse_err)",
"error encoding data (srtp_err_status_encode_err)",
"error while using semaphores (srtp_err_status_semaphore_err)",
"error while using pfkey (srtp_err_status_pfkey_err)"};
// clang-format on
/* Static methods. */
const char *DepLibSRTP::GetErrorString(srtp_err_status_t code) {
// This throws out_of_range if the given index is not in the vector.
return errors.at(code);
}
bool DepLibSRTP::IsError(srtp_err_status_t code) {
return (code != srtp_err_status_ok);
}
INSTANCE_IMP(DepLibSRTP);
DepLibSRTP::DepLibSRTP(){
MS_TRACE();
MS_DEBUG_TAG(info, "libsrtp version: \"%s\"", srtp_get_version_string());
srtp_err_status_t err = srtp_init();
#if 0
srtp_install_log_handler([](srtp_log_level_t level,
const char *msg,
void *data) {
printf("%s\n", msg);
}, nullptr);
srtp_set_debug_module("srtp", 1);
srtp_set_debug_module("hmac sha-1", 1);
srtp_set_debug_module("aes icm", 1);
srtp_set_debug_module("alloc", 1);
srtp_set_debug_module("stat test", 1);
srtp_set_debug_module("cipher", 1);
srtp_set_debug_module("auth func", 1);
srtp_set_debug_module("crypto kernel", 1);
srtp_list_debug_modules();
#endif
if (DepLibSRTP::IsError(err)) {
MS_THROW_ERROR("srtp_init() failed: %s", DepLibSRTP::GetErrorString(err));
}
// Set libsrtp event handler.
err = srtp_install_event_handler([](srtp_event_data_t *data){
MS_TRACE();
switch (data->event)
{
case event_ssrc_collision:
MS_WARN_TAG(srtp, "SSRC collision occurred");
break;
case event_key_soft_limit:
MS_WARN_TAG(srtp, "stream reached the soft key usage limit and will expire soon");
break;
case event_key_hard_limit:
MS_WARN_TAG(srtp, "stream reached the hard key usage limit and has expired");
break;
case event_packet_index_limit:
MS_WARN_TAG(srtp, "stream reached the hard packet limit (2^48 packets)");
break;
}
});
if (DepLibSRTP::IsError(err))
{
MS_THROW_ERROR("srtp_install_event_handler() failed: %s", DepLibSRTP::GetErrorString(err));
}
}
DepLibSRTP::~DepLibSRTP(){
MS_TRACE();
srtp_shutdown();
}
/////////////////////////////////////////////////////////////////////////////////////
/* Instance methods. */
SrtpSession::SrtpSession(Type type, CryptoSuite cryptoSuite, uint8_t* key, size_t keyLen)
{
_env = DepLibSRTP::Instance().shared_from_this();
MS_TRACE();
srtp_policy_t policy; // NOLINT(cppcoreguidelines-pro-type-member-init)
// Set all policy fields to 0.
std::memset(&policy, 0, sizeof(srtp_policy_t));
switch (cryptoSuite)
{
case CryptoSuite::AES_CM_128_HMAC_SHA1_80:
{
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtp);
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtcp);
break;
}
case CryptoSuite::AES_CM_128_HMAC_SHA1_32:
{
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(&policy.rtp);
// NOTE: Must be 80 for RTCP.
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtcp);
break;
}
case CryptoSuite::AEAD_AES_256_GCM:
{
srtp_crypto_policy_set_aes_gcm_256_16_auth(&policy.rtp);
srtp_crypto_policy_set_aes_gcm_256_16_auth(&policy.rtcp);
break;
}
case CryptoSuite::AEAD_AES_128_GCM:
{
srtp_crypto_policy_set_aes_gcm_128_16_auth(&policy.rtp);
srtp_crypto_policy_set_aes_gcm_128_16_auth(&policy.rtcp);
break;
}
default:
{
MS_ABORT("unknown SRTP crypto suite");
}
}
MS_ASSERT(
(int)keyLen == policy.rtp.cipher_key_len,
"given keyLen does not match policy.rtp.cipher_keyLen");
switch (type)
{
case Type::INBOUND:
policy.ssrc.type = ssrc_any_inbound;
break;
case Type::OUTBOUND:
policy.ssrc.type = ssrc_any_outbound;
break;
}
policy.ssrc.value = 0;
policy.key = key;
// Required for sending RTP retransmission without RTX.
policy.allow_repeat_tx = 1;
policy.window_size = 1024;
policy.next = nullptr;
// Set the SRTP session.
srtp_err_status_t err = srtp_create(&this->session, &policy);
if (DepLibSRTP::IsError(err))
MS_THROW_ERROR("srtp_create() failed: %s", DepLibSRTP::GetErrorString(err));
}
SrtpSession::~SrtpSession()
{
MS_TRACE();
if (this->session != nullptr)
{
srtp_err_status_t err = srtp_dealloc(this->session);
if (DepLibSRTP::IsError(err))
MS_ABORT("srtp_dealloc() failed: %s", DepLibSRTP::GetErrorString(err));
}
}
bool SrtpSession::EncryptRtp(uint8_t* data, size_t* len)
{
MS_TRACE();
srtp_err_status_t err =
srtp_protect(this->session, static_cast<void*>(data), reinterpret_cast<int*>(len));
if (DepLibSRTP::IsError(err))
{
MS_WARN_TAG(srtp, "srtp_protect() failed: %s", DepLibSRTP::GetErrorString(err));
return false;
}
return true;
}
bool SrtpSession::DecryptSrtp(uint8_t* data, size_t* len)
{
MS_TRACE();
srtp_err_status_t err =
srtp_unprotect(this->session, static_cast<void*>(data), reinterpret_cast<int*>(len));
if (DepLibSRTP::IsError(err))
{
MS_DEBUG_TAG(srtp, "srtp_unprotect() failed: %s", DepLibSRTP::GetErrorString(err));
return false;
}
return true;
}
bool SrtpSession::EncryptRtcp(uint8_t* data, size_t* len)
{
MS_TRACE();
srtp_err_status_t err = srtp_protect_rtcp(
this->session, static_cast<void*>(data), reinterpret_cast<int*>(len));
if (DepLibSRTP::IsError(err))
{
MS_WARN_TAG(srtp, "srtp_protect_rtcp() failed: %s", DepLibSRTP::GetErrorString(err));
return false;
}
return true;
}
bool SrtpSession::DecryptSrtcp(uint8_t* data, size_t* len)
{
MS_TRACE();
srtp_err_status_t err =
srtp_unprotect_rtcp(this->session, static_cast<void*>(data), reinterpret_cast<int*>(len));
if (DepLibSRTP::IsError(err))
{
MS_DEBUG_TAG(srtp, "srtp_unprotect_rtcp() failed: %s", DepLibSRTP::GetErrorString(err));
return false;
}
return true;
}
} // namespace RTC
/**
ISC License
Copyright © 2015, Iñaki Baz Castillo <ibc@aliax.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef MS_RTC_SRTP_SESSION_HPP
#define MS_RTC_SRTP_SESSION_HPP
#include "Utils.hpp"
#include <srtp2/srtp.h>
#include <vector>
#include <memory>
namespace RTC
{
class DepLibSRTP : public std::enable_shared_from_this<DepLibSRTP>
{
public:
using Ptr = std::shared_ptr<DepLibSRTP>;
~DepLibSRTP();
static bool IsError(srtp_err_status_t code);
static const char *GetErrorString(srtp_err_status_t code);
static DepLibSRTP &Instance();
private:
DepLibSRTP();
};
class SrtpSession
{
public:
enum class CryptoSuite
{
NONE = 0,
AES_CM_128_HMAC_SHA1_80 = 1,
AES_CM_128_HMAC_SHA1_32,
AEAD_AES_256_GCM,
AEAD_AES_128_GCM
};
public:
enum class Type
{
INBOUND = 1,
OUTBOUND
};
public:
SrtpSession(Type type, CryptoSuite cryptoSuite, uint8_t* key, size_t keyLen);
~SrtpSession();
public:
bool EncryptRtp(uint8_t* data, size_t* len);
bool DecryptSrtp(uint8_t* data, size_t* len);
bool EncryptRtcp(uint8_t* data, size_t* len);
bool DecryptSrtcp(uint8_t* data, size_t* len);
void RemoveStream(uint32_t ssrc)
{
srtp_remove_stream(this->session, uint32_t{ htonl(ssrc) });
}
private:
// Allocated by this.
srtp_t session{ nullptr };
DepLibSRTP::Ptr _env;
};
} // namespace RTC
#endif
/**
ISC License
Copyright © 2015, Iñaki Baz Castillo <ibc@aliax.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#define MS_CLASS "RTC::StunPacket"
// #define MS_LOG_DEV_LEVEL 3
#include "StunPacket.hpp"
#include <cstdio> // std::snprintf()
#include <cstring> // std::memcmp(), std::memcpy()
namespace RTC
{
static const uint32_t crc32Table[] =
{
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};
inline uint32_t GetCRC32(const uint8_t *data, size_t size) {
uint32_t crc{0xFFFFFFFF};
const uint8_t *p = data;
while (size--) {
crc = crc32Table[(crc ^ *p++) & 0xFF] ^ (crc >> 8);
}
return crc ^ ~0U;
}
static std::string openssl_HMACsha1(const void *key, size_t key_len, const void *data, size_t data_len){
std::string str;
str.resize(20);
unsigned int out_len;
#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER > 0x10100000L)
//openssl 1.1.0新增api,老版本api作废
HMAC_CTX *ctx = HMAC_CTX_new();
HMAC_CTX_reset(ctx);
HMAC_Init_ex(ctx, key, (int)key_len, EVP_sha1(), NULL);
HMAC_Update(ctx, (unsigned char*)data, data_len);
HMAC_Final(ctx, (unsigned char *)str.data(), &out_len);
HMAC_CTX_reset(ctx);
HMAC_CTX_free(ctx);
#else
HMAC_CTX ctx;
HMAC_CTX_init(&ctx);
HMAC_Init_ex(&ctx, key, key_len, EVP_sha1(), NULL);
HMAC_Update(&ctx, (unsigned char*)data, data_len);
HMAC_Final(&ctx, (unsigned char *)str.data(), &out_len);
HMAC_CTX_cleanup(&ctx);
#endif //defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER > 0x10100000L)
return str;
}
/* Class variables. */
const uint8_t StunPacket::magicCookie[] = { 0x21, 0x12, 0xA4, 0x42 };
/* Class methods. */
StunPacket* StunPacket::Parse(const uint8_t* data, size_t len)
{
MS_TRACE();
if (!StunPacket::IsStun(data, len))
return nullptr;
/*
The message type field is decomposed further into the following
structure:
0 1
2 3 4 5 6 7 8 9 0 1 2 3 4 5
+--+--+-+-+-+-+-+-+-+-+-+-+-+-+
|M |M |M|M|M|C|M|M|M|C|M|M|M|M|
|11|10|9|8|7|1|6|5|4|0|3|2|1|0|
+--+--+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 3: Format of STUN Message Type Field
Here the bits in the message type field are shown as most significant
(M11) through least significant (M0). M11 through M0 represent a 12-
bit encoding of the method. C1 and C0 represent a 2-bit encoding of
the class.
*/
// Get type field.
uint16_t msgType = Utils::Byte::Get2Bytes(data, 0);
// Get length field.
uint16_t msgLength = Utils::Byte::Get2Bytes(data, 2);
// length field must be total size minus header's 20 bytes, and must be multiple of 4 Bytes.
if ((static_cast<size_t>(msgLength) != len - 20) || ((msgLength & 0x03) != 0))
{
MS_WARN_TAG(
ice,
"length field + 20 does not match total size (or it is not multiple of 4 bytes), "
"packet discarded");
return nullptr;
}
// Get STUN method.
uint16_t msgMethod = (msgType & 0x000f) | ((msgType & 0x00e0) >> 1) | ((msgType & 0x3E00) >> 2);
// Get STUN class.
uint16_t msgClass = ((data[0] & 0x01) << 1) | ((data[1] & 0x10) >> 4);
// Create a new StunPacket (data + 8 points to the received TransactionID field).
auto* packet = new StunPacket(
static_cast<Class>(msgClass), static_cast<Method>(msgMethod), data + 8, data, len);
/*
STUN Attributes
After the STUN header are zero or more attributes. Each attribute
MUST be TLV encoded, with a 16-bit type, 16-bit length, and value.
Each STUN attribute MUST end on a 32-bit boundary. As mentioned
above, all fields in an attribute are transmitted most significant
bit first.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Value (variable) ....
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
// Start looking for attributes after STUN header (Byte #20).
size_t pos{ 20 };
// Flags (positions) for special MESSAGE-INTEGRITY and FINGERPRINT attributes.
bool hasMessageIntegrity{ false };
bool hasFingerprint{ false };
size_t fingerprintAttrPos; // Will point to the beginning of the attribute.
uint32_t fingerprint; // Holds the value of the FINGERPRINT attribute.
// Ensure there are at least 4 remaining bytes (attribute with 0 length).
while (pos + 4 <= len)
{
// Get the attribute type.
auto attrType = static_cast<Attribute>(Utils::Byte::Get2Bytes(data, pos));
// Get the attribute length.
uint16_t attrLength = Utils::Byte::Get2Bytes(data, pos + 2);
// Ensure the attribute length is not greater than the remaining size.
if ((pos + 4 + attrLength) > len)
{
MS_WARN_TAG(ice, "the attribute length exceeds the remaining size, packet discarded");
delete packet;
return nullptr;
}
// FINGERPRINT must be the last attribute.
if (hasFingerprint)
{
MS_WARN_TAG(ice, "attribute after FINGERPRINT is not allowed, packet discarded");
delete packet;
return nullptr;
}
// After a MESSAGE-INTEGRITY attribute just FINGERPRINT is allowed.
if (hasMessageIntegrity && attrType != Attribute::FINGERPRINT)
{
MS_WARN_TAG(
ice,
"attribute after MESSAGE-INTEGRITY other than FINGERPRINT is not allowed, "
"packet discarded");
delete packet;
return nullptr;
}
const uint8_t* attrValuePos = data + pos + 4;
switch (attrType)
{
case Attribute::USERNAME:
{
packet->SetUsername(
reinterpret_cast<const char*>(attrValuePos), static_cast<size_t>(attrLength));
break;
}
case Attribute::PRIORITY:
{
// Ensure attribute length is 4 bytes.
if (attrLength != 4)
{
MS_WARN_TAG(ice, "attribute PRIORITY must be 4 bytes length, packet discarded");
delete packet;
return nullptr;
}
packet->SetPriority(Utils::Byte::Get4Bytes(attrValuePos, 0));
break;
}
case Attribute::ICE_CONTROLLING:
{
// Ensure attribute length is 8 bytes.
if (attrLength != 8)
{
MS_WARN_TAG(ice, "attribute ICE-CONTROLLING must be 8 bytes length, packet discarded");
delete packet;
return nullptr;
}
packet->SetIceControlling(Utils::Byte::Get8Bytes(attrValuePos, 0));
break;
}
case Attribute::ICE_CONTROLLED:
{
// Ensure attribute length is 8 bytes.
if (attrLength != 8)
{
MS_WARN_TAG(ice, "attribute ICE-CONTROLLED must be 8 bytes length, packet discarded");
delete packet;
return nullptr;
}
packet->SetIceControlled(Utils::Byte::Get8Bytes(attrValuePos, 0));
break;
}
case Attribute::USE_CANDIDATE:
{
// Ensure attribute length is 0 bytes.
if (attrLength != 0)
{
MS_WARN_TAG(ice, "attribute USE-CANDIDATE must be 0 bytes length, packet discarded");
delete packet;
return nullptr;
}
packet->SetUseCandidate();
break;
}
case Attribute::MESSAGE_INTEGRITY:
{
// Ensure attribute length is 20 bytes.
if (attrLength != 20)
{
MS_WARN_TAG(ice, "attribute MESSAGE-INTEGRITY must be 20 bytes length, packet discarded");
delete packet;
return nullptr;
}
hasMessageIntegrity = true;
packet->SetMessageIntegrity(attrValuePos);
break;
}
case Attribute::FINGERPRINT:
{
// Ensure attribute length is 4 bytes.
if (attrLength != 4)
{
MS_WARN_TAG(ice, "attribute FINGERPRINT must be 4 bytes length, packet discarded");
delete packet;
return nullptr;
}
hasFingerprint = true;
fingerprintAttrPos = pos;
fingerprint = Utils::Byte::Get4Bytes(attrValuePos, 0);
packet->SetFingerprint();
break;
}
case Attribute::ERROR_CODE:
{
// Ensure attribute length >= 4bytes.
if (attrLength < 4)
{
MS_WARN_TAG(ice, "attribute ERROR-CODE must be >= 4bytes length, packet discarded");
delete packet;
return nullptr;
}
uint8_t errorClass = Utils::Byte::Get1Byte(attrValuePos, 2);
uint8_t errorNumber = Utils::Byte::Get1Byte(attrValuePos, 3);
auto errorCode = static_cast<uint16_t>(errorClass * 100 + errorNumber);
packet->SetErrorCode(errorCode);
break;
}
default:;
}
// Set next attribute position.
pos =
static_cast<size_t>(Utils::Byte::PadTo4Bytes(static_cast<uint16_t>(pos + 4 + attrLength)));
}
// Ensure current position matches the total length.
if (pos != len)
{
MS_WARN_TAG(ice, "computed packet size does not match total size, packet discarded");
delete packet;
return nullptr;
}
// If it has FINGERPRINT attribute then verify it.
if (hasFingerprint)
{
// Compute the CRC32 of the received packet up to (but excluding) the
// FINGERPRINT attribute and XOR it with 0x5354554e.
uint32_t computedFingerprint = GetCRC32(data, fingerprintAttrPos) ^ 0x5354554e;
// Compare with the FINGERPRINT value in the packet.
if (fingerprint != computedFingerprint)
{
MS_WARN_TAG(
ice,
"computed FINGERPRINT value does not match the value in the packet, "
"packet discarded");
delete packet;
return nullptr;
}
}
return packet;
}
/* Instance methods. */
StunPacket::StunPacket(
Class klass, Method method, const uint8_t* transactionId, const uint8_t* data, size_t size)
: klass(klass), method(method), transactionId(transactionId), data(const_cast<uint8_t*>(data)),
size(size)
{
MS_TRACE();
}
StunPacket::~StunPacket()
{
MS_TRACE();
}
#if 0
void StunPacket::Dump() const
{
MS_TRACE();
MS_DUMP("<StunPacket>");
std::string klass;
switch (this->klass)
{
case Class::REQUEST:
klass = "Request";
break;
case Class::INDICATION:
klass = "Indication";
break;
case Class::SUCCESS_RESPONSE:
klass = "SuccessResponse";
break;
case Class::ERROR_RESPONSE:
klass = "ErrorResponse";
break;
}
if (this->method == Method::BINDING)
{
MS_DUMP(" Binding %s", klass.c_str());
}
else
{
// This prints the unknown method number. Example: TURN Allocate => 0x003.
MS_DUMP(" %s with unknown method %#.3x", klass.c_str(), static_cast<uint16_t>(this->method));
}
MS_DUMP(" size: %zu bytes", this->size);
static char transactionId[25];
for (int i{ 0 }; i < 12; ++i)
{
// NOTE: n must be 3 because snprintf adds a \0 after printed chars.
std::snprintf(transactionId + (i * 2), 3, "%.2x", this->transactionId[i]);
}
MS_DUMP(" transactionId: %s", transactionId);
if (this->errorCode != 0u)
MS_DUMP(" errorCode: %" PRIu16, this->errorCode);
if (!this->username.empty())
MS_DUMP(" username: %s", this->username.c_str());
if (this->priority != 0u)
MS_DUMP(" priority: %" PRIu32, this->priority);
if (this->iceControlling != 0u)
MS_DUMP(" iceControlling: %" PRIu64, this->iceControlling);
if (this->iceControlled != 0u)
MS_DUMP(" iceControlled: %" PRIu64, this->iceControlled);
if (this->hasUseCandidate)
MS_DUMP(" useCandidate");
if (this->xorMappedAddress != nullptr)
{
int family;
uint16_t port;
std::string ip;
Utils::IP::GetAddressInfo(this->xorMappedAddress, family, ip, port);
MS_DUMP(" xorMappedAddress: %s : %" PRIu16, ip.c_str(), port);
}
if (this->messageIntegrity != nullptr)
{
static char messageIntegrity[41];
for (int i{ 0 }; i < 20; ++i)
{
std::snprintf(messageIntegrity + (i * 2), 3, "%.2x", this->messageIntegrity[i]);
}
MS_DUMP(" messageIntegrity: %s", messageIntegrity);
}
if (this->hasFingerprint)
MS_DUMP(" has fingerprint");
MS_DUMP("</StunPacket>");
}
#endif
StunPacket::Authentication StunPacket::CheckAuthentication(
const std::string& localUsername, const std::string& localPassword)
{
MS_TRACE();
switch (this->klass)
{
case Class::REQUEST:
case Class::INDICATION:
{
// Both USERNAME and MESSAGE-INTEGRITY must be present.
if (!this->messageIntegrity || this->username.empty())
return Authentication::BAD_REQUEST;
// Check that USERNAME attribute begins with our local username plus ":".
size_t localUsernameLen = localUsername.length();
if (
this->username.length() <= localUsernameLen || this->username.at(localUsernameLen) != ':' ||
(this->username.compare(0, localUsernameLen, localUsername) != 0))
{
return Authentication::UNAUTHORIZED;
}
break;
}
// This method cannot check authentication in received responses (as we
// are ICE-Lite and don't generate requests).
case Class::SUCCESS_RESPONSE:
case Class::ERROR_RESPONSE:
{
MS_ERROR("cannot check authentication for a STUN response");
return Authentication::BAD_REQUEST;
}
}
// If there is FINGERPRINT it must be discarded for MESSAGE-INTEGRITY calculation,
// so the header length field must be modified (and later restored).
if (this->hasFingerprint)
// Set the header length field: full size - header length (20) - FINGERPRINT length (8).
Utils::Byte::Set2Bytes(this->data, 2, static_cast<uint16_t>(this->size - 20 - 8));
// Calculate the HMAC-SHA1 of the message according to MESSAGE-INTEGRITY rules.
auto computedMessageIntegrity = openssl_HMACsha1(
localPassword.data(),localPassword.size(), this->data, (this->messageIntegrity - 4) - this->data);
Authentication result;
// Compare the computed HMAC-SHA1 with the MESSAGE-INTEGRITY in the packet.
if (std::memcmp(this->messageIntegrity, computedMessageIntegrity.data(), computedMessageIntegrity.size()) == 0)
result = Authentication::OK;
else
result = Authentication::UNAUTHORIZED;
// Restore the header length field.
if (this->hasFingerprint)
Utils::Byte::Set2Bytes(this->data, 2, static_cast<uint16_t>(this->size - 20));
return result;
}
StunPacket* StunPacket::CreateSuccessResponse()
{
MS_TRACE();
MS_ASSERT(
this->klass == Class::REQUEST,
"attempt to create a success response for a non Request STUN packet");
return new StunPacket(Class::SUCCESS_RESPONSE, this->method, this->transactionId, nullptr, 0);
}
StunPacket* StunPacket::CreateErrorResponse(uint16_t errorCode)
{
MS_TRACE();
MS_ASSERT(
this->klass == Class::REQUEST,
"attempt to create an error response for a non Request STUN packet");
auto* response =
new StunPacket(Class::ERROR_RESPONSE, this->method, this->transactionId, nullptr, 0);
response->SetErrorCode(errorCode);
return response;
}
void StunPacket::Authenticate(const std::string& password)
{
// Just for Request, Indication and SuccessResponse messages.
if (this->klass == Class::ERROR_RESPONSE)
{
MS_ERROR("cannot set password for ErrorResponse messages");
return;
}
this->password = password;
}
void StunPacket::Serialize(uint8_t* buffer)
{
MS_TRACE();
// Some useful variables.
uint16_t usernamePaddedLen{ 0 };
uint16_t xorMappedAddressPaddedLen{ 0 };
bool addXorMappedAddress =
((this->xorMappedAddress != nullptr) && this->method == StunPacket::Method::BINDING &&
this->klass == Class::SUCCESS_RESPONSE);
bool addErrorCode = ((this->errorCode != 0u) && this->klass == Class::ERROR_RESPONSE);
bool addMessageIntegrity = (this->klass != Class::ERROR_RESPONSE && !this->password.empty());
bool addFingerprint{ true }; // Do always.
// Update data pointer.
this->data = buffer;
// First calculate the total required size for the entire packet.
this->size = 20; // Header.
if (!this->username.empty())
{
usernamePaddedLen = Utils::Byte::PadTo4Bytes(static_cast<uint16_t>(this->username.length()));
this->size += 4 + usernamePaddedLen;
}
if (this->priority != 0u)
this->size += 4 + 4;
if (this->iceControlling != 0u)
this->size += 4 + 8;
if (this->iceControlled != 0u)
this->size += 4 + 8;
if (this->hasUseCandidate)
this->size += 4;
if (addXorMappedAddress)
{
switch (this->xorMappedAddress->sa_family)
{
case AF_INET:
{
xorMappedAddressPaddedLen = 8;
this->size += 4 + 8;
break;
}
case AF_INET6:
{
xorMappedAddressPaddedLen = 20;
this->size += 4 + 20;
break;
}
default:
{
MS_ERROR("invalid inet family in XOR-MAPPED-ADDRESS attribute");
addXorMappedAddress = false;
}
}
}
if (addErrorCode)
this->size += 4 + 4;
if (addMessageIntegrity)
this->size += 4 + 20;
if (addFingerprint)
this->size += 4 + 4;
// Merge class and method fields into type.
uint16_t typeField = (static_cast<uint16_t>(this->method) & 0x0f80) << 2;
typeField |= (static_cast<uint16_t>(this->method) & 0x0070) << 1;
typeField |= (static_cast<uint16_t>(this->method) & 0x000f);
typeField |= (static_cast<uint16_t>(this->klass) & 0x02) << 7;
typeField |= (static_cast<uint16_t>(this->klass) & 0x01) << 4;
// Set type field.
Utils::Byte::Set2Bytes(buffer, 0, typeField);
// Set length field.
Utils::Byte::Set2Bytes(buffer, 2, static_cast<uint16_t>(this->size) - 20);
// Set magic cookie.
std::memcpy(buffer + 4, StunPacket::magicCookie, 4);
// Set TransactionId field.
std::memcpy(buffer + 8, this->transactionId, 12);
// Update the transaction ID pointer.
this->transactionId = buffer + 8;
// Add atributes.
size_t pos{ 20 };
// Add USERNAME.
if (usernamePaddedLen != 0u)
{
Utils::Byte::Set2Bytes(buffer, pos, static_cast<uint16_t>(Attribute::USERNAME));
Utils::Byte::Set2Bytes(buffer, pos + 2, static_cast<uint16_t>(this->username.length()));
std::memcpy(buffer + pos + 4, this->username.c_str(), this->username.length());
pos += 4 + usernamePaddedLen;
}
// Add PRIORITY.
if (this->priority != 0u)
{
Utils::Byte::Set2Bytes(buffer, pos, static_cast<uint16_t>(Attribute::PRIORITY));
Utils::Byte::Set2Bytes(buffer, pos + 2, 4);
Utils::Byte::Set4Bytes(buffer, pos + 4, this->priority);
pos += 4 + 4;
}
// Add ICE-CONTROLLING.
if (this->iceControlling != 0u)
{
Utils::Byte::Set2Bytes(buffer, pos, static_cast<uint16_t>(Attribute::ICE_CONTROLLING));
Utils::Byte::Set2Bytes(buffer, pos + 2, 8);
Utils::Byte::Set8Bytes(buffer, pos + 4, this->iceControlling);
pos += 4 + 8;
}
// Add ICE-CONTROLLED.
if (this->iceControlled != 0u)
{
Utils::Byte::Set2Bytes(buffer, pos, static_cast<uint16_t>(Attribute::ICE_CONTROLLED));
Utils::Byte::Set2Bytes(buffer, pos + 2, 8);
Utils::Byte::Set8Bytes(buffer, pos + 4, this->iceControlled);
pos += 4 + 8;
}
// Add USE-CANDIDATE.
if (this->hasUseCandidate)
{
Utils::Byte::Set2Bytes(buffer, pos, static_cast<uint16_t>(Attribute::USE_CANDIDATE));
Utils::Byte::Set2Bytes(buffer, pos + 2, 0);
pos += 4;
}
// Add XOR-MAPPED-ADDRESS
if (addXorMappedAddress)
{
Utils::Byte::Set2Bytes(buffer, pos, static_cast<uint16_t>(Attribute::XOR_MAPPED_ADDRESS));
Utils::Byte::Set2Bytes(buffer, pos + 2, xorMappedAddressPaddedLen);
uint8_t* attrValue = buffer + pos + 4;
switch (this->xorMappedAddress->sa_family)
{
case AF_INET:
{
// Set first byte to 0.
attrValue[0] = 0;
// Set inet family.
attrValue[1] = 0x01;
// Set port and XOR it.
std::memcpy(
attrValue + 2,
&(reinterpret_cast<const sockaddr_in*>(this->xorMappedAddress))->sin_port,
2);
attrValue[2] ^= StunPacket::magicCookie[0];
attrValue[3] ^= StunPacket::magicCookie[1];
// Set address and XOR it.
std::memcpy(
attrValue + 4,
&(reinterpret_cast<const sockaddr_in*>(this->xorMappedAddress))->sin_addr.s_addr,
4);
attrValue[4] ^= StunPacket::magicCookie[0];
attrValue[5] ^= StunPacket::magicCookie[1];
attrValue[6] ^= StunPacket::magicCookie[2];
attrValue[7] ^= StunPacket::magicCookie[3];
pos += 4 + 8;
break;
}
case AF_INET6:
{
// Set first byte to 0.
attrValue[0] = 0;
// Set inet family.
attrValue[1] = 0x02;
// Set port and XOR it.
std::memcpy(
attrValue + 2,
&(reinterpret_cast<const sockaddr_in6*>(this->xorMappedAddress))->sin6_port,
2);
attrValue[2] ^= StunPacket::magicCookie[0];
attrValue[3] ^= StunPacket::magicCookie[1];
// Set address and XOR it.
std::memcpy(
attrValue + 4,
&(reinterpret_cast<const sockaddr_in6*>(this->xorMappedAddress))->sin6_addr.s6_addr,
16);
attrValue[4] ^= StunPacket::magicCookie[0];
attrValue[5] ^= StunPacket::magicCookie[1];
attrValue[6] ^= StunPacket::magicCookie[2];
attrValue[7] ^= StunPacket::magicCookie[3];
attrValue[8] ^= this->transactionId[0];
attrValue[9] ^= this->transactionId[1];
attrValue[10] ^= this->transactionId[2];
attrValue[11] ^= this->transactionId[3];
attrValue[12] ^= this->transactionId[4];
attrValue[13] ^= this->transactionId[5];
attrValue[14] ^= this->transactionId[6];
attrValue[15] ^= this->transactionId[7];
attrValue[16] ^= this->transactionId[8];
attrValue[17] ^= this->transactionId[9];
attrValue[18] ^= this->transactionId[10];
attrValue[19] ^= this->transactionId[11];
pos += 4 + 20;
break;
}
}
}
// Add ERROR-CODE.
if (addErrorCode)
{
Utils::Byte::Set2Bytes(buffer, pos, static_cast<uint16_t>(Attribute::ERROR_CODE));
Utils::Byte::Set2Bytes(buffer, pos + 2, 4);
auto codeClass = static_cast<uint8_t>(this->errorCode / 100);
uint8_t codeNumber = static_cast<uint8_t>(this->errorCode) - (codeClass * 100);
Utils::Byte::Set2Bytes(buffer, pos + 4, 0);
Utils::Byte::Set1Byte(buffer, pos + 6, codeClass);
Utils::Byte::Set1Byte(buffer, pos + 7, codeNumber);
pos += 4 + 4;
}
// Add MESSAGE-INTEGRITY.
if (addMessageIntegrity)
{
// Ignore FINGERPRINT.
if (addFingerprint)
Utils::Byte::Set2Bytes(buffer, 2, static_cast<uint16_t>(this->size - 20 - 8));
// Calculate the HMAC-SHA1 of the packet according to MESSAGE-INTEGRITY rules.
auto computedMessageIntegrity = openssl_HMACsha1(this->password.data(), this->password.size(), buffer, pos);
Utils::Byte::Set2Bytes(buffer, pos, static_cast<uint16_t>(Attribute::MESSAGE_INTEGRITY));
Utils::Byte::Set2Bytes(buffer, pos + 2, 20);
std::memcpy(buffer + pos + 4, computedMessageIntegrity.data(), computedMessageIntegrity.size());
// Update the pointer.
this->messageIntegrity = buffer + pos + 4;
pos += 4 + 20;
// Restore length field.
if (addFingerprint)
Utils::Byte::Set2Bytes(buffer, 2, static_cast<uint16_t>(this->size - 20));
}
else
{
// Unset the pointer (if it was set).
this->messageIntegrity = nullptr;
}
// Add FINGERPRINT.
if (addFingerprint)
{
// Compute the CRC32 of the packet up to (but excluding) the FINGERPRINT
// attribute and XOR it with 0x5354554e.
uint32_t computedFingerprint = GetCRC32(buffer, pos) ^ 0x5354554e;
Utils::Byte::Set2Bytes(buffer, pos, static_cast<uint16_t>(Attribute::FINGERPRINT));
Utils::Byte::Set2Bytes(buffer, pos + 2, 4);
Utils::Byte::Set4Bytes(buffer, pos + 4, computedFingerprint);
pos += 4 + 4;
// Set flag.
this->hasFingerprint = true;
}
else
{
this->hasFingerprint = false;
}
MS_ASSERT(pos == this->size, "pos != this->size");
}
} // namespace RTC
/**
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_STUN_PACKET_HPP
#define MS_RTC_STUN_PACKET_HPP
#include "logger.h"
#include "Utils.hpp"
#include <string>
namespace RTC
{
class StunPacket
{
public:
// STUN message class.
enum class Class : uint16_t
{
REQUEST = 0,
INDICATION = 1,
SUCCESS_RESPONSE = 2,
ERROR_RESPONSE = 3
};
// STUN message method.
enum class Method : uint16_t
{
BINDING = 1
};
// Attribute type.
enum class Attribute : uint16_t
{
MAPPED_ADDRESS = 0x0001,
USERNAME = 0x0006,
MESSAGE_INTEGRITY = 0x0008,
ERROR_CODE = 0x0009,
UNKNOWN_ATTRIBUTES = 0x000A,
REALM = 0x0014,
NONCE = 0x0015,
XOR_MAPPED_ADDRESS = 0x0020,
PRIORITY = 0x0024,
USE_CANDIDATE = 0x0025,
SOFTWARE = 0x8022,
ALTERNATE_SERVER = 0x8023,
FINGERPRINT = 0x8028,
ICE_CONTROLLED = 0x8029,
ICE_CONTROLLING = 0x802A
};
// Authentication result.
enum class Authentication
{
OK = 0,
UNAUTHORIZED = 1,
BAD_REQUEST = 2
};
public:
static bool IsStun(const uint8_t* data, size_t len)
{
// clang-format off
return (
// STUN headers are 20 bytes.
(len >= 20) &&
// DOC: https://tools.ietf.org/html/draft-ietf-avtcore-rfc5764-mux-fixes
(data[0] < 3) &&
// Magic cookie must match.
(data[4] == StunPacket::magicCookie[0]) && (data[5] == StunPacket::magicCookie[1]) &&
(data[6] == StunPacket::magicCookie[2]) && (data[7] == StunPacket::magicCookie[3])
);
// clang-format on
}
static StunPacket* Parse(const uint8_t* data, size_t len);
private:
static const uint8_t magicCookie[];
public:
StunPacket(
Class klass, Method method, const uint8_t* transactionId, const uint8_t* data, size_t size);
~StunPacket();
void Dump() const;
Class GetClass() const
{
return this->klass;
}
Method GetMethod() const
{
return this->method;
}
const uint8_t* GetData() const
{
return this->data;
}
size_t GetSize() const
{
return this->size;
}
void SetUsername(const char* username, size_t len)
{
this->username.assign(username, len);
}
void SetPriority(uint32_t priority)
{
this->priority = priority;
}
void SetIceControlling(uint64_t iceControlling)
{
this->iceControlling = iceControlling;
}
void SetIceControlled(uint64_t iceControlled)
{
this->iceControlled = iceControlled;
}
void SetUseCandidate()
{
this->hasUseCandidate = true;
}
void SetXorMappedAddress(const struct sockaddr* xorMappedAddress)
{
this->xorMappedAddress = xorMappedAddress;
}
void SetErrorCode(uint16_t errorCode)
{
this->errorCode = errorCode;
}
void SetMessageIntegrity(const uint8_t* messageIntegrity)
{
this->messageIntegrity = messageIntegrity;
}
void SetFingerprint()
{
this->hasFingerprint = true;
}
const std::string& GetUsername() const
{
return this->username;
}
uint32_t GetPriority() const
{
return this->priority;
}
uint64_t GetIceControlling() const
{
return this->iceControlling;
}
uint64_t GetIceControlled() const
{
return this->iceControlled;
}
bool HasUseCandidate() const
{
return this->hasUseCandidate;
}
uint16_t GetErrorCode() const
{
return this->errorCode;
}
bool HasMessageIntegrity() const
{
return (this->messageIntegrity ? true : false);
}
bool HasFingerprint() const
{
return this->hasFingerprint;
}
Authentication CheckAuthentication(
const std::string& localUsername, const std::string& localPassword);
StunPacket* CreateSuccessResponse();
StunPacket* CreateErrorResponse(uint16_t errorCode);
void Authenticate(const std::string& password);
void Serialize(uint8_t* buffer);
private:
// Passed by argument.
Class klass; // 2 bytes.
Method method; // 2 bytes.
const uint8_t* transactionId{ nullptr }; // 12 bytes.
uint8_t* data{ nullptr }; // Pointer to binary data.
size_t size{ 0u }; // The full message size (including header).
// STUN attributes.
std::string username; // Less than 513 bytes.
uint32_t priority{ 0u }; // 4 bytes unsigned integer.
uint64_t iceControlling{ 0u }; // 8 bytes unsigned integer.
uint64_t iceControlled{ 0u }; // 8 bytes unsigned integer.
bool hasUseCandidate{ false }; // 0 bytes.
const uint8_t* messageIntegrity{ nullptr }; // 20 bytes.
bool hasFingerprint{ false }; // 4 bytes.
const struct sockaddr* xorMappedAddress{ nullptr }; // 8 or 20 bytes.
uint16_t errorCode{ 0u }; // 4 bytes (no reason phrase).
std::string password;
};
} // namespace RTC
#endif
/**
ISC License
Copyright © 2015, Iñaki Baz Castillo <ibc@aliax.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef MS_UTILS_HPP
#define MS_UTILS_HPP
#if defined(_WIN32)
#include <winsock2.h>
#include <ws2tcpip.h>
#include <Iphlpapi.h>
#pragma comment (lib, "Ws2_32.lib")
#pragma comment(lib,"Iphlpapi.lib")
#else
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#endif // defined(_WIN32)
#include <algorithm>// std::transform(), std::find(), std::min(), std::max()
#include <cinttypes>// PRIu64, etc
#include <cmath>
#include <cstddef>// size_t
#include <cstdint>// uint8_t, etc
#include <cstring>// std::memcmp(), std::memcpy()
#include <memory>
#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <string>
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
/*
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
*
* Use of this source code is governed by MIT 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 "WebRtcTransport.h"
#include <iostream>
#include "RtpExt.h"
#include "Rtcp/Rtcp.h"
#include "Rtcp/RtcpFCI.h"
#include "Rtsp/RtpReceiver.h"
#define RTX_SSRC_OFFSET 2
#define RTP_CNAME "zlmediakit-rtp"
#define RTP_LABEL "zlmediakit-label"
#define RTP_MSLABEL "zlmediakit-mslabel"
#define RTP_MSID RTP_MSLABEL " " RTP_LABEL
//RTC配置项目
namespace RTC {
#define RTC_FIELD "rtc."
//rtp和rtcp接受超时时间
const string kTimeOutSec = RTC_FIELD"timeoutSec";
//服务器外网ip
const string kExternIP = RTC_FIELD"externIP";
//设置remb比特率,非0时关闭twcc并开启remb。该设置在rtc推流时有效,可以控制推流画质
const string kRembBitRate = RTC_FIELD"rembBitRate";
static onceToken token([]() {
mINI::Instance()[kTimeOutSec] = 15;
mINI::Instance()[kExternIP] = "";
mINI::Instance()[kRembBitRate] = 0;
});
}//namespace RTC
WebRtcTransport::WebRtcTransport(const EventPoller::Ptr &poller) {
_poller = poller;
_dtls_transport = std::make_shared<RTC::DtlsTransport>(poller, this);
_ice_server = std::make_shared<RTC::IceServer>(this, makeRandStr(4), makeRandStr(28).substr(4));
}
void WebRtcTransport::onCreate(){
}
void WebRtcTransport::onDestory(){
_dtls_transport = nullptr;
_ice_server = nullptr;
}
const EventPoller::Ptr& WebRtcTransport::getPoller() const{
return _poller;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void WebRtcTransport::OnIceServerSendStunPacket(const RTC::IceServer *iceServer, const RTC::StunPacket *packet, RTC::TransportTuple *tuple) {
onSendSockData((char *) packet->GetData(), packet->GetSize(), (struct sockaddr_in *) tuple);
}
void WebRtcTransport::OnIceServerSelectedTuple(const RTC::IceServer *iceServer, RTC::TransportTuple *tuple) {
InfoL;
}
void WebRtcTransport::OnIceServerConnected(const RTC::IceServer *iceServer) {
InfoL;
}
void WebRtcTransport::OnIceServerCompleted(const RTC::IceServer *iceServer) {
InfoL;
if (_answer_sdp->media[0].role == DtlsRole::passive) {
_dtls_transport->Run(RTC::DtlsTransport::Role::SERVER);
} else {
_dtls_transport->Run(RTC::DtlsTransport::Role::CLIENT);
}
}
void WebRtcTransport::OnIceServerDisconnected(const RTC::IceServer *iceServer) {
InfoL;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void WebRtcTransport::OnDtlsTransportConnected(
const RTC::DtlsTransport *dtlsTransport,
RTC::SrtpSession::CryptoSuite srtpCryptoSuite,
uint8_t *srtpLocalKey,
size_t srtpLocalKeyLen,
uint8_t *srtpRemoteKey,
size_t srtpRemoteKeyLen,
std::string &remoteCert) {
InfoL;
_srtp_session_send = std::make_shared<RTC::SrtpSession>(RTC::SrtpSession::Type::OUTBOUND, srtpCryptoSuite, srtpLocalKey, srtpLocalKeyLen);
_srtp_session_recv = std::make_shared<RTC::SrtpSession>(RTC::SrtpSession::Type::INBOUND, srtpCryptoSuite, srtpRemoteKey, srtpRemoteKeyLen);
onStartWebRTC();
}
void WebRtcTransport::OnDtlsTransportSendData(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) {
onSendSockData((char *)data, len);
}
void WebRtcTransport::OnDtlsTransportConnecting(const RTC::DtlsTransport *dtlsTransport) {
InfoL;
}
void WebRtcTransport::OnDtlsTransportFailed(const RTC::DtlsTransport *dtlsTransport) {
InfoL;
onShutdown(SockException(Err_shutdown, "dtls transport failed"));
}
void WebRtcTransport::OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) {
InfoL;
onShutdown(SockException(Err_shutdown, "dtls close notify received"));
}
void WebRtcTransport::OnDtlsTransportApplicationDataReceived(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) {
InfoL << hexdump(data, len);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void WebRtcTransport::onSendSockData(const char *buf, size_t len, bool flush){
auto tuple = _ice_server->GetSelectedTuple();
assert(tuple);
onSendSockData(buf, len, (struct sockaddr_in *) tuple, flush);
}
const RtcSession& WebRtcTransport::getSdp(SdpType type) const{
switch (type) {
case SdpType::offer: return *_offer_sdp;
case SdpType::answer: return *_answer_sdp;
default: throw std::invalid_argument("不识别的sdp类型");
}
}
RTC::TransportTuple* WebRtcTransport::getSelectedTuple() const{
return _ice_server->GetSelectedTuple();
}
void WebRtcTransport::sendRtcpRemb(uint32_t ssrc, size_t bit_rate) {
auto remb = FCI_REMB::create({ssrc}, (uint32_t)bit_rate);
auto fb = RtcpFB::create(PSFBType::RTCP_PSFB_REMB, remb.data(), remb.size());
fb->ssrc = htonl(0);
fb->ssrc_media = htonl(ssrc);
sendRtcpPacket((char *) fb.get(), fb->getSize(), true);
TraceL << ssrc << " " << bit_rate;
}
void WebRtcTransport::sendRtcpPli(uint32_t ssrc) {
auto pli = RtcpFB::create(PSFBType::RTCP_PSFB_PLI);
pli->ssrc = htonl(0);
pli->ssrc_media = htonl(ssrc);
sendRtcpPacket((char *) pli.get(), pli->getSize(), true);
}
string getFingerprint(const string &algorithm_str, const std::shared_ptr<RTC::DtlsTransport> &transport){
auto algorithm = RTC::DtlsTransport::GetFingerprintAlgorithm(algorithm_str);
for (auto &finger_prints : transport->GetLocalFingerprints()) {
if (finger_prints.algorithm == algorithm) {
return finger_prints.value;
}
}
throw std::invalid_argument(StrPrinter << "不支持的加密算法:" << algorithm_str);
}
void WebRtcTransport::setRemoteDtlsFingerprint(const RtcSession &remote){
//设置远端dtls签名
RTC::DtlsTransport::Fingerprint remote_fingerprint;
remote_fingerprint.algorithm = RTC::DtlsTransport::GetFingerprintAlgorithm(_offer_sdp->media[0].fingerprint.algorithm);
remote_fingerprint.value = _offer_sdp->media[0].fingerprint.hash;
_dtls_transport->SetRemoteFingerprint(remote_fingerprint);
}
void WebRtcTransport::onCheckSdp(SdpType type, RtcSession &sdp){
for (auto &m : sdp.media) {
if (m.type != TrackApplication && !m.rtcp_mux) {
throw std::invalid_argument("只支持rtcp-mux模式");
}
}
if (sdp.group.mids.empty()) {
throw std::invalid_argument("只支持group BUNDLE模式");
}
if (type == SdpType::offer) {
sdp.checkValidSSRC();
}
}
void WebRtcTransport::onRtcConfigure(RtcConfigure &configure) const {
//开启remb后关闭twcc,因为开启twcc后remb无效
GET_CONFIG(size_t, remb_bit_rate, RTC::kRembBitRate);
configure.enableTWCC(!remb_bit_rate);
}
std::string WebRtcTransport::getAnswerSdp(const string &offer){
try {
//// 解析offer sdp ////
_offer_sdp = std::make_shared<RtcSession>();
_offer_sdp->loadFrom(offer);
onCheckSdp(SdpType::offer, *_offer_sdp);
setRemoteDtlsFingerprint(*_offer_sdp);
//// sdp 配置 ////
SdpAttrFingerprint fingerprint;
fingerprint.algorithm = _offer_sdp->media[0].fingerprint.algorithm;
fingerprint.hash = getFingerprint(fingerprint.algorithm, _dtls_transport);
RtcConfigure configure;
configure.setDefaultSetting(_ice_server->GetUsernameFragment(), _ice_server->GetPassword(),
RtpDirection::sendrecv, fingerprint);
onRtcConfigure(configure);
//// 生成answer sdp ////
_answer_sdp = configure.createAnswer(*_offer_sdp);
onCheckSdp(SdpType::answer, *_answer_sdp);
return _answer_sdp->toString();
} catch (exception &ex) {
onShutdown(SockException(Err_shutdown, ex.what()));
throw;
}
}
bool is_dtls(char *buf) {
return ((*buf > 19) && (*buf < 64));
}
bool is_rtp(char *buf) {
RtpHeader *header = (RtpHeader *) buf;
return ((header->pt < 64) || (header->pt >= 96));
}
bool is_rtcp(char *buf) {
RtpHeader *header = (RtpHeader *) buf;
return ((header->pt >= 64) && (header->pt < 96));
}
void WebRtcTransport::inputSockData(char *buf, size_t len, RTC::TransportTuple *tuple) {
if (RTC::StunPacket::IsStun((const uint8_t *) buf, len)) {
RTC::StunPacket *packet = RTC::StunPacket::Parse((const uint8_t *) buf, len);
if (packet == nullptr) {
WarnL << "parse stun error" << std::endl;
return;
}
_ice_server->ProcessStunPacket(packet, tuple);
return;
}
if (is_dtls(buf)) {
_dtls_transport->ProcessDtlsData((uint8_t *) buf, len);
return;
}
if (is_rtp(buf)) {
if (_srtp_session_recv->DecryptSrtp((uint8_t *) buf, &len)) {
onRtp(buf, len);
} else {
WarnL;
}
return;
}
if (is_rtcp(buf)) {
if (_srtp_session_recv->DecryptSrtcp((uint8_t *) buf, &len)) {
onRtcp(buf, len);
} else {
WarnL;
}
return;
}
}
void WebRtcTransport::sendRtpPacket(const char *buf, size_t len, bool flush, void *ctx) {
if (_srtp_session_send) {
//预留rtx加入的两个字节
CHECK(len + SRTP_MAX_TRAILER_LEN + 2 <= sizeof(_srtp_buf));
memcpy(_srtp_buf, buf, len);
onBeforeEncryptRtp((char *) _srtp_buf, len, ctx);
if (_srtp_session_send->EncryptRtp(_srtp_buf, &len)) {
onSendSockData((char *) _srtp_buf, len, flush);
}
}
}
void WebRtcTransport::sendRtcpPacket(const char *buf, size_t len, bool flush, void *ctx){
if (_srtp_session_send) {
CHECK(len + SRTP_MAX_TRAILER_LEN <= sizeof(_srtp_buf));
memcpy(_srtp_buf, buf, len);
onBeforeEncryptRtcp((char *) _srtp_buf, len, ctx);
if (_srtp_session_send->EncryptRtcp(_srtp_buf, &len)) {
onSendSockData((char *) _srtp_buf, len, flush);
}
}
}
///////////////////////////////////////////////////////////////////////////////////
WebRtcTransportImp::Ptr WebRtcTransportImp::create(const EventPoller::Ptr &poller){
WebRtcTransportImp::Ptr ret(new WebRtcTransportImp(poller), [](WebRtcTransportImp *ptr){
ptr->onDestory();
delete ptr;
});
ret->onCreate();
return ret;
}
void WebRtcTransportImp::onCreate(){
WebRtcTransport::onCreate();
_socket = Socket::createSocket(getPoller(), false);
//随机端口,绑定全部网卡
_socket->bindUdpSock(0);
weak_ptr<WebRtcTransportImp> weak_self = shared_from_this();
_socket->setOnRead([weak_self](const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) mutable {
auto strong_self = weak_self.lock();
if (strong_self) {
strong_self->inputSockData(buf->data(), buf->size(), addr);
}
});
_self = shared_from_this();
GET_CONFIG(float, timeoutSec, RTC::kTimeOutSec);
_timer = std::make_shared<Timer>(timeoutSec / 2, [weak_self]() {
auto strong_self = weak_self.lock();
if (!strong_self) {
return false;
}
if (strong_self->_alive_ticker.elapsedTime() > timeoutSec * 1000) {
strong_self->onShutdown(SockException(Err_timeout, "接受rtp和rtcp超时"));
}
return true;
}, getPoller());
}
WebRtcTransportImp::WebRtcTransportImp(const EventPoller::Ptr &poller) : WebRtcTransport(poller) {
InfoL << this;
}
WebRtcTransportImp::~WebRtcTransportImp() {
InfoL << this;
}
void WebRtcTransportImp::onDestory() {
WebRtcTransport::onDestory();
uint64_t duration = _alive_ticker.createdTime() / 1000;
//流量统计事件广播
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
if (_reader) {
WarnL << "RTC播放器("
<< _media_info._vhost << "/"
<< _media_info._app << "/"
<< _media_info._streamid
<< ")结束播放,耗时(s):" << duration;
if (_bytes_usage >= iFlowThreshold * 1024) {
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, _bytes_usage, duration, true, static_cast<SockInfo &>(*_socket));
}
}
if (_push_src) {
WarnL << "RTC推流器("
<< _media_info._vhost << "/"
<< _media_info._app << "/"
<< _media_info._streamid
<< ")结束推流,耗时(s):" << duration;
if (_bytes_usage >= iFlowThreshold * 1024) {
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, _bytes_usage, duration, false, static_cast<SockInfo &>(*_socket));
}
}
}
void WebRtcTransportImp::attach(const RtspMediaSource::Ptr &src, const MediaInfo &info, bool is_play) {
assert(src);
_media_info = info;
if (is_play) {
_play_src = src;
} else {
_push_src = src;
}
}
void WebRtcTransportImp::onSendSockData(const char *buf, size_t len, struct sockaddr_in *dst, bool flush) {
auto ptr = BufferRaw::create();
ptr->assign(buf, len);
_socket->send(ptr, (struct sockaddr *)(dst), sizeof(struct sockaddr), flush);
}
///////////////////////////////////////////////////////////////////
bool WebRtcTransportImp::canSendRtp() const{
auto &sdp = getSdp(SdpType::answer);
return _play_src && (sdp.media[0].direction == RtpDirection::sendrecv || sdp.media[0].direction == RtpDirection::sendonly);
}
bool WebRtcTransportImp::canRecvRtp() const{
auto &sdp = getSdp(SdpType::answer);
return _push_src && (sdp.media[0].direction == RtpDirection::sendrecv || sdp.media[0].direction == RtpDirection::recvonly);
}
void WebRtcTransportImp::onStartWebRTC() {
//获取ssrc和pt相关信息,届时收到rtp和rtcp时分别可以根据pt和ssrc找到相关的信息
for (auto &m_answer : getSdp(SdpType::answer).media) {
auto m_offer = getSdp(SdpType::offer).getMedia(m_answer.type);
auto info = std::make_shared<RtpPayloadInfo>();
info->media = &m_answer;
info->answer_ssrc_rtp = m_answer.getRtpSSRC();
info->answer_ssrc_rtx = m_answer.getRtxSSRC();
info->offer_ssrc_rtp = m_offer->getRtpSSRC();
info->offer_ssrc_rtx = m_offer->getRtxSSRC();
info->plan_rtp = &m_answer.plan[0];;
info->plan_rtx = m_answer.getRelatedRtxPlan(info->plan_rtp->pt);
info->rtcp_context_recv = std::make_shared<RtcpContext>(info->plan_rtp->sample_rate, true);
info->rtcp_context_send = std::make_shared<RtcpContext>(info->plan_rtp->sample_rate, false);
info->receiver = std::make_shared<RtpReceiverImp>([info, this](RtpPacket::Ptr rtp) mutable {
onSortedRtp(*info, std::move(rtp));
});
info->nack_ctx.setOnNack([info, this](const FCI_NACK &nack) mutable {
onSendNack(*info, nack);
});
//send ssrc --> RtpPayloadInfo
_rtp_info_ssrc[info->answer_ssrc_rtp] = std::make_pair(false, info);
_rtp_info_ssrc[info->answer_ssrc_rtx] = std::make_pair(true, info);
//recv ssrc --> RtpPayloadInfo
_rtp_info_ssrc[info->offer_ssrc_rtp] = std::make_pair(false, info);;
_rtp_info_ssrc[info->offer_ssrc_rtx] = std::make_pair(true, info);;
//rtp pt --> RtpPayloadInfo
_rtp_info_pt.emplace(info->plan_rtp->pt, std::make_pair(false, info));
if (info->plan_rtx) {
//rtx pt --> RtpPayloadInfo
_rtp_info_pt.emplace(info->plan_rtx->pt, std::make_pair(true, info));
}
if (m_offer->type != TrackApplication) {
//记录rtp ext类型与id的关系,方便接收或发送rtp时修改rtp ext id
for (auto &ext : m_offer->extmap) {
auto ext_type = RtpExt::getExtType(ext.ext);
_rtp_ext_id_to_type.emplace(ext.id, ext_type);
_rtp_ext_type_to_id.emplace(ext_type, ext.id);
}
}
}
if (canRecvRtp()) {
_push_src->setSdp(getSdp(SdpType::answer).toRtspSdp());
}
if (canSendRtp()) {
_reader = _play_src->getRing()->attach(getPoller(), true);
weak_ptr<WebRtcTransportImp> weak_self = shared_from_this();
_reader->setReadCB([weak_self](const RtspMediaSource::RingDataType &pkt) {
auto strongSelf = weak_self.lock();
if (!strongSelf) {
return;
}
size_t i = 0;
pkt->for_each([&](const RtpPacket::Ptr &rtp) {
strongSelf->onSendRtp(rtp, ++i == pkt->size());
});
});
_reader->setDetachCB([weak_self](){
auto strongSelf = weak_self.lock();
if (!strongSelf) {
return;
}
strongSelf->onShutdown(SockException(Err_eof, "rtsp ring buffer detached"));
});
RtcSession rtsp_send_sdp;
rtsp_send_sdp.loadFrom(_play_src->getSdp(), false);
for (auto &m : getSdp(SdpType::answer).media) {
if (m.type == TrackApplication) {
continue;
}
auto rtsp_media = rtsp_send_sdp.getMedia(m.type);
if (rtsp_media && getCodecId(rtsp_media->plan[0].codec) == getCodecId(m.plan[0].codec)) {
auto it = _rtp_info_pt.find(m.plan[0].pt);
CHECK(it != _rtp_info_pt.end());
//记录发送rtp时约定的信息,届时发送rtp时需要修改pt和ssrc
_send_rtp_info[m.type] = it->second.second;
}
}
}
//使用完毕后,释放强引用,这样确保推流器断开后能及时注销媒体
_play_src = nullptr;
}
void WebRtcTransportImp::onCheckSdp(SdpType type, RtcSession &sdp){
WebRtcTransport::onCheckSdp(type, sdp);
if (type != SdpType::answer) {
//我们只修改answer sdp
return;
}
//修改answer sdp的ip、端口信息
GET_CONFIG(string, extern_ip, RTC::kExternIP);
for (auto &m : sdp.media) {
m.addr.reset();
m.addr.address = extern_ip.empty() ? SockUtil::get_local_ip() : extern_ip;
m.rtcp_addr.reset();
m.rtcp_addr.address = m.addr.address;
m.rtcp_addr.port = _socket->get_local_port();
m.port = m.rtcp_addr.port;
sdp.origin.address = m.addr.address;
}
if (!canSendRtp()) {
//设置我们发送的rtp的ssrc
return;
}
for (auto &m : sdp.media) {
if (m.type == TrackApplication) {
continue;
}
//添加answer sdp的ssrc信息
m.rtp_rtx_ssrc.emplace_back();
m.rtp_rtx_ssrc[0].ssrc = _play_src->getSsrc(m.type);
m.rtp_rtx_ssrc[0].cname = RTP_CNAME;
m.rtp_rtx_ssrc[0].label = RTP_LABEL;
m.rtp_rtx_ssrc[0].mslabel = RTP_MSLABEL;
m.rtp_rtx_ssrc[0].msid = RTP_MSID;
if (m.getRelatedRtxPlan(m.plan[0].pt)) {
m.rtp_rtx_ssrc.emplace_back();
m.rtp_rtx_ssrc[1] = m.rtp_rtx_ssrc[0];
m.rtp_rtx_ssrc[1].ssrc += RTX_SSRC_OFFSET;
}
}
}
void WebRtcTransportImp::onRtcConfigure(RtcConfigure &configure) const {
WebRtcTransport::onRtcConfigure(configure);
if (_play_src) {
//这是播放,同时也可能有推流
configure.video.direction = _push_src ? RtpDirection::sendrecv : RtpDirection::sendonly;
configure.audio.direction = configure.video.direction;
configure.setPlayRtspInfo(_play_src->getSdp());
} else if (_push_src) {
//这只是推流
configure.video.direction = RtpDirection::recvonly;
configure.audio.direction = RtpDirection::recvonly;
} else {
throw std::invalid_argument("未设置播放或推流的媒体源");
}
//添加接收端口candidate信息
configure.addCandidate(*getIceCandidate());
}
SdpAttrCandidate::Ptr WebRtcTransportImp::getIceCandidate() const{
auto candidate = std::make_shared<SdpAttrCandidate>();
candidate->foundation = "udpcandidate";
//rtp端口
candidate->component = 1;
candidate->transport = "udp";
//优先级,单candidate时随便
candidate->priority = 100;
GET_CONFIG(string, extern_ip, RTC::kExternIP);
candidate->address = extern_ip.empty() ? SockUtil::get_local_ip() : extern_ip;
candidate->port = _socket->get_local_port();
candidate->type = "host";
return candidate;
}
///////////////////////////////////////////////////////////////////
class RtpReceiverImp : public RtpReceiver {
public:
RtpReceiverImp( function<void(RtpPacket::Ptr rtp)> cb){
_on_sort = std::move(cb);
}
~RtpReceiverImp() override = default;
bool inputRtp(TrackType type, int samplerate, uint8_t *ptr, size_t len){
return handleOneRtp((int) type, type, samplerate, ptr, len);
}
protected:
void onRtpSorted(RtpPacket::Ptr rtp, int track_index) override {
_on_sort(std::move(rtp));
}
private:
function<void(RtpPacket::Ptr rtp)> _on_sort;
};
void WebRtcTransportImp::onRtcp(const char *buf, size_t len) {
_bytes_usage += len;
auto rtcps = RtcpHeader::loadFromBytes((char *) buf, len);
for (auto rtcp : rtcps) {
switch ((RtcpType) rtcp->pt) {
case RtcpType::RTCP_SR : {
//对方汇报rtp发送情况
RtcpSR *sr = (RtcpSR *) rtcp;
auto it = _rtp_info_ssrc.find(sr->ssrc);
if (it != _rtp_info_ssrc.end()) {
auto rtx = it->second.first;
if (!rtx) {
auto &info = it->second.second;
info->rtcp_context_recv->onRtcp(sr);
auto rr = info->rtcp_context_recv->createRtcpRR(info->answer_ssrc_rtp, info->offer_ssrc_rtp);
sendRtcpPacket(rr->data(), rr->size(), true);
}
} else {
WarnL << "未识别的sr rtcp包:" << rtcp->dumpString();
}
break;
}
case RtcpType::RTCP_RR : {
_alive_ticker.resetTime();
//对方汇报rtp接收情况
RtcpRR *rr = (RtcpRR *) rtcp;
for (auto item : rr->getItemList()) {
auto it = _rtp_info_ssrc.find(item->ssrc);
if (it != _rtp_info_ssrc.end()) {
auto rtx = it->second.first;
if (!rtx) {
auto &info = it->second.second;
auto sr = info->rtcp_context_send->createRtcpSR(info->answer_ssrc_rtp);
sendRtcpPacket(sr->data(), sr->size(), true);
}
} else {
WarnL << "未识别的rr rtcp包:" << rtcp->dumpString();
}
}
break;
}
case RtcpType::RTCP_BYE : {
//对方汇报停止发送rtp
RtcpBye *bye = (RtcpBye *) rtcp;
for (auto ssrc : bye->getSSRC()) {
auto it = _rtp_info_ssrc.find(*ssrc);
if (it == _rtp_info_ssrc.end()) {
WarnL << "未识别的bye rtcp包:" << rtcp->dumpString();
continue;
}
_rtp_info_ssrc.erase(it);
}
onShutdown(SockException(Err_eof, "rtcp bye message received"));
break;
}
case RtcpType::RTCP_PSFB:
case RtcpType::RTCP_RTPFB: {
if ((RtcpType) rtcp->pt == RtcpType::RTCP_PSFB) {
break;
}
//RTPFB
switch ((RTPFBType) rtcp->report_count) {
case RTPFBType::RTCP_RTPFB_NACK : {
RtcpFB *fb = (RtcpFB *) rtcp;
auto it = _rtp_info_ssrc.find(fb->ssrc_media);
if (it == _rtp_info_ssrc.end()) {
WarnL << "未识别的 rtcp包:" << rtcp->dumpString();
return;
}
auto rtx = it->second.first;
if (!rtx) {
auto &info = it->second.second;
auto &fci = fb->getFci<FCI_NACK>();
info->nack_list.for_each_nack(fci, [&](const RtpPacket::Ptr &rtp) {
//rtp重传
onSendRtp(rtp, true, true);
});
}
break;
}
default: break;
}
break;
}
default: break;
}
}
}
///////////////////////////////////////////////////////////////////
void WebRtcTransportImp::changeRtpExtId(const RtpPayloadInfo *info, const RtpHeader *header, bool is_recv, bool is_rtx) const{
auto ext_map = RtpExt::getExtValue(header);
for (auto &pr : ext_map) {
if (is_recv) {
auto it = _rtp_ext_id_to_type.find(pr.first);
if (it == _rtp_ext_id_to_type.end()) {
WarnL << "接收rtp时,忽略不识别的rtp ext, id=" << (int) pr.first;
pr.second.clearExt();
continue;
}
pr.second.setType(it->second);
//重新赋值ext id为 ext type,作为后面处理ext的统一中间类型
pr.second.setExtId((uint8_t) it->second);
} else {
pr.second.setType((RtpExtType) pr.first);
auto it = _rtp_ext_type_to_id.find((RtpExtType) pr.first);
if (it == _rtp_ext_type_to_id.end()) {
WarnL << "发送rtp时, 忽略不被客户端支持rtp ext:" << pr.second.dumpString();
pr.second.clearExt();
continue;
}
//重新赋值ext id为客户端sdp声明的类型
pr.second.setExtId(it->second);
}
}
}
void WebRtcTransportImp::onRtp(const char *buf, size_t len) {
onRtp_l(buf, len, false);
}
void WebRtcTransportImp::onRtp_l(const char *buf, size_t len, bool rtx) {
if (!rtx) {
_bytes_usage += len;
_alive_ticker.resetTime();
}
RtpHeader *rtp = (RtpHeader *) buf;
//根据接收到的rtp的pt信息,找到该流的信息
auto it = _rtp_info_pt.find(rtp->pt);
if (it == _rtp_info_pt.end()) {
WarnL;
return;
}
auto &info = it->second.second;
if (!it->second.first) {
//这是普通的rtp数据
auto seq = ntohs(rtp->seq);
#if 0
if (!rtx && info->media->type == TrackVideo && seq % 100 == 0) {
//此处模拟接受丢包
DebugL << "recv dropped:" << seq;
return;
}
#endif
if (!rtx) {
//统计rtp接受情况,便于生成nack rtcp包
info->nack_ctx.received(seq);
//时间戳转换成毫秒
auto stamp_ms = ntohl(rtp->stamp) * uint64_t(1000) / info->plan_rtp->sample_rate;
//统计rtp收到的情况,好做rr汇报
info->rtcp_context_recv->onRtp(seq, stamp_ms, len);
}
//修改ext id至统一
changeRtpExtId(info.get(), rtp, true, rtx);
//解析并排序rtp
info->receiver->inputRtp(info->media->type, info->plan_rtp->sample_rate, (uint8_t *) buf, len);
return;
}
//这里是rtx重传包
//https://datatracker.ietf.org/doc/html/rfc4588#section-4
auto payload = rtp->getPayloadData();
auto size = rtp->getPayloadSize(len);
if (size < 2) {
return;
}
//前两个字节是原始的rtp的seq
auto origin_seq = payload[0] << 8 | payload[1];
InfoL << "received rtx rtp: " << origin_seq;
rtp->seq = htons(origin_seq);
rtp->ssrc = htonl(info->offer_ssrc_rtp);
rtp->pt = info->plan_rtp->pt;
memmove((uint8_t *) buf + 2, buf, payload - (uint8_t *) buf);
buf += 2;
len -= 2;
onRtp_l(buf, len, true);
}
void WebRtcTransportImp::onSendNack(RtpPayloadInfo &info, const FCI_NACK &nack) {
auto rtcp = RtcpFB::create(RTPFBType::RTCP_RTPFB_NACK, &nack, FCI_NACK::kSize);
rtcp->ssrc = htons(info.answer_ssrc_rtp);
rtcp->ssrc_media = htonl(info.offer_ssrc_rtp);
sendRtcpPacket((char *) rtcp.get(), rtcp->getSize(), true);
}
///////////////////////////////////////////////////////////////////
void WebRtcTransportImp::onSortedRtp(RtpPayloadInfo &info, RtpPacket::Ptr rtp) {
if (info.media->type == TrackVideo && _pli_ticker.elapsedTime() > 2000) {
//定期发送pli请求关键帧,方便非rtc等协议
_pli_ticker.resetTime();
sendRtcpPli(rtp->getSSRC());
//开启remb,则发送remb包调节比特率
GET_CONFIG(size_t, remb_bit_rate, RTC::kRembBitRate);
if (remb_bit_rate && getSdp(SdpType::answer).supportRtcpFb(SdpConst::kRembRtcpFb)) {
sendRtcpRemb(rtp->getSSRC(), remb_bit_rate);
}
}
if (_push_src) {
_push_src->onWrite(std::move(rtp), false);
}
}
///////////////////////////////////////////////////////////////////
void WebRtcTransportImp::onSendRtp(const RtpPacket::Ptr &rtp, bool flush, bool rtx){
auto &info = _send_rtp_info[rtp->type];
if (!info) {
//忽略,对方不支持该编码类型
return;
}
if (!rtx) {
//统计rtp发送情况,好做sr汇报
info->rtcp_context_send->onRtp(rtp->getSeq(), rtp->getStampMS(), rtp->size() - RtpPacket::kRtpTcpHeaderSize);
info->nack_list.push_back(rtp);
#if 0
//此处模拟发送丢包
if (rtp->type == TrackVideo && rtp->getSeq() % 100 == 0) {
DebugL << "send dropped:" << rtp->getSeq();
return;
}
#endif
} else {
WarnL << "send rtx rtp:" << rtp->getSeq();
}
pair<bool/*rtx*/, RtpPayloadInfo *> ctx{rtx, info.get()};
sendRtpPacket(rtp->data() + RtpPacket::kRtpTcpHeaderSize, rtp->size() - RtpPacket::kRtpTcpHeaderSize, flush, &ctx);
_bytes_usage += rtp->size() - RtpPacket::kRtpTcpHeaderSize;
}
void WebRtcTransportImp::onBeforeEncryptRtp(const char *buf, size_t &len, void *ctx) {
auto pr = (pair<bool/*rtx*/, RtpPayloadInfo *> *) ctx;
auto header = (RtpHeader *) buf;
if (!pr->first || !pr->second->plan_rtx) {
//普通的rtp,或者不支持rtx, 修改目标pt和ssrc
changeRtpExtId(pr->second, header, false, false);
header->pt = pr->second->plan_rtp->pt;
header->ssrc = htonl(pr->second->answer_ssrc_rtp);
} else {
//重传的rtp, rtx
changeRtpExtId(pr->second, header, false, true);
header->pt = pr->second->plan_rtx->pt;
if (pr->second->answer_ssrc_rtx) {
//有rtx单独的ssrc,有些情况下,浏览器支持rtx,但是未指定rtx单独的ssrc
header->ssrc = htonl(pr->second->answer_ssrc_rtx);
} else {
//未单独指定rtx的ssrc,那么使用rtp的ssrc
header->ssrc = htonl(pr->second->answer_ssrc_rtp);
}
auto origin_seq = ntohs(header->seq);
//seq跟原来的不一样
header->seq = htons(_rtx_seq[pr->second->media->type]++);
auto payload = header->getPayloadData();
auto payload_size = header->getPayloadSize(len);
if (payload_size) {
//rtp负载后移两个字节,这两个字节用于存放osn
//https://datatracker.ietf.org/doc/html/rfc4588#section-4
memmove(payload + 2, payload, payload_size);
}
payload[0] = origin_seq >> 8;
payload[1] = origin_seq & 0xFF;
len += 2;
}
}
void WebRtcTransportImp::onShutdown(const SockException &ex){
WarnL << ex.what();
_self = nullptr;
}
/////////////////////////////////////////////////////////////////////////////////////////////
bool WebRtcTransportImp::close(MediaSource &sender, bool force) {
//此回调在其他线程触发
if(!_push_src || (!force && _push_src->totalReaderCount())){
return false;
}
string err = StrPrinter << "close media:" << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force;
onShutdown(SockException(Err_shutdown,err));
return true;
}
int WebRtcTransportImp::totalReaderCount(MediaSource &sender) {
return _push_src ? _push_src->totalReaderCount() : sender.readerCount();
}
MediaOriginType WebRtcTransportImp::getOriginType(MediaSource &sender) const {
return MediaOriginType::rtc_push;
}
string WebRtcTransportImp::getOriginUrl(MediaSource &sender) const {
return "";
}
std::shared_ptr<SockInfo> WebRtcTransportImp::getOriginSock(MediaSource &sender) const {
return const_cast<WebRtcTransportImp *>(this)->shared_from_this();
}
/////////////////////////////////////////////////////////////////////////////////////////////
string WebRtcTransportImp::get_local_ip() {
return getSdp(SdpType::answer).media[0].candidate[0].address;
}
uint16_t WebRtcTransportImp::get_local_port() {
return _socket->get_local_port();
}
string WebRtcTransportImp::get_peer_ip() {
return SockUtil::inet_ntoa(((struct sockaddr_in *) getSelectedTuple())->sin_addr);
}
uint16_t WebRtcTransportImp::get_peer_port() {
return ntohs(((struct sockaddr_in *) getSelectedTuple())->sin_port);
}
string WebRtcTransportImp::getIdentifier() const {
return StrPrinter << this;
}
\ No newline at end of file
/*
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
*
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
*
* Use of this source code is governed by MIT license that can be found in the
* LICENSE file in the root of the source tree. All contributing project authors
* may be found in the AUTHORS file in the root of the source tree.
*/
#pragma once
#include <memory>
#include <string>
#include "DtlsTransport.hpp"
#include "IceServer.hpp"
#include "SrtpSession.hpp"
#include "StunPacket.hpp"
#include "Sdp.h"
#include "Poller/EventPoller.h"
#include "Network/Socket.h"
#include "Rtsp/RtspMediaSourceImp.h"
#include "Rtcp/RtcpContext.h"
#include "Rtcp/RtcpFCI.h"
using namespace toolkit;
using namespace mediakit;
class WebRtcTransport : public RTC::DtlsTransport::Listener, public RTC::IceServer::Listener {
public:
using Ptr = std::shared_ptr<WebRtcTransport>;
WebRtcTransport(const EventPoller::Ptr &poller);
~WebRtcTransport() override = default;
/**
* 创建对象
*/
virtual void onCreate();
/**
* 销毁对象
*/
virtual void onDestory();
/**
* 创建webrtc answer sdp
* @param offer offer sdp
* @return answer sdp
*/
std::string getAnswerSdp(const string &offer);
/**
* socket收到udp数据
* @param buf 数据指针
* @param len 数据长度
* @param tuple 数据来源
*/
void inputSockData(char *buf, size_t len, RTC::TransportTuple *tuple);
/**
* 发送rtp
* @param buf rtcp内容
* @param len rtcp长度
* @param flush 是否flush socket
* @param ctx 用户指针
*/
void sendRtpPacket(const char *buf, size_t len, bool flush, void *ctx = nullptr);
void sendRtcpPacket(const char *buf, size_t len, bool flush, void *ctx = nullptr);
const EventPoller::Ptr& getPoller() const;
protected:
//// dtls相关的回调 ////
void OnDtlsTransportConnecting(const RTC::DtlsTransport *dtlsTransport) override;
void OnDtlsTransportConnected(const RTC::DtlsTransport *dtlsTransport,
RTC::SrtpSession::CryptoSuite srtpCryptoSuite,
uint8_t *srtpLocalKey,
size_t srtpLocalKeyLen,
uint8_t *srtpRemoteKey,
size_t srtpRemoteKeyLen,
std::string &remoteCert) override;
void OnDtlsTransportFailed(const RTC::DtlsTransport *dtlsTransport) override;
void OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) override;
void OnDtlsTransportSendData(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) override;
void OnDtlsTransportApplicationDataReceived(const RTC::DtlsTransport *dtlsTransport, const uint8_t *data, size_t len) override;
protected:
//// ice相关的回调 ///
void OnIceServerSendStunPacket(const RTC::IceServer *iceServer, const RTC::StunPacket *packet, RTC::TransportTuple *tuple) override;
void OnIceServerSelectedTuple(const RTC::IceServer *iceServer, 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;
protected:
virtual void onStartWebRTC() = 0;
virtual void onRtcConfigure(RtcConfigure &configure) const;
virtual void onCheckSdp(SdpType type, RtcSession &sdp);
virtual void onSendSockData(const char *buf, size_t len, struct sockaddr_in *dst, bool flush = true) = 0;
virtual void onRtp(const char *buf, size_t len) = 0;
virtual void onRtcp(const char *buf, size_t len) = 0;
virtual void onShutdown(const SockException &ex) = 0;
virtual void onBeforeEncryptRtp(const char *buf, size_t &len, void *ctx) = 0;
virtual void onBeforeEncryptRtcp(const char *buf, size_t &len, void *ctx) = 0;
protected:
const RtcSession& getSdp(SdpType type) const;
RTC::TransportTuple* getSelectedTuple() const;
void sendRtcpRemb(uint32_t ssrc, size_t bit_rate);
void sendRtcpPli(uint32_t ssrc);
private:
void onSendSockData(const char *buf, size_t len, bool flush = true);
void setRemoteDtlsFingerprint(const RtcSession &remote);
private:
uint8_t _srtp_buf[2000];
EventPoller::Ptr _poller;
std::shared_ptr<RTC::IceServer> _ice_server;
std::shared_ptr<RTC::DtlsTransport> _dtls_transport;
std::shared_ptr<RTC::SrtpSession> _srtp_session_send;
std::shared_ptr<RTC::SrtpSession> _srtp_session_recv;
RtcSession::Ptr _offer_sdp;
RtcSession::Ptr _answer_sdp;
};
class RtpReceiverImp;
class NackList {
public:
void push_back(RtpPacket::Ptr rtp) {
auto seq = rtp->getSeq();
_nack_cache_seq.emplace_back(seq);
_nack_cache_pkt.emplace(seq, std::move(rtp));
while (get_cache_ms() > kMaxNackMS) {
//需要清除部分nack缓存
pop_front();
}
}
template<typename FUNC>
void for_each_nack(const FCI_NACK &nack, const FUNC &func) {
auto seq = nack.getPid();
for (auto bit : nack.getBitArray()) {
if (bit) {
//丢包
RtpPacket::Ptr *ptr = get_rtp(seq);
if (ptr) {
func(*ptr);
}
}
++seq;
}
}
private:
void pop_front() {
if (_nack_cache_seq.empty()) {
return;
}
_nack_cache_pkt.erase(_nack_cache_seq.front());
_nack_cache_seq.pop_front();
}
RtpPacket::Ptr *get_rtp(uint16_t seq) {
auto it = _nack_cache_pkt.find(seq);
if (it == _nack_cache_pkt.end()) {
return nullptr;
}
return &it->second;
}
uint32_t get_cache_ms() {
if (_nack_cache_seq.size() < 2) {
return 0;
}
uint32_t back = _nack_cache_pkt[_nack_cache_seq.back()]->getStampMS();
uint32_t front = _nack_cache_pkt[_nack_cache_seq.front()]->getStampMS();
if (back > front) {
return back - front;
}
//很有可能回环了
return back + (UINT32_MAX - front);
}
private:
static constexpr uint32_t kMaxNackMS = 10 * 1000;
deque<uint16_t> _nack_cache_seq;
unordered_map<uint16_t, RtpPacket::Ptr > _nack_cache_pkt;
};
class NackContext {
public:
using onNack = function<void(const FCI_NACK &nack)>;
void received(uint16_t seq) {
if (!_last_max_seq && _seq.empty()) {
_last_max_seq = seq - 1;
}
_seq.emplace(seq);
auto max_seq = *_seq.rbegin();
auto min_seq = *_seq.begin();
auto diff = max_seq - min_seq;
if (!diff) {
return;
}
if (diff > UINT32_MAX / 2) {
//回环
_seq.clear();
_last_max_seq = min_seq;
return;
}
if (_seq.size() == diff + 1 && _last_max_seq + 1 == min_seq) {
//都是连续的seq,未丢包
_seq.clear();
_last_max_seq = max_seq;
} else {
//seq不连续,有丢包
if (min_seq == _last_max_seq + 1) {
//前面部分seq是连续的,未丢包,移除之
eraseFrontSeq();
}
//有丢包,丢包从_last_max_seq开始
if (max_seq - _last_max_seq > FCI_NACK::kBitSize) {
vector<bool> vec;
vec.resize(FCI_NACK::kBitSize);
for (auto i = 0; i < FCI_NACK::kBitSize; ++i) {
vec[i] = _seq.find(_last_max_seq + i + 2) == _seq.end();
}
doNack(FCI_NACK(_last_max_seq + 1, vec));
_last_max_seq += FCI_NACK::kBitSize + 1;
if (_last_max_seq >= max_seq) {
_seq.clear();
} else {
auto it = _seq.emplace_hint(_seq.begin(), _last_max_seq);
_seq.erase(_seq.begin(), it);
}
}
}
}
void setOnNack(onNack cb) {
_cb = std::move(cb);
}
private:
void doNack(const FCI_NACK &nack) {
if (_cb) {
_cb(nack);
}
}
void eraseFrontSeq(){
//前面部分seq是连续的,未丢包,移除之
for (auto it = _seq.begin(); it != _seq.end();) {
if (*it != _last_max_seq + 1) {
//seq不连续,丢包了
break;
}
_last_max_seq = *it;
it = _seq.erase(it);
}
}
private:
onNack _cb;
set<uint16_t> _seq;
uint16_t _last_max_seq = 0;
};
class WebRtcTransportImp : public WebRtcTransport, public MediaSourceEvent, public SockInfo, public std::enable_shared_from_this<WebRtcTransportImp>{
public:
using Ptr = std::shared_ptr<WebRtcTransportImp>;
~WebRtcTransportImp() override;
/**
* 创建WebRTC对象
* @param poller 改对象需要绑定的线程
* @return 对象
*/
static Ptr create(const EventPoller::Ptr &poller);
/**
* 绑定rtsp媒体源
* @param src 媒体源
* @param is_play 是播放还是推流
*/
void attach(const RtspMediaSource::Ptr &src, const MediaInfo &info, bool is_play = true);
protected:
void onStartWebRTC() override;
void onSendSockData(const char *buf, size_t len, struct sockaddr_in *dst, bool flush = true) override;
void onCheckSdp(SdpType type, RtcSession &sdp) override;
void onRtcConfigure(RtcConfigure &configure) const override;
void onRtp(const char *buf, size_t len) override;
void onRtp_l(const char *buf, size_t len, bool rtx);
void onRtcp(const char *buf, size_t len) override;
void onBeforeEncryptRtp(const char *buf, size_t &len, void *ctx) override;
void onBeforeEncryptRtcp(const char *buf, size_t &len, void *ctx) override {};
void onShutdown(const SockException &ex) override;
///////MediaSourceEvent override///////
// 关闭
bool close(MediaSource &sender, bool force) override;
// 播放总人数
int totalReaderCount(MediaSource &sender) override;
// 获取媒体源类型
MediaOriginType getOriginType(MediaSource &sender) const override;
// 获取媒体源url或者文件路径
string getOriginUrl(MediaSource &sender) const override;
// 获取媒体源客户端相关信息
std::shared_ptr<SockInfo> getOriginSock(MediaSource &sender) const override;
///////SockInfo override///////
//获取本机ip
string get_local_ip() override;
//获取本机端口号
uint16_t get_local_port() override;
//获取对方ip
string get_peer_ip() override;
//获取对方端口号
uint16_t get_peer_port() override;
//获取标识符
string getIdentifier() const override;
private:
WebRtcTransportImp(const EventPoller::Ptr &poller);
void onCreate() override;
void onDestory() override;
void onSendRtp(const RtpPacket::Ptr &rtp, bool flush, bool rtx = false);
SdpAttrCandidate::Ptr getIceCandidate() const;
bool canSendRtp() const;
bool canRecvRtp() const;
class RtpPayloadInfo {
public:
using Ptr = std::shared_ptr<RtpPayloadInfo>;
const RtcCodecPlan *plan_rtp;
const RtcCodecPlan *plan_rtx;
uint32_t offer_ssrc_rtp = 0;
uint32_t offer_ssrc_rtx = 0;
uint32_t answer_ssrc_rtp = 0;
uint32_t answer_ssrc_rtx = 0;
const RtcMedia *media;
NackList nack_list;
NackContext nack_ctx;
RtcpContext::Ptr rtcp_context_recv;
RtcpContext::Ptr rtcp_context_send;
std::shared_ptr<RtpReceiverImp> receiver;
};
void onSortedRtp(RtpPayloadInfo &info, RtpPacket::Ptr rtp);
void onSendNack(RtpPayloadInfo &info, const FCI_NACK &nack);
void changeRtpExtId(const RtpPayloadInfo *info, const RtpHeader *header, bool is_recv, bool is_rtx = false) const;
private:
uint16_t _rtx_seq[2] = {0, 0};
//用掉的总流量
uint64_t _bytes_usage = 0;
//媒体相关元数据
MediaInfo _media_info;
//保持自我强引用
Ptr _self;
//检测超时的定时器
Timer::Ptr _timer;
//刷新计时器
Ticker _alive_ticker;
//pli rtcp计时器
Ticker _pli_ticker;
//复合udp端口,接收一切rtp与rtcp
Socket::Ptr _socket;
//推流的rtsp源
RtspMediaSource::Ptr _push_src;
//播放的rtsp源
RtspMediaSource::Ptr _play_src;
//播放rtsp源的reader对象
RtspMediaSource::RingType::RingReader::Ptr _reader;
//根据发送rtp的track类型获取相关信息
RtpPayloadInfo::Ptr _send_rtp_info[2];
//根据接收rtp的pt获取相关信息
unordered_map<uint8_t/*pt*/, std::pair<bool/*is rtx*/,RtpPayloadInfo::Ptr> > _rtp_info_pt;
//根据rtcp的ssrc获取相关信息
unordered_map<uint32_t/*ssrc*/, std::pair<bool/*is rtx*/,RtpPayloadInfo::Ptr> > _rtp_info_ssrc;
//发送rtp时需要修改rtp ext id
map<RtpExtType, uint8_t> _rtp_ext_type_to_id;
//接收rtp时需要修改rtp ext id
unordered_map<uint8_t, RtpExtType> _rtp_ext_id_to_type;
};
\ No newline at end of file
v=0
o=- 2712902958023202213 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0 1
a=msid-semantic: WMS
m=audio 9 UDP/TLS/RTP/SAVPF 111
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:68ws
a=ice-pwd:68wscAOadU4rdMwZ4Ow14M5u
a=ice-options:trickle
a=fingerprint:sha-256 BF:F8:C2:35:19:2E:C3:17:44:5A:BA:DF:08:C4:65:87:AB:0C:85:B9:91:69:80:8E:55:D9:2F:EF:5C:D6:F3:A2
a=setup:active
a=mid:0
a=ice-lite
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=recvonly
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1
m=video 9 UDP/TLS/RTP/SAVPF 102 121
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:68ws
a=ice-pwd:68wscAOadU4rdMwZ4Ow14M5u
a=ice-options:trickle
a=fingerprint:sha-256 BF:F8:C2:35:19:2E:C3:17:44:5A:BA:DF:08:C4:65:87:AB:0C:85:B9:91:69:80:8E:55:D9:2F:EF:5C:D6:F3:A2
a=setup:active
a=mid:1
a=ice-lite
a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:13 urn:3gpp:video-orientation
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=recvonly
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:102 H264/90000
a=rtcp-fb:102 goog-remb
a=rtcp-fb:102 transport-cc
a=rtcp-fb:102 ccm fir
a=rtcp-fb:102 nack
a=rtcp-fb:102 nack pli
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
a=rtpmap:121 rtx/90000
a=fmtp:121 apt=102
v=0
o=mozilla...THIS_IS_SDPARTA-87.0 5715694060540081921 0 IN IP4 0.0.0.0
s=-
t=0 0
a=fingerprint:sha-256 9C:9C:D9:4B:2B:82:84:95:C2:23:BC:3A:0B:BE:3B:8B:D5:6A:DA:A6:B2:F4:43:5E:C1:07:48:F8:AF:CE:FC:3D
a=group:BUNDLE video data
a=ice-options:trickle
a=msid-semantic:WMS *
m=video 9 UDP/TLS/RTP/SAVPF 120 122 121 123 126 127 97 98
c=IN IP4 0.0.0.0
a=sendrecv
a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:4 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:5 urn:ietf:params:rtp-hdrext:toffset
a=extmap:7 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=fmtp:126 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1
a=fmtp:97 profile-level-id=42e01f;level-asymmetry-allowed=1
a=fmtp:120 max-fs=12288;max-fr=60
a=fmtp:122 apt=120
a=fmtp:121 max-fs=12288;max-fr=60
a=fmtp:123 apt=121
a=fmtp:127 apt=126
a=fmtp:98 apt=97
a=ice-pwd:518cb4fc626f82bef2ada4e9221dfb50
a=ice-ufrag:d5484fed
a=mid:video
a=msid:{9cd17d8b-f96d-4663-866b-72ddd3b8b54b} {3dcd399d-4933-41d5-89fd-87070cfc3cf6}
a=rtcp-fb:120 nack
a=rtcp-fb:120 nack pli
a=rtcp-fb:120 ccm fir
a=rtcp-fb:120 goog-remb
a=rtcp-fb:120 transport-cc
a=rtcp-fb:121 nack
a=rtcp-fb:121 nack pli
a=rtcp-fb:121 ccm fir
a=rtcp-fb:121 goog-remb
a=rtcp-fb:121 transport-cc
a=rtcp-fb:126 nack
a=rtcp-fb:126 nack pli
a=rtcp-fb:126 ccm fir
a=rtcp-fb:126 goog-remb
a=rtcp-fb:126 transport-cc
a=rtcp-fb:97 nack
a=rtcp-fb:97 nack pli
a=rtcp-fb:97 ccm fir
a=rtcp-fb:97 goog-remb
a=rtcp-fb:97 transport-cc
a=rtcp-mux
a=rtpmap:120 VP8/90000
a=rtpmap:122 rtx/90000
a=rtpmap:121 VP9/90000
a=rtpmap:123 rtx/90000
a=rtpmap:126 H264/90000
a=rtpmap:127 rtx/90000
a=rtpmap:97 H264/90000
a=rtpmap:98 rtx/90000
a=setup:active
a=ssrc:697402393 cname:{f1ba68ef-ad59-4fff-a1f3-dbbb0cda3118}
a=ssrc:258662392 cname:{f1ba68ef-ad59-4fff-a1f3-dbbb0cda3118}
a=ssrc-group:FID 697402393 258662392
m=application 9 UDP/DTLS/SCTP webrtc-datachannel
c=IN IP4 0.0.0.0
a=sendrecv
a=ice-pwd:518cb4fc626f82bef2ada4e9221dfb50
a=ice-ufrag:d5484fed
a=mid:data
a=setup:active
a=sctp-port:5000
a=max-message-size:1073741823
v=0
o=mozilla...THIS_IS_SDPARTA-87.0 1617247876363377 1 IN IP4 23.101.8.213
s=-
t=0 0
a=group:BUNDLE video data
a=msid-semantic: WMS janus
a=ice-lite
a=sendrecv
m=video 9 UDP/TLS/RTP/SAVPF 120 121 126 97 122 123 127 98
c=IN IP4 23.101.8.213
a=sendrecv
a=mid:video
a=rtcp-mux
a=ice-ufrag:ePgh
a=ice-pwd:ZURiB67/xs69E76aOa7JDw
a=ice-options:trickle
a=fingerprint:sha-256 EF:7A:50:9C:05:8C:EF:84:4D:72:B2:74:30:BA:FD:82:76:D1:C3:FE:0C:A0:10:43:B8:6C:B2:ED:B3:F7:77:8B
a=setup:actpass
a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:4 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:5 urn:ietf:params:rtp-hdrext:toffset
a=extmap:6/recvonly http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:7 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=fmtp:126 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1
a=fmtp:97 profile-level-id=42e01f;level-asymmetry-allowed=1
a=fmtp:120 max-fs=12288;max-fr=60
a=fmtp:121 max-fs=12288;max-fr=60
a=rtcp-fb:120 nack
a=rtcp-fb:120 nack pli
a=rtcp-fb:120 ccm fir
a=rtcp-fb:120 goog-remb
a=rtcp-fb:120 transport-cc
a=rtcp-fb:121 nack
a=rtcp-fb:121 nack pli
a=rtcp-fb:121 ccm fir
a=rtcp-fb:121 goog-remb
a=rtcp-fb:121 transport-cc
a=rtcp-fb:126 nack
a=rtcp-fb:126 nack pli
a=rtcp-fb:126 ccm fir
a=rtcp-fb:126 goog-remb
a=rtcp-fb:126 transport-cc
a=rtcp-fb:97 nack
a=rtcp-fb:97 nack pli
a=rtcp-fb:97 ccm fir
a=rtcp-fb:97 goog-remb
a=rtcp-fb:97 transport-cc
a=rtpmap:120 VP8/90000
a=rtpmap:121 VP9/90000
a=rtpmap:126 H264/90000
a=rtpmap:97 H264/90000
a=rtpmap:122 rtx/90000
a=fmtp:122 apt=120
a=rtpmap:123 rtx/90000
a=fmtp:123 apt=121
a=rtpmap:127 rtx/90000
a=fmtp:127 apt=126
a=rtpmap:98 rtx/90000
a=fmtp:98 apt=97
a=ssrc-group:FID 3581519470 2258376012
a=msid:janus janusv0
a=ssrc:3581519470 cname:janus
a=ssrc:3581519470 msid:janus janusv0
a=ssrc:3581519470 mslabel:janus
a=ssrc:3581519470 label:janusv0
a=ssrc:2258376012 cname:janus
a=ssrc:2258376012 msid:janus janusv0
a=ssrc:2258376012 mslabel:janus
a=ssrc:2258376012 label:janusv0
a=candidate:1 1 udp 2013266431 23.101.8.213 41901 typ host
a=end-of-candidates
m=application 9 UDP/DTLS/SCTP webrtc-datachannel
c=IN IP4 23.101.8.213
a=sendrecv
a=sctp-port:5000
a=mid:data
a=ice-ufrag:ePgh
a=ice-pwd:ZURiB67/xs69E76aOa7JDw
a=ice-options:trickle
a=fingerprint:sha-256 EF:7A:50:9C:05:8C:EF:84:4D:72:B2:74:30:BA:FD:82:76:D1:C3:FE:0C:A0:10:43:B8:6C:B2:ED:B3:F7:77:8B
a=setup:actpass
a=candidate:1 1 udp 2013266431 23.101.8.213 41901 typ host
a=end-of-candidates
#pragma once
#include <stdio.h>
#include <assert.h>
#if 0
#define MS_TRACE()
#define MS_ERROR(fmt, ...) printf("error:" fmt "\n", ##__VA_ARGS__)
#define MS_THROW_ERROR(fmt, ...) do{ printf("throw:" fmt "\n", ##__VA_ARGS__); throw std::runtime_error("error"); } while(false);
#define MS_DUMP(fmt, ...) printf("dump:" fmt "\n", ##__VA_ARGS__)
#define MS_DEBUG_2TAGS(tag1, tag2,fmt, ...) printf("debug:" fmt "\n", ##__VA_ARGS__)
#define MS_WARN_2TAGS(tag1, tag2,fmt, ...) printf("warn:" fmt "\n", ##__VA_ARGS__)
#define MS_DEBUG_TAG(tag,fmt, ...) printf("debug:" fmt "\n", ##__VA_ARGS__)
#define MS_ASSERT(con, fmt, ...) do{if(!(con)) { printf("assert failed:%s" fmt "\n", #con, ##__VA_ARGS__);} assert(con); } while(false);
#define MS_ABORT(fmt, ...) do{ printf("abort:" fmt "\n", ##__VA_ARGS__); abort(); } while(false);
#define MS_WARN_TAG(tag,fmt, ...) printf("warn:" fmt "\n", ##__VA_ARGS__)
#define MS_DEBUG_DEV(fmt, ...) printf("debug:" fmt "\n", ##__VA_ARGS__)
#else
#define MS_TRACE()
#define MS_ERROR(fmt, ...)
#define MS_THROW_ERROR(fmt, ...)
#define MS_DUMP(fmt, ...)
#define MS_DEBUG_2TAGS(tag1, tag2,fmt, ...)
#define MS_WARN_2TAGS(tag1, tag2,fmt, ...)
#define MS_DEBUG_TAG(tag,fmt, ...)
#define MS_ASSERT(con, fmt, ...)
#define MS_ABORT(fmt, ...)
#define MS_WARN_TAG(tag,fmt, ...)
#define MS_DEBUG_DEV(fmt, ...)
#endif
\ No newline at end of file
# chrome的sdp
v=0
o=- 403371946498103831 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0 1
a=extmap-allow-mixed
a=msid-semantic: WMS
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:pW4Z
a=ice-pwd:S38S++HW3eTcPTyytsNI1XVp
a=ice-options:trickle
a=fingerprint:sha-256 04:32:7B:56:7D:F7:D4:EC:65:7C:04:6C:F8:0B:03:F0:35:A9:1A:C3:43:3E:18:95:67:E6:0D:D1:EE:C9:16:8C
a=setup:actpass
a=mid:0
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=sendrecv
a=msid:- a3c6a137-1291-45cd-b985-07a9bd365452
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:110 telephone-event/48000
a=rtpmap:112 telephone-event/32000
a=rtpmap:113 telephone-event/16000
a=rtpmap:126 telephone-event/8000
a=ssrc:3626257331 cname:JSFJMbaE9Pu5tevN
a=ssrc:3626257331 msid:- a3c6a137-1291-45cd-b985-07a9bd365452
a=ssrc:3626257331 mslabel:-
a=ssrc:3626257331 label:a3c6a137-1291-45cd-b985-07a9bd365452
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 121 127 120 125 107 108 109 35 36 124 119 123 118 114 115 116
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:pW4Z
a=ice-pwd:S38S++HW3eTcPTyytsNI1XVp
a=ice-options:trickle
a=fingerprint:sha-256 04:32:7B:56:7D:F7:D4:EC:65:7C:04:6C:F8:0B:03:F0:35:A9:1A:C3:43:3E:18:95:67:E6:0D:D1:EE:C9:16:8C
a=setup:actpass
a=mid:1
a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:13 urn:3gpp:video-orientation
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=sendrecv
a=msid:- 261e5384-9cf6-479d-9d59-aaf924d1a2ea
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:98 VP9/90000
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=fmtp:98 profile-id=0
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:100 VP9/90000
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=fmtp:100 profile-id=2
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:102 H264/90000
a=rtcp-fb:102 goog-remb
a=rtcp-fb:102 transport-cc
a=rtcp-fb:102 ccm fir
a=rtcp-fb:102 nack
a=rtcp-fb:102 nack pli
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
a=rtpmap:121 rtx/90000
a=fmtp:121 apt=102
a=rtpmap:127 H264/90000
a=rtcp-fb:127 goog-remb
a=rtcp-fb:127 transport-cc
a=rtcp-fb:127 ccm fir
a=rtcp-fb:127 nack
a=rtcp-fb:127 nack pli
a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f
a=rtpmap:120 rtx/90000
a=fmtp:120 apt=127
a=rtpmap:125 H264/90000
a=rtcp-fb:125 goog-remb
a=rtcp-fb:125 transport-cc
a=rtcp-fb:125 ccm fir
a=rtcp-fb:125 nack
a=rtcp-fb:125 nack pli
a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:107 rtx/90000
a=fmtp:107 apt=125
a=rtpmap:108 H264/90000
a=rtcp-fb:108 goog-remb
a=rtcp-fb:108 transport-cc
a=rtcp-fb:108 ccm fir
a=rtcp-fb:108 nack
a=rtcp-fb:108 nack pli
a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
a=rtpmap:109 rtx/90000
a=fmtp:109 apt=108
a=rtpmap:35 AV1X/90000
a=rtcp-fb:35 goog-remb
a=rtcp-fb:35 transport-cc
a=rtcp-fb:35 ccm fir
a=rtcp-fb:35 nack
a=rtcp-fb:35 nack pli
a=rtpmap:36 rtx/90000
a=fmtp:36 apt=35
a=rtpmap:124 H264/90000
a=rtcp-fb:124 goog-remb
a=rtcp-fb:124 transport-cc
a=rtcp-fb:124 ccm fir
a=rtcp-fb:124 nack
a=rtcp-fb:124 nack pli
a=fmtp:124 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f
a=rtpmap:119 rtx/90000
a=fmtp:119 apt=124
a=rtpmap:123 H264/90000
a=rtcp-fb:123 goog-remb
a=rtcp-fb:123 transport-cc
a=rtcp-fb:123 ccm fir
a=rtcp-fb:123 nack
a=rtcp-fb:123 nack pli
a=fmtp:123 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f
a=rtpmap:118 rtx/90000
a=fmtp:118 apt=123
a=rtpmap:114 red/90000
a=rtpmap:115 rtx/90000
a=fmtp:115 apt=114
a=rtpmap:116 ulpfec/90000
a=rid:q send
a=rid:h send
a=rid:f send
a=simulcast:send q;h;f
#firefox的sdp
v=0
o=mozilla...THIS_IS_SDPARTA-88.0.1 3954544078885279475 0 IN IP4 0.0.0.0
s=-
t=0 0
a=fingerprint:sha-256 9B:4F:D1:D2:A5:ED:08:BC:E8:D7:DD:D8:59:2C:E6:3D:19:F9:4C:67:9C:D9:9B:7B:C9:47:7A:3A:1F:05:C8:96
a=group:BUNDLE 0 1
a=ice-options:trickle
a=msid-semantic:WMS *
m=audio 9 UDP/TLS/RTP/SAVPF 109 9 0 8 101
c=IN IP4 0.0.0.0
a=sendrecv
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2/recvonly urn:ietf:params:rtp-hdrext:csrc-audio-level
a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid
a=fmtp:109 maxplaybackrate=48000;stereo=1;useinbandfec=1
a=fmtp:101 0-15
a=ice-pwd:92a9ced6d734f7ff2a45cde8b29572a9
a=ice-ufrag:b986b945
a=mid:0
a=msid:- {ea61729a-c244-4c79-aeb7-b57765fefa26}
a=rtcp-mux
a=rtpmap:109 opus/48000/2
a=rtpmap:9 G722/8000/1
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:101 telephone-event/8000/1
a=setup:actpass
a=ssrc:3000327501 cname:{12e7c547-559e-46e2-94db-f1ad474c95dc}
m=video 9 UDP/TLS/RTP/SAVPF 120 124 121 125 126 127 97 98
c=IN IP4 0.0.0.0
a=sendrecv
a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:4 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:5 urn:ietf:params:rtp-hdrext:toffset
a=extmap:6/recvonly http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:7 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:8/sendonly urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:9/sendonly urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=fmtp:126 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1
a=fmtp:97 profile-level-id=42e01f;level-asymmetry-allowed=1
a=fmtp:120 max-fs=12288;max-fr=60
a=fmtp:124 apt=120
a=fmtp:121 max-fs=12288;max-fr=60
a=fmtp:125 apt=121
a=fmtp:127 apt=126
a=fmtp:98 apt=97
a=ice-pwd:92a9ced6d734f7ff2a45cde8b29572a9
a=ice-ufrag:b986b945
a=mid:1
a=msid:- {3bfe1b80-20eb-4b42-b8b7-fac45fb281bf}
a=rid:q send
a=rid:h send
a=rid:f send
a=rtcp-fb:120 nack
a=rtcp-fb:120 nack pli
a=rtcp-fb:120 ccm fir
a=rtcp-fb:120 goog-remb
a=rtcp-fb:120 transport-cc
a=rtcp-fb:121 nack
a=rtcp-fb:121 nack pli
a=rtcp-fb:121 ccm fir
a=rtcp-fb:121 goog-remb
a=rtcp-fb:121 transport-cc
a=rtcp-fb:126 nack
a=rtcp-fb:126 nack pli
a=rtcp-fb:126 ccm fir
a=rtcp-fb:126 goog-remb
a=rtcp-fb:126 transport-cc
a=rtcp-fb:97 nack
a=rtcp-fb:97 nack pli
a=rtcp-fb:97 ccm fir
a=rtcp-fb:97 goog-remb
a=rtcp-fb:97 transport-cc
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:120 VP8/90000
a=rtpmap:124 rtx/90000
a=rtpmap:121 VP9/90000
a=rtpmap:125 rtx/90000
a=rtpmap:126 H264/90000
a=rtpmap:127 rtx/90000
a=rtpmap:97 H264/90000
a=rtpmap:98 rtx/90000
a=setup:actpass
a=simulcast:send q;h;f
a=ssrc:2581133096 cname:{12e7c547-559e-46e2-94db-f1ad474c95dc}
a=ssrc:773854125 cname:{12e7c547-559e-46e2-94db-f1ad474c95dc}
a=ssrc:4100728001 cname:{12e7c547-559e-46e2-94db-f1ad474c95dc}
v=0
o=- 8056465047193717905 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0 1
a=extmap-allow-mixed
a=msid-semantic: WMS
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:LtFR
a=ice-pwd:sUVVlvhNoL2g/GL36TyfZGwP
a=ice-options:trickle
a=fingerprint:sha-256 21:21:07:E8:3C:D0:3B:45:87:9A:31:86:DE:4F:C1:BA:E1:0E:96:BA:41:36:6E:3A:3F:C6:C8:92:95:5B:71:5F
a=setup:actpass
a=mid:0
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=sendrecv
a=msid:- 2ebeb64c-2eb3-4c4f-b5d5-d578245b969e
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:110 telephone-event/48000
a=rtpmap:112 telephone-event/32000
a=rtpmap:113 telephone-event/16000
a=rtpmap:126 telephone-event/8000
a=ssrc:905965261 cname:7iEkMV0/MMfqSEce
a=ssrc:905965261 msid:- 2ebeb64c-2eb3-4c4f-b5d5-d578245b969e
a=ssrc:905965261 mslabel:-
a=ssrc:905965261 label:2ebeb64c-2eb3-4c4f-b5d5-d578245b969e
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 121 127 120 125 107 108 109 35 36 124 119 123 118 114 115 116
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:LtFR
a=ice-pwd:sUVVlvhNoL2g/GL36TyfZGwP
a=ice-options:trickle
a=fingerprint:sha-256 21:21:07:E8:3C:D0:3B:45:87:9A:31:86:DE:4F:C1:BA:E1:0E:96:BA:41:36:6E:3A:3F:C6:C8:92:95:5B:71:5F
a=setup:actpass
a=mid:1
a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:13 urn:3gpp:video-orientation
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=sendrecv
a=msid:- f36bb41d-d05d-4310-b05b-7913d0029b18
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:98 VP9/90000
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=fmtp:98 profile-id=0
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:100 VP9/90000
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=fmtp:100 profile-id=2
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:102 H264/90000
a=rtcp-fb:102 goog-remb
a=rtcp-fb:102 transport-cc
a=rtcp-fb:102 ccm fir
a=rtcp-fb:102 nack
a=rtcp-fb:102 nack pli
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
a=rtpmap:121 rtx/90000
a=fmtp:121 apt=102
a=rtpmap:127 H264/90000
a=rtcp-fb:127 goog-remb
a=rtcp-fb:127 transport-cc
a=rtcp-fb:127 ccm fir
a=rtcp-fb:127 nack
a=rtcp-fb:127 nack pli
a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f
a=rtpmap:120 rtx/90000
a=fmtp:120 apt=127
a=rtpmap:125 H264/90000
a=rtcp-fb:125 goog-remb
a=rtcp-fb:125 transport-cc
a=rtcp-fb:125 ccm fir
a=rtcp-fb:125 nack
a=rtcp-fb:125 nack pli
a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:107 rtx/90000
a=fmtp:107 apt=125
a=rtpmap:108 H264/90000
a=rtcp-fb:108 goog-remb
a=rtcp-fb:108 transport-cc
a=rtcp-fb:108 ccm fir
a=rtcp-fb:108 nack
a=rtcp-fb:108 nack pli
a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
a=rtpmap:109 rtx/90000
a=fmtp:109 apt=108
a=rtpmap:35 AV1X/90000
a=rtcp-fb:35 goog-remb
a=rtcp-fb:35 transport-cc
a=rtcp-fb:35 ccm fir
a=rtcp-fb:35 nack
a=rtcp-fb:35 nack pli
a=rtpmap:36 rtx/90000
a=fmtp:36 apt=35
a=rtpmap:124 H264/90000
a=rtcp-fb:124 goog-remb
a=rtcp-fb:124 transport-cc
a=rtcp-fb:124 ccm fir
a=rtcp-fb:124 nack
a=rtcp-fb:124 nack pli
a=fmtp:124 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d0032
a=rtpmap:119 rtx/90000
a=fmtp:119 apt=124
a=rtpmap:123 H264/90000
a=rtcp-fb:123 goog-remb
a=rtcp-fb:123 transport-cc
a=rtcp-fb:123 ccm fir
a=rtcp-fb:123 nack
a=rtcp-fb:123 nack pli
a=fmtp:123 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640032
a=rtpmap:118 rtx/90000
a=fmtp:118 apt=123
a=rtpmap:114 red/90000
a=rtpmap:115 rtx/90000
a=fmtp:115 apt=114
a=rtpmap:116 ulpfec/90000
a=ssrc-group:FID 2678501654 361960375
a=ssrc:2678501654 cname:7iEkMV0/MMfqSEce
a=ssrc:2678501654 msid:- f36bb41d-d05d-4310-b05b-7913d0029b18
a=ssrc:2678501654 mslabel:-
a=ssrc:2678501654 label:f36bb41d-d05d-4310-b05b-7913d0029b18
a=ssrc:361960375 cname:7iEkMV0/MMfqSEce
a=ssrc:361960375 msid:- f36bb41d-d05d-4310-b05b-7913d0029b18
a=ssrc:361960375 mslabel:-
a=ssrc:361960375 label:f36bb41d-d05d-4310-b05b-7913d0029b18
\ No newline at end of file
# 致谢与声明
本文件夹下部分文件提取自[MediaSoup](https://github.com/versatica/mediasoup) ,分别为:
- ice相关功能:
- IceServer.cpp
- IceServer.hpp
- StunPacket.cpp
- StunPacket.hpp
- Utils.hpp
- dtls相关功能:
- DtlsTransport.cpp
- DtlsTransport.hpp
- srtp相关功能:
- SrtpSession.cpp
- SrtpSession.hpp
以上源码有一定的修改和裁剪,感谢MediaSoup开源项目及作者,
用户在使用本项目的同时,应该同时遵循MediaSoup的开源协议。
同时,在此也感谢开源项目[easy_webrtc_server](https://github.com/Mihawk086/easy_webrtc_server) 及作者,
在集成MediaSoup相关代码前期,主要参考这个项目。
另外,感谢[big panda](<2381267071@qq.com>) 开发并贡献的webrtc js测试客户端(www/webrtc目录下文件),
其开源项目地址为:https://gitee.com/xiongguangjie/zlmrtcclient.js
# 现状与规划
ZLMediaKit的WebRTC相关功能目前仅供测试与开发,现在还不成熟,后续主要工作有:
- 1、完善webrtc rtcp相关功能,包括丢包重传、带宽检测等功能。
- 2、实现rtp重传等相关功能。
- 3、实现simulcast相关功能。
- 4、fec、rtp扩展等其他功能。
- 5、如果精力允许,逐步替换MediaSoup相关代码,改用自有版权代码。
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
<html>
<meta charset="utf-8">
<head>
<title>ZLM RTC demo</title>
<script src="./ZLMRTCClient.js"></script>
</head>
<body>
<div style="text-align: center;">
<div>
<video id='video' controls autoplay style="text-align:left;">
Your browser is too old which doesn't support HTML5 video.
</video>
<video id='selfVideo' controls autoplay style="text-align:right;">
Your browser is too old which doesn't support HTML5 video.
</video>
</div>
<div>
<p>
<label for="streamUrl">url:</label>
<input type="text" style="co" id='streamUrl' value="http://127.0.0.1/index/api/webrtc?app=live&stream=test&type=play">
</p>
<p>
<label for="simulcast">simulcast:</label>
<input type="checkbox" id='simulcast' checked="checked">
</p>
<p>
<label for="useCamera">useCamera:</label>
<input type="checkbox" id='useCamera' checked="checked">
</p>
<p>
<label for="audioEnable">audioEnable:</label>
<input type="checkbox" id='audioEnable' checked="checked">
</p>
<p>
<label for="videoEnable">videoEnable:</label>
<input type="checkbox" id='videoEnable' checked="checked">
</p>
<p>
<label for="method">method(play or push):</label>
<input type="radio" name="method" value="push" >push
<input type="radio" name="method" value="play" checked = true>play
</p>
<p>
<label for="resolution">resolution:</label>
<select id="resolution">
</select>
</p>
<button onclick="start()">开始</button>
<button onclick="stop()">停止</button>
</div>
</div>
<script>
var player = null
var recvOnly = true
var resArr = []
document.getElementsByName("method").forEach((el,idx)=>{
el.onclick=function(e){
if(el.value == "play")
{
let url = document.getElementById('streamUrl').value;
document.getElementById('streamUrl').value = url.replace("&type=push","&type=play");
recvOnly = true;
}
else
{
let url = document.getElementById('streamUrl').value;
document.getElementById('streamUrl').value = url.replace("&type=play","&type=push");
recvOnly = false;
}
};
});
ZLMRTCClient.GetAllScanResolution().forEach((r,i)=>{
opt = document.createElement('option');
opt.text = r.label +"("+r.width+"x"+r.height+")";
opt.value = r;
document.getElementById("resolution").add(opt,null)
//console.log(opt.text.match(/\d+/g))
})
function start_play(){
let elr = document.getElementById("resolution");
let res = elr.options[elr.selectedIndex].text.match(/\d+/g);
let h = parseInt(res.pop());
let w = parseInt(res.pop());
player = new ZLMRTCClient.Endpoint(
{
element: document.getElementById('video'),// video 标签
debug: true,// 是否打印日志
zlmsdpUrl:document.getElementById('streamUrl').value,//流地址
simulcast:false,//document.getElementById('simulcast').checked,
useCamera:document.getElementById('useCamera').checked,
audioEnable:document.getElementById('audioEnable').checked,
videoEnable:document.getElementById('videoEnable').checked,
recvOnly:recvOnly,
resolution:{w:w,h:h}
}
);
player.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR,function(e)
{// ICE 协商出错
console.log('ICE 协商出错')
});
player.on(ZLMRTCClient.Events.WEBRTC_ON_REMOTE_STREAMS,function(e)
{//获取到了远端流,可以播放
console.log('播放成功',e.streams)
});
player.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANSWER_EXCHANGE_FAILED,function(e)
{// offer answer 交换失败
console.log('offer answer 交换失败',e)
stop();
});
player.on(ZLMRTCClient.Events.WEBRTC_ON_LOCAL_STREAM,function(s)
{// 获取到了本地流
document.getElementById('selfVideo').srcObject=s;
document.getElementById('selfVideo').muted = true;
//console.log('offer answer 交换失败',e)
});
player.on(ZLMRTCClient.Events.CAPTURE_STREAM_FAILED,function(s)
{// 获取本地流失败
console.log('获取本地流失败')
});
}
function start()
{
stop();
let elr = document.getElementById("resolution");
let res = elr.options[elr.selectedIndex].text.match(/\d+/g);
let h = parseInt(res.pop());
let w = parseInt(res.pop());
if(document.getElementById('useCamera').checked && !recvOnly)
{
ZLMRTCClient.isSupportResolution(w,h).then(e=>{
start_play()
}).catch(e=>{
alert("not support resolution")
});
}else{
start_play()
}
}
function stop()
{
if(player)
{
player.close();
player = null;
var local = document.getElementById('selfVideo');
local.removeAttribute('srcObject');
local.load();
}
}
</script>
</body>
<script>
</script>
</html>
\ No newline at end of file
感谢[big panda](<2381267071@qq.com>) 开发并贡献此webrtc js测试客户端,
其开源项目地址为:https://gitee.com/xiongguangjie/zlmrtcclient.js
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论