15、WebRTC 入门:信令交换、NAT穿透、P2P连接、音视频通话实现。

说实话,WebRTC 是我这几年研究得最过瘾的技术之一。你想想看,浏览器之间直接传视频、传音频,不需要中间服务器转发数据——这在十年前简直是天方夜谭。但 WebRTC 做到了。

这一章,我会带你从零开始,把 WebRTC 的核心机制拆开揉碎。信令交换、NAT 穿透、P2P 连接,再到最终的音视频通话实现。嗯,咱们一步步来。

15.1 什么是 WebRTC?

WebRTC,全称 Web Real-Time Communication。说白了,就是让浏览器、Android 应用、iOS 应用之间能直接进行音视频通话的技术。

它不是一个单一协议,而是一整套框架。包括媒体捕获、编码解码、网络传输、安全加密等等。我当年第一次接触时,也被它的复杂度吓了一跳。但后来发现,核心就三个东西:

  • 获取媒体流:从摄像头、麦克风拿到音视频数据
  • 建立连接:通过信令和 NAT 穿透,找到对方设备
  • 传输数据:用 SRTP/SCTP 协议安全传输音视频

核心要点:WebRTC 的终极目标,是让两个设备之间建立一条直接的、加密的、低延迟的音视频通道。

15.2 信令交换——建立连接的“握手”

信令交换,是 WebRTC 的第一步。两个设备要建立连接,总得先知道对方在哪吧?

信令的作用就是交换“元数据”。包括:

  • 媒体能力(支持什么编码、什么分辨率)
  • 网络信息(IP 地址、端口号)
  • 会话描述(SDP 协议)

WebRTC 本身没有规定信令怎么传。你可以用 WebSocket、HTTP 轮询、甚至用鸽子送信都行。我个人习惯用 WebSocket,实时性好,双向通信方便。

15.2.1 SDP 协议

SDP(Session Description Protocol)是信令交换的核心。它描述了一个多媒体会话的所有参数。

举个例子,一个典型的 SDP 长这样:

v=0
o=- 123456 2 IN IP4 192.168.1.100
s=-
t=0 0
m=audio 49170 RTP/AVP 0
a=rtpmap:0 PCMU/8000
m=video 51372 RTP/AVP 96
a=rtpmap:96 H264/90000

你看,它告诉对方:我支持音频(PCMU 编码),支持视频(H264 编码),我的 IP 是 192.168.1.100,端口是 49170 和 51372。

信令交换的过程,就是双方互相发送 SDP 的过程。发起方创建 Offer,接收方回复 Answer。

我的经验:SDP 里有很多细节参数,比如 a=ice-ufrag、a=ice-pwd 这些。刚开始看不懂没关系,你只要知道它们是用来做 NAT 穿透的就行。

15.3 NAT 穿透——找到对方真正的 IP

这里有个大问题。你的设备在局域网里,IP 是 192.168.x.x。对方的设备也在另一个局域网里。你们怎么找到对方?

这就是 NAT(网络地址转换)穿透要解决的问题。

WebRTC 使用 ICE(Interactive Connectivity Establishment)框架来解决。ICE 会收集所有可能的候选地址(Candidate),然后尝试连接。

15.3.1 候选地址类型

类型 说明 优先级
Host 本机 IP,比如 192.168.1.100 最高
Srflx 通过 STUN 服务器获取的公网 IP 中等
Relay 通过 TURN 服务器中转的地址 最低

ICE 会先尝试 Host 地址。如果不行(比如双方不在同一个局域网),就尝试 Srflx 地址。还不行?那就只能用 Relay 地址了。

我曾经在一个项目中遇到过极端情况:用户的网络环境极其复杂,多层 NAT 嵌套,STUN 都拿不到正确的公网 IP。最后只能靠 TURN 中转,延迟虽然高了点,但至少能通。

避坑指南:STUN 服务器只能帮你拿到公网 IP,但有些对称 NAT 环境下,STUN 也搞不定。这时候必须上 TURN。我曾经因为没部署 TURN 服务器,导致 30% 的用户连不上——血的教训。

15.4 P2P 连接——直连通道的建立

信令交换完了,候选地址也收集好了。接下来就是真正的 P2P 连接建立。

ICE 会按照优先级,依次尝试每一对候选地址组合。这个过程叫“连通性检查”。

举个例子:

  • 你的 Host 地址 + 对方的 Host 地址 → 尝试
  • 你的 Srflx 地址 + 对方的 Srflx 地址 → 尝试
  • 你的 Relay 地址 + 对方的 Relay 地址 → 尝试

一旦某对地址连接成功,ICE 就会选择这条路径作为最终的传输通道。

这里有个细节:连接成功后,双方还会通过 STUN 定期发送心跳包,确保连接还活着。如果网络变化了(比如从 Wi-Fi 切到 4G),ICE 会重新协商,切换到新的路径。

关键点:P2P 连接不是一次性的。它会根据网络状况动态调整。这也是 WebRTC 能适应复杂网络环境的原因。

15.5 音视频通话实现——从理论到代码

好了,理论讲完了。咱们来看看 Android 上怎么实现一个简单的音视频通话。

15.5.1 引入 WebRTC 库

在 Android 项目里,我推荐用 Google 官方的 Maven 依赖:

implementation 'org.webrtc:google-webrtc:1.0.32006'

注意版本号。不同版本 API 可能有细微差异。我一般用最新稳定版。

15.5.2 创建 PeerConnectionFactory

这是 WebRTC 的核心工厂类。所有连接对象都由它创建。

PeerConnectionFactory.InitializationOptions initOptions =
    PeerConnectionFactory.InitializationOptions.builder(context)
        .setFieldTrials("")
        .createInitializationOptions();
PeerConnectionFactory.initialize(initOptions);

PeerConnectionFactory factory = PeerConnectionFactory.builder()
    .setVideoDecoderFactory(new DefaultVideoDecoderFactory(eglBaseContext))
    .setVideoEncoderFactory(new DefaultVideoEncoderFactory(eglBaseContext, true, true))
    .createPeerConnectionFactory();

15.5.3 创建 PeerConnection

每个连接对应一个 PeerConnection 对象。

PeerConnection.RTCConfiguration config = new PeerConnection.RTCConfiguration(iceServers);
config.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN;

PeerConnection connection = factory.createPeerConnection(config, observer);

这里有个关键参数:iceServers。你需要配置 STUN 和 TURN 服务器地址。

List<PeerConnection.IceServer> iceServers = new ArrayList<>();
iceServers.add(PeerConnection.IceServer.builder("stun:stun.l.google.com:19302").createIceServer());
iceServers.add(PeerConnection.IceServer.builder("turn:your-turn-server.com:3478")
    .setUsername("user")
    .setPassword("pass")
    .createIceServer());

15.5.4 添加媒体流

从摄像头和麦克风获取数据,添加到连接中。

VideoSource videoSource = factory.createVideoSource(false);
VideoCapturer capturer = createCameraCapturer();
capturer.initialize(surfaceTextureHelper, context, videoSource.getCapturerObserver());
capturer.startCapture(width, height, fps);

VideoTrack videoTrack = factory.createVideoTrack("video_track", videoSource);
connection.addTrack(videoTrack);

AudioSource audioSource = factory.createAudioSource(new MediaConstraints());
AudioTrack audioTrack = factory.createAudioTrack("audio_track", audioSource);
connection.addTrack(audioTrack);

15.5.5 发起连接

发起方创建 Offer,通过信令通道发送给对方。

connection.createOffer(new SdpObserver() {
    @Override
    public void onCreateSuccess(SessionDescription sdp) {
        connection.setLocalDescription(new SdpObserver() {
            @Override
            public void onSetSuccess() {
                // 通过信令通道发送 sdp 给远端
                signalingClient.send(sdp);
            }
        }, sdp);
    }
}, new MediaConstraints());

接收方收到 Offer 后,创建 Answer 并返回。

connection.createAnswer(new SdpObserver() {
    @Override
    public void onCreateSuccess(SessionDescription sdp) {
        connection.setLocalDescription(new SdpObserver() {
            @Override
            public void onSetSuccess() {
                signalingClient.send(sdp);
            }
        }, sdp);
    }
}, new MediaConstraints());

15.5.6 处理 ICE 候选

连接过程中,双方会不断产生 ICE 候选地址。需要通过信令通道交换。

connection.addIceCandidate(candidate);

当所有候选都处理完毕,连接就建立成功了。这时候,音视频数据就会在两端之间实时传输。

我的建议:刚开始做的时候,先不要急着加太多功能。先把最基本的“能看到对方画面、能听到对方声音”跑通。然后再慢慢加美颜、加录制、加屏幕共享。一口吃不成胖子。

15.6 整体流程回顾

为了让你更直观地理解整个流程,我画了一张图:

WebRTC 音视频通话完整流程 设备 A(发起方) 设备 B(接收方) 信令服务器 STUN / TURN 服务器 ① Offer SDP ② Answer SDP ③ ICE 候选 ④ ICE 候选 ⑤ 获取公网地址 / 中继地址 ⑥ P2P 直连通道建立 ⑦ 音视频数据实时传输(SRTP) 信令 ICE 候选 P2P 连接 音视频流

整个流程其实就七个步骤:

  1. 设备 A 通过信令服务器发送 Offer SDP
  2. 设备 B 回复 Answer SDP
  3. 双方交换 ICE 候选地址
  4. 通过 STUN/TURN 获取公网或中继地址
  5. 建立 P2P 直连通道
  6. 音视频数据开始实时传输

你看,是不是清晰多了?

15.7 避坑指南

最后,分享几个我踩过的坑:

  • 信令服务器一定要稳定:信令只在连接建立前使用,但如果连接断了需要重连,信令还得用。我遇到过信令服务器挂了,所有用户都连不上的情况。
  • TURN 服务器带宽要够:如果走中继,所有音视频数据都经过 TURN 服务器。一个 720p 的视频流大约需要 1-2 Mbps。100 个用户同时通话,服务器带宽至少 200 Mbps。
  • Android 权限别忘了:摄像头、麦克风、网络权限都要在 AndroidManifest 里声明。运行时权限也要处理。我刚开始做的时候忘了申请 CAMERA 权限,画面一直是黑的,排查了半天。
  • 注意线程模型:WebRTC 的回调很多是在内部线程池里执行的。更新 UI 时记得切到主线程。

总结一下:WebRTC 的核心就是信令交换 + NAT 穿透 + P2P 连接。信令用 SDP 描述媒体能力,ICE 负责找到对方,P2P 通道建立后音视频数据直接传输。Android 上的实现,就是创建 PeerConnectionFactory、配置 ICE 服务器、添加媒体流、发起连接。就这么简单。

嗯,这一章的内容就到这里。代码部分你可以直接拿去用,但建议自己动手敲一遍。只有亲手跑通了,才能真正理解 WebRTC 的运作机制。


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