You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用WebRTC API解复用RTP音频流

使用WebRTC API解复用RTP音频流

嘿,刚好碰到过类似的场景,我来给你唠唠怎么用WebRTC API搞定RTP音频流的解复用问题~

你现在做的是那种服务器转发RTP包的语音聊天应用对吧?每个客户端都有自己唯一的SSRC,而且目前的代码已经能让所有音频流正常播放了,这其实是因为WebRTC在底层已经帮你做了不少工作,但如果需要更精细的控制,或者想搞明白背后的逻辑,咱们可以拆解来看:

为啥现有代码能正常工作?

WebRTC的RTCPeerConnection在接收到RTP包时,会自动根据包里面的SSRC字段来区分不同的媒体源,把来自不同客户端的音频流分别封装成MediaStreamTrack,然后通过ontrack事件推送给你。所以你的ontrack回调里每次收到的ev.track,其实就是某一个客户端的独立音频轨道,直接把它加到MediaStream里就能播放啦。

怎么手动区分/解复用流?

如果需要对不同客户端的流做自定义处理(比如标记说话者、过滤特定流、自定义混音),咱们可以从这两个方向入手:

1. 利用WebRTC自带的轨道和接收器信息

在ontrack事件里,你可以通过接收器的参数拿到对应流的SSRC,以此来标识不同的客户端:

function join_vc(offer) {
    const rtc_conn = new RTCPeerConnection({
        bundlePolicy: 'max-compat'
    });

    rtc_conn.ontrack = (ev) => {
        // 拿到当前轨道对应的SSRC,用来区分不同客户端
        const ssrc = ev.receiver.rtpParameters.encodings[0].ssrc;
        console.log(`收到来自客户端(SSRC: ${ssrc})的音频流`);
        
        // 创建媒体流并播放
        const audioStream = new MediaStream([ev.track]);
        const audioElement = document.createElement('audio');
        audioElement.srcObject = audioStream;
        audioElement.volume = 1;
        // 可以给audioElement加个标识,比如id设为SSRC值,方便后续管理
        audioElement.id = `audio-${ssrc}`;
        document.body.appendChild(audioElement);
        
        audioElement.play().catch(err => {
            console.error(`播放SSRC: ${ssrc}的音频失败:`, err);
        });
    };

    // 处理SDP协商的逻辑
    rtc_conn.setRemoteDescription(offer)
        .then(() => rtc_conn.createAnswer())
        .then(answer => rtc_conn.setLocalDescription(answer))
        .catch(err => console.error('连接协商出错:', err));
}

这种方式最省心,完全依赖WebRTC的自动处理,只需要在收到轨道时标记SSRC就行。

2. 手动处理RTP包(自定义解复用)

如果需要更底层的控制,比如自己解析RTP包、过滤特定包,就可以用RTCRtpScriptTransform把RTP包传到Worker里处理,在Worker里根据SSRC来解复用:

首先修改主进程的代码,给音频接收器添加变换:

function join_vc(offer) {
    const rtc_conn = new RTCPeerConnection({
        bundlePolicy: 'max-compat'
    });

    // 先添加一个仅接收的音频收发器
    rtc_conn.addTransceiver('audio', { direction: 'recvonly' });

    // 获取音频接收器,添加RTP变换
    setTimeout(() => {
        const audioReceiver = rtc_conn.getReceivers().find(recv => recv.track.kind === 'audio');
        if (audioReceiver) {
            // 创建一个Worker来处理RTP包
            const rtpTransform = new RTCRtpScriptTransform(new Worker('rtp-demux-worker.js'));
            audioReceiver.transform = rtpTransform;
        }
    }, 100);

    rtc_conn.ontrack = (ev) => {
        const ssrc = ev.receiver.rtpParameters.encodings[0].ssrc;
        console.log(`收到SSRC: ${ssrc}的音频轨道`);
        
        const audioStream = new MediaStream([ev.track]);
        const audioElement = document.createElement('audio');
        audioElement.srcObject = audioStream;
        audioElement.id = `audio-${ssrc}`;
        document.body.appendChild(audioElement);
        
        audioElement.play().catch(err => console.error(`播放失败:`, err));
    };

    // SDP协商逻辑
    rtc_conn.setRemoteDescription(offer)
        .then(() => rtc_conn.createAnswer())
        .then(answer => rtc_conn.setLocalDescription(answer))
        .catch(err => console.error('连接出错:', err));
}

然后创建rtp-demux-worker.js文件,在Worker里处理RTP包的解复用:

// 监听RTP包事件
self.onrtp = (event) => {
    const rtpPacket = event.rtp;
    // 从RTP头里拿到SSRC
    const packetSSRC = rtpPacket.ssrc;

    // 这里可以自定义逻辑:比如只保留特定SSRC的包,或者给包打标记
    console.log(`收到RTP包,SSRC: ${packetSSRC}`);

    // 处理完后把包发回给WebRTC继续处理
    event.sendBack(rtpPacket);
};

这种方式适合需要对RTP包做自定义处理的场景,比如过滤掉某个客户端的流,或者在转发前修改包内容。

关键注意点

  • 服务器转发RTP包的时候一定不要修改包的SSRC,这是WebRTC区分不同流的核心标识,改了就会导致解复用失败。
  • 每个MediaStreamTrack对应一个SSRC,也就是一个客户端的音频流,你可以把多个轨道加到同一个MediaStream里混音播放,也可以单独播放每个轨道。

备注:内容来源于stack exchange,提问作者Inexspectatus somnium

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 17:24:38