WebRTC架构与媒体协商流程
WebRTC是浏览器原生支持的实时通信技术,实现点对点的音视频传输和数据通道,延迟控制在毫秒级。WebRTC通信流程包括信令协商、NAT穿透和媒体传输三个阶段。信令协商通过SDP(Session Description Protocol)交换媒体能力,ICE(Interactive Connectivity Establishment)框架处理NAT穿透,建立P2P连接后通过SRTP进行加密的音视频传输。
WebRTC的核心API包括getUserMedia(获取本地媒体流)、RTCPeerConnection(建立和管理P2P连接)和RTCDataChannel(双向数据通道)。一个完整的WebRTC通话需要信令服务器配合,用于在两端之间交换SDP和ICE候选信息。
getUserMedia与RTCPeerConnection API
async function getLocalStream() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 1280 },
height: { ideal: 720 },
frameRate: { ideal: 30, max: 60 },
facingMode: 'user'
},
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
channelCount: 2,
sampleRate: 48000
}
});
localVideo.srcObject = stream;
localStream = stream;
return stream;
} catch (err) {
console.error('获取媒体流失败:', err);
if (err.name === 'NotAllowedError') {
return await navigator.mediaDevices.getUserMedia({ audio: true });
}
throw err;
}
}
function createPeerConnection(config) {
const pc = new RTCPeerConnection({
iceServers: config.iceServers,
iceTransportPolicy: 'all',
bundlePolicy: 'max-bundle',
rtcpMuxPolicy: 'require',
iceCandidatePoolSize: 10
});
localStream.getTracks().forEach(track => {
pc.addTrack(track, localStream);
});
pc.ontrack = (event) => {
const remoteStream = new MediaStream();
event.streams[0].getTracks().forEach(track => {
remoteStream.addTrack(track);
});
remoteVideo.srcObject = remoteStream;
};
pc.onicecandidate = (event) => {
if (event.candidate) {
signaling.send({
type: 'ice-candidate',
candidate: event.candidate
});
}
};
pc.onconnectionstatechange = () => {
console.log('连接状态:', pc.connectionState);
if (pc.connectionState === 'failed') {
restartIce(pc);
}
};
return pc;
}
async function restartIce(pc) {
const offer = await pc.createOffer({ iceRestart: true });
await pc.setLocalDescription(offer);
signaling.send({ type: 'offer', sdp: offer });
}
SDP协商流程与Offer/Answer模型
async function createOffer(pc) {
const offer = await pc.createOffer({
offerToReceiveAudio: true,
offerToReceiveVideo: true,
voiceActivityDetection: true
});
await pc.setLocalDescription(offer);
signaling.send({ type: 'offer', sdp: offer.sdp, roomId: currentRoom });
return offer;
}
async function handleOffer(pc, remoteOffer) {
await pc.setRemoteDescription(new RTCSessionDescription({
type: 'offer', sdp: remoteOffer
}));
const answer = await pc.createAnswer({ voiceActivityDetection: true });
await pc.setLocalDescription(answer);
signaling.send({ type: 'answer', sdp: answer.sdp, roomId: currentRoom });
return answer;
}
async function handleRemoteIceCandidate(pc, candidate) {
try {
await pc.addIceCandidate(new RTCIceCandidate(candidate));
} catch (err) {
console.error('添加ICE候选失败:', err);
pendingCandidates.push(candidate);
}
}
STUN/TURN服务器配置与NAT穿透
NAT穿透是WebRTC面临的核心挑战。ICE框架依次尝试三种连接路径:主机候选(直连)、服务器反射候选(STUN)和中继候选(TURN)。STUN服务器帮助设备发现其公网地址,TURN服务器在P2P连接失败时提供中继转发。
const rtcConfig = {
iceServers: [
{ urls: 'stun:stun.yunthe.com:3478' },
{
urls: [
'turn:turn.yunthe.com:3478?transport=udp',
'turn:turn.yunthe.com:3478?transport=tcp',
'turns:turn.yunthe.com:5349?transport=tcp'
],
username: 'webrtc-user',
credential: 'turn-password-2026'
}
],
iceTransportPolicy: 'all',
iceCandidatePoolSize: 10
};
// coturn服务器配置 (/etc/turnserver.conf)
listening-port=3478
tls-listening-port=5349
external-ip=203.0.113.50
min-port=49152
max-port=65535
lt-cred-mech
use-auth-secret
static-auth-secret=turn-shared-secret-2026
realm=yunthe.com
total-quota=100
// ICE候选类型检测
function analyzeIceCandidates(pc) {
pc.addEventListener('icegatheringstatechange', () => {
if (pc.iceGatheringState === 'complete') {
pc.getStats().then(reports => {
let hasHost = false, hasSrflx = false, hasRelay = false;
reports.forEach(r => {
if (r.type === 'local-candidate') {
if (r.candidateType === 'host') hasHost = true;
if (r.candidateType === 'srflx') hasSrflx = true;
if (r.candidateType === 'relay') hasRelay = true;
}
});
if (!hasSrflx && !hasRelay) {
console.warn('可能为对称NAT,需要TURN中继');
}
});
}
});
}
信令服务器实现
信令服务器负责在通话双方之间转发SDP和ICE候选信息。WebSocket是最常用的信令传输协议。信令服务器本身不参与媒体传输,仅在连接建立阶段起中介作用。
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
const rooms = new Map();
wss.on('connection', (ws) => {
let currentRoom = null;
ws.on('message', (message) => {
const data = JSON.parse(message);
switch (data.type) {
case 'join':
currentRoom = data.roomId;
if (!rooms.has(currentRoom)) rooms.set(currentRoom, new Set());
rooms.get(currentRoom).add(ws);
broadcast(ws, currentRoom, { type: 'user-joined', userId: data.userId });
break;
case 'offer':
case 'answer':
case 'ice-candidate':
broadcast(ws, currentRoom, data);
break;
case 'leave':
rooms.get(currentRoom)?.delete(ws);
broadcast(ws, currentRoom, { type: 'user-left' });
break;
}
});
ws.on('close', () => {
if (currentRoom && rooms.has(currentRoom)) {
rooms.get(currentRoom).delete(ws);
if (rooms.get(currentRoom).size === 0) rooms.delete(currentRoom);
}
});
});
function broadcast(sender, roomId, data) {
const room = rooms.get(roomId);
if (!room) return;
const msg = JSON.stringify(data);
room.forEach(c => {
if (c !== sender && c.readyState === WebSocket.OPEN) c.send(msg);
});
}
弱网环境下的传输优化
在移动网络和高延迟场景下,通过Simulcast(多分辨率编码)和带宽自适应策略,在弱网环境下保持通话连续性。
async function enableSimulcast(pc, stream) {
const videoTrack = stream.getVideoTracks()[0];
const sender = pc.addTrack(videoTrack, stream);
const params = sender.getParameters();
if (!params.encodings) {
params.encodings = [
{ rid: 'low', scaleResolutionDownBy: 4, maxBitrate: 150000 },
{ rid: 'medium', scaleResolutionDownBy: 2, maxBitrate: 500000 },
{ rid: 'high', scaleResolutionDownBy: 1, maxBitrate: 1500000 }
];
await sender.setParameters(params);
}
return sender;
}
function setupAdaptiveBitrate(pc) {
let lastBitrate = 0;
setInterval(async () => {
const stats = await pc.getStats();
stats.forEach(r => {
if (r.type === 'outbound-rtp' && r.kind === 'video') {
const bitrate = r.bytesSent * 8;
if (Math.abs(bitrate - lastBitrate) > 100000) {
console.log(`视频发送码率: ${(bitrate / 1000).toFixed(0)} kbps`);
lastBitrate = bitrate;
if (bitrate < 300000) setPreferredLayer('low');
else if (bitrate < 800000) setPreferredLayer('medium');
else setPreferredLayer('high');
}
}
});
}, 2000);
}
对于大规模视频会议场景,P2P架构受限于带宽和连接数,需要引入SFU(Selective Forwarding Unit)媒体服务器。mediasoup、Janus、LiveKit等开源SFU方案支持数百人同时通话。SFU不混合音视频流,而是选择性转发各路流,相比MCU混流方案延迟更低、服务器负载更轻。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/webrtc-shi-shi-tong-xin-shi-zhan-p2p-shi-pin-tong-hua-yu/