Маршрутизация аудио с низкой задержкой от одного устройства CoreAudio к другому - PullRequest
1 голос
/ 19 февраля 2020

Во-первых, некоторая справочная информация: я пишу приложение MacOS / X, которое использует CoreAudio для получения аудиосигнала от входного потока устройства CoreAudio, выполнения некоторой обработки звука в реальном времени, а затем отправляет его обратно этому Поток вывода устройства CoreAudio для прослушивания пользователем.

Это приложение использует API-интерфейсы CoreAudio более низкого уровня (т. Е. AudioDeviceAddIOProc, AudioDeviceStart, et c - не AudioUnits), чтобы получить эксклюзивный доступ к указанное пользователем устройство CoreAudio, установите желаемую частоту дискретизации (96 кГц) и сделайте свое дело. Он работает очень хорошо, и я очень доволен его производительностью.

Однако моя программа в настоящее время имеет ограничение - она ​​может использовать только одно устройство CoreAudio одновременно. Я хотел бы расширить свое приложение, чтобы пользователь мог выбирать свое «устройство ввода CoreAudio» и свое «устройство вывода CoreAudio» независимо друг от друга, а не ограничиваться использованием только одного устройства CoreAudio, которое обеспечивает оба источник входного аудиосигнала и приемник аудиосигнала.

Мой вопрос: какой метод рекомендуется использовать для этого? Я могу потребовать, чтобы оба устройства CoreAudio были настроены на одну и ту же частоту дискретизации, но даже когда я это сделаю, я думаю, что мне придется решать различные проблемы, такие как:

  • объединение отдельных AudioDeviceStart() -обусловленные обратные вызовы от двух устройств, которые, как я подозреваю, не будут вызываться в каком-либо четко определенном порядке и могут даже вызываться одновременно по отношению друг к другу (?). Мне нужно было бы каким-то образом передавать звук от одного обратного вызова к другому, в идеале без существенного увеличения задержки звука.

  • Обработка различий в тактовых частотах дискретизации двух устройств. Например, даже если оба устройства номинально установлены на частоту дискретизации 96 кГц, я подозреваю, что на самом деле это может быть тот случай, когда, например, восходящее устройство генерирует сэмплы на частоте 95,9999 кГц, в то время как нисходящее устройство потребляет их на 96,000001 кГц (или наоборот), и это в конечном итоге привело бы к тому, что я получал либо «недостаточно», либо «слишком много» выборок для подачи на нисходящее устройство во время данного обратного вызова рендеринга, что вызывало сбой.

  • Любые другие но они еще не рассмотрены

Как другие программы MacOS / X решают эти проблемы?

Ответы [ 2 ]

2 голосов
/ 22 февраля 2020

Алгоритм «негерметичного сегмента» в сочетании с дробным интерполяционным ресэмплером может использоваться для динамической регулировки очень небольших (и непостоянных!) Разностей частоты дискретизации. Большие скачки или пропуски в ставках обычно требуют более сложных стратегий сокрытия ошибок. Множество вариантов циклических / кольцевых буферов без блокировки с использованием примитивов atomi c для передачи данных между асинхронными c аудиопотоками. Я использую таймеры маха или таймер связи CADisplay для управления потоками опроса пользовательского интерфейса (для элементов управления, дисплеев и т. Д. c.). Обычно я сначала пытаюсь запустить выход и заполнить его тишиной, пока вход не начнет подавать сэмплы, а затем постепенно затухать. Затем снова затухать, чтобы замолчать после остановки входа.

2 голосов
/ 19 февраля 2020

Время go Я играл с концептуальным игровым аудиомиксером в C. Ничего из этого не закончено, но вещи действительно работают. Библиотека использует самый низкий из доступных Core Audio API, таким образом, действительно, с такими вещами, как AudioDeviceCreateIOProcID и AudioObjectAddPropertyListener.

Короче говоря, эта игровая площадка позволяет мне использовать несколько аудиоустройств, известных MacOS, и маршрутизировать один или несколько аудио потоки между ними, проходя через различные виды «узлов» на этом пути (например, представьте себе узел матричного микшера).

Сначала, в ответ на ваши вопросы

AudioDeviceStart() инициированные обратные вызовы будет запускать каждый из другого (случайного) потока. Кроме того, обратные вызовы не будут вызываться в определенном порядке c. Я также обнаружил, что разница между обратными вызовами может сильно различаться (по-видимому, в зависимости от аудиоустройства, предоставляющего / запрашивающего данные). Чтобы решить эту проблему, я использовал безблокировочный (то есть, использующий счетчики Atomi c) кольцевой буфер.

Ваша озабоченность по поводу различных часовых доменов очень реальна. Два устройства, работающие на частоте 96 кГц, будут работать на разных скоростях. Это может go хорошо в течение долгого времени, но в конечном итоге у одного из них закончатся данные и начнется сбой. Если внешние устройства не синхронизируются друг с другом, например, с помощью слова или ptp, они будут работать в своем собственном временном домене. Для передачи аудио между разными временными областями вам нужно будет asyn c -sample-rate-преобразовать аудиоданные. И SR C должен будет иметь возможность конвертировать в очень маленьких крыс ios и корректировать по пути. Одним из тех, кто делает это очень хорошо, является Soxr . В мире Core Audio есть VarispeedNode, который позволяет вам делать в основном то же самое. Большим недостатком решения asyn c -sr c является задержка, которую он вводит, однако, возможно, вы могли бы указать «низкую задержку».

В вашем случае синхронизация различных аудиоустройств будет самая большая проблема В моем случае я обнаружил, что обратные вызовы различных аудиоустройств слишком сильно различаются, чтобы выбрать один из них для «синхронизации часов», поэтому в итоге я создал автономную временную область, тщательно рассчитав время выполнения цикла обработки. Для этого я использовал низкоуровневые механизмы синхронизации, такие как mach_wait_until() и mach_absolute_time() (документации по этому вопросу немного).

Агрегированные устройства

Однако может быть другое решение. Глядя на документацию в AudioHardware.h от платформы CoreAudio, кажется, что есть способ создать агрегатное устройство программно с использованием AudioHardwareCreateAggregateDevice(). Это позволяет MacOS управлять синхронизацией различных аудиоустройств. Также обратите внимание на ключ kAudioAggregateDeviceIsPrivateKey, который позволяет вам создавать агрегатное устройство, не публикуя его во всей системе. Таким образом, устройство не будет отображаться в Audio MIDI Setup (я думаю). Также обратите внимание, что этот ключ приводит к исчезновению агрегата, когда процесс, который его создал, останавливается. Это может или не может быть то, что вам нужно, но это будет очень надежный способ реализации с использованием нескольких аудиоустройств. Если бы мне снова пришлось писать программное обеспечение, я бы определенно изучил этот способ синхронизации.

Прочие полезные советы и подсказки

В целом при работе с аудио с низкой задержкой вы хотите достичь максимально детерминированное поведение c. Но я уверен, что вы знаете об этом.

Другой недостаток заключается в том, что документация API Core Audio недоступна на сайте разработчика Apple (https://developer.apple.com/documentation/coreaudio/core_audio_functions?language=objc). Для этого вам нужно погрузиться в заголовки платформы Core Audio, где вы найдете много полезной документации по использованию API.

На моей машине заголовки расположены по адресу: /Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/CoreAudio.framework/Versions/A/Headers

Дополнительное чтение:

http://atastypixel.com/blog/four-common-mistakes-in-audio-development http://www.rossbencina.com/code/real-time-audio-programming-101-time-waits-for-nothing https://developer.apple.com/library/archive/qa/qa1467/_index.html

...