h264 码流格式简述

h264 码流格式简述(Annex-B 格式)

1 nal unit stream(Network Abstraction Layer Unit Stream)

h.264 编码器把原始的 yuv 图像文件编码成码流文件,生成的码流文件称为 NAL 单元流(NAL unit Stream),NALU stream 由一个个 NALU(nal 单元) 组成(https://www.cnblogs.com/TaigaCon/p/5215448.html):
h264 码流格式简述_第1张图片

2 nal 单元分割方式

多个 nalu 之间,通过分割字节,组成了 nalu stream,分隔符定义如下:

zero_byte(0x00),一个字节。如果当前的 NAL 单元为 sps、pps 或者一个访问单元(access unit)的第一个 NAL 单元,这个字节就会存在
start_code_prefix_one_3bytes(0x000001),三个字节。固定存在的 NAL 单元起始码,用来指示下面为一个 NAL 单元

所以我们常看到的 nalu 分隔符一般由 0x00000001 或则 0x000001 组成。

3 nal 单元结构

NALU 由 header + payload 组成(http://iphome.hhi.de/wiegand/assets/pdfs/DIC_H264_07.pdf):
h264 码流格式简述_第2张图片

4 nalu header 结构

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

F:1 bit,forbidden_zero_bit。固定为 0
NRI:2 bit,nal_ref_idc。用于指示该 nalu 的重要性,实际应用层代码一般不关心此值
Type:5 bit,nal_unit_type。指定 nalu 的类型,如下所示:
h264 码流格式简述_第3张图片
我们通过读取 nalu 的第一个字节,取得 nalu header,然后判断 type,就能知道此 nalu 所属的帧类型,常见的如 IDR/I 帧的 type=5,P/B 帧的 type=1,sps 的 type=7,pps 的 type=8 等。
在 rfc6184 中,又为 nal_unit_type 从类型值 24 开始进行了扩充:
Table 1. Summary of NAL unit types and the corresponding packet
types

  NAL Unit  Packet    Packet Type Name               Section
  Type      Type
  -------------------------------------------------------------
  0        reserved                                     -
  1-23     NAL unit  Single NAL unit packet             5.6
  24       STAP-A    Single-time aggregation packet     5.7.1
  25       STAP-B    Single-time aggregation packet     5.7.1
  26       MTAP16    Multi-time aggregation packet      5.7.2
  27       MTAP24    Multi-time aggregation packet      5.7.2
  28       FU-A      Fragmentation unit                 5.8
  29       FU-B      Fragmentation unit                 5.8
  30-31    reserved                                     -

注意,扩充的 nal_unit_type 类型并不是编码器输出的类型,而是为了适应 rtp payload 打包而定义的打包类型。

5 nalu payload

nalu payload 由 rbsp 结构组成。
rbsp 可以分为 video-codec-layer rbsp(如 I/P/B 帧等) 和 non-video-codec-layer rbsp(如 sps/pps/sei 等)。
防竞争字节emulation_prevention_three_byte,1字节,固定0x03,在 rbsp 中出现连续的 0x0000 两字节结构时,在后面添加 0x03 作为防竞争字节,避免与 nal 单元分割字节冲突:

0x000000 => 0x00000300
0x000001 => 0x00000301
0x000002 => 0x00000302
0x000003 => 0x00000303

rbsp 尾部

rbsp_stop_one_bit,1位,固定为1
rbsp_alignment_zero_bit,用于字节对齐,可选

SPS/PPS

SPS/PPS 不同于 slice,虽然也是编码器输出的内容,但是不包含任何原始码流。他们中包含的是解码器需要的解码信息,例如图像的宽高、一些编码参数等。

在编码的时候,可以通过 ffmpeg 设置 AV_CODEC_FLAG_GLOBAL_HEADER 参数,那么 sps、pps 会作为 extra data 出现在 AVCodecContext::extradata 变量中,而不是出现在每个

IDR 帧的前面。用户发送数据的时候,最好每次发送 idr 帧时,都将 sps、pps 一起发送。
sps、pps 会有一个 id 值,如 sps 中的 seq_parameter_set_id,用于标识 sps 版本。pps 中的 pic_parameter_set_id,用于标识 pps 版本(且 pps 也有一个 seq_parameter_set_id,用于标识参考的 sps)。idr 也会有一个 pic_parameter_set_id,用于标识参考的 pps id(然后通过 pps 的 seq_parameter_set_id 跟踪参考的 sps)。当编码参数发生变化时,这些值也会发生变化,所以发送给接收端的数据,一定要及时更新 sps、pps,否则会发生解码错误。

6 slice(条带)

编码后原始码流被保存到了称为 slice 的结构中,编码后的一帧图像可以对应一个 slice。但是因为一些编码器设置,例如设置了输出 slice 的数量、进行了多线程并行编码等,编码后的一帧图像,也会分为多个 slice,每个 slice 各自负责了图像中某一块的编码。
slice 也分为 header 和 data 部分:
h264 码流格式简述_第4张图片

通过 header 部分,我们可以得到当前 slice 在一帧编码图像中的位置(第几个)、当前编码图像是 I/P/B 帧等中的哪一种、当前编码图像参考的 SPS/PPS 等重要信息。

6.1 access unit(访问单元)
访问单元代表一张编码图像,不包含 sps、pps 等外部数据。由于一帧编码后的图像可能会生成多个 slice,所以,access unit(访问单元)可以由属于一帧编码图像的多个 slice(nalu) 组成。

6.2 idr 帧
idr 帧是立即刷新帧,意味着接收端收到 idr 帧时,前面的参考帧缓存都可以丢弃了(注意,仅针对 close-gop),且 idr 后面的帧不会参考 idr 前面的任何帧。
idr 与 I 帧不同,I 帧不具有刷新参考缓冲区的功能,使用 ffmpeg 编码时,可以设置 AVCodecContext::gop_size 来指定多少帧产生一个 IDR 帧。

你可能感兴趣的:(c++,音视频,c语言,ffmpeg,h.264)