AVAudioEngine 可以做实时的音效处理,用 Effect Unit 加效果
4.1 播放先
设置 AudioEngine,添加节点,连接节点
func setupAudioEngine(){
// 添加节点
attachNodes()
// 连接节点
connectNodes()
// 准备 AudioEngine
engine.prepare()
// AVAudioEngine 的数据流,采用推 push 模型
// 使用计时器,每隔 0.1 秒左右,调度播放资源 let interval = 1 / (readFormat.sampleRate / Double(readBufferSize)) let timer = Timer(timeInterval: interval / 2, repeats: true) {
[weak self] _ in guard self?.state != .stopped else { return }
// 分配缓冲 buffer, 调度播放资源
self?.scheduleNextBuffer()
self?.handleTimeUpdate()
self?.notifyTimeUpdated()
}
RunLoop.current.add(timer, forMode: .common)
}
// 添加播放节点
open func attachNodes() {
engine.attach(playerNode)
}
// 播放节点,连通到输出
open func connectNodes() {
engine.connect(playerNode, to: engine.mainMixerNode, format: readFormat)
}
调度播放资源,将数据 ( 上步创建的音频缓冲 buffer )交给 AudioEngine 的播放节点 playerNode
func scheduleNextBuffer(){
guard let reader = reader else { return }
// 通过状态记录,管理播放
// 播放状态,就是一个开关
guard !isFileSchedulingComplete || repeats else { return } do {
// 拿到,上步创建音频缓冲 buffer let nextScheduledBuffer = try reader.read(readBufferSize)
// playerNode 播放消费掉
playerNode.scheduleBuffer(nextScheduledBuffer)
} catch ReaderError.reachedEndOfFile {
isFileSchedulingComplete = true } catch { }
}
开启播放
public func play() {
// 没播放,才开启
guard !playerNode.isPlaying else { return } if !engine.isRunning { do {
try engine.start()
} catch { }
}
// 提升用户体验,播放前,先静音 let lastVolume = volumeRampTargetValue ?? volume
volume = 0
// 播放节点播放
playerNode.play()
// 250 毫秒后,正常音量播放
swellVolume(to: lastVolume)
// 更新播放状态
state = .playing
}
4.2 音效后
添加实时的音高、播放速度效果
// 使用 AVAudioUnitTimePitch 单元,调节播放速度和音高效果 let timePitchNode = AVAudioUnitTimePitch()
override func attachNodes() {
// 添加播放节点
super.attachNodes()
// 添加音效节点
engine.attach(timePitchNode)
}
// 相当于在播放节点和输出节点中间,插入音效节点
override func connectNodes() {
engine.connect(playerNode, to: timePitchNode, format: readFormat)
engine.connect(timePitchNode, to: engine.mainMixerNode, format: readFormat)
}