【rtp over tcp/rtsp解析】&&【H264格式与RTP包解析】&&【以H264的打包方式---分片打包(type = 28 FU-A方式为例)】

原文地址:https://www.cnblogs.com/iFrank/p/15434438.html

、rtp over tcp的RTP/RTCP包格式的前四个字节说明

  RTP/RTCP Socket和RTSP Socket共享TCP Socket,所以必须要有一个标识来区别三个数据。

  RTP和RTCP数据会以 "$"符号 + 一个字节的通道编号 + 2个字节的数据长度,共四个字节的前缀开始,RTSP数据没有四个字节的前缀;RTP和RTCP数据的区别在于第二个字节的通道编号

  所以第一个字节’$'用于与RTSP区分,第二个字节用于区分RTP和RTCP(RTP和RTCP的channel是在RTSP的SETUP过程中,客户端发送给服务端的------一般情况下 RTP通道编号是偶数,RTCP通道编号是奇数)。RTP/RTCP的前缀四个字节如下所示:(在rtp over tcp发送协议下)

字节

描述

第一个字节

‘$’,标识符,用于与RTSP区分

第二个字节

channel,用于区分RTP和RTCP

第三和第四个字节

RTP包的大小

2、rtp over tcp的包格式

  根据前面的说明,现在RTP的打包方式要在之前的每个RTP包前面加上四个字节,如下所示:

​编辑

3、H.264格式

参考:

I帧、P帧、B帧、GOP、IDR 和PTS, DTS之间的关系 - 夜行过客 - 博客园 (cnblogs.com)

  注:可通过Elecard StreamEye Tools工具【例如 Elecard Stream Analyzer、Elecard StreamEye等】来分析H264码流

  H264协议里定义了三种帧,完整编码的帧叫I帧,参考之前的I帧生成的只包含差异部分编码的帧叫P帧,还有一种参考前后的帧编码的帧叫B帧。
  H264采用的核心算法是帧内压缩和帧间压缩,帧内压缩是生成I帧的算法,帧间压缩是生成B帧和P帧的算法。

  序列的说明
  在H264中图像以序列为单位进行组织,一个序列是一段图像编码后的数据流,以I帧开始,到下一个I帧结束。
  一个序列的第一个图像叫做 IDR 图像(立即刷新图像),IDR 图像都是 I 帧图像。H.264 引入 IDR 图像是为了解码的重同步,当解码器解码到 IDR 图像时,立即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。这样,如果前一个序列出现重大错误,在这里可以获得重新同步的机会。IDR图像之后的图像永远不会使用IDR之前的图像的数据来解码。
  一个序列就是一段内容差异不太大的图像编码后生成的一串数据流。当运动变化比较少时,一个序列可以很长,因为运动变化少就代表图像画面的内容变动很小,所以就可以编一个I帧,然后一直P帧、B帧了。当运动变化多时,可能一个序列就比较短了,比如就包含一个I帧和3、4个P帧。

  在视频编码序列中,GOP即Group of picture(图像组),指两个I帧之间的距离,Reference(参考周期)指两个P帧之间的距离。两个I帧之间形成一组图片,就是GOP(Group Of Picture):

​编辑​编辑

  GOP说白了就是两个I帧之间的间隔:比较说GOP为120,如果是720p60的话,那就是2s一次I帧,GOP一般设置为编码器每秒输出的帧数,即每秒帧率,一般为25或30,当然也可设置为其他值。
  在一个GOP中,P、B帧是由I帧预测得到的,当I帧的图像质量比较差时,会影响到一个GOP中后续P、B帧的图像质量,直到下一个GOP 开始才有可能得以恢复,所以GOP值也不宜设置过大。
  由于P、B帧的复杂度大于I帧,所以过多的P、B帧会影响编码效率,使编码效率降低。另外,过长的GOP还会影响Seek操作的响应速度,由于P、B帧是由前面的I或P帧预测得到的,所以Seek操作需要直接定位,解码某一个P或B帧时,需要先解码得到本GOP内的I帧及之前的N个预测帧才可以,GOP值越长,需要解码的预测帧就越多,seek响应的时间也越长。

  在H.264/AVC视频编码标准中,整个系统框架被分为了两个层面:视频编码层面(VCL)和网络抽象层面(NAL)。其中,前者负责有效表示视频数据的内容,而后者则负责格式化数据并提供头信息,以保证数据适合各种信道和存储介质上的传输。因此我们平时的每帧数据就是一个NAL单元(SPS与PPS除外)。在实际的H264数据帧中,往往帧前面带有00 00 00 01 或 00 00 01分隔符,一般来说编码器编出的首帧数据为PPS与SPS,接着为I帧……

​编辑

  H.264由一个一个的NALU组成,每个NALU之间使用00 00 00 01或00 00 01分隔开,每个NALU的第一次字节都有特殊的含义,其内容如下:

描述
bit[7]必须为0
bit[5-6]标记该NALU的重要性
bit[0-4]NALU单元的类型

  常用Nalu_type: 

0x67 (0 11 00111) SPS 非常重要 type = 7 

0x68 (0 11 01000) PPS 非常重要 type = 8 

0x65 (0 11 00101) IDR帧 关键帧 非常重要 type = 5 

0x61 (0 11 00001) 非IDR的I帧 或 P帧 非常重要 type = 1  非IDR的I帧 不大常见 【这个大概率是P帧,通过工具查看某个H264的P slice,开头为00 00 00 01 61】

0x41 (0 10 00001) 非IDR的I帧 或 P帧 重要 type = 1 【非IDR的I帧P帧都是有可能的,具体通过工具分析】

0x01 (0 00 00001) B帧 不重要 type = 1 

0x06 (0 00 00110) SEI 不重要 type = 6

  所以判断是否为I帧的算法为: (NALU类型  & 0001  1111) = 5  

  最简单的办法是找0x65或0x25(I frame启始位),

  或者去找0x67或0x27(SPS)

  和0x68或0x28(PPS)后面的完整包。

  SPS和PPS后面必然跟着I frame。(68之后,出现  000001 开始就是关键帧数据 )

  好,对于H.264格式了解这么多就够了,我们的目的是想从一个H.264的文件中将一个一个的NALU提取出来,然后封装成RTP包,下面介绍如何将NALU封装成RTP包。

3.1、H264 NAL RTP打包

  NALU是H264用于网络传输的单元类型,一个完整的NALU单元一般是以0x000001或者0x00000001开始,其后跟的则是NALU头和NALU的数据;我们在网络传输的时候,会去掉开始的0x000001或者0x00000001的标志;一般需要将这些标志替换为RTP payload的头部(1个字节);

4、下面在给出了RTP包格式------永远是 RTP头+RTP载荷

参考

参考https://www.cnblogs.com/kimiway/p/4427310.html【H264 NAL RTP打包】

4.1、RTP载荷H264码流

  首先要明确,RTP包的格式是绝不会变的,永远都是RTP头+RTP载荷:

​编辑

  红色为RTP协议头,黄色是RTP载荷【H264码流,其中NALU数据就是RBSP数据】

​编辑

4.1.1、RTP载荷第一个字节格式跟NALU头一样:【后面的格式与H264的RTP打包格式相关】

Python

      +---------------+
      |0|1|2|3|4|5|6|7|
      +-+-+-+-+-+-+-+-+
      |F|NRI|  Type   |
      +---------------+

F:【1 bit】 forbidden_zero_bit, 占1位,在 H.264 规范中规定了这一位必须为 0

NRI:【2 bits】 nal_ref_idc, 占2位,取值从0到3,指示这个 NALU 的重要性,取值越大约重要。

Type:【5 bits】nalu是指包含在 NAL 单元中的 RBSP 数据结构的类型,其中0未定义,1-19在264协议中有定义,20-23为264协议指定的保留位。

(1)单一 NAL 单元模式,即一个 RTP 包仅由一个完整的 NALU 组成. 这种情况下 RTP NAL 头类型字段和原始的 H.264的   NALU 头类型字段是一样的。【1-23】

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

  这是一个序列参数集 NAL 单元. [00 00 00 01] 是四个字节的开始码, 67 是 NALU 头, 42 开始的数据是 NALU 内容。

  封装成 RTP 包将如下:[ RTP Header ] [ 67 42 A0 1E 23 56 0E 2F ],即只要去掉 4 个字节的开始码就可以了。

(2)组合封包模式 ,即可能是由多个 NAL 单元组成一个 RTP 包. 分别有4种组合方式: STAP-A, STAP-B, MTAP16, MTAP24。 【那么这里的类型值分别是 24, 25, 26 以及 27】

    【STAP-A   单一时间的组合包     24

        STAP-B   单一时间的组合包     25

        MTAP16   多个时间的组合包    26 

        MTAP24   多个时间的组合包    27】

(3)分片封包模式,用于把一个 NALU 单元封装成多个 RTP 包. 存在两种类型 FU-A 和 FU-B.。【类型值分别是 28 和 29】

    【FU-A     分片的单元   28

        FU-B     分片的单元  29  

      没有定义 30-31 】

4.2、RTP报头格式

​编辑

版本号(V):2Bit,用来标志使用RTP版本

填充位(P):1Bit,如果该位置位,则该RTP包的尾部就包含填充的附加字节

扩展位(X):1Bit,如果该位置位,则该RTP包的固定头部后面就跟着一个扩展头部

CSRC技术器(CC):4Bit,含有固定头部后面跟着的CSRC的数据

标记位(M):1Bit,该位的解释由配置文档来承担

载荷类型(PT):7Bit,标识了RTP载荷的类型

序列号(SN):16Bit,发送方在每发送完一个RTP包后就将该域的值增加1,可以由该域检测包的丢失及恢复包的序列。序列号的初始值是随机的

时间戳:32比特,记录了该包中数据的第一个字节的采样时刻

同步源标识符(SSRC):32比特,同步源就是RTP包源的来源。在同一个RTP会话中不能有两个相同的SSRC值

贡献源列表(CSRC List):0-15项,每项32比特,这个不常用

4.3、H.264的RTP打包方式

H.264可以由三种RTP打包方式:

(1)单NALU打包【RTP载荷第一个字节 type=1-23】

  一个RTP包包含一个完整的NALU。

(2)组合打包【RTP载荷第一个字节 type=24-27】

  对于较小的NALU,一个RTP包可包含多个完整的NALU。

(3)分片打包【RTP载荷第一个字节 type=28-29】

  对于较大的NALU,一个NALU可以分为多个RTP包发送。

注意:这里要区分好概念,每一个RTP包都包含一个RTP头部和RTP荷载,这是固定的。而H.264发送数据可支持三种RTP打包方式。

  比较常用的是单NALU打包分片打包,本文也只介绍这两种:

4.4、单NALU打包

  所谓单NALU打包就是将一整个NALU的数据放入RTP包的载荷中,这是最简单的一种方式,无需过多的讲解。

4.5、分片打包【以FU-A类型举例】

  每个RTP包都有大小限制的,因为RTP一般都是使用UDP发送,UDP没有流量控制,所以要限制每一次发送的大小,所以如果一个NALU的太大,就需要分成多个RTP包发送,如何分成多个RTP包,下面来好好讲一讲。

  RTP头部是固定的,那么只能在RTP载荷中去添加额外信息来说明这个RTP包是表示同一个NALU,如果是分片打包的话,那么在RTP载荷开始有两个字节的信息,然后再是NALU的内容:

​编辑

4.5.1、FU-A【type=28】分片打包类型的RTP载荷格式

VB

     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | FU indicator  |   FU header   |                               |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |
    |                                                               |
    |                         FU payload                            |
    |                                                               |
    |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                               :...OPTIONAL RTP padding        |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

注意,FU payload中并没有传送NALU的头部,
NALU的头部由FU indicator(前3位)和FU header(后五位)组成:nal_unit_type = (fu_indicator & 0xe0) | (fu_header & 0x1f);

4.5.2、RTP载荷第一个字节位FU Indicator,其格式如下:【这个与NALU头第一个字节格式一样】

Python

       +--FU indicator-+
       |0|1|2|3|4|5|6|7|
       +-+-+-+-+-+-+-+-+
       |F|NRI|  Type   |
       +---------------+

  高三位:与NALU第一个字节的高三位相同,Type:28<----->0x1c,表示该RTP包一个分片,为什么是28?此处28为FU-A【H264的RTP打包格式:分片格式】因为H.264的规范中定义的,此外还有许多其他Type,这里不详讲,如果想弄清楚,可以参考https://www.cnblogs.com/kimiway/p/4427310.html【H264 NAL RTP打包】

F:【1 bit】 forbidden_zero_bit, 占1位,在 H.264 规范中规定了这一位必须为 0

NRI:【2 bits】 nal_ref_idc, 占2位,取值从0到3,指示这个 NALU 的重要性,取值越大约重要。

Type:【5 bits】nalu是指包含在 NAL 单元中的 RBSP 数据结构的类型,其中0未定义,1-19在264协议中有定义,20-23为264协议指定的保留位。

(1)单一 NAL 单元模式,即一个 RTP 包仅由一个完整的 NALU 组成. 这种情况下 RTP NAL 头类型字段和原始的 H.264的   NALU 头类型字段是一样的。【1-23】

(2)组合封包模式 ,即可能是由多个 NAL 单元组成一个 RTP 包. 分别有4种组合方式: STAP-A, STAP-B, MTAP16, MTAP24。 【那么这里的类型值分别是 24, 25, 26 以及 27】

    【STAP-A   单一时间的组合包     24

        STAP-B   单一时间的组合包     25

        MTAP16   多个时间的组合包    26 

        MTAP24   多个时间的组合包    27】

(3)分片封包模式,用于把一个 NALU 单元封装成多个 RTP 包. 存在两种类型 FU-A 和 FU-B.。【类型值分别是 28 和 29】

    【FU-A     分片的单元   28

        FU-B     分片的单元  29  

      没有定义 30-31 】

4.5.3、RTP载荷第二个字节位FU Header,其格式如下

PowerShell

      +---FU header---+
      |0|1|2|3|4|5|6|7|
      +-+-+-+-+-+-+-+-+
      |S|E|R|  Type   |
      +---------------+

S:占1位如果是1表示当前这个包是FU-A分片打包的起始包

E:占1位如果是1表示当前这个包是FU-A分片打包的结束包

R: 占1位,保留位,为0

Type:实际包含的nalu的类型,后续才是NALU data

注意,FU payload中并没有传送NALU的头部,NALU的头部由FU indicator(前3位)和FU header(后五位)组成:nal_unit_type = (fu_indicator & 0xe0) | (fu_header & 0x1f);

4.5.4、FU-A分包方式注意点

       ①关于时间戳,需要注意的是h264的采样率为90000HZ(被标准固定死的,为了方便转换成npt时间,见维基百科:https://en.wikipedia.org/wiki/RTP_audio_video_profile),因此时间戳的单位为1(秒)/90000,因此如果当前视频帧率为25fps,那时间戳间隔或者说增量应该为3600,如果帧率为30fps,则增量为3000,以此类推。
       ②关于h264拆包,按照FU-A方式说明:
        1)第一个FU-A包的FU indicator:F应该为当前NALU头的F,而NRI应该为当前NALU头的NRI,Type则等于28,表明它是FU-A包。FU header生成方法:S = 1,E = 0,R = 0,Type则等于NALU头中的Type。
        2)后续的N个FU-A包的FU indicator和第一个是完全一样的,如果不是最后一个包,则FU header应该为:S = 0,E = 0,R = 0,Type等于NALU头中的Type。
        3)最后一个FU-A包FU header应该为:S = 0,E = 1,R = 0,Type等于NALU头中的Type。

       ③FU payload中并没有传送NALU的头部,NALU的头部由FU indicator(前3位)和FU header(后五位)组成:nal_unit_type = (fu_indicator & 0xe0) | (fu_header & 0x1f);

       ④FU-A 还原的时候,也是0x00 00 00 01 开始,不需要自己额外添加0x00 00 00 01
       ⑤ FU-A 的的解析,start end等数据要解析好
       ⑥single nal unit 也是以0x00 00 00 01开始,也不需要自己添加分隔符

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值