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

为Blob添加元数据:解决MediaRecorder分块音频处理异常问题

实时音频转文本的MediaRecorder数据块问题

我用MediaRecorder获取用户麦克风输入,每次收到数据就实时发送到后端处理,无需等待音频文件完整。但将音频发送给Whisper API时,只有第一块数据能正常识别,后续数据块均无法正常处理。排查后发现,MediaRecorder仅在第一个Blob中设置元数据,导致后续音频文件损坏。

我尝试过用node-lame(需依赖外部程序)、wavefile(不支持缓冲区)转换Blob,但都未成功。目前想到两种解决方案:

  • 为每个Blob添加元数据,但查阅资料后仍不知如何手动设置音频头部
  • 找到合理调用MediaRecorder.start和MediaRecorder.stop的方式,避免数据丢失

以下是当前代码:

前端组件代码(speech-to-text.component.ts)

import { Component, OnInit } from '@angular/core';
import { SpeechService } from '../service/speech.service';

@Component({
  selector: 'app-speech-to-text',
  templateUrl: './speech-to-text.component.html',
  styleUrls: ['./speech-to-text.component.scss'],
})
export class SpeechToTextComponent implements OnInit {
  mediaRecorder!: MediaRecorder;

  constructor(private speechService: SpeechService) {}

  async ngOnInit() {
    const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
    this.mediaRecorder = new MediaRecorder(stream);

    this.mediaRecorder.addEventListener('dataavailable', (event) => {
      this.speechService
        .sendAudioBlob(new Blob([event.data], { type: 'audio/mp3' }))
        .then((result) => console.log(result));
    });
  }

  record() {
    this.mediaRecorder.start(1000);
  }

  stop() {
    this.mediaRecorder.stop();
  }
}

前端服务代码(speech.service.ts)

import { Injectable } from '@angular/core';
import { AppHttpClientService } from './app-http-client/app-http-client.service';

@Injectable({
  providedIn: 'root',
})
export class SpeechService {
  constructor(private httpService: AppHttpClientService) {}

  async sendAudioBlob(blob: Blob) {
    const formData = new FormData();
    formData.append('blob', blob);
    const result = await this.httpService.post('speeches/text', formData);
    return result;
  }
}

后端服务代码(speeches.service.js)

const openai = require('../models/openai');
const fs = require('fs');
const formidable = require('formidable');
const { MODEL_NAME } = require('./speech.constants');

const fileDirectory = `${__dirname}/../../../uploads`;

const SpeechesService = {
  speechToText: async (req) => {
    const fileName = `${Date.now()}.mp3`;

    const form = new formidable.Formidable({
      uploadDir: fileDirectory,
      filename: () => fileName,
    });

    await form.parse(req);
    const resp = await openai
      .createTranscription(fs.createReadStream(`${fileDirectory}/${fileName}`), MODEL_NAME)
      .catch();

    SpeechesService.deleteAudio(fileName);
    return resp.data;
  },

  deleteAudio: async (fileName) => fs.unlink(`${fileDirectory}/${fileName}`, () => {}),
};

module.exports = SpeechesService;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 02:15:17