Speex разделенные аудиоданные - WebAudio - VOIP

Я запускаю небольшое приложение, которое кодирует и декодирует аудио массив с кодеком speex в javascript: https://github.com/dbieber/audiorecorder

с небольшим массивом, заполненным синусоидальной волной

for(var i=0;i<16384;i++)
    data.push(Math.sin(i/10));

это работает. Но я хочу создать приложение VOIP и иметь более одного массива. Поэтому, если я разделю свой массив на 2 части, кодирование> декодирование> слияние, это не будет звучать так же, как раньше.

Взгляните на это:

скрипка: http://jsfiddle.net/exh63zqL/

Обе кнопки должны давать одинаковый аудиовыход.

Как я могу получить одинаковый результат в обоих направлениях? Это специальный режим в speex.js для разделения аудиоданных?


person Cracker0dks    schedule 02.03.2015    source источник
comment
На кнопке Исходный вы воспроизводите с verg, а не с decV, оба имеют одинаковое искажение после кодирования-декодирования, хотя в середине объединенного звука есть заметный разрыв. Вы также не зацикливаете все свои массивы должным образом, но я не верю, что исправление этого остановит прерывание звука посередине.   -  person Paul S.    schedule 05.03.2015


Ответы (2)


Speex — это кодек с потерями, поэтому выходные данные являются лишь приближением исходной синусоиды.

Ваша синусоидальная частота составляет около 7 кГц, что близко к верхней полосе пропускания кодека 8 кГц и, как таковая, еще более вероятно, что она будет изменена.

То, что выводит кодек, выглядит как гребенка импульсов дираха, которые будут звучать как исходная синусоида, слышимая через телефон, что, безусловно, отличается от оригинала.

См. эту скрипку, где вы можете послушать, что кодек делает из ваших исходных синусоидальных волн, будь то разделенные на половина или нет.

//Generate a continus sinus in 2 arrays
var len = 16384;
var buffer1 = [];
var buffer2 = [];
var buffer = [];
for(var i=0;i<len;i++){
    buffer.push(Math.sin(i/10));
    if(i < len/2)
        buffer1.push(Math.sin(i/10));
    else
        buffer2.push(Math.sin(i/10));
}
//Encode and decode both arrays seperatly
var en = Codec.encode(buffer1);
var dec1 = Codec.decode(en);

var en = Codec.encode(buffer2);
var dec2 = Codec.decode(en);

//Merge the arrays to 1 output array
var merge = [];
for(var i in dec1)
    merge.push(dec1[i]);

for(var i in dec2)
    merge.push(dec2[i]);

//encode and decode the whole array
var en = Codec.encode(buffer);
var dec = Codec.decode(en);

//-----------------
//Down under is only for playing the 2 different arrays
//-------------------
var audioCtx = new window.AudioContext || new window.webkitAudioContext;
function play (sound)
{
    var audioBuffer = audioCtx.createBuffer(1, sound.length, 44100);
    var bufferData = audioBuffer.getChannelData(0);
    bufferData.set(sound);

    var source = audioCtx.createBufferSource();
    source.buffer = audioBuffer;
    source.connect(audioCtx.destination);
    source.start();
}

$("#o").click(function() { play(dec); });
$("#c1").click(function() { play(dec1); });
$("#c2").click(function() { play(dec2); });
$("#m").click(function() { play(merge); });

Если вы объедините два выхода декодера половинного сигнала, вы услышите дополнительный щелчок из-за резкого перехода от одного сигнала к другому, звучащий в основном как релейная коммутация.
Чтобы избежать этого, вам придется сгладить значения вокруг точка слияния двух ваших буферов.

person kuroi neko    schedule 05.03.2015
comment
Да хороший улов. Также обратите внимание на декодирование отдельного фрагмента данных. Необходимо сохранить состояние декодера, как описано в комментариях здесь - person Kirill Slatin; 05.03.2015
comment
Действительно, но я предполагаю, что идея заключалась в том, чтобы сшить вместе два независимо закодированных сигнала. - person kuroi neko; 05.03.2015
comment
идея состоит в том, чтобы передавать голосовые данные в реальном времени, поэтому мне приходится разбивать сигнал на части. Спасибо за ваш ответ @kuroineko, я постараюсь сгладить его, если теперь в speex lib есть встроенное решение. - person Cracker0dks; 05.03.2015
comment
ну, встроенное решение состоит в том, чтобы поддерживать контекст кодека живым и кормить его непрерывным потоком. Если вы используете его для передачи одного источника из точки в точку, это должно быть выполнимо. - person kuroi neko; 05.03.2015
comment
@kuroineko где контекст речи? и как я могу сохранить его живым? - person Cracker0dks; 05.03.2015

Обратите внимание, что Speex — это кодек с потерями. Таким образом, по определению он не может дать такой же результат, как закодированный буфер. Кроме того, он разработан как кодек для голоса. Таким образом, диапазон 1-2 кГц будет наиболее эффективным, так как он рассчитан на определенную форму сигнала. В чем-то ее можно сравнить с технологией JPEG для растровых изображений.

Я немного изменил ваш пример jsfiddle, чтобы вы могли поиграть с разными параметрами и сравнить результаты. Просто предоставить простую синусоиду с неизвестной частотой — неправильный способ проверки кодека. Однако в примере видно разное влияние на исходный сигнал на разных частотах.

buffer1.push(Math.sin(2*Math.PI*i*frequency/sampleRate));

Я думаю, вам следует построить пример с записанным голосом и сравнить результаты в этом случае. Это было бы правильнее.

В общем, чтобы понять идею в деталях, вам нужно изучить цифровую обработку сигналов. Я даже не могу дать правильную ссылку, так как это целая наука, и она математически интенсивна. (единственная известная мне подходящая книга для чтения находится на русском). Если кто-нибудь здесь с сильным математическим образованием может поделиться подходящей литературой для этого случая, я был бы признателен.

РЕДАКТИРОВАТЬ: как упоминал Курои Неко, есть проблема с границами буфера. И кажется, что невозможно сохранить состояние декодера, как указано в этом post, потому что библиотека в использование не поддерживает его. Если вы посмотрите на исходный код, вы увидите, что они используют сторонний кодек speex и не предоставляют полный доступ к его функциям. Я думаю, что лучшим подходом было бы найти достойную библиотеку для speex, поддерживающую восстановление состояния, аналогичную этой

person Kirill Slatin    schedule 05.03.2015
comment
Спасибо за Ваш ответ. Я много гуглил и использовал оригинальную библиотеку, прежде чем публиковать этот вопрос. Я думаю, что это единственная библиотека для речи в Javascript. Если эта библиотека не поддерживает это, я должен создать решение для себя, что сложно. - person Cracker0dks; 05.03.2015
comment
Тот, на который я разместил ссылку, похоже, init принимает speex_bits. Однако я не пробовал это самостоятельно. Speex — открытый кодек, не могу поверить, что библиотек так мало. По крайней мере, должен быть один с полной поддержкой - person Kirill Slatin; 05.03.2015
comment
Да. Класс Coder, как бы он ни назывался в используемой библиотеке, должен иметь что-то похожее на Coder.init(speex_bits), вызываемое перед декодированием следующего буфера, где speex_bits — это предыдущий фрагмент данных речи. Библиотека, которую вы используете для записи микрофона, просто не предоставляет расширенных функций стороннего кодека speex, который они используют. Мне не удалось понять, что они используют, поэтому, я думаю, было бы проще найти хорошую библиотеку только для речи. - person Kirill Slatin; 06.03.2015
comment
Я немного изменил скрипку и использовал оригинальную речь напрямую без библиотеки recorder.js, как вам грустно. jsfiddle.net/exh63zqL/5, поэтому я могу вызвать init перед декодированием. (Посмотрите на новую функцию декодирования/кодирования) Но все еще не работает нормально. У вас есть идея? - person Cracker0dks; 08.03.2015
comment
Я разговаривал с моим другом, у которого есть опыт работы с VoIP, и он сказал, что щелчок в конце буфера происходит, когда приоритет процесса кодека не высок (не наш случай в браузере) и размер буфера не соответствует фрагменту обрабатываемых данных. по кодеку. В его случае это было 120 мс. В этом кодеке js трудно увидеть размер фрагмента. Я пытался поиграть с размером буфера, однако не получил никаких улучшений :( - person Kirill Slatin; 12.03.2015