iOS 編程:使用 AVFoundation 框架錄制和播放音頻

原文: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"死遭。這些按鈕也是用代碼鏈接起來的。

image

添加 AVFoundation 框架

默認(rèn)情況下凯旋,AVFoundation 框架沒有捆綁在任何 Xcode 項目中呀潭。所以你必須手動添加它。在項目導(dǎo)航欄中瓦阐,選擇 "AudioDemo" 項目蜗侈,接著選擇 TARGETS 下的 "AudioDemo"篷牌,然后點擊 "Build Phases"睡蟋。展開 "Link Binary with Libraries",點擊 "+"按鈕枷颊,添加 "AVFoundation.framework"戳杀。

image

要使用 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 中聲明 AVAudioRecorderAVAudioPlayer 的實例變量猾瘸。

@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.mviewDidLoad 方法中進行設(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" 按鈕钳枕,收聽播放。

image

大家可以從這里下載完整的源碼赏壹,供大家參考鱼炒。如果你有什么問題,歡迎給我留言蝌借。

本篇文章由來自 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
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市酬荞,隨后出現(xiàn)的幾起案子搓劫,更是在濱河造成了極大的恐慌,老刑警劉巖混巧,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件枪向,死亡現(xiàn)場離奇詭異,居然都是意外死亡咧党,警方通過查閱死者的電腦和手機秘蛔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來傍衡,“玉大人深员,你說我怎么就攤上這事〈鲜妫” “怎么了辨液?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長箱残。 經(jīng)常有香客問我滔迈,道長,這世上最難降的妖魔是什么被辑? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任燎悍,我火速辦了婚禮,結(jié)果婚禮上盼理,老公的妹妹穿的比我還像新娘谈山。我一直安慰自己,他們只是感情好宏怔,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布奏路。 她就那樣靜靜地躺著,像睡著了一般臊诊。 火紅的嫁衣襯著肌膚如雪鸽粉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天抓艳,我揣著相機與錄音触机,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛儡首,可吹牛的內(nèi)容都是我干的片任。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼蔬胯,長吁一口氣:“原來是場噩夢啊……” “哼对供!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起笔宿,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤犁钟,失蹤者是張志新(化名)和其女友劉穎棱诱,沒想到半個月后泼橘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡迈勋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年炬灭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片靡菇。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡重归,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出厦凤,到底是詐尸還是另有隱情鼻吮,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布较鼓,位于F島的核電站椎木,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏博烂。R本人自食惡果不足惜香椎,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望禽篱。 院中可真熱鬧畜伐,春花似錦、人聲如沸躺率。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悼吱。三九已至慎框,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間舆绎,已是汗流浹背鲤脏。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人猎醇。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓窥突,卻偏偏與公主長得像,于是被迫代替她去往敵國和親硫嘶。 傳聞我的和親對象是個殘疾皇子阻问,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容