欢迎大家前往 腾讯云+社区 ,获取更多腾讯海量技术实践干货哦~
一、问题背景与分析
不久前,团队发现其Android平台App在播放MV视频《凤凰花开的路口》时,会带有如电流声一般的杂音,这影响了用户体验。 研发同学在初步定位时,发现有如下特征:
- Android平台杂音问题必现;
- iOS、PC平台能正常播放,没有噪音。
然而,各平台都是统一用HLS格式播放,即源头都是一样的。对于该问题,我们的定位思路如下:
- 梳理视频播放流程;
- 找到切入点排查。
二、播放流程概览
分析播放流程如上图(图中内容从左往右),概括其关键步骤如下:
-
播放器初始化:
-
创建读数据线程:
read_thread
; -
创建存放audio解码前数据的队列:
audioq
; -
创建存放audio解码后数据的队列:
sampq
。
-
创建读数据线程:
-
数据读取:
- ①创建context;
-
②探测协议类型:
avformat_open_input
; -
③探测媒体类型:
avformat_find_stream_info
; -
④获取音视频流:
av_find_best_stream
; -
⑤打开媒体解码器:
stream_component_open
; -
⑥读取媒体数据,获得AVPacket:
av_read_frame(ic, pkt)
; -
⑦音视频数据分别送入
audioq
中; - 重复⑥、⑦步骤到数据完毕。
-
音频解码:
-
在
audio_thread
中对audioq
中的数据进行decoder_decode_frame
解码; -
解码后的帧
AVFrame
存放到sampq
中;
-
在
-
音频播放:
-
aout_thread_n
中,通过调用回调接口sdl_audio_callback
,对sampq
中的音频帧数据进行解码成PCM数据; -
写入PCM数据到buffer数组,并由
AudioTrack
播放。
-
三、问题分解与切入
在梳理出播放流程后,标记出找到有可能出错的环节,方便进行“分层定位”(图中黄色标记)
- 播放下载文件是否有问题;
- 数据读取是否有问题;
- 音频解码逻辑是否有问题;
-
AudioTrack
的设置是否有问题;
接下来,根据难易程度,对上述环节逐个验证。
1、播放下载文件是否正常
把Android平台播放的ts文件与各平台的进行比对,发现两者一样,该环节正常。
2、AudioTrack设置是否正常
通过日志检查
AudioTrack
以下配置参数:
- 采样率
- 位深
- 频道
以上参数设置的值与音频流的相符合,该环节正常。
3、音频解码逻辑是否有问题
验证解码逻辑是否有问题,可以通过对PCM数据进行分析来确认。 对
aout_thread_n
进行修改,将PCM数据额外输出到本地,并与正常的PCM数据进行对比。
正常PCM数据频谱图:
异常PCM数据频谱图:
正常PCM数据波形图:
异常PCM数据波形图:
对比分析可得出:
- 从频谱图中看出,异常的PCM在人耳十分敏感的频响(1000~8000Hz )区域内的音频数据严重缺失,导致“杂音问题”
- 从波形图中看出,异常的与正常的无声区和有声区都吻合,若解封装、解码逻辑出现异常,极大几率是呈现无波动(一条直线的形式)情况。因此可以先大胆 假设解码、解封装逻辑是符合预期的
若解码逻辑正常,再结合之前已经验证文件下载正常。可以 推测是数据读取环节出现异常 。
4、数据读取是否有问题
通过对数据读取的各步骤增加日志后,发现在
av_find_best_stream
音频流选择时出现异常:
ffmpeg -i
发现,该视频ts分片有2个音频流
通过强制分别读取两条音频流数据播放,发现:
- 第一条正常播放(PCM数据正常)
- 第二条播放杂音(PCM数据异常)
- Android平台选择了第二条进行播放
基于此,也就验证了在第3步中的假设是正确的。
由上分析,可以得出结论: Android平台选择了第二条数据有问题的流进行播放。
四、问题根源:音频流选择
1、选择方式
分析代码,大致如下所列,
av_find_best_stream
函数选择音频流,该函数会根据2个主要参数进行选择:
-
各音频流的在探测媒体类型(
avformat_find_stream_info
)时, 额外解码出来的帧数 (选择多的) - 各音频流的 比特率 (选择高的)
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 |
第二条流的解码帧数和比特率要比第一条高 ,因此选择了第二条流播放
2、对比同类方案
分析了以上选择规则后,我们对各平台、框架进行了选择规则的对比: