关于ios:Audio-Kit-播放的相关源代码看看

36次阅读

共计 4242 个字符,预计需要花费 11 分钟才能阅读完成。

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

正文完
 0