Audio Kit 真正厉害的是,MIDI 电子乐相关,
本文简单看看 Audio Kit 播放相关的源代码
调用部分
let engine = AudioEngine()
let player = AudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
player.isLooping = true
engine.output = player
do {
try engine.start()
} catch {
print("AudioKit did not start! \(error)")
}
}
// 停止播放
func toStop(){
player.stop()
}
// 播放
func toPlay(){
player.stop()
let url = Bundle.main.url(forResource: "x", withExtension: "mp3")
var file: AVAudioFile?
do {
file = try AVAudioFile(forReading: url!)
} catch {
print(error)
}
guard let f = file else {
return
}
let buffer = try! AVAudioPCMBuffer(file: f)!
player.buffer = buffer
player.schedule(at: nil)
player.play()
}
播放使用 3 步:
- 创建 engine 和 player, 指定输出
engine.output = player
再开启 engine
- 准备播放文件
拿文件 url, 创建 AVAudioFile,
再拿 AVAudioFile,去创建 AVAudioPCMBuffer,
- 给音频播放节点调度 AVAudioPCMBuffer,去播放
源代码部分
准备 engine 的工作,连接节点,
engine.output = player
背后的是,
/// Output node
public var output: Node? {
didSet {
// AVAudioEngine doesn't allow the outputNode to be changed while the engine is running
let wasRunning = avEngine.isRunning
if wasRunning { stop() }
// remove the exisiting node if it is present
if let node = oldValue {
mainMixerNode?.removeInput(node)
node.detach()
avEngine.outputNode.disconnect(input: node.avAudioNode)
}
// if non nil, set the main output now
if let node = output {
avEngine.attach(node.avAudioNode)
// has the sample rate changed?
if let currentSampleRate = mainMixerNode?.avAudioUnitOrNode.outputFormat(forBus: 0).sampleRate,
currentSampleRate != Settings.sampleRate {
print("Sample Rate has changed, creating new mainMixerNode at", Settings.sampleRate)
removeEngineMixer()
}
// 上面的是,完成初始化的状态,
// 下面的是,建立新状态,我们需要的
// create the on demand mixer if needed
createEngineMixer()
mainMixerNode?.addInput(node)
mainMixerNode?.makeAVConnections()
}
if wasRunning { try? start() }
}
}
里面的代码,主要是辞旧迎新
辞旧: 这个 engine, 可能不是第一次使用,把他以前的状态,给去除掉
迎新: 建立我们需要的状态
把 player 、 mixer 和 avEngine.outputNode,添加进 engine,
engine: player -> mixer -> avEngine.outputNode
private func createEngineMixer() {
guard mainMixerNode == nil else { return }
let mixer = Mixer()
avEngine.attach(mixer.avAudioNode)
avEngine.connect(mixer.avAudioNode, to: avEngine.outputNode, format: Settings.audioFormat)
mainMixerNode = mixer
}
这个方法中,avEngine 添加 Mixer 节点,并且连好了
/// Add input to the mixer
/// - Parameter node: Node to add
public func addInput(_ node: Node) {
guard !hasInput(node) else {
print(" Error: Node is already connected to Mixer.")
return
}
connections.append(node)
makeAVConnections()
}
可以看出, 这里的逻辑有些冗余,
如果 mixer 有了这个节点,makeAVConnections()
才正好走一遍,
否则,一般会多调用一次
mainMixerNode?.addInput(node)
mainMixerNode?.makeAVConnections()
准备音频文件
extension AVAudioPCMBuffer {
/// Read the contents of the url into this buffer
public convenience init?(url: URL) throws {
guard let file = try? AVAudioFile(forReading: url) else { return nil }
try self.init(file: file)
}
/// Read entire file and return a new AVAudioPCMBuffer with its contents
public convenience init?(file: AVAudioFile) throws {
file.framePosition = 0
self.init(pcmFormat: file.processingFormat,
frameCapacity: AVAudioFrameCount(file.length))
try file.read(into: self)
}
}
关键是这个方法, public convenience init?(file: AVAudioFile) throws
先申请一块内存,
再把音频数据,读进去
去播放
即,给资源,调度资源,去播放
player.schedule(at: nil)
调度音频资源,塞给播放节点,这一步,
public func schedule(at when: AVAudioTime? = nil) {
if isBuffered, let buffer = buffer {
playerNode.scheduleBuffer(buffer,
at: nil,
options: bufferOptions,
completionCallbackType: .dataPlayedBack) { _ in
self.internalCompletionHandler()
}
scheduleTime = when ?? AVAudioTime.now()
} else if let file = file {
playerNode.scheduleFile(file,
at: when,
completionCallbackType: .dataPlayedBack) { _ in
self.internalCompletionHandler()
}
scheduleTime = when ?? AVAudioTime.now()
} else {
print("The player needs a file or a valid buffer to schedule")
scheduleTime = nil
}
}
这里有两种播放的形式,音频缓冲 buffer 和音频文件 file
有了音频播放文件,才好播放
其他功能:
循环播放功能
先改记录的属性
public var isLooping: Bool = false {
didSet {
bufferOptions = isLooping ? .loops : .interrupts
}
}
然后是循环播放的时机
调度音频资源方法,有一个完成回调
open func scheduleBuffer(_ buffer: AVAudioPCMBuffer, at when: AVAudioTime?, options: AVAudioPlayerNodeBufferOptions = [], completionCallbackType callbackType: AVAudioPlayerNodeCompletionCallbackType, completionHandler: AVAudioPlayerNodeCompletionHandler? = nil)
其回调方法中,
如果要求循环播放,则再次播放,走 play()
func internalCompletionHandler() {
guard isPlaying, engine?.isInManualRenderingMode == false else { return }
scheduleTime = nil
completionHandler?()
isPlaying = false
if !isBuffered, isLooping, engine?.isRunning == true {
print("Playing loop...")
play()
return
}
}
音频时长
extension AVAudioFile {
/// Duration in seconds
public var duration: TimeInterval {
Double(length) / fileFormat.sampleRate
}
}
音频资源文件,里面有多少帧,直接看 length
,
第一次获取所有的帧的数目,比较消耗性能
所有的帧的数目 / 采样率,就是音频文件的时长了