第三方数据延迟不让出款 深度好文 | Android高性能音频解析
1.引言
1.1目的和对象
该文档用于音频流畅性,聚焦优化音频卡顿、杂音问题。适用于音频开发人员查看。
1.2背景
游戏、k歌、直播等一些使用场景,音频对时延有较高的要求,保障低延迟,就需要更小的,降低整个链路数据传递时延,但是抗性能抖动能力就会下降。如果音频线程CPU调度延迟,生产数据不及时,系统就会错过周期,产生补0噪声。
一方面,音频系统框架对于音频性能策略设计偏保守,没有提供类似IOS,系统那样的一套API 用于标记音频线程,系统也就不知道APP的线程哪些是音频线程,哪些不是。无法对APP音频线程在CPU资源上做一些适度地倾斜。
另一方面,生态控制力较弱,多数APP在音频逻辑的实现过程中,忽略了CPU调度因素,没有按照的建议设置音频线程的优先级,也就是默认的优先级(Nice 120)。在整机CPU重载场景下,APP内部音频关联线程,特别是解码器线程CPU调度延迟,导致数据生产不及时,触发声音卡顿、杂音问题,这样的问题越来越突出。
一些APP在实现过程中,在关键的播放线程中,调用耗时接口,阻塞了数据传递,导致了声音卡顿。
还有一些应用面在主线程/UI渲染线程中,调用Audio的耗时接口,导致游戏画面渲染帧率降低。
与APP开发伙伴交流过程中,发现APP开发者和系统开发者,存在一些理解上的GAP,APP开发者对音频系统不熟悉,而系统开发者对APP内部业务逻辑不熟悉。遇到问题debug起来,耗时较长,不利于提升用户体验。音频流畅性体验是基础体验的一个核心,也是一个痛点。本文聚焦音频流畅性分析,希望能有助于音频APP开发者和音频系统开发者联合调优,提升用户音频基础体验。
1.3音频格式
人耳的听觉范围是在20Hz到20KHz之间的频段,不同于人眼的“暂留”生理特性,人耳鼓膜的灵敏度远高于人眼。两耳间距产生的微秒级时延(700us)带来的细微声压、声相变化,可辨别远处的音源方位。普通人可清晰辨识低至10db的响度变化,经过专业训练的“金耳朵”,可识别更低db的响度变化,人耳对时延敏感,对声音卡顿敏感。这也是音频采样率(48000 fps)远高于画面的帧率(60 fps)的原因。通过大幅增加音频流采样次数,使得响度曲线变化更平滑,避免出现邻帧之间有较大的db变化,避免听觉上不适感。
图1.1 室内5.1声道声场还原
表1.1 PCM编码类型
16bit,2ch 音乐,它的音频帧大小为:2 * 2 = 4 字节。
32bit,5.1ch 音乐,它的音频帧大小为:4 * 6 = 24 字节。
最常见采样率:
通话:8k,16k, 32k
视频及游戏:24k, 44.1k,48k
高清音乐:96k, 192k
图1.2 采样与量化
音频需要足够高的采样率,使得数据更平滑。
计算公式:bRate = 采样率 * 位宽 * 声道数
例如 48k,7.1声道,16bit pcm编码 码率为:48000 * 8 * 2 = Bps
2.常见杂音类型
杂音是主观体验的概念,技术上常称为音频卡顿,“闻香可以识女人,看波形也能知音”,不同原因,有不同的杂音波形特征。例如写0,断点,重复数据,削顶,截断,高频/低频截止,白噪声,无规律断点数据。
2.1pop音
常常称为“破音”,是属于断音的一类。数据不连续,有明显跳变发生。听起来,“啪!啪!啪!”的破音,耳朵有不适感。常见的有seek pop noise,在快进,快退,或者倍速播放时,更容易遇到这类杂音。
2.2 补0
特征是,删除重点的0数据,数据就是连续的了,因为有连续两次跳变,听起来杂音特征比pop音更明显一些。
删除0后:
2.3截断音
属于断音的一种,与pop音的区别是,启播的第一帧,没有做淡入,或者停播的最后一帧,没有做淡出。
常见场景例如刷短视频,切换视频之间,有时候会听到“啪!啪!啪!”的破音。简单来说就是,需要做淡入淡出。
2.3.1切换截断音
2.3.2起始截断音
开始播放阶段,没有做淡入(Fade In),产生的杂音。
2.3.3 结尾截断音
2.4 削顶
削顶,又称为“削波”,原因是音频信号的响度,超过了编码范围,即0db,并不是说音源存在问题,只是音源信号,幅度太大,超过手机音频系统的表达能力,无法进行还原。超出的部分,被统一削减到0db。听起来,声音有连续卡顿的感觉,不自然。
2.5 蜂鸣音
听感起来,像蜂鸣器,汽笛声。波形上,4ms重复数据,常见于游戏声音卡顿。
2.6 啸叫
2.6.1 什么是啸叫?
啸叫现象是指音频信号通过扬声器播放后,经过一定的传播路径,再次被麦克风拾取,经过放大器的处理后,最后经由扬声器播放,倘若在“扬声器-麦克风-扬声器”的闭环电路中,存在某种正反馈导致某些音频频率发生自激振荡,就会产生啸叫现象。
常见于VOIP会议,通话双方,如果距离太近,就容易产生啸叫。听起来,响度非常大,声音尖锐,感官非常难受。啸叫的产生会掩盖正常语音,给人的听感也不好,而且啸叫频点能量很高,严重时甚至能破坏会议中的扩声设备,因此我们需要对啸叫进行抑制。受限于尺寸,和算法功耗,手机侧啸叫抑制能力有限。
从波形上,可以看到,响度非常大。
2.6.2 啸叫的原理
正反馈系统,信号一次又一次的被循环放大。
2.7 环境底噪
主要是指mic录到的环境噪声,经过降噪算法后,通常比较轻微,不仔细几乎听不出来。但是如果降噪算法调音的不均衡,可能导致底噪大,或者音质损失。
2.8 其他无规律噪声
白噪声。
3. 音频通路基础
3.1 播放流程拆解
对系统来说,APP是黑盒子,不同的应用有不同的逻辑实现方式,特别是游戏,可能有更复杂的业务逻辑,多达100+个线程,系统不知道那个是音频播放相关的。音频能识别到的只有/调用入口,涉及到最多两个线程。以赖线程之间的唤醒关系推测,梳理大概的流程如下:
音乐、视频类三方APP:
游戏音频流程:
3.2 音频通路
3.2.1 音频播放链路
3.2.2 音频通路
限于篇幅,这里不对每个通路进行展开介绍,可自行阅读代码和官方文档。
3.2.3 通路
是基于mmap思路构建的,音频通路。尽量减少数据传递环节,以降低负载和功耗,同时降低层层传递带来的时延。这个通路是强烈推荐使用的API,可替代 ES,支持音频低延迟。也可以通过设置参数,走到低功耗通路,例如deep 通路。
以下以低延迟播放实例来分析工作原理,从上看,并没有经过 write数据,可以看到的线程并没有被唤醒产生负载。可以证明:是通过共享内存方式来传递数据,这种方式非常高效,避免了数据层层Copy。
这里简单介绍一下性能相关的内容:
流程图如下:
设置性能模式:
每个 都具有性能模式,而这对应用行为的影响很大。共有三种模式:
可以通过调用 ()来选择性能模式,并通过调用 ()来获得当前模式。
在当前版本的 中,为了尽量减少延迟时间,必须将 性能模式与高优先级回调配合使用。请参阅以下示例:
关于的详细介绍,请移步 SPEC,这里不再详细展开:
3.2.4 Low 通路
也就是常说的Fast通路,有的平台和通路放在一起,有的是独立的Low 通路,这样实现依赖平台的性能,特别是ADSP性能,都有各自的优缺点。
的 通常设置为4-5ms,通路时延较低,但更容易受到整机性能的影响,产生性能杂音。
的使用,通常是通过 ES,或者Sound pool API。
3.2.5 ULL 通路
极致低延迟通路ULL实质上,为通路的进一步压缩,甚至压缩到1ms,非实时系统,因此CPU调度上,无法保障1ms内得到调度,因此,这个通路,很少有开放使用。如果追求极致低延迟,推荐使用来实现。
4. 音频与CPU调度
4.1 TASK运行状态机
4.2 调度器
4.2.1 调度器分类
Stop调度器,
:优先级最高的调度类,可以抢占其他所有进程,不能被其他进程抢占;
调度器, :使用红黑树,把进程按照绝对截止期限进行排序,选择最小进程进行调度运行;
RT调度器, :实时调度器,为每个优先级维护一个队列;
CFS调度器, :完全公平调度器,采用完全公平调度算法,引入虚拟运行时间概念;
IDLE-Task调度器, :空闲调度器,每个CPU都会有一个idle线程,当没有其他进程可以调度时,调度运行idle线程;
4.2.2 调度优先级策略
linux内核的三种调度方法:1, 分时调度策略,2,实时调度策略,先到先服务3,实时调度策略,时间片轮转
实时线程将得到优先调用,实时进程根据实时优先级决定调度权值,分时进程则通过nice和值决定权值,nice越小,越大,被调度的概率越大,也就是曾经使用了cpu最少的进程将会得到优先调度。
4.2.3 CFS调度类
cfs是绝对公平调度算法,理想情况下,优先级相同的两个task,运行时间应该各占cpu的50%,同理3个则cpu利用率为1/3。但是cfs中弱化了优先级的概念而是使用权重来决定任务的运行时间。
例如:3个任务A,B,C权重()分别1,2,3;则总权重,一个调度周期为6单位时间,理想状态下,A应占用1单位,B为2,C为3。
cfs中使用虚拟时间来决定运行的task,nice值-20到20 --> -->; cfs中使用来管理调度实体se。每次选取最小的task进行执行。在rb tree中最小的se在的最左侧。
cfs是通过限制当前task的运行时间来实现公平的,task的单调递增,它在中向右移动,让出cpu使用权给更小的task。
Task数据结构
4.2.4 RT 调度类
实现调度分为RR和FIFO两类
和的不同:
当采用策略的进程的时间片用完,系统将重新分配时间片,并置于就绪队列尾。放在队列尾保证了所有具有相同优先级的RR任务的调度公平。
一旦占用cpu则一直运行。一直运行直到有更高优先级任务到达或自己放弃。
如果有相同优先级的实时进程(根据优先级计算的调度权值是一样的)已经准备好,FIFO时必须等待该进程主动放弃后才可以运行这个优先级相同的任务。而RR可以让每个任务都执行一段时间。
相同点:
RR和FIFO都只用于实时任务。
创建时优先级大于0(1-99)。
按照可抢占优先级调度算法进行。
就绪态的实时任务立即抢占非实时任务。
rt 调度类数据结构
4.3 与
平台的进程会根据运行状态,在不同的进行迁移,不同设置的不同,导致后台进程的定时器超时唤醒变长。会触发推组,切后台,会从top group 推组到组,相应的会从0.5ms 提高到40ms。如果播放器,实现上没有使用,那么就可能会遇到应用切换到后台播放音频卡顿。
4.3.1 基本原理
以下简单介绍一下基本原理:
4.3.2 音频播放录制请使用
在定义中,一直处于,因此不会有切后台变大,导致的卡音问题。因此,APP实现中,特别注意,音频解码器,播放关联线程,需要放到中。避开前后台切换,迁移的问题。
查看app的方法:/proc/pid/
例如,pid为11871:查看为:0.5ms
当按home按键,这项数值就会变成
4.4 音频优先级
AOSP定义上,音频定义为CFS最高优先级Nice。
默认优先级为120, 数量越小,优先级越高。例如最高的 为 120 -19 =101, 对应cfs线程级别的101。
总体优先级为:Audio > Video > UI
5. 性能卡音案例
有了以上的音频性能基础,分析起问题来,就不那么困难了。
分析音频卡顿,最有效的还是,以下介绍几个案例:
5.1 APP播放线程调度延迟
通过我们可以看到,APP的为音频关键线程,出现了调度上的延迟,我们可以看到问题点,缓存已经消化空了,CPU已经满载,但是有明显的压频行为。因此最终原因为:
APP的线程优先级设置不合理,默认nice的120,没有按着的标准来设置,至少设置为104,必要时设置为101。如果nice设置的过低,CPU满载时,就容易抢不到cpu。
负载太高,触发温升限频。
5.2 游戏音频线程调度延迟
5.2.1 audio dump发现蜂鸣杂音
在track阶段就dump到了杂音,可以确定问题发生在app内部。
持续4ms重复数据
5.2.2 发现游戏音频线程调度延迟
5.2.3 根因:优先级设置太低
使用 ES发起了播放,实时性要求较高,当前优先是默认的120,优先级非常低。推荐APP参考的建议来将线程设置为nice 101即 级别。
5.3 音频系统线程调度
音频优先级设置偏保守,IOS,系统调度上,对音频有明显的倾斜。在加上链路太长,多个进程周转数据,所以音频流畅性体验,要低于IOS和。
5.4 APP在播放线程调用音频耗时接口
最常见的接口是(), 这个接口在系统没有切换时候的时候,会很快返回,但是如果再插拔耳机,或者接听,挂断电话/VOIP电话时,就会耗时很长,导致线程,长时间阻塞,不产生数据。而 ,一遍又一遍的吧,4ms的重复过来,导致了蜂鸣杂音的产生。类似的耗时API还有,(),()。
从上看,被阻塞,根据唤醒关系结合,可以定位到,此时正在做接口调用。由于lock被切换设备的线程持有,而切换设备又比较耗时,从而导致,被卡住,无法生产数据。Audio系统到了重复的杂音数据。
误区:接口可以立刻返回。事实上,内部有复杂的逻辑,例如切换设备,有很多业务逻辑要处理。一个简单的场景,voip来电,这时候,需要和蓝牙耳机建立链路,握手过程,可能就需要150-300ms,整个过程非常耗时。如果这时候在主线程去调用,,等接口,都会被阻塞掉,直到耳机返回,或者连接超时。
5.5 渲染线程调用导致帧率低
例如这份日志里,在渲染线程中,去执行.start(),结果阻塞580ms,导致画面卡顿。应尽量避免在UI线程,图形线程中,调用接口。
5.6 设置过大过小
的作用是控制APP起播的起播缓存大小,设置越大,时延越高,越不容易卡音。设置越小,时延越低,越容易卡音。
5.6.1 短促音设置过大导致无声
5.6.2 设置过小卡音
常见的问题是使用 ES播放,设置的太小,缓存提效,当cpu调度不及时,就容易触发性能卡音。
5.7 频繁调用Audio接口,导致整机重启
系统只提供了31个线程池,也就是系统最大并发数为31个,如果APP频繁调用Audio耗时接口,被占用,甚至被耗尽,导致整机重启。以下某APP,持续不停的去查音量,导致系统资源被耗尽,最终触发重启。
线程池设置为31个
6.音频联合调优建议
6.1规范设置音频优先级
APP 需要参考AOSP标准,将音频数据关联线程,例如解码器,至少nice设置为104级别。
6.2 高实时的线程,避免调用耗时的音频API
尽量不要在音频解码器,以及主线程中调用音频耗时接口,例如(),等等接口,这些接口大都会持锁,导致切换设备时,耗时较长。
6.3播放逻辑进
尽量将音频播放逻辑,放在中,避免切后台,被改大,CPU被限频限核,导致的声音卡顿。
6.4 非必要避免频繁调用耗时的Audio接口
例如查询音量,音频设备连接状态。
7. 参考文献
[1]#
[2]
[3]
[4]
高通camx入门学习 | camx初认识
学习完入门课程视频,可以去找工作了?
Hal|如何学习一个新平台
学习路线 | 个人推荐
一篇文章带你了解 最新框架
独家 | 面试流程、经验分享
《 开发入门》视频课程、《Camx入门学习》已经上架了,可以加我微信咨询,目前针对星球成员免费开放,也欢迎加入“小驰成长圈”星球
觉得不错,点个赞呗