5. PeerConnection核心:RTCPeerConnection对象创建、配置STUN/TURN服务器、建立点对点连接

好,咱们今天聊点硬核的。前面几章我们把WebRTC的底层原理、信令流程都捋了一遍,现在终于要动手创建真正的连接了。这一章,我会带你手把手搞定RTCPeerConnection——这个WebRTC里最核心、也最让人头疼的对象。

说实话,我刚开始接触WebRTC时,觉得PeerConnection就是个黑盒子。传个视频嘛,new一个对象不就完了?结果第一次联调,两边死活连不上,日志里全是ICE状态来回跳。后来才发现,STUN/TURN配置写错了,端口被防火墙拦了……嗯,这些坑我今天都会讲到。

5.1 RTCPeerConnection是什么?

简单说,RTCPeerConnection就是WebRTC里负责建立和维护点对点连接的核心对象。它封装了ICE、DTLS、SRTP等一系列协议栈,你只需要调用它的API,就能实现音视频数据的传输。

我习惯把它理解成一个“连接管家”。你告诉它“我要跟谁连”,它就去帮你协商编解码器、打洞、加密、传输。你只管往里面塞音视频流,剩下的它全包了。

核心职责:

  • 管理ICE代理,收集候选地址
  • 处理STUN/TURN打洞
  • 协商SDP(会话描述协议)
  • 建立DTLS加密通道
  • 传输音视频数据

说白了,没有PeerConnection,WebRTC就是个空壳。你想想看,浏览器之间怎么直接传数据?全靠它。

5.2 创建RTCPeerConnection对象

在Android上,创建PeerConnection需要借助PeerConnectionFactory。这个工厂类负责创建所有WebRTC相关的对象,包括PeerConnection、AudioSource、VideoSource等。

我记得第一次写的时候,直接new了一个PeerConnection,结果编译报错。后来翻文档才发现,WebRTC的设计者把创建过程拆成了两步:先初始化工厂,再用工厂创建连接。这么做的好处是,你可以统一配置日志、线程、编解码器等全局参数。

5.2.1 初始化PeerConnectionFactory

// 1. 初始化WebRTC全局设置
PeerConnectionFactory.InitializationOptions initOptions =
    PeerConnectionFactory.InitializationOptions.builder(context)
        .setFieldTrials("WebRTC-H264HighProfile/Enabled/")
        .setEnableInternalTracer(true)
        .createInitializationOptions();
PeerConnectionFactory.initialize(initOptions);

// 2. 创建工厂实例
PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
PeerConnectionFactory factory = PeerConnectionFactory.builder()
    .setOptions(options)
    .createPeerConnectionFactory();

这里有个细节:setFieldTrials是用来开启实验性功能的。比如H264 High Profile,默认是关闭的,但很多Android设备支持。我建议你打开它,画质能提升不少。

5.2.2 创建PeerConnection

工厂准备好了,接下来就是创建连接对象。你需要提供一个RTCConfiguration,里面包含ICE服务器、约束条件等配置。

// 创建配置
PeerConnection.RTCConfiguration config = new PeerConnection.RTCConfiguration(iceServers);
config.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN;
config.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY;
config.keyType = PeerConnection.KeyType.ECDSA;

// 创建连接
PeerConnection connection = factory.createPeerConnection(config, observer);

注意sdpSemantics这个参数。WebRTC有两种SDP语义:Plan BUnified Plan。Plan B是旧版,一个m=行可以塞多个流;Unified Plan是标准版,每个流独立一个m=行。我强烈建议你用Unified Plan,因为Plan B已经被Chrome废弃了。

避坑指南:我曾经在项目里用了Plan B,结果跟新版Chrome联调时,对方收不到视频流。查了半天才发现是SDP语义不兼容。从那以后,我所有新项目都统一用Unified Plan。

5.3 配置STUN/TURN服务器

创建PeerConnection时,最重要的就是配置ICE服务器。ICE(Interactive Connectivity Establishment)负责帮两端找到最优的通信路径。STUN和TURN就是ICE的左右手。

5.3.1 STUN vs TURN

特性 STUN TURN
作用 获取公网IP和端口 中继转发媒体数据
适用场景 两端都在公网或NAT简单 对称NAT或防火墙限制
带宽消耗 几乎为零 高(服务器转发所有数据)
延迟 较高
成本 免费/低 高(需要服务器带宽)

说白了,STUN是“问路”,TURN是“代驾”。能直接连就STUN,连不上就TURN兜底。

5.3.2 配置ICE服务器

List<PeerConnection.IceServer> iceServers = new ArrayList<>();

// STUN服务器(Google公共)
PeerConnection.IceServer stunServer = PeerConnection.IceServer.builder("stun:stun.l.google.com:19302")
    .createIceServer();
iceServers.add(stunServer);

// TURN服务器(需要自己搭建或购买)
PeerConnection.IceServer turnServer = PeerConnection.IceServer.builder("turn:turn.example.com:3478")
    .setUsername("user")
    .setPassword("pass")
    .createIceServer();
iceServers.add(turnServer);

这里要注意:STUN服务器是免费的,Google、Cloudflare都提供公共STUN。但TURN服务器必须自己搭建或购买云服务。我建议至少配两个STUN和一个TURN,这样ICE候选地址更丰富,连接成功率更高。

个人经验:我在生产环境中,通常会配3个STUN和2个TURN。STUN用Google和Cloudflare的公共服务器,TURN用腾讯云或阿里云的中继服务。这样即使某个服务器挂了,还有备用。

5.4 建立点对点连接

配置好PeerConnection后,真正的连接建立就开始了。这个过程分为三步:创建Offer设置本地描述处理远端描述。信令通道负责传递这些SDP和ICE候选信息。

5.4.1 创建Offer(发起方)

// 创建Offer
connection.createOffer(new SdpObserver() {
    @Override
    public void onCreateSuccess(SessionDescription sdp) {
        // 设置本地描述
        connection.setLocalDescription(new SdpObserver() {
            @Override
            public void onSetSuccess() {
                // 通过信令通道发送Offer给远端
                signalingClient.sendMessage(sdp);
            }
            // ... 其他回调
        }, sdp);
    }
}, new MediaConstraints());

创建Offer时,PeerConnection会根据你添加的媒体流(音频、视频)生成对应的SDP。这个SDP包含了编解码器、IP端口、指纹等信息。远端收到后,会生成Answer回复你。

5.4.2 处理Answer(应答方)

// 收到远端的Offer后
connection.setRemoteDescription(new SdpObserver() {
    @Override
    public void onSetSuccess() {
        // 创建Answer
        connection.createAnswer(new SdpObserver() {
            @Override
            public void onCreateSuccess(SessionDescription sdp) {
                // 设置本地描述
                connection.setLocalDescription(new SdpObserver() {
                    @Override
                    public void onSetSuccess() {
                        // 发送Answer给发起方
                        signalingClient.sendMessage(sdp);
                    }
                }, sdp);
            }
        }, new MediaConstraints());
    }
}, offerSdp);

这里有个容易踩的坑:必须先设置远端描述,再创建Answer。顺序反了会导致SDP协商失败。我刚开始做的时候,就犯过这个错,结果两边SDP对不上,连接一直卡在“checking”状态。

5.4.3 ICE候选交换

SDP交换完成后,PeerConnection会自动开始ICE候选收集。你需要监听onIceCandidate回调,把候选信息通过信令通道发给远端。

// 监听ICE候选
connection.addIceCandidate(new IceCandidate("candidate:...", 1, "udp", 2122260223, "192.168.1.100", 54321, "typ host"));

// 收到远端的候选
connection.addIceCandidate(candidate);

ICE候选分为三种类型:

  • host:本地地址(如192.168.x.x)
  • srflx:STUN反射地址(公网IP)
  • relay:TURN中继地址

PeerConnection会按优先级排序,优先尝试host,不行再试srflx,最后用relay。这个过程是自动的,你只需要把候选传过去就行。

5.5 连接状态监控

连接建立后,你需要监控ICE连接状态,以便处理断线重连等异常情况。

PeerConnection.Observer observer = new PeerConnection.Observer() {
    @Override
    public void onIceConnectionState(IceConnectionState state) {
        switch (state) {
            case NEW:
                // 初始状态
                break;
            case CHECKING:
                // 正在检查候选
                break;
            case CONNECTED:
                // 连接成功
                break;
            case COMPLETED:
                // 连接完成(所有候选都检查完毕)
                break;
            case DISCONNECTED:
                // 连接断开
                break;
            case FAILED:
                // 连接失败
                break;
            case CLOSED:
                // 连接关闭
                break;
        }
    }
};

我建议你在DISCONNECTEDFAILED状态时,尝试重新创建PeerConnection。因为有些网络切换(比如WiFi切4G)会导致ICE会话失效,重新创建是最稳妥的办法。

核心要点总结:

  • RTCPeerConnection是WebRTC的连接核心,负责ICE、DTLS、SRTP
  • 创建时务必使用Unified Plan语义
  • STUN/TURN配置要合理,至少配一个TURN兜底
  • SDP协商顺序不能错:先设远端,再创建Answer
  • ICE候选交换是自动的,但需要信令通道传递
  • 监控连接状态,做好断线重连

好了,这一章的内容就到这里。PeerConnection的创建和配置是WebRTC开发的基础,也是最容易出问题的地方。我建议你动手写一个Demo,把STUN/TURN配好,跑通一次完整的连接流程。相信我,当你看到两边的视频画面成功显示时,那种成就感是看多少文档都换不来的。


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