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