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

向Azure Speech-To-Text API发送字节数组时遇NoMatch问题求助

问题分析与解决

核心问题

浏览器的MediaRecorder即使指定type: 'audio/wav',也无法保证生成标准的PCM 16kHz单声道WAV文件——大部分现代浏览器(如Chrome)默认会用WebM格式编码音频,而非Azure Speech-To-Text要求的未压缩PCM WAV格式。这就是录制的音频发送后触发NoMatch错误的原因,而测试数据是符合要求的格式,所以能正常工作。

解决方案步骤

1. 替换MediaRecorder,用AudioContext处理原始音频数据

通过AudioContext获取原始PCM音频样本,手动封装成标准WAV格式,确保匹配Azure的要求。修改后的录制逻辑如下:

let languageCode = "da-DK";
let audioContext;
let scriptProcessor;
let inputNode;
let audioBuffer = [];
let isRecording = false;

function startRecording() {
    const constraints = {
        audio: {
            sampleRate: 16000,
            channelCount: 1,
            echoCancellation: false
        }
    };

    navigator.mediaDevices.getUserMedia(constraints)
        .then(function (stream) {
            audioContext = new AudioContext({ sampleRate: 16000 });
            inputNode = audioContext.createMediaStreamSource(stream);
            
            // 创建ScriptProcessor处理音频数据
            scriptProcessor = audioContext.createScriptProcessor(4096, 1, 1);
            
            inputNode.connect(scriptProcessor);
            scriptProcessor.connect(audioContext.destination);

            scriptProcessor.onaudioprocess = function(e) {
                // 获取单声道PCM数据并转换为16位整数
                const inputData = e.inputBuffer.getChannelData(0);
                const int16Array = new Int16Array(inputData.length);
                for (let i = 0; i < inputData.length; i++) {
                    let sample = inputData[i] * 32767;
                    int16Array[i] = sample < -32768 ? -32768 : (sample > 32767 ? 32767 : sample);
                }
                audioBuffer.push(int16Array);
            };

            recordButton.disabled = true;
            stopButton.disabled = false;
            playButton.disabled = true;
        })
        .catch(function (err) {
            console.error('录音错误: ' + err);
        });
}

function stopRecording() {
    inputNode.disconnect();
    scriptProcessor.disconnect();
    audioContext.close();

    // 合并所有音频数据
    const totalLength = audioBuffer.reduce((sum, buffer) => sum + buffer.length, 0);
    const combinedBuffer = new Int16Array(totalLength);
    let offset = 0;
    for (const buffer of audioBuffer) {
        combinedBuffer.set(buffer, offset);
        offset += buffer.length;
    }

    // 封装成标准WAV文件
    const wavBlob = createWavBlob(combinedBuffer, 16000, 1);
    const reader = new FileReader();
    reader.onload = function () {
        const audioBytes = new Uint8Array(reader.result);
        const base64String = btoa(String.fromCharCode.apply(null, audioBytes));
        voiceToText(languageCode, base64String);
    };
    reader.readAsArrayBuffer(wavBlob);

    // 重置缓冲区
    audioBuffer = [];
    recordButton.disabled = false;
    stopButton.disabled = true;
    playButton.disabled = false;
}

// 手动生成标准WAV格式Blob
function createWavBlob(int16Array, sampleRate, channels) {
    const buffer = new ArrayBuffer(44 + int16Array.length * 2);
    const view = new DataView(buffer);

    // 写入WAV文件头
    view.setUint8(0, 0x52); view.setUint8(1, 0x49); view.setUint8(2, 0x46); view.setUint8(3, 0x46);
    view.setUint32(4, 44 + int16Array.length * 2, true);
    view.setUint8(8, 0x57); view.setUint8(9, 0x41); view.setUint8(10, 0x56); view.setUint8(11, 0x45);
    view.setUint8(12, 0x66); view.setUint8(13, 0x6D); view.setUint8(14, 0x74); view.setUint8(15, 0x20);
    view.setUint32(16, 16, true); view.setUint16(20, 1, true); 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);
    view.setUint8(36, 0x64); view.setUint8(37, 0x61); view.setUint8(38, 0x74); view.setUint8(39, 0x61);
    view.setUint32(40, int16Array.length * 2, true);

    // 写入PCM数据
    const dataView = new Uint8Array(buffer, 44);
    dataView.set(new Uint8Array(int16Array.buffer));

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

// 保留原voiceToText函数
function voiceToText(myLanguageCode, wavData) {
    $.ajax({
        type: "POST",
        url: "https://localhost:7058/api/Conversation/postVoiceToString",
        contentType: "application/json",
        data: JSON.stringify({
            LanguageCode: myLanguageCode,
            WAV: wavData
        }),
        success: function (result) {
            console.log(result);
        },
        error: function (req, status, error) {
            console.log(status);
        }
    });
}

2. 验证音频格式

录制完成后,可通过以下方式确认格式合规:

  • 将生成的Blob保存为本地文件,用Audacity等工具打开,检查是否为16kHz、单声道、16位PCM格式。
  • 查看自研API日志,确认转发给Azure的音频数据格式与测试数据一致。

3. 额外注意事项

  • 确保浏览器支持AudioContext和ScriptProcessorNode(现代浏览器均兼容)。
  • 关闭回声消除(echoCancellation: false),避免音频失真影响识别精度。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 22:22:45