WebRTC(七):媒体能力协商
目的
在 WebRTC 中,每个浏览器或终端支持的音视频编解码器、分辨率、码率、帧率等可能不同。媒体能力协商的目的就是:
- 确保双方能“听得懂”对方发的媒体流;
- 明确谁发送、谁接收、怎么发送;
- 保障连接的互操作性和兼容性。
P2P的基本流程
参与角色
角色 | 说明 |
---|---|
peerA | 发起连接的端(通常是主叫) |
peerB | 接收连接的端(通常是被叫) |
signal | 信令服务器,用于中转 SDP 和 ICE 信息,但不参与媒体传输 |
stun/turn | STUN/TURN 服务器,用于穿透 NAT 或作为中继,辅助建立 P2P 或 Relay 通信 |
流程详解
连接信令服务器
peerA
和peerB
分别通过 WebSocket 或其他方式连接信令服务器,用于后续中转 SDP 和 ICE 数据。
PeerConnection 创建 + 添加媒体流(媒体准备)
peerA
:
- 创建
RTCPeerConnection
实例。 - 通过
getUserMedia()
获取本地音视频流。 addTrack()
或addStream()
将媒体加入连接对象。
创建 Offer SDP(发起协商)
peerA
调用createOffer()
:- 生成包含自身媒体能力(支持的音视频编解码器、方向、SSRC、码率等)的 SDP。
peerA
调用setLocalDescription(offer)
:- 表示将该 SDP 用作自己的本地描述。
- 浏览器会开始进行 ICE 候选收集(即寻找可用的 IP/端口路径)。
发送 Offer SDP(信令交换)
peerA
将 Offer SDP 通过signal
发送给peerB
。- 此时可以看到
peerA → signal → peerB
的 “Send SDP Offer”。
接收方处理 Offer
peerB
:
- 创建
RTCPeerConnection
实例。 setRemoteDescription(offer)
设置远端描述(即peerA
的 SDP)。- 浏览器据此了解
peerA
的媒体能力,并开始准备回答。
创建 Answer SDP(应答协商)
peerB
调用createAnswer()
,根据双方交集生成 Answer SDP。- 调用
setLocalDescription(answer)
设置为自己的本地描述。
发送 Answer SDP(信令交换)
peerB → signal → peerA
发送 Answer SDP。peerA
调用setRemoteDescription(answer)
,设置为远端描述,协商正式完成。
到这一步为止,媒体能力协商完成(Codec、方向等确认)。
ICE 候选协商(网络通路探测)
- 双方浏览器后台通过 STUN 向公网发送绑定请求,收集本地的候选地址(ICE candidate)。
- 每收集到一个 ICE 候选地址,会触发
onicecandidate
事件。
发送和添加 ICE 候选(网络路径建立)
- 每次
onicecandidate
被触发,候选地址通过信令发送给对方:peerA
→signal
→peerB
peerB
→signal
→peerA
- 收到对方候选后,使用
addIceCandidate()
添加。
当 ICE 连接状态变为 connected
或 completed
时,即可开始进行媒体传输。
媒体流建立
- 媒体协商完成,网络路径打通后:
peerB
触发onAddStream
或ontrack
事件,表示收到远端音视频流。- 同样地,
peerA
也会收到对方的流。
重点流程
阶段 | 关键函数 | 作用 |
---|---|---|
媒体协商 | createOffer / createAnswer | 生成 SDP |
setLocalDescription / setRemoteDescription | 设置 SDP 本地/远端描述 | |
网络协商 | onicecandidate / addIceCandidate | 交换并使用候选地址建立连接 |
信令传输 | signal | 中转 SDP 和 ICE,但不传输媒体 |
媒体传输 | addTrack / ontrack | 接收对方音视频流 |
重要函数
createOffer()
作用
生成一个 SDP Offer,描述本地支持的音视频媒体能力(如 codec、媒体方向、分辨率、带宽等)。
使用示例
const offer = await pc.createOffer();
应用场景
- 发起方调用,用于开始媒体协商;
- 会触发 ICE 候选的收集。
createAnswer()
作用
接收到对方 SDP Offer 后,根据自己的能力生成一个 SDP Answer。
使用示例
const answer = await pc.createAnswer();
应用场景
- 接收方调用,用于回应媒体协商;
- 也会触发 ICE 候选收集。
setLocalDescription(desc)
作用
设置本地描述(Offer 或 Answer),并开始 ICE 候选收集。
使用示例
await pc.setLocalDescription(offer); // offer 或 answer 都可以
说明
- 必须在发送 SDP 之前调用;
- 必须配合
createOffer
或createAnswer
使用。
setRemoteDescription(desc)
作用
设置远端 SDP 描述(对方发送的 offer 或 answer),用于协商建立媒体连接。
使用示例
await pc.setRemoteDescription(remoteOffer);
注意
- 必须在
createAnswer()
前调用(即先设置远端再生成应答); - SDP 是通过信令服务器中转接收的。
onicecandidate
作用
监听本地收集到的每一个 ICE 候选地址(IP + port),用于网络路径穿透。
示例
pc.onicecandidate = (event) => {if (event.candidate) {sendCandidateToPeer(event.candidate); // 通过信令发送}
};
注意
- 每收集一个候选地址就触发一次;
- 全部收集完毕会收到一个
null
的event.candidate
。
addIceCandidate(candidate)
作用
将从对方接收到的 ICE 候选加入本地 ICE 代理中,用于连接建立。
示例
await pc.addIceCandidate(remoteCandidate);
注意
- 一定要在
setRemoteDescription
之后调用; - 多次调用用于添加多个候选。
addTrack
作用
添加音频或视频轨道(MediaStreamTrack)。
示例
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
const pc = new RTCPeerConnection();stream.getTracks().forEach(track => {pc.addTrack(track, stream); // 同一个 MediaStream 确保同步
});
特性
特性 | 说明 |
---|---|
精细轨道控制 | 每次添加单个 MediaStreamTrack (音频或视频) |
支持多轨道发送 | 可多次调用添加多个音频/视频轨 |
支持轨道同步 | 若多个轨道来自同一个 MediaStream ,浏览器会自动尝试同步播放 |
返回 RTCRtpSender | 可动态设置编码参数、带宽、分辨率等 |
支持动态添加轨道 | 连接建立后也可添加轨道,会触发重新协商 |
可用于替代 addStream | 现代 WebRTC 标准推荐使用,addStream() 已废弃 |
与 ontrack 配套使用 | 远端通过 ontrack 事件接收媒体轨道 |
轨道标签与 ID 传递 | SDP 会携带轨道的 id 和所属流 stream id (用于标识同步组) |
支持 simulcast(多编码) | 搭配 RTCRtpSender.setParameters() 支持多编码流发送 |
ontrack
作用
当远端添加了新的媒体轨(音轨/视频轨)时触发,一般用于播放远程视频或音频。
示例
pc.ontrack = (event) => {remoteVideo.srcObject = event.streams[0];
};
特性
- 可接收多个轨道;
- 是
addTrack()
和recvonly
类型媒体方向配合使用的标准回调。
发起端与接收端
发起端
const pc = new RTCPeerConnection();pc.onicecandidate = e => sendCandidate(e.candidate);
pc.ontrack = e => remoteVideo.srcObject = e.streams[0];localStream.getTracks().forEach(track => {pc.addTrack(track, localStream);
});const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
sendOffer(offer);
接收端
const pc = new RTCPeerConnection();pc.onicecandidate = e => sendCandidate(e.candidate);
pc.ontrack = e => remoteVideo.srcObject = e.streams[0];await pc.setRemoteDescription(offer);
localStream.getTracks().forEach(track => {pc.addTrack(track, localStream);
});
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
sendAnswer(answer);
总结
- 目的:确保双方选用兼容的音视频编解码器、分辨率、码率和传输参数,实现高质量的实时通信。
- 载体:基于 SDP(Session Description Protocol)协议,描述媒体的相关能力。
- 流程:
- 一方创建并发送包含自身支持能力的 SDP Offer
- 对方收到后生成 SDP Answer,确认兼容参数
- 双方通过设置本地和远端描述完成协商
- 内容包括:
- 编解码器列表(如 VP8、H.264、Opus)
- 媒体流方向(发送、接收、双向)
- 带宽限制和媒体属性
- 网络传输细节(ICE、DTLS安全层)
- 结果:协商成功后,双方基于共同支持的参数建立安全稳定的音视频流通道。