如何通过socket.io实现单客户端向多客户端实时传输音频流
实现整体思路
你选择的Socket.IO中转广播方案可以很好避免WebRTC点对点下发送端带宽随收听人数线性增长的问题,核心实现逻辑分为三步:发送端通过音频处理节点实时采集原始PCM音频数据,通过Socket.IO发送到服务端;服务端直接将音频数据广播给所有需要收听的客户端;接收端拿到音频数据后通过Web Audio API实时播放即可。
1. 发送端改造(原代码修改)
在原有本地播放逻辑的基础上新增ScriptProcessorNode节点采集实时音频数据,直接传输二进制数据保证传输效率:
$(document).ready(function () { var socket = io("ws://127.0.0.1:4385"); // 缓冲区大小越小延迟越低,可根据实际网络情况调整,单声道配置可将数据量减半 const BUFFER_SIZE = 4096; const SAMPLE_RATE = 44100; if (navigator.mediaDevices) { console.log('getUserMedia supported.'); var constraints = { audio: true, sampleRate: SAMPLE_RATE }; navigator.mediaDevices.getUserMedia(constraints) .then(function (stream) { let ctx = new AudioContext({ sampleRate: SAMPLE_RATE }); let source = ctx.createMediaStreamSource(stream); // 创建音频处理节点采集原始数据 let processor = ctx.createScriptProcessor(BUFFER_SIZE, 1, 1); source.connect(processor); processor.connect(ctx.destination); // 不需要本地播放可删掉这行 // 监听音频缓冲区事件,拿到原始PCM数据 processor.onaudioprocess = function(e) { const audioData = e.inputBuffer.getChannelData(0); // 直接传输二进制ArrayBuffer,效率远高于base64 socket.emit('audio-stream', audioData.buffer); }; }) .catch(function (err) { console.log('The following error occurred: ' + err); }) } });
2. Socket.IO服务端逻辑
服务端仅做中转广播,不需要对音频数据做任何处理,以下是Node.js版本示例:
const { Server } = require("socket.io"); const io = new Server(4385, { cors: { origin: "*" } // 生产环境请替换为允许的业务域名 }); io.on('connection', (socket) => { // 收到发送端的音频流,广播给除发送端外所有连接客户端 socket.on('audio-stream', (audioBuffer) => { socket.broadcast.emit('audio-play', audioBuffer); // 多房间场景下可改为指定房间广播:io.to('房间ID').emit('audio-play', audioBuffer) }); });
3. 接收端播放逻辑
接收端维护音频数据队列解决网络抖动问题,拿到数据后实时解码播放:
$(document).ready(function () { var socket = io("ws://127.0.0.1:4385"); const SAMPLE_RATE = 44100; let ctx = new AudioContext({ sampleRate: SAMPLE_RATE }); // 音频数据队列,累计2个缓冲区再开始播放避免网络抖动导致断音 let audioQueue = []; let isPlaying = false; // 监听服务端发来的音频流 socket.on('audio-play', (audioBuffer) => { const pcmData = new Float32Array(audioBuffer); audioQueue.push(pcmData); if (audioQueue.length >= 2 && !isPlaying) { playBuffer(); } }); function playBuffer() { if (audioQueue.length === 0) { isPlaying = false; return; } isPlaying = true; const pcmData = audioQueue.shift(); // 创建音频缓冲区写入PCM数据 const buffer = ctx.createBuffer(1, pcmData.length, SAMPLE_RATE); buffer.copyToChannel(pcmData, 0); // 创建播放节点 const source = ctx.createBufferSource(); source.buffer = buffer; source.connect(ctx.destination); // 当前缓冲区播放完成后自动播放下一个 source.onended = playBuffer; source.start(); } });
可优化方向
- 延迟优化:将BUFFER_SIZE调低到2048/1024,可将端到端延迟控制在100ms以内
- 带宽优化:发送端使用Opus编码压缩PCM数据,接收端解码后播放,可将数据量降低到原有的1/10左右
- 性能优化:现代浏览器可将
ScriptProcessorNode替换为AudioWorklet,避免主线程阻塞导致的音频卡顿
内容的提问来源于stack exchange,提问作者MadLuca
相关产品推荐
相关产品推荐

