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

NALU数据打RTP包流程详解

程序员文章站 2022-03-22 21:25:47
...

 最近在看RTP发送H264数据的文章,感觉很乱,没有比较清晰易懂的教程,自己整理了一下各种资料,备忘!


  --------Part A  ----

   先说说H264数据,H264在网络传输的是NALU(NAL单元),NALU的结构是:NAL头+RBSP,实际传输中的数据流如图所示:

  NALU数据打RTP包流程详解NALU数据打RTP包流程详解

NALU头用来标识后面的RBSP是什么类型的数据,他是否会被其他帧参考以及网络传输是否有错误。

NALU头结构为1个字节,既 forbidden_bit(1bit) + nal_reference_bit(2bit) + nal_unit_type(5bit),如下

NALU数据打RTP包流程详解NALU数据打RTP包流程详解

1.forbidden_bit: 禁止位,初始为0,当网络发现NAL单元有比特错误时可设置为1,以便接收方纠错或丢掉该单元。

2.nal_reference_bit:nal重要性指示,标志该NAL单元的重要性,值越大,越重要,解码器在解码处理不过来的时候,可以丢掉重要性为0的NALU

3.nal_unit_type:NALU类型取值如下表所示:

NALU数据打RTP包流程详解

NALU数据打RTP包流程详解


 H264的NALU头里面.nal_unit_type的有效值只会是1~23,在打RTP包时,在某些情况下我们会填24-31,后面我们会解释,如下:

NALU数据打RTP包流程详解NALU数据打RTP包流程详解

       图3(打RTP包时会用到的扩展类型)


对H264数据打RTP包而言,我们了解这些基础知识就ok了,深入了解H264,请查阅其它资料.



   --------Part B  ----

发送RTP数据报时,需要设置头部(Header)和负载(Payload)两部分,也就是数据头+数据这样的形式。先来看下Header

NALU数据打RTP包流程详解NALU数据打RTP包流程详解

V版版号(2bit),

P填充位(1bit),

X扩展位(1bit),

CCCSRC的计数位(4bits);

M标记位(1bit;

PT有效载荷的类型(7bits,比如h264视频对应的值就是PT= 96

sequence number2Bytes,RTP包的发送序号;

timestamp时间戳位(4Bytes);

SSRC同步标识位(4Bytes);

CSRC不是RTP必须的(4Bytes)。


 这样的话,用一个结构体来存储RTPHeader数据,如下,

typedef struct 
{
    /**//* 1byte (0) */
    unsigned char csrc_len:4;        /* expect 0, csrc计数器,没啥用*/
    unsigned char extension:1;       /* expect 1, see RTP_OP below ,扩展位,不用关心*/
    unsigned char padding:1;         /*expect 0 , 填充位,不用关心*/
    unsigned char version:2;        /* expect 2,版本号,固定为2 */
    /**//* 1byte (1) */
    unsigned char payload:7;        /* RTP_PAYLOAD_RTSP , 负载类型*/
    unsigned char marker:1;        /* expect 1,是否是尾包,后面解释 */
    /**//* 2bytes (2, 3) */
    unsigned short seq_no;         /* RTP包序号,比如100,101,102,类推 */  
    /**//* 4bytes (4-7) */
    unsigned long timestamp;      /* 时间戳位 */     
    /**//* 4bytes (8-11) */
    unsigned long ssrc;            /* stream number is used here. 在本RTP会话中全局唯一就行*/
    // CSRC(4Bytes)不是RTP必须的,因此不定义它
} RTP_FIXED_HEADER;

在讲负载(Payload)前,我们先看看RTPUDP发送h264数据时的3种打包情况。

由于UDP数据报长度超过1500字节时(俗称MTU),会自动拆分发送,增大了丢包概率,那么去除UDP数据报头以及RTPHeader部分,一般设置Payload部分最大长度为1400字节即可,那么对H264NALU单元打RTP就意味着3种情况.


第一:RTP包里只包含一个NALU,(它的数据小于1400字节)

第二:RTP包里只包含N个NALU,(NNALU的数据累加小于1400字节)

第三:NALU数据大于1400字节, (比如5400字节,5400/1400>3.8,要拆分分4RTP)


但是我们处理H264数据时,一般是对NALU逐一进行处理的,因此我们只考虑第一和第三种情况。

我们来看第一种情况,RTP包里只包含一个NALU的情况,这时的RTP负载(Payload)部分如下图

NALU数据打RTP包流程详解NALU数据打RTP包流程详解

 从内存分布上可以理解为 RTP PlayLoad = [PlayLoad_head] + [RBSP]

我们知道一个 NALU = [NAL_head] + [RBSP]

我们还发现,PlayLoad_head 和 NAL_head 都是一个字节,结构相同,如下

NALU数据打RTP包流程详解NALU数据打RTP包流程详解

在这种情况下,PlayLoad_head 和 NAL_head的结构和值都是一样的,因此

RTP PlayLoad = NALU= [NAL_head] + [RBSP]


假如一个H264的NALU是这样的:[00 00 00 01 67 42 A0 1E 23 56 0E 2F ... ]

 这是一个序列参数集NAL单元。[00 00 00 01]是四个字节的开始码,67是NALU头,42开始的数据是NALU内容(RBSP),封装成RTP包将如下:  [RTP HEADER] + [67 42 A0 1E 23 56 0E 2F ...]


发送单一NALU单元的伪代码如下:

{
     RTP_FIXED_HEADER        *rtp_hdr;  
     rtp_hdr =(RTP_FIXED_HEADER*)&sendBuf[0];  
     rtp_hdr->payload     = H264;   //也就是96
     rtp_hdr->version     = 2;  
     rtp_hdr->marker      = 0;  
     rtp_hdr->ssrc        = htonl(10);   //全局唯一就行
  
   if(nalu_t->len<=MAX_RTP_PKT_LENGTH)
    {  
    rtp_hdr->marker = 1;  //一个Nalu单元只用了一个RTP包,当然也算尾包
    rtp_hdr->seq_no     = htons(seq_num ++);  //序号+1
  
    nalu_payload    = &sendBuf[12];   //RTP头就12字节
    // nalu_t->buf第一个字节就是NALU_head
    memcpy(nalu_payload, nalu_t->buf, nalu_t->len); 
  
    ts_current=ts_current+timestamp_increse;  //时间戳
    rtp_hdr->timestamp   = htonl(ts_current);  
    bytes = nalu_t->len + 12 ;  
  	
    ::send(socketFd, sendBuf, bytes, 0);  
}


我们来看第三种情况,一个NALU拆分为NRTP包的情况,这时的RTP负载(Payload)部分如下图

NALU数据打RTP包流程详解

这种模式叫分片封包模式,其中 FU IndicatorFU header都是1个字节,结构如下:

NALU数据打RTP包流程详解NALU数据打RTP包流程详解


我们发现FU Indicator的结构和NALU头结构是一样的,不过值不完全一样

U Indicator->F    = NALU头结构-> F
FU Indicator->NRI  = NALU头结构->NRI
FU Indicator->Type  = 28 (也就是扩展类型,FU-A)
另外

FU header->Type = NALU头结构->Tpye
FU header->S = 开始位,设置成1,指示分片NAL单元的开始,也就是开始包
FU header->D = 结束位,设置成1,指示分片NAL单元的结束,也就是尾包
FU header->R = 保留位必须设置为0


分包模式下的RTP包如下:

[RTP HEADER] + [FU Indicator] + [FU header] + [RBSP] 

分包模式的伪代码如下:


     RTP_FIXED_HEADER        *rtp_hdr;  
     rtp_hdr =(RTP_FIXED_HEADER*)&sendBuf[0];  
     rtp_hdr->payload     = H264;  //
     rtp_hdr->version     = 2;  
     rtp_hdr->marker      = 0;  
     rtp_hdr->ssrc        = htonl(10);  


    int k=0,l=0;  
    k = nalu_t->len/MAX_RTP_PKT_LENGTH;   // 比如k为7,表示有7个完整RTP包+1个尾包
    l = nalu_t->len%MAX_RTP_PKT_LENGTH;   // 比如l为126,表示尾包的RBSP为126个字节
    int t=0;  
    ts_current = ts_current+timestamp_increse;   //时间戳
    rtp_hdr->timestamp = htonl(ts_current);  

    while(t<=k)
    {  
        rtp_hdr->seq_no = htons(seq_num ++);   //***累加
        if(!t) //第一包
        {  
            rtp_hdr->marker=0;   // 不是尾包
            fu_ind =(FU_INDICATOR*)&sendBuf[12];  
            fu_ind->F=nalu_t->forbidden_bit;  
            fu_ind->NRI=nalu_t->nal_reference_idc>>5;  
            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=nalu_t->nal_unit_type;  
  
            nalu_payload=&sendBuf[14];  
            memcpy(nalu_payload,nalu_t->buf+1,MAX_RTP_PKT_LENGTH);  
  
            bytes=MAX_RTP_PKT_LENGTH+14;  
            ::send( socketFd, sendBuf, bytes, 0 );  
            t++;  
        }
        else if(k==t)  // 尾包的情况
        {  
            rtp_hdr->marker=1;  // 尾包
            fu_ind =(FU_INDICATOR*)&sendBuf[12];  
            fu_ind->F=nalu_t->forbidden_bit;  
            fu_ind->NRI=nalu_t->nal_reference_idc>>5;  
            fu_ind->TYPE=28;  
  
            fu_hdr =(FU_HEADER*)&sendBuf[13];  
            fu_hdr->R=0;  
            fu_hdr->S=0;  
            fu_hdr->TYPE=nalu_t->nal_unit_type;  
            fu_hdr->E=1;  
  
            nalu_payload=&sendBuf[14];  
            memcpy(nalu_payload,nalu_t->buf+t*MAX_RTP_PKT_LENGTH+1,l-1);  
            bytes=l-1+14;  
            ::send( socketFd, sendBuf, bytes, 0 );  
            t++;  
        }
         else if(t<k&&0!=t) // 注意,头包,中间包,尾包,fu_hdr->R,fu_hdr->S,fu_hdr->E 的值不一样
         {  
            rtp_hdr->marker=0;  
            fu_ind =(FU_INDICATOR*)&sendBuf[12];  
            fu_ind->F=nalu_t->forbidden_bit;  
            fu_ind->NRI=nalu_t->nal_reference_idc>>5;  
            fu_ind->TYPE=28;  
  
            fu_hdr =(FU_HEADER*)&sendBuf[13];  
            fu_hdr->R=0; 
            fu_hdr->S=0;  
            fu_hdr->E=0;  
            fu_hdr->TYPE=nalu_t->nal_unit_type;  
  
            nalu_payload=&sendBuf[14];  
            memcpy(nalu_payload,nalu_t->buf+t*MAX_RTP_PKT_LENGTH+1,MAX_RTP_PKT_LENGTH);  
            bytes=MAX_RTP_PKT_LENGTH+14;  
            ::send( socketFd, sendBuf, bytes, 0 );  
            t++;  
        } 
   }


这里只列出了FU-A(Type = 28)的分包模式,FU-B的分包模式没测试过,应该差不多

测试源码:http://download.csdn.net/detail/heker2010/9901919



参考文章:

通过RTP协议传输H264视频

http://blog.csdn.net/westlor/article/details/50538058

《H264--2--语法及结构》

http://blog.csdn.net/yangzhongxuan/article/details/8003494














相关标签: h264 rtp