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

参考:https://blog.csdn.net/bytxl/article/details/50395427

来源:https://blog.csdn.net/David_361/article/details/104982931

posted @ 2021-01-08 19:41  青蛙的博客  阅读(162)  评论(0)    收藏  举报