海思3518E开发笔记6.2——RTSP分包H264源码分析

源码

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

	for(is=0;is<MAX_RTSP_CLIENT;is++)
	{
		if(g_rtspClients[is].status!=RTSP_SENDING)
		{
		    continue;
		}
		int heart = g_rtspClients[is].seqnum % 10000;
		
		char* nalu_payload;
		int nAvFrmLen = 0;
		int nIsIFrm = 0;
		int nNaluType = 0;
		char sendbuf[500*1024+32];

	
		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);
		int	bytes=0;
		unsigned int timestamp_increse=0;
		
		timestamp_increse=(unsigned int)(90000.0 / 25);

		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++); 
			nalu_hdr =(NALU_HEADER*)&sendbuf[12]; 
			nalu_hdr->F=0; 
			nalu_hdr->NRI=  nIsIFrm; 
			nalu_hdr->TYPE=  nNaluType;
			nalu_payload=&sendbuf[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)
		{
			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];
					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;					
					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];
					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;		
					sendto(udpfd, sendbuf, bytes, 0, (struct sockaddr *)&server,sizeof(server));
					t++;
				}
				else if(t<k && 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];
					//fu_hdr->E=0;
					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;	
					sendto(udpfd, sendbuf, bytes, 0, (struct sockaddr *)&server,sizeof(server));
					t++;
				}
			}
		}

	}

	//------------------------------------------------------------
}

分析

通过socket接口去发送是比较简单的,但是RTSP发送H264涉及到一个分包的问题,要添加包头

假如包丢了,那只要重新把这个包发一遍就行了;如果不分包,包丢了就要重头开始发

首先对状态进行判断

然后获取heart,用数据结构中的seqnum去对10000取余,超过10000就从头开始计
这个heart类似心跳包

下面定义一些变量。其中nAvFrmLen,这个变量一看名字,有AV,代表audio、video,frame表示帧,len表示长度。意思就是,这个变量代表视频或者音频一帧的长度

然后进行网络编程常规的填充UDP/TCP的选项、IP地址、端口号

一个重要的变量是timestamp_increseRTP中的timestamp)。
每一帧都对应一个RTP的timestamp,帧与帧之间有一个固定间隔,这个间隔就是timestamp_increase。上一帧到下一帧,就是用上一帧的timestamp加上固定的间隔即可。
一般H264的时钟频率是90000,时钟频率除以帧率就是timestamp的固定增量

接着去填充RTP的包头。

  • payload——不用多说
  • version——RTP的版本号,当前版本号是2,未来若有跟新,这里也要相应进行改动
  • marker——当前位置是不是一帧的最后一包,因为有可能一帧被分包发送。对视频是标记一帧的结束,对音频标记一帧的开始
  • ssrc——信源标记

以上内容为发送前的配置
以下为发送
长度塞满一包的情况

else if(nAvFrmLen>nalu_sent_len)
		{
			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];
					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;					
					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];
					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;		
					sendto(udpfd, sendbuf, bytes, 0, (struct sockaddr *)&server,sizeof(server));
					t++;
				}
				else if(t<k && 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];
					//fu_hdr->E=0;
					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;	
					sendto(udpfd, sendbuf, bytes, 0, (struct sockaddr *)&server,sizeof(server));
					t++;
				}
			}
		}

	}

一上来就是整除取余,整除是看一共有几包,取余是看剩余的长度

g_rtspClients[is].tsvid是用来存放timestamp的值,算出来之后转换成网络字节序,放到rtp_hdr

然后进一个while循环,一包一包的去发,把能被一包长度整除的包都发完。

  • t==0——第一包
  • k==t——最后一包
  • t<k && t!=0——中间的包
    分这么细的原因就是,他们包头的填充格式是不一样的

第一包

准备发第一包的时候可以发现,包头装完之后紧接着的不是有效数据,而是fu_ind,再后面才是有效数据。(参考RTP协议详解中分包的内容)
因为如果h264传输的时候,包太大,会被分为多个片。NALU的头会被如下的两个自己代替
在这里插入图片描述
NALU header中的type表示这一个nalu的类型,如果它是一个分片比如type=28(I帧的分片),即FU-A。

在这里插入图片描述
fu_ind从12开始,是应为RTP的报头占11个字节(自己编写的代码中需要注意对齐)。
F固定是0;重要性应该解析SPS提取得出,我看的这份代码里不是的,不规范;TYPE表示这是一个分片
下半部分是fu_header。S为1,E不能为1。一个NALU既是开头又是结尾的情况,就是只有它一包,直接发就行了,不用分片。
type是NALU中的type,需要提取解析

然后第14个字节开始填充有效数据
于是结构图如下

在这里插入图片描述

最后一个整包

在这里插入图片描述
这里rtp头的marker置1,说明这是帧的最后一包
这里的分包和之前一样
nalu被分为fu_ind和fu_header
不同的是fu_header中的End=1

中间的整包

在这里插入图片描述
既不是开头也不是结尾,R和S都是0
其他一样

这个情况是正好被一包长度整除,最后一包也塞满。
配置好之后就发出去

长度不够整包

在这里插入图片描述

nalu_sent_len这个值是根据网卡带宽设置的,使用ifconfig指令查看,MTU字段就是网卡带宽。这里我们开发板的带宽是1500,代码中设置了1400

这个判断条件就是剩余的不足1400字节的内容,即最后一包,长度不足1400字节。
marker为1,也就印证了这是一帧的结束

由于最后一包不用分片,所以他是NALU_header。只有NaluTYpe不一样,其他都和FU_ind一样

只有payload放进去,timestamp加上去。
这里只有13个字节,应为不是两个字节的FU_ind而是一个字节的NALU_header

这些数据配置完再放进去就结束了

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Spark!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值