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

如何将MediaRecorder API获取的音频Blob编码为LINEAR16?

实时麦克风音频转Google Speech To Text的编码转换问题

我想把用户麦克风的音频实时发送到Google Speech To Text API,打算用它的RecognizeStream功能。具体实现是网页端用MediaRecorder API加WebSocket连接Node.js服务器,每2秒把ondataavailable事件拿到的音频Blob发往服务器,再由服务器调用Google库的recognizeStream.write(blob)传给API,但现在API无响应且超时,排查后确认是编码问题导致的。

我对音频编码不熟悉,找不到无需物理文件、直接在代码里把原始Blob转成LINEAR16 Blob的方法,还要尽可能降低延迟。试过用Sox软件和对应的npm包转换,但没法实现无文件操作。


前端发送音频代码

const mediaConstraints = { audio: true };

mediaStream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
mediaRecorder = new MediaRecorder(mediaStream);
let isRecording = false;

mediaRecorder.ondataavailable = (event) => {
     if (isRecording && event.data.size > 0) {
             socket.emit('audioChunk', event.data);
     }
};

Node.js后端接收及处理代码

const encoding = 'LINEAR16';
const languageCode = 'fr-FR';
const sampleRateHertz = 16000;

const request = {
  config: {
    encoding: encoding,
    languageCode: languageCode,
    sampleRateHertz: sampleRateHertz,
  },
  interimResults: true, // 如果需要中间结果,设为true
};

io.on('connection', (socket) => {
  console.log('用户已连接');

  const recognizeStream = client
  .streamingRecognize(request)
  .on('error', console.error)
  .on('data', data =>
    process.stdout.write(
      data.results[0] && data.results[0].alternatives[0]
        ? `转录结果: ${data.results[0].alternatives[0].transcript}\n`
        : '\n\n达到转录时间限制,请按Ctrl+C\n'
    )
  );

  socket.on('audioChunk', (chunk) => { 
    const blob = new Blob([chunk], {type: 'audio/webm;codecs=opus'});
    // 此处需要转换编码
    recognizeStream.write(blob.binaryData);
  });

解决方案:实时转码为LINEAR16

方案1:前端直接采集LINEAR16格式(推荐,降低后端压力)

MediaRecorder默认输出WebM/Opus,无法直接输出LINEAR16,但可以用AudioContext直接处理原始音频数据,转换成16kHz、16位单声道PCM(即LINEAR16):

// 前端替换MediaRecorder的代码
const mediaConstraints = { audio: { sampleRate: 16000, channelCount: 1 } };
const mediaStream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
const audioContext = new AudioContext({ sampleRate: 16000 });
const source = audioContext.createMediaStreamSource(mediaStream);
// 缓冲区大小选4096/8192,平衡延迟与性能
const processor = audioContext.createScriptProcessor(4096, 1, 1);

source.connect(processor);
processor.connect(audioContext.destination);

processor.onaudioprocess = (event) => {
  if (!isRecording) return;
  // 获取原始Float32格式PCM数据
  const inputBuffer = event.inputBuffer.getChannelData(0);
  // 转换为16位整数(LINEAR16格式)
  const int16Array = new Int16Array(inputBuffer.length);
  for (let i = 0; i < inputBuffer.length; i++) {
    // 将范围-1~1的Float32转成范围-32768~32767的Int16
    const sample = Math.max(-1, Math.min(1, inputBuffer[i]));
    int16Array[i] = sample < 0 ? sample * 0x8000 : sample * 0x7FFF;
  }
  // 转成Blob发送
  const blob = new Blob([int16Array], { type: 'audio/L16;rate=16000' });
  socket.emit('audioChunk', blob);
};

此时后端无需转码,直接传入Google流:

// 后端修改接收部分
socket.on('audioChunk', (chunk) => { 
  recognizeStream.write(chunk);
});

方案2:后端实时转码

如果必须在后端处理,可以用轻量库或ffmpeg实现无文件转码:

用@discordjs/opus转码(低延迟)

先安装依赖:npm install @discordjs/opus

const { OpusDecoder } = require('@discordjs/opus');

// 初始化解码器,匹配前端Opus参数
const decoder = new OpusDecoder(16000, 1);

socket.on('audioChunk', (chunk) => { 
  const buffer = Buffer.from(chunk);
  // 解码Opus到Float32格式PCM
  const pcmFloat = decoder.decode(buffer);
  // 转成Int16Array(LINEAR16)
  const int16Array = new Int16Array(pcmFloat.length);
  for (let i = 0; i < pcmFloat.length; i++) {
    const sample = Math.max(-1, Math.min(1, pcmFloat[i]));
    int16Array[i] = sample < 0 ? sample * 0x8000 : sample * 0x7FFF;
  }
  // 写入Google流
  recognizeStream.write(Buffer.from(int16Array));
});

用fluent-ffmpeg转码

安装依赖:npm install fluent-ffmpeg

const ffmpeg = require('fluent-ffmpeg');

socket.on('audioChunk', (chunk) => { 
  const buffer = Buffer.from(chunk);
  // 实时转码,不生成物理文件
  ffmpeg()
    .input(buffer)
    .inputFormat('webm')
    .audioCodec('pcm_s16le') // LINEAR16对应编码
    .audioChannels(1)
    .audioFrequency(16000)
    .format('s16le')
    .on('data', (data) => {
      recognizeStream.write(data);
    })
    .on('error', (err) => {
      console.error('转码错误:', err);
    })
    .run();
});

注意事项

  • 前端采集时指定sampleRate:16000和channelCount:1,与Google API配置一致,避免额外转码
  • 前端使用ScriptProcessor时,缓冲区大小不要过大,否则会增加延迟
  • 后端优先选择轻量转码库,比ffmpeg延迟更低

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 11:59:53