mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-25 20:27:34 +08:00
Merge branch 'dev' of https://github.com/xia-chu/ZLMediaKit into dev_test
This commit is contained in:
commit
307be4339f
1
.gitignore
vendored
1
.gitignore
vendored
@ -46,3 +46,4 @@
|
||||
/3rdpart/media-server/.idea/
|
||||
/ios/
|
||||
/cmake-build-*
|
||||
/3rdpart/ZLToolKit/cmake-build-mq/
|
||||
|
@ -12,10 +12,6 @@
|
||||
#define ZLMEDIAKIT_ASSERT_H
|
||||
|
||||
#include <stdio.h>
|
||||
#ifndef NDEBUG
|
||||
#ifdef assert
|
||||
#undef assert
|
||||
#endif//assert
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@ -25,6 +21,13 @@
|
||||
}
|
||||
#endif
|
||||
|
||||
#define CHECK(exp) Assert_Throw(!(exp), #exp, __FUNCTION__, __FILE__, __LINE__)
|
||||
|
||||
#ifndef NDEBUG
|
||||
#ifdef assert
|
||||
#undef assert
|
||||
#endif//assert
|
||||
|
||||
#define assert(exp) Assert_Throw(!(exp), #exp, __FUNCTION__, __FILE__, __LINE__);
|
||||
#else
|
||||
#define assert(e) ((void)0)
|
||||
|
@ -55,6 +55,7 @@ option(ENABLE_TESTS "Enable Tests" true)
|
||||
option(ENABLE_SERVER "Enable Server" true)
|
||||
option(ENABLE_MEM_DEBUG "Enable Memory Debug" false)
|
||||
option(ENABLE_ASAN "Enable Address Sanitize" false)
|
||||
option(ENABLE_WEBRTC "Enable WebRTC" true)
|
||||
|
||||
if (ENABLE_MEM_DEBUG)
|
||||
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)
|
||||
include_directories(${OPENSSL_INCLUDE_DIR})
|
||||
add_definitions(-DENABLE_OPENSSL)
|
||||
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()
|
||||
message(WARNING "openssl未找到,rtmp将不支持flash播放器,https/wss/rtsps/rtmps也将失效")
|
||||
endif ()
|
||||
@ -224,6 +229,24 @@ if(ENABLE_API)
|
||||
add_subdirectory(api)
|
||||
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(ENABLE_TESTS)
|
||||
|
55
cmake/FindSRTP.cmake
Normal file
55
cmake/FindSRTP.cmake
Normal file
@ -0,0 +1,55 @@
|
||||
############################################################################
|
||||
# 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)
|
@ -140,7 +140,7 @@ charSet=utf-8
|
||||
#http链接超时时间
|
||||
keepAliveSecond=30
|
||||
#http请求体最大字节数,如果post的body太大,则不适合缓存body在内存
|
||||
maxReqSize=4096
|
||||
maxReqSize=40960
|
||||
#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>
|
||||
#http服务器监听端口
|
||||
@ -210,13 +210,23 @@ port=10000
|
||||
#rtp超时时间,单位秒
|
||||
timeoutSec=15
|
||||
|
||||
[rtc]
|
||||
#rtc播放推流、播放超时时间
|
||||
timeoutSec=15
|
||||
#本机对rtc客户端的可见ip,作为服务器时一般为公网ip,置空时,会自动获取网卡ip
|
||||
externIP=
|
||||
#设置remb比特率,非0时关闭twcc并开启remb。该设置在rtc推流时有效,可以控制推流画质
|
||||
rembBitRate=1000000
|
||||
|
||||
[rtsp]
|
||||
#rtsp专有鉴权方式是采用base64还是md5方式
|
||||
authBasic=0
|
||||
#rtsp拉流代理是否是直接代理模式
|
||||
#rtsp拉流、推流代理是否是直接代理模式
|
||||
#直接代理后支持任意编码格式,但是会导致GOP缓存无法定位到I帧,可能会导致开播花屏
|
||||
#并且如果是tcp方式拉流,如果rtp大于mtu会导致无法使用udp方式代理
|
||||
#假定您的拉流源地址不是264或265或AAC,那么你可以使用直接代理的方式来支持rtsp代理
|
||||
#如果你是rtsp推拉流,但是rtc播放,也建议关闭直接代理模式,
|
||||
#因为直接代理时,rtp中可能没有sps pps,会导致rtc无法播放
|
||||
#默认开启rtsp直接代理,rtmp由于没有这些问题,是强制开启直接代理的
|
||||
directProxy=1
|
||||
#rtsp必须在此时间内完成握手,否则服务器会断开链接,单位秒
|
||||
|
@ -2,10 +2,12 @@
|
||||
%global use_devtoolset 0
|
||||
%bcond_without faac
|
||||
%bcond_without x264
|
||||
%bcond_without webrtc
|
||||
%else
|
||||
%global use_devtoolset 1
|
||||
%bcond_with faac
|
||||
%bcond_with x264
|
||||
%bcond_with webrtc
|
||||
%endif
|
||||
|
||||
%bcond_without openssl
|
||||
@ -22,8 +24,12 @@ URL: https://github.com/xia-chu/ZLMediaKit
|
||||
Source0: %{name}-%{version}.tar.xz
|
||||
|
||||
%if %{with openssl}
|
||||
%if 0%{?rhel} <= 7 && %{with webrtc}
|
||||
BuildRequires: openssl11-devel
|
||||
%else
|
||||
BuildRequires: openssl-devel
|
||||
%endif
|
||||
%endif
|
||||
|
||||
%if %{with mysql}
|
||||
BuildRequires: mysql-devel
|
||||
@ -37,6 +43,10 @@ BuildRequires: faac-devel
|
||||
BuildRequires: x264-devel
|
||||
%endif
|
||||
|
||||
%if %{with webrtc}
|
||||
BuildRequires: libsrtp-devel >= 2.0
|
||||
%endif
|
||||
|
||||
%if 0%{?use_devtoolset}
|
||||
BuildRequires: devtoolset-8-gcc-c++
|
||||
%endif
|
||||
@ -88,6 +98,10 @@ pushd %{_target_platform}
|
||||
-DENABLE_MYSQL:BOOL=%{with mysql} \
|
||||
-DENABLE_FAAC:BOOL=%{with faac} \
|
||||
-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_RTPPROXY:BOOL=ON \
|
||||
-DENABLE_API:BOOL=ON \
|
||||
|
@ -48,4 +48,8 @@ else()
|
||||
install(TARGETS MediaServer DESTINATION ${INSTALL_PATH_EXECUTABLE})
|
||||
endif()
|
||||
|
||||
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 @@
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
#include "Rtp/RtpServer.h"
|
||||
#endif
|
||||
#ifdef ENABLE_WEBRTC
|
||||
#include "../webrtc/WebRtcTransport.h"
|
||||
#endif
|
||||
|
||||
using namespace toolkit;
|
||||
using namespace mediakit;
|
||||
@ -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) {
|
||||
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)
|
||||
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参数
|
||||
static ApiArgsType getAllArgs(const Parser &parser) {
|
||||
ApiArgsType allArgs;
|
||||
@ -380,8 +411,15 @@ void installWebApi() {
|
||||
int changed = API::Success;
|
||||
for (auto &pr : allArgs) {
|
||||
if (ini.find(pr.first) == ini.end()) {
|
||||
#if 1
|
||||
//没有这个key
|
||||
continue;
|
||||
#else
|
||||
// 新增配置选项,为了动态添加多个ffmpeg cmd 模板
|
||||
ini[pr.first] = pr.second;
|
||||
// 防止changed变化
|
||||
continue;
|
||||
#endif
|
||||
}
|
||||
if (ini[pr.first] == pr.second) {
|
||||
continue;
|
||||
@ -1161,6 +1199,90 @@ void installWebApi() {
|
||||
#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////////////
|
||||
api_regist("/index/hook/on_publish",[](API_ARGS_MAP){
|
||||
//开始推流事件
|
||||
|
@ -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_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_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
|
||||
|
||||
//注册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
|
||||
//注册http请求参数是Json::Value类型,但是可以异步回复的的http api
|
||||
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>
|
||||
bool checkArgs(Args &&args, First &&first) {
|
||||
return !args[first].empty();
|
||||
@ -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)...);
|
||||
}
|
||||
|
||||
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参数是否为空的宏
|
||||
#define CHECK_ARGS(...) \
|
||||
if(!checkArgs(allArgs,##__VA_ARGS__)){ \
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "Rtp/RtpServer.h"
|
||||
#include "WebApi.h"
|
||||
#include "WebHook.h"
|
||||
#include "../webrtc/Sdp.h"
|
||||
|
||||
#if defined(ENABLE_VERSION)
|
||||
#include "Version.h"
|
||||
|
@ -35,6 +35,7 @@ string getOriginTypeString(MediaOriginType type){
|
||||
SWITCH_CASE(ffmpeg_pull);
|
||||
SWITCH_CASE(mp4_vod);
|
||||
SWITCH_CASE(device_chn);
|
||||
SWITCH_CASE(rtc_push);
|
||||
default : return "unknown";
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,8 @@ enum class MediaOriginType : uint8_t {
|
||||
pull,
|
||||
ffmpeg_pull,
|
||||
mp4_vod,
|
||||
device_chn
|
||||
device_chn,
|
||||
rtc_push,
|
||||
};
|
||||
|
||||
string getOriginTypeString(MediaOriginType type);
|
||||
|
@ -205,7 +205,7 @@ public:
|
||||
if (bitrate) {
|
||||
_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;
|
||||
char buf[4] = {0};
|
||||
|
@ -69,7 +69,7 @@ CommonRtpEncoder::CommonRtpEncoder(CodecId codec, uint32_t ssrc, uint32_t mtu_si
|
||||
}
|
||||
|
||||
void CommonRtpEncoder::inputFrame(const Frame::Ptr &frame){
|
||||
auto stamp = frame->dts();
|
||||
auto stamp = frame->pts();
|
||||
auto ptr = frame->data() + frame->prefixSize();
|
||||
auto len = frame->size() - frame->prefixSize();
|
||||
auto remain_size = len;
|
||||
|
@ -11,7 +11,7 @@
|
||||
#include "Frame.h"
|
||||
#include "H264.h"
|
||||
#include "H265.h"
|
||||
|
||||
#include "Common/Parser.h"
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
@ -106,30 +106,50 @@ Frame::Ptr Frame::getCacheAbleFrame(const Frame::Ptr &frame){
|
||||
return std::make_shared<FrameCacheAble>(frame);
|
||||
}
|
||||
|
||||
#define SWITCH_CASE(codec_id) case codec_id : return #codec_id
|
||||
const char *getCodecName(CodecId codecId) {
|
||||
TrackType getTrackType(CodecId codecId) {
|
||||
switch (codecId) {
|
||||
SWITCH_CASE(CodecH264);
|
||||
SWITCH_CASE(CodecH265);
|
||||
SWITCH_CASE(CodecAAC);
|
||||
SWITCH_CASE(CodecG711A);
|
||||
SWITCH_CASE(CodecG711U);
|
||||
SWITCH_CASE(CodecOpus);
|
||||
SWITCH_CASE(CodecL16);
|
||||
default : return "unknown codec";
|
||||
#define XX(name, type, value, str) case name : return type;
|
||||
CODEC_MAP(XX)
|
||||
#undef XX
|
||||
default : return TrackInvalid;
|
||||
}
|
||||
}
|
||||
|
||||
TrackType getTrackType(CodecId codecId){
|
||||
switch (codecId){
|
||||
case CodecH264:
|
||||
case CodecH265: return TrackVideo;
|
||||
case CodecAAC:
|
||||
case CodecG711A:
|
||||
case CodecG711U:
|
||||
case CodecOpus:
|
||||
case CodecL16: return TrackAudio;
|
||||
default: return TrackInvalid;
|
||||
const char *getCodecName(CodecId codec) {
|
||||
switch (codec) {
|
||||
#define XX(name, type, value, str) case name : return str;
|
||||
CODEC_MAP(XX)
|
||||
#undef XX
|
||||
default : return "invalid";
|
||||
}
|
||||
}
|
||||
|
||||
#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{
|
||||
}
|
||||
switch (frame->getCodecId()) {
|
||||
case CodecH264 : {
|
||||
if (H264_TYPE(frame->data()[frame->prefixSize()]) == H264Frame::NAL_B_P) {
|
||||
//如果是264的b/p帧,那么也刷新输出
|
||||
auto type = H264_TYPE(frame->data()[frame->prefixSize()]);
|
||||
if ((frame->data()[frame->prefixSize()+1]&0x80) !=0 && type >=H264Frame::NAL_B_P && type<=H264Frame::NAL_IDR ) {// sei aud pps sps 不判断
|
||||
//264 新一帧的开始,刷新输出
|
||||
return true;
|
||||
}else{
|
||||
// 不刷新输出
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CodecH265 : {
|
||||
if (H265_TYPE(frame->data()[frame->prefixSize()]) == H265Frame::NAL_TRAIL_R) {
|
||||
//如果是265的TRAIL_R帧,那么也刷新输出
|
||||
auto type = H265_TYPE(frame->data()[frame->prefixSize()]);
|
||||
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;
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -217,8 +244,33 @@ void FrameMerger::doMerge(BufferLikeString &merged, const Frame::Ptr &frame) con
|
||||
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) {
|
||||
if(shouldDrop(frame)){
|
||||
return;
|
||||
}
|
||||
if (willFlush(frame)) {
|
||||
Frame::Ptr back = _frameCached.back();
|
||||
Buffer::Ptr merged_frame = back;
|
||||
|
@ -21,26 +21,52 @@ using namespace toolkit;
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
typedef enum {
|
||||
CodecInvalid = -1,
|
||||
CodecH264 = 0,
|
||||
CodecH265,
|
||||
CodecAAC,
|
||||
CodecG711A,
|
||||
CodecG711U,
|
||||
CodecOpus,
|
||||
CodecL16,
|
||||
CodecMax = 0x7FFF
|
||||
} CodecId;
|
||||
|
||||
typedef enum {
|
||||
TrackInvalid = -1,
|
||||
TrackVideo = 0,
|
||||
TrackAudio,
|
||||
TrackTitle,
|
||||
TrackMax = 3
|
||||
TrackApplication,
|
||||
TrackMax
|
||||
} 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:
|
||||
private:
|
||||
bool willFlush(const Frame::Ptr &frame) const;
|
||||
void doMerge(BufferLikeString &buffer, const Frame::Ptr &frame) const;
|
||||
bool shouldDrop(const Frame::Ptr &frame) const;
|
||||
|
||||
private:
|
||||
int _type;
|
||||
|
@ -33,7 +33,7 @@ public:
|
||||
if (bitrate) {
|
||||
_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";
|
||||
}
|
||||
|
||||
|
@ -181,17 +181,15 @@ void H264Track::inputFrame_l(const Frame::Ptr &frame){
|
||||
_pps = string(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize());
|
||||
break;
|
||||
}
|
||||
case H264Frame::NAL_IDR: {
|
||||
insertConfigFrame(frame);
|
||||
VideoTrack::inputFrame(frame);
|
||||
break;
|
||||
}
|
||||
case H264Frame::NAL_AUD: {
|
||||
//忽略AUD帧;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
if (frame->keyFrame()) {
|
||||
insertConfigFrame(frame);
|
||||
}
|
||||
VideoTrack::inputFrame(frame);
|
||||
break;
|
||||
}
|
||||
@ -235,7 +233,7 @@ public:
|
||||
if (bitrate) {
|
||||
_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=";
|
||||
|
||||
char strTemp[1024];
|
||||
@ -281,7 +279,8 @@ Sdp::Ptr H264Track::getSdp() {
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
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 {
|
||||
|
@ -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);
|
||||
_lastPacket = nullptr;
|
||||
}
|
||||
@ -214,10 +214,6 @@ void H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
_lastPacket->buffer.append((char *) &size, 4);
|
||||
_lastPacket->buffer.append(pcData, iLen);
|
||||
_lastPacket->body_size = _lastPacket->buffer.size();
|
||||
if (type == H264Frame::NAL_B_P) {
|
||||
RtmpCodec::inputRtmp(_lastPacket);
|
||||
_lastPacket = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void H264RtmpEncoder::makeVideoConfigPkt() {
|
||||
|
@ -184,21 +184,39 @@ H264RtpEncoder::H264RtpEncoder(uint32_t ssrc, uint32_t mtu, uint32_t sample_rate
|
||||
: RtpInfo(ssrc, mtu, sample_rate, pt, interleaved) {
|
||||
}
|
||||
|
||||
void H264RtpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
auto ptr = frame->data() + frame->prefixSize();
|
||||
auto len = frame->size() - frame->prefixSize();
|
||||
auto pts = frame->pts();
|
||||
auto nal_type = H264_TYPE(ptr[0]);
|
||||
void H264RtpEncoder::insertConfigFrame(uint32_t pts){
|
||||
if (!_sps || !_pps) {
|
||||
return;
|
||||
}
|
||||
//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;
|
||||
if (len <= packet_size + 1) {
|
||||
//小于FU-A打包最小字节长度要求,采用STAP-A模式
|
||||
packRtpStapA(ptr, len, pts, is_mark, gop_pos);
|
||||
return;
|
||||
}
|
||||
|
||||
//末尾5bit为nalu type,固定为28(FU-A)
|
||||
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);
|
||||
fu_flags->start_bit = 1;
|
||||
|
||||
//超过MTU则按照FU-A模式打包
|
||||
if (len > packet_size + 1) {
|
||||
size_t offset = 1;
|
||||
while (!fu_flags->end_bit) {
|
||||
if (!fu_flags->start_bit && len <= offset + packet_size) {
|
||||
@ -208,7 +226,7 @@ void H264RtpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
}
|
||||
|
||||
//传入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 负载部分
|
||||
uint8_t *payload = rtp->getPayload();
|
||||
//FU-A 第1个字节
|
||||
@ -218,19 +236,57 @@ void H264RtpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
//H264 数据
|
||||
memcpy(payload + 2, (uint8_t *) ptr + offset, packet_size);
|
||||
//输入到rtp环形缓存
|
||||
RtpCodec::inputRtp(rtp, fu_flags->start_bit && nal_type == H264Frame::NAL_IDR);
|
||||
RtpCodec::inputRtp(rtp, gop_pos);
|
||||
|
||||
offset += packet_size;
|
||||
fu_flags->start_bit = 0;
|
||||
}
|
||||
} else {
|
||||
//如果帧长度不超过mtu, 则按照Single NAL unit packet per H.264 方式打包
|
||||
makeH264Rtp(ptr, len, false, false, pts);
|
||||
}
|
||||
}
|
||||
|
||||
void H264RtpEncoder::makeH264Rtp(const void* data, size_t len, bool mark, bool gop_pos, uint32_t uiStamp) {
|
||||
RtpCodec::inputRtp(makeRtp(getTrackType(), data, len, mark, uiStamp), gop_pos);
|
||||
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::inputFrame_l(const Frame::Ptr &frame, bool is_mark){
|
||||
if (frame->keyFrame()) {
|
||||
//保证每一个关键帧前都有SPS与PPS
|
||||
insertConfigFrame(frame->pts());
|
||||
}
|
||||
packRtp(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize(), frame->pts(), is_mark, false);
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
|
@ -82,7 +82,16 @@ public:
|
||||
void inputFrame(const Frame::Ptr &frame) override;
|
||||
|
||||
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{
|
||||
|
@ -64,10 +64,11 @@ bool H265Frame::configFrame() const {
|
||||
}
|
||||
|
||||
bool H265Frame::isKeyFrame(int type, const char *ptr) {
|
||||
if (!ptr || type != NAL_IDR_W_RADL) {
|
||||
return type >= NAL_BLA_W_LP && type <= NAL_RSV_IRAP_VCL23;
|
||||
if(ptr){
|
||||
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(){
|
||||
@ -254,7 +255,7 @@ public:
|
||||
if (bitrate) {
|
||||
_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 << "sprop-vps=";
|
||||
_printer << encodeBase64(strVPS) << "; ";
|
||||
|
@ -165,11 +165,11 @@ void H265RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
}
|
||||
}
|
||||
|
||||
if(type == H265Frame::NAL_SEI_PREFIX || type == H265Frame::NAL_SEI_SUFFIX){
|
||||
return;
|
||||
if(type == H265Frame::NAL_SEI_PREFIX || type == H265Frame::NAL_SEI_SUFFIX || type == H265Frame::NAL_AUD){
|
||||
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);
|
||||
_lastPacket = nullptr;
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ public:
|
||||
if (bitrate) {
|
||||
_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";
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ public:
|
||||
if (bitrate) {
|
||||
_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";
|
||||
}
|
||||
|
||||
|
@ -9,8 +9,10 @@
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <assert.h>
|
||||
#include "Rtcp.h"
|
||||
#include "Util/logger.h"
|
||||
#include "RtcpFCI.h"
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
@ -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) {
|
||||
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) {
|
||||
@ -45,23 +65,46 @@ static void setupHeader(RtcpHeader *rtcp, RtcpType type, size_t report_count, si
|
||||
//items总个数
|
||||
rtcp->report_count = report_count;
|
||||
rtcp->pt = (uint8_t) type;
|
||||
//不包含rtcp头的长度
|
||||
rtcp->length = htons((uint16_t)((total_bytes / 4) - 1));
|
||||
rtcp->setSize(total_bytes);
|
||||
}
|
||||
|
||||
static void setupPadding(RtcpHeader *rtcp, size_t padding_size) {
|
||||
if (padding_size) {
|
||||
rtcp->padding = 1;
|
||||
((uint8_t *) rtcp)[rtcp->getSize() - 1] = padding_size & 0xFF;
|
||||
} else {
|
||||
rtcp->padding = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RtcpHeader::net2Host() {
|
||||
length = ntohs(length);
|
||||
}
|
||||
|
||||
string RtcpHeader::dumpHeader() const{
|
||||
_StrPrinter printer;
|
||||
printer << "version:" << version << "\r\n";
|
||||
if (padding) {
|
||||
printer << "padding:" << padding << " " << getPaddingSize() << "\r\n";
|
||||
} else {
|
||||
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";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
printer << "pt:" << rtcpTypeToStr((RtcpType)pt) << "\r\n";
|
||||
printer << "length:" << length << "\r\n";
|
||||
printer << "size:" << getSize() << "\r\n";
|
||||
printer << "--------\r\n";
|
||||
return std::move(printer);
|
||||
}
|
||||
@ -82,8 +125,37 @@ string RtcpHeader::dumpString() const {
|
||||
RtcpSdes *rtcp = (RtcpSdes *)this;
|
||||
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){
|
||||
@ -105,6 +177,20 @@ void RtcpHeader::net2Host(size_t len){
|
||||
sdes->net2Host(len);
|
||||
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));
|
||||
}
|
||||
}
|
||||
@ -115,13 +201,17 @@ vector<RtcpHeader *> RtcpHeader::loadFromBytes(char *data, size_t len){
|
||||
char *ptr = data;
|
||||
while (remain > (ssize_t) sizeof(RtcpHeader)) {
|
||||
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 {
|
||||
rtcp->net2Host(rtcp_len);
|
||||
ret.emplace_back(rtcp);
|
||||
} catch (std::exception &ex) {
|
||||
//不能处理的rtcp包,或者无法解析的rtcp包,忽略掉
|
||||
WarnL << ex.what();
|
||||
WarnL << ex.what() << ",长度为:" << rtcp_len;
|
||||
}
|
||||
ptr += rtcp_len;
|
||||
remain -= rtcp_len;
|
||||
@ -133,7 +223,6 @@ class BufferRtcp : public Buffer {
|
||||
public:
|
||||
BufferRtcp(std::shared_ptr<RtcpHeader> rtcp) {
|
||||
_rtcp = std::move(rtcp);
|
||||
_size = (htons(_rtcp->length) + 1) << 2;
|
||||
}
|
||||
|
||||
~BufferRtcp() override {}
|
||||
@ -143,11 +232,10 @@ public:
|
||||
}
|
||||
|
||||
size_t size() const override {
|
||||
return _size;
|
||||
return _rtcp->getSize();
|
||||
}
|
||||
|
||||
private:
|
||||
std::size_t _size;
|
||||
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) {
|
||||
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];
|
||||
setupHeader(ptr, RtcpType::RTCP_SR, item_count, bytes);
|
||||
setupPadding(ptr, bytes - real_size);
|
||||
return std::shared_ptr<RtcpSR>(ptr, [](RtcpSR *ptr) {
|
||||
delete[] (char *) ptr;
|
||||
});
|
||||
@ -202,21 +292,17 @@ if (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时内存越界*/ \
|
||||
if (report_count != item_count) { \
|
||||
WarnL << rtcpTypeToStr((RtcpType)pt) << " report_count 字段不正确,已修正为:" << (int)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) {
|
||||
static const size_t kMinSize = sizeof(RtcpSR) - sizeof(items);
|
||||
CHECK_MIN_SIZE(size, kMinSize);
|
||||
|
||||
RtcpHeader::net2Host();
|
||||
ssrc = ntohl(ssrc);
|
||||
ntpmsw = ntohl(ntpmsw);
|
||||
ntplsw = ntohl(ntplsw);
|
||||
@ -231,7 +317,7 @@ void RtcpSR::net2Host(size_t size) {
|
||||
++ptr;
|
||||
++item_count;
|
||||
}
|
||||
CHECK_LENGTH(size, item_count);
|
||||
CHECK_REPORT_COUNT(item_count);
|
||||
}
|
||||
|
||||
vector<ReportItem*> RtcpSR::getItemList(){
|
||||
@ -272,9 +358,11 @@ void ReportItem::net2Host() {
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
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];
|
||||
setupHeader(ptr, RtcpType::RTCP_RR, item_count, bytes);
|
||||
setupPadding(ptr, bytes - real_size);
|
||||
return std::shared_ptr<RtcpRR>(ptr, [](RtcpRR *ptr) {
|
||||
delete[] (char *) ptr;
|
||||
});
|
||||
@ -296,7 +384,6 @@ string RtcpRR::dumpString() const{
|
||||
void RtcpRR::net2Host(size_t size) {
|
||||
static const size_t kMinSize = sizeof(RtcpRR) - sizeof(items);
|
||||
CHECK_MIN_SIZE(size, kMinSize);
|
||||
RtcpHeader::net2Host();
|
||||
ssrc = ntohl(ssrc);
|
||||
|
||||
ReportItem *ptr = &items;
|
||||
@ -306,7 +393,7 @@ void RtcpRR::net2Host(size_t size) {
|
||||
++ptr;
|
||||
++item_count;
|
||||
}
|
||||
CHECK_LENGTH(size, item_count);
|
||||
CHECK_REPORT_COUNT(item_count);
|
||||
}
|
||||
|
||||
vector<ReportItem*> RtcpRR::getItemList() {
|
||||
@ -326,7 +413,7 @@ void SdesItem::net2Host() {
|
||||
}
|
||||
|
||||
size_t SdesItem::totalBytes() const{
|
||||
return alignSize(minSize() + length);
|
||||
return alignSize(minSize() + txt_len);
|
||||
}
|
||||
|
||||
size_t SdesItem::minSize() {
|
||||
@ -337,30 +424,32 @@ string SdesItem::dumpString() const{
|
||||
_StrPrinter printer;
|
||||
printer << "ssrc:" << ssrc << "\r\n";
|
||||
printer << "type:" << sdesTypeToStr((SdesType) type) << "\r\n";
|
||||
printer << "length:" << (int) length << "\r\n";
|
||||
printer << "text:" << (length ? string(&text, length) : "") << "\r\n";
|
||||
printer << "txt_len:" << (int) txt_len << "\r\n";
|
||||
printer << "text:" << (txt_len ? string(text, txt_len) : "") << "\r\n";
|
||||
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;
|
||||
for (auto &text : item_text) {
|
||||
//统计所有SdesItem对象占用的空间
|
||||
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 item_ptr = &ptr->items;
|
||||
for (auto &text : item_text) {
|
||||
item_ptr->length = (0xFF & text.size());
|
||||
item_ptr->txt_len = (0xFF & text.size());
|
||||
//确保赋值\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());
|
||||
}
|
||||
|
||||
setupHeader(ptr, RtcpType::RTCP_SDES, item_text.size(), bytes);
|
||||
setupPadding(ptr, bytes - real_size);
|
||||
return std::shared_ptr<RtcpSdes>(ptr, [](RtcpSdes *ptr) {
|
||||
delete [] (char *) ptr;
|
||||
});
|
||||
@ -381,7 +470,6 @@ string RtcpSdes::dumpString() const {
|
||||
void RtcpSdes::net2Host(size_t size) {
|
||||
static const size_t kMinSize = sizeof(RtcpSdes) - sizeof(items);
|
||||
CHECK_MIN_SIZE(size, kMinSize);
|
||||
RtcpHeader::net2Host();
|
||||
SdesItem *ptr = &items;
|
||||
int item_count = 0;
|
||||
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) {
|
||||
ptr = (SdesItem *) ((char *) ptr + ptr->totalBytes());
|
||||
++item_count;
|
||||
}
|
||||
CHECK_LENGTH(size, item_count);
|
||||
CHECK_REPORT_COUNT(item_count);
|
||||
}
|
||||
|
||||
vector<SdesItem *> RtcpSdes::getItemList() {
|
||||
@ -402,4 +490,202 @@ vector<SdesItem *> RtcpSdes::getItemList() {
|
||||
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
|
249
src/Rtcp/Rtcp.h
249
src/Rtcp/Rtcp.h
@ -25,8 +25,7 @@ namespace mediakit {
|
||||
#pragma pack(push, 1)
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
//https://datatracker.ietf.org/doc/rfc3550
|
||||
|
||||
//http://www.networksorcery.com/enp/protocol/rtcp.htm
|
||||
#define RTCP_PT_MAP(XX) \
|
||||
XX(RTCP_FIR, 192) \
|
||||
XX(RTCP_NACK, 193) \
|
||||
@ -44,6 +43,7 @@ namespace mediakit {
|
||||
XX(RTCP_RSI, 209) \
|
||||
XX(RTCP_TOKEN, 210)
|
||||
|
||||
//https://tools.ietf.org/html/rfc3550#section-6.5
|
||||
#define SDES_TYPE_MAP(XX) \
|
||||
XX(RTCP_SDES_END, 0) \
|
||||
XX(RTCP_SDES_CNAME, 1) \
|
||||
@ -55,6 +55,63 @@ namespace mediakit {
|
||||
XX(RTCP_SDES_NOTE, 7) \
|
||||
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类型枚举
|
||||
enum class RtcpType : uint8_t {
|
||||
#define XX(key, value) key = value,
|
||||
@ -69,6 +126,20 @@ enum class SdesType : uint8_t {
|
||||
#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转描述字符串
|
||||
*/
|
||||
@ -79,6 +150,16 @@ const char *rtcpTypeToStr(RtcpType type);
|
||||
*/
|
||||
const char *sdesTypeToStr(SdesType type);
|
||||
|
||||
/**
|
||||
* psfb枚举转描述字符串
|
||||
*/
|
||||
const char *psfbTypeToStr(PSFBType type);
|
||||
|
||||
/**
|
||||
* rtpfb枚举转描述字符串
|
||||
*/
|
||||
const char *rtpfbTypeToStr(RTPFBType type);
|
||||
|
||||
class RtcpHeader {
|
||||
public:
|
||||
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||
@ -91,13 +172,15 @@ public:
|
||||
#else
|
||||
//reception report count
|
||||
uint32_t report_count: 5;
|
||||
//padding,固定为0
|
||||
//padding,末尾是否有追加填充
|
||||
uint32_t padding: 1;
|
||||
//版本号,固定为2
|
||||
uint32_t version: 2;
|
||||
#endif
|
||||
//rtcp类型,RtcpType
|
||||
uint32_t pt: 8;
|
||||
|
||||
private:
|
||||
//长度
|
||||
uint32_t length: 16;
|
||||
|
||||
@ -124,11 +207,23 @@ public:
|
||||
*/
|
||||
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:
|
||||
//SdesType
|
||||
uint8_t type;
|
||||
//text长度股,可以为0
|
||||
uint8_t length;
|
||||
uint8_t txt_len;
|
||||
//不定长
|
||||
char text;
|
||||
char text[1];
|
||||
//最后以RTCP_SDES_END结尾
|
||||
//只字段为占位字段,不代表真实位置
|
||||
uint8_t end;
|
||||
@ -434,7 +529,7 @@ public:
|
||||
* @param item_text SdesItem列表,只赋值length和text部分
|
||||
* @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对象指针列表
|
||||
@ -456,6 +551,142 @@ private:
|
||||
void net2Host(size_t size);
|
||||
} 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)
|
||||
#pragma pack(pop)
|
||||
#endif // defined(_WIN32)
|
||||
|
517
src/Rtcp/RtcpFCI.cpp
Normal file
517
src/Rtcp/RtcpFCI.cpp
Normal file
@ -0,0 +1,517 @@
|
||||
/*
|
||||
* 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
|
369
src/Rtcp/RtcpFCI.h
Normal file
369
src/Rtcp/RtcpFCI.h
Normal file
@ -0,0 +1,369 @@
|
||||
/*
|
||||
* 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) {
|
||||
std::string type = dec.load<std::string>();
|
||||
if (type == "@setDataFrame") {
|
||||
setMetaData(dec);
|
||||
} else if (type == "onMetaData") {
|
||||
//兼容某些不规范的推流器
|
||||
_publisher_metadata = dec.load<AMFValue>();
|
||||
} else {
|
||||
TraceP(this) << "unknown notify:" << type;
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ using namespace toolkit;
|
||||
|
||||
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 {
|
||||
public:
|
||||
PacketSortor() = default;
|
||||
|
@ -458,18 +458,27 @@ size_t RtpHeader::getExtSize() const {
|
||||
return 0;
|
||||
}
|
||||
auto ext_ptr = &payload + getCsrcSize();
|
||||
uint16_t reserved = AV_RB16(ext_ptr);
|
||||
//uint16_t reserved = AV_RB16(ext_ptr);
|
||||
//每个ext占用4字节
|
||||
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() {
|
||||
if (!ext) {
|
||||
return nullptr;
|
||||
}
|
||||
auto ext_ptr = &payload + getCsrcSize();
|
||||
//多出的4个字节分别为reserved、ext_len
|
||||
return ext_ptr + 4 + getExtSize();
|
||||
return ext_ptr + 4;
|
||||
}
|
||||
|
||||
size_t RtpHeader::getPayloadOffset() const {
|
||||
@ -521,6 +530,10 @@ RtpHeader* RtpPacket::getHeader(){
|
||||
return (RtpHeader*)(data() + RtpPacket::kRtpTcpHeaderSize);
|
||||
}
|
||||
|
||||
string RtpPacket::dumpString() const{
|
||||
return ((RtpPacket *) this)->getHeader()->dumpString(size() - RtpPacket::kRtpTcpHeaderSize);
|
||||
}
|
||||
|
||||
uint16_t RtpPacket::getSeq(){
|
||||
return ntohs(getHeader()->seq);
|
||||
}
|
||||
|
@ -119,6 +119,8 @@ public:
|
||||
|
||||
//返回ext字段字节长度
|
||||
size_t getExtSize() const;
|
||||
//返回ext reserved值
|
||||
uint16_t getExtReserved() const;
|
||||
//返回ext段首地址,不存在时返回nullptr
|
||||
uint8_t *getExtData();
|
||||
|
||||
@ -150,7 +152,11 @@ public:
|
||||
kRtpTcpHeaderSize = 4
|
||||
};
|
||||
|
||||
//获取rtp头
|
||||
RtpHeader* getHeader();
|
||||
//打印调试信息
|
||||
string dumpString() const;
|
||||
|
||||
//主机字节序的seq
|
||||
uint16_t getSeq();
|
||||
//主机字节序的时间戳,已经转换为毫秒
|
||||
|
@ -56,8 +56,12 @@ public:
|
||||
//需要解复用rtp
|
||||
key_pos = _demuxer->inputRtp(rtp);
|
||||
}
|
||||
GET_CONFIG(bool, directProxy, Rtsp::kDirectProxy);
|
||||
if (directProxy) {
|
||||
//直接代理模式才直接使用原始rtp
|
||||
RtspMediaSource::onWrite(std::move(rtp), key_pos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取观看总人数,包括(hls/rtsp/rtmp)
|
||||
@ -72,8 +76,10 @@ public:
|
||||
* @param enableMP4 是否mp4录制
|
||||
*/
|
||||
void setProtocolTranslation(bool enableHls,bool enableMP4){
|
||||
//不重复生成rtsp
|
||||
_muxer = std::make_shared<MultiMediaSourceMuxer>(getVhost(), getApp(), getId(), _demuxer->getDuration(), false, true, enableHls, enableMP4);
|
||||
GET_CONFIG(bool, directProxy, Rtsp::kDirectProxy);
|
||||
//开启直接代理模式时,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->setTrackListener(static_pointer_cast<RtspMediaSourceImp>(shared_from_this()));
|
||||
//让_muxer对象拦截一部分事件(比如说录像相关事件)
|
||||
|
25
tests/test_rtcp_fci.cpp
Normal file
25
tests/test_rtcp_fci.cpp
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
37
tests/test_rtcp_nack.cpp
Normal file
37
tests/test_rtcp_nack.cpp
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
1483
webrtc/DtlsTransport.cpp
Normal file
1483
webrtc/DtlsTransport.cpp
Normal file
File diff suppressed because it is too large
Load Diff
254
webrtc/DtlsTransport.hpp
Normal file
254
webrtc/DtlsTransport.hpp
Normal file
@ -0,0 +1,254 @@
|
||||
/**
|
||||
ISC License
|
||||
|
||||
Copyright © 2015, Iñaki Baz Castillo <ibc@aliax.net>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MS_RTC_DTLS_TRANSPORT_HPP
|
||||
#define MS_RTC_DTLS_TRANSPORT_HPP
|
||||
|
||||
#include "SrtpSession.hpp"
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "Poller/Timer.h"
|
||||
#include "Poller/EventPoller.h"
|
||||
using namespace toolkit;
|
||||
|
||||
namespace RTC
|
||||
{
|
||||
class DtlsTransport : public std::enable_shared_from_this<DtlsTransport>
|
||||
{
|
||||
public:
|
||||
enum class DtlsState
|
||||
{
|
||||
NEW = 1,
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
FAILED,
|
||||
CLOSED
|
||||
};
|
||||
|
||||
public:
|
||||
enum class Role
|
||||
{
|
||||
NONE = 0,
|
||||
AUTO = 1,
|
||||
CLIENT,
|
||||
SERVER
|
||||
};
|
||||
|
||||
public:
|
||||
enum class FingerprintAlgorithm
|
||||
{
|
||||
NONE = 0,
|
||||
SHA1 = 1,
|
||||
SHA224,
|
||||
SHA256,
|
||||
SHA384,
|
||||
SHA512
|
||||
};
|
||||
|
||||
public:
|
||||
struct Fingerprint
|
||||
{
|
||||
FingerprintAlgorithm algorithm{ FingerprintAlgorithm::NONE };
|
||||
std::string value;
|
||||
};
|
||||
|
||||
private:
|
||||
struct SrtpCryptoSuiteMapEntry
|
||||
{
|
||||
RTC::SrtpSession::CryptoSuite cryptoSuite;
|
||||
const char* name;
|
||||
};
|
||||
|
||||
class DtlsEnvironment : public std::enable_shared_from_this<DtlsEnvironment>
|
||||
{
|
||||
public:
|
||||
using Ptr = std::shared_ptr<DtlsEnvironment>;
|
||||
~DtlsEnvironment();
|
||||
static DtlsEnvironment& Instance();
|
||||
|
||||
private:
|
||||
DtlsEnvironment();
|
||||
void GenerateCertificateAndPrivateKey();
|
||||
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
|
526
webrtc/IceServer.cpp
Normal file
526
webrtc/IceServer.cpp
Normal file
@ -0,0 +1,526 @@
|
||||
/**
|
||||
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
|
136
webrtc/IceServer.hpp
Normal file
136
webrtc/IceServer.hpp
Normal file
@ -0,0 +1,136 @@
|
||||
/**
|
||||
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
|
564
webrtc/RtpExt.cpp
Normal file
564
webrtc/RtpExt.cpp
Normal file
@ -0,0 +1,564 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
112
webrtc/RtpExt.h
Normal file
112
webrtc/RtpExt.h
Normal file
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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
|
1788
webrtc/Sdp.cpp
Normal file
1788
webrtc/Sdp.cpp
Normal file
File diff suppressed because it is too large
Load Diff
750
webrtc/Sdp.h
Normal file
750
webrtc/Sdp.h
Normal file
@ -0,0 +1,750 @@
|
||||
/*
|
||||
* 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
|
299
webrtc/SrtpSession.cpp
Normal file
299
webrtc/SrtpSession.cpp
Normal file
@ -0,0 +1,299 @@
|
||||
/**
|
||||
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
|
83
webrtc/SrtpSession.hpp
Normal file
83
webrtc/SrtpSession.hpp
Normal file
@ -0,0 +1,83 @@
|
||||
/**
|
||||
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
|
880
webrtc/StunPacket.cpp
Normal file
880
webrtc/StunPacket.cpp
Normal file
@ -0,0 +1,880 @@
|
||||
/**
|
||||
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
|
213
webrtc/StunPacket.hpp
Normal file
213
webrtc/StunPacket.hpp
Normal file
@ -0,0 +1,213 @@
|
||||
/**
|
||||
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
|
134
webrtc/Utils.hpp
Normal file
134
webrtc/Utils.hpp
Normal file
@ -0,0 +1,134 @@
|
||||
/**
|
||||
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
|
913
webrtc/WebRtcTransport.cpp
Normal file
913
webrtc/WebRtcTransport.cpp
Normal file
@ -0,0 +1,913 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
395
webrtc/WebRtcTransport.h
Normal file
395
webrtc/WebRtcTransport.h
Normal file
@ -0,0 +1,395 @@
|
||||
/*
|
||||
* 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;
|
||||
};
|
50
webrtc/answer.sdp
Normal file
50
webrtc/answer.sdp
Normal file
@ -0,0 +1,50 @@
|
||||
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
|
||||
|
69
webrtc/janus_answer.sdp
Normal file
69
webrtc/janus_answer.sdp
Normal file
@ -0,0 +1,69 @@
|
||||
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
|
83
webrtc/janus_offer.sdp
Normal file
83
webrtc/janus_offer.sdp
Normal file
@ -0,0 +1,83 @@
|
||||
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
|
29
webrtc/logger.h
Normal file
29
webrtc/logger.h
Normal file
@ -0,0 +1,29 @@
|
||||
#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
|
255
webrtc/offer-simulcast.sdp
Normal file
255
webrtc/offer-simulcast.sdp
Normal file
@ -0,0 +1,255 @@
|
||||
# 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}
|
169
webrtc/offer.sdp
Normal file
169
webrtc/offer.sdp
Normal file
@ -0,0 +1,169 @@
|
||||
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
|
37
webrtc/readme.md
Normal file
37
webrtc/readme.md
Normal file
@ -0,0 +1,37 @@
|
||||
# 致谢与声明
|
||||
本文件夹下部分文件提取自[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相关代码,改用自有版权代码。
|
||||
|
7648
www/webrtc/ZLMRTCClient.js
Normal file
7648
www/webrtc/ZLMRTCClient.js
Normal file
File diff suppressed because it is too large
Load Diff
1
www/webrtc/ZLMRTCClient.js.map
Normal file
1
www/webrtc/ZLMRTCClient.js.map
Normal file
File diff suppressed because one or more lines are too long
192
www/webrtc/index.html
Normal file
192
www/webrtc/index.html
Normal file
@ -0,0 +1,192 @@
|
||||
<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>
|
2
www/webrtc/readme.txt
Normal file
2
www/webrtc/readme.txt
Normal file
@ -0,0 +1,2 @@
|
||||
感谢[big panda](<2381267071@qq.com>) 开发并贡献此webrtc js测试客户端,
|
||||
其开源项目地址为:https://gitee.com/xiongguangjie/zlmrtcclient.js
|
Loading…
Reference in New Issue
Block a user