iOS音訊-AVAudioSession

weixin_34007291發表於2018-12-01

最近在處理錄音方面的問題,做個轉載
參考連結
參考連結

AVAudioSession就是用來管理多個APP對音訊硬體裝置(麥克風,揚聲器)的資源使用。

舉例一下AVAudioSession可以做這些事情

  • 設定自己的APP是否和其他APP音訊同時存在,還是中斷其他APP聲音
  • 在手機調到靜音模式下,自己的APP音訊是否可以播放出聲音
  • 電話或者其他APP中斷自己APP的音訊的事件處理
  • 指定音訊輸入和輸出的裝置(比如是聽筒輸出聲音,還是揚聲器輸出聲音)
  • 是否支援錄音,錄音同時是否支援音訊播放

AVAudioSession Category

AVAudioSession的介面比較簡單。APP啟動的時候會自動啟用AVAudioSession,當然我們可以手動啟用程式碼如下。

//匯入標頭檔案
#import <AVFoundation/AVFoundation.h>

//AVAudioSession是一個單例類
AVAudioSession *session = [AVAudioSession sharedInstance];
//AVAudioSessionCategorySoloAmbient是系統預設的category
[session setCategory:AVAudioSessionCategorySoloAmbient error:nil];
//啟用AVAudioSession
[session setActive:YES error:nil];

可以看到設定session這裡有兩個引數,category和options
Category iOS下目前有七種,每種Category都對應是否支援下面四種能力

  • Interrupts non-mixable apps audio:是否打斷不支援混音播放的APP

  • Silenced by the Silent switch:是否會響應手機靜音鍵開關

  • Supports audio input:是否支援音訊錄製

  • Supports audio output:是否支援音訊播放

    Category 是否允許音訊播放/錄音 是否打斷其他不支援混音APP 是否會被靜音鍵或鎖屏鍵靜音
    AVAudioSessionCategoryAmbient 支援
    AVAudioSessionCategoryAudioProcessing 不支援播放,不支援錄製
    AVAudioSessionCategoryPlayback 只支援播放 預設YES,可以重寫為NO
    AVAudioSessionCategoryRecord 只支援錄製 否(鎖屏下仍可錄製)
    AVAudioSessionCategorySoloAmbient 只支援播放
    AVAudioSessionCategoryMultiRoute 支援播放,支援錄製
    AVAudioSessionCategoryPlayAndRecord 支援播放,支援錄製 預設YES,可以重寫為NO
  • AVAudioSessionCategoryAmbient,只支援音訊播放。這個 Category,音訊會被靜音鍵和鎖屏鍵靜音。並且不會打斷其他應用的音訊播放。

  • AVAudioSessionCategorySoloAmbient,這個是系統預設使用的 Category,只支援音訊播放。音訊會被靜音鍵和鎖屏鍵靜音。和AVAudioSessionCategoryAmbient不同的是,這個會打斷其他應用的音訊播放

  • AVAudioSessionCategoryPlayback,只支援音訊播放。你的音訊不會被靜音鍵和鎖屏鍵靜音。適用於音訊是主要功能的APP,像網易雲這些音樂app,鎖屏後依然可以播放。

需要注意一下,選擇支援在靜音鍵切到靜音狀態以及鎖屏鍵切到鎖屏狀態下仍然可以播放音訊 Category 時,必須在應用中開啟支援後臺音訊功能,詳見 UIBackgroundModes
  • AVAudioSessionCategoryRecord,只支援音訊錄製。不支援播放。
  • AVAudioSessionCategoryPlayAndRecord,支援音訊播放和錄製。音訊的輸入和輸出不需要同步進行,也可以同步進行。需要音訊通話類應用,可以使用這個 Category。
  • AVAudioSessionCategoryAudioProcessing,只支援本地音訊編解碼處理。不支援播放和錄製。
  • AVAudioSessionCategoryMultiRoute,支援音訊播放和錄製。允許多條音訊流的同步輸入和輸出。(比如USB連線外部揚聲器輸出音訊,藍芽耳機同時播放另一路音訊這種特殊需求)

可以通過AVAudioSession的屬性來讀取當前裝置支援的Category

@property(readonly) NSArray<NSString *> *availableCategories;

設定Category程式碼

NSError *setCategoryError = nil;
BOOL isSuccess = [[AVAudioSession sharedInstance]         setCategory:AVAudioSessionCategoryAmbient error:&setCategoryError];
if (!success) { 
//這裡可以讀取setCategoryError.localizedDescription檢視錯誤原因
}

AVAudioSession Mode&&Options

剛剛介紹的Category定義了七種主場景,實際開發需求中有時候需要對Category進行微調整,我們發現這個介面還有兩個引數Mode和Options。

AVAudioSession Mode

我們通過讀取下面這條屬性獲取當前裝置支援的Mode

@property(readonly) NSArray<NSString *> *availableModes;

iOS下有七種mode來定製我們的Category行為

模式 相容的 場景
AVAudioSessionModeDefault All 預設模式
AVAudioSessionModeVoiceChat AVAudioSessionCategoryPlayAndRecord VoIP
AVAudioSessionModeGameChat AVAudioSessionCategoryPlayAndRecord 遊戲錄製,GKVoiceChat自動設定
AVAudioSessionModeVideoRecording AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryRecord 錄製視訊
AVAudioSessionModeMoviePlayback AVAudioSessionCategoryPlayback 視訊播放
AVAudioSessionModeMeasurement AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryRecord AVAudioSessionCategoryPlayback 最小系統
AVAudioSessionModeVideoChat AVAudioSessionCategoryPlayAndRecord 視訊通話

下面逐一介紹下每個Mode

  • AVAudioSessionModeDefault,預設模式,與所有的 Category 相容

  • AVAudioSessionModeVoiceChat,適用於VoIP 型別的應用。只能是 - AVAudioSessionCategoryPlayAndRecord Category下。在這個模式系統會自動配置AVAudioSessionCategoryOptionAllowBluetooth 這個選項。系統會自動選擇最佳的內建麥克風組合支援語音聊天。

  • AVAudioSessionModeVideoChat,用於視訊聊天型別應用,只能是 AVAudioSessionCategoryPlayAndRecord Category下。適在這個模式系統會自動配置 AVAudioSessionCategoryOptionAllowBluetooth 和 AVAudioSessionCategoryOptionDefaultToSpeaker 選項。系統會自動選擇最佳的內建麥克風組合支援視訊聊天。

  • AVAudioSessionModeGameChat,適用於遊戲類應用。使用 GKVoiceChat 物件的應用會自動設定這個模式和 AVAudioSessionCategoryPlayAndRecord Category。實際引數和AVAudioSessionModeVideoChat一致

  • AVAudioSessionModeVideoRecording,適用於使用攝像頭採集視訊的應用。只能是 AVAudioSessionCategoryPlayAndRecord 和 AVAudioSessionCategoryRecord 這兩個 Category下。這個模式搭配 AVCaptureSession API 結合來用可以更好地控制音視訊的輸入輸出路徑。(例如,設定 automaticallyConfiguresApplicationAudioSession 屬性,系統會自動選擇最佳輸出路徑。

  • AVAudioSessionModeMeasurement,最小化系統。只用於 AVAudioSessionCategoryPlayAndRecord、AVAudioSessionCategoryRecord、AVAudioSessionCategoryPlayback 這幾種 Category。

  • AVAudioSessionModeMoviePlayback,適用於播放視訊的應用。只用於 AVAudioSessionCategoryPlayback 這個Category。

AVAudioSession Options

我們還可以使用options去微調Category行為,如下表

Option Option功能說明 相容的 Category
AVAudioSessionCategoryOptionMixWithOthers 支援和其他APP音訊 mix AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryPlayback AVAudioSessionCategoryMultiRoute
AVAudioSessionCategoryOptionDuckOthers 系統智慧調低其他APP音訊音量 AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryPlayback AVAudioSessionCategoryMultiRoute
AVAudioSessionCategoryOptionAllowBluetooth 支援藍芽音訊輸入 AVAudioSessionCategoryRecord AVAudioSessionCategoryPlayAndRecord
AVAudioSessionCategoryOptionDefaultToSpeaker 設定預設輸出音訊到揚聲器 AVAudioSessionCategoryPlayAndRecord

調優我們的Category

通過Category和合適的Mode和Options的搭配我們可以調優出我們的效果,下面舉兩個應用場景:

用過高德地圖的都知道,在後臺播放QQ音樂的時候,如果導航語音出來,QQ音樂不會停止,而是被智慧壓低和混音,等導航語音播報完後,QQ音樂正常播放,這裡我們需要後臺播放音樂,所以Category使用AVAudioSessionCategoryPlayback,需要混音和智慧壓低其他APP音量,所以Options選用 AVAudioSessionCategoryOptionMixWithOthers和AVAudioSessionCategoryOptionDuckOthers

BOOL isSuccess = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback         withOptions:AVAudioSessionCategoryOptionMixWithOthers | AVAudioSessionCategoryOptionDuckOthers error:&setCategoryError];

音訊中斷處理

中斷髮生時,應用程式的AVAudioSession會傳送通知AVAudioSessionInterruptionNotification,註冊通知程式碼如下:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];

在接收到通知的userInfo中,會包含一個AVAudioSessionInterruptionTypeKey,用來標識中斷開始和中斷結束.
當中斷型別為AVAudioSessionInterruptionTypeKeyEnded時,userInfo中還會包含一個AVAudioSessionInterruptionOptions來表明音訊會話是否已經重新啟用以及是否可以再次播放.示例程式碼如下:

- (void)handleInterruption:(NSNotification *)notification
{
  _descriptionLabel.text = @"handleInterruption";
NSDictionary *info = notification.userInfo;
AVAudioSessionInterruptionType type = [info[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
if (type == AVAudioSessionInterruptionTypeBegan) {
    //Handle InterruptionBegan
}else{
    AVAudioSessionInterruptionOptions options = [info[AVAudioSessionInterruptionOptionKey] unsignedIntegerValue];
    if (options == AVAudioSessionInterruptionOptionShouldResume) {
        //Handle Resume
        
    }
  }
}

在iOS裝置上新增或移除音訊輸入,輸出線路時,會發生線路改變,比如使用者插入耳機或斷開USB麥克風.當這些事件發生時,音訊會根據情況改變輸入或輸入線路,同時AVAudioSession會傳送一個相關變化的通知AVAudioSessionRouteChangeNotification.註冊通知的相關程式碼如下:

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleRouteChange:) name:AVAudioSessionRouteChangeNotification object:[AVAudioSession sharedInstance]];

根據蘋果公司的文件,當使用者插入耳機時,隱含的意思是使用者不希望外界聽到具體的音訊了內容,這就意味著當使用者斷開耳機時,播放的內容可能需要保密,所以我們需要在斷開耳機時停止音訊播放.

AVAudioSessionRouteChangeNotification通知的userinfo中會帶有通知傳送的原因資訊及前一個線路的描述.線路變更的原因儲存在userinfo的AVAudioSessionRouteChangeReasonKey值中,通過返回值可以推斷出不同的事件,對於舊音訊裝置中斷對應的reason為AVAudioSessionRouteChangeReasonOldDeviceUnavailable.但光憑這個reason並不能斷定是耳機斷開,所以還需要使用通過AVAudioSessionRouteChangePreviousRouteKey獲得上一線路的描述資訊,注意線路的描述資訊整合在一個輸入NSArray和一個輸出NSArray中,陣列中的元素都是AVAudioSessionPortDescription物件.我們需要從線路描述中找到第一個輸出介面並判斷其是否為耳機介面,如果為耳機,則停止播放.

- (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];
NSString *portType = previousOutput.portType;
if ([portType isEqualToString:AVAudioSessionPortHeadphones]) {
    }
  }
}
總結:AVAudioSession的作用就是管理音訊這一唯一硬體資源的分配,通過調優合適的AVAudioSession來適配我們的APP對於音訊的功能需求。切換音訊場景時候,需要相應的切換AVAudioSession。

相關文章