使用JavaScript录制音频对接微软认知服务遇SPXERR_INVALID_HEADER错误
解决SPXERR_INVALID_HEADER异常:录制音频转WAV格式问题
问题根源
你遇到的SPXERR_INVALID_HEADER异常,核心原因是前端保存的文件并非真正的WAV格式。虽然你将Blob的type设为audio/wav,但MediaRecorder实际录制的是WebM格式音频(多数浏览器不支持直接录制WAV,你代码里也注释了设置audio/wav不生效),只是强行修改了Blob的类型标识,文件内容仍为WebM编码,导致认知服务Speech SDK识别文件头时出错。
解决方案
需要在前端将录制的WebM音频解码后,重新编码为标准WAV格式,再传给后端。以下是修改后的完整前端代码:
function startRecording(btn) { var countdown = 5; var audioChunks = []; var mediaRecorder = null; var options = { mimeType: 'audio/webm' }; navigator.mediaDevices.getUserMedia({ audio: true }) .then(function (stream) { mediaRecorder = new MediaRecorder(stream, options); mediaRecorder.start(); setTimeout(function () { mediaRecorder.stop(); stream.getTracks().forEach(track => track.stop()); btn.text("Waiting for the results"); }, countdown * 1000); mediaRecorder.addEventListener("dataavailable", function (event) { audioChunks.push(event.data); }); mediaRecorder.addEventListener("stop", async function () { // 把WebM Blob转换为AudioBuffer const webmBlob = new Blob(audioChunks, { type: 'audio/webm' }); const arrayBuffer = await webmBlob.arrayBuffer(); const audioContext = new AudioContext(); const audioBuffer = await audioContext.decodeAudioData(arrayBuffer); // 将AudioBuffer编码为标准WAV格式 const wavBlob = audioBufferToWav(audioBuffer); var formData = new FormData(); formData.append('audioBlob', wavBlob, 'recording.wav'); $.ajax({ url: '@Url.Action("ProcessAudio", "Home")', type: 'POST', data: formData, processData: false, contentType: false, success: function (data) { $('#modalContent').html(data); $('#myModal').modal('show'); }, error: function () { console.log('Error processing audio'); } }); }); }) .catch(function (error) { console.log('Error starting recording:', error); }); } // 辅助函数:将AudioBuffer转换为WAV格式Blob function audioBufferToWav(buffer) { const length = buffer.length * buffer.numberOfChannels * 2 + 44; const arrayBuffer = new ArrayBuffer(length); const view = new DataView(arrayBuffer); 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); // fmt子块长度 setUint32(16); // 音频格式(PCM) setUint16(1); // 声道数 setUint16(buffer.numberOfChannels); // 采样率 setUint32(buffer.sampleRate); // 字节率 setUint32(buffer.sampleRate * 2 * buffer.numberOfChannels); // 块对齐 setUint16(buffer.numberOfChannels * 2); // 位深度(16位) setUint16(16); // data子块标识 setUint32(0x61746164); // data子块长度 setUint32(length - pos - 4); // 写入PCM音频数据 for (let i = 0; i < buffer.numberOfChannels; i++) { channels.push(buffer.getChannelData(i)); } while (pos < length) { for (let i = 0; i < buffer.numberOfChannels; i++) { 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([arrayBuffer], { type: 'audio/wav' }); }
说明
- 后端代码无需修改,现在前端传递的是标准PCM编码的WAV文件,符合Speech SDK的格式要求。
- 可以用音频工具(如Audacity)打开保存的文件,验证其实际编码为PCM 16位WAV。
内容的提问来源于stack exchange,提问作者CitrusBoy
相关产品推荐
相关产品推荐

