FFMS2读取多媒体文件中的声音的Bug

FFMS2是AviSynth的一个来源插件,以FFMPEG为支撑,能读入各种各样的多媒体文件。但是今天在使用它的时候,遇到了Bug。

这个Bug的具体表现是,如果我有一个MP4视频文件,我用某些切割工具对文件进行过切割,因为切割的时候不一定是在关键帧处切割的,而切割过程中又没有重新进行编码,有的工具在这样切割之后生成的文件,音频和视频的第一帧(关键帧)的时间戳都是小于0。小于一点点貌似没事,我遇到Bug的时候,这个时间戳我记得是负的4秒多。

对于这样的文件,我再用FFMS2在AviSynth里载入(记得要包含声音),载入之后用类似 last+last 这样的语句把它们连接起来。这样的AviSynth脚本送到播放器里面播放的话,到了两个片段的连接处,就会发现播放器死了。不是概率性出现而是每次必现。

我从GitHub下载了FFMS2的源代码(https://github.com/FFMS/ffms2),在MSYS2环境中执行它那个 build-win-deps.sh 下载 ffmpeg 和编译(因为opencore-amr我用不到,为了节省时间,我就把它注释和屏蔽掉了)。之后用 VisualStudio 打开它那个工程,编译了FFMS2.dll。不论是Debug版本还是Release版本,自己编译的也还是会出现同样的问题。

但是因为是自己编译的,所以用 Debug 版本调试起来就很方便。在卡死的时候中断程序运行,会发现卡死的代码在 audiosource.cpp 的 FFMS_AudioSource::GetAudio 函数里。它不断在一个循环里打转,也不进什么函数,也不出去。很是郁闷。

我重新运行了程序,试图在它将要变成这个样子的临界点用条件断点(判断 Start 变量的值)断了下来。跟踪跟进去,发现和正常的时候相比,此时有个地方不太一样:它在正常的时候,是在数据不够的时候进 DecodeNextBlock 函数,然后 avcodec_decode_audio4 解码一个包,把解码结果 cache 起来。但是在我刚才所说的情况中,因为进了下一个片段,它会先 Seek 当前进度到文件开头,然后再调用 DecodeNextBlock。我观察到的结果是,它还没 Seek 的时候,avcodec_decode_audio4 函数运行的都没什么问题;但是它对这样的文件进行 Seek 之后,读取出来的 Packet 就再也没办法在 avcodec_decode_audio4 里解码后,通过 GotFrame 参数返回 1 了。

也就是说,经过这样跟踪下来,可以认为问题出在 FFMPEG 上,或者调用 FFMPEG 的方式有问题。

我是先考虑了 FFMPEG 可能有问题。因为 build-win-deps.sh 脚本是直接拉的 FFMPEG (git://source.ffmpeg.org/ffmpeg.git) 的 master 分支的代码。我尝试把它替换成去链接官网下载的带正式版本号的 FFMPEG (http://ffmpeg.zeranoe.com/builds/) 的 DLL。具体方法是 build-win-deps.sh 脚本执行完成之后会产生一个 deps 文件夹,在里面可以找到一堆 libav*********.a 这样的文件。因为编译器用的是VC,所以这些 .a 文件其实是 .lib 文件。把它换成官网下的 FFMPEG 的 dev 压缩包里解压出来的文件名差不多的 lib 文件,然后再编译FFMS2工程,能得到一个比较小的 FFMS2.DLL (因为是动态链接到 FFMPEG)。配上 FFMPEG 官网下的 shared 那个压缩包里解压出来的 DLL 文件进行测试,会卡死的问题消失了。因此我判断是 FFMS2 拉的 FFMPEG 的 master 分支可能有什么问题,或者静态链接情况下会有什么问题、在动态链接的时候会消失的。

这样编译出来的 FFMS2 还有一个问题,它在 FFMS2_Init 函数里面,会对 FFMPEG 设置几个回调函数去拦截日志输出。如果用动态链接版的 FFMPEG 对 Avisynth 脚本进行转码,此时 FFMS2 和 FFMPEG.EXE 会用的同一套 FFMPEG 的DLL文件(AV****.DLL 那些)。这样在 Avisynth 加载 FFMS2 插件的时候,这个回调函数的设置会洗掉 FFMPEG.EXE 设置的回调,导致你转码过程中看不到任何进度信息。因为大概看了一下,FFMS2 也并没有想把 FFMPEG 的日志拿来干嘛用,所以我就直接把 FFMS2_Init 里几个注册日志回调的代码给注释掉了。再重新构建 FFMS2.DLL,然后拿一个用了 FFMS2 的 Avisynth 脚本去用 FFMPEG.EXE 转码,没有问题了。

发表评论