AVAudioSession配置研究

官方針對AVAudioSession的解釋

參考Apple官方文檔, AudioSession 用來描述APP的音頻行為, 大概有以下幾類與之對應(yīng)的 Category 是闡釋其音頻行為的關(guān)鍵信息, 比如:

  1. 系統(tǒng)說明你的app使用音頻的模式(比如是播放還是錄音,是否支持藍(lán)牙播放姥宝,是否支持后臺播放)
  2. 為你的app選擇音頻的輸入輸出設(shè)備(比如輸入用的麥克風(fēng)健民,輸出是耳機(jī)、手機(jī)功放或者airplay)
  3. 協(xié)助管理多個音源需要播放時的行為(例如同時使用多個音樂播放app惶翻,或者突然有電話接入)

AVAudioSession的Category

Category 手機(jī)靜音開關(guān) or 鎖屏?xí)r是否靜音 中斷其他APP的音頻(后臺音樂) 允許錄音 和 播放
AVAudioSessionCategoryAmbient Yes No Output only
AVAudioSessionCategorySoloAmbient(默認(rèn)) Yes Yes Output only
AVAudioSessionCategoryPlayback No Yes Output only
AVAudioSessionCategoryRecord No(音頻錄制繼續(xù)) Yes Input only
AVAudioSessionCategoryPlayAndRecord No Yes Input and output
AVAudioSessionCategoryMultiRoute No Yes Input and output

簡單說明:

  1. AVAudioSessionCategoryAmbient:只支持播放闹瞧,可以和其他的音樂播放器一起播放宣谈。同時尔破,當(dāng)用戶鎖屏或者靜音時也會隨著靜音.
  2. AVAudioSessionCategorySoloAmbient: 也是只用于播放,但是會關(guān)閉其他的音樂播放器的播放娱两,同時莺匠,當(dāng)用戶鎖屏或者靜音時也會隨著靜音。
  3. AVAudioSessionCategoryPlayback: 只支持播放十兢,當(dāng)鎖屏?xí)r不會靜音趣竣,此category也會關(guān)閉其他的播放器的播放;
  4. AVAudioSessionCategoryRecord: 只支持錄音旱物,此category會關(guān)閉其他的播放器遥缕,當(dāng)鎖屏?xí)r不會靜音。
  5. AVAudioSessionCategoryPlayAndRecord:即支持播放也支持錄音宵呛,比如VoIP单匣,打電話這種場景。
  6. AVAudioSessionCategoryMultiRoute:即支持播放也支持錄音,當(dāng)鎖屏?xí)r不會靜音户秤。這個類別可以支持多個設(shè)備輸入輸出, 比如耳機(jī), USB設(shè)備同時音頻輸出
  7. AVAudioSessionCategoryAudioProcessing: 主要用于音頻格式處理码秉,一般可以配合AudioUnit進(jìn)行使用。

AVAudioSession的選項Options

在前面配置Category時, 我們可以增加Options來簡單調(diào)整! 注意Options與Category類型相關(guān), 在配置前, 先判斷 Options是否匹配Category, 可以使用[AVAudioSession sharedInstance].availableCategories來查看當(dāng)前Category支持的Options,常規(guī)的選項.

  1. AVAudioSessionCategoryOptionMixWithOthers, 是否可以和其他后臺App進(jìn)行混音. 簡單說就是如果后臺有音樂APP, 是否和他一起播放
  2. AVAudioSessionCategoryOptionDuckOthers, 是否壓低其他App聲音. 我們常見的導(dǎo)航APP在語音播報時, 就會降低其他的音樂APP的聲音, 在播報完成以后, 音樂APP聲音恢復(fù)正常.
  3. AVAudioSessionCategoryOptionAllowBluetooth, 如果要支持藍(lán)牙耳機(jī)虎忌,則需要設(shè)置這個選項
  4. AVAudioSessionCategoryOptionDefaultToSpeaker, 是否默認(rèn)用揚(yáng)聲器聲音. (在Category是 PlayAndRecord時, 系統(tǒng)的默認(rèn)音頻輸出使用的是聽筒), 如果在VoIP模式下泡徙,希望默認(rèn)打開免提功能,需要設(shè)置這個選項

目前主要的選項有這幾種膜蠢,都有對應(yīng)的使用場景堪藐,除此之外,在iOS9還提供了AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers

iOS10又新加了兩個AVAudioSessionCategoryOptionAllowBluetoothA2DP 挑围、AVAudioSessionCategoryOptionAllowAirPlay用來支持藍(lán)牙A2DP耳機(jī)和AirPlay

配置選項常見的API:

(BOOL)setCategory:(NSString *)category withOptions:(AVAudioSessionCategoryOptions)options error:(NSError **)outError;

不過這個過程礁竞,感覺缺少一個setOption的接口,既然已經(jīng)是當(dāng)前處于的Category杉辙,干嘛還要再設(shè)置選項的時候再指定Category呢模捂?

AVAudioSession的Mode

通過Category和Options的微調(diào), 基本覆蓋了主要的場景, 在上面的邏輯還需要針對不同的子場景進(jìn)行調(diào)整, 就需要配置Mode, 具體7個子mode如下:

  1. AVAudioSessionModeDefault: 適用于所有的類別,
  2. AVAudioSessionModeVoiceChat:適用與AVAudioSessionCategoryPlayAndRecord蜘矢,主要用于VoIP場景狂男,此時系統(tǒng)會選擇最佳的輸入設(shè)備,比如插上耳機(jī)就使用耳機(jī)上的麥克風(fēng)進(jìn)行采集品腹。此時有個副作用岖食,他會設(shè)置類別的選項為"AVAudioSessionCategoryOptionAllowBluetooth"從而支持藍(lán)牙耳機(jī)。
  3. AVAudioSessionModeVideoChat:適用與AVAudioSessionCategoryPlayAndRecord舞吭, 主要用于視頻通話泡垃,系統(tǒng)也會選擇最佳的輸入設(shè)備,比如插上耳機(jī)就使用耳機(jī)上的麥克風(fēng)進(jìn)行采集并且會設(shè)置類別的選項為"AVAudioSessionCategoryOptionAllowBluetooth" 和 "AVAudioSessionCategoryOptionDefaultToSpeaker"羡鸥。
  4. AVAudioSessionModeGameChat:適用與AVAudioSessionCategoryPlayAndRecord蔑穴,適用于游戲App的采集和播放,比如“GKVoiceChat”對象惧浴,一般不需要手動設(shè)置存和。
  5. AVAudioSessionModeVideoRecording:適用與AVAudioSessionCategoryPlayAndRecord和AVAudioSessionCategoryRecord,主要用于錄制視頻衷旅。
  6. AVAudioSessionModeMoviePlayback:適用與AVAudioSessionCategoryPlayAndRecord哑姚,主要用于視頻播放;
  7. AVAudioSessionModeMeasurement:適用與AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryRecord AVAudioSessionCategoryPlayback芜茵,主要用于最小系統(tǒng)

另外幾種和音頻APP關(guān)系不大叙量,一般我們只需要關(guān)注VoIP或者視頻通話即可.

我們一般都是在設(shè)置Category以后再才設(shè)置 Mode:

(BOOL)setMode:(NSString *)mode error:(NSError **)outError;

AVAudioSession的一些屬性

注意app中的AVAudioSession通常使用方法[AVAudioSession sharedInstance] 使用一個單例對象去配置, 我們在啟動時, 可以獲取它的內(nèi)部的一些屬性:

    NSLog(@"availableCategories: %@", [[AVAudioSession sharedInstance] availableCategories]);
    NSLog(@"availableModes: %@", [[AVAudioSession sharedInstance] availableModes]);
    NSLog(@"availableInputs: %@", [[AVAudioSession sharedInstance] availableInputs]);
    NSLog(@"outputDataSources: %@", [[AVAudioSession sharedInstance] outputDataSources]);
    NSLog(@"inputDataSources: %@", [[AVAudioSession sharedInstance] inputDataSources]);

    NSLog(@"default category: %@", [[AVAudioSession sharedInstance] category]);
    NSLog(@"default mode: %@", [[AVAudioSession sharedInstance] mode]);
    NSLog(@"default categoryOptions: %lu", (unsigned long)[[AVAudioSession sharedInstance] categoryOptions]);


默認(rèn)情況下, availableInputs 只會擁有iPhone的前置內(nèi)置麥克風(fēng)!

2021-04-28 10:19:58.789597+0800 WBHTTPDNSSDK_Example[24486:12088553] availableCategories: (
    AVAudioSessionCategoryAmbient,
    AVAudioSessionCategorySoloAmbient,
    AVAudioSessionCategoryPlayback,
    AVAudioSessionCategoryRecord,
    AVAudioSessionCategoryPlayAndRecord,
    AVAudioSessionCategoryMultiRoute
)
2021-04-28 10:19:58.792350+0800 WBHTTPDNSSDK_Example[24486:12088553] availableModes: (
    AVAudioSessionModeDefault,
    AVAudioSessionModeVoiceChat,
    AVAudioSessionModeVideoRecording,
    AVAudioSessionModeMeasurement,
    AVAudioSessionModeMoviePlayback,
    AVAudioSessionModeVideoChat,
    AVAudioSessionModeSpokenAudio,
    AVAudioSessionModeVoicePrompt
)
2021-04-28 10:19:58.794185+0800 WBHTTPDNSSDK_Example[24486:12088553] availableInputs: (
    "<AVAudioSessionPortDescription: 0x283c59440, type = MicrophoneBuiltIn; name = iPhone 麥克風(fēng); UID = Built-In Microphone; selectedDataSource = 前>"
)
2021-04-28 11:24:25.896577+0800 WBHTTPDNSSDK_Example[24949:12117560] outputDataSources: (
)
2021-04-28 11:24:25.898820+0800 WBHTTPDNSSDK_Example[24949:12117560] inputDataSources: (
    "<AVAudioSessionDataSourceDescription: 0x28177c950, ID = 1835216945; name = 下>",
    "<AVAudioSessionDataSourceDescription: 0x28177c920, ID = 1835216946; name = 前>",
    "<AVAudioSessionDataSourceDescription: 0x28177c910, ID = 1835216947; name = 返回>"
)

2021-04-28 10:19:58.794562+0800 WBHTTPDNSSDK_Example[24486:12088553] default category: AVAudioSessionCategorySoloAmbient
2021-04-28 10:19:58.794817+0800 WBHTTPDNSSDK_Example[24486:12088553] default mode: AVAudioSessionModeDefault
2021-04-28 10:19:58.795797+0800 WBHTTPDNSSDK_Example[24486:12088553] default categoryOptions: 0

注意我們可以在初始條件下打印inputDataSources, 通過AVAudioSessionDataSourceDescription的內(nèi)容, 可以知道設(shè)備擁有的麥克風(fēng)數(shù)目.

實(shí)際上輸入數(shù)據(jù)源, 可能有多個, 我們直接通過設(shè)置Category等配置系統(tǒng)會自動幫我們選擇具體使用的麥克風(fēng)

當(dāng)我們帶上Airpod藍(lán)牙耳機(jī), 并配置Options為AVAudioSessionCategoryOptionAllowBluetooth時, :

    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:nil];

    NSLog(@"availableCategories: %@", [[AVAudioSession sharedInstance] availableCategories]);
    NSLog(@"availableModes: %@", [[AVAudioSession sharedInstance] availableModes]);
    NSLog(@"availableInputs: %@", [[AVAudioSession sharedInstance] availableInputs]);
    
    NSLog(@"default category: %@", [[AVAudioSession sharedInstance] category]);
    NSLog(@"default mode: %@", [[AVAudioSession sharedInstance] mode]);
    NSLog(@"default categoryOptions: %lu", (unsigned long)[[AVAudioSession sharedInstance] categoryOptions]);

AVAudioSessionCategoryOptionAllowBluetooth情況下, availableInputs 會擁有iPhone的前置內(nèi)置麥克風(fēng)(有多個)以及airpod藍(lán)牙麥克風(fēng)!

2021-04-28 10:36:48.549186+0800 WBHTTPDNSSDK_Example[24659:12095941] availableCategories: (
    AVAudioSessionCategoryAmbient,
    AVAudioSessionCategorySoloAmbient,
    AVAudioSessionCategoryPlayback,
    AVAudioSessionCategoryRecord,
    AVAudioSessionCategoryPlayAndRecord,
    AVAudioSessionCategoryMultiRoute
)
2021-04-28 10:36:48.551766+0800 WBHTTPDNSSDK_Example[24659:12095941] availableModes: (
    AVAudioSessionModeDefault,
    AVAudioSessionModeVoiceChat,
    AVAudioSessionModeVideoRecording,
    AVAudioSessionModeMeasurement,
    AVAudioSessionModeMoviePlayback,
    AVAudioSessionModeVideoChat,
    AVAudioSessionModeSpokenAudio,
    AVAudioSessionModeVoicePrompt
)
2021-04-28 10:36:48.562432+0800 WBHTTPDNSSDK_Example[24659:12095941] availableInputs: (
    "<AVAudioSessionPortDescription: 0x282df0aa0, type = MicrophoneBuiltIn; name = iPhone 麥克風(fēng); UID = Built-In Microphone; selectedDataSource = 前>",
    "<AVAudioSessionPortDescription: 0x282df09d0, type = BluetoothHFP; name = free的AirPods; UID = E8:36:17:DA:DB:A7-tsco; selectedDataSource = (null)>"
)
2021-04-28 10:36:48.562916+0800 WBHTTPDNSSDK_Example[24659:12095941] default category: AVAudioSessionCategoryPlayAndRecord
2021-04-28 10:36:48.563238+0800 WBHTTPDNSSDK_Example[24659:12095941] default mode: AVAudioSessionModeDefault
2021-04-28 10:36:48.564578+0800 WBHTTPDNSSDK_Example[24659:12095941] default categoryOptions: 4

當(dāng)我們帶上 airpods 藍(lán)牙耳機(jī), 并插上有線耳機(jī), 并配置AVAudioSessionCategoryOptionAllowBluetooth 時, 能看到可用的音頻輸入方式為: 內(nèi)置麥克風(fēng), 有線麥克風(fēng), 耳機(jī)麥克風(fēng):

2021-04-28 10:36:48.549186+0800 WBHTTPDNSSDK_Example[24659:12095941] availableCategories: (
    AVAudioSessionCategoryAmbient,
    AVAudioSessionCategorySoloAmbient,
    AVAudioSessionCategoryPlayback,
    AVAudioSessionCategoryRecord,
    AVAudioSessionCategoryPlayAndRecord,
    AVAudioSessionCategoryMultiRoute
)
2021-04-28 10:36:48.551766+0800 WBHTTPDNSSDK_Example[24659:12095941] availableModes: (
    AVAudioSessionModeDefault,
    AVAudioSessionModeVoiceChat,
    AVAudioSessionModeVideoRecording,
    AVAudioSessionModeMeasurement,
    AVAudioSessionModeMoviePlayback,
    AVAudioSessionModeVideoChat,
    AVAudioSessionModeSpokenAudio,
    AVAudioSessionModeVoicePrompt
)
2021-04-28 10:36:48.562432+0800 WBHTTPDNSSDK_Example[24659:12095941] availableInputs: (
    "<AVAudioSessionPortDescription: 0x280254b70, type = MicrophoneBuiltIn; name = iPhone 麥克風(fēng); UID = Built-In Microphone; selectedDataSource = 前>",
    "<AVAudioSessionPortDescription: 0x280254be0, type = BluetoothHFP; name = free的AirPods; UID = E8:36:17:DA:DB:A7-tsco; selectedDataSource = (null)>",
    "<AVAudioSessionPortDescription: 0x280254bf0, type = MicrophoneWired; name = 耳機(jī)麥克風(fēng); UID = Wired Microphone; selectedDataSource = (null)>"
)

2021-04-28 10:36:48.562916+0800 WBHTTPDNSSDK_Example[24659:12095941] default category: AVAudioSessionCategoryPlayAndRecord
2021-04-28 10:36:48.563238+0800 WBHTTPDNSSDK_Example[24659:12095941] default mode: AVAudioSessionModeDefault
2021-04-28 10:36:48.564578+0800 WBHTTPDNSSDK_Example[24659:12095941] default categoryOptions: 4

當(dāng)我們帶上 airpods 藍(lán)牙耳機(jī), 并插上有線耳機(jī), 并配置Option = 0(不配置藍(lán)牙) 時, 打印的可用音頻Inputs只有內(nèi)置的前置麥克風(fēng)和有線耳機(jī)麥克風(fēng):

2021-04-28 10:36:48.562432+0800 WBHTTPDNSSDK_Example[24659:12095941] availableInputs: (
    "<AVAudioSessionPortDescription: 0x280254b70, type = MicrophoneBuiltIn; name = iPhone 麥克風(fēng); UID = Built-In Microphone; selectedDataSource = 前>",
    "<AVAudioSessionPortDescription: 0x280254bf0, type = MicrophoneWired; name = 耳機(jī)麥克風(fēng); UID = Wired Microphone; selectedDataSource = (null)>"
)

AVAudioSession的激活問題

Apple 的文檔中提到, 當(dāng)我們使用 setCategory...方法時, 如果當(dāng)前AVAudioSession已經(jīng)激活, 系統(tǒng)就會立即改變Category, 如果是未激活狀態(tài)inActive, 則需要我們設(shè)置Active==YES才會激活對應(yīng)狀態(tài)

The audio session’s category defines how the app uses audio. Typically, you set the category before activating the session. You can also set the category while the session is active, but this results in an immediate route change.

常見的激活A(yù)PI:

AVAudioSession *audioSession = [AVAudioSession sharedInstance];
//設(shè)置為播放和錄音狀態(tài),以便可以在錄制完之后播放錄音
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
[audioSession setActive:YES error:nil];

停止激活A(yù)PI:

AVAudioSession *audioSession = [AVAudioSession sharedInstance];  
[audioSession setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:&error];  

AVAudioSession強(qiáng)制選擇Audio Route, 也就是音頻的output位置是揚(yáng)聲器還是聽筒!!!

當(dāng)APP在使用時, 可能同時接入多個外設(shè)音頻(有線耳機(jī), 藍(lán)牙耳機(jī), 默認(rèn)揚(yáng)聲器), 默認(rèn)情況下, AudioSession會根據(jù)Last in Wins原則選擇, 就是聲音會導(dǎo)向最后接入的設(shè)備.

默認(rèn)情況下, 聲音會從默認(rèn)揚(yáng)聲器出來. 但是當(dāng)我們設(shè)置Category是在PlayAndRecord時, iPhone會將默認(rèn)的輸出修改成聽筒, 也就是前置攝像頭旁邊的一個揚(yáng)聲器(支持貼耳的近距離檢測!!!), 這種場景下, 有以下兩種方式將輸出修改成揚(yáng)聲器:

  1. 修改Options 稱為AVAudioSessionCategoryOptionDefaultToSpeaker. 也就是 [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:&error]; 此時沒有外設(shè)會從揚(yáng)聲器播放
  2. 直接使用 AudioSession的 overrideOutputAudioPort 方法, 也就是修改音頻的輸出接口[audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&error]; 此時不論是否有外設(shè), 都從揚(yáng)聲器播放

簡單看一下這個方法的系統(tǒng)解釋:

Summary

Temporarily changes the current audio route.
Declaration

- (BOOL)overrideOutputAudioPort:(AVAudioSessionPortOverride)portOverride error:(NSError * _Nullable *)outError;
Discussion

If your app uses the AVAudioSessionCategoryPlayAndRecord category, calling this method with the AVAudioSessionPortOverrideSpeaker option causes the system to route audio to the built-in speaker and microphone regardless of other settings. This change remains in effect only until the current route changes or you call this method again with the AVAudioSessionPortOverrideNone option.
If you’d prefer to permanently enable this behavior, you should instead set the category’s AVAudioSessionCategoryOptionDefaultToSpeaker option. Setting this option routes to the speaker rather than the receiver if no other accessory such as headphones are in use.
Note
The preferred method for routing audio to the speaker instead of the receiver for speakerphone functionality is through the use of the Media Player framework’s MPVolumeView class.

也就是說這個方法只是暫時AVAudioSessionCategoryPlayAndRecord category情況下, 將音頻輸出強(qiáng)制輸出到AVAudioSessionPortOverrideSpeaker揚(yáng)聲器, 在使用完以后, 再設(shè)置回AVAudioSessionPortOverrideNone.

如果需要永久將PlayAndRecord Category中, 音頻輸出到揚(yáng)聲器, 最好的方式是直接設(shè)置Options為AVAudioSessionCategoryOptionDefaultToSpeaker.

具體的代碼如下:

//方法一 切換這個方法會自動切換設(shè)備 耳機(jī)的插拔會自動切換
if (_isSpeakerMode) { //揚(yáng)聲器模式
  [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:nil];
}else{
// 在PlayAndRecord這個category下九串,聽筒會成為默認(rèn)的輸出設(shè)備
  [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:0 error:nil];


//方法二 切換這個方法會自動切換設(shè)備 耳機(jī)需要自己判斷
if ([self hasHeadset]) {
  [[AVAudioSession sharedInstance] overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:nil];
}else{
  [[AVAudioSession sharedInstance] overrideOutputAudioPort:_isSpeakerMode?AVAudioSessionPortOverrideSpeaker: AVAudioSessionPortOverrideNone error:nil];

}

系統(tǒng)中斷響應(yīng)

系統(tǒng)的中斷響應(yīng)分成兩類:

  1. 一般性的中斷: 包括電話, 鬧鈴等, 通知類型AVAudioSessionInterruptionNotification
  2. 其他APP可能占用AudioSession, 比如后臺錄音時, 打開了其他音樂視頻類APP. 通知類型AVAudioSessionSilenceSecondaryAudioHintNotification

外面的聲音設(shè)備改變的

默認(rèn)情況下, AudioSession會在App啟動時選擇一個最優(yōu)的輸出方案, 比如插入耳機(jī)的時候, 就用耳機(jī). 但是這個過程中, 用戶可能拔出耳機(jī), 我們App要如何感知這樣的情況呢

系統(tǒng)會通過 AVAudioSessionRouteChangeNotification來進(jìn)行通知其中在其userInfo中有鍵:AVAudioSessionRouteChangeReasonKey來表示改變的原因绞佩,原因的鍵值有一下幾種:

  • AVAudioSessionRouteChangeReasonUnknown:未知原因

  • AVAudioSessionRouteChangeReasonNewDeviceAvailable:有新設(shè)備可用

  • AVAudioSessionRouteChangeReasonOldDeviceUnavailable:老設(shè)備不可用
  • AVAudioSessionRouteChangeReasonCategoryChange:類別改變了
  • AVAudioSessionRouteChangeReasonOverride:App重置了輸出設(shè)置
  • AVAudioSessionRouteChangeReasonWakeFromSleep:從睡眠狀態(tài)呼醒
  • AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:當(dāng)前Category下沒有合適的設(shè)備
  • AVAudioSessionRouteChangeReasonRouteConfigurationChange: Rotuer的配置改變了

當(dāng)前項目中可能會涉及AVAudioSession的地方

在音頻SDK中, 針對AVAudioSession都需要指定對應(yīng)的Category, mode, options等配置.

具體過程如下:

  1. 我們常規(guī)配置Category為:AVAudioSessionCategoryPlayAndRecord, 默認(rèn)音頻輸出為聽筒(前置攝像頭旁邊)
  2. 然后Options, 可以根據(jù)是否需要支持藍(lán)牙耳機(jī): AVAudioSessionCategoryOptionAllowBluetooth
  3. Options 是否需要音頻輸出到揚(yáng)聲器(手機(jī)底部): AVAudioSessionCategoryOptionDefaultToSpeaker

我們可以在SDK使用完音頻資源以后通過如下配置停止Category的服務(wù), 這樣之前的背景音樂APP聲音會繼續(xù)播放:

[[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionInterruptionOptionShouldResume error:nil];

ps: iPhone有多個音頻輸出位置, 手機(jī)頂部是聽筒(聲音小),手機(jī)底部是一個揚(yáng)聲器(聲音大), 一般電話時, 默認(rèn)是聽筒播放聲音, 當(dāng)開啟免提就是底部揚(yáng)聲器播放聲音

在使用 Category = AVAudioSessionCategoryPlayAndRecord 默認(rèn)會使用聽筒播放音樂!

常規(guī)如下配置:
1. 使用Category = AVAudioSessionCategoryPlayAndRecord, Options = 0, Mode = Default
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:0 error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];

有如下幾種情況:

  1. 當(dāng)不接外設(shè)時, 此時音頻從前置攝像頭的聽筒輸出, 聲音會非常小!!!

  2. 當(dāng)僅插入有線耳機(jī)時, 此時音頻從有線耳機(jī)輸出!

  3. 當(dāng)僅接入藍(lán)牙耳機(jī)時, 未插入有線耳機(jī)時, 音頻從聽筒輸出!!!不會從藍(lán)牙耳機(jī)輸出

  4. 當(dāng)先插入有線耳機(jī), 然后接入藍(lán)牙耳機(jī)時(根據(jù)apple策略, 聲音會優(yōu)先從藍(lán)牙耳機(jī)輸出), 此時還是會從有線耳機(jī)輸出聲音!!!不會從藍(lán)牙耳機(jī)輸出

簡單結(jié)論 , Category == PlayAndRecord, Options == 0 ==> 音頻從聽筒輸出:

  1. 聲音優(yōu)先從有線耳機(jī)輸出, 如果沒有有線耳機(jī), 從聽筒輸出!!!
  2. 不論是否接入藍(lán)牙耳機(jī), 都不會從藍(lán)牙耳機(jī)輸出音頻!!!

此時, 在沒有有線耳機(jī)的情況下, 聲音太小!!! 體驗不好!!! 我們需要增加Options來進(jìn)行改進(jìn)

2. 使用Category = AVAudioSessionCategoryPlayAndRecord, Options = AVAudioSessionCategoryOptionDefaultToSpeaker , Mode = Default
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];

有如下幾種情況:

  1. 當(dāng)不接外設(shè)時, 此時音頻從揚(yáng)聲器輸出, 聲音很大!!!

  2. 當(dāng)僅插入有線耳機(jī)時, 此時音頻從有線耳機(jī)輸出!!!

  3. 當(dāng)僅接入藍(lán)牙耳機(jī)時, 未插入有線耳機(jī)時, 音頻從揚(yáng)聲器輸出!!!不會從藍(lán)牙耳機(jī)輸出

  4. 當(dāng)先插入有線耳機(jī), 然后接入藍(lán)牙耳機(jī)時(根據(jù)apple策略, 聲音會優(yōu)先從藍(lán)牙耳機(jī)輸出), 此時還是會從有線耳機(jī)輸出聲音!!!不會從藍(lán)牙耳機(jī)輸出

簡單結(jié)論, Category == PlayAndRecord, Options = AVAudioSessionCategoryOptionDefaultToSpeaker ==> 默認(rèn)音頻從揚(yáng)聲器輸出:

  1. 聲音優(yōu)先從有線耳機(jī)輸出, 如果沒有有線耳機(jī), 從揚(yáng)聲器輸出!!!
  2. 不論是否接入藍(lán)牙耳機(jī), 都不會從藍(lán)牙耳機(jī)輸出音頻!!!
3. 使用Category = AVAudioSessionCategoryPlayAndRecord, Options = AVAudioSessionCategoryOptionAllowBluetooth , Mode = Default

有如下幾種情況:

  1. 當(dāng)不接外設(shè)時, 此時音頻從聽筒輸出, 聲音會非常小!!!

  2. 當(dāng)僅插入有線耳機(jī)時, 此時音頻從有線耳機(jī)輸出!!!

  3. 當(dāng)僅接入藍(lán)牙耳機(jī)時, 未插入有線耳機(jī)時, 音頻此時會從藍(lán)牙耳機(jī)輸出!!!

  4. 當(dāng)先插入有線耳機(jī), 然后接入藍(lán)牙耳機(jī)時(根據(jù)apple策略, 聲音會優(yōu)先從藍(lán)牙耳機(jī)輸出), 此時會從藍(lán)牙耳機(jī)輸出

簡單結(jié)論, Category == PlayAndRecord, Options = AVAudioSessionCategoryOptionAllowBluetooth ==> 默認(rèn)音頻從聽筒輸出:

  1. 聲音優(yōu)先外設(shè)輸出(據(jù)Last in Wins原則選擇, 就是聲音會導(dǎo)向最后接入的設(shè)備), 從聽筒輸出!!!
  2. 此時音頻可以從藍(lán)牙耳機(jī)輸出音頻
4. 使用Category = AVAudioSessionCategoryPlayAndRecord, Options = AVAudioSessionCategoryOptionAllowBluetoot|AVAudioSessionCategoryOptionDefaultToSpeaker , Mode = Default

結(jié)論: 如果沒有外設(shè), 會從揚(yáng)聲器輸出, 如果有外設(shè), 根據(jù)Last in Wins 原則從外設(shè)(有線耳機(jī), 藍(lán)牙耳機(jī))輸出.

5. 特殊場景!!! 不論手機(jī)是否接入外設(shè)! 強(qiáng)制手機(jī)使用揚(yáng)聲器播放!!!

直接使用如下關(guān)鍵的是overrideOutputAudioPort API, 而Options是否使用AVAudioSessionCategoryOptionAllowBluetoot|AVAudioSessionCategoryOptionDefaultToSpeaker并不影響:

[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:0 error:nil]; // 這里Options 
[[AVAudioSession sharedInstance] setMode:AVAudioSessionModeDefault error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
    
    // 強(qiáng)制音頻輸出到揚(yáng)聲器
[[AVAudioSession sharedInstance] overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:nil];

在設(shè)置overrideOutputAudioPortSpeaker 以后, 不論是否接入外設(shè), 設(shè)備會強(qiáng)制使用揚(yáng)聲器作為音頻播放目的!!! 因此該方法的優(yōu)先級非常高!!!

一定要在設(shè)置AVAudioSessionPortOverrideSpeaker 以后, 在結(jié)束使用時候調(diào)用 [[AVAudioSession sharedInstance] overrideOutputAudioPort:0 error:nil]; 將強(qiáng)制從揚(yáng)聲器輸出音頻改成系統(tǒng)默認(rèn)(也就是Options)!!! 否則可能導(dǎo)致SDK影響APP的其他服務(wù)(一般SDK不會去修改overrideOutputAudioPort!!!)

最終建議

我們的SDK在使用AVAudioSession時, 建議使用最小權(quán)限使用原則, 在即將要使用音頻資源時, 去配置AVAudioSession的相關(guān)API, 在音頻使用結(jié)束以后, 關(guān)閉!!!

另外如果需要防止其他模塊的影響, 建議使用如下代碼 ( 如果有特殊需求, 自行更改. 比如要強(qiáng)制要求不論是否擁有外設(shè), 用揚(yáng)聲器輸出音頻):

[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionAllowBluetoot|AVAudioSessionCategoryOptionDefaultToSpeaker error:nil];
[[AVAudioSession sharedInstance] setMode:AVAudioSessionModeDefault error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
[[AVAudioSession sharedInstance] overrideOutputAudioPort:0 error:nil];

在不使用音頻邏輯時, 將AVAudioSession關(guān)閉:

[[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionInterruptionOptionShouldResume error:nil];

以上是針對Category == PlayAndRecord情況SDK的相關(guān)邏輯!!!

另外為了更好的音頻體驗邏輯, 為了充分利用PlayAndRecord的默認(rèn)輸出是聽筒, 其他的Category的默認(rèn)輸出是揚(yáng)聲器, 可以使用距離監(jiān)聽器, 去切換音頻播放中的一些邏輯, 如下:

- (void)sensorStateChange:(NSNotification *)notification {
    //如果此時手機(jī)靠近面部放在耳朵旁寺鸥,那么聲音將通過聽筒輸出,并將屏幕變暗
    if ([[UIDevice currentDevice] proximityState] == YES) {
        [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
    }  else {
        [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
    }
}

- (void)startProximityMonitering {
    [[NSNotificationCenter defaultCenter] addObserver:mPlayerManager
                                             selector:@selector(sensorStateChange:) name:@"UIDeviceProximityStateDidChangeNotification"
                                               object:nil];
    dispatch_async(dispatch_get_main_queue(), ^{
        [[UIDevice currentDevice] setProximityMonitoringEnabled:YES];
    });
}

- (void)stopProximityMonitering {
    [[NSNotificationCenter defaultCenter] removeObserver:mPlayerManager name:@"UIDeviceProximityStateDidChangeNotification" object:nil];
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
    dispatch_async(dispatch_get_main_queue(), ^{
        [[UIDevice currentDevice] setProximityMonitoringEnabled:NO];
    });
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末品山,一起剝皮案震驚了整個濱河市胆建,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌肘交,老刑警劉巖笆载,帶你破解...
    沈念sama閱讀 222,807評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異涯呻,居然都是意外死亡凉驻,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評論 3 399
  • 文/潘曉璐 我一進(jìn)店門复罐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涝登,“玉大人,你說我怎么就攤上這事效诅≌凸觯” “怎么了?”我有些...
    開封第一講書人閱讀 169,589評論 0 363
  • 文/不壞的土叔 我叫張陵乱投,是天一觀的道長咽笼。 經(jīng)常有香客問我,道長戚炫,這世上最難降的妖魔是什么剑刑? 我笑而不...
    開封第一講書人閱讀 60,188評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮嘹悼,結(jié)果婚禮上叛甫,老公的妹妹穿的比我還像新娘层宫。我一直安慰自己杨伙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評論 6 398
  • 文/花漫 我一把揭開白布萌腿。 她就那樣靜靜地躺著限匣,像睡著了一般。 火紅的嫁衣襯著肌膚如雪毁菱。 梳的紋絲不亂的頭發(fā)上米死,一...
    開封第一講書人閱讀 52,785評論 1 314
  • 那天,我揣著相機(jī)與錄音贮庞,去河邊找鬼峦筒。 笑死,一個胖子當(dāng)著我的面吹牛窗慎,可吹牛的內(nèi)容都是我干的物喷。 我是一名探鬼主播卤材,決...
    沈念sama閱讀 41,220評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼峦失!你這毒婦竟也來了扇丛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,167評論 0 277
  • 序言:老撾萬榮一對情侶失蹤尉辑,失蹤者是張志新(化名)和其女友劉穎帆精,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體隧魄,經(jīng)...
    沈念sama閱讀 46,698評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卓练,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了堤器。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昆庇。...
    茶點(diǎn)故事閱讀 40,912評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖闸溃,靈堂內(nèi)的尸體忽然破棺而出整吆,到底是詐尸還是另有隱情,我是刑警寧澤辉川,帶...
    沈念sama閱讀 36,572評論 5 351
  • 正文 年R本政府宣布表蝙,位于F島的核電站,受9級特大地震影響乓旗,放射性物質(zhì)發(fā)生泄漏府蛇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評論 3 336
  • 文/蒙蒙 一屿愚、第九天 我趴在偏房一處隱蔽的房頂上張望汇跨。 院中可真熱鬧,春花似錦妆距、人聲如沸穷遂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蚪黑。三九已至,卻和暖如春中剩,著一層夾襖步出監(jiān)牢的瞬間忌穿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評論 1 274
  • 我被黑心中介騙來泰國打工结啼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留掠剑,地道東北人。 一個月前我還...
    沈念sama閱讀 49,359評論 3 379
  • 正文 我出身青樓郊愧,卻偏偏與公主長得像朴译,于是被迫代替她去往敵國和親沸伏。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評論 2 361

推薦閱讀更多精彩內(nèi)容