iOS 音频处理总结
前言
前段时间在阅读苹果音频文档(均列在参考资料一节里面了),并做了一些音频相关的开发(主要是带回音消除的录音)。这里做一个总结。
关于 Audio Session
每一个 app 带有一个 AVAudioSession
的单例(也就是说正常情况下你无法获得第二个 AVAudioSession
实例)。iOS 系统上每个 app 有各自不同的 AVAudioSession
实例。通过使用这个实例的方法可以告诉系统当前的 app 是怎样使用手机的音频服务的,然后系统会根据每个 app 的配置进行相应的协调,尽量满足所有 app 的请求,当无法满足的时候,系统尽量满足前台 app 的要求或者系统电话服务等。比如说,如果当前另外一个 app 正在播放的话,当前 app 可能希望能将其播放的音频和其他 app 的音频一起播放,而不是暂停其他 app 的音频服务;又比如说当前 app 需要播放音频或者进行录音;比如当前 app 只是播放音频;比如当前 app 只是录音;比如当前 app 播放音频时候屏蔽所有其他 app 的音频等等,总之就是告诉系统当前 app 是如何使用它的音频服务的。
Audio Session 有三个比较重要的概念:
- category
- mode
- option
通过配置这三个内容,表达了当前 app 的使用音频服务的具体意图。
在某些情况下,我们不需要配置 Audio Session 的 category,比如如果使用 AVAudioRecorder 来录音,并不需要配置 category 为 AVAudioSessionCategoryRecord
,因为系统在我们使用 AVAudioRecorder 的录音服务的时候已经为我们配置了。同时 Audio Session 有默认配置(如果当前 app 不进行配置的话)。当默认配置无法满足需求的时候,就可以手动配置 Audio Session
Audio Session 另一个重要功能是配置系统音频服务硬件参数,比如配置输入的声道数,采样率,IO 缓存时间等等。其 API 中 setPreferred__ 开头的方法作用就是这些。
配置完 Audio Session 以后,当我们要求的音频服务受到打断(比如,电话来了,则系统要停止录音和播放;比如,app 退到后台运行了,如果没有配置后台运行的话,系统也会停止当前 app 的音频服务;),我们可以使用通知中心的方式来监听,并做一下相应的处理。音频服务中断有两个概念比较重要,就是中断开始以及中断结束,我们可以在中断开始的时候记录当前播放时间点,中断结束的时候重新开始播放(当然系统默认行为是会在中断结束时重新开始播放音频,但是如果默认行为无法满足需求时候,就需要自行处理了)。
Audio Session 另一个重要我们需要监听的变化是路由变化 (Route Change)。比如有新的输出源来了(比如用户把耳机插进去或者是用户开始使用蓝牙耳机),或者原来的输出源不可用了(用户拔掉耳机等)。
还有一些其他的功能,比如当前其他 app 是否在播放音频,请求麦克风权限等,可以查看具体的 API 文档 AVAudioSession。
Audio Queue Service
使用 Audio Queue Service 我们可以做到录音或者播放音频。当然我们使用 AVAudioPlayer 也能很简单的做播放音频功能,那为什么要用到 Audio Queue Service 呢?它有几个优点
- 设想你的要严格同步不同音频的播放。 Audio Queue Service 的回调函数包含相应的时间,来满足你的需求。
- 如果是播放音频用 Audio Queue Service,在将音频数据给它的回调函数之前做一些处理,比如变声等。
- 如果是录音,可以对回调函数传回来的音频数据做处理,比如写到文件或者对这些音频数据进行其他任何处理
理解 Audio Queue Service 比较重要的是它的 buffer queue。拿录音来说,一般设置的缓存是3个。首先通过 AudioQueueEnqueueBuffer
将可用缓存提供给相应的 queue。然后系统开始将记录下的音频数据放到第一个缓存,当缓存满的时候,回调函数会将该 buffer 返回给你并将该缓存出列,在回调函数中我们可以对这些数据进行处理,与此同时系统开始将数据写到第二个缓存,当我们的回调函数处理完第一个返回的缓存时候,我们需要重新使用 AudioQueueEnqueueBuffer
将该缓存入列,以便系统再次使用。当第二个缓存返回的时候,系统开始往第三个缓存写数据,写完之后返回第三个缓存,并开始往之前返回的第一个缓存写数据。这就是一个典型的队列结构(先进先出,后进后出)。
Audio Unit
Audio Unit 是所有 iOS 以及 macOS 上音频框架的最底层,无论使用的是 AVAudioRecorder、AVAudioPlayer、或者 Audio Queue Service、OpenAL 等,最终底层实现都是通过 Audio Unit 来完成的。
在 iOS 上可用的 audio unit 是有限的,macOS 上面可以自定义一个 audio unit 但是 iOS 上不行,只能使用系统提供的 audio unit。
什么时候使用 Audio Unit ?官方的说法是,当你需要高度可控的、高性能、高灵活性或者需要某种特别的功能(比如回音消除,只在 audio unit 提供支持,所有高层 API 均不支持回音消除)的时候,才需要使用 audio unit。
有4类 audio unit(具体用途看名字就能理解):
- Effect
- Mixing
- I/O
- Format convert
使用 audio unit 有两种方式:
- 直接使用
- 混合构建使用,AUGraph
第一种方式是对于比较简单的结构。
第二种方式是用于构建复杂的音频处理流程。配置具体的 audio unit 的属性的时候还是会用到直接使用种的方法。
Audio Unit 重要概念
audio unit 重要的概念是 scope 和 element。scope 包含 element。
scope 分三种:
- Input scope
- Output scope
- global scope
scope 概念有一点抽象,可以这样理解 scope,比如 input scope 表示里面所有的 element 都需要一个输入。output scope 表示里面所有的 element 都会输出到某个地方。至于 global scope,应该是用来配置一些和输入输出概念无关的属性。
element 官方的解释是可以理解成 bus,就是将数据从 element 的一头传到另一头。
其他
- 音频格式转换
- 音频(流)读写
iOS 录音的几种方式
- 使用 AVAudioRecorder
- 使用 Audio Queue Service
- 使用 Audio Unit
- 使用 OpenAL
iOS 播放音频的方式
- 使用 AVAudioPlayer
- 使用 AVPlayer
- 使用 System Sound Services
- 使用 Audio Queue Service
- 使用 Audio Unit
- 使用 OpenAL
一些需要思考的问题
- 如何获取当前扬声器播放的音频数据?(包括其他 app)
- 如何实时录音,同时当前手机正在播放音频
- 如何做回音消除,或者不借助系统提供的回音消除功能来完成回音消除的需求?
这里有些问题我也不知道如何解答,若有了解的,请多多指教一下。
一些有用的开源代码
- aurioTouch 官方的关于 audio unit 使用 demo 代码。注意其中关于 AVAudioSession 配置的顺序是错的,你可以看它的代码和 AVAudioSession Api 的说明来知道错误的地方
- XBEchoCancellation 可以学习其中关于回音消除的使用。官方的 aurioTouch demo 简单的改 audio unit 类型为 voiceprocess 也可以做回音消除,但是当涉及到同时播放音频时,官方 demo 在某些 iPhone 上会失败,所以建议参考这个源码