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

使用MediaRecorder录制音频调用Google Speech-to-Text返回空转录问题

问题:MediaRecorder录制音频调用Google Speech-to-Text API返回空转录结果

我基于Next.js/React开发语音转文字功能,使用Google Speech-to-Text API。目前遇到的问题是:用浏览器MediaRecorder API录制的音频,调用API后返回空转录结果;但用Audacity录制的音频能正常得到转录内容。

客户端代码:

const startRecording = () => {
    navigator.mediaDevices
      .getUserMedia({ audio: true })
      .then((stream) => {
        const recorder = new MediaRecorder(stream, {
          mimeType: "audio/webm; codecs=opus",
          bitsPerSecond: 128000,
          sampleRate: 48000,
          echoCancellation: true,
          noiseSuppression: true,
          channelCount: 1,
        });
        const chunks = [];

        recorder.addEventListener("dataavailable", (event) => {
          chunks.push(event.data);
        });

        recorder.addEventListener("stop", () => {
          const blob = new Blob(chunks, { type: "audio/wav" });
          const url = URL.createObjectURL(blob);
          setAudioUrl(url);
          setRecording(false);
          setAudioBlob(blob); // Update the audioBlob state variable
        });

        recorder.start();
        setMediaRecorder(recorder);
        setRecording(true);
      })
      .catch((error) => {
        console.log(error);
      });
  };

服务端代码:

async function transcribeContextClasses() {
      const file = fs.readFileSync("public/audio/1680169074745_audio.wav");
      const audioBytes = file.toString("base64");
      
      const audio = {
        content: audioBytes,
      };

      const speechContext = {
        phrases: ["$TIME"],
      };

      const config = {
        encoding: "LINEAR16",
        sampleRateHertz: 48000,
        languageCode: "en-US",
        speechContexts: [speechContext],
      };

      const request = {
        config: config,
        audio: audio,
      };

      const [response] = await speechClient.recognize(request);
      const transcription = response.results
        .map((result) => result.alternatives[0].transcript)
        .join("\n");
      console.log(`Transcription: ${transcription}`);
    }

问题原因

你在客户端犯了一个关键错误:MediaRecorder实际录制的是Opus编码的WebM音频,但停止录制时只是把Blob的type标记为audio/wav,并没有真正将音频编码转换为LINEAR16(PCM编码的WAV格式)。而Google Speech-to-Text API的配置中encoding: "LINEAR16",它无法解析Opus编码的音频,因此返回空转录结果。

Audacity导出的是标准LINEAR16编码的WAV文件,完全符合API的要求,所以能正常识别。


解决方案

需要在客户端将录制的Opus/WebM音频转换为LINEAR16编码的WAV格式,再传给服务端。可以通过Web Audio API实现编码转换:

修正后的客户端代码:

// 音频格式转换函数:Opus/WebM -> LINEAR16 WAV
const convertOpusToLinear16Wav = async (opusBlob) => {
  return new Promise((resolve, reject) => {
    const audioContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 48000 });
    const fileReader = new FileReader();

    fileReader.onload = async (e) => {
      try {
        const arrayBuffer = e.target.result;
        const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
        
        // 提取16位PCM数据
        const length = audioBuffer.length * 2; // 单声道,每个采样占2字节
        const buffer = new ArrayBuffer(length);
        const view = new DataView(buffer);
        let offset = 0;
        
        const channelData = audioBuffer.getChannelData(0);
        for (const sample of channelData) {
          // 将浮点数采样转换为16位整数
          const intSample = sample < 0 ? sample * 0x8000 : sample * 0x7FFF;
          view.setInt16(offset, intSample, true); // 小端字节序
          offset += 2;
        }

        // 生成标准WAV文件头
        const wavHeader = createWavHeader(48000, 1, length);
        const wavBlob = new Blob([wavHeader, view], { type: "audio/wav" });
        resolve(wavBlob);
      } catch (err) {
        reject(err);
      }
    };

    fileReader.onerror = reject;
    fileReader.readAsArrayBuffer(opusBlob);
  });
};

// 生成WAV文件头的辅助函数
const createWavHeader = (sampleRate, channels, dataByteLength) => {
  const buffer = new ArrayBuffer(44);
  const view = new DataView(buffer);

  // RIFF块
  view.setUint8(0, 82); view.setUint8(1, 73); view.setUint8(2, 70); view.setUint8(3, 70);
  view.setUint32(4, dataByteLength + 36, true);
  // WAVE块
  view.setUint8(8, 87); view.setUint8(9, 65); view.setUint8(10, 86); view.setUint8(11, 69);
  // fmt子块
  view.setUint8(12, 102); view.setUint8(13, 109); view.setUint8(14, 116); view.setUint8(15, 32);
  view.setUint32(16, 16, true); // PCM格式
  view.setUint16(20, 1, true); // 音频格式(PCM=1)
  view.setUint16(22, channels, true);
  view.setUint32(24, sampleRate, true);
  view.setUint32(28, sampleRate * channels * 2, true); // 字节率
  view.setUint16(32, channels * 2, true); // 块对齐
  view.setUint16(34, 16, true); // 位深
  // data子块
  view.setUint8(36, 100); view.setUint8(37, 97); view.setUint8(38, 116); view.setUint8(39, 97);
  view.setUint32(40, dataByteLength, true);

  return buffer;
};

// 修改录制停止后的处理逻辑
const startRecording = () => {
    navigator.mediaDevices
      .getUserMedia({ audio: true })
      .then((stream) => {
        const recorder = new MediaRecorder(stream, {
          mimeType: "audio/webm; codecs=opus",
          bitsPerSecond: 128000,
          echoCancellation: true,
          noiseSuppression: true,
          channelCount: 1,
        });
        const chunks = [];

        recorder.addEventListener("dataavailable", (event) => {
          chunks.push(event.data);
        });

        recorder.addEventListener("stop", async () => {
          const opusBlob = new Blob(chunks, { type: "audio/webm; codecs=opus" });
          try {
            // 转换为LINEAR16编码的WAV
            const wavBlob = await convertOpusToLinear16Wav(opusBlob);
            const url = URL.createObjectURL(wavBlob);
            setAudioUrl(url);
            setRecording(false);
            setAudioBlob(wavBlob); // 保存转换后的WAV文件
          } catch (err) {
            console.error("音频转换失败:", err);
          }
        });

        recorder.start();
        setMediaRecorder(recorder);
        setRecording(true);
      })
      .catch((error) => {
        console.log(error);
      });
  };

关键修改点:

  1. 新增convertOpusToLinear16Wav函数:利用Web Audio API解码Opus音频,将浮点数采样转换为16位PCM整数数据。
  2. 新增createWavHeader函数:生成符合标准的WAV文件头,确保输出的音频是LINEAR16编码格式。
  3. 调整录制停止逻辑:保留原始Opus格式Blob,转换为标准WAV后再更新状态变量。

处理后,传给服务端的音频完全符合Google Speech-to-Text API的要求,即可正常返回转录结果。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 11:02:05