解决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")
额外注意事项
- 前端原代码的
GetAudio函数缺少闭合的});,修正后的代码已补上。 - 后端原代码最后返回
render(request,'get_sound.html')不符合AJAX预期,改为返回HttpResponse("true")或JsonResponse更合适。 - 客户端转换方案依赖
AudioContext,需要现代浏览器支持,IE无法兼容。
内容的提问来源于stack exchange,提问作者Harsh
相关产品推荐
相关产品推荐

