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

WebRTC新手:如何通过WebSocket以RTP传输浏览器录制的原始音频

刚好我之前做过类似的实时音频流式传输场景,咱们一步步来拆解你的需求,帮你实现从浏览器录制音频并发送RTP包的功能:

一、先搞懂RTP音频包的基本结构

RTP(实时传输协议)是用来传输实时音视频的标准协议,一个基础的RTP包由12字节的头部加上音频负载组成:

  • 头部包含版本号(固定为2)、负载类型(比如PCMU是0、OPUS是111)、序列号、时间戳、SSRC(同步源标识符)这些关键信息
  • 负载就是编码后的音频数据,浏览器里拿到的原始音频是Float32格式,需要先转成适合RTP传输的编码(比如16位PCM或OPUS)
二、从你的现有代码入手,处理实时音频数据

你现在用的ScriptProcessor虽然已经被浏览器标记为弃用(推荐后续改用AudioWorklet),但目前还是可以用来获取实时音频帧的。咱们修改onaudioprocess回调,把Float32的音频样本转成16位PCM(这是RTP常用的原始负载格式):

processor.onaudioprocess = function(e) {
  // 单声道为例,获取左声道的Float32音频数据
  const inputFloat32 = e.inputBuffer.getChannelData(0);
  // 把Float32(范围-1.0~1.0)转成Int16(范围-32768~32767)
  const int16Data = new Int16Array(inputFloat32.length);
  for (let i = 0; i < inputFloat32.length; i++) {
    const sample = Math.max(-1, Math.min(1, inputFloat32[i])); // 防止溢出
    int16Data[i] = sample < 0 ? sample * 32768 : sample * 32767;
  }
  // 现在int16Data就是可以用来封装RTP的原始音频数据了
  const rtpPacket = buildRTPPacket(int16Data);
  // 通过WebSocket发送RTP包
  if (ws && ws.readyState === WebSocket.OPEN) {
    ws.send(rtpPacket);
  }
};
三、实现RTP数据包的构建函数

接下来写buildRTPPacket函数,按照RTP协议规范组装头部和负载:

// 初始化RTP的基础参数
let rtpSequence = 0; // 序列号,每次发送自增
const rtpSSRC = Math.floor(Math.random() * 0xFFFFFFFF); // 随机生成唯一的同步源ID
const sampleRate = 44100; // 和你的AudioContext采样率保持一致
const payloadType = 111; // 用OPUS编码(如果用PCMU填0,PCMA填8)

function buildRTPPacket(payload) {
  // 基础RTP头部是12字节
  const rtpHeader = new Uint8Array(12);
  
  // 第1字节:版本(V=2) + 填充(P=0) + 扩展(X=0) + CSRC计数(CC=0)
  rtpHeader[0] = 0x80;
  // 第2字节:标记位(M=0) + 负载类型(PT=111)
  rtpHeader[1] = payloadType;
  // 第3-4字节:序列号(大端序)
  rtpHeader[2] = (rtpSequence >> 8) & 0xFF;
  rtpHeader[3] = rtpSequence & 0xFF;
  // 第5-8字节:时间戳(大端序,累计采样数更准确,这里每次加当前帧的采样数)
  const timestamp = rtpSequence * 1024; // 因为你的ScriptProcessor缓冲区是1024采样
  rtpHeader[4] = (timestamp >> 24) & 0xFF;
  rtpHeader[5] = (timestamp >> 16) & 0xFF;
  rtpHeader[6] = (timestamp >> 8) & 0xFF;
  rtpHeader[7] = timestamp & 0xFF;
  // 第9-12字节:SSRC(大端序)
  rtpHeader[8] = (rtpSSRC >> 24) & 0xFF;
  rtpHeader[9] = (rtpSSRC >> 16) & 0xFF;
  rtpHeader[10] = (rtpSSRC >> 8) & 0xFF;
  rtpHeader[11] = rtpSSRC & 0xFF;

  // 把Int16的负载转成Uint8Array,和头部拼接成完整RTP包
  const payloadUint8 = new Uint8Array(payload.buffer);
  const rtpPacket = new Uint8Array(rtpHeader.length + payloadUint8.length);
  rtpPacket.set(rtpHeader, 0);
  rtpPacket.set(payloadUint8, rtpHeader.length);

  // 序列号自增,超过65535就重置
  rtpSequence++;
  if (rtpSequence > 0xFFFF) rtpSequence = 0;

  return rtpPacket;
}
四、用WebSocket实现流式传输

最后,在你的handleSuccess函数里初始化WebSocket连接,确保可以把RTP包实时发送到远程机器:

// 全局WebSocket实例
let ws;

const handleSuccess = function(stream) {
  // ... 你的现有代码(AudioContext、ScriptProcessor这些) ...

  // 连接到远程机器的WebSocket服务
  ws = new WebSocket('ws://你的远程机器IP:端口号');
  ws.binaryType = 'arraybuffer'; // 必须设置成二进制类型,因为RTP是二进制包

  ws.onopen = () => {
    console.log('WebSocket连接成功,开始发送音频RTP包');
  };

  ws.onerror = (error) => {
    console.error('WebSocket连接出错:', error);
  };

  ws.onclose = () => {
    console.log('WebSocket连接关闭');
  };

  // ... 你的现有代码(播放流的部分) ...
};
几个关键注意事项
  • 关于ScriptProcessor的替代方案:ScriptProcessor存在性能瓶颈,后续建议改用AudioWorklet,它是浏览器推荐的高效实时音频处理API,处理逻辑放在独立的工作线程,不会阻塞主线程
  • 编码优化:如果要减小传输体积,推荐用OPUS编码替代原始PCM,你可以用浏览器的WebCodecs API把Int16的PCM转成OPUS编码后再封装RTP包
  • 接收端处理:远程机器需要解析RTP包,提取负载后解码播放,比如用FFmpeg或者自己实现RTP解析逻辑,示例命令:ffplay -protocol_whitelist "file,udp,rtp" -i rtp://0.0.0.0:端口(如果是UDP的话,但你用的是WebSocket,需要先把RTP包从WebSocket里提取出来再喂给解码器)
  • Record RTC的使用:如果你用Record RTC是为了录制保存,那它没问题,但流式传输的话,还是用实时音频处理+WebSocket的方式更直接,Record RTC更适合批量处理录制后的音频

内容的提问来源于stack exchange,提问作者Rajan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:26:04