FFmpegFFmpeg与音视频流媒体工程开发相关

FFmpeg video filter FATE测试过程介绍

2020-02-08  本文已影响0人  郭叶军

FATE (FFmpeg Automated Testing Environment) 是FFmpeg社区开发的自动化测试框架,支持对FFmpeg API的测试,也支持对FFmpeg内部函数的测试。对FFmpeg video filter的测试,就属于对API的测试。之前为了为一个video filter增加fate测试,大概研究了一下,感觉FFmpeg官方主页的资料还可以更详细,在酝酿写一篇介绍的时候,看到"大师兄悟空"在微信公共号“流媒体技术”发布了《如何编写FFmpeg自动化测试用例》(一、二),就一时懒得动笔。

趁最近几天假期,想想还是记录一下为佳,除了分享外,还可以自用备查。我的记录角度是在Linux环境下如何一步步的探索出FFmpeg video filter FATE的测试过程,从而理解如何为一个video filter增加fate测试。可能这样更实用,一个新的任务,缺少足够资料,都可以用这样类似的方法去探索。成文顺序虽然大致是按探索过程来的,但是,为了阅读逻辑性,有些地方会直接介绍后来才理解到的知识点。

先上一个图,列一下接下来会用到的主要文件以及目录。


file

得到FFmpeg源代码并且编译:

#演示目的,所以,所有内容都放在/tmp/目录下
$ mkdir -p /tmp/ffmpeg_work     
$ cd /tmp/ffmpeg_work

$ git clone https://git.ffmpeg.org/ffmpeg.git

#新建编译目录,以和源码目录分离
$ mkdir build
$ cd build

#configure参数有很多,这里简单起见,使用默认值
$ ../ffmpeg/configure

$ make -j8

#可以看到编译出了ffmpeg和ffplay等
#其中带_g后缀的文件名表示symbol没有被strip。
$ ls
config.asm  doc      ffmpeg    ffplay    ffprobe
fftools     libavdevice  libavformat  libswresample
Makefile  tests config.h    ffbuild  ffmpeg_g
ffplay_g  ffprobe_g  libavcodec  libavfilter
libavutil  libswscale     src.

在FATE中有很多测试,有些测试是自包含的,有些测试还需要额外的资料,在FFmpeg中一般称之为SAMPLES,可以用下面的方法获取SAMPLES。

$ make fate-rsync SAMPLES=fate-suite/
#命令输出如下,表示用rsync方法来同步资料
rsync -vrltLW --timeout=60 --contimeout=60 rsync://fate-suite.ffmpeg.org/fate-suite/ fate-suite/

不幸的是,由于网络原因,这条命令很难成功执行。所以,只能假设这些资料都已经在你的系统的/tmp/ffmpeg_work/fate-suite/目录下面了。需要提醒的是,这些资料可能会经常更新(一般来说,只会增加内容,不会修改文件),如果你要跑完整FATE的话,需要时时更新。

对于自包含不需要SAMPLES的测试,只需要运行下面的命令。

$ make fate
GEN     tests/pixfmts.mak
# 下面的输出提示也表明后续测试不需要SAMPLES
warning: only a subset of the fate tests will be run because SAMPLES is not specified
HOSTCC  tests/base64.o
HOSTLD  tests/base64
HOSTCC  tests/tiny_psnr.o
HOSTLD  tests/tiny_psnr
HOSTCC  tests/tiny_ssim.o
HOSTLD  tests/tiny_ssim
HOSTCC  tests/audiomatch.o
HOSTLD  tests/audiomatch
CC      tests/checkasm/aacpsdsp.o
CC      tests/checkasm/af_afir.o
CC      tests/checkasm/alacdsp.o
...
TEST    checkasm-aacpsdsp
TEST    checkasm-af_afir
TEST    checkasm-alacdsp
...

我为DNN模块加的FATE测试也属于这种类型。

对于需要SAMPLES的测试,则可以用下面的命令来指定SAMPLES对应的目录。实际上,指定SAMPLES还有其他的几种方法,这里只是选择了其中的一种方法。在指定SAMPLES后,make fate就会跑所有的测试用例,包括自包含的测试。

$ make fate SAMPLES=../fate-suite/ 
...
TEST    filter-scale500
...

有些FFmpeg video filter的fate测试属于这种类型,需要依赖SAMPLES的存在。而有些video filter的fate测试所需要的数据都已经在FFmpeg源代码目录树中了,就不需要依赖SAMPLES了。

在make fate的时候,首先会编译测试用例(见命令行输出的CC等字样),然后就会执行测试(见命令行输出的TEST字样)。根据命令行输出我们可以看到有很多个子测试被执行。

上述make fate的方法会跑多个测试,为了理解如何增加一个filter测试,我们应该先缩小到一个测试的情况。基于对makefile的了解,我们知道只要为make指定单独的一个测试作为target即可,那具体应该是什么呢,大胆猜测,就从上一步骤的命令行输出中选择字符串filter-scale500,试着执行make filter-scale500,发现并不正确。那怎么办呢,到FFmpeg源代码目录的tests目录下,用grep搜索字符串,最终在filter-video.mak文件中找到了正确的字符串是fate-filter-scale500。如下所示。

$ make filter-scale500  
make: *** No rule to make target 'filter-scale500'.  Stop.

$ cd ../ffmpeg/tests/
$ grep -rn filter-scale500 ./
./fate/filter-video.mak:478:FATE_FILTER_VSYNTH-$(CONFIG_SCALE_FILTER) += fate-filter-scale500
./fate/filter-video.mak:479:fate-filter-scale500: CMD = video_filter "scale=w=500:h=500"

$ cd -
/tmp/ffmpeg_work/build
$ make fate-filter-scale500
TEST    filter-scale500

为什么选择filter-scale500作为尝试呢,首先,这是video filter,而我要加的也是video filter的测试;其次,scale具体是干什么的,容易望文生义,比较好理解;最后,有了500为后缀后,字符串比较唯一,不容易搜索到其他容易混淆的地方去。

对于make来说,使用V=1可以输出更多的信息,如下所示。

$ V=1 make fate-filter-scale500  
TEST    filter-scale500
/tmp/ffmpeg_work/ffmpeg/tests/fate-run.sh fate-filter-scale500 "" "" "/tmp/ffmpeg_work/build" 'video_filter "scale=w=500:h=500"' '' '' '' '1' '' '' '' '' '' '' '' '' '' ''
 /tmp/ffmpeg_work/build/ffmpeg -nostdin -nostats -cpuflags all -flags +bitexact -idct simple -sws_flags +accurate_rnd+bitexact -fflags +bitexact -threads 1 -f image2 -vcodec pgmyuv -hwaccel none -threads 1 -thread_type frame+slice -i /tmp/ffmpeg_work/build/tests/vsynth1/%02d.pgm -flags +bitexact -sws_flags +accurate_rnd+bitexact -fflags +bitexact -flags +bitexact -idct simple -sws_flags +accurate_rnd+bitexact -fflags +bitexact -threads 1 -dct fastint -vf scale=w=500:h=500 -vcodec rawvideo -frames:v 5 -f nut md5:

从上述信息可以看出来,makefile中调用了fate-run.sh,而这个脚本执行时候的参数是哪里来的,看一下前面找到的filter-video.mak文件,发现匹配在一起了。

#filter-video.mak部分内容摘录
FATE_FILTER_VSYNTH-$(CONFIG_SCALE_FILTER) += fate-filter-scale500
fate-filter-scale500: CMD = video_filter "scale=w=500:h=500"

所以,如果要为video filter加一个fate测试,就是要在filter-video.mak增加相应的内容。浏览这个文件,可以看到CMD = ...的开头,除了video_filter外,还有诸如framecrc、framemd5和ffmpeg等,都是作为参数被传入fate-run.sh脚本中,具体含义将在后面介绍。

为了看到脚本fate-run.sh中每条指令的执行过程,我们用bash -x来跑这个脚本,如下所示。

# bash -x 会打印出shell脚本中每一条指令的执行情况
$ bash -x /tmp/ffmpeg_work/ffmpeg/tests/fate-run.sh fate-filter-scale500 "" "" "/tmp/ffmpeg_work/build" 'video_filter "scale=w=500:h=500"' '' '' '' '1' '' '' '' '' '' '' '' '' '' ''
...

根据上述命令行输出,一行一行的对应过去,最终会找到下面的关键脚本:

eval $command >"$outfile" 2>$errfile

还是从上述命令行输出可以看到,eval这行脚本展开后为:

eval video_filter '"scale=w=500:h=500"'

于是,eval执行这条命令,也就是来自filter-video.mak文件中的CMD的内容。而在脚本文件fate-run.sh中,已经定义了多个函数,诸如video_filter、framecrc、framemd5和ffmpeg等,相应的命令就对应着这些内部函数。

用eval启用的命令,被shell直接当做一条命令处理,无法看到内部的展开情况,(也可能是我没有找到正确的方法)。作为一个快速方法,我们直接修改脚本中的eval行如下所示。

#eval $command >"$outfile" 2>$errfile
video_filter "scale=w=500:h=500"
#为了避免和后面的混淆,所以加了exit
exit

再用bash -x来执行这个脚本,如下所示。

$ bash -x /tmp/ffmpeg_work/ffmpeg/tests/fate-run.sh fate-filter-scale500 "" "" "/tmp/ffmpeg_work/build" 'video_filter "scale=w=500:h=500"' '' '' '' '1' '' '' '' '' '' '' '' '' '' ''  
...
+ /tmp/ffmpeg_work/build/ffmpeg -nostdin -nostats -cpuflags all -flags +bitexact -idct simple -sws_flags +accurate_rnd+bitexact -fflags +bitexact -threads 1 -f image2 -vcodec pgmyuv -hwaccel none -threads 1 -thread_type frame+slice -i /tmp/ffmpeg_work/build/tests/vsynth1/%02d.pgm -flags +bitexact -sws_flags +accurate_rnd+bitexact -fflags +bitexact -flags +bitexact -idct simple -sws_flags +accurate_rnd+bitexact -fflags +bitexact -threads 1 -dct fastint -vf scale=w=500:h=500 -vcodec rawvideo -frames:v 5 -f nut md5:
...
e7d6f07710a707e4e5583aee54a8f5ff
+ exit

上述命令行输出很长,大部分篇幅是用来设置最终的/tmp/ffmpeg_work/build/ffmpeg的命令行参数,最后得到的命令行参数如上所示(两行省略号之间的那一行)。回顾一下,其实,我们前面在执行V=1 make fate-filter-scale500看到的最后一行输出,和这里看到的是相同的。而倒数第二行的e7d6...这一串字符则是ffmpeg的输出结果。我们也可以直接在命令行执行ffmpeg程序,得到相同的结果,如下所示。

$ /tmp/ffmpeg_work/build/ffmpeg -nostdin -nostats -cpuflags all -flags +bitexact -idct simple -sws_flags +accurate_rnd+bitexact -fflags +bitexact -threads 1 -f image2 -vcodec pgmyuv -hwaccel none -threads 1 -thread_type frame+slice -i /tmp/ffmpeg_work/build/tests/vsynth1/%02d.pgm -flags +bitexact -sws_flags +accurate_rnd+bitexact -fflags +bitexact -flags +bitexact -idct simple -sws_flags +accurate_rnd+bitexact -fflags +bitexact -threads 1 -dct fastint -vf scale=w=500:h=500 -vcodec rawvideo -frames:v 5 -f nut md5:
...
e7d6f07710a707e4e5583aee54a8f5ff

上述ffmpeg命令行参数的最后是-f nut md5:,表示将结果做md5的输出,输出一个字符串。如果在filter-video.mak 中,CMD是用framecrc开始的,那么,最终得到的ffmpeg命令行参数大致如下所示,其中最后的-f framecrc -表示对每个输入的图片做处理后的最终结果是一个crc值。在命令行执行此命令,得到的输出是多行字符串,分别对应着每一个输入,如下所示,一共处理了50个输入图片。

$ /tmp/ffmpeg_work/build/ffmpeg -nostdin -nostats -cpuflags all -c:v pgmyuv -hwaccel none -threads 1 -thread_type frame+slice -i /tmp/ffmpeg_work/build/tests/vsynth1/%02d.pgm -vf format=rgb24,perms=random,colorchannelmixer=.31415927:.4:.31415927:0:.27182818:.8:.27182818:0:.2:.6:.2:0 -flags +bitexact -sws_flags +accurate_rnd+bitexact -bitexact -f framecrc -
...
0,          0,          0,        1,   304128, 0x42900c13
0,          1,          1,        1,   304128, 0xfb0439bc
0,          2,          2,        1,   304128, 0x967b9f0d
0,          3,          3,        1,   304128, 0xc2c92489
0,          4,          4,        1,   304128, 0x024499b1
0,          5,          5,        1,   304128, 0x66144785
0,          6,          6,        1,   304128, 0x0e505bcd
0,          7,          7,        1,   304128, 0xc8b26ed2
0,          8,          8,        1,   304128, 0x14b5717b
0,          9,          9,        1,   304128, 0x2ba3144a
0,         10,         10,        1,   304128, 0x1185992b
0,         11,         11,        1,   304128, 0xd55b289a
0,         12,         12,        1,   304128, 0x59f2f3be
0,         13,         13,        1,   304128, 0xfe4d6adf
0,         14,         14,        1,   304128, 0x630806cc
0,         15,         15,        1,   304128, 0x2deb2f19
0,         16,         16,        1,   304128, 0xfbffa923
0,         17,         17,        1,   304128, 0xb7770d46
0,         18,         18,        1,   304128, 0xda09bd0e
0,         19,         19,        1,   304128, 0x17a422d2
0,         20,         20,        1,   304128, 0xbb6172f5
0,         21,         21,        1,   304128, 0xcf639456
0,         22,         22,        1,   304128, 0xdb0ae1ac
0,         23,         23,        1,   304128, 0x850d6a68
0,         24,         24,        1,   304128, 0xdc8409fb
0,         25,         25,        1,   304128, 0x26216c51
0,         26,         26,        1,   304128, 0x1d0004de
0,         27,         27,        1,   304128, 0xed019a70
0,         28,         28,        1,   304128, 0xb1abd985
0,         29,         29,        1,   304128, 0xec1c14b2
0,         30,         30,        1,   304128, 0x046db068
0,         31,         31,        1,   304128, 0xa4fb1029
0,         32,         32,        1,   304128, 0x49e05e61
0,         33,         33,        1,   304128, 0x7668d6d1
0,         34,         34,        1,   304128, 0x6dd0ce9d
0,         35,         35,        1,   304128, 0x87983f5e
0,         36,         36,        1,   304128, 0xb98278cf
0,         37,         37,        1,   304128, 0x55186244
0,         38,         38,        1,   304128, 0x3135e7ea
0,         39,         39,        1,   304128, 0xdbf59a2c
0,         40,         40,        1,   304128, 0x944cdc92
0,         41,         41,        1,   304128, 0x5849dfe8
0,         42,         42,        1,   304128, 0xaf9075ba
0,         43,         43,        1,   304128, 0xb4f01118
0,         44,         44,        1,   304128, 0x4dfb711f
0,         45,         45,        1,   304128, 0xb558e732
0,         46,         46,        1,   304128, 0xb23a171e
0,         47,         47,        1,   304128, 0xb5c68065
0,         48,         48,        1,   304128, 0xcf1b122e
0,         49,         49,        1,   304128, 0x1e2d38e5

还有一些情况,在filter-video.mak 中,CMD是直接用ffmpeg开头,一般来说,最后得到的ffmpeg参数最后会是-f rawvideo -,表示最后输出的不是crc也不是md5,而是rawvideo的内容。

回到fate-run.sh,再看一下eval行。

eval $command >"$outfile" 2>$errfile

不管是md5输出、crc输出还是rawvideo输出,都会被重定向到outfile文件中。然后,outfile文件内容和参考文件内容比较,得出测试是pass还是fail的结论。

继续用前面的bash -x的方法来执行fate-run.sh,继续看eval之后的命令行输出,我们可以看到md5和crc的参考文件的名字和所在的目录。一般来说,参考文件被保存在目录/tmp/ffmpeg_work/ffmpeg/tests/ref/fate/下。而对于rawvideo输出,情况略有不同。

有了crc和md5后,为什么还要rawvideo?目前我的理解是当处理过程中涉及到了float运算,或者frame的格式就是float相关的,那么,由于不同架构CPU的输出结果在浮点数操作上会略有不同,而FFmpeg是跨平台支持诸如X86、ARM、PowerPC等多种CPU的,我们就无法用crc或者md5这种bit-exact的方法进行比较了,只能采用基于rawvideo的方式进行比较,并且允许存在一定的偏差。在FATE中,可以用oneoff的方法进行rawvideo的比较,在filter-video.mak文件中差不多可以这样写:

FATE_FILTER_SAMPLES-yes += fate-filter-abc
fate-filter-abc: CMD = ffmpeg -i $(SRC) -frames:v 1 -vf ... -f rawvideo -
fate-filter-abc: CMP = oneoff

#指出参考文件所在的目录和文件名,
#需要将此参考文件发到samples-request@ffmpeg.org并申请放在相应目录下
fate-filter-abc: REF = $(SAMPLES)/filter-reference/....raw

#假如frame格式是float,这里就要指出这是f32的比较
fate-filter-abc: CMP_UNIT=f32

#在比较时允许存在的偏差
fate-filter-abc: FUZZ = 1

在fate-run.sh中,oneoff最终会调用tiny_psnr,如下所示。具体不再展开,大致意思就是根据filter-video.mak文件中的参数设置,对当前测试得到的rawvideo文件和参考文件做个比较,判断两者是否相同。

do_tiny_psnr(){
    psnr=$(tests/tiny_psnr${HOSTEXECSUF} "$1" "$2" $cmp_unit $cmp_shift 0) || return 1
    val=$(expr "$psnr" : ".*$3: *\([0-9.]*\)")
    size1=$(expr "$psnr" : '.*bytes: *\([0-9]*\)')
    size2=$(expr "$psnr" : '.*bytes:[ 0-9]*/ *\([0-9]*\)')
    val_cmp=$(compare $val $cmp_target $fuzz)
    size_cmp=$(compare $size1 $size2 $size_tolerance)
    if [ "$val_cmp" != 0 ] || [ "$size_cmp" != 0 ]; then
        echo "$psnr"
        if [ "$val_cmp" != 0 ]; then
            echo "$3: |$val - $cmp_target| >= $fuzz"
        fi
        if [ "$size_cmp" != 0 ]; then
            echo "size: |$size1 - $size2| >= $size_tolerance"
        fi
        return 1
    fi
}

oneoff(){
    do_tiny_psnr "$1" "$2" MAXDIFF
}

经过上述探索过程后,知其然亦知其所以然,再来写一个video filter的fate测试,就得心应手了。

以上内容是本人业余时间兴趣之作,限于水平,差错难免,仅代表个人观点,和本人任职公司无关。

本文由博客一文多发平台 OpenWrite 发布!

上一篇下一篇

猜你喜欢

热点阅读