官方针对AVAudioSession的解释
参考Apple官方文档, AudioSession 用来描述APP的音频行为, 大概有以下几类与之对应的 Category 是阐释其音频行为的关键信息, 比如:
- 系统说明你的app使用音频的模式(比如是播放还是录音,是否支持蓝牙播放,是否支持后台播放)
- 为你的app选择音频的输入输出设备(比如输入用的麦克风,输出是耳机、手机功放或者airplay)
- 协助管理多个音源需要播放时的行为(例如同时使用多个音乐播放app,或者突然有电话接入)
AVAudioSession的Category
Category | 手机静音开关 or 锁屏时是否静音 | 中断其他APP的音频(后台音乐) | 允许录音 和 播放 |
---|---|---|---|
AVAudioSessionCategoryAmbient | Yes | No | Output only |
AVAudioSessionCategorySoloAmbient(默认) | Yes | Yes | Output only |
AVAudioSessionCategoryPlayback | No | Yes | Output only |
AVAudioSessionCategoryRecord | No(音频录制继续) | Yes | Input only |
AVAudioSessionCategoryPlayAndRecord | No | Yes | Input and output |
AVAudioSessionCategoryMultiRoute | No | Yes | Input and output |
简单说明:
- AVAudioSessionCategoryAmbient:只支持播放,可以和其他的音乐播放器一起播放。同时,当用户锁屏或者静音时也会随着静音.
- AVAudioSessionCategorySoloAmbient: 也是只用于播放,但是会关闭其他的音乐播放器的播放,同时,当用户锁屏或者静音时也会随着静音。
- AVAudioSessionCategoryPlayback: 只支持播放,当锁屏时不会静音,此category也会关闭其他的播放器的播放;
- AVAudioSessionCategoryRecord: 只支持录音,此category会关闭其他的播放器,当锁屏时不会静音。
- AVAudioSessionCategoryPlayAndRecord:即支持播放也支持录音,比如VoIP,打电话这种场景。
- AVAudioSessionCategoryMultiRoute:即支持播放也支持录音,当锁屏时不会静音。这个类别可以支持多个设备输入输出, 比如耳机, USB设备同时音频输出
- AVAudioSessionCategoryAudioProcessing: 主要用于音频格式处理,一般可以配合AudioUnit进行使用。
AVAudioSession的选项Options
在前面配置Category时, 我们可以增加Options来简单调整! 注意Options与Category类型相关, 在配置前, 先判断 Options是否匹配Category, 可以使用[AVAudioSession sharedInstance].availableCategories
来查看当前Category支持的Options,常规的选项.
- AVAudioSessionCategoryOptionMixWithOthers, 是否可以和其他后台App进行混音. 简单说就是如果后台有音乐APP, 是否和他一起播放
- AVAudioSessionCategoryOptionDuckOthers, 是否压低其他App声音. 我们常见的导航APP在语音播报时, 就会降低其他的音乐APP的声音, 在播报完成以后, 音乐APP声音恢复正常.
- AVAudioSessionCategoryOptionAllowBluetooth, 如果要支持蓝牙耳机,则需要设置这个选项
- AVAudioSessionCategoryOptionDefaultToSpeaker, 是否默认用扬声器声音. (在Category是
PlayAndRecord
时, 系统的默认音频输出使用的是听筒), 如果在VoIP模式下,希望默认打开免提功能,需要设置这个选项
目前主要的选项有这几种,都有对应的使用场景,除此之外,在iOS9还提供了
AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers
iOS10又新加了两个
AVAudioSessionCategoryOptionAllowBluetoothA2DP
、AVAudioSessionCategoryOptionAllowAirPlay
用来支持蓝牙A2DP耳机和AirPlay
配置选项常见的API:
(BOOL)setCategory:(NSString *)category withOptions:(AVAudioSessionCategoryOptions)options error:(NSError **)outError;
不过这个过程,感觉缺少一个
setOption
的接口,既然已经是当前处于的Category,干嘛还要再设置选项的时候再指定Category呢?
AVAudioSession的Mode
通过Category和Options的微调, 基本覆盖了主要的场景, 在上面的逻辑还需要针对不同的子场景进行调整, 就需要配置Mode
, 具体7个子mode如下:
- AVAudioSessionModeDefault: 适用于所有的类别,
- AVAudioSessionModeVoiceChat:适用与AVAudioSessionCategoryPlayAndRecord,主要用于VoIP场景,此时系统会选择最佳的输入设备,比如插上耳机就使用耳机上的麦克风进行采集。此时有个副作用,他会设置类别的选项为"AVAudioSessionCategoryOptionAllowBluetooth"从而支持蓝牙耳机。
- AVAudioSessionModeVideoChat:适用与AVAudioSessionCategoryPlayAndRecord, 主要用于视频通话,系统也会选择最佳的输入设备,比如插上耳机就使用耳机上的麦克风进行采集并且会设置类别的选项为"AVAudioSessionCategoryOptionAllowBluetooth" 和 "AVAudioSessionCategoryOptionDefaultToSpeaker"。
- AVAudioSessionModeGameChat:适用与AVAudioSessionCategoryPlayAndRecord,适用于游戏App的采集和播放,比如“GKVoiceChat”对象,一般不需要手动设置。
- AVAudioSessionModeVideoRecording:适用与AVAudioSessionCategoryPlayAndRecord和AVAudioSessionCategoryRecord,主要用于录制视频。
- AVAudioSessionModeMoviePlayback:适用与AVAudioSessionCategoryPlayAndRecord,主要用于视频播放;
- AVAudioSessionModeMeasurement:适用与AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryRecord AVAudioSessionCategoryPlayback,主要用于最小系统
另外几种和音频APP关系不大,一般我们只需要关注VoIP或者视频通话即可.
我们一般都是在设置Category以后再才设置 Mode:
(BOOL)setMode:(NSString *)mode error:(NSError **)outError;
AVAudioSession的一些属性
注意app中的AVAudioSession通常使用方法[AVAudioSession sharedInstance]
使用一个单例对象去配置, 我们在启动时, 可以获取它的内部的一些属性:
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]);
默认情况下, availableInputs
只会拥有iPhone的前置内置麦克风!
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 麦克风; 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
的内容, 可以知道设备拥有的麦克风数目.实际上输入数据源, 可能有多个, 我们直接通过设置Category等配置系统会自动帮我们选择具体使用的麦克风
当我们带上Airpod蓝牙耳机, 并配置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的前置内置麦克风(有多个)以及airpod蓝牙麦克风!
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 麦克风; 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
当我们带上 airpods 蓝牙耳机, 并插上有线耳机, 并配置AVAudioSessionCategoryOptionAllowBluetooth
时, 能看到可用的音频输入方式为: 内置麦克风, 有线麦克风, 耳机麦克风:
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 麦克风; 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 = 耳机麦克风; 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
当我们带上 airpods 蓝牙耳机, 并插上有线耳机, 并配置Option = 0(不配置蓝牙) 时, 打印的可用音频Inputs只有内置的前置麦克风和有线耳机麦克风:
2021-04-28 10:36:48.562432+0800 WBHTTPDNSSDK_Example[24659:12095941] availableInputs: (
"<AVAudioSessionPortDescription: 0x280254b70, type = MicrophoneBuiltIn; name = iPhone 麦克风; UID = Built-In Microphone; selectedDataSource = 前>",
"<AVAudioSessionPortDescription: 0x280254bf0, type = MicrophoneWired; name = 耳机麦克风; UID = Wired Microphone; selectedDataSource = (null)>"
)
AVAudioSession的激活问题
Apple 的文档中提到, 当我们使用 setCategory...
方法时, 如果当前AVAudioSession已经激活, 系统就会立即改变Category, 如果是未激活状态inActive
, 则需要我们设置Active==YES
才会激活对应状态
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.
常见的激活API:
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
//设置为播放和录音状态,以便可以在录制完之后播放录音
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
[audioSession setActive:YES error:nil];
停止激活API:
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:&error];
AVAudioSession强制选择Audio Route, 也就是音频的output位置是扬声器还是听筒!!!
当APP在使用时, 可能同时接入多个外设音频(有线耳机, 蓝牙耳机, 默认扬声器), 默认情况下, AudioSession会根据Last in Wins
原则选择, 就是声音会导向最后接入的设备.
默认情况下, 声音会从默认扬声器出来. 但是当我们设置Category是在PlayAndRecord
时, iPhone会将默认的输出修改成听筒
, 也就是前置摄像头旁边的一个扬声器(支持贴耳的近距离检测!!!), 这种场景下, 有以下两种方式将输出修改成扬声器:
- 修改Options 称为
AVAudioSessionCategoryOptionDefaultToSpeaker
. 也就是[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:&error];
此时没有外设会从扬声器播放 - 直接使用 AudioSession的
overrideOutputAudioPort
方法, 也就是修改音频的输出接口[audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&error];
此时不论是否有外设, 都从扬声器播放
简单看一下这个方法的系统解释:
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情况下, 将音频输出强制输出到AVAudioSessionPortOverrideSpeaker
扬声器, 在使用完以后, 再设置回AVAudioSessionPortOverrideNone
.
如果需要永久将PlayAndRecord
Category中, 音频输出到扬声器, 最好的方式是直接设置Options为AVAudioSessionCategoryOptionDefaultToSpeaker
.
具体的代码如下:
//方法一 切换这个方法会自动切换设备 耳机的插拔会自动切换
if (_isSpeakerMode) { //扬声器模式
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:nil];
}else{
// 在PlayAndRecord这个category下,听筒会成为默认的输出设备
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:0 error:nil];
//方法二 切换这个方法会自动切换设备 耳机需要自己判断
if ([self hasHeadset]) {
[[AVAudioSession sharedInstance] overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:nil];
}else{
[[AVAudioSession sharedInstance] overrideOutputAudioPort:_isSpeakerMode?AVAudioSessionPortOverrideSpeaker: AVAudioSessionPortOverrideNone error:nil];
}
系统中断响应
系统的中断响应分成两类:
- 一般性的中断: 包括电话, 闹铃等, 通知类型
AVAudioSessionInterruptionNotification
- 其他APP可能占用AudioSession, 比如后台录音时, 打开了其他音乐视频类APP. 通知类型
AVAudioSessionSilenceSecondaryAudioHintNotification
外面的声音设备改变的
默认情况下, AudioSession会在App启动时选择一个最优的输出方案, 比如插入耳机的时候, 就用耳机. 但是这个过程中, 用户可能拔出耳机, 我们App要如何感知这样的情况呢
系统会通过 AVAudioSessionRouteChangeNotification
来进行通知其中在其userInfo中有键:AVAudioSessionRouteChangeReasonKey
来表示改变的原因,原因的键值有一下几种:
AVAudioSessionRouteChangeReasonUnknown:未知原因
AVAudioSessionRouteChangeReasonNewDeviceAvailable:有新设备可用
- AVAudioSessionRouteChangeReasonOldDeviceUnavailable:老设备不可用
- AVAudioSessionRouteChangeReasonCategoryChange:类别改变了
- AVAudioSessionRouteChangeReasonOverride:App重置了输出设置
- AVAudioSessionRouteChangeReasonWakeFromSleep:从睡眠状态呼醒
- AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:当前Category下没有合适的设备
- AVAudioSessionRouteChangeReasonRouteConfigurationChange: Rotuer的配置改变了
当前项目中可能会涉及AVAudioSession的地方
在音频SDK中, 针对AVAudioSession都需要指定对应的Category, mode, options等配置.
具体过程如下:
- 我们常规配置Category为:
AVAudioSessionCategoryPlayAndRecord
, 默认音频输出为听筒(前置摄像头旁边) - 然后Options, 可以根据是否需要支持蓝牙耳机:
AVAudioSessionCategoryOptionAllowBluetooth
- Options 是否需要音频输出到扬声器(手机底部):
AVAudioSessionCategoryOptionDefaultToSpeaker
我们可以在SDK使用完音频资源以后通过如下配置停止Category的服务, 这样之前的背景音乐APP声音会继续播放:
[[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionInterruptionOptionShouldResume error:nil];
ps: iPhone有多个音频输出位置, 手机顶部是听筒(声音小),手机底部是一个扬声器(声音大), 一般电话时, 默认是听筒播放声音, 当开启免提就是底部扬声器播放声音
在使用 Category = AVAudioSessionCategoryPlayAndRecord 默认会使用听筒播放音乐!
常规如下配置:
1. 使用Category = AVAudioSessionCategoryPlayAndRecord, Options = 0, Mode = Default
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:0 error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
有如下几种情况:
当不接外设时, 此时音频从前置摄像头的听筒输出, 声音会非常小!!!
当仅插入有线耳机时, 此时音频从有线耳机输出!
当仅接入蓝牙耳机时, 未插入有线耳机时, 音频从听筒输出!!!不会从蓝牙耳机输出
当先插入有线耳机, 然后接入蓝牙耳机时(根据apple策略, 声音会优先从蓝牙耳机输出), 此时还是会从有线耳机输出声音!!!不会从蓝牙耳机输出
简单结论 , Category == PlayAndRecord, Options == 0
==> 音频从听筒输出:
- 声音优先从有线耳机输出, 如果没有有线耳机, 从听筒输出!!!
- 不论是否接入蓝牙耳机, 都不会从蓝牙耳机输出音频!!!
此时, 在没有有线耳机的情况下, 声音太小!!! 体验不好!!! 我们需要增加Options来进行改进
2. 使用Category = AVAudioSessionCategoryPlayAndRecord, Options = AVAudioSessionCategoryOptionDefaultToSpeaker , Mode = Default
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
有如下几种情况:
当不接外设时, 此时音频从扬声器输出, 声音很大!!!
当仅插入有线耳机时, 此时音频从有线耳机输出!!!
当仅接入蓝牙耳机时, 未插入有线耳机时, 音频从扬声器输出!!!不会从蓝牙耳机输出
当先插入有线耳机, 然后接入蓝牙耳机时(根据apple策略, 声音会优先从蓝牙耳机输出), 此时还是会从有线耳机输出声音!!!不会从蓝牙耳机输出
简单结论, Category == PlayAndRecord, Options = AVAudioSessionCategoryOptionDefaultToSpeaker
==> 默认音频从扬声器输出:
- 声音优先从有线耳机输出, 如果没有有线耳机, 从扬声器输出!!!
- 不论是否接入蓝牙耳机, 都不会从蓝牙耳机输出音频!!!
3. 使用Category = AVAudioSessionCategoryPlayAndRecord, Options = AVAudioSessionCategoryOptionAllowBluetooth , Mode = Default
有如下几种情况:
当不接外设时, 此时音频从听筒输出, 声音会非常小!!!
当仅插入有线耳机时, 此时音频从有线耳机输出!!!
当仅接入蓝牙耳机时, 未插入有线耳机时, 音频此时会从蓝牙耳机输出!!!
当先插入有线耳机, 然后接入蓝牙耳机时(根据apple策略, 声音会优先从蓝牙耳机输出), 此时会从蓝牙耳机输出
简单结论, Category == PlayAndRecord, Options = AVAudioSessionCategoryOptionAllowBluetooth
==> 默认音频从听筒输出:
- 声音优先外设输出(据
Last in Wins
原则选择, 就是声音会导向最后接入的设备), 从听筒输出!!! - 此时音频可以从蓝牙耳机输出音频
4. 使用Category = AVAudioSessionCategoryPlayAndRecord, Options = AVAudioSessionCategoryOptionAllowBluetoot|AVAudioSessionCategoryOptionDefaultToSpeaker , Mode = Default
结论: 如果没有外设, 会从扬声器输出, 如果有外设, 根据Last in Wins
原则从外设(有线耳机, 蓝牙耳机)输出.
5. 特殊场景!!! 不论手机是否接入外设! 强制手机使用扬声器播放!!!
直接使用如下关键的是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];
// 强制音频输出到扬声器
[[AVAudioSession sharedInstance] overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:nil];
在设置overrideOutputAudioPort
为Speaker
以后, 不论是否接入外设, 设备会强制使用扬声器作为音频播放目的!!! 因此该方法的优先级非常高!!!
一定要在设置AVAudioSessionPortOverrideSpeaker
以后, 在结束使用时候调用 [[AVAudioSession sharedInstance] overrideOutputAudioPort:0 error:nil];
将强制从扬声器输出音频改成系统默认(也就是Options)!!! 否则可能导致SDK影响APP的其他服务(一般SDK不会去修改overrideOutputAudioPort!!!)
最终建议
我们的SDK在使用AVAudioSession时, 建议使用最小权限使用原则, 在即将要使用音频资源时, 去配置AVAudioSession的相关API, 在音频使用结束以后, 关闭!!!
另外如果需要防止其他模块的影响, 建议使用如下代码 ( 如果有特殊需求, 自行更改. 比如要强制要求不论是否拥有外设, 用扬声器输出音频):
[[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关闭:
[[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionInterruptionOptionShouldResume error:nil];
以上是针对Category == PlayAndRecord
情况SDK的相关逻辑!!!
另外为了更好的音频体验逻辑, 为了充分利用PlayAndRecord
的默认输出是听筒, 其他的Category的默认输出是扬声器, 可以使用距离监听器, 去切换音频播放中的一些逻辑, 如下:
- (void)sensorStateChange:(NSNotification *)notification {
//如果此时手机靠近面部放在耳朵旁,那么声音将通过听筒输出,并将屏幕变暗
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];
});
}