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发送过程分为两部分一部分是数据包的构建,另一部分是将数据发送到网络。
在这里插入图片描述

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐