mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2026-07-05 10:58:11 +08:00
Merge branch 'dev' of https://github.com/xia-chu/ZLMediaKit into dev_test
This commit is contained in:
@@ -35,6 +35,7 @@ string getOriginTypeString(MediaOriginType type){
|
||||
SWITCH_CASE(ffmpeg_pull);
|
||||
SWITCH_CASE(mp4_vod);
|
||||
SWITCH_CASE(device_chn);
|
||||
SWITCH_CASE(rtc_push);
|
||||
default : return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,8 @@ enum class MediaOriginType : uint8_t {
|
||||
pull,
|
||||
ffmpeg_pull,
|
||||
mp4_vod,
|
||||
device_chn
|
||||
device_chn,
|
||||
rtc_push,
|
||||
};
|
||||
|
||||
string getOriginTypeString(MediaOriginType type);
|
||||
|
||||
@@ -205,7 +205,7 @@ public:
|
||||
if (bitrate) {
|
||||
_printer << "b=AS:" << bitrate << "\r\n";
|
||||
}
|
||||
_printer << "a=rtpmap:" << payload_type << " mpeg4-generic/" << sample_rate << "/" << channels << "\r\n";
|
||||
_printer << "a=rtpmap:" << payload_type << " " << getCodecName() << "/" << sample_rate << "/" << channels << "\r\n";
|
||||
|
||||
string configStr;
|
||||
char buf[4] = {0};
|
||||
|
||||
@@ -69,7 +69,7 @@ CommonRtpEncoder::CommonRtpEncoder(CodecId codec, uint32_t ssrc, uint32_t mtu_si
|
||||
}
|
||||
|
||||
void CommonRtpEncoder::inputFrame(const Frame::Ptr &frame){
|
||||
auto stamp = frame->dts();
|
||||
auto stamp = frame->pts();
|
||||
auto ptr = frame->data() + frame->prefixSize();
|
||||
auto len = frame->size() - frame->prefixSize();
|
||||
auto remain_size = len;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include "Frame.h"
|
||||
#include "H264.h"
|
||||
#include "H265.h"
|
||||
|
||||
#include "Common/Parser.h"
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
@@ -106,30 +106,50 @@ Frame::Ptr Frame::getCacheAbleFrame(const Frame::Ptr &frame){
|
||||
return std::make_shared<FrameCacheAble>(frame);
|
||||
}
|
||||
|
||||
#define SWITCH_CASE(codec_id) case codec_id : return #codec_id
|
||||
const char *getCodecName(CodecId codecId) {
|
||||
TrackType getTrackType(CodecId codecId) {
|
||||
switch (codecId) {
|
||||
SWITCH_CASE(CodecH264);
|
||||
SWITCH_CASE(CodecH265);
|
||||
SWITCH_CASE(CodecAAC);
|
||||
SWITCH_CASE(CodecG711A);
|
||||
SWITCH_CASE(CodecG711U);
|
||||
SWITCH_CASE(CodecOpus);
|
||||
SWITCH_CASE(CodecL16);
|
||||
default : return "unknown codec";
|
||||
#define XX(name, type, value, str) case name : return type;
|
||||
CODEC_MAP(XX)
|
||||
#undef XX
|
||||
default : return TrackInvalid;
|
||||
}
|
||||
}
|
||||
|
||||
TrackType getTrackType(CodecId codecId){
|
||||
switch (codecId){
|
||||
case CodecH264:
|
||||
case CodecH265: return TrackVideo;
|
||||
case CodecAAC:
|
||||
case CodecG711A:
|
||||
case CodecG711U:
|
||||
case CodecOpus:
|
||||
case CodecL16: return TrackAudio;
|
||||
default: return TrackInvalid;
|
||||
const char *getCodecName(CodecId codec) {
|
||||
switch (codec) {
|
||||
#define XX(name, type, value, str) case name : return str;
|
||||
CODEC_MAP(XX)
|
||||
#undef XX
|
||||
default : return "invalid";
|
||||
}
|
||||
}
|
||||
|
||||
#define XX(name, type, value, str) {str, name},
|
||||
static map<string, CodecId, StrCaseCompare> codec_map = {CODEC_MAP(XX)};
|
||||
#undef XX
|
||||
|
||||
CodecId getCodecId(const string &str){
|
||||
auto it = codec_map.find(str);
|
||||
return it == codec_map.end() ? CodecInvalid : it->second;
|
||||
}
|
||||
|
||||
static map<string, TrackType, StrCaseCompare> track_str_map = {
|
||||
{"video", TrackVideo},
|
||||
{"audio", TrackAudio},
|
||||
{"application", TrackApplication}
|
||||
};
|
||||
|
||||
TrackType getTrackType(const string &str) {
|
||||
auto it = track_str_map.find(str);
|
||||
return it == track_str_map.end() ? TrackInvalid : it->second;
|
||||
}
|
||||
|
||||
const char* getTrackString(TrackType type){
|
||||
switch (type) {
|
||||
case TrackVideo : return "video";
|
||||
case TrackAudio : return "audio";
|
||||
case TrackApplication : return "application";
|
||||
default: return "invalid";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,16 +191,23 @@ bool FrameMerger::willFlush(const Frame::Ptr &frame) const{
|
||||
}
|
||||
switch (frame->getCodecId()) {
|
||||
case CodecH264 : {
|
||||
if (H264_TYPE(frame->data()[frame->prefixSize()]) == H264Frame::NAL_B_P) {
|
||||
//如果是264的b/p帧,那么也刷新输出
|
||||
auto type = H264_TYPE(frame->data()[frame->prefixSize()]);
|
||||
if ((frame->data()[frame->prefixSize()+1]&0x80) !=0 && type >=H264Frame::NAL_B_P && type<=H264Frame::NAL_IDR ) {// sei aud pps sps 不判断
|
||||
//264 新一帧的开始,刷新输出
|
||||
return true;
|
||||
}else{
|
||||
// 不刷新输出
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CodecH265 : {
|
||||
if (H265_TYPE(frame->data()[frame->prefixSize()]) == H265Frame::NAL_TRAIL_R) {
|
||||
//如果是265的TRAIL_R帧,那么也刷新输出
|
||||
auto type = H265_TYPE(frame->data()[frame->prefixSize()]);
|
||||
if ((type>=H265Frame::NAL_TRAIL_R &&type<= H265Frame::NAL_RSV_IRAP_VCL23) && ( (frame->data()[frame->prefixSize()+2]>>7 & 0x01) != 0)) {
|
||||
//first_slice_segment_in_pic_flag is frame start
|
||||
return true;
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -217,8 +244,33 @@ void FrameMerger::doMerge(BufferLikeString &merged, const Frame::Ptr &frame) con
|
||||
default: /*不可达*/ assert(0); break;
|
||||
}
|
||||
}
|
||||
|
||||
bool FrameMerger::shouldDrop(const Frame::Ptr &frame) const{
|
||||
switch (frame->getCodecId()) {
|
||||
case CodecH264:{
|
||||
auto type = H264_TYPE(frame->data()[frame->prefixSize()]);
|
||||
if(type == H264Frame::NAL_SEI || type == H264Frame::NAL_AUD){
|
||||
// 防止吧AUD或者SEI当成一帧
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CodecH265: {
|
||||
//如果是新的一帧,前面的缓存需要输出
|
||||
auto type = H265_TYPE(frame->data()[frame->prefixSize()]);
|
||||
if(type == H265Frame::NAL_AUD || type == H265Frame::NAL_SEI_PREFIX || type == H265Frame::NAL_SEI_SUFFIX){
|
||||
// 防止吧AUD或者SEI当成一帧
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void FrameMerger::inputFrame(const Frame::Ptr &frame, const onOutput &cb) {
|
||||
if(shouldDrop(frame)){
|
||||
return;
|
||||
}
|
||||
if (willFlush(frame)) {
|
||||
Frame::Ptr back = _frameCached.back();
|
||||
Buffer::Ptr merged_frame = back;
|
||||
|
||||
@@ -21,26 +21,52 @@ using namespace toolkit;
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
typedef enum {
|
||||
CodecInvalid = -1,
|
||||
CodecH264 = 0,
|
||||
CodecH265,
|
||||
CodecAAC,
|
||||
CodecG711A,
|
||||
CodecG711U,
|
||||
CodecOpus,
|
||||
CodecL16,
|
||||
CodecMax = 0x7FFF
|
||||
} CodecId;
|
||||
|
||||
typedef enum {
|
||||
TrackInvalid = -1,
|
||||
TrackVideo = 0,
|
||||
TrackAudio,
|
||||
TrackTitle,
|
||||
TrackMax = 3
|
||||
TrackApplication,
|
||||
TrackMax
|
||||
} TrackType;
|
||||
|
||||
#define CODEC_MAP(XX) \
|
||||
XX(CodecH264, TrackVideo, 0, "H264") \
|
||||
XX(CodecH265, TrackVideo, 1, "H265") \
|
||||
XX(CodecAAC, TrackAudio, 2, "mpeg4-generic") \
|
||||
XX(CodecG711A, TrackAudio, 3, "PCMA") \
|
||||
XX(CodecG711U, TrackAudio, 4, "PCMU") \
|
||||
XX(CodecOpus, TrackAudio, 5, "opus") \
|
||||
XX(CodecL16, TrackAudio, 6, "L16") \
|
||||
XX(CodecVP8, TrackVideo, 7, "VP8") \
|
||||
XX(CodecVP9, TrackVideo, 8, "VP9") \
|
||||
XX(CodecAV1, TrackVideo, 9, "AV1X")
|
||||
|
||||
typedef enum {
|
||||
CodecInvalid = -1,
|
||||
#define XX(name, type, value, str) name = value,
|
||||
CODEC_MAP(XX)
|
||||
#undef XX
|
||||
CodecMax
|
||||
} CodecId;
|
||||
|
||||
/**
|
||||
* 字符串转媒体类型转
|
||||
*/
|
||||
TrackType getTrackType(const string &str);
|
||||
|
||||
/**
|
||||
* 媒体类型转字符串
|
||||
*/
|
||||
const char* getTrackString(TrackType type);
|
||||
|
||||
/**
|
||||
* 根据SDP中描述获取codec_id
|
||||
* @param str
|
||||
* @return
|
||||
*/
|
||||
CodecId getCodecId(const string &str);
|
||||
|
||||
/**
|
||||
* 获取编码器名称
|
||||
*/
|
||||
@@ -449,6 +475,7 @@ public:
|
||||
private:
|
||||
bool willFlush(const Frame::Ptr &frame) const;
|
||||
void doMerge(BufferLikeString &buffer, const Frame::Ptr &frame) const;
|
||||
bool shouldDrop(const Frame::Ptr &frame) const;
|
||||
|
||||
private:
|
||||
int _type;
|
||||
|
||||
@@ -33,7 +33,7 @@ public:
|
||||
if (bitrate) {
|
||||
_printer << "b=AS:" << bitrate << "\r\n";
|
||||
}
|
||||
_printer << "a=rtpmap:" << payload_type << (codecId == CodecG711A ? " PCMA/" : " PCMU/") << sample_rate << "/" << channels << "\r\n";
|
||||
_printer << "a=rtpmap:" << payload_type << " " << getCodecName() << "/" << sample_rate << "/" << channels << "\r\n";
|
||||
_printer << "a=control:trackID=" << (int)TrackAudio << "\r\n";
|
||||
}
|
||||
|
||||
|
||||
@@ -181,17 +181,15 @@ void H264Track::inputFrame_l(const Frame::Ptr &frame){
|
||||
_pps = string(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize());
|
||||
break;
|
||||
}
|
||||
case H264Frame::NAL_IDR: {
|
||||
insertConfigFrame(frame);
|
||||
VideoTrack::inputFrame(frame);
|
||||
break;
|
||||
}
|
||||
case H264Frame::NAL_AUD: {
|
||||
//忽略AUD帧;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
if (frame->keyFrame()) {
|
||||
insertConfigFrame(frame);
|
||||
}
|
||||
VideoTrack::inputFrame(frame);
|
||||
break;
|
||||
}
|
||||
@@ -235,7 +233,7 @@ public:
|
||||
if (bitrate) {
|
||||
_printer << "b=AS:" << bitrate << "\r\n";
|
||||
}
|
||||
_printer << "a=rtpmap:" << payload_type << " H264/" << 90000 << "\r\n";
|
||||
_printer << "a=rtpmap:" << payload_type << " " << getCodecName() << "/" << 90000 << "\r\n";
|
||||
_printer << "a=fmtp:" << payload_type << " packetization-mode=1; profile-level-id=";
|
||||
|
||||
char strTemp[1024];
|
||||
@@ -281,7 +279,8 @@ Sdp::Ptr H264Track::getSdp() {
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool H264Frame::keyFrame() const {
|
||||
return H264_TYPE(_buffer[_prefix_size]) == H264Frame::NAL_IDR;
|
||||
//多slice 一帧的情况下检查 first_mb_in_slice 是否为0 表示其为一帧的开始
|
||||
return H264_TYPE(_buffer[_prefix_size]) == H264Frame::NAL_IDR && (_buffer[_prefix_size + 1] & 0x80);
|
||||
}
|
||||
|
||||
bool H264Frame::configFrame() const {
|
||||
|
||||
@@ -183,7 +183,7 @@ void H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
}
|
||||
}
|
||||
|
||||
if(_lastPacket && (_lastPacket->time_stamp != frame->dts() || type == H264Frame::NAL_B_P)) {
|
||||
if(_lastPacket && (_lastPacket->time_stamp != frame->dts() || ((pcData[1]&0x80) != 0 && type>=H264Frame::NAL_B_P && type<=H264Frame::NAL_IDR))) {
|
||||
RtmpCodec::inputRtmp(_lastPacket);
|
||||
_lastPacket = nullptr;
|
||||
}
|
||||
@@ -214,10 +214,6 @@ void H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
_lastPacket->buffer.append((char *) &size, 4);
|
||||
_lastPacket->buffer.append(pcData, iLen);
|
||||
_lastPacket->body_size = _lastPacket->buffer.size();
|
||||
if (type == H264Frame::NAL_B_P) {
|
||||
RtmpCodec::inputRtmp(_lastPacket);
|
||||
_lastPacket = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void H264RtmpEncoder::makeVideoConfigPkt() {
|
||||
|
||||
@@ -184,53 +184,109 @@ 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]);
|
||||
auto packet_size = getMaxSize() - 2;
|
||||
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);
|
||||
}
|
||||
|
||||
//末尾5bit为nalu type,固定为28(FU-A)
|
||||
auto fu_char_0 = (ptr[0] & (~0x1F)) | 28;
|
||||
auto fu_char_1 = nal_type;
|
||||
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) {
|
||||
//FU-A end
|
||||
packet_size = len - offset;
|
||||
fu_flags->end_bit = 1;
|
||||
}
|
||||
|
||||
//传入nullptr先不做payload的内存拷贝
|
||||
auto rtp = makeRtp(getTrackType(), nullptr, packet_size + 2, fu_flags->end_bit, pts);
|
||||
//rtp payload 负载部分
|
||||
uint8_t *payload = rtp->getPayload();
|
||||
//FU-A 第1个字节
|
||||
payload[0] = fu_char_0;
|
||||
//FU-A 第2个字节
|
||||
payload[1] = fu_char_1;
|
||||
//H264 数据
|
||||
memcpy(payload + 2, (uint8_t *) ptr + offset, packet_size);
|
||||
//输入到rtp环形缓存
|
||||
RtpCodec::inputRtp(rtp, fu_flags->start_bit && nal_type == H264Frame::NAL_IDR);
|
||||
|
||||
offset += packet_size;
|
||||
fu_flags->start_bit = 0;
|
||||
}
|
||||
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 {
|
||||
//如果帧长度不超过mtu, 则按照Single NAL unit packet per H.264 方式打包
|
||||
makeH264Rtp(ptr, len, false, false, pts);
|
||||
//STAP-A模式打包会大于MTU,所以采用FU-A模式
|
||||
packRtpFu(ptr, len, pts, is_mark, gop_pos);
|
||||
}
|
||||
}
|
||||
|
||||
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::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 = H264_TYPE(ptr[0]);
|
||||
FuFlags *fu_flags = (FuFlags *) (&fu_char_1);
|
||||
fu_flags->start_bit = 1;
|
||||
|
||||
size_t offset = 1;
|
||||
while (!fu_flags->end_bit) {
|
||||
if (!fu_flags->start_bit && len <= offset + packet_size) {
|
||||
//FU-A end
|
||||
packet_size = len - offset;
|
||||
fu_flags->end_bit = 1;
|
||||
}
|
||||
|
||||
//传入nullptr先不做payload的内存拷贝
|
||||
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个字节
|
||||
payload[0] = fu_char_0;
|
||||
//FU-A 第2个字节
|
||||
payload[1] = fu_char_1;
|
||||
//H264 数据
|
||||
memcpy(payload + 2, (uint8_t *) ptr + offset, packet_size);
|
||||
//输入到rtp环形缓存
|
||||
RtpCodec::inputRtp(rtp, gop_pos);
|
||||
|
||||
offset += packet_size;
|
||||
fu_flags->start_bit = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void H264RtpEncoder::packRtpStapA(const char *ptr, size_t len, uint32_t pts, bool is_mark, bool gop_pos){
|
||||
//如果帧长度不超过mtu,为了兼容性 webrtc,采用STAP-A模式打包
|
||||
auto rtp = makeRtp(getTrackType(), nullptr, len + 3, is_mark, pts);
|
||||
uint8_t *payload = rtp->getPayload();
|
||||
//STAP-A
|
||||
payload[0] = (ptr[0] & (~0x1F)) | 24;
|
||||
payload[1] = (len >> 8) & 0xFF;
|
||||
payload[2] = len & 0xff;
|
||||
memcpy(payload + 3, (uint8_t *) ptr, len);
|
||||
|
||||
RtpCodec::inputRtp(rtp, gop_pos);
|
||||
}
|
||||
|
||||
void H264RtpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
auto ptr = frame->data() + frame->prefixSize();
|
||||
switch (H264_TYPE(ptr[0])) {
|
||||
case H264Frame::NAL_AUD:
|
||||
case H264Frame::NAL_SEI : {
|
||||
return;
|
||||
}
|
||||
case H264Frame::NAL_SPS: {
|
||||
_sps = Frame::getCacheAbleFrame(frame);
|
||||
return;
|
||||
}
|
||||
case H264Frame::NAL_PPS: {
|
||||
_pps = Frame::getCacheAbleFrame(frame);
|
||||
return;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (_last_frame) {
|
||||
//如果时间戳发生了变化,那么markbit才置true
|
||||
inputFrame_l(_last_frame, _last_frame->pts() != frame->pts());
|
||||
}
|
||||
_last_frame = Frame::getCacheAbleFrame(frame);
|
||||
}
|
||||
|
||||
void H264RtpEncoder::inputFrame_l(const Frame::Ptr &frame, bool is_mark){
|
||||
if (frame->keyFrame()) {
|
||||
//保证每一个关键帧前都有SPS与PPS
|
||||
insertConfigFrame(frame->pts());
|
||||
}
|
||||
packRtp(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize(), frame->pts(), is_mark, false);
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
@@ -82,7 +82,16 @@ public:
|
||||
void inputFrame(const Frame::Ptr &frame) override;
|
||||
|
||||
private:
|
||||
void makeH264Rtp(const void *pData, size_t uiLen, bool bMark, bool gop_pos, uint32_t uiStamp);
|
||||
void insertConfigFrame(uint32_t pts);
|
||||
void inputFrame_l(const Frame::Ptr &frame, bool is_mark);
|
||||
void packRtp(const char *data, size_t len, uint32_t pts, bool is_mark, bool gop_pos);
|
||||
void packRtpFu(const char *data, size_t len, uint32_t pts, bool is_mark, bool gop_pos);
|
||||
void packRtpStapA(const char *data, size_t len, uint32_t pts, bool is_mark, bool gop_pos);
|
||||
|
||||
private:
|
||||
Frame::Ptr _sps;
|
||||
Frame::Ptr _pps;
|
||||
Frame::Ptr _last_frame;
|
||||
};
|
||||
|
||||
}//namespace mediakit{
|
||||
|
||||
@@ -64,10 +64,11 @@ bool H265Frame::configFrame() const {
|
||||
}
|
||||
|
||||
bool H265Frame::isKeyFrame(int type, const char *ptr) {
|
||||
if (!ptr || type != NAL_IDR_W_RADL) {
|
||||
return type >= NAL_BLA_W_LP && type <= NAL_RSV_IRAP_VCL23;
|
||||
if(ptr){
|
||||
return (((*((uint8_t *) ptr + 2)) >> 7) & 0x01) == 1 && (type == NAL_IDR_N_LP || type == NAL_IDR_W_RADL);
|
||||
}
|
||||
return (((*((uint8_t *) ptr + 2)) >> 7) & 0x01) == 1;
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
H265Frame::H265Frame(){
|
||||
@@ -254,7 +255,7 @@ public:
|
||||
if (bitrate) {
|
||||
_printer << "b=AS:" << bitrate << "\r\n";
|
||||
}
|
||||
_printer << "a=rtpmap:" << payload_type << " H265/" << 90000 << "\r\n";
|
||||
_printer << "a=rtpmap:" << payload_type << " " << getCodecName() << "/" << 90000 << "\r\n";
|
||||
_printer << "a=fmtp:" << payload_type << " ";
|
||||
_printer << "sprop-vps=";
|
||||
_printer << encodeBase64(strVPS) << "; ";
|
||||
|
||||
@@ -165,11 +165,11 @@ void H265RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
}
|
||||
}
|
||||
|
||||
if(type == H265Frame::NAL_SEI_PREFIX || type == H265Frame::NAL_SEI_SUFFIX){
|
||||
return;
|
||||
if(type == H265Frame::NAL_SEI_PREFIX || type == H265Frame::NAL_SEI_SUFFIX || type == H265Frame::NAL_AUD){
|
||||
return;// 防止sei aud 作为一帧
|
||||
}
|
||||
|
||||
if (_lastPacket && (_lastPacket->time_stamp != frame->dts() || type == H265Frame::NAL_TRAIL_R)) {
|
||||
if (_lastPacket && (_lastPacket->time_stamp != frame->dts() || (type >=H264Frame::NAL_B_P && type<=H264Frame::NAL_IDR && (pcData[2]>>7 &0x01) !=0))) {
|
||||
RtmpCodec::inputRtmp(_lastPacket);
|
||||
_lastPacket = nullptr;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public:
|
||||
if (bitrate) {
|
||||
_printer << "b=AS:" << bitrate << "\r\n";
|
||||
}
|
||||
_printer << "a=rtpmap:" << payload_type << " L16/" << sample_rate << "/" << channels << "\r\n";
|
||||
_printer << "a=rtpmap:" << payload_type << " " << getCodecName() << "/" << sample_rate << "/" << channels << "\r\n";
|
||||
_printer << "a=control:trackID=" << (int)TrackAudio << "\r\n";
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ public:
|
||||
if (bitrate) {
|
||||
_printer << "b=AS:" << bitrate << "\r\n";
|
||||
}
|
||||
_printer << "a=rtpmap:" << payload_type << " opus/" << sample_rate << "/" << channels << "\r\n";
|
||||
_printer << "a=rtpmap:" << payload_type << " " << getCodecName() << "/" << sample_rate << "/" << channels << "\r\n";
|
||||
_printer << "a=control:trackID=" << (int)TrackAudio << "\r\n";
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <assert.h>
|
||||
#include "Rtcp.h"
|
||||
#include "Util/logger.h"
|
||||
#include "RtcpFCI.h"
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
@@ -32,8 +34,26 @@ const char *sdesTypeToStr(SdesType type){
|
||||
}
|
||||
}
|
||||
|
||||
const char *psfbTypeToStr(PSFBType type) {
|
||||
switch (type){
|
||||
#define SWITCH_CASE(key, value) case PSFBType::key : return #value "(" #key ")";
|
||||
PSFB_TYPE_MAP(SWITCH_CASE)
|
||||
#undef SWITCH_CASE
|
||||
default: return "unknown payload-specific fb message fmt type";
|
||||
}
|
||||
}
|
||||
|
||||
const char *rtpfbTypeToStr(RTPFBType type) {
|
||||
switch (type){
|
||||
#define SWITCH_CASE(key, value) case RTPFBType::key : return #value "(" #key ")";
|
||||
RTPFB_TYPE_MAP(SWITCH_CASE)
|
||||
#undef SWITCH_CASE
|
||||
default: return "unknown transport layer feedback messages fmt type";
|
||||
}
|
||||
}
|
||||
|
||||
static size_t alignSize(size_t bytes) {
|
||||
return (size_t)((bytes + 3) / 4) << 2;
|
||||
return (size_t)((bytes + 3) >> 2 ) << 2;
|
||||
}
|
||||
|
||||
static void setupHeader(RtcpHeader *rtcp, RtcpType type, size_t report_count, size_t total_bytes) {
|
||||
@@ -45,23 +65,46 @@ static void setupHeader(RtcpHeader *rtcp, RtcpType type, size_t report_count, si
|
||||
//items总个数
|
||||
rtcp->report_count = report_count;
|
||||
rtcp->pt = (uint8_t) type;
|
||||
//不包含rtcp头的长度
|
||||
rtcp->length = htons((uint16_t)((total_bytes / 4) - 1));
|
||||
rtcp->setSize(total_bytes);
|
||||
}
|
||||
|
||||
static void setupPadding(RtcpHeader *rtcp, size_t padding_size) {
|
||||
if (padding_size) {
|
||||
rtcp->padding = 1;
|
||||
((uint8_t *) rtcp)[rtcp->getSize() - 1] = padding_size & 0xFF;
|
||||
} else {
|
||||
rtcp->padding = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RtcpHeader::net2Host() {
|
||||
length = ntohs(length);
|
||||
}
|
||||
|
||||
string RtcpHeader::dumpHeader() const{
|
||||
_StrPrinter printer;
|
||||
printer << "version:" << version << "\r\n";
|
||||
printer << "padding:" << padding << "\r\n";
|
||||
printer << "report_count:" << report_count << "\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,10 +125,39 @@ 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){
|
||||
switch ((RtcpType)pt) {
|
||||
case RtcpType::RTCP_SR: {
|
||||
@@ -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
|
||||
251
src/Rtcp/Rtcp.h
251
src/Rtcp/Rtcp.h
@@ -25,8 +25,7 @@ namespace mediakit {
|
||||
#pragma pack(push, 1)
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
//https://datatracker.ietf.org/doc/rfc3550
|
||||
|
||||
//http://www.networksorcery.com/enp/protocol/rtcp.htm
|
||||
#define RTCP_PT_MAP(XX) \
|
||||
XX(RTCP_FIR, 192) \
|
||||
XX(RTCP_NACK, 193) \
|
||||
@@ -43,7 +42,8 @@ namespace mediakit {
|
||||
XX(RTCP_AVB, 208) \
|
||||
XX(RTCP_RSI, 209) \
|
||||
XX(RTCP_TOKEN, 210)
|
||||
|
||||
|
||||
//https://tools.ietf.org/html/rfc3550#section-6.5
|
||||
#define SDES_TYPE_MAP(XX) \
|
||||
XX(RTCP_SDES_END, 0) \
|
||||
XX(RTCP_SDES_CNAME, 1) \
|
||||
@@ -55,6 +55,63 @@ namespace mediakit {
|
||||
XX(RTCP_SDES_NOTE, 7) \
|
||||
XX(RTCP_SDES_PRIVATE, 8)
|
||||
|
||||
//https://datatracker.ietf.org/doc/rfc4585/?include_text=1
|
||||
//6.3. Payload-Specific Feedback Messages
|
||||
//
|
||||
// Payload-Specific FB messages are identified by the value PT=PSFB as
|
||||
// RTCP message type.
|
||||
//
|
||||
// Three payload-specific FB messages are defined so far plus an
|
||||
// application layer FB message. They are identified by means of the
|
||||
// FMT parameter as follows:
|
||||
//
|
||||
// 0: unassigned
|
||||
// 1: Picture Loss Indication (PLI)
|
||||
// 2: Slice Loss Indication (SLI)
|
||||
// 3: Reference Picture Selection Indication (RPSI)
|
||||
// 4: FIR https://tools.ietf.org/html/rfc5104#section-4.3.1.1
|
||||
// 5: TSTR https://tools.ietf.org/html/rfc5104#section-4.3.2.1
|
||||
// 6: TSTN https://tools.ietf.org/html/rfc5104#section-4.3.2.1
|
||||
// 7: VBCM https://tools.ietf.org/html/rfc5104#section-4.3.4.1
|
||||
// 8-14: unassigned
|
||||
// 15: REMB / Application layer FB (AFB) message, https://tools.ietf.org/html/draft-alvestrand-rmcat-remb-03
|
||||
// 16-30: unassigned
|
||||
// 31: reserved for future expansion of the sequence number space
|
||||
#define PSFB_TYPE_MAP(XX) \
|
||||
XX(RTCP_PSFB_PLI, 1) \
|
||||
XX(RTCP_PSFB_SLI, 2) \
|
||||
XX(RTCP_PSFB_RPSI, 3) \
|
||||
XX(RTCP_PSFB_FIR, 4) \
|
||||
XX(RTCP_PSFB_TSTR, 5)\
|
||||
XX(RTCP_PSFB_TSTN, 6)\
|
||||
XX(RTCP_PSFB_VBCM, 7) \
|
||||
XX(RTCP_PSFB_REMB, 15)
|
||||
|
||||
//https://tools.ietf.org/html/rfc4585#section-6.2
|
||||
//6.2. Transport Layer Feedback Messages
|
||||
//
|
||||
// Transport layer FB messages are identified by the value RTPFB as RTCP
|
||||
// message type.
|
||||
//
|
||||
// A single general purpose transport layer FB message is defined in
|
||||
// this document: Generic NACK. It is identified by means of the FMT
|
||||
// parameter as follows:
|
||||
//
|
||||
// 0: unassigned
|
||||
// 1: Generic NACK
|
||||
// 2: reserved https://tools.ietf.org/html/rfc5104#section-4.2
|
||||
// 3: TMMBR https://tools.ietf.org/html/rfc5104#section-4.2.1.1
|
||||
// 4: TMMBN https://tools.ietf.org/html/rfc5104#section-4.2.2.1
|
||||
// 5-14: unassigned
|
||||
// 15 transport-cc https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01
|
||||
// 16-30: unassigned
|
||||
// 31: reserved for future expansion of the identifier number space
|
||||
#define RTPFB_TYPE_MAP(XX) \
|
||||
XX(RTCP_RTPFB_NACK, 1) \
|
||||
XX(RTCP_RTPFB_TMMBR, 3) \
|
||||
XX(RTCP_RTPFB_TMMBN, 4) \
|
||||
XX(RTCP_RTPFB_TWCC, 15)
|
||||
|
||||
//rtcp类型枚举
|
||||
enum class RtcpType : uint8_t {
|
||||
#define XX(key, value) key = value,
|
||||
@@ -69,6 +126,20 @@ enum class SdesType : uint8_t {
|
||||
#undef XX
|
||||
};
|
||||
|
||||
//psfb类型枚举
|
||||
enum class PSFBType : uint8_t {
|
||||
#define XX(key, value) key = value,
|
||||
PSFB_TYPE_MAP(XX)
|
||||
#undef XX
|
||||
};
|
||||
|
||||
//rtpfb类型枚举
|
||||
enum class RTPFBType : uint8_t {
|
||||
#define XX(key, value) key = value,
|
||||
RTPFB_TYPE_MAP(XX)
|
||||
#undef XX
|
||||
};
|
||||
|
||||
/**
|
||||
* RtcpType转描述字符串
|
||||
*/
|
||||
@@ -79,6 +150,16 @@ const char *rtcpTypeToStr(RtcpType type);
|
||||
*/
|
||||
const char *sdesTypeToStr(SdesType type);
|
||||
|
||||
/**
|
||||
* psfb枚举转描述字符串
|
||||
*/
|
||||
const char *psfbTypeToStr(PSFBType type);
|
||||
|
||||
/**
|
||||
* rtpfb枚举转描述字符串
|
||||
*/
|
||||
const char *rtpfbTypeToStr(RTPFBType type);
|
||||
|
||||
class RtcpHeader {
|
||||
public:
|
||||
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||
@@ -91,13 +172,15 @@ public:
|
||||
#else
|
||||
//reception report count
|
||||
uint32_t report_count: 5;
|
||||
//padding,固定为0
|
||||
//padding,末尾是否有追加填充
|
||||
uint32_t padding: 1;
|
||||
//版本号,固定为2
|
||||
uint32_t version: 2;
|
||||
#endif
|
||||
//rtcp类型,RtcpType
|
||||
uint32_t pt: 8;
|
||||
|
||||
private:
|
||||
//长度
|
||||
uint32_t length: 16;
|
||||
|
||||
@@ -124,11 +207,23 @@ public:
|
||||
*/
|
||||
string dumpString() const;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* 网络字节序转换为主机字节序
|
||||
* 根据length字段获取rtcp总长度
|
||||
*/
|
||||
void net2Host();
|
||||
size_t getSize() const;
|
||||
|
||||
/**
|
||||
* 后面追加padding数据长度
|
||||
*/
|
||||
size_t getPaddingSize() const;
|
||||
|
||||
/**
|
||||
* 设置rtcp length字段
|
||||
* @param size rtcp总长度,单位字节
|
||||
*/
|
||||
void setSize(size_t size);
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
* 打印字段详情
|
||||
@@ -389,9 +484,9 @@ public:
|
||||
//SdesType
|
||||
uint8_t type;
|
||||
//text长度股,可以为0
|
||||
uint8_t length;
|
||||
uint8_t txt_len;
|
||||
//不定长
|
||||
char text;
|
||||
char text[1];
|
||||
//最后以RTCP_SDES_END结尾
|
||||
//只字段为占位字段,不代表真实位置
|
||||
uint8_t end;
|
||||
@@ -434,7 +529,7 @@ public:
|
||||
* @param item_text SdesItem列表,只赋值length和text部分
|
||||
* @return SDES包
|
||||
*/
|
||||
static std::shared_ptr<RtcpSdes> create(const std::initializer_list<string> &item_text);
|
||||
static std::shared_ptr<RtcpSdes> create(const std::vector<string> &item_text);
|
||||
|
||||
/**
|
||||
* 获取SdesItem对象指针列表
|
||||
@@ -456,6 +551,142 @@ private:
|
||||
void net2Host(size_t size);
|
||||
} PACKED;
|
||||
|
||||
// https://tools.ietf.org/html/rfc4585#section-6.1
|
||||
// 6.1. Common Packet Format for Feedback Messages
|
||||
//
|
||||
// All FB messages MUST use a common packet format that is depicted in
|
||||
// Figure 3:
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// |V=2|P| FMT | PT | length |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | SSRC of packet sender |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | SSRC of media source |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// : Feedback Control Information (FCI) :
|
||||
// : :
|
||||
// rtcpfb和psfb的数据结构一致
|
||||
class RtcpFB : public RtcpHeader {
|
||||
public:
|
||||
friend class RtcpHeader;
|
||||
uint32_t ssrc;
|
||||
uint32_t ssrc_media;
|
||||
|
||||
public:
|
||||
/**
|
||||
* 创建psfb类型的反馈包
|
||||
*/
|
||||
static std::shared_ptr<RtcpFB> create(PSFBType fmt, const void *fci = nullptr, size_t fci_len = 0);
|
||||
|
||||
/**
|
||||
* 创建rtpfb类型的反馈包
|
||||
*/
|
||||
static std::shared_ptr<RtcpFB> create(RTPFBType fmt, const void *fci = nullptr, size_t fci_len = 0);
|
||||
|
||||
/**
|
||||
* fci转换成某对象指针
|
||||
* @tparam Type 对象类型
|
||||
* @return 对象指针
|
||||
*/
|
||||
template<typename Type>
|
||||
const Type& getFci() const{
|
||||
auto fci_data = getFciPtr();
|
||||
auto fci_len = getFciSize();
|
||||
Type *fci = (Type *) fci_data;
|
||||
fci->check(fci_len);
|
||||
return *fci;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取fci指针
|
||||
*/
|
||||
const void *getFciPtr() const;
|
||||
|
||||
/**
|
||||
* 获取fci数据长度
|
||||
*/
|
||||
size_t getFciSize() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* 打印字段详情
|
||||
* 使用net2Host转换成主机字节序后才可使用此函数
|
||||
*/
|
||||
string dumpString() const;
|
||||
|
||||
/**
|
||||
* 网络字节序转换为主机字节序
|
||||
* @param size 字节长度,防止内存越界
|
||||
*/
|
||||
void net2Host(size_t size);
|
||||
|
||||
private:
|
||||
static std::shared_ptr<RtcpFB> create_l(RtcpType type, int fmt, const void *fci, size_t fci_len);
|
||||
} PACKED;
|
||||
|
||||
//BYE
|
||||
/*
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|V=2|P| SC | PT=BYE=203 | length |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| SSRC/CSRC |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
: ... :
|
||||
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||
(opt) | length | reason for leaving ...
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
|
||||
class RtcpBye : public RtcpHeader {
|
||||
public:
|
||||
friend class RtcpHeader;
|
||||
/* 变长,根据count决定有多少个ssrc */
|
||||
uint32_t ssrc[1];
|
||||
|
||||
/** 中间可能有若干个 ssrc **/
|
||||
|
||||
/* 可选 */
|
||||
uint8_t reason_len;
|
||||
char reason[1];
|
||||
|
||||
public:
|
||||
/**
|
||||
* 创建bye包
|
||||
* @param ssrc ssrc列表
|
||||
* @param reason 原因
|
||||
* @return rtcp bye包
|
||||
*/
|
||||
static std::shared_ptr<RtcpBye> create(const std::vector<uint32_t> &ssrc, const string &reason);
|
||||
|
||||
/**
|
||||
* 获取ssrc列表
|
||||
*/
|
||||
vector<uint32_t *> getSSRC();
|
||||
|
||||
/**
|
||||
* 获取原因
|
||||
*/
|
||||
string getReason() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* 打印字段详情
|
||||
* 使用net2Host转换成主机字节序后才可使用此函数
|
||||
*/
|
||||
string dumpString() const;
|
||||
|
||||
/**
|
||||
* 网络字节序转换为主机字节序
|
||||
* @param size 字节长度,防止内存越界
|
||||
*/
|
||||
void net2Host(size_t size);
|
||||
} PACKED;
|
||||
|
||||
#if defined(_WIN32)
|
||||
#pragma pack(pop)
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
517
src/Rtcp/RtcpFCI.cpp
Normal file
517
src/Rtcp/RtcpFCI.cpp
Normal file
@@ -0,0 +1,517 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "RtcpFCI.h"
|
||||
#include "Util/logger.h"
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
void FCI_SLI::check(size_t size){
|
||||
CHECK(size >= kSize);
|
||||
}
|
||||
|
||||
FCI_SLI::FCI_SLI(uint16_t first, uint16_t number, uint8_t pic_id) {
|
||||
//13 bits
|
||||
first &= 0x1FFF;
|
||||
//13 bits
|
||||
number &= 0x1FFF;
|
||||
//6 bits
|
||||
pic_id &= 0x3F;
|
||||
data = (first << 19) | (number << 6) | pic_id;
|
||||
data = htonl(data);
|
||||
}
|
||||
|
||||
uint16_t FCI_SLI::getFirst() const {
|
||||
return ntohl(data) >> 19;
|
||||
}
|
||||
|
||||
uint16_t FCI_SLI::getNumber() const {
|
||||
return (ntohl(data) >> 6) & 0x1FFF;
|
||||
}
|
||||
|
||||
uint8_t FCI_SLI::getPicID() const {
|
||||
return ntohl(data) & 0x3F;
|
||||
}
|
||||
|
||||
string FCI_SLI::dumpString() const {
|
||||
return StrPrinter << "First:" << getFirst() << ", Number:" << getNumber() << ", PictureID:" << (int)getPicID();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void FCI_FIR::check(size_t size){
|
||||
CHECK(size >= kSize);
|
||||
}
|
||||
|
||||
uint32_t FCI_FIR::getSSRC() const{
|
||||
return ntohl(ssrc);
|
||||
}
|
||||
|
||||
uint8_t FCI_FIR::getSeq() const{
|
||||
return seq_number;
|
||||
}
|
||||
|
||||
uint32_t FCI_FIR::getReserved() const{
|
||||
return (reserved[0] << 16) | (reserved[1] << 8) | reserved[2];
|
||||
}
|
||||
|
||||
string FCI_FIR::dumpString() const {
|
||||
return StrPrinter << "ssrc:" << getSSRC() << ", seq_number:" << (int)getSeq() << ", reserved:" << getReserved();
|
||||
}
|
||||
|
||||
FCI_FIR::FCI_FIR(uint32_t ssrc, uint8_t seq_number, uint32_t reserved) {
|
||||
this->ssrc = htonl(ssrc);
|
||||
this->seq_number = seq_number;
|
||||
this->reserved[0] = (reserved >> 16) & 0xFF;
|
||||
this->reserved[1] = (reserved >> 8) & 0xFF;
|
||||
this->reserved[2] = reserved & 0xFF;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static const char kRembMagic[] = "REMB";
|
||||
|
||||
void FCI_REMB::check(size_t size){
|
||||
CHECK(size >= kSize);
|
||||
CHECK(memcmp(magic, kRembMagic, sizeof(magic)) == 0);
|
||||
auto num_ssrc = bitrate[0];
|
||||
auto expect_size = kSize + 4 * num_ssrc;
|
||||
CHECK(size >= expect_size);
|
||||
}
|
||||
|
||||
string FCI_REMB::create(const vector<uint32_t> &ssrcs, uint32_t bitrate) {
|
||||
CHECK(ssrcs.size() > 0 && ssrcs.size() <= 0xFF);
|
||||
string ret;
|
||||
ret.resize(kSize + ssrcs.size() * 4);
|
||||
FCI_REMB *thiz = (FCI_REMB *) ret.data();
|
||||
memcpy(thiz->magic, kRembMagic, sizeof(magic));
|
||||
|
||||
/* bitrate --> BR Exp/BR Mantissa */
|
||||
uint8_t b = 0;
|
||||
uint8_t exp = 0;
|
||||
uint32_t mantissa = 0;
|
||||
for (b = 0; b < 32; b++) {
|
||||
if (bitrate <= ((uint32_t) 0x3FFFF << b)) {
|
||||
exp = b;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (b > 31) {
|
||||
b = 31;
|
||||
}
|
||||
mantissa = bitrate >> b;
|
||||
//Num SSRC (8 bits)
|
||||
thiz->bitrate[0] = ssrcs.size() & 0xFF;
|
||||
//BR Exp (6 bits)/BR Mantissa (18 bits)
|
||||
thiz->bitrate[1] = (uint8_t) ((exp << 2) + ((mantissa >> 16) & 0x03));
|
||||
//BR Mantissa (18 bits)
|
||||
thiz->bitrate[2] = (uint8_t) (mantissa >> 8);
|
||||
//BR Mantissa (18 bits)
|
||||
thiz->bitrate[3] = (uint8_t) (mantissa);
|
||||
|
||||
//设置ssrc列表
|
||||
auto ptr = thiz->ssrc_feedback;
|
||||
for (auto ssrc : ssrcs) {
|
||||
*(ptr++) = htonl(ssrc);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t FCI_REMB::getBitRate() const {
|
||||
uint8_t exp = (bitrate[1] >> 2) & 0x3F;
|
||||
uint32_t mantissa = (bitrate[1] & 0x03) << 16;
|
||||
mantissa += (bitrate[2] << 8);
|
||||
mantissa += (bitrate[3]);
|
||||
return mantissa << exp;
|
||||
}
|
||||
|
||||
vector<uint32_t> FCI_REMB::getSSRC() {
|
||||
vector<uint32_t> ret;
|
||||
auto num_ssrc = bitrate[0];
|
||||
auto ptr = ssrc_feedback;
|
||||
while (num_ssrc--) {
|
||||
ret.emplace_back(ntohl(*ptr++));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
string FCI_REMB::dumpString() const {
|
||||
_StrPrinter printer;
|
||||
printer << "bitrate:" << getBitRate() << ", ssrc:";
|
||||
for (auto &ssrc : ((FCI_REMB *) this)->getSSRC()) {
|
||||
printer << ssrc << " ";
|
||||
}
|
||||
return std::move(printer);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
FCI_NACK::FCI_NACK(uint16_t pid_h, const vector<bool> &type) {
|
||||
uint16_t blp_h = 0;
|
||||
int i = kBitSize;
|
||||
for (auto item : type) {
|
||||
--i;
|
||||
if (item) {
|
||||
blp_h |= (1 << i);
|
||||
}
|
||||
}
|
||||
blp = htons(blp_h);
|
||||
pid = htons(pid_h);
|
||||
}
|
||||
|
||||
void FCI_NACK::check(size_t size){
|
||||
CHECK(size >= kSize);
|
||||
}
|
||||
|
||||
uint16_t FCI_NACK::getPid() const {
|
||||
return ntohs(pid);
|
||||
}
|
||||
|
||||
uint16_t FCI_NACK::getBlp() const {
|
||||
return ntohs(blp);
|
||||
}
|
||||
|
||||
vector<bool> FCI_NACK::getBitArray() const {
|
||||
vector<bool> ret;
|
||||
ret.resize(kBitSize + 1);
|
||||
//nack第一个包丢包
|
||||
ret[0] = true;
|
||||
|
||||
auto blp_h = getBlp();
|
||||
for (size_t i = 0; i < kBitSize; ++i) {
|
||||
ret[i + 1] = blp_h & (1 << (kBitSize - i - 1));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
string FCI_NACK::dumpString() const {
|
||||
_StrPrinter printer;
|
||||
printer << "pid:" << getPid() << ",blp:" << getBlp() << ",bit array:";
|
||||
for (auto flag : getBitArray()) {
|
||||
printer << flag << " ";
|
||||
}
|
||||
return std::move(printer);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class RunLengthChunk {
|
||||
public:
|
||||
static size_t constexpr kSize = 2;
|
||||
// 0 1
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// |T| S | Run Length |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||
uint16_t type: 1;
|
||||
uint16_t symbol: 2;
|
||||
uint16_t run_length_high: 5;
|
||||
#else
|
||||
// Run Length 高5位
|
||||
uint16_t run_length_high: 5;
|
||||
//参考SymbolStatus定义
|
||||
uint16_t symbol: 2;
|
||||
//固定为0
|
||||
uint16_t type: 1;
|
||||
#endif
|
||||
// Run Length 低8位
|
||||
uint16_t run_length_low: 8;
|
||||
|
||||
//获取Run Length
|
||||
uint16_t getRunLength() const;
|
||||
//构造函数
|
||||
RunLengthChunk(SymbolStatus status, uint16_t run_length);
|
||||
//打印本对象
|
||||
string dumpString() const;
|
||||
} PACKED;
|
||||
|
||||
RunLengthChunk::RunLengthChunk(SymbolStatus status, uint16_t run_length) {
|
||||
type = 0;
|
||||
symbol = (uint8_t)status & 0x03;
|
||||
run_length_high = (run_length >> 8) & 0x1F;
|
||||
run_length_low = run_length & 0xFF;
|
||||
}
|
||||
|
||||
uint16_t RunLengthChunk::getRunLength() const {
|
||||
CHECK(type == 0);
|
||||
return run_length_high << 8 | run_length_low;
|
||||
}
|
||||
|
||||
string RunLengthChunk::dumpString() const{
|
||||
_StrPrinter printer;
|
||||
printer << "run length chunk, symbol:" << (int)symbol << ", run length:" << getRunLength();
|
||||
return std::move(printer);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class StatusVecChunk {
|
||||
public:
|
||||
static size_t constexpr kSize = 2;
|
||||
// 0 1
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// |T|S| symbol list |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||
uint16_t type: 1;
|
||||
uint16_t symbol: 1;
|
||||
uint16_t symbol_list_high: 6;
|
||||
#else
|
||||
// symbol_list 高6位
|
||||
uint16_t symbol_list_high: 6;
|
||||
//symbol_list中元素是1个还是2个bit
|
||||
uint16_t symbol: 1;
|
||||
//固定为1
|
||||
uint16_t type: 1;
|
||||
#endif
|
||||
// symbol_list 低8位
|
||||
uint16_t symbol_list_low: 8;
|
||||
|
||||
//获取symbollist
|
||||
vector<SymbolStatus> getSymbolList() const;
|
||||
//构造函数
|
||||
StatusVecChunk(const vector<SymbolStatus> &status);
|
||||
//打印本对象
|
||||
string dumpString() const;
|
||||
} PACKED;
|
||||
|
||||
StatusVecChunk::StatusVecChunk(const vector<SymbolStatus> &status) {
|
||||
uint16_t value = 0;
|
||||
type = 1;
|
||||
if (status.size() == 14) {
|
||||
symbol = 0;
|
||||
} else if (status.size() == 7) {
|
||||
symbol = 1;
|
||||
} else {
|
||||
//非法
|
||||
CHECK(0);
|
||||
}
|
||||
int i = 13;
|
||||
for (auto &item : status) {
|
||||
CHECK(item <= SymbolStatus::reserved);
|
||||
if (!symbol) {
|
||||
CHECK(item <= SymbolStatus::small_delta);
|
||||
value |= (int) item << i;
|
||||
--i;
|
||||
} else {
|
||||
value |= (int) item << (i - 1);
|
||||
i -= 2;
|
||||
}
|
||||
}
|
||||
symbol_list_low = value & 0xFF;
|
||||
symbol_list_high = (value >> 8 ) & 0x1F;
|
||||
}
|
||||
|
||||
vector<SymbolStatus> StatusVecChunk::getSymbolList() const {
|
||||
CHECK(type == 1);
|
||||
vector<SymbolStatus> ret;
|
||||
auto thiz = ntohs(*((uint16_t *) this));
|
||||
if (symbol == 0) {
|
||||
//s = 0 时,表示symbollist的每一个bit能表示一个数据包的到达状态
|
||||
for (int i = 13; i >= 0; --i) {
|
||||
SymbolStatus status = (SymbolStatus) ((bool) (thiz & (1 << i)));
|
||||
ret.emplace_back(status);
|
||||
}
|
||||
} else {
|
||||
//s = 1 时,表示symbollist每两个bit表示一个数据包的状态
|
||||
for (int i = 12; i >= 0; i -= 2) {
|
||||
SymbolStatus status = (SymbolStatus) ((thiz & (3 << i)) >> i);
|
||||
ret.emplace_back(status);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
string StatusVecChunk::dumpString() const {
|
||||
_StrPrinter printer;
|
||||
printer << "status vector chunk, symbol:" << (int) symbol << ", symbol list:";
|
||||
auto vec = getSymbolList();
|
||||
for (auto &item : vec) {
|
||||
printer << (int) item << " ";
|
||||
}
|
||||
return std::move(printer);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
void FCI_TWCC::check(size_t size){
|
||||
CHECK(size >= kSize);
|
||||
}
|
||||
|
||||
uint16_t FCI_TWCC::getBaseSeq() const {
|
||||
return ntohs(base_seq);
|
||||
}
|
||||
|
||||
uint16_t FCI_TWCC::getPacketCount() const {
|
||||
return ntohs(pkt_status_count);
|
||||
}
|
||||
|
||||
uint32_t FCI_TWCC::getReferenceTime() const {
|
||||
uint32_t ret = 0;
|
||||
ret |= ref_time[0] << 16;
|
||||
ret |= ref_time[1] << 8;
|
||||
ret |= ref_time[2];
|
||||
return ret;
|
||||
}
|
||||
//3.1.5. Receive Delta
|
||||
//
|
||||
// Deltas are represented as multiples of 250us:
|
||||
//
|
||||
// o If the "Packet received, small delta" symbol has been appended to
|
||||
// the status list, an 8-bit unsigned receive delta will be appended
|
||||
// to recv delta list, representing a delta in the range [0, 63.75]
|
||||
// ms.
|
||||
//
|
||||
// o If the "Packet received, large or negative delta" symbol has been
|
||||
// appended to the status list, a 16-bit signed receive delta will be
|
||||
// appended to recv delta list, representing a delta in the range
|
||||
// [-8192.0, 8191.75] ms.
|
||||
//
|
||||
// o If the delta exceeds even the larger limits, a new feedback
|
||||
// message must be used, where the 24-bit base receive delta can
|
||||
// cover very large gaps.
|
||||
//
|
||||
// The smaller receive delta upper bound of 63.75 ms means that this is
|
||||
// only viable at about 1000/25.5 ~= 16 packets per second and above.
|
||||
// With a packet size of 1200 bytes/packet that amounts to a bitrate of
|
||||
// about 150 kbit/s.
|
||||
//
|
||||
// The 0.25 ms resolution means that up to 4000 packets per second can
|
||||
// be represented. With a 1200 bytes/packet payload, that amounts to
|
||||
// 38.4 Mbit/s payload bandwidth.
|
||||
|
||||
static int16_t getRecvDelta(SymbolStatus status, uint8_t *&ptr, const uint8_t *end){
|
||||
int16_t delta = 0;
|
||||
switch (status) {
|
||||
case SymbolStatus::not_received : {
|
||||
//丢包, recv delta为0个字节
|
||||
break;
|
||||
}
|
||||
case SymbolStatus::small_delta : {
|
||||
CHECK(ptr + 1 <= end);
|
||||
//时间戳增量小于256, recv delta为1个字节
|
||||
delta = *ptr;
|
||||
ptr += 1;
|
||||
break;
|
||||
}
|
||||
case SymbolStatus::large_delta : {
|
||||
CHECK(ptr + 2 <= end);
|
||||
//时间戳增量256~65535间,recv delta为2个字节
|
||||
delta = *ptr << 8 | *(ptr + 1);
|
||||
ptr += 2;
|
||||
break;
|
||||
}
|
||||
case SymbolStatus::reserved : {
|
||||
//没有时间戳
|
||||
break;
|
||||
}
|
||||
default:
|
||||
//这个逻辑分支不可达到
|
||||
CHECK(0);
|
||||
break;
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
|
||||
map<uint16_t, std::pair<SymbolStatus, uint32_t/*stamp*/> > FCI_TWCC::getPacketChunkList(size_t total_size) const {
|
||||
map<uint16_t, std::pair<SymbolStatus, uint32_t> > ret;
|
||||
auto ptr = (uint8_t *) this + kSize;
|
||||
auto end = (uint8_t *) this + total_size;
|
||||
CHECK(ptr < end);
|
||||
auto seq = getBaseSeq();
|
||||
auto rtp_count = getPacketCount();
|
||||
for (uint8_t i = 0; i < rtp_count;) {
|
||||
CHECK(ptr + RunLengthChunk::kSize <= end);
|
||||
RunLengthChunk *chunk = (RunLengthChunk *) ptr;
|
||||
if (!chunk->type) {
|
||||
//RunLengthChunk
|
||||
for (auto j = 0; j < chunk->getRunLength(); ++j) {
|
||||
ret.emplace(seq++, std::make_pair((SymbolStatus) chunk->symbol, 0));
|
||||
if (++i >= rtp_count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//StatusVecChunk
|
||||
StatusVecChunk *chunk = (StatusVecChunk *) ptr;
|
||||
for (auto &symbol : chunk->getSymbolList()) {
|
||||
ret.emplace(seq++, std::make_pair(symbol, 0));
|
||||
if (++i >= rtp_count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ptr += 2;
|
||||
}
|
||||
for (auto &pr : ret) {
|
||||
CHECK(ptr <= end);
|
||||
pr.second.second = 250 * getRecvDelta(pr.second.first, ptr, end);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
string FCI_TWCC::dumpString(size_t total_size) const {
|
||||
_StrPrinter printer;
|
||||
auto map = getPacketChunkList(total_size);
|
||||
printer << "twcc fci, base_seq:" << getBaseSeq() << ", pkt_status_count:" << getPacketCount() << ", ref time:" << getReferenceTime() << ", fb count:" << (int)fb_pkt_count << "\n";
|
||||
for (auto &pr : map) {
|
||||
printer << "rtp seq:" << pr.first <<", packet status:" << (int)(pr.second.first) << ", delta:" << pr.second.second << "\n";
|
||||
}
|
||||
return std::move(printer);
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
#if 1
|
||||
using namespace mediakit;
|
||||
void testFCI() {
|
||||
{
|
||||
FCI_SLI fci(8191, 0, 63);
|
||||
InfoL << hexdump(&fci, FCI_SLI::kSize) << fci.dumpString();
|
||||
}
|
||||
{
|
||||
FCI_FIR fci(123456, 139, 456789);
|
||||
InfoL << hexdump(&fci, FCI_FIR::kSize) << fci.dumpString();
|
||||
}
|
||||
{
|
||||
auto str = FCI_REMB::create({1234, 2345, 5678}, 4 * 1024 * 1024);
|
||||
FCI_REMB *ptr = (FCI_REMB *) str.data();
|
||||
InfoL << hexdump(str.data(), str.size()) << ptr->dumpString();
|
||||
}
|
||||
{
|
||||
FCI_NACK nack(1234, vector<bool>({1, 0, 0, 0, 1, 0, 1, 0, 1, 0}));
|
||||
InfoL << hexdump(&nack, FCI_NACK::kSize) << nack.dumpString();
|
||||
}
|
||||
|
||||
{
|
||||
RunLengthChunk chunk(SymbolStatus::large_delta, 8024);
|
||||
InfoL << hexdump(&chunk, RunLengthChunk::kSize) << chunk.dumpString();
|
||||
}
|
||||
|
||||
auto lam = [](const initializer_list<int> &lst) {
|
||||
vector<SymbolStatus> ret;
|
||||
for (auto &num : lst) {
|
||||
ret.emplace_back((SymbolStatus) num);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
{
|
||||
StatusVecChunk chunk(lam({0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1}));
|
||||
InfoL << hexdump(&chunk, StatusVecChunk::kSize) << chunk.dumpString();
|
||||
}
|
||||
{
|
||||
StatusVecChunk chunk(lam({0, 1, 2, 2, 0, 1, 2}));
|
||||
InfoL << hexdump(&chunk, StatusVecChunk::kSize) << chunk.dumpString();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
369
src/Rtcp/RtcpFCI.h
Normal file
369
src/Rtcp/RtcpFCI.h
Normal file
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef ZLMEDIAKIT_RTCPFCI_H
|
||||
#define ZLMEDIAKIT_RTCPFCI_H
|
||||
|
||||
#include "Rtcp.h"
|
||||
#include "assert.h"
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
/////////////////////////////////////////// PSFB ////////////////////////////////////////////////////
|
||||
|
||||
//PSFB fmt = 2
|
||||
//https://tools.ietf.org/html/rfc4585#section-6.3.2.2
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | First | Number | PictureID |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
//First: 13 bits
|
||||
// The macroblock (MB) address of the first lost macroblock. The MB
|
||||
// numbering is done such that the macroblock in the upper left
|
||||
// corner of the picture is considered macroblock number 1 and the
|
||||
// number for each macroblock increases from left to right and then
|
||||
// from top to bottom in raster-scan order (such that if there is a
|
||||
// total of N macroblocks in a picture, the bottom right macroblock
|
||||
// is considered macroblock number N).
|
||||
//
|
||||
// Number: 13 bits
|
||||
// The number of lost macroblocks, in scan order as discussed above.
|
||||
//
|
||||
// PictureID: 6 bits
|
||||
// The six least significant bits of the codec-specific identifier
|
||||
// that is used to reference the picture in which the loss of the
|
||||
// macroblock(s) has occurred. For many video codecs, the PictureID
|
||||
// is identical to the Temporal Reference.
|
||||
class FCI_SLI {
|
||||
public:
|
||||
static size_t constexpr kSize = 4;
|
||||
|
||||
FCI_SLI(uint16_t first, uint16_t number, uint8_t pic_id);
|
||||
|
||||
void check(size_t size);
|
||||
uint16_t getFirst() const;
|
||||
uint16_t getNumber() const;
|
||||
uint8_t getPicID() const;
|
||||
string dumpString() const;
|
||||
|
||||
private:
|
||||
uint32_t data;
|
||||
} PACKED;
|
||||
|
||||
//PSFB fmt = 3
|
||||
//https://tools.ietf.org/html/rfc4585#section-6.3.3.2
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | PB |0| Payload Type| Native RPSI bit string |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | defined per codec ... | Padding (0) |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
class FCI_RPSI {
|
||||
public:
|
||||
//The number of unused bits required to pad the length of the RPSI
|
||||
// message to a multiple of 32 bits.
|
||||
uint8_t pb;
|
||||
|
||||
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||
//0: 1 bit
|
||||
// MUST be set to zero upon transmission and ignored upon reception.
|
||||
uint8_t zero : 1;
|
||||
//Payload Type: 7 bits
|
||||
// Indicates the RTP payload type in the context of which the native
|
||||
// RPSI bit string MUST be interpreted.
|
||||
uint8_t pt : 7;
|
||||
#else
|
||||
uint8_t pt: 7;
|
||||
uint8_t zero: 1;
|
||||
#endif
|
||||
|
||||
// Native RPSI bit string: variable length
|
||||
// The RPSI information as natively defined by the video codec.
|
||||
char bit_string[5];
|
||||
|
||||
//Padding: #PB bits
|
||||
// A number of bits set to zero to fill up the contents of the RPSI
|
||||
// message to the next 32-bit boundary. The number of padding bits
|
||||
// MUST be indicated by the PB field.
|
||||
uint8_t padding;
|
||||
|
||||
static size_t constexpr kSize = 8;
|
||||
} PACKED;
|
||||
|
||||
//PSFB fmt = 4
|
||||
//https://tools.ietf.org/html/rfc5104#section-4.3.1.1
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | SSRC |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Seq nr. | Reserved |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
class FCI_FIR {
|
||||
public:
|
||||
static size_t constexpr kSize = 8;
|
||||
|
||||
FCI_FIR(uint32_t ssrc, uint8_t seq_number, uint32_t reserved = 0);
|
||||
|
||||
void check(size_t size);
|
||||
uint32_t getSSRC() const;
|
||||
uint8_t getSeq() const;
|
||||
uint32_t getReserved() const;
|
||||
string dumpString() const;
|
||||
|
||||
private:
|
||||
uint32_t ssrc;
|
||||
uint8_t seq_number;
|
||||
uint8_t reserved[3];
|
||||
} PACKED;
|
||||
|
||||
//PSFB fmt = 5
|
||||
//https://tools.ietf.org/html/rfc5104#section-4.3.2.1
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | SSRC |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Seq nr. | Reserved | Index |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
class FCI_TSTR {
|
||||
public:
|
||||
static size_t constexpr kSize = 8;
|
||||
|
||||
void check(size_t size) {
|
||||
CHECK(size == kSize);
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t data[kSize];
|
||||
} PACKED;
|
||||
|
||||
//PSFB fmt = 6
|
||||
//https://tools.ietf.org/html/rfc5104#section-4.3.2.1
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | SSRC |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Seq nr. | Reserved | Index |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
class FCI_TSTN : public FCI_TSTR{
|
||||
|
||||
} PACKED;
|
||||
|
||||
//PSFB fmt = 7
|
||||
//https://tools.ietf.org/html/rfc5104#section-4.3.4.1
|
||||
//0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | SSRC |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Seq nr. |0| Payload Type| Length |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | VBCM Octet String.... | Padding |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
class FCI_VBCM {
|
||||
public:
|
||||
static size_t constexpr kSize = 12;
|
||||
|
||||
void check(size_t size) {
|
||||
CHECK(size == kSize);
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t data[kSize];
|
||||
} PACKED;
|
||||
|
||||
//PSFB fmt = 15
|
||||
//https://tools.ietf.org/html/draft-alvestrand-rmcat-remb-03
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Unique identifier 'R' 'E' 'M' 'B' |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Num SSRC | BR Exp | BR Mantissa |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | SSRC feedback |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | ... |
|
||||
// Num SSRC (8 bits): Number of SSRCs in this message.
|
||||
//
|
||||
// BR Exp (6 bits): The exponential scaling of the mantissa for the
|
||||
// maximum total media bit rate value, ignoring all packet
|
||||
// overhead. The value is an unsigned integer [0..63], as
|
||||
// in RFC 5104 section 4.2.2.1.
|
||||
//
|
||||
// BR Mantissa (18 bits): The mantissa of the maximum total media bit
|
||||
// rate (ignoring all packet overhead) that the sender of
|
||||
// the REMB estimates. The BR is the estimate of the
|
||||
// traveled path for the SSRCs reported in this message.
|
||||
// The value is an unsigned integer in number of bits per
|
||||
// second.
|
||||
//
|
||||
// SSRC feedback (32 bits) Consists of one or more SSRC entries which
|
||||
// this feedback message applies to.
|
||||
class FCI_REMB {
|
||||
public:
|
||||
static size_t constexpr kSize = 8;
|
||||
|
||||
static string create(const std::vector<uint32_t> &ssrcs, uint32_t bitrate);
|
||||
void check(size_t size);
|
||||
string dumpString() const;
|
||||
uint32_t getBitRate() const;
|
||||
vector<uint32_t> getSSRC();
|
||||
|
||||
private:
|
||||
//Unique identifier 'R' 'E' 'M' 'B'
|
||||
char magic[4];
|
||||
//Num SSRC (8 bits)/BR Exp (6 bits)/ BR Mantissa (18 bits)
|
||||
uint8_t bitrate[4];
|
||||
// SSRC feedback (32 bits) Consists of one or more SSRC entries which
|
||||
// this feedback message applies to.
|
||||
uint32_t ssrc_feedback[1];
|
||||
} PACKED;
|
||||
|
||||
/////////////////////////////////////////// RTPFB ////////////////////////////////////////////////////
|
||||
|
||||
//RTPFB fmt = 1
|
||||
//https://tools.ietf.org/html/rfc4585#section-6.2.1
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | PID | BLP |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
class FCI_NACK {
|
||||
public:
|
||||
static constexpr size_t kSize = 4;
|
||||
static constexpr size_t kBitSize = 16;
|
||||
|
||||
FCI_NACK(uint16_t pid_h, const vector<bool> &type);
|
||||
|
||||
void check(size_t size);
|
||||
uint16_t getPid() const;
|
||||
uint16_t getBlp() const;
|
||||
//返回丢包列表,总长度17,第一个包必丢
|
||||
vector<bool> getBitArray() const;
|
||||
string dumpString() const;
|
||||
|
||||
private:
|
||||
// The PID field is used to specify a lost packet. The PID field
|
||||
// refers to the RTP sequence number of the lost packet.
|
||||
uint16_t pid;
|
||||
// bitmask of following lost packets (BLP): 16 bits
|
||||
uint16_t blp;
|
||||
} PACKED;
|
||||
|
||||
//RTPFB fmt = 3
|
||||
//https://tools.ietf.org/html/rfc5104#section-4.2.1.1
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | SSRC |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | MxTBR Exp | MxTBR Mantissa |Measured Overhead|
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
class FCI_TMMBR {
|
||||
public:
|
||||
static size_t constexpr kSize = 8;
|
||||
|
||||
void check(size_t size) {
|
||||
CHECK(size == kSize);
|
||||
}
|
||||
|
||||
private:
|
||||
//SSRC (32 bits): The SSRC value of the media sender that is
|
||||
// requested to obey the new maximum bit rate.
|
||||
uint32_t ssrc;
|
||||
|
||||
// MxTBR Exp (6 bits): The exponential scaling of the mantissa for the
|
||||
// maximum total media bit rate value. The value is an
|
||||
// unsigned integer [0..63].
|
||||
// MxTBR Mantissa (17 bits): The mantissa of the maximum total media
|
||||
// bit rate value as an unsigned integer.
|
||||
// Measured Overhead (9 bits): The measured average packet overhead
|
||||
// value in bytes. The measurement SHALL be done according
|
||||
// to the description in section 4.2.1.2. The value is an
|
||||
// unsigned integer [0..511].
|
||||
uint32_t max_tbr;
|
||||
} PACKED;
|
||||
|
||||
//RTPFB fmt = 4
|
||||
// https://tools.ietf.org/html/rfc5104#section-4.2.2.1
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | SSRC |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | MxTBR Exp | MxTBR Mantissa |Measured Overhead|
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
class FCI_TMMBN : public FCI_TMMBR{
|
||||
public:
|
||||
|
||||
} PACKED;
|
||||
|
||||
enum class SymbolStatus : uint8_t{
|
||||
//Packet not received
|
||||
not_received = 0,
|
||||
//Packet received, small delta (所谓small detal是指能用一个字节表示的数值)
|
||||
small_delta = 1,
|
||||
// Packet received, large ornegative delta (large即是能用两个字节表示的数值)
|
||||
large_delta = 2,
|
||||
//Reserved
|
||||
reserved = 3
|
||||
};
|
||||
|
||||
//RTPFB fmt = 15
|
||||
//https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#section-3.1
|
||||
//https://zhuanlan.zhihu.com/p/206656654
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | base sequence number | packet status count |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | reference time | fb pkt. count |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | packet chunk | packet chunk |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// . .
|
||||
// . .
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | packet chunk | recv delta | recv delta |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// . .
|
||||
// . .
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | recv delta | recv delta | zero padding |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
class FCI_TWCC{
|
||||
public:
|
||||
static size_t constexpr kSize = 8;
|
||||
|
||||
void check(size_t size);
|
||||
string dumpString(size_t total_size) const;
|
||||
uint16_t getBaseSeq() const;
|
||||
uint32_t getReferenceTime() const;
|
||||
uint16_t getPacketCount() const;
|
||||
map<uint16_t, std::pair<SymbolStatus, uint32_t/*recv delta 微秒*/> > getPacketChunkList(size_t total_size) const;
|
||||
|
||||
private:
|
||||
//base sequence number,基础序号,本次反馈的第一个包的序号;也就是RTP扩展头的序列号
|
||||
uint16_t base_seq;
|
||||
//packet status count, 包个数,本次反馈包含多少个包的状态;从基础序号开始算
|
||||
uint16_t pkt_status_count;
|
||||
//reference time,基准时间,绝对时间;计算该包中每个媒体包的到达时间都要基于这个基准时间计算
|
||||
uint8_t ref_time[3];
|
||||
//feedback packet count,反馈包号,本包是第几个transport-cc包,每次加1 |
|
||||
uint8_t fb_pkt_count;
|
||||
} PACKED;
|
||||
|
||||
} //namespace mediakit
|
||||
#endif //ZLMEDIAKIT_RTCPFCI_H
|
||||
@@ -463,6 +463,9 @@ void RtmpSession::onRtmpChunk(RtmpPacket::Ptr packet) {
|
||||
std::string type = dec.load<std::string>();
|
||||
if (type == "@setDataFrame") {
|
||||
setMetaData(dec);
|
||||
} else if (type == "onMetaData") {
|
||||
//兼容某些不规范的推流器
|
||||
_publisher_metadata = dec.load<AMFValue>();
|
||||
} else {
|
||||
TraceP(this) << "unknown notify:" << type;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
template<typename T, typename SEQ = uint16_t, size_t kMax = 256, size_t kMin = 10>
|
||||
template<typename T, typename SEQ = uint16_t, size_t kMax = 1024, size_t kMin = 32>
|
||||
class PacketSortor {
|
||||
public:
|
||||
PacketSortor() = default;
|
||||
|
||||
@@ -458,18 +458,27 @@ size_t RtpHeader::getExtSize() const {
|
||||
return 0;
|
||||
}
|
||||
auto ext_ptr = &payload + getCsrcSize();
|
||||
uint16_t reserved = AV_RB16(ext_ptr);
|
||||
//uint16_t reserved = AV_RB16(ext_ptr);
|
||||
//每个ext占用4字节
|
||||
return AV_RB16(ext_ptr + 2) << 2;
|
||||
}
|
||||
|
||||
uint16_t RtpHeader::getExtReserved() const{
|
||||
//rtp有ext
|
||||
if (!ext) {
|
||||
return 0;
|
||||
}
|
||||
auto ext_ptr = &payload + getCsrcSize();
|
||||
return AV_RB16(ext_ptr);
|
||||
}
|
||||
|
||||
uint8_t *RtpHeader::getExtData() {
|
||||
if (!ext) {
|
||||
return nullptr;
|
||||
}
|
||||
auto ext_ptr = &payload + getCsrcSize();
|
||||
//多出的4个字节分别为reserved、ext_len
|
||||
return ext_ptr + 4 + getExtSize();
|
||||
return ext_ptr + 4;
|
||||
}
|
||||
|
||||
size_t RtpHeader::getPayloadOffset() const {
|
||||
@@ -521,6 +530,10 @@ RtpHeader* RtpPacket::getHeader(){
|
||||
return (RtpHeader*)(data() + RtpPacket::kRtpTcpHeaderSize);
|
||||
}
|
||||
|
||||
string RtpPacket::dumpString() const{
|
||||
return ((RtpPacket *) this)->getHeader()->dumpString(size() - RtpPacket::kRtpTcpHeaderSize);
|
||||
}
|
||||
|
||||
uint16_t RtpPacket::getSeq(){
|
||||
return ntohs(getHeader()->seq);
|
||||
}
|
||||
|
||||
@@ -119,6 +119,8 @@ public:
|
||||
|
||||
//返回ext字段字节长度
|
||||
size_t getExtSize() const;
|
||||
//返回ext reserved值
|
||||
uint16_t getExtReserved() const;
|
||||
//返回ext段首地址,不存在时返回nullptr
|
||||
uint8_t *getExtData();
|
||||
|
||||
@@ -150,7 +152,11 @@ public:
|
||||
kRtpTcpHeaderSize = 4
|
||||
};
|
||||
|
||||
//获取rtp头
|
||||
RtpHeader* getHeader();
|
||||
//打印调试信息
|
||||
string dumpString() const;
|
||||
|
||||
//主机字节序的seq
|
||||
uint16_t getSeq();
|
||||
//主机字节序的时间戳,已经转换为毫秒
|
||||
|
||||
@@ -56,7 +56,11 @@ public:
|
||||
//需要解复用rtp
|
||||
key_pos = _demuxer->inputRtp(rtp);
|
||||
}
|
||||
RtspMediaSource::onWrite(std::move(rtp), key_pos);
|
||||
GET_CONFIG(bool, directProxy, Rtsp::kDirectProxy);
|
||||
if (directProxy) {
|
||||
//直接代理模式才直接使用原始rtp
|
||||
RtspMediaSource::onWrite(std::move(rtp), key_pos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,8 +76,10 @@ public:
|
||||
* @param enableMP4 是否mp4录制
|
||||
*/
|
||||
void setProtocolTranslation(bool enableHls,bool enableMP4){
|
||||
//不重复生成rtsp
|
||||
_muxer = std::make_shared<MultiMediaSourceMuxer>(getVhost(), getApp(), getId(), _demuxer->getDuration(), false, true, enableHls, enableMP4);
|
||||
GET_CONFIG(bool, directProxy, Rtsp::kDirectProxy);
|
||||
//开启直接代理模式时,rtsp直接代理,不重复产生;但是有些rtsp推流端,由于sdp中已有sps pps,rtp中就不再包括sps pps,
|
||||
//导致rtc无法播放,所以在rtsp推流rtc播放时,建议关闭直接代理模式
|
||||
_muxer = std::make_shared<MultiMediaSourceMuxer>(getVhost(), getApp(), getId(), _demuxer->getDuration(), !directProxy, true, enableHls, enableMP4);
|
||||
_muxer->setMediaListener(getListener());
|
||||
_muxer->setTrackListener(static_pointer_cast<RtspMediaSourceImp>(shared_from_this()));
|
||||
//让_muxer对象拦截一部分事件(比如说录像相关事件)
|
||||
|
||||
Reference in New Issue
Block a user