簡體   English   中英

使用 JavaScript 在 Chrome 中錄制音頻 as.wav

[英]Use JavaScript to record audio as .wav in Chrome

我正在構建一個網頁,該網頁記錄來自用戶設備的音頻,並將其發送到 Microsoft 的認知語音服務以進行語音到文本的轉換。 到目前為止,我已經能夠創建和播放 JavaScript 制作的 back.ogg 文件,但我需要獲取 .wav 格式的文件。

不能依賴 Blob 類型的audio/wav ,因為並非所有瀏覽器都支持它(至少我的瀏覽器不支持)。 Blob 被發送到 Django 服務器並由其存儲。 當我嘗試使用 PySoundFile 打開這些文件時,我收到一條錯誤消息,指出File contains data in an unknown format 正在使用new Blob(chunks, { type: 'audio/ogg; codecs=opus' })創建 blob,並使用django.db.FileField保存。 Blob 塊來自MediaRecorder.ondataavailable

更新:我放棄了使用MediaRecorder並選擇了ScriptProcessorNode 同樣,Firefox 有效,但 Chrome 無效。 似乎 Chrome 在音頻的末尾得到了一小部分,並在音頻的長度上重復了這一點。 這是我使用的代碼,它基於 Matt Diamond 在github.com/mattdiamond/Recorderjs的工作。 可以在webaudiodemos.appspot.com/AudioRecorder/index.html上看到使用他的作品的演示,它適用於 Firefox 和 Chrome。 另外,我的原始代碼在 class 中,但我不想包含整個 class。 如果我在翻譯中犯了任何語法錯誤,我深表歉意。

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();

更新:我已經確認,當 Chrome 緩沖區的值作為 Firefox 上的交錯輸入提供時,output 與 Chrome 的 output 相同。 這表明 Chrome 沒有用正確的值填充 recBuffers。 事實上,當我查看 Chrome 上的 recBuffers 時,每個通道都充滿了交替列表。 例如:

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]
]]

當然,實際值不同。 這只是一個例子來說明這一點。

最初,我使用 MediaRecorder 獲取音頻並從所述音頻創建一個 Blob,類型為audio/wav 這在 Chrome 中不起作用,但在 Firefox 中。 我放棄了,開始使用 ScriptProcessorNode。 同樣,它適用於 Firefox,但不適用於 Chrome。 經過一些調試,很明顯在 Chrome 上,recBuffers 被交替列表填充。 我仍然不確定為什么會發生這種情況,但我的猜測是類似於范圍或緩存,因為傳播語法解決了它。 this.recBuffers[i].push(e.inputBuffer.getChannelData(i));更改 onaudioprocess 中的一行this.recBuffers[i].push([...e.inputBuffer.getChannelData(i)]); 工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM