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

如何通过Node.js正确保存Socket.IO传输的MediaRecorder OGG音频Blob?

问题

在浏览器端通过MediaRecorder录制音频,停止录制后将Blob格式的音频数据通过Socket.IO上传至Node.js服务并写入文件,上传流程正常,但服务器生成的文件并非有效的音频文件。

客户端传输代码:

recorder.ondataavailable = async (chunk) => {
  console.log(`Mime type: ${recorder.mimeType}`);
  console.log(`blowing chunks`);
  chunks.value.push(chunk.data);

  const result = await socket.emitWithAck('data', chunk.data);
  console.log(result);
};

服务端接收代码:

socket.on('data', async (data: Buffer, ack) => {
  try {
    writeFileSync('data/testfile.ogg', data);
    ack('saved');
  } catch (err) {
    console.log(err);
    // throw (err);
  }
});

补充说明:

  • 曾尝试使用appendFile,但客户端ondataavailable仅在停止录制时触发,所以write和append效果一致,用write还省去手动删除文件的操作。
  • 已知音频媒体类型为audio/ogg,且客户端与服务端的数据大小一致,数据未丢失。

请问通过Node.js正确保存编码后的OGG音频二进制数据的方法是什么?

解决方案

问题出在Socket.IO传输Blob数据时的格式转换上:浏览器端的chunk.data是Blob对象,直接通过Socket.IO传输会被自动序列化为JSON对象,而非原始二进制数据,导致服务端收到的Buffer是序列化后的结果,并非真正的音频二进制流。

解决步骤如下:

  1. 客户端:将Blob转换为ArrayBuffer再传输
    发送前把Blob转成ArrayBuffer,确保传输的是原始二进制数据:

    recorder.ondataavailable = async (chunk) => {
      console.log(`Mime type: ${recorder.mimeType}`);
      console.log(`blowing chunks`);
      chunks.value.push(chunk.data);
    
      // 将Blob转为ArrayBuffer
      const arrayBuffer = await chunk.data.arrayBuffer();
      const result = await socket.emitWithAck('data', arrayBuffer);
      console.log(result);
    };
    
  2. 服务端:直接写入ArrayBuffer对应的Buffer
    Socket.IO会将ArrayBuffer转换为Node.js的Buffer,此时直接写入文件即可:

    import { writeFileSync } from 'fs';
    import { join } from 'path';
    
    socket.on('data', async (data: Buffer, ack) => {
      try {
        // 确保目录存在,避免写入失败
        const filePath = join(__dirname, 'data', 'testfile.ogg');
        writeFileSync(filePath, data, { flag: 'w' });
        ack('saved');
      } catch (err) {
        console.error('写入文件失败:', err);
        ack('error');
      }
    });
    
  3. 额外验证:合并多段数据(如果后续ondataavailable多次触发)
    若后续录制过程中ondataavailable会多次触发(比如设置了timeslice),则需要用appendFileSync累加数据,或者先收集所有数据再一次性写入:

    // 服务端累加数据示例
    let audioBuffer = Buffer.from([]);
    socket.on('data', (data: Buffer, ack) => {
      audioBuffer = Buffer.concat([audioBuffer, data]);
      ack('chunk received');
    });
    
    socket.on('stop', () => {
      writeFileSync('data/testfile.ogg', audioBuffer);
      audioBuffer = Buffer.from([]); // 重置缓冲区
    });
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 15:55:09