11、NALU详解与RTSP分包发送代码分析

1、NAL全称Network Abstract Layer, 即网络抽象层。

在H.264/AVC视频编码标准中,整个系统框架被分为了两个层面:视频编码层面(VCL)和网络抽象层面(NAL)。其中,前者负责有效表示视频数据的内容,而后者则负责格式化数据并提供头信息,以保证数据适合各种信道和存储介质上的传输。因此我们平时的每帧数据就是一个NAL单元(SPS与PPS除外)。在实际的H264数据帧中,往往帧前面带有00 00 00 01 或 00 00 01分隔符,分隔符后面紧跟着的第一个字节就是NAL,对于NAL的解析说明,后面有详细解释,一般来说编码器编出的首帧数据为PPS与SPS,接着为I帧……

如下图为一个h264的文件的二进制代码显示:

SPS为12字节(00 00 00 01不算在有效位内)

SPS(序列参数集):SPS对如标识符、帧数以及参考帧数目、解码图像尺寸和帧场模式等解码参数进行标识记录。
SEI(补充增强信息):这部分参数可作为H264的比特流数据而被传输,每一个SEI信息被封装成一个NAL单元。SEI对于解码器来说可能是有用的,但是对于基本的解码过程来说,并不是必须的。

如下图表示:

pps为5字节(00 00 00 01不算在有效位内)

PPS(图像参数集):PPS对如熵编码类型、有效参考图像的数目和初始化等解码参数进行标志记录。

标注一下:SPS、PPS内容是编码器给。

如下图表示:

注意:H.264编码时,在每个NAL前添加起始码 0x000001,解码器在码流中检测到起始码,当前NAL结束。为了防止NAL内部出现0x000001的数据,h.264又提出'防止竞争 emulation prevention"机制,在编码完一个NAL时,如果检测出有连续两个0x00字节,就在后面插入一个0x03。当解码器在NAL内部检测到0x000003的数据,就把0x03抛弃,恢复原始数据。

接下来就是IU帧,顺序如下图所示:

 有的会有SEI

SEI(补充增强信息):这部分参数可作为H264的比特流数据而被传输,每一个SEI信息被封装成一个NAL单元。SEI对于解码器来说可能是有用的,但是对于基本的解码过程来说,并不是必须的。

2、NALU帧格式详解

帧格式

H264帧由NALU头和NALU主体组成。
NALU头由一个字节组成,它的语法如下:

      +---------------+
      |0|1|2|3|4|5|6|7|
      +-+-+-+-+-+-+-+-+
      |F|NRI|  Type   |
      +---------------+

F: 1个比特.
  forbidden_zero_bit. 在 H.264 规范中规定了这一位必须为 0.

NRI: 2个比特.
  nal_ref_idc. 取00~11,似乎指示这个NALU的重要性,如00的NALU解码器可以丢弃它而不影响图像的回放,0~3,取值越大,表示当前NAL越重要,需要优先受到保护。如果当前NAL是属于参考帧的片,或是序列参数集,或是图像参数集这些重要的单位时,本句法元素必需大于0。

Type: 5个比特.
  nal_unit_type. 这个NALU单元的类型,1~12由H.264使用,24~31由H.264以外的应用使用,简述如下:

  0     没有定义
  1-23  NAL单元  单个 NAL 单元包
  1     不分区,非IDR图像的片
  2     片分区A
  3     片分区B
  4     片分区C
  5     IDR图像中的片
  6     补充增强信息单元(SEI)
  7     SPS
  8     PPS
  9     序列结束
  10    序列结束
  11    码流借宿
  12    填充
  13-23 保留

  24    STAP-A   单一时间的组合包
  25    STAP-B   单一时间的组合包
  26    MTAP16   多个时间的组合包
  27    MTAP24   多个时间的组合包
  28    FU-A     分片的单元
  29    FU-B     分片的单元
  30-31 没有定义

我们还是接着看最上面图的码流对应的数据来层层分析,以00 00 00 01分割之后的下一个字节就是NALU类型,将其转为二进制数据后,解读顺序为从左往右算,如下:
(1)第1位禁止位,值为1表示语法出错
(2)第2~3位为参考级别
(3)第4~8为是nal单元类型

例如上面00000001后有27,28以及25

其中0x27的二进制码为:
0010 0111
4-8为00111,转为十进制7,参考第二幅图:7对应序列参数集SPS

其中0x28的二进制码为:
0010 1000
4-8为01000,转为十进制8,参考第二幅图:8对应图像参数集PPS

其中0x25的二进制码为:
0010 0101
4-8为00101,转为十进制5,参考第二幅图:5对应IDR图像中的片(I帧)

2 STSP分包发送代码分析

因为视频流文件比较大,当在传输过程中出现错误,那么整个视频文件就会坏掉,如果通过分包发送的方法,即使出现错误,也是只影响其中的一包数据,所以传输过程中实现反包发送还是有必要的。在其它的数据传输中实现分包发送也是比较常见的。下面对在rtsp中实现的分包发送进行分析,代码如下:

RTSP_CLIENT g_rtspClients[MAX_RTSP_CLIENT];

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)	//检测客户算的状态是否处于发送状态,如果处于RTSP_SENDING状态则进行分包发送,否则进入下一个循环
		{
		    continue;	//这个命令,当有多个客户端的时候是有用的,但是这里只有一个客户端,并没有显示出他的作用
		}
		int heart = g_rtspClients[is].seqnum % 10000;	//该变量没有用到,这是从live555 移植过来的,至于作用须看live55源码
		
		char* nalu_payload;
		int nAvFrmLen = 0;
		int nIsIFrm = 0;
		int nNaluType = 0;
		char sendbuf[500*1024+32];	//发送缓冲区

	
		nAvFrmLen = buflen;		//音频或者视频的一帧数据的长度

		struct sockaddr_in server;			//server的ip信息填充
		server.sin_family=AF_INET;			
	   	server.sin_port=htons(g_rtspClients[is].rtpport[0]);   //客户端的port
	   	server.sin_addr.s_addr=inet_addr(g_rtspClients[is].IP);
		int	bytes=0;
		unsigned int timestamp_increse=0;	//     timestamp_increse=时钟频率/帧率,后面有详解
		
		timestamp_increse=(unsigned int)(90000.0 / 25);		//90000是在sdp信息中获取的

		rtp_hdr =(RTP_FIXED_HEADER*)&sendbuf[0]; 	//填充rtp 信息的包头   大小为12字节
	
		rtp_hdr->payload     = RTP_H264;   
		rtp_hdr->version     = 2;         //版本号
		rtp_hdr->marker    = 0;           //标记是否是一帧的开头,一帧若被分为多个包,不是最后一包的marker就被标记为0
		rtp_hdr->ssrc      = htonl(10);   //信源标记,标记信息的来源,不能跟别的信源重复

		if(nAvFrmLen<=nalu_sent_len)	//小于一包,一包最多只发1400字节
		{
			rtp_hdr->marker=1;		//因为一帧图像的数据是不会小于1400的,所以这个位置表示的是最后的一包数据,最后一包的数据将marker标记为1
			rtp_hdr->seq_no     = htons(g_rtspClients[is].seqnum++);  //包的序列号,每一包都会加一
			nalu_hdr =(NALU_HEADER*)&sendbuf[12]; 		//不足一包的填充的是nalu_hdr
			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 ;			//发送, 发送的长度  有效长度+13(就是12字节rtp_hdr、1字节nalu_hdr)
			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;		//k个整包
			l=nAvFrmLen%nalu_sent_len;		//整包之后剩余的长度
			int t=0;        

            g_rtspClients[is].tsvid = g_rtspClients[is].tsvid+timestamp_increse;	//每一帧里面的timestamp 的值是一样的
            rtp_hdr->timestamp=htonl(g_rtspClients[is].tsvid);        //转成网络序的    
			while(t<=k)
			{
				rtp_hdr->seq_no = htons(g_rtspClients[is].seqnum++);
			/***由于整包中的第一包、最后一包、以及中间的包的填充信息是不一样的,所以要分为三种方式发送****/
				if(t==0)	//整包里面的第一包,h264包在传输的时候,如果包太大,会被分成多个片。NALU头会被如下的2个自己代替
				{
					rtp_hdr->marker=0;
					fu_ind =(FU_INDICATOR*)&sendbuf[12];	//sendbuf前12字节被rtp_hdr占用了,所以从第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;	//NaluType
	
					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)); //发送, 发送的长度  有效长度+14(就是12字节rtp_hdr、1字节fu_ind、1字节fu_hdr)
					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++;
				}
			/****************************************************************************************/
			}
		}

	}

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

涉及到的结构体

typedef struct
{
	int index;
	int socket;
	int reqchn;
	int seqnum;
	int seqnum2;
	unsigned int tsvid;
	unsigned int tsaud;
	int status;
	int sessionid;
	int rtpport[2];
	int rtcpport;
	char IP[20];
	char urlPre[PARAM_STRING_MAX];
}RTSP_CLIENT;

typedef enum
{
	RTSP_IDLE = 0,
	RTSP_CONNECTED = 1,
	RTSP_SENDING = 2,
}RTSP_STATUS;

typedef struct _RTP_FIXED_HEADER RTP_FIXED_HEADER;
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__;

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

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

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

2.1 timestamp

RTP timestamp是用时钟频率(clock rate)计算而来表示时间的。
RTP timestamp表示每帧的时间,由于一个帧(如I帧)可能被分成多个RTP包,所以多个相同帧的RTP timestamp相等。(可以通过每帧最后一个RTP的marker标志区别帧,但最可靠的方法是查看相同RTP timestamp包为同一帧。)

 两帧之间RTP timestamp的增量 = 时钟频率 / 帧率

其中时钟频率可从SDP中获取,如:

          m=video 2834 RTP/AVP 96
          a=rtpmap:96 H264/90000

其时钟频率为90000(通常视频的时钟频率),若视频帧率为25fps,则相邻帧间RTP timestamp增量值 = 90000/25 = 3600。

另外,通常音频的时钟频率一般为8000

2.2 分包

h264包在传输的时候,如果包太大,会被分成多个片。NALU头会被如下的2个自己代替。

The FU indicator(上面提到的fu_ind) octet has the following format:

      +---------------+
      |0|1|2|3|4|5|6|7|
      +-+-+-+-+-+-+-+-+
      |F|NRI|  Type   |
      +---------------+

   别被名字吓到这个格式就是上面提到的RTP h264负载类型,Type为FU-A

The FU header(上面提到的fu_hdr) has the following format:

      +---------------+
      |0|1|2|3|4|5|6|7|
      +-+-+-+-+-+-+-+-+
      |S|E|R|  Type   |
      +---------------+

   S bit为1表示分片的NAL开始,当它为1时,E不能为1
   E bit为1表示结束,当它为1,S不能为1

   R bit保留位

   Type就是NALU头中的Type,取1-23的那个值

 

 

参考:https://www.cnblogs.com/yjg2014/p/6144977.html

参考:https://blog.csdn.net/jasonhwang/article/details/7316128

参考:https://blog.csdn.net/go_str/article/details/80340564?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值