一、多端适配的挑战与思路

做WebRTC开发,最头疼的问题是什么?我个人觉得,不是信令怎么设计,也不是媒体流怎么优化,而是——你的应用到底能在多少个平台上跑起来?

我见过太多团队,PC端跑得飞起,一到手机就卡成幻灯片。或者iOS没问题,Android直接黑屏。说白了,WebRTC本身是跨平台的,但每个平台的底层实现、硬件能力、系统限制都不一样。你想想看,同样的视频编码参数,在iPhone上可能流畅运行,在千元安卓机上就发热降频。

所以这一章,我们就来聊聊怎么搞定多端适配。我会从四个方向展开:移动端原生优化、React Native集成、Flutter插件方案、还有PWA这个轻量级选择。

核心观点: 多端适配不是"一套代码跑所有",而是"一套架构适配不同平台"。每个平台都有自己的脾气,你得顺着它来。

二、移动端WebRTC优化要点

2.1 硬件编码器的选择

移动端最关键的优化点,就是硬件编码器。我在项目中遇到过,直接用软件编码器,720p视频在手机上CPU占用直接飙到80%,手机烫得能煎鸡蛋。

正确的做法是:优先使用硬件编码器。Android上通过MediaCodec,iOS上通过VideoToolbox。WebRTC默认会尝试硬件编码,但有些定制ROM会出问题。

// Android端强制开启硬件编码
PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
// 注意:不同版本API有差异
PeerConnectionFactory factory = PeerConnectionFactory.builder()
    .setVideoEncoderFactory(new HardwareVideoEncoderFactory(
        rootEglBase.getEglBaseContext(),
        false,  // 是否使用表面纹理
        true    // 是否优先硬件编码
    ))
    .createPeerConnectionFactory();
我的经验: 在小米、华为等机型上,硬件编码器有时会输出花屏。我建议加一个降级逻辑:如果硬件编码连续3帧失败,自动切回软件编码。

2.2 分辨率与码率自适应

移动端网络环境变化大,你不能固定用1080p。我习惯用WebRTC自带的VideoEncoderAdapter做动态码率调整,但还不够。

实际项目中,我加了一个网络探测模块:

  • 当RTT > 200ms时,降低分辨率到480p
  • 当丢包率 > 5%时,降低帧率到15fps
  • 当带宽 < 500kbps时,关闭视频,只保留音频

嗯,这里要注意:不要频繁切换分辨率,否则画面会一直闪烁。我一般设置一个3秒的冷却期。

2.3 音频处理的坑

音频在移动端容易被忽略,但用户体验影响很大。我记得有一次,用户反馈通话有回声,查了半天发现是手机壳挡住了麦克风孔...好吧,这个不算。

正经的坑是:Android上不同机型的音频延迟差异巨大。从20ms到200ms都有。我建议用AudioTracksetBufferSizeInFrames方法手动控制缓冲大小,减少延迟。

// 降低音频延迟的配置
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
audioManager.setParameters("audio.ns_mode=0");  // 关闭降噪,减少延迟
audioManager.setParameters("audio.ec_mode=0");  // 关闭回声消除,如果硬件支持
警告: 关闭降噪和回声消除可能会引入噪声。只在硬件处理能力足够时使用。我一般只在旗舰机上这么做。

三、React Native集成WebRTC

3.1 选库与配置

React Native做WebRTC,最成熟的库是react-native-webrtc。但说实话,这个库的文档写得一般,我第一次集成时踩了不少坑。

安装步骤:

npm install react-native-webrtc
cd ios && pod install

Android端需要额外配置AndroidManifest.xml

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

3.2 核心API使用

在React Native中,你不能直接操作PeerConnection对象,而是通过RTCPeerConnection的JavaScript桥接。我习惯这样封装:

import {
  mediaDevices,
  RTCPeerConnection,
  RTCSessionDescription,
  RTCIceCandidate
} from 'react-native-webrtc';

const pc = new RTCPeerConnection({
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
});

// 获取本地流
const stream = await mediaDevices.getUserMedia({
  video: { facingMode: 'user' },
  audio: true
});

stream.getTracks().forEach(track => pc.addTrack(track, stream));
避坑指南: 我曾经在iOS上遇到getUserMedia返回null的问题。原因是Info.plist中没有添加摄像头和麦克风权限描述。记得加上NSCameraUsageDescriptionNSMicrophoneUsageDescription

3.3 性能优化

React Native的JS线程和原生线程是分离的,频繁的媒体状态更新会导致UI卡顿。我建议:

  • 使用useRef保存PeerConnection实例,避免重新渲染
  • 将视频渲染组件单独封装,用React.memo减少重绘
  • 远程流使用RTCView组件,设置objectFit: 'cover'防止变形

四、Flutter WebRTC插件

4.1 为什么选Flutter

我个人觉得,Flutter在跨平台UI一致性上比React Native强。而且flutter_webrtc这个插件维护得不错,API设计更贴近原生WebRTC。

安装:

dependencies:
  flutter_webrtc: ^0.9.0

4.2 核心用法

Flutter中创建PeerConnection的代码更简洁:

import 'package:flutter_webrtc/flutter_webrtc.dart';

final pc = await createPeerConnection({
  'iceServers': [
    {'urls': 'stun:stun.l.google.com:19302'}
  ]
});

// 获取本地流
final stream = await navigator.mediaDevices.getUserMedia({
  'video': {'facingMode': 'user'},
  'audio': true
});

stream.getTracks().forEach((track) {
  pc.addTrack(track, stream);
});

4.3 平台差异处理

Flutter虽然跨平台,但底层还是调原生API。我在项目中遇到过一个典型问题:Android上视频渲染用TextureRenderer,iOS上用RTCMTLVideoRenderer。插件虽然封装了,但有些参数需要单独设置。

功能 Android iOS
视频渲染 SurfaceView / TextureView MTKView / GLKView
硬件编码 MediaCodec VideoToolbox
音频采集 AudioRecord AVAudioEngine
回声消除 系统自带或WebRTC内置 系统自带
注意: Flutter的flutter_webrtc在iOS模拟器上不支持摄像头。真机调试才能用。我第一次用模拟器测了半天,以为代码写错了...

五、PWA方案:轻量级选择

5.1 PWA能做什么

PWA(Progressive Web App)说白了就是一个能离线运行的网页。对于WebRTC来说,PWA的优势是:不需要安装,打开浏览器就能用。但缺点也很明显——不能访问原生硬件编码器。

我一般建议:如果应用场景是"临时通话"或"演示Demo",用PWA就够了。如果是生产环境,还是得上原生或React Native/Flutter。

5.2 关键配置

要让PWA支持WebRTC,需要:

  • HTTPS协议(WebRTC强制要求)
  • Service Worker注册(用于离线缓存)
  • manifest.json配置(让应用可以添加到桌面)
// manifest.json
{
  "name": "WebRTC通话",
  "short_name": "通话",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "icons": [
    {
      "src": "/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    }
  ]
}

5.3 性能限制

PWA在移动端浏览器上跑WebRTC,性能大概只有原生的一半。我测试过:

  • Chrome on Android:支持硬件编码,但延迟比原生高30-50ms
  • Safari on iOS:支持有限,部分API不可用
  • 微信内置浏览器:基本不可用,WebRTC支持不完整
我的建议: 如果非要用PWA,尽量用Chrome或Edge。Safari的WebRTC实现有bug,我遇到过多次ICE连接失败的问题。

六、知识体系总览

下面这张图,是我梳理的多端适配核心逻辑。你可以看到,从底层硬件到上层框架,每个层级都有不同的适配策略。

WebRTC多端适配知识体系 应用层框架 移动端原生优化 React Native集成 Flutter插件方案 PWA方案 硬件编码器适配 分辨率自适应 音频延迟优化 react-native-webrtc JS桥接优化 权限配置 flutter_webrtc 平台差异处理 渲染组件封装 Service Worker HTTPS强制 性能限制 WebRTC核心引擎(ICE / DTLS / SRTP / SDP)

七、总结与建议

多端适配没有银弹。我个人的经验是:

  • 如果团队有原生开发能力,直接做Android和iOS原生,性能最好
  • 如果追求开发效率,Flutter比React Native更省心,UI一致性更好
  • 如果只是Demo或内部工具,PWA够用了,但别指望它能承载高并发
  • 无论选哪个方案,硬件编码器适配和网络自适应都是必须做的

嗯,最后说一句:别迷信跨平台框架。它们能解决80%的问题,但剩下20%的坑,还是得靠原生代码来填。我在项目中就遇到过Flutter插件不支持某个Android厂商的定制API,最后还是得写原生Module。

核心原则: 先跑通,再优化,最后才考虑跨平台。不要一开始就想着"一套代码跑所有",那是不现实的。

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