如何通过WebSocket定时发送浏览器录制的16kHz 16位有符号PCM音频数据
实现浏览器端麦克风音频捕获并通过WebSocket发送16kHz 16位PCM数据
当然可以实现定期获取原始音频数据并通过WebSocket发送,下面是基于现代Web API的完整方案:
核心思路
利用AudioContext配合AudioWorklet(推荐,替代已弃用的ScriptProcessorNode)实时捕获音频帧,将浏览器默认的32位浮点PCM转换为16位有符号整数PCM,定期缓存并通过WebSocket发送。
完整实现步骤
1. 初始化WebSocket连接
// 替换为你的服务器WebSocket地址 const ws = new WebSocket('ws://your-server-domain:port'); ws.onopen = () => { console.log('WebSocket连接已建立,开始音频传输'); }; ws.onerror = (err) => { console.error('WebSocket连接出错:', err); }; ws.onclose = () => { console.log('WebSocket连接已关闭'); };
2. 创建AudioWorklet处理器脚本
新建audio-capture-processor.js文件,用于处理音频数据转换和定期发送:
class AudioCaptureProcessor extends AudioWorkletProcessor { constructor() { super(); this.dataBuffer = []; // 每100ms发送一次数据(16kHz采样率下对应1600个音频样本) this.sendInterval = 100; this.lastSendTimestamp = performance.now(); } process(inputs) { const inputChannel = inputs[0][0]; if (!inputChannel) return true; // 将32位浮点PCM转换为16位有符号整数PCM const int16Data = new Int16Array(inputChannel.length); for (let i = 0; i < inputChannel.length; i++) { // 浮点范围[-1,1]映射到16位整数范围[-32768, 32767] const clampedSample = Math.max(-1, Math.min(1, inputChannel[i])); int16Data[i] = clampedSample < 0 ? clampedSample * 32768 : clampedSample * 32767; } this.dataBuffer.push(int16Data); // 检查是否到达发送时间间隔 const now = performance.now(); if (now - this.lastSendTimestamp >= this.sendInterval) { // 合并缓存的音频数据 const totalLength = this.dataBuffer.reduce((sum, arr) => sum + arr.length, 0); const mergedData = new Int16Array(totalLength); let offset = 0; this.dataBuffer.forEach(arr => { mergedData.set(arr, offset); offset += arr.length; }); // 发送二进制数据到服务器 if (ws.readyState === WebSocket.OPEN) { ws.send(mergedData.buffer); } // 重置缓存和时间戳 this.dataBuffer = []; this.lastSendTimestamp = now; } return true; } static get parameterDescriptors() { return []; } } registerProcessor('audio-capture-processor', AudioCaptureProcessor);
3. 主脚本初始化音频捕获
async function startAudioCapture() { try { // 请求麦克风权限,优先指定16kHz单声道 const audioStream = await navigator.mediaDevices.getUserMedia({ audio: { sampleRate: 16000, channelCount: 1, echoCancellation: false, // 根据需求关闭回声消除 noiseSuppression: false // 根据需求关闭降噪 }, video: false }); // 创建16kHz采样率的AudioContext const audioContext = new AudioContext({ sampleRate: 16000 }); // 加载AudioWorklet处理器 await audioContext.audioWorklet.addModule('audio-capture-processor.js'); // 创建音频源节点和捕获节点 const sourceNode = audioContext.createMediaStreamSource(audioStream); const captureNode = new AudioWorkletNode(audioContext, 'audio-capture-processor'); // 连接节点:麦克风 -> 捕获处理器 -> 扬声器(可选,如需监听自己的声音) sourceNode.connect(captureNode); captureNode.connect(audioContext.destination); console.log('音频捕获已启动'); } catch (err) { console.error('音频捕获初始化失败:', err); } } // 页面加载完成后启动捕获 window.addEventListener('load', startAudioCapture);
关键注意事项
- 采样率适配:如果浏览器无法提供16kHz的麦克风输入,
AudioContext会自动将音频重采样到16kHz,无需额外处理。 - 发送间隔调整:
sendInterval可根据需求修改,间隔越小延迟越低,但WebSocket负载越高,建议50-200ms之间。 - 兼容性方案:如果需要兼容旧版浏览器(如Chrome <66),可以用已弃用的
ScriptProcessorNode替代AudioWorklet,示例代码如下:
// 替换主脚本中的AudioWorklet部分 const scriptNode = audioContext.createScriptProcessor(4096, 1, 1); scriptNode.onaudioprocess = (event) => { const channelData = event.inputBuffer.getChannelData(0); const int16Data = new Int16Array(channelData.length); for (let i = 0; i < channelData.length; i++) { const clampedSample = Math.max(-1, Math.min(1, channelData[i])); int16Data[i] = clampedSample < 0 ? clampedSample * 32768 : clampedSample * 32767; } if (ws.readyState === WebSocket.OPEN) { ws.send(int16Data.buffer); } }; sourceNode.connect(scriptNode); scriptNode.connect(audioContext.destination);
内容的提问来源于stack exchange,提问作者Nathan
相关产品推荐
相关产品推荐

