音视频协议-RTP协议

news/2024/6/17 4:48:24 标签: rtp, 音视频, 网络协议解析

1 协议简介

音视频传输的基石:RTP和RTCP。对于协议的讲解主要是是对于RFC文档的阅读和理解。不同的使用场景用到的字段也有所侧重,RTP和RTCP定义在RFC3550中。其中RTP用于数据流的传输;RTCP用于数据流的控制。可以说rtp/rtcp协议是即时通讯不可或缺的组成。RTCP协议介绍见:音视频协议-RTCP协议介绍

2 协议格式介绍

rtp协议定义在rfc3550第5.1章RTP头定义:
在这里插入图片描述
版本号(2bit):默认为2;
填充标志(1bit):当设置为1时,最后一个字节表示填充字节数包括该字节本身,这些填充不属于荷载,解析时需要被忽略;
扩展标志(1bit):当设置为1时,rtp头后面会接一个扩展头需要解析,需要注意的是length长度是32bit为单位计算的,也就是4字节加1;
在这里插入图片描述
CSRC计数(4bit):CSRC 个数最多就是15个;
标志位M(1bit):视频编码表示一帧的结束标志;
荷载类型(7bit):具体见RFC3551,0-95已经被定义,动态协商采用96-127;

在这里插入图片描述在这里插入图片描述
序列号(16bit):序列号为2字节,只能在0-65535之间不断循环;
时间戳(32bit):初始值为随机值,根据采样步长递增,主要用于音视频同步;
同步源(32bit):随机值,同一个会话源相同;
贡献源(32bit):贡献源主要用于混合器产生数据。

3 协议解析

这里选用目前业界比较认可的JRTPLIB库进行讲解。

3.1 协议头定义

结构体设计需要考虑设备的大小端问题,大端和小的差别主要是单个字节的内部顺序,大端与协议顺序一致,小端则是相反的。定义结构体与协议一致有一个好处就是可以接收到数据后直接进行强转得到对应的rtp字段。

struct RTPHeader
{
#ifdef RTP_BIG_ENDIAN
	uint8_t version:2;	//版本
	uint8_t padding:1;	//填充
	uint8_t extension:1;//扩展
	uint8_t csrccount:4;//csrc count
	
	uint8_t marker:1; 	//标志
	uint8_t payloadtype:7;//荷载类型
#else // little endian
	uint8_t csrccount:4;
	uint8_t extension:1;
	uint8_t padding:1;
	uint8_t version:2;
	
	uint8_t payloadtype:7;
	uint8_t marker:1;
#endif // RTP_BIG_ENDIAN
	
	uint16_t sequencenumber;//序列号
	uint32_t timestamp; 	//时间戳
	uint32_t ssrc;			//同步源
};

扩展头包含两个字段:扩展id和长度

struct RTPExtensionHeader
{
	uint16_t extid;
	uint16_t length;
};

3.2 RTP协议解析

协议解析核心包含几个步骤:

  1. 利用rtp定义的头进行数据的强转,得到rtp头部信息;
  2. 跳过rtp协议头,这里需要注意没有定义cssrc所以需要利用cc计算csrc个数
  3. 填充处理,获取填充字节数
  4. 扩展头处理,这里需要注意的是extlen是32位长度的个数
  5. 计算荷载数据长度
  6. 赋值到RTPPacket中,需要主要网络字节序转换
int RTPPacket::ParseRawPacket(RTPRawPacket &rawpack)
{
	uint8_t *packetbytes;
	size_t packetlen;
	uint8_t payloadtype;
	RTPHeader *rtpheader;
	bool marker;
	int csrccount;
	bool hasextension;
	int payloadoffset,payloadlength;
	int numpadbytes;
	RTPExtensionHeader *rtpextheader;
	
	if (!rawpack.IsRTP()) // If we didn't receive it on the RTP port, we'll ignore it
		return ERR_RTP_PACKET_INVALIDPACKET;
	
	// The length should be at least the size of the RTP header
	packetlen = rawpack.GetDataLength();
	if (packetlen < sizeof(RTPHeader))
		return ERR_RTP_PACKET_INVALIDPACKET;
	
	packetbytes = (uint8_t *)rawpack.GetData();
	//1 利用rtp定义的头进行数据的强转,得到rtp头部信息
	rtpheader = (RTPHeader *)packetbytes;
	
	// The version number should be correct
	if (rtpheader->version != RTP_VERSION)
		return ERR_RTP_PACKET_INVALIDPACKET;
	
	// We'll check if this is possibly a RTCP packet. For this to be possible
	// the marker bit and payload type combined should be either an SR or RR
	// identifier
	marker = (rtpheader->marker == 0)?false:true;
	payloadtype = rtpheader->payloadtype;
	if (marker)
	{
		if (payloadtype == (RTP_RTCPTYPE_SR & 127)) // don't check high bit (this was the marker!!)
			return ERR_RTP_PACKET_INVALIDPACKET;
		if (payloadtype == (RTP_RTCPTYPE_RR & 127))
			return ERR_RTP_PACKET_INVALIDPACKET;
	}

	csrccount = rtpheader->csrccount;
	//2 跳过rtp协议头,这里需要注意没有定义cssrc所以需要利用cc计算csrc个数
	payloadoffset = sizeof(RTPHeader)+(int)(csrccount*sizeof(uint32_t));
	//3 填充处理,获取填充字节数
	if (rtpheader->padding) // adjust payload length to take padding into account
	{
		numpadbytes = (int)packetbytes[packetlen-1]; // last byte contains number of padding bytes
		if (numpadbytes <= 0)
			return ERR_RTP_PACKET_INVALIDPACKET;
	}
	else
		numpadbytes = 0;
	//4 扩展头处理,这里需要注意的是extlen是32位长度的个数
	hasextension = (rtpheader->extension == 0)?false:true;
	if (hasextension) // got header extension
	{
		rtpextheader = (RTPExtensionHeader *)(packetbytes+payloadoffset);
		payloadoffset += sizeof(RTPExtensionHeader);
		
		uint16_t exthdrlen = ntohs(rtpextheader->length);
		payloadoffset += ((int)exthdrlen)*sizeof(uint32_t);
	}
	else
	{
		rtpextheader = 0;
	}	
	//5 计算荷载数据长度
	payloadlength = packetlen-numpadbytes-payloadoffset;
	if (payloadlength < 0)
		return ERR_RTP_PACKET_INVALIDPACKET;

	// Now, we've got a valid packet, so we can create a new instance of RTPPacket
	// and fill in the members
	//6 赋值到RTPPacket中
	RTPPacket::hasextension = hasextension;
	if (hasextension)
	{
		RTPPacket::extid = ntohs(rtpextheader->extid);
		RTPPacket::extensionlength = ((int)ntohs(rtpextheader->length))*sizeof(uint32_t);
		RTPPacket::extension = ((uint8_t *)rtpextheader)+sizeof(RTPExtensionHeader);
	}

	RTPPacket::hasmarker = marker;
	RTPPacket::numcsrcs = csrccount;
	RTPPacket::payloadtype = payloadtype;
	
	// Note: we don't fill in the EXTENDED sequence number here, since we
	// don't have information about the source here. We just fill in the low
	// 16 bits
	RTPPacket::extseqnr = (uint32_t)ntohs(rtpheader->sequencenumber);

	RTPPacket::timestamp = ntohl(rtpheader->timestamp);
	RTPPacket::ssrc = ntohl(rtpheader->ssrc);
	RTPPacket::packet = packetbytes;
	RTPPacket::payload = packetbytes+payloadoffset;
	RTPPacket::packetlength = packetlen;
	RTPPacket::payloadlength = payloadlength;

	// We'll zero the data of the raw packet, since we're using it here now!
	rawpack.ZeroData();

	return 0;
}

3.3 RTP包构建

RTP包构建比较简单,就是一个简单的填空题,先计算出包的总长度,然后分配好包的大小,最后将内存强转成rtp头,然后填空即可。需要注意主机字节序转网络字节序问题。

int RTPPacket::BuildPacket(uint8_t payloadtype,const void *payloaddata,size_t payloadlen,uint16_t seqnr,
		  uint32_t timestamp,uint32_t ssrc,bool gotmarker,uint8_t numcsrcs,const uint32_t *csrcs,
		  bool gotextension,uint16_t extensionid,uint16_t extensionlen_numwords,const void *extensiondata,
		  void *buffer,size_t maxsize)
{
	//cc大小校验
	if (numcsrcs > RTP_MAXCSRCS)
		return ERR_RTP_PACKET_TOOMANYCSRCS;
	//payloadtype校验
	if (payloadtype > 127) // high bit should not be used
		return ERR_RTP_PACKET_BADPAYLOADTYPE;
	if (payloadtype == 72 || payloadtype == 73) // could cause confusion with rtcp types
		return ERR_RTP_PACKET_BADPAYLOADTYPE;
	//rtp包长度计算
	packetlength = sizeof(RTPHeader);
	packetlength += sizeof(uint32_t)*((size_t)numcsrcs);
	if (gotextension)
	{
		packetlength += sizeof(RTPExtensionHeader);
		packetlength += sizeof(uint32_t)*((size_t)extensionlen_numwords);
	}
	packetlength += payloadlen;

	if (maxsize > 0 && packetlength > maxsize)
	{
		packetlength = 0;
		return ERR_RTP_PACKET_DATAEXCEEDSMAXSIZE;
	}

	// Ok, now we'll just fill in...
	//RTP包内存分配
	RTPHeader *rtphdr;
	
	if (buffer == 0)
	{
		packet = RTPNew(GetMemoryManager(),RTPMEM_TYPE_BUFFER_RTPPACKET) uint8_t [packetlength];
		if (packet == 0)
		{
			packetlength = 0;
			return ERR_RTP_OUTOFMEM;
		}
		externalbuffer = false;
	}
	else
	{
		packet = (uint8_t *)buffer;
		externalbuffer = true;
	}
	//rtp包赋值
	RTPPacket::hasmarker = gotmarker;
	RTPPacket::hasextension = gotextension;
	RTPPacket::numcsrcs = numcsrcs;
	RTPPacket::payloadtype = payloadtype;
	RTPPacket::extseqnr = (uint32_t)seqnr;
	RTPPacket::timestamp = timestamp;
	RTPPacket::ssrc = ssrc;
	RTPPacket::payloadlength = payloadlen;
	RTPPacket::extid = extensionid;
	RTPPacket::extensionlength = ((size_t)extensionlen_numwords)*sizeof(uint32_t);
	
	rtphdr = (RTPHeader *)packet;
	rtphdr->version = RTP_VERSION;
	rtphdr->padding = 0;
	if (gotmarker)
		rtphdr->marker = 1;
	else
		rtphdr->marker = 0;
	if (gotextension)
		rtphdr->extension = 1;
	else
		rtphdr->extension = 0;
	rtphdr->csrccount = numcsrcs;
	rtphdr->payloadtype = payloadtype&127; // make sure high bit isn't set
	rtphdr->sequencenumber = htons(seqnr);
	rtphdr->timestamp = htonl(timestamp);
	rtphdr->ssrc = htonl(ssrc);
	
	uint32_t *curcsrc;
	int i;

	curcsrc = (uint32_t *)(packet+sizeof(RTPHeader));
	for (i = 0 ; i < numcsrcs ; i++,curcsrc++)
		*curcsrc = htonl(csrcs[i]);

	payload = packet+sizeof(RTPHeader)+((size_t)numcsrcs)*sizeof(uint32_t); 
	if (gotextension)
	{
		RTPExtensionHeader *rtpexthdr = (RTPExtensionHeader *)payload;

		rtpexthdr->extid = htons(extensionid);
		rtpexthdr->length = htons((uint16_t)extensionlen_numwords);
		
		payload += sizeof(RTPExtensionHeader);
		memcpy(payload,extensiondata,RTPPacket::extensionlength);
		
		payload += RTPPacket::extensionlength;
	}
	memcpy(payload,payloaddata,payloadlen);
	return 0;
}

3.4 RTP发送流程

下面是整个发送过程的调用栈,整体比较简单,就是将需要发送的数据,加入到rtp构建器中构建一个rtp包,然后调用网络管理器发送数据,具体调用时序图图如下:
在这里插入图片描述

3.5 RTP接收流程

rtp接收流程在rtppoll线程内完成,主要是接收rtp包加入到rtppcket类利用rtp解析函数进行数据解析得到rtp数据,然后为应用层所用。
在这里插入图片描述


http://www.niftyadmin.cn/n/927462.html

相关文章

音视频协议-RTCP协议介绍

音视频协议-RTCP协议介绍1 协议简介2 协议格式介绍2.1 RTCP公共头2.2 SR协议2.3 RR协议2.4 SDES协议2.5 BYE协议2.6 APP协议3 RTCP协议限制1 协议简介 RTCP和RTP协议是配合使用的音视频协议-RTP协议&#xff0c;为RTP提供信道外的传输控制&#xff0c;RTCP不参与数据传输&…

音视频协议-RTCP协议实现原理

1 前言 RTCP作为RTP控制协议&#xff0c;涵盖的内容比较多&#xff0c;用法也比较灵活&#xff0c;对于弱网下音视频质量和会话控制具有重要的作用。RTCP协议格式见&#xff1a;音视频协议-RTCP协议介绍 2 RTCP协议定义 2.1 RTCP公共头 RTCP公共头包括32字节&#xff1a;版…

事件驱动组件libevent实战

1 环境搭建 Libevent API 提供了一种机制&#xff0c;可以在文件描述符上发生特定事件或达到超时后执行回调函数。此外&#xff0c;Libevent 还支持由于信号或定期超时而引起的回调。libevent旨在替换事件驱动网络服务器中的事件循环。应用程序只需要调用 event_dispatch()&am…

读写缓冲区实现

读写缓冲区实现1 读写缓冲区概述1.1 环形缓冲区1.2 evbuffer设计2 evbuffer实现2.1 evbuffer设计原理2.2 核心API分析2.2.1 初始化evbuffer2.2.2 释放evbuffer2.2.3 添加数据2.2.4 获取evbuffer数据1 读写缓冲区概述 用户空间缓冲区存在的意义&#xff1a; &#xff08;1&…

mysql基础及操作

1 mysq环境搭建 1.1 Ubuntu16.04安装mysql apt-get install mysql-server #注意中途弹框输入&#xff1a;mysql的root密码 #安装完成后注释只允许本地连接 vi /etc/mysql/mysql.conf.d/mysqld.cnf #bind-address 127.0.0.11.2 问题&#xff1a;navicat连接数据库报…

Alexa交叉编译(avs-device-sdk)

Alexa交叉编译&#xff08;avs-device-sdk&#xff09;1 前言2 环境准备2.1 工程目录构建2.2 下载三方库文件2.3 下载源码3 编译依赖库3.1 编译portaudio3.2编译nghttp23.3 编译curl3.4 编译gstreamer3.4.1 编译zlib3.4.2 编译libffi3.4.3 编译glib3.4.4 编译gstreamer3.4.5 编…

音视频协议-SRTP

1 SRTP环境搭建 1.1 SRTP简介 STRP它是 思科的开源安全 RTP 库。RTP 是实时传输协议&#xff0c;一种用于传输实时数据&#xff08;如电话、音频和视频&#xff09;的 IETF 标准&#xff0c;由RFC 3550定义。SRTP用于为 RTP 数据提供机密性以及对 RTP 标头和有效负载进行身份…

音视频开发-视频基础

1 视频产生 视频本质就是一组连续播放的图像&#xff0c;所以研究视频离不开对于图像的研究。而图像又是由众多的色彩构成&#xff0c;这里就会涉及到图像的最小单元一个像素点的构成&#xff0c;像素点一般是由RGB三种颜色组成。 1.1 RGB图像 RGBA8888&#xff1a;一般一个…