音视频协议-RTCP协议实现原理
1 前言RTCP作为RTP控制协议,涵盖的内容比较多,用法也比较灵活,对于弱网下音视频质量和会话控制具有重要的作用。RTCP协议格式见:音视频协议-RTCP协议介绍2 RTCP协议定义2.1 RTCP公共头RTCP公共头包括32字节:版本号,填充标志,计数(不同rtcp含义有所差别),协议类型(sr,rr,sdes,bye,app),整个rtcp包的长度。enum PacketType{SR,/*
1 前言
RTCP作为RTP控制协议,涵盖的内容比较多,用法也比较灵活,对于弱网下音视频质量和会话控制具有重要的作用。RTCP协议格式见:音视频协议-RTCP协议介绍
2 RTCP协议定义
2.1 RTCP公共头
RTCP公共头包括32字节:版本号,填充标志,计数(不同rtcp含义有所差别),协议类型(sr,rr,sdes,bye,app),整个rtcp包的长度。
enum PacketType
{
SR, /**< An RTCP sender report. */
RR, /**< An RTCP receiver report. */
SDES, /**< An RTCP source description packet. */
BYE, /**< An RTCP bye packet. */
APP, /**< An RTCP packet containing application specific data. */
Unknown /**< The type of RTCP packet was not recognized. */
};
struct RTCPCommonHeader
{
#ifdef RTP_BIG_ENDIAN
uint8_t version:2; //版本
uint8_t padding:1; //填充
uint8_t count:5; //计数
#else // little endian
uint8_t count:5;
uint8_t padding:1;
uint8_t version:2;
#endif // RTP_BIG_ENDIAN
uint8_t packettype; //协议类型PacketType :200-204
uint16_t length; //rtcp整个包长度(协议头+荷载+填充)
};
2.2 RTCP发送者报告块
发送者报告块是四个字段:ntp时间包括(ntp高位和ntp低位),rtp时间戳,发送包的数据,发送总字节数。
struct RTCPSenderReport
{
uint32_t ntptime_msw; //ntp高位
uint32_t ntptime_lsw; //ntp低位
uint32_t rtptimestamp; //rtp时间戳
uint32_t packetcount; //发送包数量
uint32_t octetcount; //发送总字节数
};
2.3 RTCP接收者报告块
接受者报告块包括:报告者源,丢包率,丢包数,期望接收序列号,抖动,上次发送sr时间,上一次接收sr到发送回复的延迟时间。
struct RTCPReceiverReport
{
uint32_t ssrc; // Identifies about which SSRC's data this report is...
uint8_t fractionlost; //丢包率
uint8_t packetslost[3]; //丢包数
uint32_t exthighseqnr; //期望序列号
uint32_t jitter; //抖动
uint32_t lsr; //上次发送sr时间
uint32_t dlsr; //上次接收sr到发送回复的延迟时间
};
2.4 RTCP资源描述头
资源描述头包括:资源id,资源字符串字节数。
#define RTCP_SDES_ID_CNAME 1
#define RTCP_SDES_ID_NAME 2
#define RTCP_SDES_ID_EMAIL 3
#define RTCP_SDES_ID_PHONE 4
#define RTCP_SDES_ID_LOCATION 5
#define RTCP_SDES_ID_TOOL 6
#define RTCP_SDES_ID_NOTE 7
#define RTCP_SDES_ID_PRIVATE 8
#define RTCP_SDES_NUMITEMS_NONPRIVATE 7
#define RTCP_SDES_MAXITEMLENGTH 255
enum ItemType
{
None, /**< Used when the iteration over the items has finished. */
CNAME, /**< Used for a CNAME (canonical name) item. */
NAME, /**< Used for a NAME item. */
EMAIL, /**< Used for an EMAIL item. */
PHONE, /**< Used for a PHONE item. */
LOC, /**< Used for a LOC (location) item. */
TOOL, /**< Used for a TOOL item. */
NOTE, /**< Used for a NOTE item. */
PRIV, /**< Used for a PRIV item. */
Unknown /**< Used when there is an item present, but the type is not recognized. */
};
struct RTCPSDESHeader
{
uint8_t sdesid; //资源id:ItemType
uint8_t length; //资源描述长度
};
3 RTCP协议解析
jrtplib解析rtcp协议是采用基类继承的方式,每个rtcp协议类型单独的一个类进行对应协议操作处理。
3.1 SR协议解析
SR整体协议解析简单粗暴,就是利用结构体进行强转操作,注意网络字节序和主机字节序的转换:
inline uint32_t RTCPSRPacket::GetSenderSSRC() const
{
if (!knownformat)
return 0;
uint32_t *ssrcptr = (uint32_t *)(data+sizeof(RTCPCommonHeader));
return ntohl(*ssrcptr);
}
inline RTPNTPTime RTCPSRPacket::GetNTPTimestamp() const
{
if (!knownformat)
return RTPNTPTime(0,0);
RTCPSenderReport *sr = (RTCPSenderReport *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t));
return RTPNTPTime(ntohl(sr->ntptime_msw),ntohl(sr->ntptime_lsw));
}
inline uint32_t RTCPSRPacket::GetRTPTimestamp() const
{
if (!knownformat)
return 0;
RTCPSenderReport *sr = (RTCPSenderReport *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t));
return ntohl(sr->rtptimestamp);
}
inline uint32_t RTCPSRPacket::GetSenderPacketCount() const
{
if (!knownformat)
return 0;
RTCPSenderReport *sr = (RTCPSenderReport *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t));
return ntohl(sr->packetcount);
}
inline uint32_t RTCPSRPacket::GetSenderOctetCount() const
{
if (!knownformat)
return 0;
RTCPSenderReport *sr = (RTCPSenderReport *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t));
return ntohl(sr->octetcount);
}
inline int RTCPSRPacket::GetReceptionReportCount() const
{
if (!knownformat)
return 0;
RTCPCommonHeader *hdr = (RTCPCommonHeader *)data;
return ((int)hdr->count);
}
inline RTCPReceiverReport *RTCPSRPacket::GotoReport(int index) const
{
RTCPReceiverReport *r = (RTCPReceiverReport *)(data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)+sizeof(RTCPSenderReport)+index*sizeof(RTCPReceiverReport));
return r;
}
inline uint32_t RTCPSRPacket::GetSSRC(int index) const
{
if (!knownformat)
return 0;
RTCPReceiverReport *r = GotoReport(index);
return ntohl(r->ssrc);
}
inline uint8_t RTCPSRPacket::GetFractionLost(int index) const
{
if (!knownformat)
return 0;
RTCPReceiverReport *r = GotoReport(index);
return r->fractionlost;
}
inline int32_t RTCPSRPacket::GetLostPacketCount(int index) const
{
if (!knownformat)
return 0;
RTCPReceiverReport *r = GotoReport(index);
uint32_t count = ((uint32_t)r->packetslost[2])|(((uint32_t)r->packetslost[1])<<8)|(((uint32_t)r->packetslost[0])<<16);
if ((count&0x00800000) != 0) // test for negative number
count |= 0xFF000000;
int32_t *count2 = (int32_t *)(&count);
return (*count2);
}
inline uint32_t RTCPSRPacket::GetExtendedHighestSequenceNumber(int index) const
{
if (!knownformat)
return 0;
RTCPReceiverReport *r = GotoReport(index);
return ntohl(r->exthighseqnr);
}
inline uint32_t RTCPSRPacket::GetJitter(int index) const
{
if (!knownformat)
return 0;
RTCPReceiverReport *r = GotoReport(index);
return ntohl(r->jitter);
}
inline uint32_t RTCPSRPacket::GetLSR(int index) const
{
if (!knownformat)
return 0;
RTCPReceiverReport *r = GotoReport(index);
return ntohl(r->lsr);
}
inline uint32_t RTCPSRPacket::GetDLSR(int index) const
{
if (!knownformat)
return 0;
RTCPReceiverReport *r = GotoReport(index);
return ntohl(r->dlsr);
}
3.2 RR协议解析
RR报文解析过程和SR基本是重合,只是重写了一遍,具体见SR解析即可。
3.3 SDES协议解析
sdes包解析采用的是单个chuck依次解析过程,核心就是获取第一个chunk函数和获取下一个chunk块函数,就可以遍历整个包所有chunk。所以只能从头开始遍历整个SDES包。
inline bool RTCPSDESPacket::GotoFirstChunk()
{
if (GetChunkCount() == 0)
{
currentchunk = 0;
return false;
}
currentchunk = data+sizeof(RTCPCommonHeader);
curchunknum = 1;
itemoffset = sizeof(uint32_t);
return true;
}
/*
*获取下一个chunk需要注意的一个点是偏移量的计算:主要包括RTCPSDESHeader头部偏移;数据长度的偏移;特别注意就是还需要进行一个
*字节的对齐,这点非常重要否则后面都会错乱。
*/
inline bool RTCPSDESPacket::GotoNextChunk()
{
if (!knownformat)
return false;
if (currentchunk == 0)
return false;
if (curchunknum == GetChunkCount())
return false;
size_t offset = sizeof(uint32_t);
RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(currentchunk+sizeof(uint32_t));
while (sdeshdr->sdesid != 0)
{
offset += sizeof(RTCPSDESHeader);
offset += (size_t)(sdeshdr->length);
sdeshdr = (RTCPSDESHeader *)(currentchunk+offset);
}
offset++; // for the zero byte
//四字节对齐操作
if ((offset&0x03) != 0)
offset += (4-(offset&0x03));
currentchunk += offset;
curchunknum++;
itemoffset = sizeof(uint32_t);
return true;
}
//获取数据就是跳过头即可
inline uint8_t *RTCPSDESPacket::GetItemData()
{
if (!knownformat)
return 0;
if (currentchunk == 0)
return 0;
RTCPSDESHeader *sdeshdr = (RTCPSDESHeader *)(currentchunk+itemoffset);
if (sdeshdr->sdesid == 0)
return 0;
return (currentchunk+itemoffset+sizeof(RTCPSDESHeader));
}
3.4 BYE协议解析
BYE包解析:核心API是初始化计算出数据偏移位置,方便后续取出数据。
RTCPBYEPacket::RTCPBYEPacket(uint8_t *data,size_t datalength)
: RTCPPacket(BYE,data,datalength)
{
knownformat = false;
reasonoffset = 0;
RTCPCommonHeader *hdr;
size_t len = datalength;
hdr = (RTCPCommonHeader *)data;
if (hdr->padding)
{
uint8_t padcount = data[datalength-1];
if ((padcount & 0x03) != 0) // not a multiple of four! (see rfc 3550 p 37)
return;
if (((size_t)padcount) >= len)
return;
len -= (size_t)padcount;
}
size_t ssrclen = ((size_t)(hdr->count))*sizeof(uint32_t) + sizeof(RTCPCommonHeader);
if (ssrclen > len)
return;
if (ssrclen < len) // there's probably a reason for leaving
{
uint8_t *reasonlength = (data+ssrclen);
size_t reaslen = (size_t)(*reasonlength);
if (reaslen > (len-ssrclen-1))
return;
reasonoffset = ssrclen;
}
knownformat = true;
}
3.5 APP协议解析
app协议解析比较简单,就是获取协议类型,名称和数据几个部分的操作。
//获取app协议
inline uint8_t RTCPAPPPacket::GetSubType() const
{
if (!knownformat)
return 0;
RTCPCommonHeader *hdr = (RTCPCommonHeader *)data;
return hdr->count;
//获取app协议名
inline uint8_t *RTCPAPPPacket::GetName()
{
if (!knownformat)
return 0;
return (data+sizeof(RTCPCommonHeader)+sizeof(uint32_t));
}
//获取app数据
inline uint8_t *RTCPAPPPacket::GetAPPData()
{
if (!knownformat)
return 0;
if (appdatalen == 0)
return 0;
return (data+sizeof(RTCPCommonHeader)+sizeof(uint32_t)*2);
}
3.6 RTCP协议解析
RTCP协议解析开始总入口,rtcp协议经过此接口进行协议的分发解析。整体解析过程要点是do while循环对于多个rtcp包进行拆包解析的过程。具体解析过程包括:第一头部强转;版本校验;第一个包必须是sr或者rr报文;最后一个包校验,存在填充的情况下数据长度必须解析一致;分发到对应的协议类型解析;加入rtcp数据包队列中。
int RTCPCompoundPacket::ParseData(uint8_t *data, size_t datalen)
{
bool first;
if (datalen < sizeof(RTCPCommonHeader))
return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET;
first = true;
do
{
RTCPCommonHeader *rtcphdr;
size_t length;
rtcphdr = (RTCPCommonHeader *)data;
if (rtcphdr->version != RTP_VERSION) // check version
{
ClearPacketList();
return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET;
}
if (first)
{
// Check if first packet is SR or RR
first = false;
if ( ! (rtcphdr->packettype == RTP_RTCPTYPE_SR || rtcphdr->packettype == RTP_RTCPTYPE_RR))
{
ClearPacketList();
return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET;
}
}
length = (size_t)ntohs(rtcphdr->length);
length++;
length *= sizeof(uint32_t);
if (length > datalen) // invalid length field
{
ClearPacketList();
return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET;
}
if (rtcphdr->padding)
{
// check if it's the last packet
if (length != datalen)
{
ClearPacketList();
return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET;
}
}
RTCPPacket *p;
switch (rtcphdr->packettype)
{
case RTP_RTCPTYPE_SR:
p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPSRPACKET) RTCPSRPacket(data,length);
break;
case RTP_RTCPTYPE_RR:
p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPRRPACKET) RTCPRRPacket(data,length);
break;
case RTP_RTCPTYPE_SDES:
p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPSDESPACKET) RTCPSDESPacket(data,length);
break;
case RTP_RTCPTYPE_BYE:
p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPBYEPACKET) RTCPBYEPacket(data,length);
break;
case RTP_RTCPTYPE_APP:
p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPAPPPACKET) RTCPAPPPacket(data,length);
break;
default:
p = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPUNKNOWNPACKET) RTCPUnknownPacket(data,length);
}
if (p == 0)
{
ClearPacketList();
return ERR_RTP_OUTOFMEM;
}
rtcppacklist.push_back(p);
datalen -= length;
data += length;
} while (datalen >= (size_t)sizeof(RTCPCommonHeader));
if (datalen != 0) // some remaining bytes
{
ClearPacketList();
return ERR_RTP_RTCPCOMPOUND_INVALIDPACKET;
}
return 0;
}
3.7 RTCP包构建
发送者报告构建,就是数据填充
int RTCPCompoundPacketBuilder::StartSenderReport(uint32_t senderssrc,const RTPNTPTime &ntptimestamp,uint32_t rtptimestamp,
uint32_t packetcount,uint32_t octetcount)
{
if (!arebuilding)
return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING;
if (report.headerlength != 0)
return ERR_RTP_RTCPCOMPPACKBUILDER_ALREADYGOTREPORT;
#ifndef RTP_SUPPORT_RTCPUNKNOWN
size_t totalsize = byesize+appsize+sdes.NeededBytes();
#else
size_t totalsize = byesize+appsize+unknownsize+sdes.NeededBytes();
#endif // RTP_SUPPORT_RTCPUNKNOWN
size_t sizeleft = maximumpacketsize-totalsize;
size_t neededsize = sizeof(RTCPCommonHeader)+sizeof(uint32_t)+sizeof(RTCPSenderReport);
if (neededsize > sizeleft)
return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT;
// fill in some things
report.headerlength = sizeof(uint32_t)+sizeof(RTCPSenderReport);
report.isSR = true;
uint32_t *ssrc = (uint32_t *)report.headerdata;
*ssrc = htonl(senderssrc);
RTCPSenderReport *sr = (RTCPSenderReport *)(report.headerdata + sizeof(uint32_t));
sr->ntptime_msw = htonl(ntptimestamp.GetMSW());
sr->ntptime_lsw = htonl(ntptimestamp.GetLSW());
sr->rtptimestamp = htonl(rtptimestamp);
sr->packetcount = htonl(packetcount);
sr->octetcount = htonl(octetcount);
return 0;
}
3.8 接收块构建
接受块数据填充。
int RTCPCompoundPacketBuilder::AddReportBlock(uint32_t ssrc,uint8_t fractionlost,int32_t packetslost,uint32_t exthighestseq,
uint32_t jitter,uint32_t lsr,uint32_t dlsr)
{
if (!arebuilding)
return ERR_RTP_RTCPCOMPPACKBUILDER_NOTBUILDING;
if (report.headerlength == 0)
return ERR_RTP_RTCPCOMPPACKBUILDER_REPORTNOTSTARTED;
#ifndef RTP_SUPPORT_RTCPUNKNOWN
size_t totalothersize = byesize+appsize+sdes.NeededBytes();
#else
size_t totalothersize = byesize+appsize+unknownsize+sdes.NeededBytes();
#endif // RTP_SUPPORT_RTCPUNKNOWN
size_t reportsizewithextrablock = report.NeededBytesWithExtraReportBlock();
if ((totalothersize+reportsizewithextrablock) > maximumpacketsize)
return ERR_RTP_RTCPCOMPPACKBUILDER_NOTENOUGHBYTESLEFT;
uint8_t *buf = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTCPRECEIVERREPORT) uint8_t[sizeof(RTCPReceiverReport)];
if (buf == 0)
return ERR_RTP_OUTOFMEM;
RTCPReceiverReport *rr = (RTCPReceiverReport *)buf;
uint32_t *packlost = (uint32_t *)&packetslost;
uint32_t packlost2 = (*packlost);
rr->ssrc = htonl(ssrc);
rr->fractionlost = fractionlost;
rr->packetslost[2] = (uint8_t)(packlost2&0xFF);
rr->packetslost[1] = (uint8_t)((packlost2>>8)&0xFF);
rr->packetslost[0] = (uint8_t)((packlost2>>16)&0xFF);
rr->exthighseqnr = htonl(exthighestseq);
rr->jitter = htonl(jitter);
rr->lsr = htonl(lsr);
rr->dlsr = htonl(dlsr);
report.reportblocks.push_back(Buffer(buf,sizeof(RTCPReceiverReport)));
return 0;
}
4 RTCP协议流程
4.1 接收RTCP解析流程
rtcp解析流程,同rtp是根据协议类型,进入rtcp解析流程,进入后首先是利用RTCPCompoundPacket::ParseData进行数据解析分发,对应的rtcp协议具体类进行实际的解析见第三章协议解析部分,协议解析完成,根据不同的协议分发到对应的协议处理流程,协议处理流程并未实现需要用户根据自己的场景进行协议响应。
4.2 发送RTCP解析流程
rtcp发送过程分为两部分一部分是数据包的构建,另一部分是将数据发送到网络。
更多推荐
所有评论(0)