這里首先感謝CZ_iOS
看了他的博客,自己做了下記錄.
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可以驗證上面的默認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
枚舉值 意義
AVAudioSessionRouteChangeReasonUnknown 未知原因
AVAudioSessionRouteChangeReasonNewDeviceAvailable 有新設備可用
AVAudioSessionRouteChangeReasonOldDeviceUnavailable 老設備不可用
AVAudioSessionRouteChangeReasonCategoryChange 類別改變了
AVAudioSessionRouteChangeReasonOverride App重置了輸出設置
AVAudioSessionRouteChangeReasonWakeFromSleep 從睡眠狀態呼醒
AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory 當前Category下沒有合適的設備
AVAudioSessionRouteChangeReasonRouteConfigurationChange Rotuer的配置改變了