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

WebSocket单音频块发送时Python服务端无效输入报错问题

解决WebSocket发送单块音频Blob后服务端无效输入错误问题

问题背景

通过WebSocket每4秒从客户端发送音频Blob:

  • 原实现:客户端将每次音频数据追加到Blob后发送,虽能正常运行,但Blob体积逐渐增大导致实时性滞后
  • 修改后问题:改为仅发送当前最新音频块,服务端接收第一个Blob正常,第二个开始出现无效输入错误

问题原因

MediaRecorder的dataavailable事件返回的event.data是不带WAV文件头的原始音频片段。WAV格式需要包含采样率、位深、声道数等元数据的文件头才能被正常解析:

  • 第一次发送时,服务端可能兼容解析(或偶然包含完整头),但后续片段缺少文件头,导致音频解析器无法识别有效输入。

解决方案

方案1:客户端为每个音频片段生成完整WAV文件头

修改客户端代码,不再累积Blob,而是为当前音频片段添加标准WAV文件头后发送:

recorder.mediaRecorder.addEventListener(
  "dataavailable",
  async (event) => {
    // 处理当前音频片段,生成带WAV头的Blob
    const audioBuffer = await event.data.arrayBuffer();
    const wavBlob = createWavBlob(audioBuffer);

    if (recorder.socket?.readyState === 1) {
      recorder.socket.send(wavBlob);
    }
  }
);

recorder.mediaRecorder.start(DELAY_TIME_MILLISECONDS);

// 辅助函数:为原始PCM音频添加WAV文件头
function createWavBlob(audioBuffer) {
  // 根据实际录制参数调整以下值
  const sampleRate = 44100;
  const numChannels = 1;
  const bitDepth = 16;

  const buffer = new ArrayBuffer(44 + audioBuffer.byteLength);
  const header = new DataView(buffer);

  // 写入WAV文件头
  header.setUint8(0, 0x52); // R
  header.setUint8(1, 0x49); // I
  header.setUint8(2, 0x46); // F
  header.setUint8(3, 0x46); // F
  header.setUint32(4, 44 + audioBuffer.byteLength, true); // 文件总长度
  header.setUint8(8, 0x57); // W
  header.setUint8(9, 0x41); // A
  header.setUint8(10, 0x56); // V
  header.setUint8(11, 0x45); // E
  header.setUint8(12, 0x66); // f
  header.setUint8(13, 0x6d); // m
  header.setUint8(14, 0x74); // t
  header.setUint8(15, 0x20); // 空格
  header.setUint32(16, 16, true); // 子块长度
  header.setUint16(20, 1, true); // PCM音频格式
  header.setUint16(22, numChannels, true); // 声道数
  header.setUint32(24, sampleRate, true); // 采样率
  header.setUint32(28, sampleRate * numChannels * bitDepth / 8, true); // 字节率
  header.setUint16(32, numChannels * bitDepth / 8, true); // 块对齐
  header.setUint16(34, bitDepth, true); // 位深
  header.setUint8(36, 0x64); // d
  header.setUint8(37, 0x61); // a
  header.setUint8(38, 0x74); // t
  header.setUint8(39, 0x61); // a
  header.setUint32(40, audioBuffer.byteLength, true); // 数据长度

  // 复制原始音频数据到文件头之后
  const dataView = new DataView(buffer, 44);
  const sourceView = new DataView(audioBuffer);
  for (let i = 0; i < audioBuffer.byteLength; i++) {
    dataView.setUint8(i, sourceView.getUint8(i));
  }

  return new Blob([buffer], { type: "audio/wav; codecs=MS_PCM" });
}

方案2:服务端直接解析原始PCM数据

如果客户端发送的是原始PCM数据(不带文件头),修改服务端代码跳过WAV头解析,直接处理原始音频字节:

import numpy as np

while True:
    bytes_data = await websocket.receive_bytes()
    print("============received audio bytes")
    if bytes_data:
        # 假设客户端发送的是16位单声道、44100Hz的PCM数据
        # 直接将字节转为numpy数组
        audio_array = np.frombuffer(bytes_data, dtype=np.int16)
        # 转为浮点型(范围-1到1),适配后续处理
        audio_array = audio_array.astype(np.float32) / 32768.0
        # 后续处理逻辑...

注意事项

  • 客户端录制的音频参数(采样率、声道数、位深)必须与服务端解析时的参数完全一致,否则会出现音频失真或解析错误
  • 使用MediaRecorder时,需确保指定的mimeType为PCM编码的WAV格式,比如audio/wav; codecs=MS_PCM,避免浏览器输出其他压缩格式

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 13:45:26