音頻輸出作為硬件資源晦溪,對于iOS系統(tǒng)來說是唯一的答恶,那么要如何協(xié)調(diào)和各個App之間對這個稀缺的硬件持有關(guān)系呢囊榜?
iOS給出的解決方案是"AVAudioSession" ,通過它可以實現(xiàn)對App當(dāng)前上下文音頻資源的控制亥宿,比如
插拔耳機(jī)、接電話砂沛、是否和其他音頻數(shù)據(jù)混音等烫扼。當(dāng)你遇到:
- 是進(jìn)行錄音還是播放?
- 當(dāng)系統(tǒng)靜音鍵按下時該如何表現(xiàn)碍庵?
- 是從揚聲器還是從聽筒里面播放聲音映企?
- 插拔耳機(jī)后如何表現(xiàn)悟狱?
- 來電話/鬧鐘響了后如何表現(xiàn)?
- 其他音頻App啟動后如何表現(xiàn)堰氓?
- ...
這些場景的時候挤渐,就可以考慮一下“AVAudioSession”了。
??????
在很久以前(其實也是不是太久--iOS7以前)還有個AudioSession的存在双絮,其功能與AVAudioSession類似浴麻,但是在iOS7 以后就已經(jīng)被標(biāo)記為
“Not Applicable”,所以如果Google到了說AudioSession的內(nèi)容而不是用的AVAudioSession,那么就可以直接PASS了囤攀,當(dāng)然如果要兼容iOS6
就另當(dāng)別論了软免,不過現(xiàn)在QQ/微信都是要求iOS7的情況下沸毁,是否需要兼容iOS6就看老板們的意思吧磅氨。
Session默認(rèn)行為
- 可以進(jìn)行播放,但是不能進(jìn)行錄制饮怯。
- 當(dāng)用戶將手機(jī)上的靜音撥片撥到“靜音”狀態(tài)時蝌衔,此時如果正在播放音頻榛泛,那么播放內(nèi)容會被靜音。
- 當(dāng)用戶按了手機(jī)的鎖屏鍵或者手機(jī)自動鎖屏了噩斟,此時如果正在播放音頻曹锨,那么播放會靜音并被暫停。
- 如果你的App在開始播放的時候亩冬,此時QQ音樂等其他App正在播放艘希,那么其他播放器會被靜音并暫停。
默認(rèn)的行為相當(dāng)于設(shè)置了Category為“AVAudioSessionCategorySoloAmbient”
來看Demo硅急。
通過這播放器demo可以驗證上面的默認(rèn)Session行為覆享。
AVAudioSession
AVAudioSession以一個單例實體的形式存在,通過類方法:
+ (AVAudioSession *)sharedInstance;
獲得單例营袜。
雖然系統(tǒng)會在App啟動的時候撒顿,激活這個唯一的AVAudioSession,但是最好還是在自己ViewController的viewDidLoad
里面再次進(jìn)行激活:
- (BOOL)setActive:(BOOL)active
error:(NSError * _Nullable *)outError;
通過設(shè)置active
為"YES"激活Session荚板,設(shè)置為“?NO”解除Session的激活狀態(tài)凤壁。BOOL返回值表示是否成功,如果失敗的話可以通過NSError的error.localizedDescription
查看出錯原因跪另。
因為AVAudioSession會影響其他App的表現(xiàn)拧抖,當(dāng)自己App的Session被激活,其他App的就會被解除激活免绿,如何要讓自己的Session解除激活后恢復(fù)其他App Session的激活狀態(tài)呢唧席?
此時可以使用:
- (BOOL)setActive:(BOOL)active
withOptions:(AVAudioSessionSetActiveOptions)options
error:(NSError * _Nullable *)outError;這里的options傳入
AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
即可。當(dāng)然,也可以通過
otherAudioPlaying
變量來提前判斷當(dāng)前是否有其他App在播放音頻淌哟。
可以通過:
@property(readonly) NSString *category;
屬性迹卢,獲取當(dāng)前的Category,比如上面的播放其徒仓,默認(rèn)是
NSLog(@"Current Category:%@", [AVAudioSession sharedInstance].category);
輸出:
Current Category:AVAudioSessionCategorySoloAmbien
七大Category
AVAudioSession主要能控制App的哪些表現(xiàn)以及如何控制的呢腐碱?首先AVAudioSession將使用音頻的場景分成七大類,通過設(shè)置Session為不同的類別掉弛,可以控制:
- 當(dāng)App激活Session的時候症见,是否會打斷其他不支持混音的App聲音
- 當(dāng)用戶觸發(fā)手機(jī)上的“靜音”鍵時或者鎖屏?xí)r,是否相應(yīng)靜音
- 當(dāng)前狀態(tài)是否支持錄音
- 當(dāng)前狀態(tài)是否支持播放
每個App啟動時都會設(shè)置成上面說的默認(rèn)狀態(tài)狰晚,即其他App會被中斷同時相應(yīng)“靜音”鍵的播放模式筒饰。通過下表可以細(xì)分每個類別的支持情況:
類別 | 當(dāng)按“靜音”或者鎖屏是是否靜音 | 是否引起不支持混音的App中斷 | 是否支持錄音和播放 |
---|---|---|---|
AVAudioSessionCategoryAmbient | 是 | 否 | 只支持播放 |
AVAudioSessionCategoryAudioProcessing | - | 都不支持 | |
AVAudioSessionCategoryMultiRoute | 否 | 是 | 既可以錄音也可以播放 |
AVAudioSessionCategoryPlayAndRecord | 否 | 默認(rèn)不引起 | 既可以錄音也可以播放 |
AVAudioSessionCategoryPlayback | 否 | 默認(rèn)引起 | 只用于播放 |
AVAudioSessionCategoryRecord | 否 | 是 | 只用于錄音 |
AVAudioSessionCategorySoloAmbient | 是 | 是 | 只用于播放 |
可以看到,其實默認(rèn)的就是“AVAudioSessionCategorySoloAmbient”類別壁晒。從表中我們可以總結(jié)如下:
- AVAudioSessionCategoryAmbient : 只用于播放音樂時瓷们,并且可以和QQ音樂同時播放,比如玩游戲的時候還想聽QQ音樂的歌秒咐,那么把游戲播放背景音就設(shè)置成這種類別谬晕。同時,當(dāng)用戶鎖屏或者靜音時也會隨著靜音携取,這種類別基本使用所有App的背景場景攒钳。
- AVAudioSessionCategorySoloAmbient: 也是只用于播放,但是和"AVAudioSessionCategoryAmbient"不同的是,用了它就別想聽QQ音樂了雷滋,比如不希望QQ音樂干擾的App不撑,類似節(jié)奏大師。同樣當(dāng)用戶鎖屏或者靜音時也會隨著靜音晤斩,鎖屏了就玩不了節(jié)奏大師了焕檬。
- AVAudioSessionCategoryPlayback: 如果鎖屏了還想聽聲音怎么辦?用這個類別澳泵,比如App本身就是播放器实愚,同時當(dāng)App播放時,其他類似QQ音樂就不能播放了兔辅。所以這種類別一般用于播放器類App
- AVAudioSessionCategoryRecord: 有了播放器腊敲,肯定要錄音機(jī),比如微信語音的錄制维苔,就要用到這個類別碰辅,既然要安靜的錄音,肯定不希望有QQ音樂了介时,所以其他播放聲音會中斷乎赴。想想微信語音的場景忍法,就知道什么時候用他了。
- AVAudioSessionCategoryPlayAndRecord: 如果既想播放又想錄制該用什么模式呢榕吼?比如VoIP,打電話這種場景勉失,PlayAndRecord就是專門為這樣的場景設(shè)計的 羹蚣。
- AVAudioSessionCategoryMultiRoute: 想象一個DJ用的App,手機(jī)連著HDMI到揚聲器播放當(dāng)前的音樂乱凿,然后耳機(jī)里面播放下一曲顽素,這種常人不理解的場景,這個類別可以支持多個設(shè)備輸入輸出徒蟆。
- AVAudioSessionCategoryAudioProcessing: 主要用于音頻格式處理胁出,一般可以配合AudioUnit進(jìn)行使用
了解了這七大類別,我們就可以根據(jù)自己的需要進(jìn)行對應(yīng)類別的設(shè)置了:
- (BOOL)setCategory:(NSString *)category error:(NSError **)outError;
傳入對應(yīng)的列表枚舉即可段审。如果返回"NO"可以通過NSError的error.localizedDescription
查看原因全蝶。
可以通過:
@property(readonly) NSArray<NSString *> *availableCategories;
屬性,查看當(dāng)前設(shè)備支持哪些類別寺枉,然后再進(jìn)行設(shè)置抑淫,從而保證傳入?yún)?shù)的合法,減少錯誤的可能姥闪。
比如修改上面的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);
此時在播放音樂的時候始苇,再去按下靜音鍵,會發(fā)現(xiàn)筐喳,音樂還在繼續(xù)播放催式,不會被靜音。
類別的選項
上面介紹的這個七大類別避归,可以認(rèn)為是設(shè)定了七種主場景荣月,而這七類肯定是不能滿足開發(fā)者所有的需求的。CoreAudio提供的方法是槐脏,首先定下七種的一種基調(diào)喉童,然后在進(jìn)行微調(diào)。CoreAudio為每種Category都提供了些許選項來進(jìn)行微調(diào)顿天。
在設(shè)置完類別后堂氯,可以通過
@property(readonly) AVAudioSessionCategoryOptions categoryOptions;
屬性,查看當(dāng)前類別設(shè)置了哪些選項牌废,注意這里的返回值是AVAudioSessionCategoryOptions咽白,實際是多個options的“|”運算。默認(rèn)情況下是0鸟缕。
選項 | 適用類別 | 作用 |
---|---|---|
AVAudioSessionCategoryOptionMixWithOthers | AVAudioSessionCategoryPlayAndRecord, AVAudioSessionCategoryPlayback, and AVAudioSessionCategoryMultiRoute | 是否可以和其他后臺App進(jìn)行混音 |
AVAudioSessionCategoryOptionDuckOthers | AVAudioSessionCategoryAmbient, AVAudioSessionCategoryPlayAndRecord, AVAudioSessionCategoryPlayback, and AVAudioSessionCategoryMultiRoute | 是否壓低其他App聲音 |
AVAudioSessionCategoryOptionAllowBluetooth | AVAudioSessionCategoryRecord and AVAudioSessionCategoryPlayAndRecord | 是否支持藍(lán)牙耳機(jī) |
AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryPlayAndRecord | 是否默認(rèn)用免提聲音 |
目前主要的選項有這幾種晶框,都有對應(yīng)的使用場景排抬,除此之外,在iOS9還提供了AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers
最新的iOS10又新加了兩個AVAudioSessionCategoryOptionAllowBluetoothA2DP
授段、AVAudioSessionCategoryOptionAllowAirPlay
用來支持藍(lán)牙A2DP耳機(jī)和AirPlay蹲蒲。
來看每個選項的基本作用:
- AVAudioSessionCategoryOptionMixWithOthers : 如果確實用的AVAudioSessionCategoryPlayback實現(xiàn)的一個背景音,但是呢侵贵,又想和QQ音樂并存届搁,那么可以在AVAudioSessionCategoryPlayback類別下在設(shè)置這個選項,就可以實現(xiàn)共存了窍育。
- AVAudioSessionCategoryOptionDuckOthers:在實時通話的場景卡睦,比如QQ音樂,當(dāng)進(jìn)行視頻通話的時候漱抓,會發(fā)現(xiàn)QQ音樂自動聲音降低了表锻,此時就是通過設(shè)置這個選項來對其他音樂App進(jìn)行了壓制。
- AVAudioSessionCategoryOptionAllowBluetooth:如果要支持藍(lán)牙耳機(jī)電話乞娄,則需要設(shè)置這個選項
- AVAudioSessionCategoryOptionDefaultToSpeaker: 如果在VoIP模式下瞬逊,希望默認(rèn)打開免提功能,需要設(shè)置這個選項
通過接口:
- (BOOL)setCategory:(NSString *)category withOptions:(AVAudioSessionCategoryOptions)options error:(NSError **)outError
來對當(dāng)前的類別進(jìn)行選項的設(shè)置补胚。
比如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音樂播放器,然后再開始進(jìn)行播放溶其,會發(fā)現(xiàn)骚腥,QQ和我們的播放器都在播放,并且進(jìn)行了自動混音瓶逃。
不過這個過程束铭,感覺CoreAudio缺少一個setOption
的接口,既然已經(jīng)是當(dāng)前處于的Category厢绝,干嘛還要再設(shè)置選項的時候再指定Category呢契沫??疑惑昔汉。懈万。。
七大模式
剛講完七大類別靶病,現(xiàn)在再來七大模式会通。通過上面的七大類別,我們基本覆蓋了常用的主場景娄周,在每個主場景中可以通過Option進(jìn)行微調(diào)涕侈。為此CoreAudio提供了七大比較常見微調(diào)后的子場景。叫做各個類別的模式煤辨。
模式 | 適用的類別 | 場景 |
---|---|---|
AVAudioSessionModeDefault | 所有類別 | 默認(rèn)的模式 |
AVAudioSessionModeVoiceChat | AVAudioSessionCategoryPlayAndRecord | VoIP |
AVAudioSessionModeGameChat | AVAudioSessionCategoryPlayAndRecord | 游戲錄制裳涛,由GKVoiceChat自動設(shè)置木张,無需手動調(diào)用 |
AVAudioSessionModeVideoRecording | AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryRecord | 錄制視頻時 |
AVAudioSessionModeMoviePlayback | AVAudioSessionCategoryPlayback | 視頻播放 |
AVAudioSessionModeMeasurement | AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryRecord AVAudioSessionCategoryPlayback | 最小系統(tǒng) |
AVAudioSessionModeVideoChat | AVAudioSessionCategoryPlayAndRecord | 視頻通話 |
每個模式有其適用的類別,所以端三,并不是有“七七 四十九”種組合舷礼。如果當(dāng)前處于的類別下沒有這個模式,那么是設(shè)置不成功的郊闯。設(shè)置完Category后可以通過:
@property(readonly) NSArray<NSString *> *availableModes;
屬性且轨,查看其支持哪些屬性,做合法性校驗虚婿。
來看具體應(yīng)用:
- AVAudioSessionModeDefault: 每種類別默認(rèn)的就是這個模式,所有要想還原的話泳挥,就設(shè)置成這個模式然痊。
- AVAudioSessionModeVoiceChat:主要用于VoIP場景,此時系統(tǒng)會選擇最佳的輸入設(shè)備屉符,比如插上耳機(jī)就使用耳機(jī)上的麥克風(fēng)進(jìn)行采集剧浸。此時有個副作用,他會設(shè)置類別的選項為"AVAudioSessionCategoryOptionAllowBluetooth"從而支持藍(lán)牙耳機(jī)矗钟。
- AVAudioSessionModeVideoChat : 主要用于視頻通話唆香,比如QQ視頻、FaceTime吨艇。時系統(tǒng)也會選擇最佳的輸入設(shè)備躬它,比如插上耳機(jī)就使用耳機(jī)上的麥克風(fēng)進(jìn)行采集并且會設(shè)置類別的選項為"AVAudioSessionCategoryOptionAllowBluetooth" 和 "AVAudioSessionCategoryOptionDefaultToSpeaker"。
- AVAudioSessionModeGameChat : 適用于游戲App的采集和播放东涡,比如“GKVoiceChat”對象冯吓,一般不需要手動設(shè)置
另外幾種和音頻APP關(guān)系不大,一般我們只需要關(guān)注VoIP或者視頻通話即可疮跑。
通過調(diào)用:
- (BOOL)setMode:(NSString *)mode error:(NSError **)outError
可以在設(shè)置Category之后再設(shè)置模式组贺。
當(dāng)然,這些模式只是CoreAduio總結(jié)的祖娘,不一定完全滿足要求失尖,對于具體的模式,在iOS10中還是可以微調(diào)的渐苏。通過接口:
- (BOOL)setCategory:(NSString *)category mode:(NSString *)mode options:(AVAudioSessionCategoryOptions)options error:(NSError **)outError
但是在iOS9及以下就只能在Category上調(diào)了掀潮,其實本質(zhì)是一樣的,可以認(rèn)為是個API糖整以,接口封裝胧辽。
系統(tǒng)中斷響應(yīng)
上面說的這些Category啊、Option啊以及Mode都是對自己作為播放主體時的表現(xiàn)公黑,但是假設(shè)邑商,現(xiàn)在正在播放著摄咆,突然來電話了、鬧鐘響了或者你在后臺放歌但是用戶啟動其他App用上面的方法影響的時候人断,我們的App該如何表現(xiàn)呢吭从?最常用的場景當(dāng)然是先暫停,待恢復(fù)的時候再繼續(xù)恶迈。那我們的App要如何感知到這個終端以及何時恢復(fù)呢涩金?
AVAudioSession提供了多種Notifications來進(jìn)行此類狀況的通知。其中將來電話暇仲、鬧鈴響等都?xì)w結(jié)為一般性的中斷步做,用
AVAudioSessionInterruptionNotification
來通知。其回調(diào)回來的userInfo主要包含兩個鍵:
- AVAudioSessionInterruptionTypeKey: 取值為
AVAudioSessionInterruptionTypeBegan
表示中斷開始奈附,我們應(yīng)該暫停播放和采集全度,取值為AVAudioSessionInterruptionTypeEnded
表示中斷結(jié)束,我們可以繼續(xù)播放和采集斥滤。 - AVAudioSessionInterruptionOptionKey: 當(dāng)前只有一種值
AVAudioSessionInterruptionOptionShouldResume
表示此時也應(yīng)該恢復(fù)繼續(xù)播放和采集将鸵。
而將其他App占據(jù)AudioSession的時候用AVAudioSessionSilenceSecondaryAudioHintNotification
來進(jìn)行通知。其回調(diào)回來的userInfo鍵為:
AVAudioSessionSilenceSecondaryAudioHintTypeKey
可能包含的值:
- AVAudioSessionSilenceSecondaryAudioHintTypeBegin: 表示其他App開始占據(jù)Session
- AVAudioSessionSilenceSecondaryAudioHintTypeEnd: 表示其他App開始釋放Session
外設(shè)改變
除了其他App和系統(tǒng)服務(wù)佑颇,會對我們的App產(chǎn)生影響以外顶掉,用戶的手也會對我們產(chǎn)生影響。默認(rèn)情況下挑胸,AudioSession會在App啟動時選擇一個最優(yōu)的輸出方案痒筒,比如插入耳機(jī)的時候,就用耳機(jī)嗜暴。但是這個過程中凸克,用戶可能拔出耳機(jī),我們App要如何感知這樣的情況呢闷沥?
同樣AVAudioSession也是通過Notifications來進(jìn)行此類狀況的通知萎战。
假設(shè)有這樣的App:
![route_change](http://images.libcz.com:8000/images/blog/iOS/avfoundation/session/images/route_change.png)
最開始在錄音時,用戶插入和拔出耳機(jī)我們都停止錄音舆逃,這里通過Notification來通知有新設(shè)備了蚂维,或者設(shè)備被退出了,然后我們控制停止錄音路狮〕嫔叮或者在播放時,當(dāng)耳機(jī)被拔出出時奄妨,Notification給了通知涂籽,我們先暫停音樂播放,待耳機(jī)插回時砸抛,在繼續(xù)播放评雌。
在NSNotificationCenter中對AVAudioSessionRouteChangeNotification進(jìn)行注冊树枫。在其userInfo中有鍵:
- AVAudioSessionRouteChangeReasonKey : 表示改變的原因
枚舉值 | 意義 |
---|---|
AVAudioSessionRouteChangeReasonUnknown | 未知原因 |
AVAudioSessionRouteChangeReasonNewDeviceAvailable | 有新設(shè)備可用 |
AVAudioSessionRouteChangeReasonOldDeviceUnavailable | 老設(shè)備不可用 |
AVAudioSessionRouteChangeReasonCategoryChange | 類別改變了 |
AVAudioSessionRouteChangeReasonOverride | App重置了輸出設(shè)置 |
AVAudioSessionRouteChangeReasonWakeFromSleep | 從睡眠狀態(tài)呼醒 |
AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory | 當(dāng)前Category下沒有合適的設(shè)備 |
AVAudioSessionRouteChangeReasonRouteConfigurationChange | Rotuer的配置改變了 |
- AVAudioSessionSilenceSecondaryAudioHintTypeKey: 和上面的中斷意義意義。
總結(jié):
AVAudioSession構(gòu)建了一個音頻使用生命周期的上下文景东。當(dāng)前狀態(tài)是否可以錄音砂轻、對其他App有怎樣的影響、是否響應(yīng)系統(tǒng)的靜音鍵斤吐、如何感知來電話了等都可以通過它來實現(xiàn)搔涝。尤為重要的是AVAudioSession不僅可以和AVFoundation中的AVAudioPlyaer/AVAudioRecorder配合,其他錄音/播放工具比如AudioUnit和措、AudioQueueService也都需要他進(jìn)行錄音庄呈、靜音等上下文配合。
文中Demo參見GitHub