28、性能优化:内存管理、CPU占用、电池消耗
移动端做WebRTC,说白了就是「戴着镣铐跳舞」。
你想想看,手机就那么点内存,CPU还得省着用,电池更是金贵。PC上跑得飞快的代码,搬到手机上可能直接闪退、发烫、掉电如流水。我早期做的一个视频通话项目,在iPhone 6上测试,通话5分钟手机能煎鸡蛋——嗯,那滋味真不好受。
今天咱们就聊聊,怎么让WebRTC在移动端跑得又稳又省。
核心原则:移动端性能优化的本质,是在「用户体验」和「资源消耗」之间找平衡。不是一味省电,而是该省的时候省,该用的时候用。
一、内存管理:别让App偷偷「长胖」
移动端内存是硬约束。iOS有jetsam机制,内存超了直接杀进程,连个招呼都不打。Android虽然好一点,但OOM也是家常便饭。
1.1 视频帧缓冲池
WebRTC里视频帧是高频对象。每秒钟30帧,每帧都是YUV数据或纹理。如果每帧都new一个对象,GC会频繁触发,卡顿就来了。
我的做法:用对象池复用VideoFrame。提前分配好N个帧缓冲区,用的时候取,用完归还。我见过一个项目,没做池化,通话10分钟内存涨了80MB——全是帧对象没释放。
// 伪代码:视频帧缓冲池
class VideoFramePool {
private val pool = ArrayDeque<VideoFrame>(MAX_POOL_SIZE)
fun acquire(): VideoFrame {
return pool.removeFirstOrNull() ?: createNewFrame()
}
fun release(frame: VideoFrame) {
if (pool.size < MAX_POOL_SIZE) {
frame.clearData() // 只清数据,不释放内存
pool.addLast(frame)
} else {
frame.release() // 池满了才真正释放
}
}
}
1.2 编码器/解码器实例管理
硬编解码器是稀缺资源。iOS的VTCompressionSession、Android的MediaCodec,创建和销毁都很重。
- 不要频繁创建销毁:切换摄像头时,复用编解码器实例,而不是重建。
- 及时释放:通话结束时,一定要调用release()。我曾经踩过一个坑——Android上MediaCodec没释放,导致后续所有通话都无法硬编。
- 控制并发数:同时最多保留1个编码器+1个解码器。多路通话时,按需激活/休眠。
1.3 音频缓冲区管理
音频数据虽然小,但积少成多。10ms一包,一秒钟100包,如果不控制缓冲区大小,内存也会涨。
注意:音频JitterBuffer默认会缓存200-300ms的数据。如果网络抖动不大,可以调小到100ms,省内存也降低延迟。我一般在项目里加一个自适应策略:网络好时缓存50ms,差时扩大到200ms。
1.4 低内存时的降级策略
App应该监听系统低内存警告。收到警告后:
- 降低视频分辨率(720p → 480p)
- 降低帧率(30fps → 15fps)
- 关闭视频预览(只传不显)
- 释放非关键资源(如历史帧缓存)
经验之谈:iOS上收到didReceiveMemoryWarning后,如果不做降级,大概率在3秒内被kill。Android上onTrimMemory(TRIM_MEMORY_RUNNING_CRITICAL)也是同样的信号。别犹豫,立刻降级。
二、CPU占用:别让手机「发烧」
CPU占用高了,手机发烫,然后降频,然后卡顿,然后用户骂娘。这是个恶性循环。
2.1 分辨率与帧率自适应
别傻乎乎地一直传1080p 30fps。要根据网络和CPU负载动态调整。
// WebRTC内置的CPU自适应开关
PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
// 开启CPU自适应
options.disable_encryption = false; // 保持加密
// 设置自适应参数
mediaConstraints.mandatory.add(
new MediaConstraints.KeyValuePair(
"googCpuOveruseDetection", "true"
)
);
mediaConstraints.mandatory.add(
new MediaConstraints.KeyValuePair(
"googCpuOveruseEncodeUsage", "true"
)
);
WebRTC内部有个CPU过载检测器。它会监控编码耗时和CPU占用率。一旦超标,自动降分辨率或帧率。我个人习惯把这个阈值调低一点——默认是80%,我改成70%,让降级来得更早一些。
2.2 硬件编解码优先
软编软解是CPU杀手。同样是H.264编码,硬编比软编省电60%以上。
| 编码方式 | CPU占用 | 功耗 | 发热 |
|---|---|---|---|
| 硬件编码 | 5-10% | 低 | 微热 |
| 软件编码 | 30-50% | 高 | 烫手 |
避坑指南:Android上硬编兼容性是个大问题。有些国产手机硬编出来的码流花屏。我曾经在某个项目里加了硬编回退逻辑:先尝试硬编,如果前10帧编码失败或质量差,自动切到软编。用户无感知,但稳定多了。
2.3 渲染频率控制
UI渲染也是CPU大户。WebRTC默认每帧都渲染,但很多时候没必要。
- 远端视频:如果对方画面变化不大,可以降到15fps渲染
- 本地预览:可以降到10fps,反正自己看
- 画中画:小窗口用更低帧率
嗯,这里要注意:渲染降频不等于编码降频。编码还是30fps,只是显示的时候跳帧。这样既保证了传输质量,又省了CPU。
2.4 网络线程与UI线程分离
WebRTC默认的网络IO和编解码都在独立线程。但有些开发者图省事,把回调扔到主线程处理。这是大忌。
// 错误做法:在主线程处理视频帧
videoTrack.addSink { frame ->
runOnUiThread {
renderer.renderFrame(frame) // 阻塞主线程!
}
}
// 正确做法:在渲染线程处理
val renderThread = HandlerThread("VideoRender")
renderThread.start()
val renderHandler = Handler(renderThread.looper)
videoTrack.addSink { frame ->
renderHandler.post {
renderer.renderFrame(frame)
}
}
三、电池消耗:让App「省着点花」
电池优化往往被忽视。但用户对掉电速度非常敏感。我见过一个App,开视频通话1小时掉电60%,直接被用户打一星。
3.1 屏幕常亮控制
视频通话时,屏幕常亮是必要的。但别用最高亮度。我建议:
- 通话开始时,获取当前屏幕亮度,降低20%
- 通话结束时,恢复原始亮度
- 如果用户切到后台,立即释放屏幕常亮锁
3.2 网络唤醒策略
WiFi和蜂窝网络的功耗差别很大。WiFi下可以保持较高码率,蜂窝网络下要降码率。
我的经验:监听网络类型变化。切到4G/5G时,自动把视频码率从2Mbps降到1Mbps。切回WiFi时再恢复。别小看这个调整,能省15%的电量。
3.3 传感器采样降频
很多App在视频通话时还开着加速度计、陀螺仪做防抖或旋转。这些传感器很耗电。
- 加速度计:默认100Hz采样,通话时降到20Hz
- 陀螺仪:不用就关掉
- 摄像头:不用前置时,关闭摄像头电源(不只是停止预览)
3.4 音频播放省电模式
音频播放也有讲究。用扬声器外放比用听筒费电。如果检测到用户用听筒,可以降低音频采样率。
// 根据音频输出设备调整参数
if (audioDevice == AudioDevice.EARPIECE) {
// 听筒模式:降低采样率到16kHz
audioTrack.setSampleRate(16000)
} else {
// 扬声器模式:保持48kHz
audioTrack.setSampleRate(48000)
}
3.5 后台任务节流
App切到后台后,应该大幅降低资源消耗:
- 停止视频编码(只传音频)
- 关闭摄像头
- 降低音频码率
- 延长心跳间隔(从5秒一次延长到30秒一次)
注意:iOS后台保活时间有限(约30秒)。如果要做后台通话,必须用VoIP Push。Android上也要用前台服务,否则系统会杀进程。别指望后台还能保持视频通话——那是不可能的。
四、综合优化清单
最后,我整理了一份检查清单。每次上线前,对着过一遍:
| 类别 | 检查项 | 优先级 |
|---|---|---|
| 内存 | 视频帧是否使用对象池 | P0 |
| 内存 | 编解码器是否及时释放 | P0 |
| 内存 | 是否监听低内存警告并降级 | P1 |
| CPU | 是否开启CPU自适应 | P0 |
| CPU | 是否优先使用硬编解码 | P0 |
| CPU | 渲染线程是否独立 | P1 |
| 电池 | 屏幕亮度是否自适应 | P1 |
| 电池 | 网络切换时是否调整码率 | P1 |
| 电池 | 后台是否停止视频传输 | P0 |
说实话,性能优化没有银弹。每个项目的情况都不一样。但记住一个原则:用多少资源,取决于用户当前需要多少,而不是你能给多少。
我做了这么多年WebRTC,最大的感悟就是:移动端优化,七分在架构设计,三分在调参。架构设计好了,后面调参就是锦上添花。架构设计烂了,再怎么调也是杯水车薪。
嗯,今天就聊到这儿。这些经验都是真金白银踩坑踩出来的,希望对你有帮助。