2

我已经跳入深渊,并决定使用音频单元在 iOS 上找出低延迟音频。我已经阅读了尽可能多的文档(来自 Apple 和大量论坛),总体概念很有意义,但我仍然对一些需要帮助的概念摸不着头脑:

  1. 我在某处看到不推荐使用 AU 图表,我应该直接连接音频单元。我对此很满意...但是如何?我是否只需要使用音频单元的 Connection 属性将其连接到源 AU,然后就可以走了?初始化并启动单元,然后观看奇迹发生吗?(因为它不适合我......)

  2. 如果我只是想从我的麦克风中获取音频,对音频数据进行一些处理,然后存储该音频数据而不将其发送到 RemoteIO 扬声器、总线 0 输出,那么最好的音频单元设置是什么?我尝试连接一个 GenericOutput AudioUnit 来捕获回调中的数据,但没有任何运气......

就是这样。我可以在请求时提供代码,但为时已晚,这已经把我消灭了。如果有简单的答案,那很酷。我会随意发送任何代码片段。可以这么说,我可以轻松获得一个简单的 RemoteIO、麦克风输入、扬声器输出设置,效果很好。延迟似乎不存在(至少在我看来)。我只想对麦克风数据做一些事情并将其存储在内存中,而不会将其发送到扬声器。最终连接 eq 和混音器会很时髦,但一步一步。

FWIW,我在 Xamarin Forms/C# 领域进行编码,但是在 Objective C、Swift 或其他任何好的代码示例中。我被困在概念上,不一定是确切的代码。

谢谢!

4

2 回答 2

2

在没有图表的情况下使用音频单元非常简单且非常灵活。要连接两个单元,您可以这样调用 AudioUnitSetProperty:

AudioUnitConnection connection;
connection.sourceAudioUnit = sourceUnit;
connection.sourceOutputNumber = sourceOutputIndex;
connection.destInputNumber = destinationInputIndex;
 
AudioUnitSetProperty(
    destinationUnit,
    kAudioUnitProperty_MakeConnection,
    kAudioUnitScope_Input,
    destinationInputIndex,
    &connection,
    sizeof(connection)
);

请注意,以这种方式连接的单元需要统一设置其流格式,并且必须在初始化之前完成。

于 2020-10-01T15:04:33.293 回答
1

您的问题提到了音频单元和图表。正如评论中所说,图形概念已被将“节点”附加到 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 或仅引用 AVAudioEngine 的“inputNode”属性来录制音频,该属性将默认连接到系统选择的输入设备(麦克风、线路输入等)

拥有要处理其音频的输入节点后,接下来在该节点上“安装水龙头”。您还可以将输入节点连接到混音器节点并在那里安装分接头。

https://developer.apple.com/documentation/avfoundation/avaudionode/1387122-installtap

func installTap(onBus bus: AVAudioNodeBus, 
                bufferSize: AVAudioFrameCount, 
                format: AVAudioFormat?, 
                block tapBlock: @escaping AVAudioNodeTapBlock)

安装的分接头基本上会将您的音频流分成两个信号路径。它将继续将音频发送到 AvaudioEngine 的输出设备,并将音频发送到您定义的函数。此函数(AVAudioNodeTapBlock)从 AVAudioNode 传递给“installTap”。AVFoundation 子系统调用 AVAudioNodeTapBlock 并将输入数据一次传递一个缓冲区以及数据到达的时间。

https://developer.apple.com/documentation/avfoundation/avaudionodetapblock

typealias AVAudioNodeTapBlock = (AVAudioPCMBuffer, AVAudioTime) -> Void

现在系统正在将音频数据发送到可编程上下文,您可以使用它做任何您想做的事情。要在其他地方使用它,您可以创建一个单独的 AVAudioPCMBuffer 并将每个传入的缓冲区写入 AVAudioNodeTapBlock 中。

于 2020-09-15T02:11:50.970 回答