在 WebRTC 中,每个浏览器或终端支持的音视频编解码器、分辨率、码率、帧率等可能不同。媒体能力协商的目的就是:
角色 | 说明 |
---|---|
peerA | 发起连接的端(通常是主叫) |
peerB | 接收连接的端(通常是被叫) |
signal | 信令服务器,用于中转 SDP 和 ICE 信息,但不参与媒体传输 |
stun/turn | STUN/TURN 服务器,用于穿透 NAT 或作为中继,辅助建立 P2P 或 Relay 通信 |
peerA
和 peerB
分别通过 WebSocket 或其他方式连接信令服务器,用于后续中转 SDP 和 ICE 数据。peerA
:
RTCPeerConnection
实例。getUserMedia()
获取本地音视频流。addTrack()
或 addStream()
将媒体加入连接对象。peerA
调用 createOffer()
:
peerA
调用 setLocalDescription(offer)
:
peerA
将 Offer SDP 通过 signal
发送给 peerB
。peerA → signal → peerB
的 “Send SDP Offer”。peerB
:
RTCPeerConnection
实例。setRemoteDescription(offer)
设置远端描述(即 peerA
的 SDP)。peerA
的媒体能力,并开始准备回答。peerB
调用 createAnswer()
,根据双方交集生成 Answer SDP。setLocalDescription(answer)
设置为自己的本地描述。peerB → signal → peerA
发送 Answer SDP。peerA
调用 setRemoteDescription(answer)
,设置为远端描述,协商正式完成。到这一步为止,媒体能力协商完成(Codec、方向等确认)。
onicecandidate
事件。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 |
接收对方音视频流 |
生成一个 SDP Offer,描述本地支持的音视频媒体能力(如 codec、媒体方向、分辨率、带宽等)。
const offer = await pc.createOffer();
接收到对方 SDP Offer 后,根据自己的能力生成一个 SDP Answer。
const answer = await pc.createAnswer();
设置本地描述(Offer 或 Answer),并开始 ICE 候选收集。
await pc.setLocalDescription(offer); // offer 或 answer 都可以
createOffer
或 createAnswer
使用。设置远端 SDP 描述(对方发送的 offer 或 answer),用于协商建立媒体连接。
await pc.setRemoteDescription(remoteOffer);
createAnswer()
前调用(即先设置远端再生成应答);监听本地收集到的每一个 ICE 候选地址(IP + port),用于网络路径穿透。
pc.onicecandidate = (event) => {
if (event.candidate) {
sendCandidateToPeer(event.candidate); // 通过信令发送
}
};
null
的 event.candidate
。将从对方接收到的 ICE 候选加入本地 ICE 代理中,用于连接建立。
await pc.addIceCandidate(remoteCandidate);
setRemoteDescription
之后调用;添加音频或视频轨道(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() 支持多编码流发送 |
当远端添加了新的媒体轨(音轨/视频轨)时触发,一般用于播放远程视频或音频。
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);