Понизьте частоту звука с буферных входов с 48000 до 16000.

У меня есть recorder.js, который будет записывать звук и принимать входные данные буфера, но я хочу понизить выборку аудиобуферов, но я сильно запутался, где его вызывать, хотя я написал Это. Пожалуйста, проверьте мою функцию и, если возможно, предложите, где ее вызвать.

import InlineWorker from 'inline-worker';

export class Recorder {
    config = {
        bufferLen: 4096,
        numChannels: 2,
        mimeType: 'audio/mp3'
    };

    recording = false;

    callbacks = {
        getBuffer: [],
        exportWAV: []
    };

    constructor(source, cfg) {
        Object.assign(this.config, cfg);
        this.context = source.context;
        this.node = (this.context.createScriptProcessor ||
        this.context.createJavaScriptNode).call(this.context,
            this.config.bufferLen, this.config.numChannels, this.config.numChannels);

        this.node.onaudioprocess = (e) => {
            if (!this.recording) return;

            var buffer = [];
            for (var channel = 0; channel < this.config.numChannels; channel++) {
                buffer.push(e.inputBuffer.getChannelData(channel));
            }
            this.worker.postMessage({
                command: 'record',
                buffer: buffer
            });
        };

        source.connect(this.node);
        this.node.connect(this.context.destination);    //this should not be necessary

        let self = {};
        this.worker = new InlineWorker(function () {
            let recLength = 0,
                recBuffers = [],
                sampleRate,
                numChannels;

            this.onmessage = function (e) {
                switch (e.data.command) {
                    case 'init':
                        init(e.data.config);
                        break;
                    case 'record':
                        record(e.data.buffer);
                        break;
                    case 'exportWAV':
                        exportWAV(e.data.type);
                        break;
                    case 'getBuffer':
                        getBuffer();
                        break;
                    case 'clear':
                        clear();
                        break;
                }
            };

            function init(config) {
                sampleRate = config.sampleRate;
                numChannels = config.numChannels;
                initBuffers();
            }

            function record(inputBuffer) {
                for (var channel = 0; channel < numChannels; channel++) {
                    recBuffers[channel].push(inputBuffer[channel]);
                }
                recLength += inputBuffer[0].length;
            }

             function exportWAV(type) {
            let buffers = [];
            for (let channel = 0; channel < numChannels; channel++) {
                buffers.push(mergeBuffers(recBuffers[channel], recLength));
            }
            let interleaved;
            if (numChannels === 2) {
                interleaved = interleave(downsampleBuffer(buffers[0]), downsampleBuffer(buffers[1]));
            } else {
                interleaved = buffers[0];
            }

            let dataview = encodeWAV(interleaved);
            let audioBlob = new Blob([dataview], {type: type});

            this.postMessage({command: 'exportWAV', data: audioBlob});
        }


    function downsampleBuffer(buffer) {
        if (16000 === sampleRate) {
          return buffer;
        }
    var sampleRateRatio = sampleRate / 16000;
    var newLength = Math.round(buffer.length / sampleRateRatio);
    var result = new Float32Array(newLength);
    var offsetResult = 0;
    var offsetBuffer = 0;
    while (offsetResult < result.length) {
      var nextOffsetBuffer = Math.round((offsetResult + 1) * sampleRateRatio);
      var accum = 0,
        count = 0;
      for (var i = offsetBuffer; i < nextOffsetBuffer && i < buffer.length; i++) {
        accum += buffer[i];
        count++;
      }
      result[offsetResult] = accum / count;
      offsetResult++;
      offsetBuffer = nextOffsetBuffer;
    }
    return result;
  }

            function getBuffer() {
                let buffers = [];
                for (let channel = 0; channel < numChannels; channel++) {
                    buffers.push(mergeBuffers(recBuffers[channel], recLength));
                }
                this.postMessage({command: 'getBuffer', data: buffers});
            }

            function clear() {
                recLength = 0;
                recBuffers = [];
                initBuffers();
            }

            function initBuffers() {
                for (let channel = 0; channel < numChannels; channel++) {
                    recBuffers[channel] = [];
                }
            }

            function mergeBuffers(recBuffers, recLength) {
                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;
            }

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

                let index = 0,
                    inputIndex = 0;

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

            function floatTo16BitPCM(output, offset, input) {
                for (let i = 0; i < input.length; i++, offset += 2) {
                    let 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 (let i = 0; i < string.length; i++) {
                    view.setUint8(offset + i, string.charCodeAt(i));
                }
            }

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

                /* RIFF identifier */
                writeString(view, 0, 'RIFF');
                /* RIFF chunk 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, sampleRate, true);
                /* byte rate (sample rate * block align) */
                view.setUint32(28, 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;
            }
        }, self);

        this.worker.postMessage({
            command: 'init',
            config: {
                sampleRate: this.context.sampleRate,
                numChannels: this.config.numChannels
            }
        });

        this.worker.onmessage = (e) => {
            let cb = this.callbacks[e.data.command].pop();
            if (typeof cb == 'function') {
                cb(e.data.data);
            }
        };
    }


    record() {
        this.recording = true;
    }

    stop() {
        this.recording = false;
    }

    clear() {
        this.worker.postMessage({command: 'clear'});
    }

    getBuffer(cb) {
        cb = cb || this.config.callback;
        if (!cb) throw new Error('Callback not set');

        this.callbacks.getBuffer.push(cb);

        this.worker.postMessage({command: 'getBuffer'});
    }

    exportWAV(cb, mimeType) {
        mimeType = mimeType || this.config.mimeType;
        cb = cb || this.config.callback;
        if (!cb) throw new Error('Callback not set');

        this.callbacks.exportWAV.push(cb);

        this.worker.postMessage({
            command: 'exportWAV',
            type: mimeType
        });
    }

    static
    forceDownload(blob, filename) {
        let url = (window.URL || window.webkitURL).createObjectURL(blob);
        let link = window.document.createElement('a');
        link.href = url;
        link.download = filename || 'output.wav';
        let click = document.createEvent("Event");
        click.initEvent("click", true, true);
        link.dispatchEvent(click);
    }
}

export default Recorder;

Это код, который я взял из репозитория github, но частота дискретизации составляет 48000.

Файл после даунсэмплинга левого и правого буферов загружается здесь

Вызов из моего класса компонентов

 recorder && recorder.exportWAV(function(blob) {

            var formData=new FormData();

            formData.append("event_name",fileName);
            formData.append("file",new File([blob], fileName, {type: 'audio/mpeg;', lastModified: Date.now()}));
            formData.append("fileExtension", "mp3");
             fetch('http://localhost:6020/uploadFile', {
            method: "POST", // *GET, POST, PUT, DELETE, etc.
            cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached

            redirect: "follow", // manual, *follow, error
            referrer: "no-referrer", // no-referrer, *client
            body: formData, // body data type must match "Content-Type" header
            })
            .then(response => response.json())
            .then(function(data){ console.log( JSON.stringify( data ) ) });

          },"audio/mp3");

person Kramer    schedule 17.12.2018    source источник
comment
пожалуйста, подскажите, куда звонить. - непонятно, о чем вы спрашиваете. Вы можете позвонить туда, где вам это нужно. FWIW, вам нужно использовать фильтр нижних частот, чтобы избежать алиасинга во время передискретизации.   -  person Estus Flask    schedule 17.12.2018
comment
Создайте минимально воспроизводимый пример.   -  person ceving    schedule 17.12.2018
comment
@estus, что я хочу отметить, так это то, что я передаю в него, поскольку у меня есть два канала, и как мне объединить его в один буфер, а затем передать в понижающий дискретизатор?   -  person Kramer    schedule 17.12.2018
comment
Вопрос не очень конструктивный, потому что вы только что использовали чужой код, он достаточно большой для проверки. Если вам нужен сторонний код, могут быть библиотеки или примеры, которые лучше подходят для ваших целей (я не могу их рекомендовать). Я предполагаю, что вы должны понижать частоту дискретизации левого и правого канала отдельно, если только downsampleBuffer не был написан для поддержки нескольких каналов - кажется, что это не так, и он преобразует один канал (буфер) сразу, как следует из его названия.   -  person Estus Flask    schedule 17.12.2018
comment
@estus Я пытался сделать, как вы упомянули, а также загрузил файл, который вы можете проверить, он очень быстрый, я не знаю причины, хотя я уменьшаю его выборку .... проверьте метод exportWav (тип)   -  person Kramer    schedule 17.12.2018
comment
Файл 48 кГц. Неизвестно, что происходит с аудиоданными после exportWAV. Но вам нужно установить 16 кГц для аудиофайла.   -  person Estus Flask    schedule 17.12.2018
comment
@estus, но как мне это сделать, не могли бы вы взглянуть на код, который я пытаюсь сделать в течение длительного времени   -  person Kramer    schedule 17.12.2018
comment
дело в том, что мне нужно понизить SampleRate до 16000, потому что, если я этого не сделаю, звук будет 48 кГц, но звук в порядке, нет проблем, как только я использую понижающую дискретизацию, тогда я получил звук, который я прикрепил, это очень быстро, и если я заставлю и использую 16000 вместо this.context.sampleRate в коде звук чертовски медленный, как slo-mo   -  person Kramer    schedule 17.12.2018
comment
Опять же, в коде не показано, что вы делаете с данными после их экспорта. exportWAV просто создает большой двоичный объект. У больших двоичных объектов нет частоты дискретизации. Как упоминалось выше, в вопросе отсутствует stackoverflow.com/help/mcve   -  person Estus Flask    schedule 17.12.2018
comment
@estus хорошо, я добавил эту часть кода, пожалуйста, проверьте, что именно отсутствует или что вы делаете неправильно, последняя проверка, пожалуйста, из BLOB-объекта. Я создаю файл   -  person Kramer    schedule 17.12.2018
comment
Я понимаю. Проблема в том, что sampleRate в encodeWAV неверен. Еще проблема type: 'audio/mpeg;'. Это не мпег. Это вав.   -  person Estus Flask    schedule 17.12.2018
comment
Давайте продолжим обсуждение в чате.   -  person Kramer    schedule 17.12.2018
comment
@estus не могли бы вы ответить на мой вопрос или зайти в дискуссионный чат?   -  person Kramer    schedule 17.12.2018
comment
@estus config = { bufferLen: 4096, numChannels: 2, mimeType: 'audio/mp3' }; здесь я упомянул mp3, я только что использовал функцию, которая называется WAV   -  person Kramer    schedule 17.12.2018
comment
Я имею в виду new File([blob], fileName, {type: 'audio/mpeg;'. Это не должно быть мпег. Вам помогло исправление sampleRate в encodeWAV? Я не могу копаться в вашем коде и дать конкретный ответ, как именно его следует редактировать. Код слишком сложный. Я не использую чаты на SO, потому что они плохо работают в автономном режиме.   -  person Estus Flask    schedule 17.12.2018
comment
@estus github.com/awslabs/aws-lex-browser-audio-capture Я использовал этот код, но если вы закодируете и загрузите zip, вы получите пример и папку lib, которую я пытался использовать, но оказалось, что recorder.js требует webworkify для загрузки worker.js модуля, поэтому, когда Я установил его .... он выдал ошибку внутри этого модуля   -  person Kramer    schedule 17.12.2018
comment
@estus это сработало, если я изменю sampleRate в encodedWAV, так как если я веду консольный журнал там, он все еще будет 48000, поэтому я жестко закодировал его, но мой файл подписан PCM, и я хочу, чтобы mp3 был подписан, как это сделать есть идеи?   -  person Kramer    schedule 17.12.2018
comment
Вам нужно конвертировать wav в mp3. Это другая задача, а не исходный вопрос. Google для «клиентской стороны js mp3».   -  person Estus Flask    schedule 17.12.2018
comment
@estus На самом деле, если вы видите, что у меня есть два метода: exportWAV, один с cb, mimeType, другой только один параметр, я дал mimetype для вызова другого exportWAV, но все равно он показывает подписанный PCM   -  person Kramer    schedule 17.12.2018
comment
И в чем проблема с этим? Это действительно ПКМ. Вам необходимо кодировать аудиосигнал с помощью кодировщика MP3, чтобы он стал настоящим MP3, а не просто WAV с расширением .mp3.   -  person Estus Flask    schedule 17.12.2018
comment
@estus, пожалуйста, скажите мне, что мне делать в методе encodeWAV выше, когда я передаю WAVE, я должен напрямую передавать MP3 ??   -  person Kramer    schedule 17.12.2018
comment
Вы, возможно, знаете. Или вы можете закодировать его в WAV, а затем в MP3. Это зависит от вас и возможных решений, которые вы можете найти для кодирования MP3. Опять же, это выходит за рамки исходного вопроса (понижение дискретизации).   -  person Estus Flask    schedule 17.12.2018
comment
@estus Я не получаю эквивалентный код для реализации reactjs. Если мне нужен libmp3lame.js, который является старым js и не экспортирует класс, как я буду его использовать?   -  person Kramer    schedule 18.12.2018


Ответы (1)


Я успешно понизил разрешение своих аудиоклипов (wav-файлов), используя библиотеку recorder.js, ссылаясь на решение, данное ilikerei в этой теме. .

Добавьте этот метод в файл recorder.js для повторной выборки буфера для изменения частоты дискретизации перед вызовом метода encodeWav().

function downsampleBuffer(buffer, rate) {
            if (rate == sampleRate) {
                return buffer;
            }
            if (rate > sampleRate) {
                throw "downsampling rate show be smaller than original sample rate";
            }
            var sampleRateRatio = sampleRate / rate;
            var newLength = Math.round(buffer.length / sampleRateRatio);
            var result = new Float32Array(newLength);
            var offsetResult = 0;
            var offsetBuffer = 0;
            while (offsetResult < result.length) {
                var nextOffsetBuffer = Math.round((offsetResult + 1) * sampleRateRatio);
                 // Use average value of skipped samples
                var accum = 0, count = 0;
                for (var i = offsetBuffer; i < nextOffsetBuffer && i < buffer.length; i++) {
                    accum += buffer[i];
                    count++;
                }
                result[offsetResult] = accum / count;
                // Or you can simply get rid of the skipped samples:
                // result[offsetResult] = buffer[nextOffsetBuffer];
                offsetResult++;
                offsetBuffer = nextOffsetBuffer;
            }
            return result;
        }

Затем вызовите encodeWav с новым буфером выборки.

var downsampledBuffer = downsampleBuffer(interleaved, targetRate);
var dataview = encodeWAV(downsampledBuffer);

Теперь используйте новую частоту дискретизации для кодирования.

/* sample rate */
view.setUint32(24, newRate, true);
/* byte rate (sample rate * block align) */
view.setUint32(28, newRate * 4, true);
person Ishara Amarasekera    schedule 09.09.2019