16.H264的网络传输RTP协议解析

一:RTP协议:

1.RTP协议实际上是由实时传输协议RTP(Realtime Transport Protocol)和
实时传输控制协议RTCP(Realtime Transport Control Protocol)两部分组成的;

2.RTP协议基于多播或单播网络为用户提供连续媒体数据的实时传输服务;
RTCP协议是RTP协议的控制部分,用于实时监控数据传输质量,为系统提供拥塞控制和流控制;

3.RTP数据包:由RTP固定包头(Header)和载荷(Payload)两个部分组成;
其中,包头前12个字节的含义是固定的,而载荷数据可以视音频或视频数据。

二:H264视频传输中的RTP头解析:

/*
	RTP数据头信息:12字节	
*/
typedef struct _RTP_FIXED_HEADER
{
    /**//* byte 0 */
    unsigned char csrc_len:4;        /**//* expect 0 */
    unsigned char extension:1;        /**//* expect 1, see RTP_OP below */
    unsigned char padding:1;        /**//* expect 0 */
    unsigned char version:2;        /**//* expect 2 */
	
    /**//* byte 1 */
    unsigned char payload:7;        /**//* RTP_PAYLOAD_RTSP */
    unsigned char marker:1;        /**//* expect 1 */
	
    /**//* bytes 2, 3 */
    unsigned short seq_no; 
	
    /**//* bytes 4-7 */
    unsigned  long timestamp;  
	
    /**//* bytes 8-11 */
    unsigned long ssrc;            /**//* stream number is used here. */
} __PACKED__ RTP_FIXED_HEADER;

在这里插入图片描述
1.参数说明:
**第0字节
V:
RTP协议的版本号,占2位;
当前协议的版本号为2(根据RFC3984协议,目前使用的RTP版本号应设为0x10);

P:
填充标识,占1位:
如果在该报文的尾部填充一个或多个额外的八位组,且他们不属于有效载荷;

X:
扩展标志,占1位,如果X=1;
则在RTP报头后跟一个扩展报头;

CC:
CSRC计数器,占4位;
指示CSRC标识符的个数;

**第1字节
M:
标记位Marker bit,占1位。
如果当前NALU为一个接入单元最后的那个NALU,那么将M位置1;
或者当前RTP数据包为一个NALU的最后那个分片时,M位置1。
其余情况下M位保持位0.

PT:
载荷类型Payload type,7位;
对于H.264视频格式来说,当前并没有规定一个默认的PT值,因此选用大于95的值即可,此处可设置为0x60(十进制96)。

**第2-3字节
SQ:
序号Sequence number,16位;
序号的起始值为随机值此处设为0,每发送一个RTP数据包,序号值加1,。

**第4-7字节
TS:
时间戳Timestamp,32位,4个字节;
同序号一样,时间戳的起始值也是随机值,此处设为0.
根据RFC3984协议,与时间相应的时钟频率必须为90000HZ。

**第8-11个字节
SSRC:
同步源标识,32位,4个字节;
SSRC应该被随机生成,以使在同一个RTP会话期中没有任何两个同步源具有相同的SSRC识别符。
此处仅有一个同步源,因此将其设为0x12345678;

注:前12个字节是RTP头的基础数据,后续的CRSC特约信源是可有可无的;

CSRC:
特约信源:32位,4个字节;
扩展标志X:决定RTP头是否有CSRC特约信源,值为1则有;
CC计数器 : 决定CSRC特约信源的个数:

三:NALU在网络中的传输方式:

1.对于每一个NALU,根据其包含的数量的不同,其大小也有差异。
在网络中,当要传输的报文大小超过最大传输单元MTU(Maximum Transmission Unit)时就会产生数据包分片的情况。

2.在以太网环境中可传输的最大报文(MTU)的大小为1500字节;

3.如果发送的数据包大于MTU值,数据包就会被拆开来传输,
这样就会产生很多数据包碎片,增加丢包率,降低网络速度。

4.对于视频传输而言,若RTP包大于MTU而由底层协议机制任意拆包,可能会导致接收端播放器的延时播放甚至无法正常播放。
因此对于大于MTU的NALU单元,必须进行拆包处理。

5.RFC3984协议给出了3种不同的RTP打包方案:

a:Single NALU Packet:
在一个RTP包中只封装一个NALU,一般部标协议里小于950字节的均采用此方案;

b:Aggregation Packet:
在一个RTP包中封装多个NALU,对于较小的NALU可以采用这种打包方案,从而提高传输效率;

c:Fragmentation Unit:
一个NALU封装在多个RTP包中,在部标协议中大于950字节的NALU便采用此种方案进行拆包处理。

注:一般只是用a、b方案即可,很少使用b方案;

四:RTP有效载荷(Payload)说明:

typedef struct _FU_INDICATOR
{
    //byte 0
    unsigned char TYPE:5;
	unsigned char NRI:2; 
	unsigned char F:1;    
	
}__PACKED__ FU_INDICATOR; /**//* 1 BYTES */


typedef struct _FU_HEADER
{
   	//byte 0
    unsigned char TYPE:5;
	unsigned char R:1;
	unsigned char E:1;
	unsigned char S:1;    
} __PACKED__ FU_HEADER; /**//* 1 BYTES */

在这里插入图片描述
在这里插入图片描述
1.对于一个RTP只封装在一个NALU里:

	RTP有效载荷:载荷头Indicator + NALU数据;(这里的NALU数据指的是h264格式里的0x000001开头的数据);

完整的RTP数据 = RTP头(12字节) + 载荷头Indicator(1字节) + NALU数据(一帧h.264的数据长度)

2.对于一个NALU封装在多个RTP包里:

	RTP有效载荷:载荷头Indicator + 分片信息fu_header + NALU数据;(这里的NALU数据指的是分片的h264数据);

完整的RTP数据 = RTP头(12字节) + 载荷头Indicator(1字节)  + 分片信息fu_header(1字节) + NALU数据(一帧h.264分片后的数据长度)

五:RTP组包代码:


#define nalu_sent_len        	950
#define RTP_H264             	96
#define RTP_AUDIO            	97
#define timestamp_increse        	(90000 / 25)		//90KHz (1s 25帧) == 3600

int VENC_Sent(char *buffer,int buflen)
{
    int i;
	int is=0;
	int nChanNum=0;
	int ret = 0;

	RTP_FIXED_HEADER *rtp_hdr;
	FU_INDICATOR	 *fu_ind;
	FU_HEADER		 *fu_hdr;

	char *nalu_payload = NULL;
	int nAvFrmLen = 0;
	int nIsIFrm = 0;
	int nNaluType = 0;
	char sendbuf[500*1024+32] = {0};
	int	bytes = 0;
	char fixHeader[4] = {0x0A,0x0A,0x0A,0x0A};		
	static int frame_count = 0;

	for(is = 0; is < MAX_RTSP_CLIENT; is ++)
	{
		if(g_rtspClients[is].status != RTSP_SENDING)
		{
		    continue;
		}
		
		nAvFrmLen = buflen;
		struct sockaddr_in server;
		server.sin_family = AF_INET;
	   	server.sin_port = htons(g_rtspClients[is].rtpport[0]);          
	   	server.sin_addr.s_addr = inet_addr(g_rtspClients[is].IP);

		rtp_hdr = (RTP_FIXED_HEADER*)&sendbuf[0];
		
		rtp_hdr->payload = RTP_H264;
		rtp_hdr->version = 2;
		rtp_hdr->marker  = 0;
		rtp_hdr->ssrc    = htonl(10);
		
		if(nAvFrmLen <= nalu_sent_len)		//单包发送
		{
			rtp_hdr->marker = 1;
			rtp_hdr->seq_no = htons(g_rtspClients[is].seqnum++); 

			fu_ind = (FU_INDICATOR*)&sendbuf[12]; 
			fu_ind->F = 0; 
			fu_ind->NRI = nIsIFrm; 
			fu_ind->TYPE = nNaluType;

			nalu_payload = &sendbuf[13];	//未分片:头信息一共占13字节
			memcpy(nalu_payload, buffer, nAvFrmLen);
            g_rtspClients[is].tsvid = g_rtspClients[is].tsvid + timestamp_increse;            
			rtp_hdr->timestamp = htonl(g_rtspClients[is].tsvid);
			bytes = nAvFrmLen + 13 ;
			
			sendto(udpfd, sendbuf, bytes, 0, (struct sockaddr *)&server,sizeof(server));
		}
		else if(nAvFrmLen > nalu_sent_len)	// 分包发送
		{
			//printf("[%s:%d]:[yang] 222 nAvFrmLen = %d\n",__FUNCTION__,__LINE__,nAvFrmLen);
			int k = 0, l = 0;
			k = nAvFrmLen / nalu_sent_len;	//整包数的个数
			l = nAvFrmLen % nalu_sent_len;	//最后不足一包的数据的字节数
			int t = 0;        

            g_rtspClients[is].tsvid = g_rtspClients[is].tsvid + timestamp_increse;
            rtp_hdr->timestamp = htonl(g_rtspClients[is].tsvid);            

			while(t <= k)
			{
				rtp_hdr->seq_no = htons(g_rtspClients[is].seqnum++);
				
				if(t == 0)		//
				{
					rtp_hdr->marker = 0;
					
					fu_ind =(FU_INDICATOR*)&sendbuf[12];
					fu_ind->F = 0; 
					fu_ind->NRI = nIsIFrm;
					fu_ind->TYPE = 28;					
	
					fu_hdr = (FU_HEADER*)&sendbuf[13];	//13
					fu_hdr->E = 0;
					fu_hdr->R = 0;
					fu_hdr->S = 1;
					fu_hdr->TYPE = nNaluType;

					nalu_payload = &sendbuf[14];
					memcpy(nalu_payload,buffer,nalu_sent_len);
					bytes = nalu_sent_len + 14;	//分片数据:头信息一共占14字节
					sendto(udpfd, sendbuf, bytes, 0, (struct sockaddr *)&server,sizeof(server));
					t++;
	
				}
				else if(k == t)	//发送最后不足一包的数据
				{
					rtp_hdr->marker = 1;
					
					fu_ind =(FU_INDICATOR*)&sendbuf[12]; 
					fu_ind->F= 0 ;
					fu_ind->NRI= nIsIFrm ;
					fu_ind->TYPE=28;

					fu_hdr =(FU_HEADER*)&sendbuf[13];	// 13
					fu_hdr->R = 0;
					fu_hdr->S = 0;
					fu_hdr->TYPE = nNaluType;
					fu_hdr->E = 1;
					nalu_payload = &sendbuf[14];
					memcpy(nalu_payload,buffer + t*nalu_sent_len, l);
					bytes = l + 14;	//分片数据:头信息一共占14字节

					sendto(udpfd, sendbuf, bytes, 0, (struct sockaddr *)&server,sizeof(server));
					t++;
				}
				else if(t < k && t != 0)	//发送 1400大小的数据包
				{
					rtp_hdr->marker=0;
					
					fu_ind = (FU_INDICATOR*)&sendbuf[12]; 
					fu_ind->F = 0; 
					fu_ind->NRI = nIsIFrm;
					fu_ind->TYPE = 28;					
					fu_hdr = (FU_HEADER*)&sendbuf[13];	// 13
					fu_hdr->R = 0;
					fu_hdr->S = 0;
					fu_hdr->E = 0;
					fu_hdr->TYPE = nNaluType;
					
					nalu_payload = &sendbuf[14];	
					memcpy(nalu_payload, buffer + t*nalu_sent_len, nalu_sent_len);
					bytes = nalu_sent_len + 14;	//分片数据:头信息一共占14字节
					sendto(udpfd, sendbuf, bytes, 0, (struct sockaddr *)&server,sizeof(server));
					t++;
				}
			}
		}

	}
}


说明:
1.单包发送或分包发送的最后一包r数据的marker(M)为1,其余为0;

rtp_hdr->marker = 1;

2.时间戳遇上一帧相比固定增加3600:即(90000Hz / 25 == 3600)

g_rtspClients[is].tsvid = g_rtspClients[is].tsvid + timestamp_increse;
rtp_hdr->timestamp = htonl(g_rtspClients[is].tsvid); 

注:时间戳要转为网络字节序htonl(大端模式);

3.分片发送一个NALU时:

fu_hdr->S = 1;	//是否为分片的第一包数据,是则为1				
fu_hdr->R = 0;
fu_hdr->E = 0;	//是否为分片的最后一包数据,是则为1	

注:第一包,则fu_hdr->S必须为1,
最后一包则fu_hdr->E必须为1;
其余情况3位均为0。

4.RTP有效载荷类型:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值