AVAudioSession
做项目的时候,合拍需求需要同时播放音视频,同时需要录制用户的声音。在做的过程中发现,不去设置AVAudioSession,则使用蓝牙耳机时,视频播放的声音仍然会通过扬声器播放,以及一些意想不到的问题。
AVAudioSession
会自带一些默认的设置,以便在没有设置的情况下,能够应对一些简单的场景
作用
简单来说,AVAudioSession
的作用分为三点:
- 协调多个App的音频播放
- 告诉系统如何使用音频:录音、播放、边录边播
- 设置输入输出设备
APP启动的时候会自动帮激活AVAudioSession
,APP维护一个AVAudioSession
的单例
1 | import AVFoundation |
AVAudioSession Category
目前AVAudioSession
所支持的category
共有 7 种:
Category | 是否会被静音 键或锁屏键静音 |
是否打断不支持混音 播放的应用 |
是否允许音频 输入/输出 |
解释 |
---|---|---|---|---|
AVAudioSessionCategoryAmbient | Yes | NO | 只输出 | 只播放音频,不会被打断 |
AVAudioSessionCategoryAudioProcessing | NO | YES | 无输入和输出 | 音频处理 |
AVAudioSessionCategoryMultiRoute | NO | YES | 支持输入和输出 | 支持音频播放和录制(允许多条音频流的同步输入和输出) |
AVAudioSessionCategoryPlayAndRecord | NO | 默认 YES,可重写开关置为 NO | 支持输入和输出 | 支持音频播放和录制。 |
AVAudioSessionCategoryPlayback | NO | 默认 YES,可重写开关置为 NO | 只输出 | 只播放音频,一般音乐播放器都会选择这个 |
AVAudioSessionCategoryRecord | NO(锁屏时依然保持录制) | YES | 只输入 | 只支持音频录制 |
AVAudioSessionCategorySoloAmbient(默认) | YES | YES | 只输出 | 只播放音频,会被打断 |
第一个坑:
如果AVAudioSession
处于inactive
的状态,那么setCategory
会在激活时才发送,不会立即生效,若处于active
状态,则立即生效。
1 | try? AVAudioSession.sharedInstance().setCategory(.playAndRecord) |
若当前App激活了AVAudioSession
,则其他App的AVAudioSession
会失效,若想让其他App重新也能被激活:
1 | AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation) |
小结:
到此为止。很清楚的可以看到,常见的:
若需求是单纯的音频播放,例如播放器等选择:AVAudioSessionCategoryPlayback
若需求是需要录制,例如录音机,录制视频等选择:AVAudioSessionCategoryRecord
若需求是需要录制同时播放声音,例如短视频合拍等选择:AVAudioSessionCategoryPlayAndRecord
AVAudioSession Option
Option | 说明 | 兼容的 Category | 解释 |
---|---|---|---|
AVAudioSessionCategoryOptionMixWithOthers | 允许和其他音频 mix | AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryPlayback AVAudioSessionCategoryMultiRoute | 例如:当前App播放的声音想与QQ音乐播放的声音并存 |
AVAudioSessionCategoryOptionDuckOthers | 智能调低冲突音频音量 | AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryPlayback AVAudioSessionCategoryMultiRoute | 例如:导航时,语音播报并不会打断QQ音乐的声音,只是让其他App声音变小 |
AVAudioSessionCategoryOptionAllowBluetooth | 允许蓝牙音频输入 | AVAudioSessionCategoryRecord AVAudioSessionCategoryPlayAndRecord | 若要支持蓝牙耳机,这个是必备的 |
AVAudioSessionCategoryOptionDefaultToSpeaker | 默认输出音频到扬声器 | AVAudioSessionCategoryPlayAndRecord | 将音频输出到扬声器 |
除此之外,在iOS9还提供了AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers
最新的iOS10又新加了两个AVAudioSessionCategoryOptionAllowBluetoothA2DP
、AVAudioSessionCategoryOptionAllowAirPlay
用来支持蓝牙A2DP耳机和AirPlay。
1 | /// 通过这个方法设置option |
第二个坑:
一般来说,所有的Category
和Option
都会遵循 last in wins
原则,即最后接入的音频设备作为输入或输出的主设备。同时用于播放音频的App都需要考虑到用户使用蓝牙耳机的情况,若不设置AVAudioSessionCategoryOptionAllowBluetooth
,则可能出现虽然链接蓝牙耳机,但在一边录制一边播放
的情况下,音频还是会从扬声器中播放出来。
AVAudioSession Mode
以上的Category
和Option
基本上能够满足大部分的App使用,对于特定的情况,例如通话、游戏,AVAudioSession
还有自己特殊的优化 Mode
:
Mode | 兼容的 Category | 说明 |
---|---|---|
AVAudioSessionModeDefault | All | 默认格式 |
AVAudioSessionModeVoiceChat | AVAudioSessionCategoryPlayAndRecord | VoIP 类型的应用 |
AVAudioSessionModeGameChat | AVAudioSessionCategoryPlayAndRecord | 适用于游戏类应用 |
AVAudioSessionModeVideoRecording | AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryRecord | 适用于使用摄像头采集视频的应用,搭配AVCaptureSession使用 |
AVAudioSessionModeMoviePlayback | AVAudioSessionCategoryPlayback | 适用于播放视频的应用 |
AVAudioSessionModeMeasurement | AVAudioSessionCategoryPlayAndRecord AVAudioSessionCategoryRecord AVAudioSessionCategoryPlayback | 最小化系统(??? 不是很清楚) |
AVAudioSessionModeVideoChat | AVAudioSessionCategoryPlayAndRecord | 视频聊天类型应用 |
几个需要注意的地方 |
AVAudioSessionModeVoiceChat
适用于VoIP(基于IP的语音传输英语:Voice over Internet Protocol,缩写为VoIP)这个模式下,会自动配置上AVAudioSessionCategoryOptionAllowBluetooth
和AVAudioSessionCategoryOptionDefaultToSpeaker
。系统将自动选择最佳的麦克风组合来支持视频聊天AVAudioSessionModeVideoRecording
适用于使用摄像头采集视频的应用, 与AVCaptureSession
结合来用可以更好地控制音视频的输入输出路径。(例如,设置automaticallyConfiguresApplicationAudioSession
属性,系统会自动选择最佳输出路径.)
状态监听
耳机
这部分直接上代码吧:
1 | /// 是否有耳机 |
判断耳机种类:
1 | func updateHeadPhonesStatus() { |
外设状态
首先需要注册一个AVAudioSession.routeChangeNotification
:
1 | observers.append(NotificationCenter.default.addObserver(forName: AVAudioSession.routeChangeNotification, object: nil, queue: .main) { [weak self] notification in |
目前状态是一个枚举,主要分为8种:
枚举值 | 意义 |
---|---|
AVAudioSessionRouteChangeReasonUnknown | 未知原因 |
AVAudioSessionRouteChangeReasonNewDeviceAvailable | 有新设备可用 |
AVAudioSessionRouteChangeReasonOldDeviceUnavailable | 老设备不可用 |
AVAudioSessionRouteChangeReasonCategoryChange | 类别改变了 |
AVAudioSessionRouteChangeReasonOverride | App重置了输出设置 |
AVAudioSessionRouteChangeReasonWakeFromSleep | 从睡眠状态呼醒 |
AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory | 当前Category下没有合适的设备 |
AVAudioSessionRouteChangeReasonRouteConfigurationChange | Rotuer的配置改变了 |
可以通过判断返回的枚举值,去做相应的处理,例如拔出耳机时,停止播放,更新耳机状体等等。
其他的坑
PlayAndRecord
在一般情况下,当没有接入任何音频设备时,声音会通过扬声器来播放,但是一旦设置了PlayAndRecord
,会将默认的输出设备转为听筒。
解决办法:
1 | // 第一种:通过overrideOutputAudioPort 方法设置 |
第三种:MPVolumeView 让用户自己选择
AirPods
AirPods在不同系统上的表现
AirPods 系列耳机,对于系统的要求不一样,具体要求可以去官网查看
例如:
AirPods在iOS10.2以下表现和普通的蓝牙耳机类似,能手动通过蓝牙连接上手机。
AirPods在iOS10.2以上的能支持双击操作,双击播放音乐,双击停止音乐;分别对应远程线控中的UIEventSubtypeRemoteControlPlay
、UIEventSubtypeRemoteControlStop
等事件。
判断是否是AirPod
1 | func isAirPods() -> Bool { |
1. 远程控制的坑
在使用AirPods 的情况下,AVAudioSession
的category 设置为AVAudioSessionCategoryPlayback
,此时APP只用于播放音频,能够自动适配远程控制。但是如果是AVAudioSessionCategoryPlayAndRecord
,此时APP既需要播放音频,也需要录制音频。不能自动适配远程控制
解决办法:
1 | // 设置option .alllowBluetooth PS: 设备需要的系统不满足使用 |
2. 音频输出的问题
对于一般情况:
AVAudioSessionCategoryPlayAndRecord
+AVAudioSessionModeDefault
+AVAudioSessionCategoryOptionDefaultToSpeaker
声音会通过扬声器播放出来AVAudioSessionCategoryPlayAndRecord
+AVAudioSessionModeDefault
声音会通过听筒播放出来
但是在AirPod的情况下:
AVAudioSessionCategoryPlayAndRecord
+AVAudioSessionModeDefault
+AVAudioSessionCategoryOptionAllowBluetoothA2DP
能连接上,并能远程控制。AVAudioSessionCategoryPlayAndRecord
+AVAudioSessionModeDefault
+AVAudioSessionCategoryOptionAllowBluetooth
能连接上,但是不能远程控制。
如果是设置的其它模式,比如设置了模式,坑坑坑坑
AVAudioSessionCategoryPlayAndRecord
+AVAudioSessionModeVoiceChat
AVAudioSessionCategoryPlayAndRecord
+AVAudioSessionModeDefault
+AVAudioSessionCategoryOptionDefaultToSpeaker
不能连上Airpods了,并且在控制中心面板中也没有AirPods选项。
3. 录制播放声音不清晰
打Log发现,默认的AVAudioSessionCategoryPlayback
模式下,耳机使用的模式是BluetoothA2DPOutput
但是 在AVAudioSessionCategoryPlayAndRecord
模式下,耳机使用的是BluetoothHFP
- HeadsetPro-file(HSP)代表耳机功能,提供手机与耳机之间通信所需的基本功能。
- HandProfile(HFP)则代表免提功能,HFP在HSP的基础上增加了某些扩展功能。
- Advanced Audio Distribution Profile(A2DP),指的是蓝牙音频传输模型协定。
HFP格式的蓝牙耳机支持手机功能比较完整,用户可在耳机上操作手机设定好的重拨、来电保留、来电拒听等免提选项功能。A2DP是高级音频传送规格,允许传输立体声音频信号,相比用于 HSP 和 HFP 的单声道加密,质量要好得多。
https://www.jianshu.com/p/04a1dc879c13
在Apple 官方描述中:
“If an application uses the setPreferredInput:error: method to select a Bluetooth HFP input, the output will automatically be changed to the Bluetooth HFP output. Moreover, selecting a Bluetooth HFP output using the MPVolumeView’s route picker will automatically change the input to the Bluetooth HFP input. Therefore both the input and output will always end up on the Bluetooth HFP device even though only the input or output was set individually.“
大众的解决方案:
维持原状的方案,就是继续采用HFP来进行音频的输入和输出,这种方案可以保证输入音频由支架的麦克风来提供,输出继续由支架来进行转发。但是,问题就是会导致音乐播放的音质差.
采用A2DP来进行高质量的蓝牙音频输出,保证歌曲、声音的播放质量,采用手机麦克风来进行音频输入而不通过蓝牙来采集音频。
总结
以上就是对于一些音频知识的总结,本篇主要是针对AVAudioSession
做的分析,若有问题,请还望多包涵和指正。下一小节将会去介绍音频处理相关的内容。
About this Post
This post is written by Aaron Liu, licensed under CC BY-NC 4.0.