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

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

image

添加 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"。

image

要使用 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 中聲明 AVAudioRecorderAVAudioPlayer 的實(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.mviewDidLoad 方法中進(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" 按鈕,收聽播放。

image

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

本篇文章由來自 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
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容