import AVFoundation
import UIKit
class ViewController: UIViewController {
private let engine = AVAudioEngine()
private let player = AVAudioPlayerNode()
private let ioBufferSize = 1024.0 // Or 256.0
override func viewDidLoad() {
super.viewDidLoad()
let audioSession = AVAudioSession.sharedInstance()
try! audioSession.setPreferredIOBufferDuration(ioBufferSize / 44100.0)
try! audioSession.setActive(true)
engine.attach(player)
engine.connect(player, to: engine.mainMixerNode, format: nil)
try! engine.start()
print("IO buffer duration: \(audioSession.ioBufferDuration)")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if player.isPlaying {
player.stop()
} else {
let startTime = CACurrentMediaTime()
player.play()
let endTime = CACurrentMediaTime()
print("\(endTime - startTime)")
}
}
}
这里是使用1024缓冲区大小(我认为这是默认值)得到的play()的一些示例时间:
0.0218
0.0147
0.0211
0.0160
0.0184
0.0194
0.0129
0.0160
以下是使用256个缓冲区大小的一些示例时间:
0.0014
0.0029
0.0033
0.0023
0.0030
0.0039
0.0031
0.0032
如上所示,对于缓冲区大小为1024,执行时间往往在15-20毫秒范围内(大约在60 FPS下的一个完整帧)。对于缓冲区大小为256,大约是3毫秒 - 不算太糟糕,但当你每帧只有约17毫秒可用时仍然代价高昂。
这是在运行iOS 12.4.2的iPad Mini 2上。这显然是一台旧设备,但我在模拟器上看到的结果似乎成比例,因此似乎更多地与缓冲区大小和函数本身的行为有关,而不是使用的硬件。我不知道底层发生了什么,但play()似乎可能阻塞直到下一个音频周期的开始,或者类似于那样的东西。
要求较低的缓冲区大小似乎是部分解决方案,但也存在一些潜在的缺点。根据此处的文档,较低的缓冲区大小可能意味着从文件流式传输时需要更多的磁盘访问,并且无论如何,请求可能根本不会得到满足。此外,此处,有人报告了与低缓冲区大小相关的播放问题。考虑到所有这些,我不愿意将其作为解决方案。
这使得play()的执行时间在15-20毫秒范围内,通常意味着在60 FPS下错过一帧。如果我安排事情,只有一个调用play()是在一次中进行,并且仅在偶尔发生,也许它不会被注意到,但这并不理想。
我已经搜索了信息并在其他地方询问了这个问题,但似乎没有多少人在实践中遇到这种行为,或者对他们来说这不是问题。
AVAudioEngine旨在用于实时应用程序,因此如果我正确,AVAudioPlayerNode.play()在相对于缓冲区大小的显着时间内阻塞,这似乎是一个设计问题。我意识到这可能不是很多人正在处理的问题,但我在这里发布是要问是否有人在AVAudioEngine中遇到了这个特定问题,如果有,是否有任何见解,建议或解决方法可以提供。