【问题标题】:Obtaining microphone PCM data from getChannelData method using WebAudio API doesn't work使用 WebAudio API 从 getChannelData 方法获取麦克风 PCM 数据不起作用
【发布时间】:2020-04-02 19:00:45
【问题描述】:

我正在尝试使用 Web Audio API 从麦克风获取原始 PCM 样本。经过一番研究,似乎我应该得到"a Float32Array containing the PCM data associated with the channel"。该流程对我有用,但是当我尝试将 PCM 样本放入 WAV 文件时,我得到随机噪音,而不是我在录音时对麦克风实际说话的内容。下面是使用 AudioBuffer.getChannelData() 获取 PCM 数据的代码 sn-p,您可以将其直接粘贴到浏览器控制台中:

let recLength = 0;
let recBuffers = [];

let bufferLen = 4096;
let numChannels = 1;
let mimeType = 'audio/wav';

window.AudioContext = window.AudioContext || window.webkitAudioContext;
let audio_context = new AudioContext();

let audio_stream;

navigator.mediaDevices.getUserMedia({audio: true}).then(function (stream)
{
    audio_stream = stream;
});

// wait a sec or two

let source = audio_context.createMediaStreamSource(audio_stream);
let context = source.context;
let sampleRate = context.sampleRate; // you will need to know this number for creating a WAV file.

let node = (context.createScriptProcessor || context.createJavaScriptNode).call(context, bufferLen, numChannels, numChannels);

node.onaudioprocess = function(e)
{
    const inputBuffer = e.inputBuffer.getChannelData(0);

    recBuffers.push(inputBuffer);
    recLength += inputBuffer.length;
};

source.connect(node);
node.connect(context.destination);

// wait 10 seconds or so, while speaking into microphone

node.disconnect();

function mergeBuffers()
{
    let result = new Float32Array(recLength);
    let offset = 0;
    for (let i = 0; i < recBuffers.length; i++) {
        result.set(recBuffers[i], offset);
        offset += recBuffers[i].length;
    }
    return result;
}

let mergedBuffer = mergeBuffers();

let normalBuffer = [];
for (let i = 0; i < mergedBuffer.length; i++)
{
    normalBuffer.push(mergedBuffer[i]);
}

JSON.stringify(normalBuffer);

现在,如果我复制最后一行的字符串输出,并将其传递到任何生成 WAV 文件的库,则输出 WAV 文件只是随机噪声。如果你想自己复制这个,我使用this NodeJS library 来编写 WAV 文件:

let arr = [1,2,3]; // replace this array with the string output of normalBuffer from above

samples = [[]];
for (let i = 0; i < arr.length; i++)
{
    samples[0].push(arr[i]);
}

const fs = require("fs");
const WaveFile = require("wavefile");

let wav = new WaveFile();
wav.fromScratch(1, 44100, "32f", samples); // the sampling rate (second parameter) could be different on your machine, but you can print it out in the code snippet above to find out
fs.writeFileSync("test.wav", wav.toBuffer());

我也尝试将样本转换为无符号的 16 位整数,但我仍然遇到同样的问题,我尝试将样本乘以某个常数,以防录音音量太低,但也无济于事。

【问题讨论】:

    标签: javascript web-audio-api sampling microphone getusermedia


    【解决方案1】:

    问题(至少在 Chrome 中)是 ScriptProcessorNode 不断重复使用相同的底层音频缓冲区。这意味着您的recBuffers 数组中的每个Float32Array 都指向相同的内存。您可以通过复制数据来避免这种情况。

    线...

    const inputBuffer = e.inputBuffer.getChannelData(0);
    

    ...变成...

    const inputBuffer = new Float32Array(bufferLen);
    
    e.inputBuffer.copyFromChannel(inputBuffer, 0);
    

    请记住,这在 Safari 中不起作用,因为它还没有 copyFromChannel 方法。需要手动复制缓冲区。

    如果您只想录制 wav 文件,重用现有库(如 extendable-media-recorder)可能会更容易。但我猜你只是创建了一个 wav 文件来调试问题。

    【讨论】:

    • 谢谢克里斯古坦丁!我正要自己发布解决方案,因为我终于偶然发现了blog post,它解决了相同的底层缓冲区被覆盖并从getChannelData() 返回的问题。无论如何,我很高兴我发布了这个问题,因为 MDN documentation 提到 getChannelData() “返回包含 PCM 数据的 Float32Array”,这是一种误导性的描述。
    • 您好 Ragy,很高兴您找到了解决方案。如果您发现一些误导性文本,您可以随时编辑 MDN 页面。 wiki.developer.mozilla.org/docs/Web/API/AudioBuffer/…
    • 哦,哇,非常感谢 chrisguttandin!我从来没有真正知道这一点。我会尝试在 MDN 上编辑页面。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-20
    • 1970-01-01
    • 2013-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多