欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-02-23 21:02:22
...

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的文件的二进制代码显示:

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

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

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

如下图表示:

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

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

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

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

如下图表示:

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

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

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

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

 有的会有SEI

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

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

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

 

 

 

 

相关标签: Hi3518E学习