Как микшировать устройства ввода звука в Qt

Я новичок в мультимедийной библиотеке Qt, и в своем приложении я хочу микшировать звук с нескольких устройств ввода (например, микрофона), чтобы передавать его через TCP.

Насколько я знаю, я должен сначала получить конкретный QAudioDeviceInfo для всех необходимых устройств - вместе с соответствующим объектом QAudioFormat - и использовать его с QAudioInput. Затем я просто вызываю start() для каждого созданного объекта QAudioInput и считываю ожидающие байты с помощью readLine().

Но как я могу смешать аудиоданные нескольких устройств в один буфер?


person neuronalbit    schedule 05.01.2016    source источник


Ответы (1)


Я не уверен, есть ли для этого какой-либо конкретный метод/класс Qt. Однако сделать это самостоятельно довольно просто.

Самый простой способ (при условии, что вы используете PCM), вы можете просто сложить два потока/буфера слово за словом (насколько я помню, это 16-битные слова PCM).

Итак, если у вас есть два входных буфера:

int16 buff1[10];
int16 buff2[10];
int16 mixBuff[10];

// Fill them...
//... code goes here to read from the buffers ....

// Add them (effectively mix them)

for (int i = 0; i < 10; i++)
{
   mixBuff[i] = buff1[i] + buff2[i];
}

Теперь это очень грубо и не принимает во внимание масштабирование. Итак, представьте, что buff1 и buff2 используют 80% динамического диапазона (назовем это полной громкостью, за пределами которой вы получаете искажение), тогда, когда вы суммируете их вместе, вы получите превышение числа (т. быть перерасходом).

Каждый раз, когда вы смешиваете, вам фактически требуется половина двух входов (так что 65535/2 + 65535/2 = 65535... т.е. когда вы их суммируете, вы не можете выйти за пределы). Итак, ваш код микширования выглядит следующим образом:

for (int i = 0; i < 10; i++)
{
   mixBuff[i] = (buff1[i] >> 1) + (buff2[i] >> 1);
}

Вы можете сделать гораздо больше (удаление шума и т. д.), но тогда математика становится немного запутанной. Это очень просто. Вы можете использовать сдвиг впоследствии, чтобы увеличить / уменьшить громкость в качестве простого регулятора громкости, если хотите.

ИЗМЕНИТЬ

Следует отметить одну вещь... вы используете readline() (которая, как говорят документы, считывает данные как ASCII). Я всегда использую read(), который не указывает «формат», который он считывает, но я предполагаю, что он двоичный. Так что этот код может не работать, если вы используете readline(), но я никогда не пробовал. Это хорошо работает для read(), вы действительно не хотите работать в ASCII, если хотите манипулировать данными.

person code_fodder    schedule 06.01.2016
comment
Ну, после прочтения вашего ответа это выглядит очень просто. Определенно собираюсь попробовать это в ближайшее время. Любая рекомендация, какой размер буфера я должен выбрать? Мне нужно транслировать в реальном времени без каких-либо задержек (если это возможно). - person neuronalbit; 09.01.2016
comment
Я считаю, что это компромисс между задержкой (задержка добавляется в ожидании большого количества данных) и обработкой/эффективностью. Поэтому, если вам нужна низкая задержка, возможно, используйте размер буфера ~ 10 слов (20 байт). При 16 кбит/с 16000 / 8 = 2000 байт в секунду, так что это около 10 мс, так что довольно короткая задержка, но процессор будет сильно просыпаться. Я предполагаю, что вы используете сигнал readReady(), поэтому он просыпается только при наличии данных (т.е. вы не опрашиваете)? - person code_fodder; 11.01.2016
comment
Да, я делаю опрос и использую read(). Каждый раз, когда аудиоустройство имеет доступные байты, я добавляю данные в первичный буфер, но я просто извлекаю достаточно данных, чтобы полностью заполнить буфер, прежде чем выдать соответствующий сигнал, чтобы уведомить другие классы о том, что новый блок данных доступен. В случае, если основной буфер по-прежнему имеет максимальную емкость, но есть ожидающие данные от аудиоустройств, я отбрасываю достаточно данных в начале, чтобы освободить место для новых данных. Говоря о качестве звука, мне всегда нужно передавать аудиоданные с помощью 2 Channels, 24 Bit Depth и 48kHz Sample Rate. - person neuronalbit; 11.01.2016
comment
Я сделал небольшой расчет по определению необходимой пропускной способности. 2 channels x 24 bit x 48kHz составляют 281,25 kbyte/s данных. Для потоковой передачи в реальном времени я выбрал 10ms delay, поэтому мне нужно получать минимум 2,81 kbytes данных за каждый цикл. Это предпочтительно? - person neuronalbit; 11.01.2016
comment
Вероятно, лучше подключить QIODevice signal readyRead() к вашему слоту. Затем всякий раз, когда что-либо будет получено, ваш слот будет вызываться (более эффективно, чем опрос). Затем в своем слоте вы всегда считываете все и помещаете в свой собственный буфер, а затем, когда у вас достаточно данных, вы отправляете их. Я бы не стал заполнять буферы в приложениях реального времени, потому что вам часто нужна свобода действий. Допустим, ваши пакеты имеют размер 2000 байт, тогда вы можете увеличить буфер до 4000. Как только он достигнет 2000, отправьте пакет. Код может быть очень простым. Если пространство является проблемой, тогда сделайте все меньше. - person code_fodder; 11.01.2016
comment
Я бы сказал, что ваша задержка в 10 мс лучше только потому, что она меньше (иначе у вас будет задержка в 1 секунду в вашей системе). Я не думаю, что мы заметим задержку в 10 мс. Но когда вы переходите на 10 мс, вы действительно не хотите проводить опрос ... лучше настроить соединение (я упоминал в последнем комментарии). - person code_fodder; 11.01.2016
comment
Конечно, скоро я опубликую несколько фрагментов кода. Ваш совет по использованию сигнала readyRead() вместо опроса применим только в том случае, если я использую только один QIODevice, верно? Потому что иначе как мне удается смешивать данные с нескольких устройств? - person neuronalbit; 11.01.2016
comment
Для каждого устройства вы просто подключаете его к другому слоту. Итак (просто псевдокод): connect(IODev1, signal(readyRead(), myClass, slot(dev1Slot)) и connect(IODev2, signal(readyRead(), myClass, slot(dev2Slot)) - person code_fodder; 11.01.2016
comment
Я добавил к угрозе свое текущее состояние разработки. - person neuronalbit; 12.01.2016
comment
Круто... но я не понимаю, что вызывает этот код... когда вы вызываете цикл foreach? - это основано на таймере, сигнале или чем-то еще? - person code_fodder; 13.01.2016
comment
Поскольку это класс потока (наследующий от QThread), все это происходит в рамках метода run(). В случае возникновения ошибки цикл while завершается, и поток выдает сигнал finished(). Поток создается как член общего класса аудио. - person neuronalbit; 13.01.2016
comment
ооо... вы унаследовали qthread и заново реализовали run()? Это действительно не то, как QThread предназначен для использования. Также я думаю, что ваш код будет запускаться только один раз, после завершения цикла foreach он больше никогда не будет вызываться. Я бы сказал, что здесь есть что исправить. Взгляните на мой ответ здесь: stackoverflow.com/questions/33744997/, чтобы узнать, как реализовать исправления потоков в Qt. Вам не нужно (или не хотите) наследовать QThread, если вы не хотите изменить способ работы потоков - это первое, что нужно исправить, я бы сказал :) - person code_fodder; 13.01.2016
comment
Вы знаете, почему это называется фрагментом, верно? Потому что он показывает только минимальную часть всего кода. Данный код заключен в цикл while, который, конечно же, всегда проверяет isInterruptionRequested(). И в чем это неправильно? Почему moveToThread() лучше моей попытки? Я сделал это в первую очередь из-за ручной остановки потока, пока он зацикливается, и я не хочу ждать каких-либо сигналов QIODevice объектов, потому что иначе я не могу правильно смешать данные! - person neuronalbit; 13.01.2016
comment
Хорошо, я не вижу этот код, у меня есть только ваше описание :) Так что, если вы хотите продолжать использовать метод опроса, который также зависит от вас, но вы можете отлично смешать два других способа. , но включает в себя дизайн, управляемый событиями, который в реальном времени более эффективен, поскольку опрос поглощает вашу обработку. Я полагаю, у вас есть сон или что-то в вашей петле? .. но я думаю, что исходная проблема решена, поэтому, если у вас есть дополнительные проблемы, вы должны сделать новый пост :) Если вы пришлете мне ссылку и опубликуете больше кода (включая внешний цикл и т. д.) - person code_fodder; 13.01.2016
comment
Вы правы, я лучше перенесу это на другую угрозу :) Спасибо за реальный ответ. - person neuronalbit; 13.01.2016
comment
привет, как мы можем использовать «переключение» для регулировки громкости, как вы упомянули в ответе? - person Akın Yılmaz; 15.08.2018
comment
@AkınYılmaz для увеличения/уменьшения громкости (и это грубый подход, но простой и несколько эффективный) вы арифметически сдвигаете каждое слово в своем потоке вправо или влево (т.е. удваиваете или вдвое уменьшаете амплитуду). например если у вас есть буфер uint16_t и вы хотите немного увеличить громкость, вы делаете buffer[x] <<= 1 для каждого слова в буфере. Если вы хотите немного уменьшить buffer[x] >>= 1. Если вы хотите сильно уменьшить, вы делаете buffer[x] >>= 3... - person code_fodder; 15.08.2018