本文是Intermediate iOS 11 Programming with Swift 4系列?的 第 十 篇.

iOS SDK提供了多種框架,可以讓你在應(yīng)用中使用聲音。你可以用來播放和錄制音頻文件的框架之一是AV Foundation框架。在這一章,我將帶你通過基本的框架,并向你展示如何管理音頻回放和錄音.
AV Foundation為開發(fā)者提供了必要的api來處理iOS上的音頻。在這個demo中,我們主要使用框架的這兩個類:
AVAudioPlayer - 把它想象成一個播放聲音文件的音頻播放器。通過使用播放器,您可以在iOS中播放任何持續(xù)時間和音頻格式的聲音
AVAudioRecorder - 一個用來記錄音頻的錄音機(jī)。
Demo
為了了解如何使用API,我們將構(gòu)建一個簡單的音頻應(yīng)用程序,允許用戶錄制和播放音頻。我們的主要關(guān)注點(diǎn)是演示AVFoundation框架,這樣應(yīng)用程序的用戶界面就會非常簡單。
首先,使用 Single View Application 模板創(chuàng)建一個應(yīng)用程序,并將其命名為RecordPro. 您可以自己設(shè)計一個類似于圖10.1的用戶界面。然而,要讓您免于設(shè)置用戶界面和自定義類,您可以從這里下載模板!? 界面很簡單, 只有3個按鈕: 錄音, 暫停, 和播放.?
它也有一個計時器來顯示錄制期間經(jīng)過的時間。這些按鈕已經(jīng)連接到RecordProController類中相應(yīng)的動作方法,而RecordProController類是UIViewController的一個子類.?

在我們進(jìn)入到實現(xiàn)之前,讓我給你一個更好的關(guān)于演示應(yīng)用程序如何工作的想法:
?
* 當(dāng)用戶點(diǎn)擊錄制按鈕時,應(yīng)用程序啟動計時器并開始錄制音頻。然后,記錄按鈕被暫停按鈕取代。如果用戶點(diǎn)擊暫停按鈕,應(yīng)用程序?qū)和d浿疲钡接脩粼俅吸c(diǎn)擊按鈕。在編碼方面,它調(diào)用記錄操 作方法
* 當(dāng)用戶點(diǎn)擊停止按鈕時,應(yīng)用程序會停止錄制。我已經(jīng)把按鈕和RecordProController中的stop動作方法連接起來了。
* 要播放錄音,用戶可以點(diǎn)擊播放按鈕,這是與播放方法相關(guān)聯(lián)的。
AVAudioRecorder
AVAudioRecorder類的AV Foundation框架允許您的應(yīng)用程序提供音頻錄制功能。在iOS中,錄制的音頻來自iOS設(shè)備的內(nèi)置麥克風(fēng)或耳機(jī)麥克風(fēng)。這些設(shè)備包括iPhone、iPad或iPod touch。
首先,讓我們看看如何使用AVAudioRecorder類來記錄音頻。與SDK中的大多數(shù)api一樣,AVAudioRecorder使用了delegate模式。您可以為音頻記錄器實現(xiàn)delegate對象,以響應(yīng)音頻中斷并完成錄制。AVAudioRecorder對象的委托必須采用AVAudioRecorderDelegate協(xié)議.?
可以寫個Extension:
extension RecorderProController:?AVAudioRecorderDelegate {?
}?
記得導(dǎo)入AV Foundation 框架 !? ?
聲明 AVAudioRecorder 和 AVAudioPlayer的實例變量
var audioRecorder:??AVAudioRecorder ?
var audioPlayer: AVAudioPlayer?
讓我們先關(guān)注AVAudioRecorder。稍后我們將使用audioPlayer變量。AVAudioRecorder類提供了一種在應(yīng)用程序中記錄聲音的簡單方法。
?* 指定一個聲音文件URL
?* 設(shè)置一個音頻會話
?* 配置錄音機(jī)的初始狀態(tài)
我們將創(chuàng)建一個名為configure()的私有方法來進(jìn)行設(shè)置。將代碼插入RecordProController類中
直接上代碼:?
private func configure() {
? ? // Disable Stop/Play button when application launches
? ? stopButton.isEnabled = false
? ? playButton.isEnabled = false
? ? // Get the document directory. If fails, just skip the rest of the code
? ? guard let directoryURL = FileManager.default.urls(for: FileManager.SearchPathDirectory.documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask).first else {
? ? ? ? let alertMessage = UIAlertController(title: "Error", message: "Failed to get the document directory for recording the?“audio. Please try again later.", preferredStyle: .alert)
? ? ? ? alertMessage.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
? ? ? ? present(alertMessage, animated: true, completion: nil)
? ? ? ? return
? ? }
? ? // Set the default audio file
? ? let audioFileURL = directoryURL.appendingPathComponent("MyAudioMemo.m4a")
? ? // Setup audio session
? ? let audioSession = AVAudioSession.sharedInstance()
? ? do {
? ? ? ? try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord, with: AVAudioSessionCategoryOptions.defaultToSpeaker)
? ? ? ? // Define the recorder setting
? ? ? ? let recorderSetting: [String: Any] = [
? ? ? ? ? ? AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
“ AVSampleRateKey: 44100.0,
? ? ? ? ? ? AVNumberOfChannelsKey: 2,
? ? ? ? ? ? AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
? ? ? ? ]
? ? ? ? // Initiate and prepare the recorder
? ? ? ? audioRecorder = try AVAudioRecorder(url: audioFileURL, settings: recorderSetting)
? ? ? ? audioRecorder.delegate = self
? ? ? ? audioRecorder.isMeteringEnabled = true
? ? ? ? audioRecorder.prepareToRecord()
? ? } catch {
? ? ? ? print(error)
? ? }
}
接下來看一看 錄音按鈕的 實現(xiàn)
當(dāng)用戶點(diǎn)擊錄制按鈕時,應(yīng)用程序?qū)㈤_始錄制。錄制按鈕將被更改為暫停按鈕。如果用戶點(diǎn)擊暫停按鈕,應(yīng)用程序?qū)和R纛l錄制,直到再次點(diǎn)擊按鈕。當(dāng)用戶點(diǎn)擊“停止”按鈕時,錄音就會停止
代碼如下 :
@IBAction func record(sender: UIButton) {
? ? // Stop the audio player before recording
? ? if let player = audioPlayer, player.isPlaying {
? ? ? ? player.stop()
? ? }
?if !audioRecorder.isRecording {
? ? ? ? let audioSession = AVAudioSession.sharedInstance()
? ? ? ? do {
? ? ? ? ? ? try audioSession.setActive(true)
? ? ? ? ? ? // Start recording
? ? ? ? ? ? audioRecorder.record()
? ? ? ? ? ? // Change to the Pause image
? ? ? ? ? ? recordButton.setImage(UIImage(named: "Pause"), for: UIControlState.normal)
? ? ? ? } catch {
? ? ? ? ? ? print(error)
? ? ? ? }
? ? } else {
? ? ? ? // Pause recording
? ? ? ? audioRecorder.pause()
? ? ? // Change to the Record image
? ? ? ? recordButton.setImage(UIImage(named: "Record"), for: UIControlState.normal)
? ? }
? ? stopButton.isEnabled = true
? ? playButton.isEnabled = false
}
當(dāng)然別忘 在 info.plist 里面 加入訪問權(quán)限??


暫停按鈕的實現(xiàn)??
當(dāng)用戶點(diǎn)擊停止按鈕時,會調(diào)用停止動作方法。這個方法很簡單。我們首先重置按鈕的狀態(tài),然后調(diào)用AVAudioRecorder對象的stop方法來停止錄制。最后,我們關(guān)閉音頻會話
代碼如下 :?
@IBAction func stop(sender: UIButton) {
? ? recordButton.setImage(UIImage(named: "Record"), for: UIControlState.normal)
? ? recordButton.isEnabled = true
? ? stopButton.isEnabled = false
? ? playButton.isEnabled = true
? ? // Stop the audio recorder
? ? audioRecorder?.stop()
? ? let audioSession = AVAudioSession.sharedInstance()
? ? do {
? ? ? ? try audioSession.setActive(false)
? ? } catch {
? ? ? ? print(error)
? ? }
}
實現(xiàn) AVAudioRecorderDelegate 協(xié)議
你可以使用AVAudioRecorderDelegate協(xié)議來處理音頻中斷(比如,錄音期間的電話)以及完成錄音。在本例中,RecordProController是委托。AVAudioRecorderDelegate協(xié)議中定義的方法是可選的。為了演示目的,我們將只實現(xiàn)audioRecorderDidFinishRecording(_:successful:)方法來處理記錄的完成.
代碼如下 :?
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
? ? ? ? if flag {
? ? ? ? ? ? let alertMessage = UIAlertController(title: "Finish Recording", message: "Successfully recorded the audio!", preferredStyle: .alert)
? ? ? ? ? ? alertMessage.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
? ? ? ? ? ? present(alertMessage, animated: true, completion: nil)
? ? ? ? }
? ? }
實現(xiàn) AVAudioPlayerDelegate 協(xié)議
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
? ? ? ? playButton.isSelected = false
? ? ? ? let alertMessage = UIAlertController(title: "Finish Playing", message: "Finish playing the recording!", preferredStyle: .alert)
? ? ? ? alertMessage.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
? ? ? ? present(alertMessage, animated: true, completion: nil)
? ? }
最后實現(xiàn) 定時器?
private var timer: Timer?
private var elapsedTimeInSecond: Int = 0
func startTimer() {
? ? timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { (timer) in
? ? ? ? self.elapsedTimeInSecond += 1
? ? ? ? self.updateTimeLabel()
? ? })
}
func pauseTimer() {
? ? timer?.invalidate()
}
func resetTimer() {
timer?.invalidate()
? ? elapsedTimeInSecond = 0
? ? updateTimeLabel()
}
func updateTimeLabel() {
? ? let seconds = elapsedTimeInSecond % 60
? ? let minutes = (elapsedTimeInSecond / 60) % 60
? ? timeLabel.text = String(format: "%02d:%02d", minutes, seconds)
}
