《AV Foundation 開發秘籍》讀書筆記(二)

第二章 音頻播放和錄制

1. 音頻會話

音頻會話分類

Category 播放類型 后臺播放 靜音或屏幕關閉 音頻輸入 音頻輸出 作用
AVAudioSessionCategoryAmbient 混合播放 有影響 支持 游戲背景音樂
AVAudioSessionCategorySoloAmbient(默認) 獨占播放 有影響 支持 微信中播放語音
AVAudioSessionCategoryPlayback 可選 支持 支持 音頻播放器
AVAudioSessionCategoryRecord 獨占錄音 支持 支持 微信中錄制語音
AVAudioSessionCategoryPlayAndRecord 可選 支持 支持 支持 微信語音聊天
AVAudioSessionCategoryAudioProcessing —— —— —— 硬件解碼音頻
AVAudioSessionCategoryMultiRoute 支持 支持 多設備輸入輸出

上述分類所提供的幾種常見行為可以滿足大部分應用程序的需要,如果需要更復雜的功能,上述其中一種分類可以通過使用 options 和 modes 方法進一步自定義開發。

激活音頻會話

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    AVAudioSession *session = [AVAudioSession sharedInstance];
    NSError *error;
    if (![session setCategory:AVAudioSessionCategoryPlayback error:&error]) {
        NSLog(@"Category Error : %@", error.localizedDescription);
    }
    if (![session setActive:YES error:&error]) {
        NSLog(@"Activation Error : %@", error.localizedDescription);
    }
    
    return YES;
}

如果分類允許后臺播放,則應該打開 Capabilities 中的 Background Modes 繼而勾選后臺播放音頻選項

2. 音頻播放

除非需要從網絡流中播放音頻、需要訪問原始音頻樣本,或者需要非常低的時延,否則 AVAudioPlayer 都能勝任

- (void)viewDidLoad {
    [super viewDidLoad];

    NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"白潔01" withExtension:@"mp3"];
    
    // Must Maintain a strong reference to player
    self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
    
    if (self.player) {
        self.player.numberOfLoops = -1;  // 循環播放
        [self.player prepareToPlay];
    }
}

- (IBAction)play {
    
    [self.player play];
}

prepareToPlay 方法是可選的,在調用 play 方法前也會自動調用,作用是取得需要的音頻硬件并預加載 Audio Queue 的緩沖區,降低調用 play 方法后的延時。

- (IBAction)pause {
    
    [self.player pause];
}

- (IBAction)stop {
    
    [self.player stop];
    self.player.currentTime = 0.0f;
}

通過 pause 和 stop 方法停止的音頻都會繼續播放。最主要的區別在底層處理上,調用 stop 方法會撤銷調用 prepareToPlay 時所做的設置,而調用 pause 方法則不會。

// 音量 0 ~ 1
- (IBAction)voice:(UISlider *)sender {
    
    self.player.volume = sender.value;
}

// 聲道 -1 ~ 1
- (IBAction)pan:(UISlider *)sender {
    
    self.player.pan = sender.value;
}

// 速率 0.5 ~ 2
- (IBAction)speed:(UISlider *)sender {
    
    self.player.rate = sender.value;
}

如果要改變速率,在初始化 AVAudioPlayer 時應做出如下設置

self.player.enableRate = YES;

3. 處理中斷事件

當有電話呼入、鬧鐘響起的時候,播放中的音頻會慢慢消失和暫停,但是終止通話后,播放、停止按鈕的控件和音頻的播放沒有恢復。為了優化用戶體驗,需要監聽這些事件,并作出處理:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
- (void)handleInterruption:(NSNotification *)notification
{
    NSDictionary *info = notification.userInfo;
    
    AVAudioSessionInterruptionType type = [info[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
    if (type == AVAudioSessionInterruptionTypeBegan) {
        
        // 中斷開始,設置停止音樂
        [self.player pause];
        
    } else {
        
        // 中斷結束,判斷是否允許繼續播放
        AVAudioSessionInterruptionOptions options = [info[AVAudioSessionInterruptionOptionKey] unsignedIntegerValue];
        if (options == AVAudioSessionInterruptionOptionShouldResume) {
            
            // 允許繼續播放,則繼續播放
            [self.player play];
        }
    }
}

4. 對線路改變的響應

播放音頻期間插入耳機,音頻輸出線路變成耳機插孔并繼續播放。斷開耳機連接,音頻線路再次回到設備的內置揚聲器播放。雖然線路變化和預期一樣,不過按照蘋果官方文檔,認為該音頻應該處于靜音狀態。

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleRouteChange:) name:AVAudioSessionRouteChangeNotification object:[AVAudioSession sharedInstance]];
- (void)handleRouteChange:(NSNotification *)notification
{
    NSDictionary *info = notification.userInfo;
    
    AVAudioSessionRouteChangeReason reason = [info[AVAudioSessionRouteChangeReasonKey] unsignedIntegerValue];
    if (reason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
        
        AVAudioSessionRouteDescription *previousRoute  = info[AVAudioSessionRouteChangePreviousRouteKey];
        AVAudioSessionPortDescription  *previousOutput = previousRoute.outputs[0];
        if ([previousOutput.portType isEqualToString:AVAudioSessionPortHeadphones]) {
            
            // 停止播放音樂
            [self.player stop];
        }
    }
}

5. 音頻錄制

一般情況存在錄音功能,必然會有播放功能,所以不能使用默認的錄制音頻會話,應該使用既可以錄制又能播放的 AVAudioSessionCategoryPlayAndRecord

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSURL *url = [NSURL fileURLWithPath:[@"Users/mayan/Desktop" stringByAppendingPathComponent:@"voice.caf"]];
    NSDictionary *settings = @{
                               AVFormatIDKey            : @(kAudioFormatAppleIMA4),
                               AVSampleRateKey          : @22050.0f,
                               AVNumberOfChannelsKey    : @1,
                               };
    self.recorder = [[AVAudioRecorder alloc] initWithURL:url settings:settings error:nil];
    
    if (self.recorder) {
        self.recorder.delegate = self;
        [self.recorder prepareToRecord];
    }
}

- (IBAction)record {

    [self.recorder record];
}

- (IBAction)recordFinish {

    [self.recorder stop];
}

// 音頻錄制完成調用
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag
{
    if (flag) {
        
        // 一般把錄制好的音頻復制或者剪切到目的文件夾下
        NSURL *srcURL = self.recorder.url;
        NSURL *destURL = [NSURL URLWithString:@"目的文件路徑/音頻文件名稱.caf"];
        
        [[NSFileManager defaultManager] copyItemAtURL:srcURL toURL:destURL error:nil];
    }
}

在錄制音頻過程中,Core Audio Format(CAF)通常是最好的容器格式,因為它和內容無關可以保存 Core Audio 支持的任何音頻格式。在設置字典中指定的鍵值信息也值得討論一番:

音頻格式

AVFormatIDKey 定義了寫入內容的音頻格式,下面是常用格式:

  • kAudioFormatLinearPCM:將未壓縮的音頻流寫入到文件中。保真度最高,文件也最大;
  • kAudioFormatMPEG4AAC(AAC) 或 kAudioFormat-AppleIMA4(Apple IMA4):文件顯著縮小,還能保證高質量音頻

采樣率

AVSampleRateKey 定義了錄音器的采樣率,采樣率定義了對輸入的模擬音頻信號每一秒的采樣數。采樣率越高,越能得到高質量的內容,不過文件相對越大。標準的采樣率:8000、16000、22050、44100

通道數

AVNumberOfChannelsKey 定義記錄音頻內容的通道數。默認值 1 是單聲道錄制,2 是立體聲錄制。除非使用外部硬件錄制,否則應該創建單聲道錄音。

6. 音頻測量

AVAudioPlayer 和 AVAudioRecorder 中最實用的功能就是對音頻進行測量。Audio Metering 可以讀取音頻的平均分貝和峰值分貝數據,并使用這些數據以可視化方式將聲音大小呈現給用戶。

首先在初始化 AVAudioPlayer 或 AVAudioRecorder 時應做出如下設置

self.player.meteringEnabled = YES;
self.recorder.meteringEnabled = YES;

點擊音頻播放或者音頻錄制,開始測量

- (void)startMeterTimer
{
    [self.link invalidate];
    self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateMeter)];
    self.link.frameInterval = 4;  // 時間間隔為刷新率的 1/4
    [self.link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}

- (void)stopMeterTimer
{
    [self.link invalidate];
    self.link = nil;
}

下面分別是音頻播放情況下測量、音頻錄制情況下測量

- (void)updateMeter
{
    [self.player updateMeters];  // 刷新
    
    CGFloat num1 = [self.player averagePowerForChannel:0];
    CGFloat num2 = [self.player peakPowerForChannel:0];
    
    NSLog(@"平均分貝:%f, 峰值分貝:%f", num1, num2);
}
- (void)updateMeter
{
    [self.recorder updateMeters];  // 刷新
    
    CGFloat num1 = [self.recorder averagePowerForChannel:0];
    CGFloat num2 = [self.recorder peakPowerForChannel:0];
    
    NSLog(@"平均分貝:%f, 峰值分貝:%f", num1, num2);
}

上面方法都會返回用于表示聲音分貝(dB)等級的浮點值,這個值的范圍是 -160dB ~ 0dB

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容