Audio Session:系統與應用程序的中介

Overview

Apple經過audio sessions管理app, app與其餘app, app與外部音頻硬件間的行爲.使用audio session能夠向系統傳達你將如何使用音頻.audio session充當着app與系統間的中介.這樣咱們無需瞭解硬件相關卻能夠操控硬件行爲.html

1.Audio session

  • 配置audio session類別與模式去告訴系統在app中你想怎麼使用音頻
  • 激活audio session使配置的類別與模式能夠工做
  • 添加通知,響應重要的audio session通知,例如音頻中斷與硬件線路改變
  • 配置音頻採樣率,聲道數等信息

1.配置Audio Session

1.1. Audio Session管理Audio

audio session是應用程序與系統間的中介,用於配置音頻行爲,APP啓動時,會自動得到一個audio session的單例對象,配置而且激活它以讓音頻按照指望開始工做.數組

1.2. Categories表明Audio做用

audio session category表明音頻的主要行爲.經過設置類別, 能夠指明app是否使用的當前的輸入或輸出音頻設備,以及當別的app中正在播放音頻進入咱們app時他們的音頻是強制中止仍是與咱們的音頻一塊兒播放等等.瀏覽器

AVFoundation中定義了不少audio session categories, 你能夠根據須要自定義音頻行爲,不少類別支持播放,錄製,錄製與播放同時進行,當系統瞭解了你定義的音頻規則,它將提供給你合適的路徑去訪問硬件資源.系統也將確保別的app中的音頻以適合你應用的方式運行.bash

一些categories能夠根據Mode進一步定製,該模式用於專門指定類別的行爲,例如當使用視頻錄製模式時,系統可能會選擇一個不一樣於默認內置麥克風的麥克風,系統還能夠針對錄製調整麥克風的信號強度.服務器

1.3. 中斷處理

若是audio意外中斷,系統會將aduio session置爲停用狀態,音頻也會所以當即中止.當一個別的app的audio session被激活而且它的類別未設置與系統類別或你應用程序類別混合時,中斷就會發生.你的應用程序在收到中斷通知後應該保存當時的狀態,以及更新用戶界面等相關操做.經過註冊AVAudioSessionInterruptionNotification能夠觀察中斷的開始與結束點.session

1.4. 音頻線路改變

當用戶作出鏈接,斷開音頻輸入,輸出設備時,(如:插拔耳機)音頻線路發生變化,經過註冊AVAudioSessionRouteChangeNotification能夠在音頻線路發生變化時作出相應處理.app

1.5. Audio Sessions控制設備配置

App不能直接控制設備的硬件,可是audio session提供了一些接口去獲取或設置一些高級的音頻設置,如採樣率,聲道數等等.ide

1.6. Audio Sessions保護用戶隱私

App若是想使用音頻錄製功能必須請求用戶受權,不然沒法使用.測試

2. 激活Audio Session

在設置了audio session的category, options, mode後,咱們能夠激活它以啓動音頻.優化

2.1. 系統如何解決音頻競爭

隨着app的啓動,內置的一些服務(短信,音樂,瀏覽器,電話等)也將在後臺運行.前面的這些內置服務均可能產生音頻,若有電話打來,有短信提示等等...

2.2. 激活,停用Audio Session

雖然AVFoundation中播放與錄製能夠自動激活你的audio session, 但你能夠手動激活而且測試是否激活成功.

系統會停用你的audio session當有電話打進來,鬧鐘響了,或是日曆提醒等消息介入.當處理完這些介入的消息後,系統容許咱們手動從新激活audio sesseion.

let session = AVAudioSession.sharedInstance()
do {
    // 1) Configure your audio session category, options, and mode
    // 2) Activate your audio session to enable your custom configuration
    try session.setActive(true)
} catch let error as NSError {
    print("Unable to activate audio session: \(error.localizedDescription)")
}
複製代碼

若是咱們使用AVFoundation對象(AVPlayer, AVAudioRecorder等),系統負責在中斷結束時從新激活audio session.然而,若是你註冊了通知去從新激活audio session,你能夠驗證是否激活成功而且更新用戶界面.

  • 確保在後臺運行的VoIP應用程序的音頻會話僅在應用程序處理呼叫時才處於激活狀態。在後臺,若未收到呼叫,VoIP應用程序的音頻會話不該該是激活的。
  • 確保使用錄製類別的應用程序的音頻會話僅在錄製時處於激活狀態。在錄製開始和中止以前,請確保您的會話處於未激活狀態,以容許播放其餘聲音,例如系統聲音。
  • 若是應用程序支持後臺音頻播放或錄製,但在應用程序未主動使用音頻(或準備使用音頻)時,在進入後臺時停用其音頻會話。這樣作容許系統釋放音頻資源,以便其餘進程可使用它們。

2.3. 檢查別的Audio是否正在播放

當你的app被激活前,當前設備可能正在播放別的聲音,若是你的app是一個遊戲的app,知作別的聲音來源顯得十分重要,由於許多遊戲容許同時播放別的音樂以加強用戶體驗.

在app進入前臺前,咱們能夠經過applicationDidBecomeActive:代理方法在其中使用secondaryAudioShouldBeSilencedHint屬性來肯定音頻是否正在播放.當別的app正在播放的audio session爲不可混音配置時,該值爲true. app可使用此屬性消除次要音頻.

func setupNotifications() {
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(handleSecondaryAudio),
                                           name: .AVAudioSessionSilenceSecondaryAudioHint,
                                           object: AVAudioSession.sharedInstance())
}
 
func handleSecondaryAudio(notification: Notification) {
    // Determine hint type
    guard let userInfo = notification.userInfo,
        let typeValue = userInfo[AVAudioSessionSilenceSecondaryAudioHintTypeKey] as? UInt,
        let type = AVAudioSessionSilenceSecondaryAudioHintType(rawValue: typeValue) else {
            return
    }
 
    if type == .begin {
        // Other app audio started playing - mute secondary audio
    } else {
        // Other app audio stopped playing - restart secondary audio
    }
}
複製代碼

3. 響應中斷

在app中斷後能夠經過代碼作出響應.音頻中斷將會致使audio session停用,同時應用程序中音頻當即終止.當一個來自其餘app的競爭的audio session被激活且這個audio session類別不支持與你的app進行混音時,中斷髮生.註冊通知後咱們能夠在得知音頻中斷後作出相應處理.

App會由於中斷被暫停,當用戶接到電話時,鬧鐘,或其餘系統事件被觸發時,當中斷結束後,App會繼續運行,可是須要咱們手動從新激活audio session.

3.1. 中斷的生命週期

下圖簡單展現了當收到facetime後app的audio session與系統的audio session間激活與未激活狀態變化.

2.interruput_lifecycle

3.2. 中斷處理方法

經過註冊監聽中斷的通知能夠在中斷來的時候進行處理.處理中斷取決於你當前正在執行的操做:播放,錄製,音頻格式轉換,讀取音頻數據包等等.通常而言,咱們應儘可能避免中斷而且作到中斷後儘快恢復.

中斷前

  • 保存狀態與上下文
  • 更新用戶界面

中斷後

  • 恢復狀態與上下文
  • 更新用戶界面
  • 從新激活audio session.
Audio technology How interruptions work
AVFoundation framework 系統在中斷時會自動暫停錄製與播放,當中斷結束後從新激活audio session,恢復錄製與播放
Audio Queue Services, I/O audio unit 系統會發出中斷通知,開發者能夠保存播放與錄製狀態而且在中斷結束後從新激活audio session
System Sound Services 使用系統聲音服務在中斷來臨時保持靜音,若是中斷結束,聲音自動播放.

3.3. 處理Siri

當處理Siri時,與其餘中斷不一樣,咱們在中斷期間須要對Siri進行監聽,如在中斷期間,用戶要求Siri去暫停開發者app中的音頻播放,當app收到中斷結束的通知時,不該該自動恢復播放.同時,用戶界面須要跟Siri要求的保持一致.

3.4. 監聽中斷

註冊AVAudioSessionInterruptionNotification通知能夠監聽中斷.

func registerForNotifications() {
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(handleInterruption),
                                           name: .AVAudioSessionInterruption,
                                           object: AVAudioSession.sharedInstance())
}
 
func handleInterruption(_ notification: Notification) {
    // Handle interruption
}

func handleInterruption(_ notification: Notification) {
    guard let info = notification.userInfo,
        let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
        let type = AVAudioSessionInterruptionType(rawValue: typeValue) else {
            return
    }
    if type == .began {
        // Interruption began, take appropriate actions (save state, update user interface)
    }
    else if type == .ended {
        guard let optionsValue =
            userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else {
                return
        }
        let options = AVAudioSessionInterruptionOptions(rawValue: optionsValue)
        if options.contains(.shouldResume) {
            // Interruption Ended - playback should resume
        }
    }
}

複製代碼

注意: 沒法確保在開始中斷後必定有一個結束中斷,因此,若是沒有結束中斷,咱們在app從新播放音頻時須要老是檢查aduio session是否被激活.

3.5. 響應媒體服務器重置操做

media server經過一個共享服務器進程提供了音頻和其餘多媒體功能.儘管不多見,可是若是在你的app正在運行時收到一條重置命令,能夠經過註冊AVAudioSessionMediaServicesWereResetNotification通知監聽media server是否重置.收到通知後須要作以下操做.

  • 銷燬音頻對象而且建立新的音頻對象(如:players,recorders,converters,audio queues)
  • 重置全部audio狀態,包括AVAudioSession所有屬性
  • 在合適時機從新激活AVAudioSession對象.

註冊AVAudioSessionMediaServicesWereLostNotification能夠在media server不可用時收到通知.

若是開發者的應用程序中須要重置功能,如設置中有重置選項,可使用這個方法輕鬆重置.

4. 線路改變

audio hardware route指定的設備音頻硬件線路發生改變.當用戶插拔耳機,系統會自動改變硬件的線路.開發者能夠註冊AVAudioSessionRouteChangeNotification通知在線路變化時做出相應調整.

3.audio_route_change

如上圖,系統在app啓動時會肯定一套音頻線路,然後程序運行期間會繼續監聽當前活躍的音頻線路,在錄製期間,用戶可能插拔耳機,系統會發送一份改變線路的通知告訴開發者同時音頻中止,開發者能夠經過代碼決定是否從新激活.

播放與錄製稍有不一樣,播放時若是用戶拔掉耳機,默認暫停音頻,若是插上耳機,默認繼續播放.

4.1. 監聽Audio線路變化

緣由

  • 插拔耳機
  • 鏈接,斷開藍牙耳機
  • 插拔USB音頻設備
func setupNotifications() {
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(handleRouteChange),
                                           name: .AVAudioSessionRouteChange,
                                           object: AVAudioSession.sharedInstance())
}
 
func handleRouteChange(_ notification: Notification) {
 
}
複製代碼

userInfo中提供了關於線路改變的詳細信息.能夠查詢改變緣由經過字典中的AVAudioSessionRouteChangeReason,如當新的設備接入時,緣由爲AVAudioSessionRouteChangeReason,移除時爲AVAudioSessionRouteChangeReasonOldDeviceUnavailable

func handleRouteChange(_ notification: Notification) {
    guard let userInfo = notification.userInfo,
        let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
        let reason = AVAudioSessionRouteChangeReason(rawValue:reasonValue) else {
            return
    }
    switch reason {
    case .newDeviceAvailable:
        // Handle new device available.
    case .oldDeviceUnavailable:
        // Handle old device removed.
    default: ()
    }
}

複製代碼

當有音頻硬件插入時,你能夠查詢audio session的currentRoute屬性去肯定當前音頻輸出的位置.它將返回一個AVAudioSessionRouteDescription對象包含audio session所有的輸入輸出信息.當一個音頻硬件被移除時,咱們也能夠從該對象中查詢上一個線路.在以上兩種狀況中,咱們均可以查詢outputs屬性,經過返回的AVAudioSessionPortDescription對象提供了音頻輸出的所有信息.

func handleRouteChange(notification: NSNotification) {
    guard let userInfo = notification.userInfo,
        let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
        let reason = AVAudioSessionRouteChangeReason(rawValue:reasonValue) else {
            return
    }
    switch reason {
    case .newDeviceAvailable:
        let session = AVAudioSession.sharedInstance()
        for output in session.currentRoute.outputs where output.portType == AVAudioSessionPortHeadphones {
            headphonesConnected = true
        }
    case .oldDeviceUnavailable:
        if let previousRoute =
            userInfo[AVAudioSessionRouteChangePreviousRouteKey] as? AVAudioSessionRouteDescription {
            for output in previousRoute.outputs where output.portType == AVAudioSessionPortHeadphones {
                headphonesConnected = false
            }
        }
    default: ()
    }
}
複製代碼

5. 配置設備硬件

使用audio session屬性,能夠在運行時優化硬件音頻行爲.這樣可讓代碼適配運行設備的特性.這樣作一樣適用於用戶對音頻硬件做出的更改.

5.1. 配置初始音頻參數

使用audio session指定音頻設備的設置,如採樣率, I/O緩衝區時間.

Setting Preferred sample rate Preferred I/O buffer duration
High value Example: 48 kHz, + High audio quality, – Large file or buffer size Example: 500 mS, + Less-frequent file access, – Longer latency
Low value Example: 8 kHz, + Small file or buffer size, – Low audio quality Example: 5 mS,+ Low latency, – Frequent file access

Note: 默認音頻輸入輸出緩衝時間(I/O buffer duration)爲大多數應用提供足夠的相應時間,如44.1kHz音頻大概爲20ms響應一次,你能夠設置更低的延遲但相應數據量每次過來的也會下降,根據本身的需求進行選擇.

5.2. 設置

在激活audio session前必須完成設置內容.若是你正在運行audio session, 先停用它,而後改變設置從新激活.

let session = AVAudioSession.sharedInstance()
 
// Configure category and mode
do {
    try session.setCategory(AVAudioSessionCategoryRecord, mode: AVAudioSessionModeDefault)
} catch let error as NSError {
    print("Unable to set category: \(error.localizedDescription)")
}
 
// Set preferred sample rate
do {
    try session.setPreferredSampleRate(44_100)
} catch let error as NSError {
    print("Unable to set preferred sample rate: \(error.localizedDescription)")
}
 
// Set preferred I/O buffer duration
do {
    try session.setPreferredIOBufferDuration(0.005)
} catch let error as NSError {
    print("Unable to set preferred I/O buffer duration: \(error.localizedDescription)")
}
 
// Activate the audio session
do {
    try session.setActive(true)
} catch let error as NSError {
    print("Unable to activate session. \(error.localizedDescription)")
}
 
// Query the audio session's ioBufferDuration and sampleRate properties // to determine if the preferred values were set print("Audio Session ioBufferDuration: \(session.ioBufferDuration), sampleRate: \(session.sampleRate)") 複製代碼

5.3. 選擇,配置麥克風

一個設備可能有多個麥克風(內置,外接),iOS會根據當前使用的audio session mode自動選擇一個.mode指定了輸入數字信號處理(DSP)和可能的線路.輸入線路針對每種模式的用例進行了優化,設置mode還可能影響正在使用的音頻線路.

開發者能夠手動選擇麥克風,甚至能夠設置polar pattern若是硬件支持.

在使用任何音頻設備以前,請爲您的應用設置音頻會話類別和模式,而後激活音頻會話。

  • 設置Preferred Input

爲了找到當前設備鏈接的音頻輸入設備,可使用audio session的availableInputs屬性,該屬性返回一個AVAudioSessionPortDescription對象的數組,描述當前可用輸入設備端口,端口用portType進行標識.可使用setPreferredInput:error:設置可用的音頻輸入設備.

  • 設置Preferred Data Source

部分端口如內置麥克風,USB等支持數據源(data source),應用程序能夠經過查詢端口的dataSources屬性發現可用的數據源.對於內置麥克風,返回的數據源描述對象表明每一個單獨的麥克風。不一樣的設備爲內置麥克風返回不一樣的值。例如,iPhone 4和iPhone 4S有兩個麥克風:底部和頂部。 iPhone 5有三個麥克風:底部,前部和後部。

能夠經過數據源描述的location屬性(上,下)和orientation屬性(前,後等)的組合來識別各個內置麥克風。應用程序可使用AVAudioSessionPortDescription對象的setPreferredDataSource:error:方法設置首選數據源。

  • 設置 Preferred Polar Pattern

某些iOS設備支持爲某些內置麥克風配置麥克風極性模式。麥克風的極性模式定義了其對聲音相對於聲源方向的靈敏度。使用supportedPolarPatterns屬性返回數據源是否支持此模式,此屬性返回數據源支持的極座標模式數組(如心形或全向),或者在沒有可選模式時返回nil。若是數據源具備許多支持的極座標模式,則可使用數據源描述的setPreferredPolarPattern:error:方法設置首選極座標模式。

  • 選擇特定麥克風而且設置polar pattern.
// Preferred Mic = Front, Preferred Polar Pattern = Cardioid
let preferredMicOrientation = AVAudioSessionOrientationFront
let preferredPolarPattern = AVAudioSessionPolarPatternCardioid
 
// Retrieve your configured and activated audio session
let session = AVAudioSession.sharedInstance()
 
// Get available inputs
guard let inputs = session.availableInputs else { return }
 
// Find built-in mic
guard let builtInMic = inputs.first(where: {
    $0.portType == AVAudioSessionPortBuiltInMic
}) else { return }
 
// Find the data source at the specified orientation
guard let dataSource = builtInMic.dataSources?.first (where: {
    $0.orientation == preferredMicOrientation
}) else { return }
 
// Set data source's polar pattern do { try dataSource.setPreferredPolarPattern(preferredPolarPattern) } catch let error as NSError { print("Unable to preferred polar pattern: \(error.localizedDescription)") } // Set the data source as the input's preferred data source
do {
    try builtInMic.setPreferredDataSource(dataSource)
} catch let error as NSError {
    print("Unable to preferred dataSource: \(error.localizedDescription)")
}
 
// Set the built-in mic as the preferred input
// This call will be a no-op if already selected
do {
    try session.setPreferredInput(builtInMic)
} catch let error as NSError {
    print("Unable to preferred input: \(error.localizedDescription)")
}
 
// Print Active Configuration
session.currentRoute.inputs.forEach { portDesc in
    print("Port: \(portDesc.portType)")
    if let ds = portDesc.selectedDataSource {
        print("Name: \(ds.dataSourceName)")
        print("Polar Pattern: \(ds.selectedPolarPattern ?? "[none]")")
    }
}
Running this code on an iPhone 6s produces the following console output:

Port: MicrophoneBuiltIn
Name: Front
Polar Pattern: Cardioid

複製代碼

5.4. 模擬器運行

能夠在模擬器或設備上運行您的應用。可是,Simulator不會模擬不一樣進程或音頻線路更改中的音頻會話之間的大多數交互。在Simulator中運行應用程序時,您不能:

  • 調用中斷
  • 模擬插入或拔出耳機
  • 更改靜音開關的設置
  • 模擬屏幕鎖定
  • 測試音頻混合行爲 - 即播放音頻以及來自其餘應用(例如音樂應用)的音頻
#if arch(i386) || arch(x86_64)
    // Execute subset of code that works in the Simulator
#else
    // Execute device-only code as well as the other code
#endif
複製代碼

保護用戶隱私

爲了保護用戶隱私,應用必須在錄製音頻以前詢問並得到用戶的許可。若是用戶未授予許可,則僅記錄靜音。當您使用支持錄製的類別而且應用程序嘗試使用輸入線路時,系統會自動提示用戶得到權限。

您可使用requestRecordPermission:方法手動請求權限,而不是等待系統提示用戶提供記錄權限。使用此方法可讓您的應用得到權限,而不會中斷應用的天然流動,從而得到更好的用戶體驗。

AVAudioSession.sharedInstance().requestRecordPermission { granted in
    if granted {
        // User granted access. Present recording interface.
    } else {
        // Present message to user indicating that recording
        // can't be performed until they change their preference // under Settings -> Privacy -> Microphone } } 複製代碼

從iOS 10開始,全部訪問任何設備麥克風的應用都必須靜態聲明其意圖。爲此,應用程序如今必須在其Info.plist文件中包含NSMicrophoneUsageDescription鍵,併爲此密鑰提供目的字符串。當系統提示用戶容許訪問時,此字符串將顯示爲警報的一部分。若是應用程序嘗試訪問任何設備的麥克風而沒有此鍵和值,則應用程序將終止。


Apple 官方文檔

相關文章
相關標籤/搜索