前言
回忆一个场景,我们使用iPhone 打开一首歌曲,音频从内置扬声器中播放出来,此时有电话拨入,音乐会立即停止并处于暂停状态。此时听到的是手机呼叫的铃声,当我们挂掉电话后,刚才的音乐再次响起。在这一过程中 iOS 提供了一个可管理的音频环境,通过 音频会话(Audio Session)来管理应用程序、应用程序间和设备级别的音频行为。
音频会话介绍
音频会话在应用程序和操作系统之间扮演者中间人的角色,它提供了一种简单实用的方法使得系统得知应用程序应该如何与 iOS 音频环境进行交互。开发者不需要了解与音频硬件交互的具体细节,只需要对应用程序的行为进行抽象的配置,并把对该行为的管理委托给音频会话,可确保对用户的音频体验进行最佳管理。
所有 iOS 应用程序启动后,都具有一个默认音频会话,无论是否使用。默认音频会话来自于以下一些预配置:
- 支持音频播放,但不允许录音。
- 在 iOS 中,将响铃/静音开关设置为静音模式会使应用程序正在播放的任何音频静音。
- 在 iOS 中,当设备被锁定时,应用程序的音频会静音。
- 当应用程序播放音频时,任何其他后台音频(例如音乐应用程序正在播放的音频)都会被静音。
音频会话类别
默认音频会话提供了很多实用的功能,但在大多数情况下,开发者应该对其进行自定义以更好地满足应用程序的需求。要更改行为,需要配置应用的音频会话。幸运的是,通过设置”类别“功能,可以很容易地定制我们的特殊需求。
表达音频行为的主要机制是音频会话类别。通过设置类别,可以指定应用程序是使用输入还是输出,是否希望音乐与音频一起继续播放等等。AV Foundation 定义了许多音频会话类别,可让开发者自定义音频行为。下面表格总结了 AV Foundation 定义的 6 种类别行为细节。
类别 | 作用 | 是否允许混音 | 音频的输入与输出 | 由响铃/静音开关和屏幕锁定静音 |
---|---|---|---|---|
AVAudioSessionCategoryAmbient |
游戏、效率应用程序 | Yes | 仅输出 | Yes |
AVAudioSessionCategorySoloAmbient (默认) |
游戏、效率应用程序 | No | 仅输出 | Yes |
AVAudioSessionCategoryPlayback |
音频和视频播放器 | 可选 | 仅输出 | No |
AVAudioSessionCategoryRecord |
录音机、音频捕捉 | No | 仅输入 | No |
AVAudioSessionCategoryPlayAndRecord |
Voip、语言聊天 | 可选 | 输入和输出 | No |
AVAudioSessionCategoryMultiRoute |
使用外部硬件的高级 A/V应用程序 | No | 输入和输出 | No |
可以通过 setCategory:mode:options:error:
设置上述音频会话类别,其中一些分类可以通过使用 options
和 modes
进一步自定义附加行为。示例如下:
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error;
if (![session setCategory:AVAudioSessionCategoryPlayback error:&error]) {
NSLog(@"Category Error: %@", [error localizedDescription]);
}
if (![session setActive:YES error:&error]) {
NSLog(@"Activation Error: %@", [error localizedDescription]);
}
音频中断及处理
音频中断是应用程序音频会话的停用——它会立即停止音频。当来自其它应用程序的音频会话被激活并且该会话未被系统分类以与我们的应用程序的音频混合时,就会发生中断。在会话处于非活动状态后,系统会发送一条“被中断”消息,可以通过保存状态、更新用户界面等来响应该消息。
应用程序可能会在中断后暂停。当用户接听电话时会发生这种情况。如果用户转而忽略呼叫或解除警报,系统会发出“中断结束”消息,并且应用程序会继续运行。要恢复音频,必须重新激活音频会话。下图说明了回放应用程序的音频会话中断之前、期间和之后的事件顺序。
中断事件(在此示例中为 FaceTime 请求的到达)按如下方式进行。编号的步骤对应于图中的数字。
- 应用程序处于活动状态,正在播放音频。
- FaceTime 请求到达。系统激活 FaceTime 应用程序的音频会话。
- 系统会停用当前音频会话。此时,应用程序中的播放已停止。
- 系统会发布通知,表明会话已被停用。
- 通知处理程序需要采取适当的措施。例如,它可以更新用户界面并保存在停止点恢复播放所需的信息。
- 如果用户解除中断(忽略传入的 FaceTime 请求),系统会发布通知,指示中断已结束。
- 通知处理程序会采取适合中断结束的操作。例如,它可能会更新用户界面、重新激活音频会话并恢复播放。
- (图中未显示。)如果用户接听电话,而不是在第 6 步消除中断,则应用程序将暂停。
在开发音频功能时,我们要确保应用程序可以正确地处理中断事件,音频会话相关代码以处理中断可确保应用程序的音频在来电、时钟或日历警报响起或其他应用程序激活其音频会话时继续正常运行。
首先需要得到中断出现的通知,可以选择注册应用程序的 AVAudioSession
发送的通知 AVAudioSessionInterruptionNotification
。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
在系统发送通知调用 handlerInterruption:
时传递的NSNotification
实例包含一个 userInfo
提供中断详细信息的填充字典。可以通过从字典中检索 AVAudioSessionInterruptionType
值来确定中断的类型,中断类型指示中断是已经开始还是已经结束。
typedef NS_ENUM(NSUInteger, AVAudioSessionInterruptionType) {
AVAudioSessionInterruptionTypeBegan = 1, ///< the system has interrupted your audio session
AVAudioSessionInterruptionTypeEnded = 0, ///< the interruption has ended
};
当中断出现时,类型为
AVAudioSessionInterruptionTypeBegan
,需要采取的动作就是暂停音频播放以及 UI 界面的处理。当中断结束时,类型为
AVAudioSessionInterruptionTypeEnded
,userInfo
中可能包含一个AVAudioSessionInterruptionOptions
值,指示音频会话是否以及重新激活以及它是否可以再次播放。如果选项值为AVAudioSessionInterruptionOptionShouldResume
,则可以继续播放。
响应路由的变化
当应用程序运行时,用户可能会插入或拔出耳机,或使用带有音频连接的扩展坞。iOS 人机界面指南描述了应用程序应如何响应此类事件。要实施这些建议,可以通过音频会话处理音频硬件路由的更改。
音频硬件路由是音频信号的有线电子通路。当设备的用户插入或拔出耳机时,系统会发生线路改变, AVAudioSession
会广播一个描述该变化的通知AVAudioSessionRouteChangeNotification
给所有相关的监听者。下图描述了录入和播放期间各种路线变化的事件:
如上图所示,在应用启动后,系统会初步确定音频路由。当应用程序运行时,它会继续监控活动路线。首先考虑用户在应用程序中点击“录制”按钮的情况,由图左侧的“录制开始”框表示。
在录制过程中,用户可以插入或拔出耳机,请参见图中左下角的菱形决策元素。作为响应,系统发送包含更改原因和先前路由的路由更改通知,应用应停止录制。
播放的情况类似,但结果不同,如图右侧所示。如果用户在播放过程中拔下耳机,应用应暂停音频。如果用户在播放过程中插入耳机,应用应该只允许继续播放。那么应该怎么做呢?
首先需要注册 AVAudioSession
发送的通知AVAudioSessionRouteChangeNotification
,该通知包含一个 userInfo
字典,携带了通知发送的原因及前一个路由的描述。
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleRouteChange:)
name:AVAudioSessionRouteChangeNotification
object:[AVAudioSession sharedInstance]];
收到通知后查看保存在userInfo 字典中 AVAudioSessionRouteChangeReasonKey
判断路由变更的原因:
-
AVAudioSessionRouteChangeReasonNewDeviceAvailable
,连接新设备 -
AVAudioSessionRouteChangeReasonOldDeviceUnavailable
,移除设备
当有设备断开时,获取 userInfo
中描述前一个路由信息 的AVAudioSessionRouteChangePreviousRouteKey
,其整合在一个输入 NSArray
和一个输出 NSArray
中,判断其中第一个是否为 AVAudioSessionPortHeadphones
(耳机接口)。
- (void)handleRouteChange:(NSNotification *)notification {
NSDictionary *info = notification.userInfo;
AVAudioSessionRouteChangeReason reason =
[info[AVAudioSessionRouteChangeReasonKey] unsignedIntValue];
if (reason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
AVAudioSessionRouteDescription *previousRoute =
info[AVAudioSessionRouteChangePreviousRouteKey];
AVAudioSessionPortDescription *previousOutput = previousRoute.outputs[0];
NSString *portType = previousOutput.portType;
if ([portType isEqualToString:AVAudioSessionPortHeadphones]) {
//暂停
}
}
}
开启后台播放
音频应用程序所需的一项常见功能是后台播放音频。启用此功能后,当用户切换到另一个应用程序或锁定他们的 iOS 设备时,应用程序的音频可以继续播放。在 iOS 中启用 AirPlay 流式传输和画中画播放等高级播放功能也需要此功能。
配置这些功能的最简单方法是使用 Xcode。在 Xcode 中选择应用程序的目标,然后选择 Capabilities
选项卡。在“功能”选项卡下,将“后台模式”开关设置为“开”,然后从可用模式列表中选择“音频、AirPlay 和画中画”选项。