实时音频处理与AVAudioEngine

18

你好。我想用Swift中的新AVAudioEngine实现一个实时音频应用程序。有人对这个新框架有经验吗?实时应用程序是如何工作的?

我的第一个想法是将(处理后的)输入数据存储到AVAudioPCMBuffer对象中,然后通过AVAudioPlayerNode让其播放,就像你在我的演示类中看到的那样:

import AVFoundation

class AudioIO {
    var audioEngine: AVAudioEngine
    var audioInputNode : AVAudioInputNode
    var audioPlayerNode: AVAudioPlayerNode
    var audioMixerNode: AVAudioMixerNode
    var audioBuffer: AVAudioPCMBuffer

    init(){
        audioEngine = AVAudioEngine()
        audioPlayerNode = AVAudioPlayerNode()
        audioMixerNode = audioEngine.mainMixerNode

        let frameLength = UInt32(256)
        audioBuffer = AVAudioPCMBuffer(PCMFormat: audioPlayerNode.outputFormatForBus(0), frameCapacity: frameLength)
        audioBuffer.frameLength = frameLength

        audioInputNode = audioEngine.inputNode

        audioInputNode.installTapOnBus(0, bufferSize:frameLength, format: audioInputNode.outputFormatForBus(0), block: {(buffer, time) in
            let channels = UnsafeArray(start: buffer.floatChannelData, length: Int(buffer.format.channelCount))
            let floats = UnsafeArray(start: channels[0], length: Int(buffer.frameLength))

            for var i = 0; i < Int(self.audioBuffer.frameLength); i+=Int(self.audioMixerNode.outputFormatForBus(0).channelCount)
            {
                // doing my real time stuff
                self.audioBuffer.floatChannelData.memory[i] = floats[i];
            }
            })

        // setup audio engine
        audioEngine.attachNode(audioPlayerNode)
        audioEngine.connect(audioPlayerNode, to: audioMixerNode, format: audioPlayerNode.outputFormatForBus(0))
        audioEngine.startAndReturnError(nil)

        // play player and buffer
        audioPlayerNode.play()
        audioPlayerNode.scheduleBuffer(audioBuffer, atTime: nil, options: .Loops, completionHandler: nil)
    }
}

但这远离实时且效率不高。有什么想法或经验吗?如果您更喜欢Objective-C或Swift,也没关系,我很感激所有的笔记、评论、解决方案等。


不建议使用Objective-C进行实时编程。我不知道苹果公司在Swift中对实时编程有没有官方立场,但是在http://prod.lists.apple.com/archives/coreaudio-api/2014/Jun/msg00002.html上有一些讨论。 - sbooth
1
谢谢提供链接,但到目前为止讨论的要点是:没有人知道任何东西。;-) 但问题更多关于新编程语言或者说如果Objective-C能否进行实时处理,那么我如何使用苹果在其WWDC14会议第502号演讲中宣传的AVAudioEngine用于实时应用程序。 - Michael Dorner
3
Objective-C可用于编写实时音频应用程序,但在Core Audio的“IOProcs”内部有所限制。例如,不能进行内存分配、锁定、Objective-C方法调用等。请参见http://www.rossbencina.com/code/real-time-audio-programming-101-time-waits-for-nothing 。我想内部AVAudioEngine仅在实时方法中使用C,并且我也打赌振动器具有与“IOProcs"相同的限制。 - sbooth
Michael,对于缓冲区的处理,我建议使用简单明了的C语言。Swift和ObjC都会因为ARC、内部锁和内存分配而引入不可预测的开销。C语言最适合用于处理缓冲区。当需要将数据提供给主线程进行显示时,请使用无锁循环缓冲区和ObjC。但是你为什么要自己复制输入缓冲区呢?你可以直接将AVAudioEngine.inputNode连接到AVAudioEngine.outputNode - bio
“实时”是指录制并执行绘制麦克风信号波形或将捕获的音频即时传送到语音识别器等操作吗?如果是,请告诉我,我会将我的代码发布为答案。 - Josh
第二个是实时信号处理。谢谢。 - Michael Dorner
4个回答

12

我一直在尝试使用AVAudioEngine进行Objective-C和Swift编程。在我的Objective-C引擎中,所有音频处理都是纯粹用C完成的(通过AVAudioPCMBuffer缓存可用的原始C样本指针,并使用只有C代码的数据操作)。表现非常出色。出于好奇心,我将此引擎转移到了Swift上。像线性播放音频文件或通过FM合成生成音调等任务时,性能相当不错,但一旦涉及到数组(例如,使用颗粒合成,在其中以非线性方式播放和操作音频的部分),就会有显着的性能损失。即使进行了最佳优化,CPU使用率也比Objective-C/C版本高30-40%。我对Swift还很陌生,所以可能有其他我不知道的优化方法,但据我所知,C/C ++仍然是实时音频的最佳选择。也可以看看“The Amazing Audio Engine”。我正在考虑这个,以及直接使用较旧的C API。

如果您需要处理实时音频,则AVAudioEngine可能不适合您。请参阅我的答案:我希望每秒调用20次installTapOnBus:bufferSize:format:block:


但是:实际上我的问题与AVAudioEngine框架本身无关,也与Objective-C/Swift无关。但当然,它们是密切相关的。 - Michael Dorner
1
Michael Dorner,既然你说我没有回答你的问题,而我觉得我已经回答了,也许如果你重新表述一下,我可以补充一些有用的额外信息。我一直在利用业余时间解决类似的问题(尝试使用高级语言进行AVAudioEngine实验),并且很乐意分享我所学到的知识。 - Jason McClinsey
1
@JasonMcClinsey:你能否发布一些演示如何使用C和AVAudioPCMBuffer进行FM合成的代码?(有一个名为AVAudioUnitGenerator的新类,听起来很有前途,但文档很薄,并且说它是一个“正在开发中”的API。) - RonH
@JasonMcClinsey:我应该更加具体地说明:我正在寻找Swift代码。 - RonH
@JasonMcClinsey 我很困惑... 你之前在Objective-C中使用AVAudioEngine运行得很好 - 然后你为了某些原因将其移植到Swift。然后你发现性能急剧下降... 然后你责怪... AVAudioEngine?听起来像是Swift的移植完全应该受到责备。 - Roy Lovejoy
@RoyLovejoy,请再次阅读我的评论,因为我并没有责怪AVAudioEngine。 - Jason McClinsey

7

我正在通过套接字连接获取流音频缓冲区。如何使用音频引擎播放这些缓冲区? - Bhuvanendra Pratap Maurya

5

苹果公司已经就Swift实时编码问题发表了官方立场。在2017 WWDC音频核心专题中,一名苹果工程师表示不要在实时音频回调上下文中使用Swift或Objective C方法(这意味着只使用C、或者可能是C++或汇编语言代码)。

这是因为使用Swift和Objective C方法可能涉及内部内存管理操作,这些操作没有有界延迟。

这意味着正确的方案可能是将Objective C用于您的AVAudioEngine控制器类,但仅在tapOnBus块内使用Objective C的C子集。


2
苹果当前网站上的WWDC视频是https://developer.apple.com/videos/play/wwdc2017/501/?time=1268(“现在来到了实际的渲染逻辑。请注意,代码的这部分是用C++编写的,因为正如我所提到的,在实时环境中使用Objective-C或Swift运行时是不安全的。”)[更新:我看到@LloydRochester也引用了类似的部分。] - natevw

2
根据苹果公司在WWDC2017的音视频核心功能新特性中,在大约19:20处,工程师表示从实时上下文使用Swift是“不安全的”。这段话摘自文字记录。
由于引擎的渲染是发生在实时上下文中的,因此您将无法使用演示中看到的离线Objective-C或Swift元。而且,从实时上下文中使用Objective-C或Swift运行时是不安全的。因此,引擎本身提供了一个渲染块,您可以搜索和缓存,然后稍后使用此渲染块从实时上下文中渲染引擎。接下来要做的是设置输入节点,以便您可以向引擎提供输入数据。在这里,您指定您将提供的输入格式,并且这可以与输出不同。您还提供一个块,引擎将在需要输入数据时调用该块。当调用此块时,引擎会告诉您实际需要多少个输入帧。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接