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,

第一次获取所有的帧的数目,比拟耗费性能

所有的帧的数目 / 采样率,就是音频文件的时长了

github repo