AVAudioSession FAQ

2,898 阅读6分钟

1. 什么是AVAudioSession?

官方文档:

AVAudioSession是一个对象,用于向系统传达你将如何在应用程序中使用音频。
使用AVAudioSession可以向操作系统描述应用程序使用音频的一般策略,而无需详细说明特定场景下的表现或与音频硬件的交互。你可以将这些细节的管理委托给AVAudioSession实现,这样可以确保操作系统能够最好地管理用户的音频体验。

由文档可知,AVAudioSession是高度封装的一套极简API(隐藏了复杂的底层逻辑与实现细节),其目的是为了使开发者可以更加方便快捷的管理音频,这也符合苹果设计上层API的一贯风格。这种设计风格要求开发者可以不关注底层细节,但必须清楚的了解每个关键参数所对应的音频表现,使用场景,以开发出符合产品预期的优秀app。

2. 如何使用AVAudioSession接口?

AVAudioSession的API非常简单,经常使用的只有两个:

- (BOOL)setCategory:(AVAudioSessionCategory)category 
               mode:(AVAudioSessionMode)mode 
            options:(AVAudioSessionCategoryOptions)options 
              error:(NSError * _Nullable *)outError;  
              
- (BOOL)setActive:(BOOL)active 
      withOptions:(AVAudioSessionSetActiveOptions)options 
            error:(NSError * _Nullable *)outError;

调用API的流程也很简单,首先设置AVAudioSession的工作模式,然后激活AVAudioSession,音频设备就可以正常工作了。当音频结束工作时,为了让其他应用程序音频恢复,最好取消当前AVAudioSession的激活状态并通过AVAudioSessionSetActiveOptions加以通知。

容易犯错误的地方在于对AVAudioSessionCategory、AVAudioSessionMode、AVAudioSessionCategoryOptions三个参数的设置。由于参数之间存在组合、互斥、或大概率需要绑定设置等场景,正确的设置工作模式成为了开发人员比较头痛的难点。

下文以AVAudioSessionCategory作为主线,梳理出了各参数所对应的音频使用场景和参数之间的协作关系。

  • AVAudioSessionCategorySoloAmbient

当开发者不设置AudioSession的参数,直接激活时,系统默认会在SoloAmbient模式下运行。 该模式的音频表现特点为:

  1. 当用户锁屏或拨下静音键时,音频会被静音。
  2. 自身不具备混音能力,会打断其他同样不具备混音能力的app音频。

作为系统默认的音频工作模式,SoloAmbient满足了最基本的音频播放需求,开发者不需要(也几乎不能)为SoloAmbient配置任何CategoryOptions。

  • AVAudioSessionCategoryAmbient

除了允许混音以外,表现与SoloAmbient完全相同。

  • AVAudioSessionCategoryPlayback

' playing audio are central to the successful use of your app ' 是官方文档对Playback工作模式的定位。当播放音频是app的核心功能时,Playback是最好的选择。该模式的音频表现特点为:

  1. 用户锁屏或拨下静音键时,音频会继续播放。(锁屏播放需要开启后台播放权限)
  2. 默认不支持混音。

Playback模式下可以通过设置AVAudioSessionCategoryOptionMixWithOthers实现混音。还可通过设置AVAudioSessionCategoryOptionDuckOthers实现混音的同时降低其他app音频的音量。

  • AVAudioSessionCategoryRecord

当app只需要录制音频时,需要设置Recod作为category。值得注意的是,Record模式下通常需要绑定options:AVAudioSessionCategoryOptionAllowBluetooth,以支持蓝牙设备的音频输入功能。

  • AVAudioSessionCategoryPlayAndRecord

Playback和Record的结合体,当app既需要录,又需要播的时候,就设置PlayAndRecord吧。PlayAndRecord模式下有两个比较特别的特性:

  1. 音频输出设备默认会选择听筒,可通过设置AVAudioSessionCategoryOptionDefaultToSpeaker,改为默认路由到扬声器。
  2. 对于蓝牙设备的支持,既可以设置AVAudioSessionCategoryOptionAllowBluetoothA2DP作为输出,也可以设置AVAudioSessionCategoryOptionAllowBluetooth作为输入 / 输出。当两种options同时设置时,系统会给蓝牙设备的HFP端口更高的路由优先级。
  • AVAudioSessionCategoryMultiRoute

当需要多个音频输入输出设备同时工作时,需要设置CategoryMultiRoute参数。

一旦AVAudioSession在这种模式下运行,操作系统不会再为开发者自动选择最优的音频路由路径。你需要自己手动获取AVAudioSessionChannelDescription,并根据业务需求决定音频应该通过哪一个channel输入 / 输出。当有音频设备添加或移除时,通过监听AVAudioSessionRouteChangeNotification,及时的更新路由策略。


以上组合可以满足绝大部分业务需求。然而,想象这样一个业务场景,一款导航类app播放导航音频时,应当可以与其他音乐类app的音频混音(降低音乐的音量),但是必须要打断其他语音类app以避免给用户造成理解障碍。

要实现这个需求,我们设置category为AVAudioSessionCategoryPlayback,并为options配置AVAudioSessionCategoryOptionDuckOthers参数,以实现和音乐类音频混音并降低其音量的效果。此外、还需要给options加入AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers参数,以打断其他语音类app的音频播放。最后我们把mode参数设置成AVAudioSessionModeSpokenAudio,以告知系统,当前app也是一款语音类产品,这样,当其他语音类AVAudioSession工作时,我们的app就可以被正确的打断和恢复了。

这是一个比较有代表性的,关于category,options和mode如何协作的例子:AVAudioSessionCategory为音频的整体表现定下基调、AVAudioSessionCategoryOptions在当前的基调下对音频的某些策略进行细微调整、AVAudioSessionMode为AVAudioSession设置了一个具体的应用场景,它可能会在音频的输入 / 输出端进行与mode场景相对应的优化、也可能在某些特定条件下,直接变更音频的默认表现。

3. 应当何时设置 / 激活AVAudioSession?

开发者可以在任何时间设置AVAudioSession的参数,并激活。

我的建议是尽可能早的(最好只有一次)设置参数,尽可能晚的激活AVAudioSession。

一款复杂的音频类app可能有很多个音频相关组件,有的组件只需要播放,有的组件需要录制并播放。如果每个模块各自设置最适合自己的AVAudioSession参数,当业务需要组合音频组件时,需要更多权限的音频组件就无法正常工作了。因此,开发者最好在didFinishLaunchingWithOptions阶段,就确定最适合app使用的AVAudioSession参数,并且尽可能的不在后续业务中再做修改(或只改AVAudioSessionMode)。

尽可能晚的激活AVAudioSession,可以使你的app在真正需要播放音频时,才去影响其他app的音频表现。过早的打断其他AVAudioSession、或降低其音量往往会让用户感到莫名其妙。

4. 第三方框架篡改AVAudioSession时如何处理?

开发一款app常常不可避免的需要引入第三方sdk,音频类sdk为了保证自身业务逻辑的顺畅执行,一般会在内部直接设置合适的AVAudioSession参数(有些优秀的sdk会暴露接口)。多个sdk之间,AVAudioSession参数冲突的问题,只能交由上层开发者解决。

遗憾的是系统没有提供AVAudioSession参数变更的通知或回调,category等属性又被标记为readonly(内部也没有调用set方法),使得KVO方案也不可行。想直接监控AVAudioSession参数变化的技术方案似乎都难以实现。

通过分析可以发现,引起业务交互异常的篡改行为通常都会改变音频设备的路由,因此监听AVAudioSessionRouteChangeNotification通知,并在AVAudioSessionRouteChangeReasonKey中判断值为AVAudioSessionRouteChangeReasonCategoryChange,可以捕获所有参数冲突的篡改行为,开发者可以在这里重新设置合适的AVAudioSession参数,保证app正常运行。