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

This commit is contained in:
ziyue 2021-06-23 17:32:09 +08:00
commit 307be4339f
62 changed files with 19119 additions and 169 deletions

1
.gitignore vendored
View File

@ -46,3 +46,4 @@
/3rdpart/media-server/.idea/
/ios/
/cmake-build-*
/3rdpart/ZLToolKit/cmake-build-mq/

View File

@ -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)

View File

@ -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
View 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)

View File

@ -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必须在此时间内完成握手否则服务器会断开链接单位秒

View File

@ -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 \

View File

@ -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 ()

View File

@ -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){
//开始推流事件

View File

@ -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__)){ \

View File

@ -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"

View File

@ -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";
}
}

View File

@ -45,7 +45,8 @@ enum class MediaOriginType : uint8_t {
pull,
ffmpeg_pull,
mp4_vod,
device_chn
device_chn,
rtc_push,
};
string getOriginTypeString(MediaOriginType type);

View File

@ -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};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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";
}

View File

@ -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 {

View File

@ -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() {

View File

@ -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

View File

@ -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{

View File

@ -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) << "; ";

View File

@ -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;
}

View File

@ -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";
}

View File

@ -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";
}

View File

@ -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

View File

@ -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
View 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
View 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

View File

@ -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;
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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();
//主机字节序的时间戳,已经转换为毫秒

View File

@ -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 ppsrtp中就不再包括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
View 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
View 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

File diff suppressed because it is too large Load Diff

254
webrtc/DtlsTransport.hpp Normal file
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

750
webrtc/Sdp.h Normal file
View 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
View 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)",
"cant 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)",
"cant 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)",
"couldnt read data (srtp_err_status_read_fail)",
"couldnt 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

192
www/webrtc/index.html Normal file
View 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
View File

@ -0,0 +1,2 @@
感谢[big panda](<2381267071@qq.com>) 开发并贡献此webrtc js测试客户端,
其开源项目地址为https://gitee.com/xiongguangjie/zlmrtcclient.js