使用JavaScript在Chrome中将音频录制为.wav

使用JavaScript在Chrome中将音频录制为.wav,javascript,google-chrome,audio,wav,Javascript,Google Chrome,Audio,Wav,我正在创建一个网页,记录来自用户设备的音频,并将其发送到Microsoft认知语音服务进行语音到文本转换。到目前为止,我已经能够创建和回放用JavaScript制作的.ogg文件,但我需要以.wav格式获取这些文件 Blob类型的音频/wav不能依赖,因为不是所有浏览器都支持它,至少我不支持。blob被发送到Django服务器并由其存储。当我试图用PySoundFile打开这些文件时,我得到一个错误,该文件包含未知格式的数据。正在使用新BlobChunk创建Blob,{type:'audio/o

我正在创建一个网页,记录来自用户设备的音频,并将其发送到Microsoft认知语音服务进行语音到文本转换。到目前为止,我已经能够创建和回放用JavaScript制作的.ogg文件,但我需要以.wav格式获取这些文件

Blob类型的音频/wav不能依赖,因为不是所有浏览器都支持它,至少我不支持。blob被发送到Django服务器并由其存储。当我试图用PySoundFile打开这些文件时,我得到一个错误,该文件包含未知格式的数据。正在使用新BlobChunk创建Blob,{type:'audio/ogg;codecs=opus}并使用保存。从MediaBloomDataChunks中可以找到MediaBloomDataChunks

更新: 我放弃了使用,转而选择了。同样,Firefox可以工作,但Chrome不能。Chrome似乎在音频的末尾得到了一小部分,并在音频的长度上重复这一点。这是我使用的代码,它是基于Matt Diamond在。使用他的作品的演示可以在上看到,它可以在Firefox和Chrome上为我工作。另外,我的原始代码在一个类中,但我不想包含整个类。如果我在翻译中犯了任何语法错误,我深表歉意

let recBuffers = [[], []];
let recLength = 0;
let numChannels = 2;
let listening = false;
let timeout = null;
let constraints = {
    audio: true
};
let failedToGetUserMedia = false;

if (navigator.getUserMedia) {
    navigator.getUserMedia(constraints, (stream) => {
        init(stream);
    }, (err) => {
        alert('Unable to access audio.\n\n' + err);
        console.log('The following error occurred: ' + err);
        failedToGetUserMedia = true;
    });
}
else if (navigator.mediaDevices.getUserMedia) {
    navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
        init(stream);
    }).catch((err) => {
        alert('Unable to access audio.\n\n' + err);
        console.log('The following error occurred: ' + err);
        failedToGetUserMedia = true;
    });
}
else failedToGetUserMedia = true;

function beginRecording() {
    recBuffers = [[], []];
    recLength = 0;
    listening = true;
    timeout = setTimeout(() => {
        endRecording();
    }, maxTime);
}

function endRecording() {
    clearTimeout(timeout);
    timeout = null;
    exportWAV();
}

function init(stream) {
    let audioContext = new AudioContext();
    let source = audioContext.createMediaStreamSource(stream);
    let context = source.context;
    let node = (context.createScriptProcessor || context.createJavaScriptNode).call(context, 4096, numChannels, numChannels);
    node.onaudioprocess = (e) => {
        if (!listening) return;

        for (var i = 0; i < numChannels; i++) {
            recBuffers[i].push(e.inputBuffer.getChannelData(i));
        }

        recLength += recBuffers[0][0].length;
    }
    source.connect(node);
    node.connect(context.destination);
}

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

function interleave(inputL, inputR) {
    let len = inputL.length + inputR.length;
    let result = new Float32Array(len);

    let index = 0;
    let inputIndex = 0;

    while (index < len) {
        result[index++] = inputL[inputIndex];
        result[index++] = inputR[inputIndex];
        inputIndex++;
    }

    return result;
}

function exportWAV() {
    let buffers = [];
    for (var i = 0; i < numChannels; i++) {
        buffers.push(mergeBuffers(recBuffers[i], recLength));
    }

    let interleaved = numChannels == 2 ? interleave(buffers[0], buffers[1]) : buffers[0];
    let dataView = encodeWAV(interleaved);
    let blob = new Blob([ dataView ], { type: 'audio/wav' });
    blob.name = Math.floor((new Date()).getTime() / 1000) + '.wav';

    listening = false;

    return blob;
}

function floatTo16BitPCM(output, offset, input){
    for (var i = 0; i < input.length; i++, offset+=2){
        var s = Math.max(-1, Math.min(1, input[i]));
        output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
    }
}

function writeString(view, offset, string){
    for (var i = 0; i < string.length; i++) {
        view.setUint8(offset + i, string.charCodeAt(i));
    }
}

function encodeWAV(samples){
    var buffer = new ArrayBuffer(44 + samples.length * 2);
    var view = new DataView(buffer);

    /* RIFF identifier */
    writeString(view, 0, 'RIFF');
    /* file length */
    view.setUint32(4, 36 + samples.length * 2, true);
    /* RIFF type */
    writeString(view, 8, 'WAVE');
    /* format chunk identifier */
    writeString(view, 12, 'fmt ');
    /* format chunk length */
    view.setUint32(16, 16, true);
    /* sample format (raw) */
    view.setUint16(20, 1, true);
    /* channel count */
    view.setUint16(22, numChannels, true);
    /* sample rate */
    view.setUint32(24, context.sampleRate, true);
    /* byte rate (sample rate * block align) */
    view.setUint32(28, context.sampleRate * 4, true);
    /* block align (channel count * bytes per sample) */
    view.setUint16(32, numChannels * 2, true);
    /* bits per sample */
    view.setUint16(34, 16, true);
    /* data chunk identifier */
    writeString(view, 36, 'data');
    /* data chunk length */
    view.setUint32(40, samples.length * 2, true);

    floatTo16BitPCM(view, 44, samples);

    return view;
}

if (!failedToGetUserMedia) beginRecording();

当然,实际值是不同的。这只是一个例子来说明这一点。

最初,我使用MediaRecorder获取音频,并从音频中创建一个Blob,类型为audio/wav。这在Chrome中不起作用,但在Firefox中起作用。我放弃了,开始使用ScriptProcessorNode。同样,它在Firefox上有效,但在Chrome上无效。经过一些调试,很明显,在Chrome上,recBuffers中充满了交替列表。我仍然不知道为什么会发生这种情况,但我的猜测是,自从spread语法解决了这个问题以来,它类似于作用域或缓存。从以下位置更改onaudioprocess中的一行。recBuffers[i]。Push.inputBuffer.getChannelDatai;对此.recBuffers[i].push[…e.inputBuffer.getChannelDatai];工作。

最初,我使用MediaRecorder获取音频,并使用音频/wav类型从所述音频创建一个Blob。这在Chrome中不起作用,但在Firefox中起作用。我放弃了,开始使用ScriptProcessorNode。同样,它在Firefox上有效,但在Chrome上无效。经过一些调试,很明显,在Chrome上,recBuffers中充满了交替列表。我仍然不知道为什么会发生这种情况,但我的猜测是,自从spread语法解决了这个问题以来,它类似于作用域或缓存。从以下位置更改onaudioprocess中的一行。recBuffers[i]。Push.inputBuffer.getChannelDatai;对此.recBuffers[i].push[…e.inputBuffer.getChannelDatai];已完成。

首先使用ScriptProcessorNode录制原始PCM,而不是稍后进行转换。我想先录制为.wav格式,但我的浏览器不支持音频/wav blob类型,根据,ScriptProcessorNode已被弃用。谢谢你的建议。如果您还有其他问题,请告诉我。您的浏览器不关心blob的类型。。。您实际上指的是您的浏览器不支持MediaRecorder的音频/wav。而且,虽然ScriptProcessorNode已被弃用。。。勉强如此。在AudioWorkletNode得到更好的支持之前,他们不会删除它,这时您可以直接使用它。或者,更可能的情况是,您的浏览器首先支持音频/wav。在他们真正破坏ScriptProcessorNode之前还需要很多年。你能提供一些关于如何使用ScriptProcessorNode以wav格式录制音频的见解吗?将Blob类型设置为“音频/wav”;codecs=ms_pcm'不起作用,我无法让Mozilla的ScriptProcessorNode上的示例起作用,我只能让Chrome来播放音频文件。PySoundFile无法打开它们,Windows Media Player也无法打开。请处理audioprocess事件。e、 inputBuffer.getChannelDatabuffer将为您获取原始32位PCM数据。从那里,您可以按原样将其写入自己的WAV文件,也可以将其从32位float转换为16位little endian。另外,别忘了检查音频上下文的采样率,并将其包含在WAV文件中。与其以后进行转换,不如先使用ScriptProcessorNode录制原始PCM。我想先录制为.WAV格式,但我的浏览器不支持音频/WAV blob类型,根据,ScriptProcessorNode已弃用。谢谢你的建议。如果您还有其他问题,请告诉我。您的浏览器不关心blob的类型。。。您实际上指的是您的浏览器不支持MediaRecorder的音频/wav。而且,虽然ScriptProcessorNode已被弃用。。。勉强如此。在AudioWorkletNode得到更好的支持之前,他们不会删除它,这时您可以直接使用它。或者,更可能的情况是,您的浏览器首先支持音频/wav。
在他们真正破坏ScriptProcessorNode之前还需要很多年。你能提供一些关于如何使用ScriptProcessorNode以wav格式录制音频的见解吗?将Blob类型设置为“音频/wav”;codecs=ms_pcm'不起作用,我无法让Mozilla的ScriptProcessorNode上的示例起作用,我只能让Chrome来播放音频文件。PySoundFile无法打开它们,Windows Media Player也无法打开。请处理audioprocess事件。e、 inputBuffer.getChannelDatabuffer将为您获取原始32位PCM数据。从那里,您可以按原样将其写入自己的WAV文件,也可以将其从32位float转换为16位little endian。另外,不要忘记检查音频上下文的采样率,并将其包含在WAV文件中。啊,是的,当调用getChannelData时,返回的内容可以被底层实现重用。您需要将其复制为快照,而这正是您有效地使用排列所做的。抢手货有各种各样的有趣的事情,像这样的网络音频API。幸运的是,它不像以前那么奇怪了!旧版本的Chrome过去需要全局范围内的某些东西,否则它们会过早地被垃圾收集,破坏一切。幸运的是,大部分问题现在已经解决了。啊,是的,当您调用getChannelData时,返回的内容可以被底层实现重用。您需要将其复制为快照,而这正是您有效地使用排列所做的。抢手货有各种各样的有趣的事情,像这样的网络音频API。幸运的是,它不像以前那么奇怪了!旧版本的Chrome过去需要全局范围内的某些东西,否则它们会过早地被垃圾收集,破坏一切。幸运的是,大部分问题现在已经解决了。
recBuffers = [[
    [2, 3],
    [7, 1],
    [2, 3],
    [7, 1],
    [2, 3],
    [7, 1],
    [2, 3],
    [7, 1],
    [2, 3],
    [7, 1]
], [
    [5, 4],
    [6, 8],
    [5, 4],
    [6, 8],
    [5, 4],
    [6, 8],
    [5, 4],
    [6, 8],
    [5, 4],
    [6, 8]
]]