Процессор эффектов: мысли об архитектуре программного обеспечения, выборе языка программирования, API
В настоящее время я работаю над проектом, основным элементом которого является программное обеспечение, которое может обрабатывать сигналы в режиме реального времени.
Основная идея / концепция заключается в следующем. Аудиоданные непрерывно записываются с аудиоинтерфейса (USB) (работает на частоте 96 кГц, 24 бита). Затем он проходит через "цепочку обработки" (например, "цепочку виртуальных эффектов"), где каждый элемент может выполнять произвольную обработку данных. Данные передаются от одного элемента к следующему, пока не поступят в приемник. Данные, которые поступают в приемник, отправляются обратно на аудиоинтерфейс для вывода.
Цепочка сигналов состоит из "объектов", которые реализуют некоторый метод "обработки", который работает с порциями данных размером 1920 выборок (20 мс) (хотя можно изменить это число).
Основная идея в "псевдокоде".
while (true) {
float[] samples = audio.read(BLOCK_SIZE);
foreach (Effect e in signal_flow)
e.process(samples);
audio.write(samples);
}
Первый прототип был реализован в Python, который использует ALSA API для аудио ввода / вывода. Три отдельных потока запущены.
Первый непрерывно читает из ALSA (который является операцией блокировки) и помещает данные в очередь, а затем продолжает чтение следующего блока. (ALSA требует, чтобы я "читал (...)", по крайней мере, каждые 20 мс, поэтому мне нужна очень легкая нить для этого, которая ничего не делает, кроме чтения и хранения данных, чтобы я мог соответствовать своим временным ограничениям.)
Второй считывает выборки из входных данных из этой очереди, помещает их через цепочку сигналов и, наконец, в другую очередь для вывода.
Третий поток читает из этой очереди вывода (которая является операцией блокировки) и записывает блоки в ALSA.
У меня в основном есть две "числовые" переменные, одна из которых является размером блока, а другая - количеством блоков, которые я "предварительно отправляю" в очередь вывода, прежде чем я даже начинаю обработку, что дает мне некоторое время для "доставки большего количества образцов". msgstr "без выходного буфера занижен. Кроме того, я могу решить делать эти вещи в отдельных потоках или в отдельных процессах.
После некоторой серьезной отладки (которая включала использование как генератора сигналов, так и осциллографа!), Я наконец запустил этот подход. Самой большой проблемой была ошибка в API-интерфейсе ALSA, а именно то, что "period_size", который указывается как "количество считываемых / записываемых фреймов ", фактически представляет собой количество прочитанных фреймов, но количество записанных байтов. Когда я указываю 1920 (20 мс аудио при частоте дискретизации 96 кГц) для обоих входов и выходов, я получаю только 1/4 выхода, затем 3/4 тишины, затем 1/4 выхода. Когда я указываю 1920 * 4 = 7680 для ввода и вывода, я получаю сообщение об ошибке, что буфер устройства недостаточно велик для чтения такого количества кадров. Когда я указываю 1920 для ввода и 7680 для вывода, он работает отлично (и мои очереди тоже не "переполняются").
После этого у меня осталась одна серьезная проблема: задержка!
Я в основном начал весь этот проект, чтобы создавать свои собственные эффекты, поэтому я хочу, чтобы это программное обеспечение работало как единица эффектов для сценического / живого использования. Я читал, что когда два события находятся на расстоянии менее 30 мс, например, два стробоскопа срабатывают на расстоянии 30 мс, человеческий мозг больше не может определить, какой из них был первым, а какой - вторым. Таким образом, задержка в 30 мс или меньше будет восприниматься как "мгновенная". Это порог, где события "сливаются" и воспринимаются как "один". Если бы задержка была такой низкой, вы не могли бы сказать, какое событие является первым, например, гитарист больше не мог бы сказать, ударил ли он по струне, а затем звук пришел из PA, или если звук пришел из PA, а затем он ударил Строка, так что это будет в основном похоже на "нулевую задержку" и, следовательно, как настоящий усилитель.
Теперь задержка, к которой я сейчас пришел,... ну... у меня еще не было возможности точно измерить их, но она составляет порядка десятых долей секунды, скажем, 200 или 300 мс. Это слишком много! Я попробовал как отдельные потоки, так и процессы для ввода / вывода (поскольку в Python вы никогда не знаете, будут ли потоки действительно быстрее из-за "глобальной блокировки интерпретатора"), а процессы были значительно медленнее, что и следовало ожидать, из-за накладные расходы, связанные с межпроцессным взаимодействием и тем фактом, что два процесса ввода-вывода не делают ничего "интенсивного". Они просто ждут блокирующих операций и выполняют быстрый ввод / вывод.
В качестве еще одного замечания я хотел бы сказать, что я хочу, чтобы мое приложение размещало веб-сервер (конечно, в отдельном потоке или даже процессе), чтобы позволить пользователю настраивать путь сигнала и настраивать параметры для эффектов "устройства" в интуитивно понятный способ.
Поэтому, очевидно, мне нужно уменьшить время ожидания! У меня есть несколько путей, чтобы следовать сейчас.
Моя первая идея - заменить ALSA на JACK, который называется "с малой задержкой". Недостатки в том, что я теряю контроль над точными уровнями на АЦП / ЦАП (JACK всегда работает с числами с плавающей точкой, которые уже масштабируются до [-1.0, 1.0], независимо от того, что делает оборудование) и над частотой дискретизации (насколько я знаете, JACK "диктует" частоту дискретизации - я больше не могу выбирать - с помощью ALSA я мог бы выбрать частоту дискретизации, с которой я хочу работать, и для меня было бы прозрачно повышать / понижать выборку, если бы оборудование отклонялось от этого), что блокировка I/O (которую я нахожу довольно хорошей парадигмой) заменено механизмом обратного вызова, и что ограничения по времени, вероятно, встретить гораздо сложнее. (Я не могу самостоятельно "буферизовать" JACK. Для этого всегда требуется, чтобы обработка выполнялась за определенное время.) Кроме того, JACK API в целом выглядит более сложным, чем ALSA API. Возможно, я мог бы заменить ALSA на JACK, но я не уверен, сколько я мог бы "выиграть", учитывая, что ALSA уже является довольно "низкоуровневым" API, и JACK основывается на нем, но, вероятно, использует очень специфический способ сохранения задержки низкие. Если честно, я не совсем уверен, насколько эта задержка обусловлена ALSA и "снизу", а сколько - "более высокими уровнями".
В настоящее время я использую интерпретированный язык для сбора мусора. Это плохо для требований в реальном времени. Но каковы альтернативы? Ну, конечно, C приходит на ум, когда думаешь о приложениях реального времени, но есть серьезные недостатки.
2.1 - Я хочу, чтобы программное обеспечение имело веб-интерфейс. Создать веб-сервер на Python просто. Для него есть класс в стандартной библиотеке Python. Создание веб-сервера на C - это боль. В C нет НИЧЕГО, что могло бы сделать "сеть". Я должен начать с нуля, основываясь только на сокет API. Удачи!:-(
2.2 - Python имеет поддержку чисел. Он имеет тип numpy и scipy и, следовательно, векторные типы данных, матричное умножение, быстрое преобразование Фурье, быструю свертку, интерполяцию (сплайны и т. Д.). Это все очень полезно для решения проблемы здесь. В C я теряю все это, что означает, что я либо должен выполнить работу без нее, что очень маловероятно, либо мне придется заново создавать все это самостоятельно в C, что отнимает много времени и приводит к ошибкам. склонный.
2.3 - Python имеет "высокоуровневую" поддержку многопроцессорности и многопоточности. Я могу связываться между процессами и потоками через синхронизированные очереди, реализуя свое решение в стиле "пул потоков / реплицированные рабочие", что является чрезвычайно полезной парадигмой для рассматриваемой здесь проблемы. В Си мне придется прибегать к потокам POSIX и низкоуровневым примитивам синхронизации, таким как мьютики. (Это правильное множественное число для "mutex"? Как в "Unix" -> "Unices"?) Опять же, это отнимает много времени и подвержено ошибкам.
Любые предложения на каких языках использовать? Языки, с которыми я знаком, включают Python, C-Sharp, Java, C, Go, немного C++ (мне никогда не нужен этот язык - либо низкоуровневый, то я использую C, либо высокоуровневый, то я использовать язык сборки мусора, интерпретатор или язык, скомпилированный с помощью байт-кода), а также некоторые веб-технологии (особенно JavaScript с AJAX и т. д., которые будут полезны, когда дело доходит до пользовательского интерфейса).
Все это должно выполняться в дистрибутиве Linux, желательно с как можно меньшим количеством зависимостей / библиотек. "Стандартные" библиотеки, таким образом, предпочтительнее "экзотических". Независимость от платформы, особенно возможность работать под Windows, является плюсом, но не обязательным требованием.
Я согласен с тем, что пользовательский интерфейс реализован на другом языке (на ум приходит Go для веб-сервера), нежели фактическая обработка (которая, возможно, "должна быть сделана в C", если она должна быть в реальном времени?), Хотя это приносит много дополнительной сложности, так как оба затем будут работать в отдельных процессах и обмениваться данными, скажем, через Unix Domain Sockets, что требует, чтобы все данные передавались взад и вперед, чтобы быть представленными в виде байтового потока (так как это все транспорты сокетов), поэтому необходимо разработать протокол межпроцессного взаимодействия и выполнить много разборов и на этом уровне. Есть ли шанс избежать этих огромных накладных расходов и сделать это на одном языке?
Есть другие идеи? В частности, получу ли я что-нибудь, перейдя из ALSA в JACK? Существуют ли другие альтернативы, которые я еще не рассматривал, которые могли бы удовлетворить задачу еще лучше?
Большое спасибо за ваше время!