QQ音乐Android端播放MV视频《凤凰花开的路口》时带有如电流声一般的杂音,影响用户的正常体验。
在初步定位中,发现有如下特征:
对于该问题,定位思路如下:
结合上图,总结关键步骤(图中内容从左往右,以音频解码播放为例):
stream_open
主要创建读数据线程read_thread
audioq
sampq
audio_thread
中对audioq
中的数据进行decoder_decode_frame
解码AVFrame
存放到sampq
中在梳理出ijkplayer播放流程后,标记出找到有可能出错的环节,方便进行“分层定位”(图中黄色标记)
以上环节,根据难易程度逐个验证。
把Android端播放的ts文件与各端的进行比对,发现两者一样,该环节正常
通过日志检查AudioTrack以下配置参数:
以上参数设置的值与音频流的相符合,该环节正常
验证解码逻辑是否有问题,可以通过对PCM数据进行分析来确认。
对Ijkplayer源码中aout_thread_n
进行修改,将PCM数据额外输出到本地,并与正常的PCM数据进行对比。
正常PCM数据频谱图:
异常PCM数据频谱图:
正常PCM数据波形图:
异常PCM数据波形图:
若解码逻辑正常,再结合之前已经验证文件下载正常。可以推测是数据读取环节出现异常。
通过对数据读取的各步骤增加日志后,发现在av_find_best_stream
音频流选择时出现异常:
ffmpeg -i
发现,该视频ts分片有2个音频流
通过强制分别读取两条音频流数据播放,发现:
(通过查看2条流的PCM数据,也验证了在第3步中的假设是正确的。)
由上得出结论:Android端选择了第二条数据有问题的流进行播放。
在Android使用FFmpeg中的av_find_best_stream
来选择音频流。
该函数会根据2个主要参数进行选择:
avformat_find_stream_info
)时,额外解码出来的帧数(选择多的)//url:https://ffmpeg.org/doxygen/2.8/libavformat_2utils_8c_source.html
//line:3572
//仅保留相关代码
int av_find_best_stream(AVFormatContext *ic, enum AVMediaType type,
int wanted_stream_nb, int related_stream,
AVCodec **decoder_ret, int flags)
{
for (i = 0; i < nb_streams; i++) {
count = st->codec_info_nb_frames; //音频流探测中解码的帧数
bitrate = avctx->bit_rate;//音频流的比特率
multiframe = FFMIN(5, count);
//先比较解码帧数,再比较音频流比特率,谁大谁选
if ((best_multiframe > multiframe) ||
(best_multiframe == multiframe && best_bitrate > bitrate) ||
(best_multiframe == multiframe && best_bitrate == bitrate && best_count >= count))
continue;
best_count = count;
best_bitrate = bitrate;
best_multiframe = multiframe;
ret = real_stream_index;//最后选择的流index
best_decoder = decoder;
}
return ret;
}
在该视频中,
codec_info_nb_frames | bit_rate | |
---|---|---|
audio_stream 1 | 38 | 122625 |
audio_stream 2 | 39 | 126375 |
第二条流的解码帧数和比特率要比第一条高,因此FFmpeg选择了第二条流播放
分析了以上选择规则后,我们对各平台、框架进行了选择规则的对比
备注:
ffmpeg -i INPUT_FILE -map 0:0 -map 0:2 -map 0:1 -c copy -y OUTPUT_FILE
ffmpeg -i INPUT_FILE_1 -i INPUT_FILE_2 -map 0:0 -map 0:1 -map 0:2 -map 1:0 -c copy OUTPUT_FILE
从以上数据看到,iOS和PC端会默认选择第一条流,而在Android端的FFmpeg和ExoPlayer会根据音频流属性来选择数值更好的一条。
但以上2个选择方案都无法识别“内容异常”的音频流。
因此处理该问题,需要从音源上进行修复和规避。
以下是解决方案: