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();
⚠️ 注意: 初始化必须在主线程执行,否则会抛出IllegalStateException。我刚开始就踩过这个坑,异步初始化导致崩溃。

视频采集与渲染

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)
💡 提示: iOS的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了。

移动端性能优化:别让手机变成暖手宝

移动端性能优化,说白了就是三个字:省电、省内存、省带宽。我总结了一张优化路线图:

移动端WebRTC性能优化路线图 视频编码优化 网络传输优化 内存与功耗优化 动态分辨率调整 硬件编码优先 帧率自适应(15-30fps) Simulcast分层编码 NACK与FEC平衡 带宽估计(GCC/BBA) EGL上下文复用 纹理缓存与池化 后台暂停视频流 目标:流畅、省电、不卡顿

视频编码优化:别让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); // 最低分辨率与帧率
🔑 关键点: 帧率不要低于15fps,否则视频会明显卡顿。分辨率可以降到320x240,但帧率要保证流畅。

网络传输优化: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()限制。
⚠️ 注意: 后台暂停视频流时,记得释放摄像头资源,否则其他App无法使用摄像头。我曾经因为这个被用户投诉过。

避坑指南:我踩过的那些坑

最后分享几个我亲身经历的坑:

  • Android权限问题:Android 6.0以上需要动态申请摄像头和麦克风权限。我曾经忘记在运行时申请,结果一直黑屏。
  • iOS音频会话冲突:如果App同时播放音乐和WebRTC通话,音频会话会冲突。记得用AVAudioSessionCategoryPlayAndRecord
  • 内存泄漏:PeerConnection和MediaStream一定要在合适的时机释放。我习惯用WeakReference管理,避免循环引用。
  • 屏幕旋转:移动端旋转屏幕时,视频方向会乱。记得监听屏幕旋转事件,调用videoSource.adaptOutputFormat()调整。

嗯,移动端适配其实不难,但细节很多。你想想看,手机就那么点资源,要跑视频编解码、网络传输、UI渲染,任何一个环节出问题都会影响体验。我个人建议,先跑通基础流程,再逐步优化性能。别一上来就想搞Simulcast,先把单流跑稳再说。


公众号:蓝海资料掘金营,微信deep3321