一、问题背景与分析
不久前,团队发现其Android平台App在播放MV视频《凤凰花开的路口》时,会带有如电流声一般的杂音,这影响了用户体验。 研发同学在初步定位时,发现有如下特征:
- Android平台杂音问题必现;
- iOS、PC平台能正常播放,没有噪音。
然而,各平台都是统一用HLS格式播放,即源头都是一样的。对于该问题,我们的定位思路如下:
- 梳理视频播放流程;
- 找到切入点排查。
二、播放流程概览
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YsNtgApK-1629987344477)(https://user-gold-cdn.xitu.io/2018/10/10/1665d7bc380cffae?imageView2/0/w/1280/h/960/ignore-error/1)]
分析播放流程如上图(图中内容从左往右),概括其关键步骤如下:
- 播放器初始化:
- 创建读数据线程:
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数据频谱图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PjDhOxUm-1629987344484)(https://user-gold-cdn.xitu.io/2018/10/10/1665d7bc37f6ba03?imageView2/0/w/1280/h/960/ignore-error/1)]
异常PCM数据频谱图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WHz15F2U-1629987344486)(https://user-gold-cdn.xitu.io/2018/10/10/1665d7bc384dd90c?imageView2/0/w/1280/h/960/ignore-error/1)]
正常PCM数据波形图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oASAGUBA-1629987344489)(https://user-gold-cdn.xitu.io/2018/10/10/1665d7bc383573a3?imageView2/0/w/1280/h/960/ignore-error/1)]
异常PCM数据波形图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VPUmls8n-1629987344491)(https://user-gold-cdn.xitu.io/2018/10/10/1665d7bc384d7603?imageView2/0/w/1280/h/960/ignore-error/1)]
对比分析可得出:
- 从频谱图中看出,异常的PCM在人耳十分敏感的频响(1000~8000Hz )区域内的音频数据严重缺失,导致“杂音问题”
- 从波形图中看出,异常的与正常的无声区和有声区都吻合,若解封装、解码逻辑出现异常,极大几率是呈现无波动(一条直线的形式)情况。因此可以先大胆假设解码、解封装逻辑是符合预期的
若解码逻辑正常,再结合之前已经验证文件下载正常。可以推测是数据读取环节出现异常。
4、数据读取是否有问题
通过对数据读取的各步骤增加日志后,发现在av_find_best_stream音频流选择时出现异常: ffmpeg -i 发现,该视频ts分片有2个音频流
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ukKPyG6U-1629987344493)(https://user-gold-cdn.xitu.io/2018/10/10/1665d7bc3a693395?imageView2/0/w/1280/h/960/ignore-error/1)]
通过强制分别读取两条音频流数据播放,发现:
- 第一条正常播放(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、对比同类方案
分析了以上选择规则后,我们对各平台、框架进行了选择规则的对比:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gUUDYZLg-1629987344494)(https://user-gold-cdn.xitu.io/2018/10/10/1665d7bd34449fd1?imageView2/0/w/1280/h/960/ignore-error/1)]
备注:
- ExoPlayer对多音频流的ts分片支持不完善(issue),因此测试时需要调整相关接口。但选择规则依然以上述所示(DefaultTrackSelector)
- iOS和PC平台采用闭源组件,因此测试时使用了**“互换两条音频流顺序”**的方法进行测试。互换后,两平台都播放了杂音音频流
ffmpeg -i INPUT_FILE -map 0:0 -map 0:2 -map 0:1 -c copy -y OUTPUT_FILE - QuickTime同样是闭源,互换音频流后无法明显差别,通过合成第三条音频流,来验证是它是对所有音频流全播放
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
3、总结
从以上数据看到,iOS和PC平台会默认选择第一条流,而在Android平台的FFmpeg和ExoPlayer会根据音频流属性来选择数值更好的一条。
- “默认选择第一条”方案能更容易地把音源问题暴露。
- “比较音频流属性”方案能更大几率地选择质量更好的流来提升用户体验。
但以上2个选择方案都无法识别“内容异常”的音频流。
五、问题解决方案
因此,处理该问题,需要从音源上进行修复和规避,我们的建议是从源头杜绝,从终端规避:
- 编辑重新上架正常音源;
- 短期内增加双音频流的检测上报,帮助后台、编辑进行复查;
- 长远看由后台开发工具,分别对存量视频进行双音频流检测和对增量视频保证只转码单音频流;
参考资料
- ffmpeg.org/doxygen/2.8…
- github.com/google/ExoP…
- www.jianshu.com/p/daf0a61cc…
- www.jianshu.com/p/a6a4bf59c…
- km.oa.com/articles/sh…
- codeday.me/bug/2017071…
相关阅读
此文已由作者授权腾讯云+社区发布,更多原文请点击
搜索关注公众号「云加社区」,第一时间获取技术干货,关注后回复1024 送你一份技术课程大礼包!
海量技术实践经验,尽在云加社区!
最后
按照国际惯例,给大家分享一套十分好用的Android进阶资料:《全网最全Android开发笔记》。
整个笔记一共8大模块、729个知识点,3382页,66万字,可以说覆盖了当下Android开发最前沿的技术点,和阿里、腾讯、字节等等大厂面试看重的技术。


因为所包含的内容足够多,所以,这份笔记不仅仅可以用来当学习资料,还可以当工具书用。
如果你需要了解某个知识点,不管是Shift+F 搜索,还是按目录进行检索,都能用最快的速度找到你要的内容。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照整个知识体系编排的。
(一)架构师必备Java基础
1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……

(二)设计思想解读开源框架
1、热修复设计
2、插件化框架设计
3、组件化框架设计
4、图片加载框架
5、网络访问框架设计
6、RXJava响应式编程框架设计
……

(三)360°全方位性能优化
1、设计思想与代码质量优化
2、程序性能优化
- 启动速度与执行效率优化
- 布局检测与优化
- 内存优化
- 耗电优化
- 网络传输与数据储存优化
- APK大小优化
3、开发效率优化
- 分布式版本控制系统Git
- 自动化构建系统Gradle
……

(四)Android框架体系架构
1、高级UI晋升
2、Android内核组件
3、大型项目必备IPC
4、数据持久与序列化
5、Framework内核解析
……

(五)NDK模块开发
1、NDK开发之C/C++入门
2、JNI模块开发
3、Linux编程
4、底层图片处理
5、音视频开发
6、机器学习
……

(六)Flutter学习进阶
1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter Dart语言系统入门
……

(七)微信小程序开发
1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战
……

(八)kotlin从入门到精通
1、准备开始
2、基础
3、类和对象
4、函数和lambda表达式
5、其他
……

好啦,这份资料就给大家介绍到这了,有需要详细文档的小伙伴,可以微信扫下方二维码领取哈~


本文分析了QQ音乐在Android平台上播放MV出现杂音的问题,通过梳理播放流程,排查各个环节,发现是音频流选择错误导致。问题根源在于Android平台选择第二条音频流,而该流存在质量问题。解决方案包括修复音源、增加双音频流检测和后台工具改进。
7545

被折叠的 条评论
为什么被折叠?



