##定義一個音頻會話
**音頻會話**是 app 和 IOS 之間的媒介,用來為 app 配置相關的音頻屬性和行為。在加載過程中,app 會自動創建一個音頻會話的單例。開發者可以通過配置音頻會話來描述 app 對音頻的需求。比如:
* 在 app 播放聲音的時候,開發者是想讓其他 app 的聲音停止還是和自己的聲音混合在一起?
* 當碰到系統鬧鐘或其他響起的時候,app 中的音頻功能會作出什么反應?
* 當用戶插拔耳機時, app 中的音頻功能會作出什么反應?
音頻會話的配置會影響 app 運行期間幾乎所有的音頻活動(除了通過系統聲音服務 API 播放的 UI 音效)。開發者可以通過查詢音頻會話來獲取 app 運行設備上的硬件特性(頻道數、采樣率等)。這些硬件特性因設備而異, app 可以根據用戶行為改變這些特性。
開發者可以顯式得讓自己的音頻會話處于激活或非激活狀態。在播放app聲音或開始錄音之前,開發者需要激活音頻會話。另外,系統可以在接到電話或鬧鐘響起時中止app音頻會話的激活狀態,這種中止被稱為***中斷(interruption)***。音頻會話的 API 中提供了對中斷進行響應和恢復的方法。
###音頻會話的默認行為
音頻會話具有如下的默認行為:
* 支持后臺播放,不支持錄音
* 當用戶開始靜音模式后,app 的音頻會被靜音。
* 設備鎖屏后,app 的音頻會被靜音。
* 當 app 的音頻開始后,設備正在播放的其他聲音會被靜音。
上述行為由默認音頻會話類別 <code>AVAudioSessionCategorySoloAmbient</code> 提供。[音頻會話類別](326381) 介紹了如何在app內使用類別。
盡管音頻會話會在 app 開始播放或錄制音頻時自動激活,但這種默認的激活方式會帶來風險。舉例來說,如果用戶使用 app 過程中有電話打入,而用戶選擇了忽略電話讓 app 繼續運行,如果沒有使用合理的后臺播放技術,那app的音頻將不會再播放。下一章描述了一些處理這類問題的策略,[處理中斷](326382) 中有進一步的討論。
開發者可以在開發過程中使用這種默認行為來提高開發效率。如果要發布 app ,那么忽略音頻會話只在下面這些場景下是安全的:
* app 只使用系統聲音服務(<code>System Sound Services</code>)或 <code>UIKit</code> 中的 <code>playInputClick</code> 方法處理音頻,不使用其他音頻API。
系統聲音服務是一種用來播放UI音效及觸發震動的IOS技術,不適用于其他任何場景。(Link)
UIKit 中的 <code>playInputClick</code> 方法允許開發者在特定的輸入或鍵盤輔助視圖(accessory view)中播放標準的鍵盤按鍵音。它的音頻會話行為由系統自動處理。(Link)
* app 不使用音頻
> 如果不滿足上述條件,一定不要在需要發布的app中使用默認的音頻會話。
###為什么通常情況下默認的音頻會話不能滿足開發者的要求
如果開發者不對音頻會話進行初始化、配置和顯式調用,那么 app 就不用對中斷或音頻源的變化作出響應。而且 app 也不能控制系統如何處理不同 app 間音頻的混合。
以下場景描述了音頻會話的默認行為,以及開發者如何來改變它:
1. 開發者開發了一款播放有聲書的 app。用戶開始聽《 The Merchant of Venice》,正當 Bassanio 大人要出場的時候,自動鎖屏的時間到了,屏幕變黑了,有聲書的音頻也被靜音了。
為了避免鎖屏靜音這種情況,開發者需為音頻會話配置一個支持后臺播放的類別,同時要在 <code>UIBackgoundModes</code> 中配置 audio 項(補充)。
2. 開發者開發了一款使用基于 OpenAL 音效的第一視角射擊類游戲。游戲中提供背景音樂,但也為用戶提供了關閉游戲背景音樂,并播發音樂庫中的音樂的功能。在選擇一曲激昂的音樂開始播放后,用戶朝著敵軍開了一槍,槍響了,用戶播放的音樂卻停了。
為了保證用戶選擇的音樂能不被干擾的繼續播放,需要將音頻會話設置為允許混合的模式。開發者可以選擇 <code>AVAudioSessionCategoryAmbient</code>類別 ,也可以通過修改 <code>AVAudioSessionCategoryPlayback</code> 類別來支持混合。
3. 開發者開發了一款使用音頻隊列服務(<code>Audio Queue Services</code>)進行后臺播放的流媒體電臺 app。正當用戶在收聽的時候,電話來了,app 的聲音按照期望中的那樣停止了。用戶選擇了拒接這個電話,并且關閉了鬧鐘。用戶點擊播放按鈕來繼續收聽,卻發現未能如愿。用戶必須重啟 app 才能恢復后臺播放。
要優雅的處理這種音頻隊列的中斷,開發者需要設置合適的類別,注冊 <code>AVAudioSessionInterruptionNotification</code> 通知,并讓 app 對不同的通知作出相應的反應。
###系統怎樣解決音頻需求之間的競爭
在 app 啟動時,系統的內置 app(短信、音樂、Safari、電話等)可能會在后臺運行。這些內置的 app 可能會產生聲音,比如收到短信后。
如果將 IOS 設備看作一個飛機場,把app看作滑行的飛機,那么系統就是調度中心。飛機(app)向調度中心(系統)發布一個使用音頻的請求,同時表明它需要的優先級,而最終何時獲得超過“正在跑道上”使用音頻的其他飛機(其他 app)的權限由調度中心(系統)決定。app 通過音頻會話來跟系統進行交互。下圖描述了一個典型的場景:你的 app 想要在音樂 app 正在播放音樂的時候使用音頻。這種情況下,你的 app 會中斷音樂 app。
<center>

</center>
<small>
第一步,app 請求激活它的音頻會話。這種請求可能會在 app 加載過程中產生,也可能會在響應用戶點擊某個播放按鈕的事件中產生。第二步,系統開始處理這次激活請求。圖中的 SpeakHere app 使用了需要其他音頻靜音的類別。
在第三和第四步中,系統結束了音樂 app 的音頻會話的激活狀態,停止了音樂在后臺的播放。最終,系統激活了 SpeakHere 的音頻會話,SpeakHere 可以開始它的音頻行為了。
系統對于是否激活或停止任何設備上音頻會話有最終的決定權。決策過程中,電話總是擁有最高的優先權,沒有 app 的音頻權限能夠超過電話。接到電話后,無論當前正在執行什么音頻動作或是設置了哪種音頻類別,app 都會被中斷,用戶都會獲得“你接到了電話”的通知。
</small>
###集成 <code>AVCaptureSession</code>
<code>AV Foudation</code> 中的捕獲 API(<code>AVCaptureDevice</code>、<code>AVCaptureSession</code>)可以使開發者得到同步獲取來自相機或麥克風的音頻或視頻輸入。在 IOS7 中,表示麥克風輸入的 <code>AVCaptureDevice</code> 對象可以共享 app 的 <code>AVAudioSession</code>。默認情況下,<code>AVCaptureSession</code> 會在使用麥克風的時候會給 <code>AVAudioSession</code> 設置最適合錄音的配置。如果將 <code>automaticallyConfiguresApplicationAudioSession</code> 屬性設為 NO,這種默認配置會被當前開發者的AVAudioSession配置覆蓋,<code>AVCaptureDevice</code> 也會不加修改的采用開發者的配置。在 [AVCaptureSession Class Reference](https://developer.apple.com/reference/avfoundation/avcapturesession) 和 [Media Capture](https://developer.apple.com/reference/avfoundation/avcapturesession) 中可以獲得更多相關信息。
###初始化音頻會話
系統在 app 的加載過程中提供了一個音頻會話的對象。在處理中斷之前,開發者必須初始化這個會話。
<code>AV Foundation</code> 框架會利用開發者獲取對 <code>AVAudioSession</code> 對象的引用時觸發的隱式初始化,來管理中斷。
~~~
//隱式初始化音頻會話
AVAudioSession *session = [AVAudioSession sharedInstance];
~~~
<code>session</code> 變量代表了一個已經被初始化且可以馬上使用的音頻會話。官方推薦在使用 <code>AVAudioSession</code> 類中的中斷通知,或 <code>AVAudioPlayer</code> 和 <code>AVAudioRecorder</code> 的代理協議來處理音頻中斷時,隱式的初始化音頻會話。
###添加音量和音頻源管理
<code>MPVolumeView</code> 類提供了在 app 中控制音量和音頻源的方法。音量視圖提供了一個控制音量的滑塊和一個選擇音頻輸出源的按鈕。官方建議在將音頻源切換到內置揚聲器時,使用 <code>MPVolumeView</code> 的音頻源選擇器(route picker)而不是 <code>AVAudioSessionPortOverride</code>。詳情見 [MPVolumeView Class Reference](https://developer.apple.com/reference/mediaplayer/mpvolumeview)。
###響應遙控器事件
用戶可以通過遙控器事件來控制 app 中的多媒體。開發者可能希望 app 中播放的音頻或視頻內容對來自 transport controls 或外接輔助設備的遙控事件做出響應。IOS 將這些命令轉化為 <code>UIEvent</code> 對象分發給 app。app 接收到事件后將它們發送給對應的 first responder。如果 first responder 不對事件進行處理,那么事件將會在 responder 鏈中向上傳遞。
只有正在播放音頻、且具有“Now Playing”信息的 app,才能對遙控器事件做出響應。詳情見 [Remote Control Events](https://developer.apple.com/library/content/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/CreatingDependenciesBetweenGestureRecognizers.html#//apple_ref/doc/uid/TP40009541-CH7)和[MPNowPlayingInfoCenter Class Reference](https://developer.apple.com/reference/mediaplayer/mpnowplayinginfocenter)。
###激活或關閉音頻會話
雖然系統在 app 加載的時候自動激活你的音頻會話,但蘋果官方推薦的做法是在 app 的 <code>viewDidLoad</code> 方法中進行顯式激活,并保證激活前設置合適的硬件參數。[為 app 進行硬件優化](https://developer.apple.com/library/content/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/OptimizingForDeviceHardware/OptimizingForDeviceHardware.html#//apple_ref/doc/uid/TP40007875-CH6-SW9) 中有相關的示例代碼。通過這種方式,開發者可以測試激活是否成功。如果 app 中包含一個類似播放/暫停的 UI 元素,在用戶按下播放鍵時激活會話則是更好的方式。在切換音頻會話的激活/非激活狀態時,對是否成功的切換了會話狀態做出檢查是有必要的。開發者應在代碼中對系統駁回的激活請求進行優雅的處理。
系統會在鬧鐘提醒、日歷提醒或接到電話時將關閉你的音頻會話的激活狀態。當用戶關閉提醒或拒接電話后,系統會允許重新激活你的會話。在中斷結束后是否重新激活音頻會話由 app 的類型決定,[Audio Guidelines By App Type](326386) 中有相關的介紹。
下面的代碼展示了如何激活音頻會話。
~~~
NSError *activationError = nil;
BOOL success = [[AVAudioSession sharedInstance] setActive: YES error: &activationError];
if (!success) { /* handle the error in activationError */ }
~~~
將 <code>setActive</code> 的參數設置為 <code>NO</code> 可以關閉會話的激活狀態。
如果使用 <code>AVAudioPlayer</code> 或 <code>AVAudioRecorder</code> 來播放或錄制音頻時,系統會負責在中斷結束時對會話進行重新激活。然而,官方推薦通過注冊消息通知來顯式的進行重新激活,這樣開發者才能保證激活成功,并且對 app 的狀態和 UI 進行更新。
大多數 app 不需要顯式的關閉音頻會話的激活狀態。一些需要顯式關閉的特例包括 VoIP(網絡電話)app、逐向(在轉彎時對用戶做出提醒)導航 app 和某些錄音 app。
對于一般在后臺運行網絡電話 app,要保證它的音頻會話在處理通話時是激活的,而在后臺準備接收通話時則處于非激活狀態。
對于使用錄音類別的 app 的音頻會話僅在錄音時激活。在錄音開始前和錄音結束后要關閉激活狀態以保證類似短信提示音的其他聲音能夠順利播放。
### App加載時檢查是否存在正在播放的其他音頻
在用戶打開 app 時,設備可能正在播放其他的聲音:音樂 app 可能正在播放音樂,或者瀏覽器正在播放流媒體。這種情況產生的影響對于游戲 app 來說可能更為顯著。許多游戲會有自己的背景音樂和音效。[IOS Human Interface Guidelines](https://developer.apple.com/ios/human-interface-guidelines/overview/design-principles/) 建議開發者假定用戶在玩游戲時希望保持他們原來播放的音頻作為背景音樂,同時保留游戲的音效。
檢查 <code>otherAudioPlaying</code> 的屬性值來判斷 app 加載過程中是否正在播放其他音頻;如果是的話,將游戲的背景音樂靜音,并使用 <code>AVAudioSessionCategorySoloAmbient</code> 類別。詳情見 [音頻會話類別](326381)。
###跨 app 音頻(Inter-App Audio)
跨 app 音頻的最基礎的使用形式是通過一個節點(node) app 將它的音頻輸出到另一個寄主(host) app。寄主 app 可能會將它的輸出發送給節點 app,經過節點 app 的處理后,將處理結果反饋給寄主 app。寄主 app 需要一個始終處于激活狀態的音頻會話,而節點 app 只需要在從寄主 app 或系統接收音頻輸入時激活音頻會話。根據以下方案來設置跨 app 的音頻:
* 為寄主 app 和節點 app 設置“inter-app-audio”權限
* 為寄主 app 設置 <code>UIBackgoundModes</code> 的 <code>audio</code> 標志。
* 為使用音頻輸入輸出源、同時連接到跨 app 音頻寄主的節點 app 設置 <code>UIBackgoundModes</code> 的 <code>audio</code> 標志。
* 將寄主和節點 app 的類別設為 <code> AVAudioSessionCategoryOptionMixWithOthers</code>。
* 對于連接到寄主的節點 app,保證它的音頻會話在收到系統的音頻輸入或產生音頻輸出時處于激活狀態。
>以上內容翻譯自[蘋果官方文檔](https://developer.apple.com/library/content/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/ConfiguringanAudioSession/ConfiguringanAudioSession.html),僅供學習,請勿用于商業用途,侵刪。轉載注明出處。