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

解决JavaScript录制音频Blob发送至Django语音识别模块的格式不兼容问题

问题根源

你用MediaRecorder录制的音频默认是WebM格式(通常为Opus编码),而SpeechRecognition的AudioFile仅支持PCM WAV、AIFF或原生FLAC格式,格式不匹配导致了报错。另外你的前端代码在stop事件触发后,没有调用GetAudio函数发送数据,这也是之前传输异常的原因之一。

解决方案:两种思路任选


思路1:客户端将录制的音频转为PCM WAV再发送

这种方法无需后端额外依赖,直接在前端把WebM转成WAV格式后用FormData发送。

修改你的前端代码,重点在stop事件里添加格式转换逻辑:

function start_recording(){
 navigator.mediaDevices.getUserMedia({ audio: true })
 .then(stream => {
 const mediaRecorder = new MediaRecorder(stream);
 console.log(mediaRecorder)
 mediaRecorder.start();
 const audioChunks = [];
 mediaRecorder.addEventListener("dataavailable", event => {
 audioChunks.push(event.data);
 });
 mediaRecorder.addEventListener("stop", async () => {
 console.log("stop is called")
 const audioBlob = new Blob(audioChunks);
 // 把WebM Blob转成PCM WAV Blob
 const wavBlob = await convertWebMToWav(audioBlob);
 // 调用发送函数
 GetAudio(wavBlob);
 });
 stop_button.addEventListener('click',function(){
 console.log(mediaRecorder,'stopped')
 mediaRecorder.stop()
 })
 });
}

// 核心工具函数:WebM转WAV
async function convertWebMToWav(webmBlob) {
  // 读取Blob为ArrayBuffer
  const arrayBuffer = await webmBlob.arrayBuffer();
  // 初始化AudioContext(兼容不同浏览器)
  const audioContext = new (window.AudioContext || window.webkitAudioContext)();
  // 解码WebM音频为AudioBuffer
  const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
  
  // 将AudioBuffer转成WAV格式的Blob
  const length = audioBuffer.length * audioBuffer.numberOfChannels * 2 + 44;
  const arrayBufferWav = new ArrayBuffer(length);
  const view = new DataView(arrayBufferWav);
  const channels = [];
  let offset = 0;
  let pos = 0;

  // 写入WAV文件头
  const setUint16 = (data) => {
    view.setUint16(pos, data, true);
    pos += 2;
  };
  const setUint32 = (data) => {
    view.setUint32(pos, data, true);
    pos += 4;
  };

  // RIFF标识
  setUint32(0x46464952);
  // 文件总长度
  setUint32(length - 8);
  // WAVE标识
  setUint32(0x45564157);
  // fmt子块
  setUint32(0x20746d66);
  // 子块长度(PCM固定为16)
  setUint32(16);
  // 音频格式:PCM=1
  setUint16(1);
  // 声道数
  setUint16(audioBuffer.numberOfChannels);
  // 采样率
  setUint32(audioBuffer.sampleRate);
  // 字节率 = 采样率 * 声道数 * 位深/8(这里位深16,所以是2)
  setUint32(audioBuffer.sampleRate * audioBuffer.numberOfChannels * 2);
  // 块对齐 = 声道数 * 位深/8
  setUint16(audioBuffer.numberOfChannels * 2);
  // 位深16位
  setUint16(16);
  // data子块
  setUint32(0x61746164);
  // 音频数据长度
  setUint32(length - pos - 4);

  // 写入PCM音频数据
  for (let i = 0; i < audioBuffer.numberOfChannels; i++) {
    channels.push(audioBuffer.getChannelData(i));
  }

  while (pos < length) {
    for (let i = 0; i < audioBuffer.numberOfChannels; i++) {
      // 把浮点数采样值转成16位整数
      let sample = Math.max(-1, Math.min(1, channels[i][offset]));
      sample = sample < 0 ? sample * 0x8000 : sample * 0x7FFF;
      view.setInt16(pos, sample, true);
      pos += 2;
    }
    offset++;
  }

  return new Blob([arrayBufferWav], { type: 'audio/wav' });
}

// 修复语法错误的发送函数
function GetAudio(audioBlob){
 console.log('>>>>>>>>>>get audioblob',audioBlob)
 const audioUrl = URL.createObjectURL(audioBlob);
 var form = new FormData();
 form.append('audio', audioBlob);
 let csrftoken = getCookie('csrftoken');
 $.ajax({
 url:"/get_sound/",
 headers: {'X-CSRFToken':csrftoken},
 type:'POST',
 processData:false,
 contentType:false,
 error: function (request, status, error) {
 alert(error);
 }
 }).done(function(response){
 if(response=="true"){
 console.log("true");
 alert("data sent")
 }else{
 console.log("false");
 alert("data was not recieved")
 }
 });
}

// 补充getCookie函数(如果之前未实现)
function getCookie(name) {
  let cookieValue = null;
  if (document.cookie && document.cookie !== '') {
      const cookies = document.cookie.split(';');
      for (let i = 0; i < cookies.length; i++) {
          const cookie = cookies[i].trim();
          if (cookie.substring(0, name.length + 1) === (name + '=')) {
              cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
              break;
          }
      }
  }
  return cookieValue;
}

思路2:后端接收WebM并转为WAV(需要FFmpeg)

如果不想修改前端代码,可以在后端用FFmpeg把上传的WebM文件转成WAV,再交给SpeechRecognition处理。

首先确保服务器安装了FFmpeg(Ubuntu用apt install ffmpeg,CentOS用yum install ffmpeg),然后修改views.py:

import io
import os
import tempfile
import subprocess
from django.http import HttpResponse
import speech_recognition as sr

def get_sound(request):
    print("get sound was called")
    if request.method == 'POST':
        print(request.FILES)
        if 'audio' not in request.FILES:
            return HttpResponse("false")
            
        audio_file = request.FILES['audio']
        
        # 创建临时文件保存上传的WebM
        with tempfile.NamedTemporaryFile(suffix='.webm', delete=False) as temp_webm:
            temp_webm.write(audio_file.read())
            temp_webm_path = temp_webm.name
        
        # 用FFmpeg转成PCM WAV(16位单声道16k采样率,适合语音识别)
        temp_wav_path = temp_webm_path.replace('.webm', '.wav')
        try:
            subprocess.run(
                [
                    'ffmpeg', '-i', temp_webm_path,
                    '-acodec', 'pcm_s16le', '-ar', '16000', '-ac', '1',
                    '-y',  # 覆盖现有文件
                    temp_wav_path
                ],
                check=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
        except subprocess.CalledProcessError as e:
            print(f"FFmpeg转换失败: {e.stderr.decode()}")
            os.unlink(temp_webm_path)
            return HttpResponse("false")
        
        # 处理语音识别
        recognizer = sr.Recognizer()
        try:
            with sr.AudioFile(temp_wav_path) as source:
                print("loading")
                recognizer.adjust_for_ambient_noise(source)
                audio = recognizer.listen(source)
                print("Done recording")
                
                print("Recognizing the text")
                text = recognizer.recognize_google(audio, language="en-US")
                print("Decoded Text : {}".format(text))
        except Exception as ex:
            print(ex)
            return HttpResponse("false")
        finally:
            # 清理临时文件
            os.unlink(temp_webm_path)
            os.unlink(temp_wav_path)
        
        return HttpResponse("true")
    return HttpResponse("false")

额外注意事项

  1. 前端原代码的GetAudio函数缺少闭合的});,修正后的代码已补上。
  2. 后端原代码最后返回render(request,'get_sound.html')不符合AJAX预期,改为返回HttpResponse("true")或JsonResponse更合适。
  3. 客户端转换方案依赖AudioContext,需要现代浏览器支持,IE无法兼容。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 19:02:48