【编解码】记录一个ffmpeg解码生成YUV的 color range 问题,以及video_full_range_flag用法。

1. 问题起因

注: 本文软件版本:ffmpeg 4.4 。安霸cv2x SDK 3.0.9

最近有客户在使用我们芯片的avc/hevc 编码器的提了一个问题,很有意思,我花了2天来找原因和做实验。现将过程和结论记录如此,希望能帮助到后来者。

1.1 问题的现象

客户使用我们的芯片去编码一段固定的YUV 序列(golden data,记为YUV-A),
得到HEVC码流(记作StreamB)后,再使用ffmpeg解码,得到YUV(记作YUV-B)。

ffmpeg -i case1.h265 -vcodec rawvideo -pix_fmt nv12 -an YUV-B.yuv

然后使用PSNR 和 VMAF,参考YUV-A去计算YUV-B,计算视频质量,以此想评估编码器性能。
意外的发现,直接使用默认参数编解码后,得分不高。
但是如果强行码流中的SPS中的VUI的 video_full_range_flag 字段从1改为0,得到StreamC,再去解码,得到新的YUV(记为YUV-C),再评估,参考YUV-A去计算YUV-C,得分大大提高。
因此,客户想跟我们确认这个参数是否可以改动,有没有什么影响。

1.2 问题的分析

首先得分高低肯定是因为每个像素的差值变化。
我简单比较了一下两个文件的像素值,发现YUV-B和YUV-A差值较大,而YUV-C 和 YUV-A的差值就比较小。
而在码流文件中,用vega比较了streamB 和 streamC,发现YUV数据完全一致,二者的参数中仅有SPS中的video_full_range_flag不同。
如图,一共60帧2个GOP,是只有2个SPS的数据不同。
在这里插入图片描述
但是解码出来的YUV数据差别很大,做了一个简单的数据统计,YUV-A/B/C 的统计数据如下:

输入YUV是我做的一个灰阶图,覆盖0~256范围。
YUV-A

解出来的YUV情况分别如下

YUV-B:16~235
YUV-B
YUVC:0~256
YUV-C

所以造成两个YUV这么大差异的原因,一定是ffmpeg解码流程的不同。
把ffmpeg解码YUV的两段log拿出来比较一下:
在这里插入图片描述
可以看出,ffmpeg把streamB 和 streamC识别成了不同的格式,一个是jpeg,一个mpeg。
这就是问题的关键的,这些是什么?会导致什么?
OK,下面开始正式分析ffmpeg的解码。

2. video_full_range_flag

2.1 color range : full (jpeg)/ limited(mpeg)

在正式看 video_full_range_flag 的问题之前,我们需要先了解一下mpeg 和 jpeg这么两个东西。
YUV的色彩范围分为两种:

  1. full range Y / U / V 的范围是[0, 255]
  2. limited range Y [16,235]
    UV[16,240]
    为什么会有这两种区别,主要是应用场景不同
    电视机一般只支持240个色阶,从16~255,这就是limited
    电脑显示器支持255个色阶,从0~255,这就是full。

在下面的内容中,你可以默认这些东西是等效的,因为不同软件,不同模块,使用了不同的表达方式,但是他们的含义都是一样的。

“full range” = “jpeg” = “pc” = “cg” = “high rgb”

“limited range” = “mpeg” = “tv” = “broadcast” = “low rgb”

2.2 video_full_range_flag

接下来,我们先来看一下video_full_range_flag的含义,看看这个字段能不能随意修改。
我以H264的白皮书来分析,翻到附录E。
video_full_range_flag字段解释如下:
在这里插入图片描述
后面还给了一些计算公式,简单来说就是,
如果video_full_range_flag=0,代表这段码流是limited range的。
如果video_full_range_flag=1,代表这段码流是full range的。
同时,默认值为0.
我不是很理解的就是为什么默认是0。
是个历史遗留问题?为了以前都是电视机播放? 可能是为了过去的兼容性问题吧。一直也不改。
感觉放到现在还是默认为1比较好,做一个全范围的。
我们芯片里的参数就是默认为1的。我认为1更全面更合理。

3. ffmpeg的解码问题

3.1 解码流程

回到ffmpeg的解码问题,从日志log可以看出来。
在这里插入图片描述

ffmpeg会把一种格式,解码成另一种格式。
对于 streamB -> YUV-B,是从 yuv420p(tv, bt709) 转到nv12(tv, bt709, progressive)。
对于 streamC-> YUV-C,是从 yuvj420p(pc, bt709)转到nv12(tv, bt709, progressive)。
对于输入的格式:
tv 和 pc我们前面已经解释过了,是color range,这个值就是根据 码流中的 “video_full_range_flag” 字段来确定的。
但是输出的格式:
我就不能理解了,居然默认是tv,也就是limited?
为什么呢?就是因为这个,导致了二者解码流程的不同,进而YUV数据出现巨大的差异, PSNR的得分变化很大。
下面来分析一下流程,
为什么streamB的分低,streamC的分高。

  1. StreamB
    我们的reference YUV 是 full range的。
    使用编码器编出来的H265也是full range的,没有做色彩的映射,同时标注了video_full_range_flag=1。
    ffmpeg检测到码流是full range的(因为video_full_range_flag=1),但是它默认输出limited range的 YUV,因此做了色彩映射,把[0,255]的范围映射到了[16, 235]之间,相当于每个像素值都做了偏移。
    在最后计算得分的时候,这些偏移都被当做了噪声。refer YUV是[0, 255]的,结果YUV是[16, 235]的,可想而知,MSE均方差很大,得分就低。

  2. SteamC
    我们的reference YUV 是 full range的。
    使用编码器编出来的H265也是full range的,没有做色彩的映射,但是video_full_range_flag被强制改成了0。
    ffmpeg检测到码流是limited range的(因为video_full_range_flag=0),而且它默认输出limited range的 YUV,因此没有色彩映射,保持了码流中的color range,码流中的色彩范围实际是full range,相当于每个像素值都没有添加偏移。
    在最后计算得分的时候,因为没有偏移,每个像素间的差值就小,所以MES小,得分高。

这就是根本原因。

3.2 警告: [swscaler @ 0000022f98f360c0] deprecated pixel format used, make sure you did set range correctly

log中有一句warning,之前忽略了。
看了一下代码,这句话的触发条件就是,使用yuvj420p(或者yuvj444p/yuvj422p) 这个格式。
我们的码流中的video_full_range_flag=1导致在解码过程中,设置src format是yuvj420p,触发了这个打印。
已经提醒了 检查设置range。
只不过当初没看懂罢了。
在这里插入图片描述

3.3 解决方案

针对full range的reference YUV, full range编码的encoder,在用ffmpeg解码的时候,需要指定输出的范围是full,而不能使用默认的limited!

看一下ffmpeg支持的mode:

在这里插入图片描述

ffmpeg.exe -i case.h265 -vcodec rawvideo -pix_fmt nv12 -an case.yuv -lavfi "scale=out_range=full"

注意最后的
-lavfi “scale=out_range=full”

最后再check一下ffmpeg中的log是不是变化了
在这里插入图片描述
因为有时候我发现有些参数并没有生效,但是ffmpeg也不会报错,这里还是要自己在check看一遍。

4. 参考链接

猜你喜欢

转载自blog.csdn.net/tao475824827/article/details/124732658