原文:iOS Programming 101: Record and Play Audio using AVFoundation Framework
編者按:有網(wǎng)友要求我們寫一篇關(guān)于錄音的教程柔袁。本周,我們與 Purple Development 的 Shi Yiqi 和 Raymond 一起給大家介紹 AVFoundation 框架蓖康。Yiqi 和 Raymond 是獨立的 iOS 開發(fā)者钥庇,最近他們發(fā)布了 Voice Memo Wifi届慈,可以讓用戶錄制語音備忘錄并通過 WiFi 分享菩咨。
iOS 提供了各種框架讓你在應(yīng)用程序中使用聲音。其中有一個可以讓你播放和錄制音頻文件的框架叫做 AVFoundation。在本教程中哗伯,我將帶領(lǐng)你了解該框架的基礎(chǔ)知識,并向你展示如何管理音頻播放篷角,以及錄音焊刹。
為了給大家提供一個實例,我將構(gòu)建一個簡單的音頻應(yīng)用恳蹲,讓用戶可以錄制和播放音頻虐块。我們的主要目的是演示 AVFoundation 框架,所以應(yīng)用的用戶界面非常簡單嘉蕾。
AVFoundation 提供了處理音頻的簡單方法贺奠。在本教程中,我們主要處理這兩個類错忱。
- AVAudioPlayer —— 把它看作是一個可以播放聲音文件的音頻播放器儡率。通過使用該播放器,你可以播放任何時間長度和(在 iOS 中可用的)任何音頻格式的聲音以清。
- AVAudioRecorder —— 一個用于錄制音頻的音頻記錄器儿普。
從示例項目開始
首先,創(chuàng)建一個 "Single View Application" 模版的項目掷倔,并命名為 "AudioDemo"眉孩。為了讓你免于設(shè)置用戶界面和代碼框架,你可以從這里下載項目模板勒葱。
我為你創(chuàng)建了一個簡單的用戶界面浪汪,它只包含三個按鈕,包括 "Record"凛虽、"Stop"和 "Play"死遭。這些按鈕也是用代碼鏈接起來的。
添加 AVFoundation 框架
默認(rèn)情況下凯旋,AVFoundation 框架沒有捆綁在任何 Xcode 項目中呀潭。所以你必須手動添加它。在項目導(dǎo)航欄中瓦阐,選擇 "AudioDemo" 項目蜗侈,接著選擇 TARGETS 下的 "AudioDemo"篷牌,然后點擊 "Build Phases"睡蟋。展開 "Link Binary with Libraries",點擊 "+"按鈕枷颊,添加 "AVFoundation.framework"戳杀。
要使用 AVAudioPlayer
和 AVAudioRecorder 這兩個類该面,我們需要在 ViewController.h
中導(dǎo)入:
#import <AVFoundation/AVFoundation.h>
使用 AVAudioRecorder 錄制音頻
首先,我們來看看如何使用 AVAudioRecorder
來錄制音頻信卡。在 ViewController.h
中添加 AVAudioRecorderDelegate
協(xié)議和 AVAudioPlayerDelegate
協(xié)議隔缀。我們將在講解代碼的時候?qū)@兩個委托進行解釋。
@interface ViewController () <AVAudioRecorderDelegate, AVAudioPlayerDelegate>
接下來傍菇,在 ViewController.m
中聲明 AVAudioRecorder
和 AVAudioPlayer
的實例變量猾瘸。
@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 中錄制聲音的簡單方法。要使用錄音機丢习,你必須準(zhǔn)備一些東西:
- 指定存放聲音文件的 URL 路徑牵触。
- 設(shè)置音頻會話(
AVAudioSession
)。 - 配置 audio recorder 的初始狀態(tài)咐低。
我們將在 ViewController.m
的 viewDidLoad
方法中進行設(shè)置揽思,只需要在該方法中編輯以下代碼即可:
- (void)viewDidLoad {
[super viewDidLoad];
// 當(dāng)應(yīng)用啟動時,禁用 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è)置音頻會話
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
// 定義錄音設(shè)置項
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];
}
注:為了演示的目的见擦,我們忽略了錯誤處理钉汗。在實際應(yīng)用中,不要忘記包含適當(dāng)?shù)腻e誤處理鲤屡。
在上面的代碼中损痰,我們首先定義了用于保存錄音的聲音文件URL。然后配置音頻會話(audio session)酒来。iOS 通過使用音頻會話來處理應(yīng)用程序的音頻行為徐钠。在啟動時,你的應(yīng)用會自動獲得一個音頻會話役首。你可以通過調(diào)用 [AVAudioSession sharedInstance]
來獲得會話單例尝丐,并對其進行配置。在這里衡奥,我們告訴 iOS爹袁,應(yīng)用程序使用 AVAudioSessionCategoryPlayAndRecord
類別,可以實現(xiàn)音頻輸入和輸出矮固。關(guān)于音頻會話的細節(jié)我們就不多說了失息,大家可以查看官方文檔了解更多細節(jié)。
AVAudioRecorder
使用基于字典的設(shè)置進行配置档址。在第21-25行盹兢,我們使用可選的鍵來配置音頻數(shù)據(jù)格式、采樣率和通道數(shù)守伸。最后绎秒,我們通過調(diào)用 prepareToRecord:
方法來啟動音頻記錄器。
注:關(guān)于其他設(shè)置鍵尼摹,可以參考 AVFoundation 音頻設(shè)置常量见芹。
實現(xiàn)錄音按鈕
我們已經(jīng)完成了音頻的準(zhǔn)備工作剂娄。讓我們繼續(xù)實現(xiàn) "Record" 按鈕的動作方法。在進入代碼之前玄呛,我先解釋一下 "Record" 按鈕的工作原理阅懦。當(dāng)用戶點擊 "Record" 按鈕時,應(yīng)用程序?qū)㈤_始錄制徘铝,按鈕文字將改為 "Pause"耳胎。如果用戶點擊暫停按鈕,應(yīng)用程序?qū)和d浺籼杷钡皆俅吸c擊 "Record" 按鈕场晶。只有當(dāng)用戶點擊 "Stop" 按鈕時,錄音才會停止怠缸。
在 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)用程序就會激活音頻會話并開始錄音恨樟。為了讓錄音工作(或聲音播放),你的音頻會話必須處于激活(active)狀態(tài)疚俱。
通常來說劝术,你可以使用 AVAudioRecorder
類的以下方法來控制錄音行為:
-
record
- 開始/恢復(fù)錄音 -
pause
- 暫停錄音 -
stop
- 停止錄音
實現(xiàn)停止按鈕
對于 "Stop" 按鈕,我們只需調(diào)用錄音器的 stop:
方法呆奕,然后停用音頻會話养晋。在 stopButtonTapped:
方法中編輯添加以下代碼。
// 停止按鈕
- (IBAction)stopButtonTapped:(id)sender {
[self.recorder stop];
[self.recordButton setTitle:@"Record" forState:UIControlStateNormal];
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setActive:NO error:nil];
}
實現(xiàn) AVAudioRecorderDelegate 協(xié)議
你可以利用 AVAudioRecorderDelegate
協(xié)議來處理音頻中斷(比如說梁钾,音頻錄制過程中有一個來電電話)和錄制的完成绳泉。在本例中,ViewController 遵守此協(xié)議姆泻。AVAudioRecorderDelegate
協(xié)議中定義的方法是可選的零酪。這里,我們只實現(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
實現(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];
}
}
上面的代碼非常簡單罗售。通常情況下辜窑,配置一個音頻播放器要做這幾件事:
- 初始化音頻播放并指定一個聲音文件給它钩述。在本例中寨躁,是錄音器的音頻文件(即
recorder.url
)。 - 指定音頻播放器的委托對象牙勘,它處理中斷以及播放完成事件职恳。
- 調(diào)用
play:
方法來播放聲音文件。
實現(xiàn) AVAudioPlayerDelegate 協(xié)議
AVAudioPlayer
對象的委托必須遵守 AVAudioPlayerDelegate
協(xié)議方面。在本例中放钦,它是 ViewController。委托者允許你處理中斷恭金、音頻解碼錯誤操禀,并在音頻播放完畢后更新用戶界面。然而横腿,AVAudioPlayerDelegate
協(xié)議中的所有方法都是可選的颓屑。為了演示它是如何工作的,我們將實現(xiàn) audioPlayerDidFinishPlaying:
方法來在音頻播放完成后顯示一個警報提示耿焊。其他方法的用法揪惦,可以參考AUAudioPlayerDelegate
協(xié)議的官方文檔。
在 ViewController.m
中添加以下代碼:
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
// 1.實例化UIAlertController對象
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Done" message:@"Finish playing the recording!" preferredStyle:UIAlertControllerStyleAlert];
// 2.實例化UIAlertAction按鈕:確定按鈕
UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:nil];
[alert addAction:defaultAction];
// 3.顯示alertController
[self presentViewController:alert animated:YES completion:nil];
}
編譯并運行應(yīng)用
你可以使用實際設(shè)備或軟件模擬器測試音頻錄制和播放罗侯。如果你使用實際設(shè)備(如iPhone)測試應(yīng)用程序器腋,則錄制的音頻來自于通過內(nèi)置麥克風(fēng)或耳機麥克風(fēng)連接的設(shè)備。另外钩杰,如果你使用模擬器測試應(yīng)用程序纫塌,音頻來自系統(tǒng)偏好設(shè)置中的默認(rèn)音頻輸入設(shè)備。
注:訪問麥克風(fēng)需要訪問并獲取隱私權(quán)限讲弄,因此护戳,請在項目的 Info.plist
文件中設(shè)置 Privacy - Microphone Usage Description
項。
所以請繼續(xù)編譯并運行該應(yīng)用吧! 點 "Record" 按鈕垂睬,開始錄制媳荒。說點什么,點 "Stop" 按鈕驹饺,然后選擇 "Play" 按鈕钳枕,收聽播放。
大家可以從這里下載完整的源碼赏壹,供大家參考鱼炒。如果你有什么問題,歡迎給我留言蝌借。
本篇文章由來自 Purple Development 的 Yiqi Shi 和 Raymond 貢獻昔瞧。Yiqi 和 Raymond 是獨立的 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)用啟動時凝化,禁用 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è)置音頻會話
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
// 定義錄音設(shè)置項
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.實例化UIAlertController對象
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Done" message:@"Finish playing the recording!" preferredStyle:UIAlertControllerStyleAlert];
// 2.實例化UIAlertAction按鈕:確定按鈕
UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:nil];
[alert addAction:defaultAction];
// 3.顯示alertController
[self presentViewController:alert animated:YES completion:nil];
}
@end