20、移动端适配:Android WebRTC集成、iOS WebRTC集成、移动端性能优化
移动端适配,说白了就是让WebRTC在手机上也跑得溜。我刚开始做移动端WebRTC时,觉得不就是把PC端的代码搬过来嘛?结果被现实狠狠教育了一顿。Android和iOS的底层差异、硬件编解码的坑、内存泄漏的问题……嗯,今天咱们把这些硬骨头一块啃了。
Android WebRTC集成:从零到一
Android集成WebRTC,我个人习惯用Google官方的webrtc-android源码编译,或者直接用Maven依赖。如果你不想折腾编译环境,直接加依赖就行:
// app/build.gradle
implementation 'org.webrtc:google-webrtc:1.0.32006'
但要注意,版本号要跟你的Android SDK匹配。我曾经遇到过因为版本不对,导致视频渲染黑屏的问题,折腾了两天才发现是API不兼容。
核心步骤:PeerConnection初始化
Android端初始化PeerConnection,关键是要正确设置PeerConnectionFactory。我一般这样写:
// 初始化PeerConnectionFactory
PeerConnectionFactory.InitializationOptions initOptions =
PeerConnectionFactory.InitializationOptions.builder(context)
.setFieldTrials("")
.createInitializationOptions();
PeerConnectionFactory.initialize(initOptions);
PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
PeerConnectionFactory factory = PeerConnectionFactory.builder()
.setOptions(options)
.createPeerConnectionFactory();
视频采集与渲染
Android的视频采集,我推荐用Camera2Enumerator,它比旧的Camera1Enumerator更稳定,支持更多分辨率。渲染则用SurfaceViewRenderer:
// 创建视频源
VideoSource videoSource = factory.createVideoSource(false);
VideoCapturer videoCapturer = createCameraCapturer(new Camera2Enumerator(context));
videoCapturer.initialize(surfaceTextureHelper, context, videoSource.getCapturerObserver());
videoCapturer.startCapture(1280, 720, 30);
// 创建渲染器
SurfaceViewRenderer localRenderer = findViewById(R.id.local_renderer);
localRenderer.init(eglBaseContext, null);
videoSource.getCapturerObserver().onFrameCaptured(frame);
这里有个小技巧:eglBaseContext最好复用,不要每次创建新的,否则GPU内存会暴涨。我在项目中就遇到过OOM,就是因为没复用EGL上下文。
iOS WebRTC集成:Swift与Objective-C的抉择
iOS端集成,我个人更倾向用Swift,但WebRTC官方库是Objective-C写的。没关系,Swift可以无缝调用OC。你只需要用CocoaPods或Swift Package Manager引入:
// Podfile
pod 'GoogleWebRTC'
或者用SPM:
// Xcode -> File -> Add Packages
https://github.com/google/webrtc-ios
核心步骤:RTCPeerConnection初始化
iOS的初始化跟Android类似,但要注意RTCPeerConnectionFactory的线程模型:
// Swift代码
let factory = RTCPeerConnectionFactory()
let config = RTCConfiguration()
config.iceServers = [RTCIceServer(urlStrings: ["stun:stun.l.google.com:19302"])]
let constraints = RTCMediaConstraints(mandatoryConstraints: nil,
optionalConstraints: nil)
let connection = factory.peerConnection(with: config,
constraints: constraints,
delegate: self)
RTCAudioSession需要手动激活,否则可能没有声音。我习惯在建立连接前调用:RTCAudioSession.sharedInstance().isAudioEnabled = true。
视频渲染:RTCMTLVideoRenderer
iOS端渲染,我推荐用RTCMTLVideoRenderer(Metal渲染),比OpenGL ES更省电。你只需要创建一个RTCMTLVideoView,然后设置到track上:
let localVideoView = RTCMTLVideoView(frame: view.bounds)
localVideoView.videoContentMode = .scaleAspectFill
view.addSubview(localVideoView)
// 绑定到视频track
localVideoTrack.add(localVideoView)
嗯,这里要注意:RTCMTLVideoView必须在主线程创建和更新,否则会闪退。我曾经在后台线程更新UI,结果App直接crash了。
移动端性能优化:别让手机变成暖手宝
移动端性能优化,说白了就是三个字:省电、省内存、省带宽。我总结了一张优化路线图:
视频编码优化:别让CPU累死
移动端CPU资源有限,我建议优先使用硬件编码。Android用MediaCodec,iOS用VideoToolbox。WebRTC默认会优先选硬件编码,但你要确保配置正确:
// Android端强制硬件编码
PeerConnectionFactory factory = PeerConnectionFactory.builder()
.setVideoEncoderFactory(new DefaultVideoEncoderFactory(
eglBaseContext,
true, // 启用硬件编码
true)) // 启用硬件解码
.createPeerConnectionFactory();
另外,动态分辨率调整也很重要。当网络变差时,自动降低分辨率,而不是降低帧率。我习惯用VideoSource.adaptOutputFormat()来动态调整:
// 根据网络状况调整分辨率
videoSource.adaptOutputFormat(
640, 480, 15, // 目标分辨率与帧率
320, 240, 10); // 最低分辨率与帧率
网络传输优化:Simulcast与带宽估计
移动端网络环境复杂,WiFi和4G/5G切换频繁。我推荐用Simulcast(分层编码),让接收端根据自身网络选择合适的分层。WebRTC原生支持Simulcast,但需要手动开启:
// 创建Simulcast流
MediaConstraints simulcastConstraints = new MediaConstraints();
simulcastConstraints.mandatory.add(
new MediaConstraints.KeyValuePair("googUseSimulcast", "true"));
// 在offer中设置
PeerConnectionParameters params = new PeerConnectionParameters();
params.simulcast = true;
带宽估计方面,WebRTC默认用GCC(Google Congestion Control),但移动端我建议开启Transport-CC(Transport-wide Congestion Control),它更精准:
// 开启Transport-CC
PeerConnectionFactory.InitializationOptions initOptions =
PeerConnectionFactory.InitializationOptions.builder(context)
.setFieldTrials("WebRTC-TransportCC/Enabled/")
.createInitializationOptions();
内存与功耗优化:别让手机发烫
移动端最怕的就是发烫。我总结了几条铁律:
- EGL上下文复用:不要每次渲染都创建新的EGL上下文,复用能减少50%的GPU开销。
- 纹理缓存:视频帧的纹理不要频繁创建销毁,用对象池复用。
- 后台暂停:App进入后台时,暂停视频采集和渲染,只保留音频。iOS用
UIApplicationDidEnterBackgroundNotification监听。 - 降低屏幕刷新率:如果视频只有30fps,没必要让屏幕跑60Hz。Android可以用
setFrameRate()限制。
避坑指南:我踩过的那些坑
最后分享几个我亲身经历的坑:
- Android权限问题:Android 6.0以上需要动态申请摄像头和麦克风权限。我曾经忘记在运行时申请,结果一直黑屏。
- iOS音频会话冲突:如果App同时播放音乐和WebRTC通话,音频会话会冲突。记得用
AVAudioSessionCategoryPlayAndRecord。 - 内存泄漏:PeerConnection和MediaStream一定要在合适的时机释放。我习惯用
WeakReference管理,避免循环引用。 - 屏幕旋转:移动端旋转屏幕时,视频方向会乱。记得监听屏幕旋转事件,调用
videoSource.adaptOutputFormat()调整。
嗯,移动端适配其实不难,但细节很多。你想想看,手机就那么点资源,要跑视频编解码、网络传输、UI渲染,任何一个环节出问题都会影响体验。我个人建议,先跑通基础流程,再逐步优化性能。别一上来就想搞Simulcast,先把单流跑稳再说。
公众号:蓝海资料掘金营,微信deep3321