標籤:uid 參數 介紹 min 標記 config suitable queue 輸入輸出
音訊輸出作為硬體資源,對於iOS系統來說是唯一的,那麼要如何協調和各個App之間對這個稀缺的硬體持有關係呢?
iOS給出的解決方案是"AVAudioSession" ,通過它可以實現對App當前上下文音頻資源的控制,比如
插拔耳機、接電話、是否和其他音頻資料混音等。當你遇到:
- 是進行錄音還是播放?
- 當系統靜音鍵按下時該如何表現?
- 是從擴音器還是從耳機裡面播放聲音?
- 插拔耳機後如何表現?
- 來電話/鬧鐘響了後如何表現?
- 其他音頻App啟動後如何表現?
- ...
這些情境的時候,就可以考慮一下“AVAudioSession”了。
??????
在很久以前(其實也是不是太久--iOS7以前)還有個AudioSession的存在,其功能與AVAudioSession類似,但是在iOS7 以後就已經被標記為
“Not Applicable”,所以如果Google到了說AudioSession的內容而不是用的AVAudioSession,那麼就可以直接PASS了,當然如果要相容iOS6
就另當別論了,不過現在QQ/都是要求iOS7的情況下,是否需要相容iOS6就看老闆們的意思吧。
Session預設行為
- 可以進行播放,但是不能進行錄製。
- 當使用者將手機上的靜音按板撥到“靜音”狀態時,此時如果現正播放音頻,那麼播放內容會被靜音。
- 當使用者按了手機的鎖屏鍵或者手機自動鎖屏了,此時如果現正播放音頻,那麼播放會靜音並被暫停。
- 如果你的App在開始播放的時候,此時QQ音樂等其他App現正播放,那麼其他播放器會被靜音並暫停。
預設的行為相當於設定了Category為“AVAudioSessionCategorySoloAmbient”
來看Demo。
demo_player
通過這播放器demo可以驗證上面的預設Session行為。
AVAudioSession
AVAudioSession以一個單例實體的形式存在,通過類方法:
+ (AVAudioSession *)sharedInstance;
獲得單例。
雖然系統會在App啟動的時候,啟用這個唯一的AVAudioSession,但是最好還是在自己ViewController的viewDidLoad
裡面再次進行啟用:
- (BOOL)setActive:(BOOL)active error:(NSError * _Nullable *)outError;
通過設定active
為"YES"啟用Session,設定為“?NO”解除Session的啟用狀態。BOOL傳回值表示是否成功,如果失敗的話可以通過NSError的error.localizedDescription
查看出錯原因。
因為AVAudioSession會影響其他App的表現,當自己App的Session被啟用,其他App的就會被解除啟用,如何要讓自己的Session解除啟用後恢複其他App Session的啟用狀態呢?
此時可以使用:
- (BOOL)setActive:(BOOL)active
withOptions:(AVAudioSessionSetActiveOptions)options
error:(NSError * _Nullable *)outError;
這裡的options傳入AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
即可。
當然,也可以通過otherAudioPlaying
變數來提前判斷當前是否有其他App在播放音頻。
可以通過:
@property(readonly) NSString *category;
屬性,擷取當前的Category,比如上面的播放其,預設是
NSLog(@"Current Category:%@", [AVAudioSession sharedInstance].category);
輸出:
Current Category:AVAudioSessionCategorySoloAmbien
七大Category
AVAudioSession主要能控制App的哪些表現以及如何控制的呢?首先AVAudioSession將使用音訊情境分成七大類,通過設定Session為不同的類別,可以控制:
- 當App啟用Session的時候,是否會打斷其他不支援混音的App聲音
- 當使用者觸發手機上的“靜音”鍵時或者鎖屏時,是否相應靜音
- 目前狀態是否支援錄音
- 目前狀態是否支援播放
每個App啟動時都會設定成上面說的預設狀態,即其他App會被中斷同時相應“靜音”鍵的播放模式。通過下表可以細分每個類別的支援情況:
類別 |
當按“靜音”或者鎖屏是是否靜音 |
是否引起不支援混音的App中斷 |
是否支援錄音和播放 |
AVAudioSessionCategoryAmbient |
是 |
否 |
只支援播放 |
AVAudioSessionCategoryAudioProcessing |
- |
都不支援 |
AVAudioSessionCategoryMultiRoute |
否 |
是 |
既可以錄音也可以播放 |
AVAudioSessionCategoryPlayAndRecord |
否 |
預設不引起 |
既可以錄音也可以播放 |
AVAudioSessionCategoryPlayback |
否 |
預設引起 |
只用於播放 |
AVAudioSessionCategoryRecord |
否 |
是 |
只用於錄音 |
AVAudioSessionCategorySoloAmbient |
是 |
是 |
只用於播放 |
可以看到,其實預設的就是“AVAudioSessionCategorySoloAmbient”類別。從表中我們可以總結如下:
- AVAudioSessionCategoryAmbient : 只用於播放音樂時,並且可以和QQ音樂同時播放,比如玩遊戲的時候還想聽QQ音樂的歌,那麼把遊戲播放背景音就設定成這種類別。同時,當使用者鎖屏或者靜音時也會隨著靜音,這種類別基本使用所有App的背景情境。
- AVAudioSessionCategorySoloAmbient: 也是只用於播放,但是和"AVAudioSessionCategoryAmbient"不同的是,用了它就別想聽QQ音樂了,比如不希望QQ音樂幹擾的App,類似節奏大師。同樣當使用者鎖屏或者靜音時也會隨著靜音,鎖屏了就玩不了節奏大師了。
- AVAudioSessionCategoryPlayback: 如果鎖屏了還想聽聲音怎麼辦?用這個類別,比如App本身就是播放器,同時當App播放時,其他類似QQ音樂就不能播放了。所以這種類別一般用於播放器類App
- AVAudioSessionCategoryRecord: 有了播放器,肯定要錄音機,比如語音的錄製,就要用到這個類別,既然要安靜的錄音,肯定不希望有QQ音樂了,所以其他播放聲音會中斷。想想語音的情境,就知道什麼時候用他了。
- AVAudioSessionCategoryPlayAndRecord: 如果既想播放又想錄製該用什麼模式呢?比如VoIP,打電話這種情境,PlayAndRecord就是專門為這樣的情境設計的 。
- AVAudioSessionCategoryMultiRoute: 想象一個DJ用的App,手機連著HDMI到擴音器播放當前的音樂,然後耳機裡面播放下一曲,這種常人不理解的情境,這個類別可以支援多個裝置輸入輸出。
- AVAudioSessionCategoryAudioProcessing: 主要用於音頻格式處理,一般可以配合AudioUnit進行使用
瞭解了這七大類別,我們就可以根據自己的需要進行對應類別的設定了:
- (BOOL)setCategory:(NSString *)category error:(NSError **)outError;
傳入對應的列表枚舉即可。如果返回"NO"可以通過NSError的error.localizedDescription
查看原因。
可以通過:
@property(readonly) NSArray<NSString *> *availableCategories;
屬性,查看當前裝置支援哪些類別,然後再進行設定,從而保證傳入參數的合法,減少錯誤的可能。
比如修改上面的Demo例子:
NSLog(@"Current Category:%@", [AVAudioSession sharedInstance].category); NSError *error = nil; [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error]; if (nil != error) { NSLog(@"set Option error %@", error.localizedDescription); } NSLog(@"Current Category:%@", [AVAudioSession sharedInstance].category);
此時在播放音樂的時候,再去按下靜音鍵,會發現,音樂還在繼續播放,不會被靜音。
類別的選項
上面介紹的這個七大類別,可以認為是設定了七種主情境,而這七類肯定是不能滿足開發人員所有的需求的。CoreAudio提供的方法是,首先定下七種的一種基調,然後在進行微調。CoreAudio為每種Category都提供了些許選項來進行微調。
在設定完類別後,可以通過
@property(readonly) AVAudioSessionCategoryOptions categoryOptions;
屬性,查看當前類別設定了哪些選項,注意這裡的傳回值是AVAudioSessionCategoryOptions,實際是多個options的“|”運算。預設情況下是0。
選項 |
適用類別 |
作用 |
AVAudioSessionCategoryOptionMixWithOthers |
AVAudioSessionCategoryPlayAndRecord, AVAudioSessionCategoryPlayback, and AVAudioSessionCategoryMultiRoute |
是否可以和其他後台App進行混音 |
AVAudioSessionCategoryOptionDuckOthers |
AVAudioSessionCategoryAmbient, AVAudioSessionCategoryPlayAndRecord, AVAudioSessionCategoryPlayback, and AVAudioSessionCategoryMultiRoute |
是否壓低其他App聲音 |
AVAudioSessionCategoryOptionAllowBluetooth |
AVAudioSessionCategoryRecord and AVAudioSessionCategoryPlayAndRecord |
是否支援藍芽耳機 |
AVAudioSessionCategoryOptionDefaultToSpeaker |
AVAudioSessionCategoryPlayAndRecord |
是否預設用免提聲音 |
目前主要的選項有這幾種,都有對應的使用情境,除此之外,在iOS9還提供了AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers
最新的iOS10又新加了兩個AVAudioSessionCategoryOptionAllowBluetoothA2DP
、AVAudioSessionCategoryOptionAllowAirPlay
用來支援藍芽A2DP耳機和AirPlay。
來看每個選項的基本作用:
- AVAudioSessionCategoryOptionMixWithOthers : 如果確實用的AVAudioSessionCategoryPlayback實現的一個背景音,但是呢,又想和QQ音樂並存,那麼可以在AVAudioSessionCategoryPlayback類別下在設定這個選項,就可以實現共存了。
- AVAudioSessionCategoryOptionDuckOthers:在即時通話的情境,比如QQ音樂,當進行視訊通話的時候,會發現QQ音樂自動聲音降低了,此時就是通過設定這個選項來對其他音樂App進行了壓制。
- AVAudioSessionCategoryOptionAllowBluetooth:如果要支援藍芽耳機電話,則需要設定這個選項
- AVAudioSessionCategoryOptionDefaultToSpeaker: 如果在VoIP模式下,希望預設開啟免提功能,需要設定這個選項
通過介面:
- (BOOL)setCategory:(NSString *)category withOptions:(AVAudioSessionCategoryOptions)options error:(NSError **)outError
來對當前的類別進行選項的設定。
比如Demo中:
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:&error]; if (nil != error) { NSLog(@"set Option error %@", error.localizedDescription); } options = [[AVAudioSession sharedInstance] categoryOptions]; NSLog(@"Category[%@] has %lu options", [AVAudioSession sharedInstance].category, options);
此時,先開啟QQ音樂播放器,然後再開始進行播放,會發現,QQ和我們的播放器都在播放,並且進行了自動混音。
不過這個過程,感覺CoreAudio缺少一個setOption
的介面,既然已經是當前處於的Category,幹嘛還要再設定選項的時候再指定Category呢??疑惑。。。
七大模式
剛講完七大類別,現在再來七大模式。通過上面的七大類別,我們基本覆蓋了常用的主情境,在每個主情境中可以通過Option進行微調。為此CoreAudio提供了七大比較常見微調後的子情境。叫做各個類別的模式。
模式 |
適用的類別 |
情境 |
AVAudioSessionModeDefault |
所有類別 |
預設的模式 |
AVAudioSessionModeVoiceChat |
AVAudioSessionCategoryPlayAndRecord |
VoIP |
AVAudioSessionModeGameChat |
AVAudioSessionCategoryPlayAndRecord |
遊戲錄製,由GKVoiceChat自動化佈建,無需手動調用 |
AVAudioSessionModeVideoRecording |
AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryRecord |
錄製視頻時 |
AVAudioSessionModeMoviePlayback |
AVAudioSessionCategoryPlayback |
視頻播放 |
AVAudioSessionModeMeasurement |
AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryRecord AVAudioSessionCategoryPlayback |
最小系統 |
AVAudioSessionModeVideoChat |
AVAudioSessionCategoryPlayAndRecord |
視訊通話 |
每個模式有其適用的類別,所以,並不是有“七七 四十九”種組合。如果當前處於的類別下沒有這個模式,那麼是設定不成功的。設定完Category後可以通過:
@property(readonly) NSArray<NSString *> *availableModes;
屬性,查看其支援哪些屬性,做合法性校正。
來看具體應用:
- AVAudioSessionModeDefault: 每種類別預設的就是這個模式,所有要想還原的話,就設定成這個模式。
- AVAudioSessionModeVoiceChat:主要用於VoIP情境,此時系統會選擇最佳的輸入裝置,比如插上耳機就使用耳機上的麥克風進行採集。此時有個副作用,他會設定類別的選項為"AVAudioSessionCategoryOptionAllowBluetooth"從而支援藍芽耳機。
- AVAudioSessionModeVideoChat : 主要用於視訊通話,比如QQ視頻、FaceTime。時系統也會選擇最佳的輸入裝置,比如插上耳機就使用耳機上的麥克風進行採集並且會設定類別的選項為"AVAudioSessionCategoryOptionAllowBluetooth" 和 "AVAudioSessionCategoryOptionDefaultToSpeaker"。
- AVAudioSessionModeGameChat : 適用於遊戲App的採集和播放,比如“GKVoiceChat”對象,一般不需要手動設定
另外幾種和音頻APP關係不大,一般我們只需要關注VoIP或者視訊通話即可。
通過調用:
- (BOOL)setMode:(NSString *)mode error:(NSError **)outError
可以在設定Category之後再設定模式。
當然,這些模式只是CoreAduio總結的,不一定完全滿足要求,對於具體的模式,在iOS10中還是可以微調的。通過介面:
- (BOOL)setCategory:(NSString *)category mode:(NSString *)mode options:(AVAudioSessionCategoryOptions)options error:(NSError **)outError
但是在iOS9及以下就只能在Category上調了,其實本質是一樣的,可以認為是個API糖,介面封裝。
系統中斷響應
上面說的這些Category啊、Option啊以及Mode都是對自己作為播放主體時的表現,但是假設,現在現正播放著,突然來電話了、鬧鐘響了或者你在後台放歌但是使用者啟動其他App用上面的方法影響的時候,我們的App該如何表現呢?最常用的情境當然是先暫停,待恢複的時候再繼續。那我們的App要如何感知到這個終端以及何時恢複呢?
AVAudioSession提供了多種Notifications來進行此類狀況的通知。其中將來電話、鬧鈴響等都歸結為一般性的中斷,用
AVAudioSessionInterruptionNotification
來通知。其回調回來的userInfo主要包含兩個鍵:
- AVAudioSessionInterruptionTypeKey: 取值為
AVAudioSessionInterruptionTypeBegan
表示中斷開始,我們應該暫停播放和採集,取值為AVAudioSessionInterruptionTypeEnded
表示中斷結束,我們可以繼續播放和採集。
- AVAudioSessionInterruptionOptionKey: 當前只有一種值
AVAudioSessionInterruptionOptionShouldResume
表示此時也應該恢複繼續播放和採集。
而將其他App佔據AudioSession的時候用AVAudioSessionSilenceSecondaryAudioHintNotification
來進行通知。其回調回來的userInfo鍵為:
AVAudioSessionSilenceSecondaryAudioHintTypeKey
可能包含的值:
- AVAudioSessionSilenceSecondaryAudioHintTypeBegin: 表示其他App開始佔據Session
- AVAudioSessionSilenceSecondaryAudioHintTypeEnd: 表示其他App開始釋放Session
外設改變
除了其他App和系統服務,會對我們的App產生影響以外,使用者的手也會對我們產生影響。預設情況下,AudioSession會在App啟動時選擇一個最優的輸出方案,比如插入耳機的時候,就用耳機。但是這個過程中,使用者可能拔出耳機,我們App要如何感知這樣的情況呢?
同樣AVAudioSession也是通過Notifications來進行此類狀況的通知。
假設有這樣的App:
route_change
最開始在錄音時,使用者插入和拔出耳機我們都停止錄音,這裡通過Notification來通知有新裝置了,或者裝置被退出了,然後我們控制停止錄音。或者在播放時,當耳機被拔出出時,Notification給了通知,我們先暫停音樂播放,待耳機插回時,在繼續播放。
在NSNotificationCenter中對AVAudioSessionRouteChangeNotification進行註冊。在其userInfo中有鍵:
- AVAudioSessionRouteChangeReasonKey : 表示改變的原因
枚舉值 |
意義 |
AVAudioSessionRouteChangeReasonUnknown |
未知原因 |
AVAudioSessionRouteChangeReasonNewDeviceAvailable |
有新裝置可用 |
AVAudioSessionRouteChangeReasonOldDeviceUnavailable |
老裝置不可用 |
AVAudioSessionRouteChangeReasonCategoryChange |
類別改變了 |
AVAudioSessionRouteChangeReasonOverride |
App重設了輸出設定 |
AVAudioSessionRouteChangeReasonWakeFromSleep |
從睡眠狀態呼醒 |
AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory |
當前Category下沒有合適的裝置 |
AVAudioSessionRouteChangeReasonRouteConfigurationChange |
Rotuer的配置改變了 |
- AVAudioSessionSilenceSecondaryAudioHintTypeKey: 和上面的中斷意義意義。
總結:
AVAudioSession構建了一個音頻使用生命週期的上下文。目前狀態是否可以錄音、對其他App有怎樣的影響、是否響應系統的靜音鍵、如何感知來電話了等都可以通過它來實現。尤為重要的是AVAudioSession不僅可以和AVFoundation中的AVAudioPlyaer/AVAudioRecorder配合,其他錄音/播放工具比如AudioUnit、AudioQueueService也都需要他進行錄音、靜音等上下文配合。
文中Demo參見GitHub
參考文檔
- Audio Session Programming Guide
- AVAudioSession Class Reference
- Audio Session Services Reference
CZ_iOS
連結:https://www.jianshu.com/p/3e0a399380df
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。
iOS音頻掌柜-- AVAudioSession