iOSAVAudioEngine使⽤教程
翻译: AK
声明:转发本⽂,请联系作者授权
在这个AVAudioEngine教程中,您将学习如何使⽤Apple的更⾼级⾳频⼯具包添加⾼级⾳频功能.
向⼤多数iOS开发⼈员提及⾳频处理,它们会给你带来恐惧和恐惧。这是因为,在iOS 8之前,要深⼊了解⾮常低层的Core Audio frameworw - 只有少数勇敢才才能做到这⼀点。值得庆幸的是,随着iOS 8和AVAudioEngine的发布,这⼀切都在2014年发⽣了变化。这个AVAudioEngine教程将向您展⽰如何使⽤Apple的新的更⾼级别的⾳频⼯具包来制作⾳频处理应⽤程序,⽽⽆需深⼊研究Core Audio。
那就对了!您不再需要搜索模糊的基于指针的C / C ++结构和内存缓冲区来收集原始⾳频数据。
在这个AVAudioEngine教程中,您将使⽤AVAudioEngine构建下⼀个优秀的播客应⽤程序:Raycast。更具体地说,您将添加由UI控制的⾳频功能:播放/暂停按钮,跳过前进/后退按钮,进度条和播放速率选择器。当你完成后,你会有⼀个很棒的应⽤程序,可以听听和。
开始
在开始之前,下载这个教程的材料(在⽂章的最下⾯,你可以看到这个下载按钮),使⽤Xcode编译运⾏,你就可以看到基础界⾯了.
这些控制没有做任何事, 但是他们都联接到了IBOutlets和关联了IBActions 在view controllers中.
iOS Audio Framework 介绍
在进⼊⼯作之前,我们先快速浏览⼀下iOS Audio frameworks
CoreAudio and AudioToolbox 都是c低级接⼝的 frameworks.
AVFoundation 是 Objective-C/Swift framework.
AVAudioEngine 是 AVFoundation 的⼀部分.
AVAudioEngine是⼀个定义⼀组连接的⾳频节点的类,你在项⽬添加两个节点 AVAudioPlayerNode 和 AVAudioUnitTimePitch
设置⾳频
打开ViewController.swift并查看类内容。在顶部,您将看到所有连接的接⼝和类变量。这些事件已经连接到了 storyboard中.
在setupAudio() 加⼊下⾯代码:
// 1
audioFileURL = Bundle.main.url(forResource: "Intro", withExtension: "mp4")
// 2
engine.attach(player)
engine.prepare()
do {
// 3
try engine.start()
} catch let error {
print(error.localizedDescription)
}
仔细看都发⽣哪些事情:
1. 获取bundle中声⾳⽂件的URL, 把audioFileURL 传值给 audioFile
2. 在连接其它节点之前先要把播放器连接到engine,这些节点将处理和输出⾳频.这些节点将⽣成,处⾳频引擎提供连接到播放器节点的主
混⾳器节点。默认情况下,主混⾳器连接到engine默认输出节点(iOS设备扬声器)。 prepare()预分配所需的资源.
接下来,将下⾯的代码scheduleAudioFile()⽅法中
guard let audioFile = audioFile else { return }
skipFrame = 0
player.scheduleFile(audioFile, at:nil) { [weak self] in
self?.needsFileScheduled = true
}
这会播放整个audioFile。 at:是您希望⾳频播放的指定时间(AVAudioTime).设置为nil会⽴即开始播放。该⽂件仅会播放⼀次。再次点击“播放”按钮不会从头重新开始。您需要重新安排再次播放。播放完⾳频⽂件后,在完成回调中设置needsFileScheduled
其它的播放⽅法
//提供了预加载⾳频数据的缓冲区
- scheduleBuffer(AVAudioPCMBuffer, completionHandler:AVAudioNodeCompletionHandler? = nil):
//这就像scheduleFile,可以指定开始播放的⾳频帧和播放的帧数
- scheduleSegment(AVAudioFile, startingFrame:AVAudioFramePosition, frameCount:AVAudioFrameCount, at:AVAudioTime?, completionHandler:下⾯代码添加到 playTapped ⽅法中
// 1
sender.isSelected = !sender.isSelected
// 2
if player.isPlaying {
player.pause()
} else {
if needsFileScheduled {
needsFileScheduled = false
scheduleAudioFile()
}
player.play()
}
标记
- 1 切换按钮的选择状态,这会更改storyboard中设置的按钮图像
- 2 使⽤player.isPlaying来判断当前播放器的状态,如果正常可以暂停 如果没有播放,你要检查⼀下needsFileScheduled 和⾳频⽂件
编译并运⾏ 然后点 playPauseButton 你应该可以听到Ray’s的什么歌, 但没有UI反馈 你不知道⽂件⼀共多长, 现在播放到哪了.
添加进度回调
添加下⾯代码 到 viewDidLoad()⽅法中
updater = CADisplayLink(target: self, selector: #selector(updateUI))
updater?.add(to: .current, forMode: .defaultRunLoopMode)
updater?.isPaused = true
CADisplayLink是⼀个计时器对象,与显⽰器的刷新率同步。您使⽤selector updateUI实例化它。然后,将其添加到运⾏循环中 - 在本例中为default run loop。最后,它不需要开始运⾏,只⽤将isPaused设置为true
修改playTapped(_:)⽅法中的实现
sender.isSelected = !sender.isSelected
nodeselector
if player.isPlaying {
disconnectVolumeTap()
updater?.isPaused = true
player.pause()
} else {
if needsFileScheduled {
needsFileScheduled = false
scheduleAudioFile()
}
connectVolumeTap()
updater?.isPaused = false
player.play()
}
这⾥的关键是使⽤updater.isPaused = true暂停UI。您将在下⾯的VU Meter部分中了解connectVolumeTap()和disconnectVolumeTap()
使⽤下⾯的代码覆盖 var currentFrame: AVAudioFramePosition = 0
var currentFrame: AVAudioFramePosition {
// 1
guard
let lastRenderTime = player.lastRenderTime,
// 2
let playerTime = player.playerTime(forNodeTime: lastRenderTime)
else {
return0
}
// 3
return playerTime.sampleTime
}
currentFrame 是播放器返回的最新⼀个⾳频数据,下⾯我们仔细看
1, player.lastRenderTime 返回引擎的的开始时间,如果没有播放 lastRenderTime 返回NIL
2,player.playerTime(forNodeTime:) 转换 lastRenderTime 到播放器的开始时间,如果播放器没有播放playerTime 返回nil 3,sampleTime 是⾳频⽂件数据中的时间戳
下⾯更新UI 把下⾯的代码放到updateUI()⽅法中
// 1
currentPosition = currentFrame + skipFrame
currentPosition = max(currentPosition, 0)
currentPosition = min(currentPosition, audioLengthSamples)
// 2
progressBar.progress = Float(currentPosition) / Float(audioLengthSamples)
let time = Float(currentPosition) / audioSampleRate
< = formatted(time: time)
< = formatted(time: audioLengthSeconds - time)
// 3
if currentPosition >= audioLengthSamples {
player.stop()
updater?.isPaused = true
playPauseButton.isSelected = false
disconnectVolumeTap()
}
让我们⼀步步查看
1,属性skipFrame是添加到currentFrame或从currentFrame中减去的偏移量,最初设置为零。确保currentPosition不超出⽂件范围 2,将progressBar.progress更新为audioFile中的currentPosition。通过将currentPosition除以audioFile的sampleRate来计算时间。将countUpLabel和countDownLabel⽂本更新为audioFile中的当前时间
3,如果 currentPosition已经到了⽂件的结尾 然后:
停⽌播放器
暂停timer
重新设置playPauseButton的选中状
断开⾳量的事件
编译运⾏ 然后再⼀次点击 playPauseButton,你可以听到Ray’s intro,同时可以看到进度条的时间信息
实现VU Meter
现在是时候添加VU Meter功能了。这是⼀个UIView定位在两栏之间。视图的⾼度由播放⾳频的平均功率决定。这是您进⾏某些⾳频处理的第⼀次机会。
您将计算1k⾳频样本缓冲区的平均功率。确定⾳频样本缓冲器的平均功率的常⽤⽅法是计算样本的均⽅根(RMS)。
平均功率是以分贝表⽰的⼀系列⾳频样本数据的平均值。还有峰值功率,这是⼀系列样本数据中的最⼤值
在connectVolumeTap⽅法下⾯ 添加⼀个⼯具⽅法
func scaledPower(power: Float) -> Float {
// 1
guard power.isFinite else { return0.0 }
// 2
if power < minDb {
return0.0
} else if power >= 1.0 {
return1.0
} else {
// 3
return (fabs(minDb) - fabs(power)) / fabs(minDb)
}
}
scaledPower(power :)将负功率分贝值转换为正值,以调整上⾯的stant值。这是它的作⽤
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论