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,你可以用浏览器的
WebCodecsAPI把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
相关产品推荐
相关产品推荐

