WebRTC实时通信实战:P2P视频通话与NAT穿透方案

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/

(0)
小编小编
上一篇 3小时前
下一篇 3小时前

相关推荐