原文:iOS Programming 101: Record and Play Audio using AVFoundation Framework
編者按:有網(wǎng)友要求我們寫一篇關(guān)于錄音的教程。本周,我們與 Purple Development 的 Shi Yiqi 和 Raymond 一起給大家介紹 AVFoundation 框架。Yiqi 和 Raymond 是獨(dú)立的 iOS 開發(fā)者,最近他們發(fā)布了 Voice Memo Wifi,可以讓用戶錄制語音備忘錄并通過 WiFi 分享。
iOS 提供了各種框架讓你在應(yīng)用程序中使用聲音。其中有一個(gè)可以讓你播放和錄制音頻文件的框架叫做 AVFoundation。在本教程中,我將帶領(lǐng)你了解該框架的基礎(chǔ)知識(shí),并向你展示如何管理音頻播放,以及錄音。
為了給大家提供一個(gè)實(shí)例,我將構(gòu)建一個(gè)簡(jiǎn)單的音頻應(yīng)用,讓用戶可以錄制和播放音頻。我們的主要目的是演示 AVFoundation 框架,所以應(yīng)用的用戶界面非常簡(jiǎn)單。
AVFoundation 提供了處理音頻的簡(jiǎn)單方法。在本教程中,我們主要處理這兩個(gè)類。
- AVAudioPlayer —— 把它看作是一個(gè)可以播放聲音文件的音頻播放器。通過使用該播放器,你可以播放任何時(shí)間長(zhǎng)度和(在 iOS 中可用的)任何音頻格式的聲音。
- AVAudioRecorder —— 一個(gè)用于錄制音頻的音頻記錄器。
從示例項(xiàng)目開始
首先,創(chuàng)建一個(gè) "Single View Application" 模版的項(xiàng)目,并命名為 "AudioDemo"。為了讓你免于設(shè)置用戶界面和代碼框架,你可以從這里下載項(xiàng)目模板。
我為你創(chuàng)建了一個(gè)簡(jiǎn)單的用戶界面,它只包含三個(gè)按鈕,包括 "Record"、"Stop"和 "Play"。這些按鈕也是用代碼鏈接起來的。

添加 AVFoundation 框架
默認(rèn)情況下,AVFoundation 框架沒有捆綁在任何 Xcode 項(xiàng)目中。所以你必須手動(dòng)添加它。在項(xiàng)目導(dǎo)航欄中,選擇 "AudioDemo" 項(xiàng)目,接著選擇 TARGETS 下的 "AudioDemo",然后點(diǎn)擊 "Build Phases"。展開 "Link Binary with Libraries",點(diǎn)擊 "+"按鈕,添加 "AVFoundation.framework"。

要使用 AVAudioPlayer 和 AVAudioRecorder 這兩個(gè)類,我們需要在 ViewController.h 中導(dǎo)入:
#import <AVFoundation/AVFoundation.h>
使用 AVAudioRecorder 錄制音頻
首先,我們來看看如何使用 AVAudioRecorder 來錄制音頻。在 ViewController.h 中添加 AVAudioRecorderDelegate 協(xié)議和 AVAudioPlayerDelegate 協(xié)議。我們將在講解代碼的時(shí)候?qū)@兩個(gè)委托進(jìn)行解釋。
@interface ViewController () <AVAudioRecorderDelegate, AVAudioPlayerDelegate>
接下來,在 ViewController.m 中聲明 AVAudioRecorder 和 AVAudioPlayer 的實(shí)例變量。
@interface ViewController () <AVAudioRecorderDelegate, AVAudioPlayerDelegate>
@property (weak, nonatomic) IBOutlet UIButton *recordButton;
@property (weak, nonatomic) IBOutlet UIButton *stopButton;
@property (weak, nonatomic) IBOutlet UIButton *playButton;
@property (nonatomic, strong) AVAudioRecorder *recorder;
@property (nonatomic, strong) AVAudioPlayer *player;
@end
AVAudioRecorder 類提供了一種在 iOS 中錄制聲音的簡(jiǎn)單方法。要使用錄音機(jī),你必須準(zhǔn)備一些東西:
- 指定存放聲音文件的 URL 路徑。
- 設(shè)置音頻會(huì)話(
AVAudioSession)。 - 配置 audio recorder 的初始狀態(tài)。
我們將在 ViewController.m 的 viewDidLoad 方法中進(jìn)行設(shè)置,只需要在該方法中編輯以下代碼即可:
- (void)viewDidLoad {
[super viewDidLoad];
// 當(dāng)應(yīng)用啟動(dòng)時(shí),禁用 Stop/Play 按鈕
[self.stopButton setEnabled:NO];
[self.playButton setEnabled:NO];
// 設(shè)置音頻文件
NSArray *pathComponents = [NSArray arrayWithObjects:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject], @"MyAudioMemo.m4a",nil];
NSURL *outputFileURL = [NSURL fileURLWithPathComponents:pathComponents];
// 設(shè)置音頻會(huì)話
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
// 定義錄音設(shè)置項(xiàng)
NSMutableDictionary *recordSetting = [[NSMutableDictionary alloc] init];
[recordSetting setValue:[NSNumber numberWithInt:kAudioFormatMPEG4AAC] forKey:AVFormatIDKey];
[recordSetting setValue:[NSNumber numberWithFloat:44100.0] forKey:AVSampleRateKey];
[recordSetting setValue:[NSNumber numberWithInt:2] forKey:AVNumberOfChannelsKey];
// 初始化錄音器并設(shè)置為準(zhǔn)備狀態(tài)
self.recorder = [[AVAudioRecorder alloc] initWithURL:outputFileURL settings:recordSetting error:nil];
self.recorder.delegate = self;
self.recorder.meteringEnabled = YES;
[self.recorder prepareToRecord];
}
注:為了演示的目的,我們忽略了錯(cuò)誤處理。在實(shí)際應(yīng)用中,不要忘記包含適當(dāng)?shù)腻e(cuò)誤處理。
在上面的代碼中,我們首先定義了用于保存錄音的聲音文件URL。然后配置音頻會(huì)話(audio session)。iOS 通過使用音頻會(huì)話來處理應(yīng)用程序的音頻行為。在啟動(dòng)時(shí),你的應(yīng)用會(huì)自動(dòng)獲得一個(gè)音頻會(huì)話。你可以通過調(diào)用 [AVAudioSession sharedInstance] 來獲得會(huì)話單例,并對(duì)其進(jìn)行配置。在這里,我們告訴 iOS,應(yīng)用程序使用 AVAudioSessionCategoryPlayAndRecord 類別,可以實(shí)現(xiàn)音頻輸入和輸出。關(guān)于音頻會(huì)話的細(xì)節(jié)我們就不多說了,大家可以查看官方文檔了解更多細(xì)節(jié)。
AVAudioRecorder 使用基于字典的設(shè)置進(jìn)行配置。在第21-25行,我們使用可選的鍵來配置音頻數(shù)據(jù)格式、采樣率和通道數(shù)。最后,我們通過調(diào)用 prepareToRecord: 方法來啟動(dòng)音頻記錄器。
注:關(guān)于其他設(shè)置鍵,可以參考 AVFoundation 音頻設(shè)置常量。
實(shí)現(xiàn)錄音按鈕
我們已經(jīng)完成了音頻的準(zhǔn)備工作。讓我們繼續(xù)實(shí)現(xiàn) "Record" 按鈕的動(dòng)作方法。在進(jìn)入代碼之前,我先解釋一下 "Record" 按鈕的工作原理。當(dāng)用戶點(diǎn)擊 "Record" 按鈕時(shí),應(yīng)用程序?qū)㈤_始錄制,按鈕文字將改為 "Pause"。如果用戶點(diǎn)擊暫停按鈕,應(yīng)用程序?qū)和d浺?,直到再次點(diǎn)擊 "Record" 按鈕。只有當(dāng)用戶點(diǎn)擊 "Stop" 按鈕時(shí),錄音才會(huì)停止。
在 recordButtonTapped: 方法中編輯以下代碼:
// 錄制/暫停按鈕
- (IBAction)recordButtonTapped:(id)sender {
// 在錄制前停止音頻播放
if (self.player.isPlaying) {
[self.player stop];
}
if (!self.recorder.isRecording) {
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setActive:YES error:nil];
// 開始錄音
[self.recorder record];
[self.recordButton setTitle:@"Pause" forState:UIControlStateNormal];
} else {
// 停止錄音
[self.recorder pause];
[self.recordButton setTitle:@"Record" forState:UIControlStateNormal];
}
[self.stopButton setEnabled:YES];
[self.playButton setEnabled:NO];
}
在上面的代碼中,我們首先檢查音頻播放器是否正在播放中。如果音頻播放器正在播放,我們只需使用 stop: 方法停止它。上述代碼的第 7 行確定應(yīng)用程序是否處于錄音模式。如果不在錄音模式下,應(yīng)用程序就會(huì)激活音頻會(huì)話并開始錄音。為了讓錄音工作(或聲音播放),你的音頻會(huì)話必須處于激活(active)狀態(tài)。
通常來說,你可以使用 AVAudioRecorder 類的以下方法來控制錄音行為:
-
record- 開始/恢復(fù)錄音 -
pause- 暫停錄音 -
stop- 停止錄音
實(shí)現(xiàn)停止按鈕
對(duì)于 "Stop" 按鈕,我們只需調(diào)用錄音器的 stop: 方法,然后停用音頻會(huì)話。在 stopButtonTapped: 方法中編輯添加以下代碼。
// 停止按鈕
- (IBAction)stopButtonTapped:(id)sender {
[self.recorder stop];
[self.recordButton setTitle:@"Record" forState:UIControlStateNormal];
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setActive:NO error:nil];
}
實(shí)現(xiàn) AVAudioRecorderDelegate 協(xié)議
你可以利用 AVAudioRecorderDelegate 協(xié)議來處理音頻中斷(比如說,音頻錄制過程中有一個(gè)來電電話)和錄制的完成。在本例中,ViewController 遵守此協(xié)議。AVAudioRecorderDelegate 協(xié)議中定義的方法是可選的。這里,我們只實(shí)現(xiàn) audioRecorderDidFinishRecording: 方法來處理錄音的完成。在 ViewController.m 中添加以下代碼。
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag {
[self.recordButton setTitle:@"Record" forState:UIControlStateNormal];
[self.stopButton setEnabled:NO];
[self.playButton setEnabled:YES];
}
完成錄制后,我們只需將 "Pause" 按鈕改回 "Record" 按鈕即可。
使用 AVAudioPlayer 播放聲音
最后,就到了使用 AVAudioPlayer 實(shí)現(xiàn)音頻播放的 "Play" 按鈕了。在 ViewController.m中,在 playButtonTapped: 方法中編輯添加以下代碼:
// 播放按鈕
- (IBAction)playButtonTapped:(id)sender {
if (!self.recorder.isRecording) {
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:self.recorder.url error:nil];
self.player.delegate = self;
[self.player play];
}
}
上面的代碼非常簡(jiǎn)單。通常情況下,配置一個(gè)音頻播放器要做這幾件事:
- 初始化音頻播放并指定一個(gè)聲音文件給它。在本例中,是錄音器的音頻文件(即
recorder.url)。 - 指定音頻播放器的委托對(duì)象,它處理中斷以及播放完成事件。
- 調(diào)用
play:方法來播放聲音文件。
實(shí)現(xiàn) AVAudioPlayerDelegate 協(xié)議
AVAudioPlayer 對(duì)象的委托必須遵守 AVAudioPlayerDelegate 協(xié)議。在本例中,它是 ViewController。委托者允許你處理中斷、音頻解碼錯(cuò)誤,并在音頻播放完畢后更新用戶界面。然而,AVAudioPlayerDelegate 協(xié)議中的所有方法都是可選的。為了演示它是如何工作的,我們將實(shí)現(xiàn) audioPlayerDidFinishPlaying: 方法來在音頻播放完成后顯示一個(gè)警報(bào)提示。其他方法的用法,可以參考AUAudioPlayerDelegate 協(xié)議的官方文檔。
在 ViewController.m 中添加以下代碼:
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
// 1.實(shí)例化UIAlertController對(duì)象
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Done" message:@"Finish playing the recording!" preferredStyle:UIAlertControllerStyleAlert];
// 2.實(shí)例化UIAlertAction按鈕:確定按鈕
UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:nil];
[alert addAction:defaultAction];
// 3.顯示alertController
[self presentViewController:alert animated:YES completion:nil];
}
編譯并運(yùn)行應(yīng)用
你可以使用實(shí)際設(shè)備或軟件模擬器測(cè)試音頻錄制和播放。如果你使用實(shí)際設(shè)備(如iPhone)測(cè)試應(yīng)用程序,則錄制的音頻來自于通過內(nèi)置麥克風(fēng)或耳機(jī)麥克風(fēng)連接的設(shè)備。另外,如果你使用模擬器測(cè)試應(yīng)用程序,音頻來自系統(tǒng)偏好設(shè)置中的默認(rèn)音頻輸入設(shè)備。
注:訪問麥克風(fēng)需要訪問并獲取隱私權(quán)限,因此,請(qǐng)?jiān)陧?xiàng)目的 Info.plist 文件中設(shè)置 Privacy - Microphone Usage Description 項(xiàng)。
所以請(qǐng)繼續(xù)編譯并運(yùn)行該應(yīng)用吧! 點(diǎn) "Record" 按鈕,開始錄制。說點(diǎn)什么,點(diǎn) "Stop" 按鈕,然后選擇 "Play" 按鈕,收聽播放。

大家可以從這里下載完整的源碼,供大家參考。如果你有什么問題,歡迎給我留言。
本篇文章由來自 Purple Development 的 Yiqi Shi 和 Raymond 貢獻(xiàn)。Yiqi 和 Raymond 是獨(dú)立的 iOS 開發(fā)者,最近他們發(fā)布了 Voice Memo Wifi,可以讓用戶錄制語音備忘錄并通過 WiFi 分享。
附:完整源碼
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
@interface ViewController () <AVAudioRecorderDelegate, AVAudioPlayerDelegate>
@property (weak, nonatomic) IBOutlet UIButton *recordButton;
@property (weak, nonatomic) IBOutlet UIButton *stopButton;
@property (weak, nonatomic) IBOutlet UIButton *playButton;
@property (nonatomic, strong) AVAudioRecorder *recorder;
@property (nonatomic, strong) AVAudioPlayer *player;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 當(dāng)應(yīng)用啟動(dòng)時(shí),禁用 Stop/Play 按鈕
[self.stopButton setEnabled:NO];
[self.playButton setEnabled:NO];
// 設(shè)置音頻文件
NSArray *pathComponents = [NSArray arrayWithObjects:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject], @"MyAudioMemo.m4a",nil];
NSURL *outputFileURL = [NSURL fileURLWithPathComponents:pathComponents];
// 設(shè)置音頻會(huì)話
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
// 定義錄音設(shè)置項(xiàng)
NSMutableDictionary *recordSetting = [[NSMutableDictionary alloc] init];
[recordSetting setValue:[NSNumber numberWithInt:kAudioFormatMPEG4AAC] forKey:AVFormatIDKey];
[recordSetting setValue:[NSNumber numberWithFloat:44100.0] forKey:AVSampleRateKey];
[recordSetting setValue:[NSNumber numberWithInt:2] forKey:AVNumberOfChannelsKey];
// 初始化錄音器并設(shè)置為準(zhǔn)備狀態(tài)
self.recorder = [[AVAudioRecorder alloc] initWithURL:outputFileURL settings:recordSetting error:nil];
self.recorder.delegate = self;
self.recorder.meteringEnabled = YES;
[self.recorder prepareToRecord];
}
#pragma mark - Actions
// 錄制/暫停按鈕
- (IBAction)recordButtonTapped:(id)sender {
// 在錄制前停止音頻播放
if (self.player.isPlaying) {
[self.player stop];
}
if (!self.recorder.isRecording) {
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setActive:YES error:nil];
// 開始錄音
[self.recorder record];
[self.recordButton setTitle:@"Pause" forState:UIControlStateNormal];
} else {
// 停止錄音
[self.recorder pause];
[self.recordButton setTitle:@"Record" forState:UIControlStateNormal];
}
[self.stopButton setEnabled:YES];
[self.playButton setEnabled:NO];
}
// 停止按鈕
- (IBAction)stopButtonTapped:(id)sender {
[self.recorder stop];
[self.recordButton setTitle:@"Record" forState:UIControlStateNormal];
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setActive:NO error:nil];
}
// 播放按鈕
- (IBAction)playButtonTapped:(id)sender {
if (!self.recorder.isRecording) {
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:self.recorder.url error:nil];
self.player.delegate = self;
[self.player play];
}
}
#pragma mark - AVAudioRecorderDelegate
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag {
[self.recordButton setTitle:@"Record" forState:UIControlStateNormal];
[self.stopButton setEnabled:NO];
[self.playButton setEnabled:YES];
}
#pragma mark - AVAudioPlayerDelegate
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
// 1.實(shí)例化UIAlertController對(duì)象
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Done" message:@"Finish playing the recording!" preferredStyle:UIAlertControllerStyleAlert];
// 2.實(shí)例化UIAlertAction按鈕:確定按鈕
UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:nil];
[alert addAction:defaultAction];
// 3.顯示alertController
[self presentViewController:alert animated:YES completion:nil];
}
@end