如何将多个 WAV 音频块合并为一个?

时间:2021-06-21 14:52:57

标签: javascript blob

我的问题:

我正在尝试将多个 blob audio 文件合并为一个 blob 并将其下载到页面上。

我尝试了什么:

我尝试通过以下方式连接音频 blob:

方法 - 1:

const url = window.URL.createObjectURL(new Blob(fullBlobArray), {
                    type: 'audio/*'
                });
const a = document.createElement("a");
                document.body.appendChild(a);
                a.style = "display: none";
                a.href = url;
                a.download = "testing.wav";
                a.click();
                URL.revokeObjectURL(url);
                a.remove();

方法 - 2(使用 - ConcatenateBlobs.js 插件 - ConcatenateJS

ConcatenateBlobs(fullBlobArray, 'audio/wav', function (fullBlob) {
                    const url = window.URL.createObjectURL(fullBlob);
                    const a = document.createElement("a");
                    document.body.appendChild(a);
                    a.style = "display: none";
                    a.href = url;
                    a.download = "testing.wav";
                    a.click();
                    URL.revokeObjectURL(url);
                    a.remove();

                    //Close the window if it downloaded.
                    window.close();

输出解释如下:

如果您有以下音频块:

[audio1, audio2, audio3]

然后,从上面的代码下载后,只有第一个文件(即 Audio )中的 audio1 被播放。但是完整blob的文件大小是audio1 + audio2 + audio3

的总大小

我不知道我哪里出错了。请帮我解决这个问题。

1 个答案:

答案 0 :(得分:0)

终于找到解决办法了!!! 感谢这个 StackOverflow article。非常感谢为本文所做的努力。

感谢评论(@Bergi、@Zac、@Peter Krebs 在评论中)我们需要根据 WAV 格式格式化 blob

用于将多个 WAV 文件合并为一个文件,代码如下:

wav_merger.js

var  _index;

function readFileAsync(blob) {
    return new Promise((resolve, reject) => {
        let reader = new FileReader();

        reader.addEventListener("loadend", function () {
            resolve(reader.result);
        });

        reader.onerror = reject;

        reader.readAsArrayBuffer(blob);
    })
}

function getBufferFromBlobs(blobArray) {
    return new Promise((resolve, reject) => {
        var _arrBytes = [];
        var _promises = [];
        if (blobArray.length > 0) {
            $.each(blobArray, function (index, blob) {
                _index = index;
                var dfd = $.Deferred();
                readFileAsync(blob).then(function (byteArray) {
                    _arrBytes.push(byteArray);
                    dfd.resolve();
                });
                _promises.push(dfd);
            });

            $.when.apply($, _promises).done(function () {
                var _blob = combineWavsBuffers(_arrBytes);
                resolve(_blob);
            });
        }
    });
}

function loadWav(blobArray) {
    return getBufferFromBlobs(blobArray);
    debugger;
    //    .then(function (bufferArray) {
    //    return combineWavsBuffers(bufferArray); //Combine original wav buffer and play
    //});
}

function combineWavsBuffers(bufferArray) {

    if (bufferArray.length > 0) {
        var _bufferLengths = bufferArray.map(buffer => buffer.byteLength);

        // Getting sum of numbers
        var _totalBufferLength = _bufferLengths.reduce(function (a, b) {
            return a + b;
        }, 0);

        var tmp = new Uint8Array(_totalBufferLength);

        //Get buffer1 audio data to create the new combined wav
        var audioData = getAudioData.WavHeader.readHeader(new DataView(bufferArray[0]));
        var _bufferLength = 0;
        $.each(bufferArray, function (index, buffer) {
            //Combine array bytes of original wavs buffers.
            tmp.set(new Uint8Array(buffer), _bufferLength);

            _bufferLength+= buffer.byteLength;
        });
        
        //Send combined buffer and send audio data to create the audio data of combined
        var arrBytesFinal = getWavBytes(tmp, {
            isFloat: false,       // floating point or 16-bit integer
            numChannels: audioData.channels,
            sampleRate: audioData.sampleRate,
        });

        //Create a Blob as Base64 Raw data with audio/wav type
        return new Blob([arrBytesFinal], { type: 'audio/wav; codecs=MS_PCM' });
    }
    return null;
}

//Combine two audio .wav buffers.
function combineWavsBuffers1(buffer1, buffer2) {

    //Combine array bytes of original wavs buffers
    var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
    tmp.set(new Uint8Array(buffer1), 0);
    tmp.set(new Uint8Array(buffer2), buffer1.byteLength);

    //Get buffer1 audio data to create the new combined wav
    var audioData = getAudioData.WavHeader.readHeader(new DataView(buffer1));
    console.log('Audio Data: ', audioData);

    //Send combined buffer and send audio data to create the audio data of combined
    var arrBytesFinal = getWavBytes(tmp, {
        isFloat: false,       // floating point or 16-bit integer
        numChannels: audioData.channels,
        sampleRate: audioData.sampleRate,
    });

    //Create a Blob as Base64 Raw data with audio/wav type
    return new Blob([arrBytesFinal], { type: 'audio/wav; codecs=MS_PCM' });
}

//Other functions //////////////////////////////////////////////////////////////

// Returns Uint8Array of WAV bytes
function getWavBytes(buffer, options) {
    const type = options.isFloat ? Float32Array : Uint16Array
    const numFrames = buffer.byteLength / type.BYTES_PER_ELEMENT

    const headerBytes = getWavHeader(Object.assign({}, options, { numFrames }))
    const wavBytes = new Uint8Array(headerBytes.length + buffer.byteLength);

    // prepend header, then add pcmBytes
    wavBytes.set(headerBytes, 0)
    wavBytes.set(new Uint8Array(buffer), headerBytes.length)

    return wavBytes
}

// adapted from https://gist.github.com/also/900023
// returns Uint8Array of WAV header bytes
function getWavHeader(options) {
    const numFrames = options.numFrames
    const numChannels = options.numChannels || 2
    const sampleRate = options.sampleRate || 44100
    const bytesPerSample = options.isFloat ? 4 : 2
    const format = options.isFloat ? 3 : 1

    const blockAlign = numChannels * bytesPerSample
    const byteRate = sampleRate * blockAlign
    const dataSize = numFrames * blockAlign

    const buffer = new ArrayBuffer(44)
    const dv = new DataView(buffer)

    let p = 0

    function writeString(s) {
        for (let i = 0; i < s.length; i++) {
            dv.setUint8(p + i, s.charCodeAt(i))
        }
        p += s.length
    }

    function writeUint32(d) {
        dv.setUint32(p, d, true)
        p += 4
    }

    function writeUint16(d) {
        dv.setUint16(p, d, true)
        p += 2
    }

    writeString('RIFF')              // ChunkID
    writeUint32(dataSize + 36)       // ChunkSize
    writeString('WAVE')              // Format
    writeString('fmt ')              // Subchunk1ID
    writeUint32(16)                  // Subchunk1Size
    writeUint16(format)              // AudioFormat
    writeUint16(numChannels)         // NumChannels
    writeUint32(sampleRate)          // SampleRate
    writeUint32(byteRate)            // ByteRate
    writeUint16(blockAlign)          // BlockAlign
    writeUint16(bytesPerSample * 8)  // BitsPerSample
    writeString('data')              // Subchunk2ID
    writeUint32(dataSize)            // Subchunk2Size

    return new Uint8Array(buffer)
}

function getAudioData() {


    function WavHeader() {
        this.dataOffset = 0;
        this.dataLen = 0;
        this.channels = 0;
        this.sampleRate = 0;
    }

    function fourccToInt(fourcc) {
        return fourcc.charCodeAt(0) << 24 | fourcc.charCodeAt(1) << 16 | fourcc.charCodeAt(2) << 8 | fourcc.charCodeAt(3);
    }

    WavHeader.RIFF = fourccToInt("RIFF");
    WavHeader.WAVE = fourccToInt("WAVE");
    WavHeader.fmt_ = fourccToInt("fmt ");
    WavHeader.data = fourccToInt("data");

    WavHeader.readHeader = function (dataView) {
        var w = new WavHeader();

        var header = dataView.getUint32(0, false);
        if (WavHeader.RIFF != header) {
            return;
        }
        var fileLen = dataView.getUint32(4, true);
        if (WavHeader.WAVE != dataView.getUint32(8, false)) {
            return;
        }
        if (WavHeader.fmt_ != dataView.getUint32(12, false)) {
            return;
        }
        var fmtLen = dataView.getUint32(16, true);
        var pos = 16 + 4;
        switch (fmtLen) {
            case 16:
            case 18:
                w.channels = dataView.getUint16(pos + 2, true);
                w.sampleRate = dataView.getUint32(pos + 4, true);
                break;
            default:
                throw 'extended fmt chunk not implemented';
        }
        pos += fmtLen;
        var data = WavHeader.data;
        var len = 0;
        while (data != header) {
            header = dataView.getUint32(pos, false);
            len = dataView.getUint32(pos + 4, true);
            if (data == header) {
                break;
            }
            pos += (len + 8);
        }
        w.dataLen = len;
        w.dataOffset = pos + 8;
        return w;
    };

    getAudioData.WavHeader = WavHeader;

}

getAudioData();

custom_script.js

getBufferFromBlobs(fullBlobArray).then(function (singleBlob) {
                    const url = window.URL.createObjectURL(singleBlob);
                    const a = document.createElement("a");
                    document.body.appendChild(a);
                    a.style = "display: none";
                    a.href = url;
                    a.download = "testing.wav";
                    a.click();
                    URL.revokeObjectURL(url);
                    a.remove();
                });