Аудиоустройства iOS - подключение с помощью графиков?

Я спрыгнул с глубокого конца и решил выяснить звук с низкой задержкой на iOS с помощью Audio Units. Я прочитал столько документации (от Apple и множества форумов), сколько смог найти, и общие концепции имеют смысл, но я все еще ломаю голову над некоторыми концепциями, с которыми мне нужна помощь:

  1. Я где-то видел, что AU Graphs устарели и вместо этого я должен подключать аудиоустройства напрямую. Я не против... но как? Мне просто нужно использовать свойство Connection аудиоустройства, чтобы подключить его к источнику AU, и я пойду? Инициализировать и запустить юниты и наблюдать за волшебством? (потому что это не для меня...)

  2. Какую настройку аудиоустройства лучше всего использовать, если я просто хочу получить звук с микрофона, выполнить некоторую обработку аудиоданных, а затем сохранить эти аудиоданные, не отправляя их на динамик 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.

Другие вопросы по тегам