目录
- 简介
- 运行Fate测试
- Fate的Makefile中的一些targets和variables
- 一个Fate测试示例 fate-filter-scale500
参考
- FFmpeg Automated Testing Environment
- 大师兄悟空/如何编写 FFmpeg 自动化测试用例(一)
- 大师兄悟空/如何编写 FFmpeg 自动化测试用例(二)
- 郭叶军/FFmpeg video filter FATE测试过程介绍
1. 简介
官网对FATE的介绍
FATE(FFmpeg Automated Testing Environment): ffmpeg回归测试的套件以及提供了一种在服务器上对测试结果进行聚合和展示的方式。
包含3部分内容:
- 介绍如何在ffmpeg源码目录对ffmpeg的可执行文件进行测试。(建议如果有ffmpeg源码进行修改的话,推荐都进行FATE测试,避免修改在一些不经常使用的平台上出现问题。)
- 介绍如何提交ffmpeg FATE的测试结果到ffmpeg Fate公开的服务器。(比如服务器上没有提到你使用的编译环境,编译环境包括CPU、OS、compiler的一些组合)
- 详尽地介绍了FATE的makefile中的targets和variables。
FFmpeg公开服务器的测试结果:http://fate.ffmpeg.org/
2. 运行Fate测试
2.1 获取测试的完整列表
执行下面的命令
make fate-list
- 如果没有执行过make,这个命令会执行文件编译的过程
会打印下面这些信息:
fate-acodec-adpcm-adx
fate-acodec-adpcm-adx-trellis
fate-acodec-adpcm-argo
fate-acodec-adpcm-ima_alp
fate-acodec-adpcm-ima_apm
fate-acodec-adpcm-ima_qt
fate-acodec-adpcm-ima_qt-trellis
fate-acodec-adpcm-ima_ssi
fate-acodec-adpcm-ima_wav
- commit 9687cae2b468e09e35df4cea92cc2e6a0e6c93b3,2022-04-04 有1616条测试。
2.2 下载Fate测试的一些素材
在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的话,需要时时更新。
2.3. 执行Fate测试
执行完整的Fate测试
make fate SAMPLES=fate-suite/
执行指定的一些Fate测试case,例如
make fate-ffprobe_compact fate-ffprobe_xml
3. Fate的Makefile中的一些targets和variables
3.1 Makefile targets
- fate-rsync:下载/同步sample文件到配置的samples目录。
- fate-list:列出所有的fate/regression测试targes。
- fate:运行Fate测试套件(需要fate-suite的数据集)。
3.2 Makefile variables
- V:信息级别, 取值0、1、2; can be set to 0, 1 or 2.
- 0: 只显示测试的参数
- 1: 只显示测试中使用的命令行
- 2: 显示所有信息
- TARGET_EXEC:指定或重写用于运行测试的wrapper。TARGET_EXEC选项提供了一种valgrind、qemu user或wine中或通过ssh在远程目标上运行FATE wrapper的方法。
- KEEP: 设置为“1”,以在测试成功时保留命运测试生成的临时文件。默认值为“0”,这将删除这些文件。测试失败时,始终保留文件。在tests/data/fate/目录中。
4. 一个Fate测试示例 fate-filter-scale500
$ make V=2 fate-filter-scale500
gcc -I. -I./ -D_ISOC99_SOURCE -D_POSIX_C_SOURCE=200112 -D_XOPEN_SOURCE=600 -D_POSIX_C_SOURCE=200112 -D_XOPEN_SOURCE=600 -std=c99 -Wall -O3 -MMD -MF tests/videogen.d -MT tests/videogen.o -c -o tests/videogen.o tests/videogen.c
gcc -o tests/videogen tests/videogen.o -lm
./tests/videogen 'tests/vsynth1/'
TEST filter-scale500
./tests/fate-run.sh fate-filter-scale500 "" "" "/root/work/" 'video_filter "scale=w=500:h=500"' '' '' '' '1' '' '' '' '' '' '' '' '' '' '1'
/root/work/ffmpeg -nostdin -nostats -noauto_conversion_filters -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 /root/work/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:
fate-filter-scale500的测试用例的定义
//tests/fate/filter-video.mak
FATE_FILTER_VSYNTH-$(CONFIG_SCALE_FILTER) += fate-filter-scale500
fate-filter-scale500: CMD = video_filter "scale=w=500:h=500"
......
FATE_AVCONV-$(call DEMDEC, IMAGE2, PGMYUV) += $(FATE_FILTER_VSYNTH-yes)
//tests/Makefile
include $(SRC_PATH)/tests/fate/filter-video.mak
......
FATE_FFMPEG += $(FATE_FFMPEG-yes) $(FATE_AVCONV) $(FATE_AVCONV-yes)
FATE-$(CONFIG_FFMPEG) += $(FATE_FFMPEG)
......
FATE += $(FATE-yes)
......
$(FATE) $(FATE_TESTS-no): $(FATE_UTILS:%=tests/%$(HOSTEXESUF)) | $(FATE_OUTDIRS)
@echo "TEST $(@:fate-%=%)"
$(Q)$(SRC_PATH)/tests/fate-run.sh $@ "$(TARGET_SAMPLES)" "$(TARGET_EXEC)" "$(TARGET_PATH)" '$(CMD)' '$(CMP)' '$(REF)' '$(FUZZ)' '$(THREADS)' '$(THREAD_TYPE)' '$(CPUFLAGS)' '$(CMP_SHIFT)' '$(CMP_TARGET)' '$(SIZE_TOLERANCE)' '$(CMP_UNIT)' '$(GEN)' '$(HWACCEL)' '$(REPORT)' '$(KEEP)'
为了看fate-run.sh中每条指令的执行过程,我们用bash -x来跑这个脚本,如下所示。
[root@localhost src]# bash -x ./tests/fate-run.sh fate-filter-scale500 "" "" "/root/workspace/ffmpeg" 'video_filter "scale=w=500:h=500"' '' '' '' '1' '' '' '' '' '' '' '' '' '' ''
+ export LC_ALL=C
+ LC_ALL=C
++ dirname ./tests/fate-run.sh
+ base=./tests
+ . ./tests/md5.sh
+++ echo
+++ md5sum -b
++ '[' 'X68b329da9893e34099c7d8ad5cb9c940 *-' '!=' X ']'
+ base64=tests/base64
+ test=filter-scale500
+ target_samples=
+ target_exec=
+ target_path=/root/workspace/ffmpeg
+ command='video_filter "scale=w=500:h=500"'
+ cmp=diff
+ ref=./tests/ref/fate/filter-scale500
+ fuzz=1
+ threads=1
+ thread_type=frame+slice
+ cpuflags=all
+ cmp_shift=0
+ cmp_target=0
+ size_tolerance=0
+ cmp_unit=2
+ gen=no
+ hwaccel=none
+ report_type=standard
+ keep=0
+ outdir=tests/data/fate
+ outfile=tests/data/fate/filter-scale500
+ errfile=tests/data/fate/filter-scale500.err
+ cmpfile=tests/data/fate/filter-scale500.diff
+ repfile=tests/data/fate/filter-scale500.rep
+ FLAGS='-flags +bitexact -sws_flags +accurate_rnd+bitexact -fflags +bitexact'
+ DEC_OPTS='-threads 1 -idct simple -flags +bitexact -sws_flags +accurate_rnd+bitexact -fflags +bitexact'
+ ENC_OPTS='-threads 1 -idct simple -dct fastint'
+ ffmpeg2=' /root/workspace/ffmpeg/ffmpeg'
+ raw_src=/root/workspace/ffmpeg/tests/vsynth1/%02d.pgm
+ pcm_src=/root/workspace/ffmpeg/tests/data/asynth1.sw
+ crcfile=tests/data/filter-scale500.lavf.crc
+ target_crcfile=/root/workspace/tests/data/filter-scale500.lavf.crc
+ '[' 0 -gt 0 ']'
+ echov=:
+ AVCONV_OPTS='-nostdin -nostats -noauto_conversion_filters -y -cpuflags all -filter_threads 1'
+ COMMON_OPTS='-flags +bitexact -idct simple -sws_flags +accurate_rnd+bitexact -fflags +bitexact'
+ DEC_OPTS='-flags +bitexact -idct simple -sws_flags +accurate_rnd+bitexact -fflags +bitexact -threads 1'
+ ENC_OPTS='-flags +bitexact -idct simple -sws_flags +accurate_rnd+bitexact -fflags +bitexact -threads 1 -dct fastint'
+ set -f
+ exec
+ eval video_filter '"scale=w=500:h=500"'
+ err=0
+ '[' 0 -gt 128 ']'
+ test -e ./tests/ref/fate/filter-scale500
+ case $cmp in
+ diff -u -b ./tests/ref/fate/filter-scale500 tests/data/fate/filter-scale500
+ cmperr=0
+ test 0 = 0
+ err=0
+ '[' standard = ignore ']'
+ test 0 = 0
+ '[' 0 -eq 0 ']'
+ test standard = standard
+ unset cmpo erro
+ echo filter-scale500:0::
+ test 0 '!=' 0
+ test 0 = 0
+ test 0 = 0
+ rm -f tests/data/fate/filter-scale500 tests/data/fate/filter-scale500.err tests/data/fate/filter-scale500.diff
+ exit 0
根据上述命令行输出,最终会找到下面的关键脚本:
eval $command >"$outfile" 2>$errfile
还是从上述命令行输出可以看到,eval这行脚本展开后为:
eval video_filter '"scale=w=500:h=500"'
用eval启用的命令,被shell直接当做一条命令处理,无法看到内部的展开情况。作为一个快速方法,我们直接修改脚本中的eval行如下所示。
$ bash -x /root/workspace/ffmpeg/tests/fate-run.sh fate-filter-scale500 "" "" "/tmp/ffmpeg_work/build" 'video_filter "scale=w=500:h=500"' '' '' '' '1' '' '' '' '' '' '' '' '' '' ''
...
+ /root/workspace/ffmpeg/ffmpeg -nostdin -nostats -noauto_conversion_filters -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 /root/workspace/ffmpeg/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
上述命令行输出很长,大部分篇幅是用来设置最终的ffmpeg的命令行参数,最后得到的命令行参数如上所示(两行省略号之间的那一行)。回顾一下,其实,我们前面在执行make V=2 fate-filter-scale500
看到的最后一行输出,和这里看到的是相同的。而倒数第二行的e7d6...这一串字符则是ffmpeg的输出结果。我们也可以直接在命令行执行ffmpeg程序,得到相同的结果。
上述ffmpeg命令行参数的最后是-f nut md5:,表示将结果做md5的输出,输出一个字符串。会与/tests/ref/fate中的值进行对比。
diff -u -b ./tests/ref/fate/filter-scale500 tests/data/fate/filter-scale500
在filter-video.mak 中,变量CMD
- 如果是用framecrc开始的,那么将对每个输入的图片做处理后的最终结果是一个crc值。最后得到的输出是多行字符串。
- 如果是直接用ffmpeg开头,一般来说,最后得到的ffmpeg参数最后会是-f rawvideo -,表示最后输出的不是crc也不是md5,而是rawvideo的内容。
测试结果判断
回到fate-run.sh,再看一下eval行。
eval $command >"$outfile" 2>$errfile
不管是md5输出、crc输出还是rawvideo输出,都会被重定向到outfile文件内容和参考文件内容比较,得出测试是pass还是fail的结论。一般来说,参考文件被保存在目录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
#指出参考文件所在的目录和文件名,
#需要将此参考文件发到[email protected]并申请放在相应目录下
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
}
理解FFmpeg Fate脚本需要一些基础:
- Makefile 基本原理和用法
- bash 脚本基本用法