Аудиоустройства iOS - подключение с помощью графиков?
Я спрыгнул с глубокого конца и решил выяснить звук с низкой задержкой на iOS с помощью Audio Units. Я прочитал столько документации (от Apple и множества форумов), сколько смог найти, и общие концепции имеют смысл, но я все еще ломаю голову над некоторыми концепциями, с которыми мне нужна помощь:
Я где-то видел, что AU Graphs устарели и вместо этого я должен подключать аудиоустройства напрямую. Я не против... но как? Мне просто нужно использовать свойство Connection аудиоустройства, чтобы подключить его к источнику AU, и я пойду? Инициализировать и запустить юниты и наблюдать за волшебством? (потому что это не для меня...)
Какую настройку аудиоустройства лучше всего использовать, если я просто хочу получить звук с микрофона, выполнить некоторую обработку аудиоданных, а затем сохранить эти аудиоданные, не отправляя их на динамик RemoteIO, выход шины 0? Я попытался подключить GenericOutput AudioUnit, чтобы поймать данные в обратном вызове, но безуспешно...
Вот и все. Я могу предоставить код по запросу, но уже слишком поздно, и это меня уничтожило. Если есть простой ответ, это круто. Я пришлю любые фрагменты кода по желанию. Достаточно сказать, что я легко могу получить простую настройку RemoteIO, микрофонного входа и выхода динамика, которая отлично работает. Задержки кажутся несуществующими (по крайней мере, на мой слух). Я просто хочу что-то сделать с данными микрофона и сохранить их в памяти, не передавая их на динамик. В конце концов, подключение эквалайзера и микшера стало бы модным, но постепенно.
FWIW, я кодирую на Xamarin Forms/C# land, но примеры кода на Objective C, Swift или что-то еще в порядке. Я застрял на концепциях, не обязательно на точном коде.
СПАСИБО!
0 ответов
Работать с аудиоустройствами без графика довольно просто и очень гибко. Чтобы соединить два устройства, вы вызываете AudioUnitSetProperty следующим образом:
AudioUnitConnection connection;
connection.sourceAudioUnit = sourceUnit;
connection.sourceOutputNumber = sourceOutputIndex;
connection.destInputNumber = destinationInputIndex;
AudioUnitSetProperty(
destinationUnit,
kAudioUnitProperty_MakeConnection,
kAudioUnitScope_Input,
destinationInputIndex,
&connection,
sizeof(connection)
);
Обратите внимание, что для модулей, подключенных таким образом, требуется, чтобы их формат потока был установлен единообразно, и что это должно быть сделано до их инициализации.
В вашем вопросе упоминаются аудиоустройства и графики. Как сказано в комментариях, концепция графа была заменена идеей присоединения "узлов" к AVAudioEngine. Затем эти узлы "подключаются" к другим узлам. Соединение узлов создает пути сигнала, а запуск двигателя делает все это возможным. Это может быть очевидно, но я пытаюсь ответить здесь в целом. Все это можно сделать в Swift или Objective-C.
В отношении звука iOS следует учитывать две высокоуровневые перспективы: идея "хоста" и идея "плагина". Хост - это приложение, на котором размещены плагины. Плагин обычно создается как "расширение приложения", и при необходимости вы можете найти дополнительные аудиоустройства. Вы сказали, что у вас есть один, который делает то, что вы хотите, поэтому все это объясняет код, используемый в хосте.
Присоедините AudioUnit к AVaudioEngine
var components = [AVAudioUnitComponent]()
let description =
AudioComponentDescription(
componentType: 0,
componentSubType: 0,
componentManufacturer: 0,
componentFlags: 0,
componentFlagsMask: 0
)
components = AVAudioUnitComponentManager.shared().components(matching: description)
.compactMap({ au -> AVAudioUnitComponent? in
if AudioUnitTypes.codeInTypes(
au.audioComponentDescription.componentType,
AudioUnitTypes.instrumentAudioUnitTypes,
AudioUnitTypes.fxAudioUnitTypes,
AudioUnitTypes.midiAudioUnitTypes
) && !AudioUnitTypes.isApplePlugin(au.manufacturerName) {
return au
}
return nil
})
guard let component = components.first else { fatalError("bugs") }
let description = component.audioComponentDescription
AVAudioUnit.instantiate(with: description) { (audioUnit: AVAudioUnit?, error: Error?) in
if let e = error {
return print("\(e)")
}
// save and connect
guard let audioUnit = audioUnit else {
print("Audio Unit was Nil")
return
}
let hardwareFormat = self.engine.outputNode.outputFormat(forBus: 0)
self.engine.attach(au)
self.engine.connect(au, to: self.engine.mainMixerNode, format: hardwareFormat)
}
После того, как ваш AudioUnit загружен, вы можете подключить свой Athe AVAudioNodeTapBlock ниже, он имеет больше возможностей, поскольку он должен быть двоичным или чем-то, что могут загружать другие хост-приложения, которые не ваши.
Запись AVAudioInputNode
(Вы можете заменить аудиоблок входным узлом.)
В приложении вы можете записывать звук, создав AVAudioInputNode или просто ссылаясь на свойство inputNode AVAudioEngine, которое по умолчанию будет подключено к выбранному устройству ввода системы (микрофон, линейный вход и т. Д.).
Когда у вас есть входной узел, звук которого вы хотите обработать, затем "установите кран" на этом узле. Вы также можете подключить узел ввода к узлу микшера и установить там кран.
https://developer.apple.com/documentation/avfoundation/avaudionode/1387122-installtap
func installTap(onBus bus: AVAudioNodeBus,
bufferSize: AVAudioFrameCount,
format: AVAudioFormat?,
block tapBlock: @escaping AVAudioNodeTapBlock)
Установленный кран разделит ваш аудиопоток на два пути сигнала. Он будет продолжать отправлять звук на устройство вывода AvaudioEngine, а также отправлять звук в функцию, которую вы определяете. Эта функция (AVAudioNodeTapBlock) передается в installTap из AVAudioNode. Подсистема AVFoundation вызывает AVAudioNodeTapBlock и передает вам входные данные по одному буферу за раз вместе со временем прибытия данных.
https://developer.apple.com/documentation/avfoundation/avaudionodetapblock
typealias AVAudioNodeTapBlock = (AVAudioPCMBuffer, AVAudioTime) -> Void
Теперь система отправляет аудиоданные в программируемый контекст, и вы можете делать с ними все, что хотите. Чтобы использовать его в другом месте, вы можете создать отдельный AVAudioPCMBuffer и записать каждый из переданных буферов в него в AVAudioNodeTapBlock.