h264_probe探测h264格式—FFmpeg源码分析 - 弦外之音

/ 0评 / 0

作者:罗上文,微信:Loken1,公众号:FFmpeg弦外之音

本文使用的命令如下:

ffmpeg -i juren-30s.h264 out.flv -y

请参考《用Ubuntu18与clion调试FFmpeg》搭建好调试环境,juren-30s.h264 在 GitHub 里面有的,请自行下载。


几乎每一个 Demuxer 都有自己的 probe 探测函数的,探测函数被调用的位置,如下:

1-1

h264_probe() 函数的内部流程如下:

1-2

h264_probe() 函数的重点如下:

1,找到每个 NALU 的开头

Annex B 格式的各个 NALU 之间是用 Start Codes (0x000001) 跟 填充字符 0 来分隔。所以在 for 循环里面,需要找到 起始码,同时还要处理填充字符 0,如下:

1-3

这段代码是比较复杂的,我也看了挺久的,首先变量 code 默认是 -1,注意,他是无符号的 32位类型的。所以 code 的二进制表示是

code = -1 = 0b1111 1111 1111 1111 1111 1111 1111 1111

code 内存的 32 位全部都是 1,然后他循环里,每次都把 code 右移 8 位,运算如下:

0b1111 1111 1111 1111 1111 1111 1111 1111 << 8 = 0b1111 1111 1111 1111 1111 1111 0000 0000

可以看到,最后8位变成了 0,所以空出来了8位的空间。这 8 位的内存空间,其实是给 p->buf[i] 用的,p->buf[i] 占 8 位。

所以 (code << 8) + p->buf[i] 这句代码的含义,就是每次都读取一个字节,然后放在末尾。

然后,下面的这句代码也是不太容易理解的,如下:

if ((code & 0xffffff00) == 0x100)

因为 起始码 前面可能会填充多个 0 来对齐数据长度,所以我们需要处理填充字符。其实这段代码是处理了填充字符 0 的。你需要把 0xffffff00 跟 0x100 转成二进制才能看懂他的代码。

0xffffff00 = 0b1111 1111 1111 1111 1111 1111 0000 0000
0x100 = 0b0000 0000 0000 0000 0000 0001 0000 0000

因此 code & 0xffffff00 操作,会消除最后 8 位,但是,但是,前面的 24 位是保留的,所以上一次读取出来的那个字节是保留下来的,注意,是上一次。

因此当读取到 67 这个字节的时候,如下,67 就会被放到变量 code 的后 8 位,这时候 code 就等于下面的值。

code        = 0b0000 0000 0000 0000 0000 0001 0110 0111
1-4

因此 if ((code & 0xffffff00) == 0x100) 就会成立,就算找到 NALU 了。读者可以自行调试一下,就很容易看懂这段代码。

小总结:这段代码的逻辑其实就是要找到一字节的 0x1,然后前面的 2 个字节必须全是 0 ,这就是找到 NALU 的逻辑。


2,判断 NALU 是否产生了比特错误

之前在《H.264 Annex B格式分析》讲过,NALU 头部的第一位如果是 1,也就是 forbidden_bit 字段如果等于 1,就代表这个 NALU 发生了比特错误,所以下面这句代码就是判断 比特错误的。

if (code & 0x80) // forbidden_bit
    return 0;

0x80 转成 二进制表达如下:

0x80 = 0b1000 0000

3,ref_zero 判断 NALU 是否符合H.264标准

1-5

上图中的 变量 ref_idc 就是 StreamAnalyzer 软件里面展示的 nal_ref_idc ,变量 type 就是 nal_unit_type,如下:

1-5-2

那 ref_zero[] 这个变量是干什么的呢?我也不知道他为什么起名字叫 zero,但是这个命名还是挺好的。只是我不知道怎么把 zero 翻译成中文表达给大家听。

ref_zero[] 其实是一个映射表,这个映射表是用来判断 NALU 的数据是否符合H264标准。这个在《新一代视频压缩编码标准》一书有介绍,如下:

al_ref_idc 指的是当前 NAL 的优先级。取值范围为 0~3,值越高,表示当前 NAL 越重要,越需要优先受到保护。H.264 规定如果当前 NAL 是一个序列参数集,或一个图像参数集,属于参考图像的片或片分区等重要的数据单位时,本句法元素必须大于0。但在大于 0 时具体该取何值,并没有进一步的规定,通信双方可以灵活地制定策略。当 nal_unit_type 等于 5 时,nal_ref_idc 大于 0。当 nal_unit_type 等于 6、9、10、11 或12 时,nal_ref_idc 等于 0。

上面这段话,其实也是 H.264 标准的内容,他限制了在什么场景下,nal_ref_idc 的取值范围,必须这么做

我们基于这个逻辑来理解 FFmpeg 的代码就好理解很多了,例如下面这句代码。

if (ref_zero[type] == 1 && ref_idc)
   return 0;

ref_zero[] 数组里,哪些 type 等于 1 ?就是 6、9、10、11 或12。在这些场景下,如果 ref_idc 不等于 0,也就是有值,这个 NALU 就不符合标准,就直接返回 0,0 是最低的打分。

其他的判断也是类似的原理,读者自行推导一番就可以了。


4,统计 sps,pps 等 NALU 的数量

当通过 zero_ref 的判断之后,就代表这个 NALU 是符合 H.264 标准的。然后根据 type 来判断 NALU 的类型,来统计 NALU。

1-6

最后,如果找到 spspps 等 NALU 数据,h264_probe() 函数返回的分数就会很高,就可以探测出来输入的数据是 h.264 了。


h264_probe() 函数里面还有一些代码是没有讲解的,请读者自行思考,如下:

1,变量 res 代表什么?

if (ref_zero[type] == 2) {
    if (!(code == 0x100 && !p->buf[i + 1] && !p->buf[i + 2]))
        res++;
}

2,函数 init_get_bits8() 有何作用?

ret = init_get_bits8(&gb, p->buf + i + 1, p->buf_size - i - 1);

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注