ffmpeg5.0
版本发布了,使用最新版本的ffplay
工具去播放一个speex编码的音频flv文件,发现音频不正常,
视频呈现快进现象.
问题分析
ffplay
播放没有任何报错,debug日志也没有什么信息.
使用ffmpeg
将音频转码成aac
, 发现也是一样的问题,转码后的媒体文件同样不正常.
这个flv文件应该是没有问题的,因为我在另一个环境下播放过,是完全正常的, 另一个环境ffmpeg版本是4.2.
所以这应该是从4.2到5.0版本引入的问题.
先ffprobe
分析一下这个文件
|
|
分析后发现了大概问题所在了,我这个flv文件的speex编码是20ms的frame_size
, 封装时是6个编码包
聚合为一个音频包,对应libspeexenc
中的frames_per_packet
编码参数.最新版的ffmpeg
解码speex音频
时只解出了1/6的音频samples,看起来像是每个音频包只解码出了第一个音频frame
,120ms的音频包解码后
只剩下了20ms.
git log -p -- libavcodec/libspeexdec.c
查看这个解码器提交历史, 2022年有几个小改动,看起来都没有动过逻辑,再早
的改动是2019年的了,那应该是较老的版本了.
再次确认了一下ffmpeg 4.2
版本,解码这个flv文件确实是完全正常的.
排查到这里,这个问题就显得很奇怪了,难道不是解码器的问题吗?
git bisect找问题
ffmpeg提交众多,现在确认老版本正常,新版本有问题,但是又无法很快的精准找到问题原因,于是我想到了
git bisect
, 这个命令能二分查找哪个版本引入的问题,git bisect
的要求是需要二分查找的中间版本能判定是否问题版本.
好了,这个问题天然适合使用git bisect
来快速查找,首先需要准备一个判定脚本,用来判定当前版本是否问题版本
|
|
脚本先编译当前版本的ffmpeg,然后命令行转码后分析音频数量,这个文件使用正常版本分析出的数量大于330, 异常版本小于这个数字,则返回非0码退出.
启动git bisect
|
|
接下来等它自动二分查找就行了,这个过程时间比较长,因为ffmpeg编译比较耗时,在1000多个提交范围里,大约10次查找,结果就出来了:
|
|
见证真相的时候到了,git bisect
明确指出了哪个提交是第一个坏的提交,看看提交信息,add native Speex decoder
, 恍然大悟,原来有问题的是native speex decoder
, 不是基于libspeex
的那个decoder.难怪之前查找libspeexdec
没有什么信息.
ffmpeg从这个提交把speex
解码器默认切换成了他自己实现的解码器,而不是第三方的libspeexdec
,而这个解码器在处理多frames聚合一包音频的flv文件时是有问题的.
仍然使用libspeexdec试试播放这个flv文件,一切正常
|
|
原因分析
知道了这个问题是ffmpeg的native speex decoder
的问题,来看代码,这个decoder初始化时会去根据extra_data
初始化一些解码参数,但是speex
封装到flv容器中是没有类似aac,h264的sequence header
信息的. 所以初始化的时候使用了一些默认的参数,比如frames_per_packet
就被默认初始化为1.
解码时通过一个循环解码每个音频包,由于这里frames_per_packet
被初始化为1,所以只能解码得出第一个音频frame.这就是问题所在了.
|
|
这个问题根因是因为speex
封装到flv没有提供头信息
表明它的一些解码参数,我去查了flv标准文档,它是支持speex编码的,但是里头并没有明确说明是否应该包含头信息.而ogg格式的封装是有这个头信息的.
那么我的flv源文件是怎么来的呢,也是通过ffmpeg系列编码封装得到的.
|
|
使用这样的ffmpeg命令得到的flv文件就是不含speex头信息的.
那么为什么libspeexdec
这个解码器就能正常工作呢,看看它的代码吧, 它的初始化流程和native speex decoder
基本一样,只是解码过程中,没有依赖与frames_per_packet
的值,而是直接将一个AVPacket的数据送入libspeex解码器接口中,由第三方的libspeex解码器处理解码,最终能够的到正确的解码数据.