zoukankan      html  css  js  c++  java
  • 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(七)RTP音视频传输解析层之H264传输格式

    一、H264传输封包格式的2个概念

    (1)组包模式(Packetization Modes)

    RFC3984中定义了3种组包模式:单NALU模式(Single Nal Unit Mode)、非交错模式(Non-interleaved Mode)和交错模式(Interleaved Mode)。

    “单NALU模式”:NALU封包在传输过程中必须是整包传输,不可以分包(指应用层的分包,并非指传输层)。而且NALU必须是严格按照解码顺序传输,也就是说,假设1s中连续的24帧分别标记为:frame1,frame2...,frame24,则传输必须严格按frame1,frame2...,frame24这个顺序传输。

    “非交错模式”:NALU必须是严格按照解码顺序传输,也就是说,假设1s中连续的24帧分别标记为:frame1,frame2...,frame24,则传输必须严格按frame1,frame2...,frame24这个顺序传输。该模式可以分包(指应用层的分包,并非指传输层)。

    “交错模式”:NALU可以不按照解码顺序传输,也就是说,假设1s中连续的24帧分别标记为:frame1,frame2...,frame24,则传输顺序可以是frame15,frame7,frame9...。该模式可以分包(指应用层的分包,并非指传输层)。

    (2)封包类型(Packet Type)

    RFC3984中定义了7种封包类型:Nal Unit, STAP-A, STAP-B, MTAP16, MTAP24, FU-A, FU-B。

    这些类型分别对应着不同的传输属性(如支持应用层的“大包分小包”、“小包组大包”)。其中比较常见的是FU-A(Fragmentation Units A)这种类型。

    “组包模式”和“封包类型”一起规定了H264的传输格式,但它们之间也非随意组合的,具体如下图:

    二、SDP中的组包模式

    H264传输的组包模式在SDP中被指定,下图是截取的一段SDP内容

     其中“packetization-mode=1”即规定了H264的组包模式。3种组包模式分别对应编号0,1,2(见RFC3984),1表示“非交错模式”。

    三、本地NALU和传输中NALU

    (1)本地NALU

    现在我们本地有一个NALU,大小为3000字节,如下图。

    其中NALU Header分别由,1bit禁止位,2bit权限位,和5bit类型位。

    其中type的有效值为1-12,分别代表了NALU的不同类型,数值0禁用,13-31保留(type由5bit表示,范围为0-31)。

     (2)传输中的NALU

    假设“组包模式”=“非交错模式”,“封包类型”=“FU-A”,并且将上述所述的包拆分成了3个进行传输,我们来举例说明传输中的NALU和本地的NALU的区别。

     

    以上就是按顺序传输到客户端的3个封包。与本地NALU不同的是,NALU Header的type不再是1-12,而是28(28表示FU-A传输格式,见RFC3984),真正的NALU的type被包含在FU-A Header中。

    FU-A Header的格式如下

    S(Start):起始包指示位,即当传输的是第1个NALU分包时,该位置1。上图中Pack 1该位会被置位;

    E(End):结束宝指示位,即当传输的是最后1个NALU分包时,该位置1。上图中Pack 3该位会被置位;

    R(Reserved):保留位,忽略之。

    Type:NALU类型,即原来在NALU Header中的Type。

    当客户端收到这3个分包时,便可以将其还原成本地NALU的格式了。

    四、源码分析

    在nalu_types_h264.cpp中,首先分析函数:

    size_t FU_A::CopyData(uint8_t * buf, uint8_t * data, size_t size)

    它的作用是将data中的数据复制到buf中,一共复制size个字节,返回实际复制的字节数。其中buf为用户的缓冲区,data为rtp接收的数据。

     1 size_t FU_A::CopyData(uint8_t * buf, uint8_t * data, size_t size) 
     2 {
     3     size_t CopySize = 0;
     4     if(!buf || !data) return 0;
     5 
     6     StartFlag = IsPacketStart(data);
     7     EndFlag = IsPacketEnd(data);
     8 
     9     uint8_t NALUHeader = 0;
    10     NALUHeader = (uint8_t)(  
    11             ParseNALUHeader_F(data)      |   
    12             ParseNALUHeader_NRI(data)    |   
    13             ParseNALUHeader_Type(data)
    14             );
    15 
    16     if(StartFlag) {
    17 
    18         // NALU start code size
    19         buf[0] = 0; buf[1] = 0; buf[2] = 0; buf[3] = 1;
    20         CopySize += 4;  
    21         memcpy(buf + CopySize, &NALUHeader, sizeof(NALUHeader));
    22         CopySize += sizeof(NALUHeader);
    23     }
    24     const int FU_A_HeaderSize = 2;
    25     memcpy(buf + CopySize, data + FU_A_HeaderSize, size - FU_A_HeaderSize);
    26     CopySize += size - FU_A_HeaderSize;
    27 
    28     return CopySize;
    29 }

    仔细看一下源码,我们会发现该函数先解析data的前2个字节(IsPacketStart、IsPacketEnd、ParseNALUHeader_F、ParseNALUHeader_NRI和ParseNALUHeader_Type,源码如下),如果该数据为NALU的第1个RTP分包,则在其最前面添加{0,0,0,1},以标注NALU的开头。

     1 bool FU_A::IsPacketStart(const uint8_t * rtp_payload)
     2 {
     3     if(!IsPacketThisType(rtp_payload)) return false;
     4 
     5     uint8_t PacketS_Mask = 0x80; // binary:1000_0000
     6 
     7     return (rtp_payload[1] & PacketS_Mask);
     8 }
     9 
    10 bool FU_A::IsPacketEnd(const uint8_t * rtp_payload)
    11 {
    12     if(!IsPacketThisType(rtp_payload)) return false;
    13 
    14     uint8_t PacketE_Mask = 0x40; // binary:0100_0000
    15 
    16     return (rtp_payload[1] & PacketE_Mask);
    17 }
    18 
    19 uint16_t FU_A::ParseNALUHeader_F(const uint8_t * rtp_payload)
    20 {
    21     if(!rtp_payload) return FU_A_ERR;
    22     if(FU_A_ID != (rtp_payload[0] & FU_A_ID)) return FU_A_ERR;
    23 
    24     uint16_t NALUHeader_F_Mask = 0x0080; // binary: 1000_0000
    25 
    26     // "F" at the byte of rtp_payload[0]
    27     return (rtp_payload[0] & NALUHeader_F_Mask);
    28 }
    29 
    30 uint16_t FU_A::ParseNALUHeader_NRI(const uint8_t * rtp_payload)
    31 {
    32     if(!rtp_payload) return FU_A_ERR;
    33     if(FU_A_ID != (rtp_payload[0] & FU_A_ID)) return FU_A_ERR;
    34 
    35     uint16_t NALUHeader_NRI_Mask = 0x0060; // binary: 0110_0000
    36 
    37     // "NRI" at the byte of rtp_payload[0]
    38     return (rtp_payload[0] & NALUHeader_NRI_Mask);
    39 
    40 }
    41 
    42 uint16_t FU_A::ParseNALUHeader_Type(const uint8_t * rtp_payload)
    43 {
    44     if(!rtp_payload) return FU_A_ERR;
    45     if(FU_A_ID != (rtp_payload[0] & FU_A_ID)) return FU_A_ERR;
    46 
    47     uint16_t NALUHeader_Type_Mask = 0x001F; // binary: 0001_1111
    48 
    49     // "Type" at the byte of rtp_payload[0]
    50     return (rtp_payload[1] & NALUHeader_Type_Mask);
    51 }

    上一篇                 回目录                 下一篇

  • 相关阅读:
    mysql常用命令(2)
    mysql常用命令(1)
    svn使用方法介绍(1)
    java设计模式
    maven常见错误
    Powershell上线MSF
    Alibaba Nacos 认证绕过
    好视通-视频会议存在弱口令&任意文件下载漏洞
    金山 V8 终端安全系统存在默认口令
    (CVE-2021-3297)Zyxel NBG2105身份验证绕过漏洞
  • 原文地址:https://www.cnblogs.com/ansersion/p/7652050.html
Copyright © 2011-2022 走看看