zoukankan      html  css  js  c++  java
  • NALU数据打RTP包流程详解

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

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

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

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

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

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

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

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

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

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


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


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


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


    V版版号(2bit),

    P填充位(1bit),

    X扩展位(1bit),


    CC是CSRC的计数位(4bits);

    M标记位(1bit);

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


    sequence number(2Bytes),RTP包的发送序号;

    timestamp时间戳位(4Bytes);

    SSRC同步标识位(4Bytes);

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

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


    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)前,我们先看看RTP以UDP发送h264数据时的3种打包情况。

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

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

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

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

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


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


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

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

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

    在这种情况下,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拆分为N个RTP包的情况,这时的RTP负载(Payload)部分如下图

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


    我们发现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的分包模式没测试过,应该差不多

  • 相关阅读:
    hdu 1269 迷宫城堡 (并查集)
    hdu 1272 小希的迷宫 (深搜)
    hdu 1026 Ignatius and the Princess I (深搜)
    hdu 1099 Lottery
    hdu 1068 Girls and Boys (二分匹配)
    几个基础数位DP(hdu 2089,hdu 3555,uestc 1307 windy 数)
    hdu 1072 Nightmare (广搜)
    hdu 1398 Square Coins (母函数)
    hdu 1253 胜利大逃亡 (深搜)
    hdu 1115 Lifting the Stone (求重心)
  • 原文地址:https://www.cnblogs.com/ordinary-world/p/10061218.html
Copyright © 2011-2022 走看看