前言
????回憶一個(gè)場景,我們使用iPhone 打開一首歌曲,音頻從內(nèi)置揚(yáng)聲器中播放出來,此時(shí)有電話撥入,音樂會立即停止并處于暫停狀態(tài)。此時(shí)聽到的是手機(jī)呼叫的鈴聲,當(dāng)我們掛掉電話后,剛才的音樂再次響起。在這一過程中 iOS 提供了一個(gè)可管理的音頻環(huán)境,通過 音頻會話(Audio Session)來管理應(yīng)用程序、應(yīng)用程序間和設(shè)備級別的音頻行為。
音頻會話介紹
????音頻會話在應(yīng)用程序和操作系統(tǒng)之間扮演者中間人的角色,它提供了一種簡單實(shí)用的方法使得系統(tǒng)得知應(yīng)用程序應(yīng)該如何與 iOS 音頻環(huán)境進(jìn)行交互。開發(fā)者不需要了解與音頻硬件交互的具體細(xì)節(jié),只需要對應(yīng)用程序的行為進(jìn)行抽象的配置,并把對該行為的管理委托給音頻會話,可確保對用戶的音頻體驗(yàn)進(jìn)行最佳管理。

????所有 iOS 應(yīng)用程序啟動后,都具有一個(gè)默認(rèn)音頻會話,無論是否使用。默認(rèn)音頻會話來自于以下一些預(yù)配置:
- 支持音頻播放,但不允許錄音。
- 在 iOS 中,將響鈴/靜音開關(guān)設(shè)置為靜音模式會使應(yīng)用程序正在播放的任何音頻靜音。
- 在 iOS 中,當(dāng)設(shè)備被鎖定時(shí),應(yīng)用程序的音頻會靜音。
- 當(dāng)應(yīng)用程序播放音頻時(shí),任何其他后臺音頻(例如音樂應(yīng)用程序正在播放的音頻)都會被靜音。
音頻會話類別
????默認(rèn)音頻會話提供了很多實(shí)用的功能,但在大多數(shù)情況下,開發(fā)者應(yīng)該對其進(jìn)行自定義以更好地滿足應(yīng)用程序的需求。要更改行為,需要配置應(yīng)用的音頻會話。幸運(yùn)的是,通過設(shè)置”類別“功能,可以很容易地定制我們的特殊需求。
????表達(dá)音頻行為的主要機(jī)制是音頻會話類別。通過設(shè)置類別,可以指定應(yīng)用程序是使用輸入還是輸出,是否希望音樂與音頻一起繼續(xù)播放等等。AV Foundation 定義了許多音頻會話類別,可讓開發(fā)者自定義音頻行為。下面表格總結(jié)了 AV Foundation 定義的 6 種類別行為細(xì)節(jié)。
| 類別 | 作用 | 是否允許混音 | 音頻的輸入與輸出 | 由響鈴/靜音開關(guān)和屏幕鎖定靜音 |
|---|---|---|---|---|
AVAudioSessionCategoryAmbient |
游戲、效率應(yīng)用程序 | Yes | 僅輸出 | Yes |
AVAudioSessionCategorySoloAmbient (默認(rèn)) |
游戲、效率應(yīng)用程序 | No | 僅輸出 | Yes |
AVAudioSessionCategoryPlayback |
音頻和視頻播放器 | 可選 | 僅輸出 | No |
AVAudioSessionCategoryRecord |
錄音機(jī)、音頻捕捉 | No | 僅輸入 | No |
AVAudioSessionCategoryPlayAndRecord |
Voip、語言聊天 | 可選 | 輸入和輸出 | No |
AVAudioSessionCategoryMultiRoute |
使用外部硬件的高級 A/V應(yīng)用程序 | No | 輸入和輸出 | No |
????可以通過 setCategory:mode:options:error:設(shè)置上述音頻會話類別,其中一些分類可以通過使用 options 和 modes進(jìn)一步自定義附加行為。示例如下:
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]);
}
音頻中斷及處理
????音頻中斷是應(yīng)用程序音頻會話的停用——它會立即停止音頻。當(dāng)來自其它應(yīng)用程序的音頻會話被激活并且該會話未被系統(tǒng)分類以與我們的應(yīng)用程序的音頻混合時(shí),就會發(fā)生中斷。在會話處于非活動狀態(tài)后,系統(tǒng)會發(fā)送一條“被中斷”消息,可以通過保存狀態(tài)、更新用戶界面等來響應(yīng)該消息。
????應(yīng)用程序可能會在中斷后暫停。當(dāng)用戶接聽電話時(shí)會發(fā)生這種情況。如果用戶轉(zhuǎn)而忽略呼叫或解除警報(bào),系統(tǒng)會發(fā)出“中斷結(jié)束”消息,并且應(yīng)用程序會繼續(xù)運(yùn)行。要恢復(fù)音頻,必須重新激活音頻會話。下圖說明了回放應(yīng)用程序的音頻會話中斷之前、期間和之后的事件順序。

中斷事件(在此示例中為 FaceTime 請求的到達(dá))按如下方式進(jìn)行。編號的步驟對應(yīng)于圖中的數(shù)字。
- 應(yīng)用程序處于活動狀態(tài),正在播放音頻。
- FaceTime 請求到達(dá)。系統(tǒng)激活 FaceTime 應(yīng)用程序的音頻會話。
- 系統(tǒng)會停用當(dāng)前音頻會話。此時(shí),應(yīng)用程序中的播放已停止。
- 系統(tǒng)會發(fā)布通知,表明會話已被停用。
- 通知處理程序需要采取適當(dāng)?shù)拇胧@?,它可以更新用戶界面并保存在停止點(diǎn)恢復(fù)播放所需的信息。
- 如果用戶解除中斷(忽略傳入的 FaceTime 請求),系統(tǒng)會發(fā)布通知,指示中斷已結(jié)束。
- 通知處理程序會采取適合中斷結(jié)束的操作。例如,它可能會更新用戶界面、重新激活音頻會話并恢復(fù)播放。
- (圖中未顯示。)如果用戶接聽電話,而不是在第 6 步消除中斷,則應(yīng)用程序?qū)和!?/li>
????在開發(fā)音頻功能時(shí),我們要確保應(yīng)用程序可以正確地處理中斷事件,音頻會話相關(guān)代碼以處理中斷可確保應(yīng)用程序的音頻在來電、時(shí)鐘或日歷警報(bào)響起或其他應(yīng)用程序激活其音頻會話時(shí)繼續(xù)正常運(yùn)行。
首先需要得到中斷出現(xiàn)的通知,可以選擇注冊應(yīng)用程序的 AVAudioSession 發(fā)送的通知 AVAudioSessionInterruptionNotification。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
????在系統(tǒng)發(fā)送通知調(diào)用 handlerInterruption: 時(shí)傳遞的NSNotification 實(shí)例包含一個(gè) userInfo 提供中斷詳細(xì)信息的填充字典。可以通過從字典中檢索 AVAudioSessionInterruptionType 值來確定中斷的類型,中斷類型指示中斷是已經(jīng)開始還是已經(jīng)結(jié)束。
typedef NS_ENUM(NSUInteger, AVAudioSessionInterruptionType) {
AVAudioSessionInterruptionTypeBegan = 1, ///< the system has interrupted your audio session
AVAudioSessionInterruptionTypeEnded = 0, ///< the interruption has ended
};
當(dāng)中斷出現(xiàn)時(shí),類型為
AVAudioSessionInterruptionTypeBegan,需要采取的動作就是暫停音頻播放以及 UI 界面的處理。當(dāng)中斷結(jié)束時(shí),類型為
AVAudioSessionInterruptionTypeEnded,userInfo中可能包含一個(gè)AVAudioSessionInterruptionOptions值,指示音頻會話是否以及重新激活以及它是否可以再次播放。如果選項(xiàng)值為AVAudioSessionInterruptionOptionShouldResume,則可以繼續(xù)播放。
響應(yīng)路由的變化
????當(dāng)應(yīng)用程序運(yùn)行時(shí),用戶可能會插入或拔出耳機(jī),或使用帶有音頻連接的擴(kuò)展塢。iOS 人機(jī)界面指南描述了應(yīng)用程序應(yīng)如何響應(yīng)此類事件。要實(shí)施這些建議,可以通過音頻會話處理音頻硬件路由的更改。
????音頻硬件路由是音頻信號的有線電子通路。當(dāng)設(shè)備的用戶插入或拔出耳機(jī)時(shí),系統(tǒng)會發(fā)生線路改變, AVAudioSession 會廣播一個(gè)描述該變化的通知AVAudioSessionRouteChangeNotification 給所有相關(guān)的監(jiān)聽者。下圖描述了錄入和播放期間各種路線變化的事件:

????如上圖所示,在應(yīng)用啟動后,系統(tǒng)會初步確定音頻路由。當(dāng)應(yīng)用程序運(yùn)行時(shí),它會繼續(xù)監(jiān)控活動路線。首先考慮用戶在應(yīng)用程序中點(diǎn)擊“錄制”按鈕的情況,由圖左側(cè)的“錄制開始”框表示。
????在錄制過程中,用戶可以插入或拔出耳機(jī),請參見圖中左下角的菱形決策元素。作為響應(yīng),系統(tǒng)發(fā)送包含更改原因和先前路由的路由更改通知,應(yīng)用應(yīng)停止錄制。
????播放的情況類似,但結(jié)果不同,如圖右側(cè)所示。如果用戶在播放過程中拔下耳機(jī),應(yīng)用應(yīng)暫停音頻。如果用戶在播放過程中插入耳機(jī),應(yīng)用應(yīng)該只允許繼續(xù)播放。那么應(yīng)該怎么做呢?
????首先需要注冊 AVAudioSession 發(fā)送的通知AVAudioSessionRouteChangeNotification,該通知包含一個(gè) userInfo 字典,攜帶了通知發(fā)送的原因及前一個(gè)路由的描述。
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleRouteChange:)
name:AVAudioSessionRouteChangeNotification
object:[AVAudioSession sharedInstance]];
????收到通知后查看保存在userInfo 字典中 AVAudioSessionRouteChangeReasonKey 判斷路由變更的原因:
-
AVAudioSessionRouteChangeReasonNewDeviceAvailable,連接新設(shè)備 -
AVAudioSessionRouteChangeReasonOldDeviceUnavailable,移除設(shè)備
????當(dāng)有設(shè)備斷開時(shí),獲取 userInfo 中描述前一個(gè)路由信息 的AVAudioSessionRouteChangePreviousRouteKey,其整合在一個(gè)輸入 NSArray 和一個(gè)輸出 NSArray 中,判斷其中第一個(gè)是否為 AVAudioSessionPortHeadphones (耳機(jī)接口)。
- (void)handleRouteChange:(NSNotification *)notification {
NSDictionary *info = notification.userInfo;
AVAudioSessionRouteChangeReason reason =
[info[AVAudioSessionRouteChangeReasonKey] unsignedIntValue];
if (reason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
AVAudioSessionRouteDescription *previousRoute =
info[AVAudioSessionRouteChangePreviousRouteKey];
AVAudioSessionPortDescription *previousOutput = previousRoute.outputs[0];
NSString *portType = previousOutput.portType;
if ([portType isEqualToString:AVAudioSessionPortHeadphones]) {
//暫停
}
}
}
開啟后臺播放
????音頻應(yīng)用程序所需的一項(xiàng)常見功能是后臺播放音頻。啟用此功能后,當(dāng)用戶切換到另一個(gè)應(yīng)用程序或鎖定他們的 iOS 設(shè)備時(shí),應(yīng)用程序的音頻可以繼續(xù)播放。在 iOS 中啟用 AirPlay 流式傳輸和畫中畫播放等高級播放功能也需要此功能。
????配置這些功能的最簡單方法是使用 Xcode。在 Xcode 中選擇應(yīng)用程序的目標(biāo),然后選擇 Capabilities 選項(xiàng)卡。在“功能”選項(xiàng)卡下,將“后臺模式”開關(guān)設(shè)置為“開”,然后從可用模式列表中選擇“音頻、AirPlay 和畫中畫”選項(xiàng)。
