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
,
第一次获取所有的帧的数目,比拟耗费性能
所有的帧的数目 / 采样率,就是音频文件的时长了