使用 AVAudioEngine 的实时音频
Posted
技术标签:
【中文标题】使用 AVAudioEngine 的实时音频【英文标题】:Realtime Audio with AVAudioEngine 【发布时间】:2014-08-14 12:08:36 【问题描述】:嘿嘿。我想用 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,我很感谢所有笔记、评论、cmets、解决方案等。
【问题讨论】:
Objective-C 不推荐用于实时编程。我还不知道 Apple 在 Swift 实时编程方面采取了官方立场,但在 prod.lists.apple.com/archives/coreaudio-api/2014/Jun/… 上有一些讨论 感谢您提供的链接,但从本次讨论到现在的要点:没有人知道任何事情。 ;-) 但问题是关于新的编程语言,或者如果 Objective-C 能够实时处理,那么我如何将 AVAudioEngine 用于实时应用程序,这是 Apple 在其 WWDC14 会议号中宣传的。 502.跨度> Objective-C 可用于编写实时音频应用程序,但在 Core Audio 的IOProcs
中可以执行的操作有一些限制。例如,没有内存分配,没有锁,没有 Objective-C 方法调用等。参见rossbencina.com/code/… 我想在内部AVAudioEngine
在实时方法中只使用 C,我也打赌水龙头有相同的限制IOProcs
.
Michael,对于缓冲区抽头,我建议使用简单明了的 C。由于 ARC、内部锁和内存分配,Swift 和 ObjC 都引入了不可预测的开销。 C 最适合用于处理缓冲区。在将数据提供给主线程进行显示时,请使用无锁循环缓冲区和 ObjC。但是你为什么要自己复制输入缓冲区呢?您可以将AVAudioEngine.inputNode
直接连接到AVAudioEngine.outputNode
。
您所说的“实时”是指录制,并执行诸如绘制麦克风信号的波形之类的操作,或者将捕获的音频即时馈送到语音识别器?如果是这样,请告诉我,我会发布我的代码作为答案。
【参考方案1】:
我一直在 Objective-C 和 Swift 中尝试使用 AVAudioEngine。在我的引擎的 Objective-C 版本中,所有音频处理都完全在 C 中完成(通过缓存可通过 AVAudioPCMBuffer 获得的原始 C 样本指针,并仅使用 C 代码对数据进行操作)。表演令人印象深刻。出于好奇,我将这个引擎移植到了 Swift。对于线性播放音频文件或通过 FM 合成生成音调之类的任务,性能非常好,但只要涉及到阵列(例如,使用颗粒合成,音频部分会以非线性方式播放和操作),对性能有重大影响。即使进行了最佳优化,CPU 使用率也比 Objective-C/C 版本高 30-40%。我是 Swift 新手,所以也许还有其他我不知道的优化,但据我所知,C/C++ 仍然是实时音频的最佳选择。另请查看 The Amazing Audio Engine。我正在考虑这个,以及直接使用旧的 C API。
如果您需要处理现场音频,那么 AVAudioEngine 可能不适合您。请参阅我对这个问题的回答:I want to call 20 times per second the installTapOnBus:bufferSize:format:block:
【讨论】:
但是:实际上我的问题与 AVAudioEngine 框架本身无关,与 Objective-C/Swift 无关。但当然,它是密切相关的。 Michael Dorner,既然你说我没有回答你的问题,在我看来我回答了,也许如果你改写一下,我可以添加一些有用的额外信息。我在空闲时间一直在解决类似的问题(用更高级别的语言用 AVAudioEngine 进行实验),并且有兴趣分享我学到的东西。 @JasonMcClinsey:您愿意发布一些代码来演示您如何使用 C 和 AVAudioPCMBuffer 进行 FM 合成吗? (有一个名为 AVAudioUnitGenerator 的新类,听起来很有希望,但文档很薄,并说它是一个“正在开发”的 API。) @JasonMcClinsey:我应该更具体一点:我正在寻找 Swift 代码。 @JasonMcClinsey 我很困惑.. 你的 AVAudioEngine 在 Objective-C 中运行得很好——你出于某种原因将它移植到了 Swift。然后你看到性能下降了..你责怪.. AVAudioEngine?听起来 Swift 端口是 100% 的罪魁祸首。【参考方案2】:我认为这可以满足您的要求:https://github.com/arielelkin/SwiftyAudio
注释掉失真,你就有了一个干净的循环。
【讨论】:
我正在通过套接字连接获取流音频缓冲区。如何使用音频引擎播放缓冲区?【参考方案3】:Apple 在 Swift 中的实时编码方面采取了官方立场。在 2017 WWDC session on Core Audio 中,一位 Apple 工程师说不要在实时音频回调上下文中使用 Swift 或 Objective C 方法(暗示只使用 C,或者可能是 C++ 或汇编代码)。
这是因为使用 Swift 和 Objective C 方法可能涉及没有有限延迟的内部内存管理操作。
这意味着正确的方案可能是将 Objective C 用于您的 AVAudioEngine 控制器类,但仅在 tapOnBus 块内使用(Objective C)的 C 子集。
【讨论】:
苹果当前网站上的 WWDC 视频是 developer.apple.com/videos/play/wwdc2017/501/?time=1268(“现在是实际的渲染逻辑。请注意,这部分代码是用 C++ 编写的,这是因为正如我提到的,它不是在实时上下文中使用 Objective-C 或 Swift 运行时是安全的。”)[更新:我看到 @LloydRochester 也引用了类似的部分。]【参考方案4】:据苹果公司在 WWDC2017 上的What's New in Core Audio Video 大约 19:20 表示,工程师表示从实时环境来看,使用 Swift 是“不安全的”。从成绩单中发布。
现在,因为引擎的渲染是实时发生的 上下文,您将无法使用离线渲染 Objective-C 或 我们在演示中看到的 Swift 元数据。
那是因为,使用 Objective-C 或 Swift 并不安全 来自实时上下文的运行时。因此,相反,引擎本身 为您提供可以搜索和缓存的渲染块,然后 稍后使用此渲染块实时渲染引擎 语境。接下来是——要做的就是设置你的输入节点 您可以将输入数据提供给引擎。在这里,你 指定您将提供的输入格式,这可以是 与输出不同的格式。你还提供了一个块 引擎会在需要输入数据时调用。当这 块被调用,引擎会告诉你有多少 它实际需要的输入帧。
【讨论】:
以上是关于使用 AVAudioEngine 的实时音频的主要内容,如果未能解决你的问题,请参考以下文章
使用 Swift 使用 AVAudioEngine 实时进行音高转换