一.概述
音频系统是Android系统的一个重要组成部分,本文章将会从上往下(Applicaiton到Framework)介绍安卓系统中的音频框架。为了减少大篇代码的枯燥,本文章以介绍音频框架的设计思想和原理为主,尽量地不贴代码。
在Android音频框架中,主要以下面部分组成:
Application:音频应用,如音乐播放器,录音机,收音机等。
-
Framework java层:
AudioTrack:负责回放数据的输出,属于应用框架 API 类
AudioRecord:负责录音数据的采集,属于应用框架 API 类
AudioSystem: 负责音频事务的综合管理,属于应用框架 API 类
-
Framework Libraries:
AudioTrack:负责回放数据的输出,属于本地框架 API 类
AudioRecord:负责录音数据的采集,属于本地框架 API 类
AudioSystem: 负责音频事务的综合管理,属于本地框架 API 类
AudioPolicyService:音频策略的制定者,负责音频设备切换的策略抉择、音量调节策略等
AudioFlinger:音频策略的执行者,负责输入输出流设备的管理及音频流数据的处理传输
整个框架主要模块如下图:
二.MediaPlayer与AudioTrack
播放声音可以用MediaPlayer和AudioTrack,两者都提供了java API供应用开发者使用。虽然都可以播放声音,但两者还是有很大的区别的。
MediaPlayer可以播放多种格式的声音文件,例如MP3,AAC,WAV,OGG,MIDI等。
AudioTrack 只能播放解码后的PCM数据流。PCM(Pulse Code Modulation)也被称为脉冲编码调制。PCM音频数据是未经压缩的音频采样数据裸流,它是由模拟信号经过采样、量化、编码转换成的标准的数字音频数据。
MediaPlayer 在 Native 层会创建对应的音频解码器和一个 AudioTrack,解码后的数据交由 AudioTrack 输出。
以下是AudioTrack的使用例子:
public void play() throws Exception {
final int TEST_SR = 22050; //采样率
final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO; //双声道
final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
final int TEST_MODE = AudioTrack.MODE_STREAM; //持续传输模式
final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; //音乐类型音频流
//-------- initialization --------------
int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
// 创建一个 AudioTrack 实例
AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
minBuffSize, TEST_MODE);
byte data[] = new byte[minBuffSize/2];
//-------- test --------------
// 调用 write() 写入回放数据
track.write(data, 0, data.length);
track.write(data, 0, data.length);
// 调用 play() 开始播放
track.play();
}
可以看到,使用AudioTrack需要给它指明音频流类型、采样率、声道类型,编码格式,最小的buffer大小和传输模式。
其中AudioTrack Java API 包括以下两种数据传输模式:
Transfer Mode | Description |
---|---|
MODE_STATIC | 应用进程将回放数据一次性付给 AudioTrack,适用于数据量小、时延要求高的场景 |
MODE_STREAM | 用进程需要持续调用 write() 写数据到 FIFO,写数据时有可能遭遇阻塞(等待 AudioFlinger::PlaybackThread 消费之前的数据),基本适用所有的音频场景 |
上面AudioTrack用到的steamType有以下几种类型:
Stream Type | Description |
---|---|
STREAM_VOICE_CALL | 电话语音 |
STREAM_SYSTEM | 系统声音 |
STREAM_RING | 铃声声音,如来电铃声、闹钟铃声等 |
STREAM_MUSIC | 音乐声音 |
STREAM_ALARM | 警告音 |
STREAM_NOTIFICATION | 通知音 |
STREAM_DTMF | DTMF 音(拨号盘按键音) |
定义这么多的类型主要有两种好处:
音频流的音量管理,调节一个类型的音频流音量,不会影响到其他类型的音频流
根据流类型选择合适的输出设备;比如插着有线耳机期间,音乐声(STREAM_MUSIC)只会输出到有线耳机,而铃声(STREAM_RING)会同时输出到有线耳机和外放
在AudioTrack的构造函数中会启动一个AudioTrackThread,这条线程的作用就是不断地往底层传输流数据和报告数据传输状态。接下来,流数据交由AudioFlinger 与AudioPolicyService 继续处理。
三. AudioFlinger 与AudioPolicyService
AudioFlinger - 策略的执行者:具体与音频设备进行通信。维护现有系统中的音频设备以及多个音频里的混音如何处理。
AudioPolicyService - 策略的制定者:什么时候打开音频接口的设备,某种stream类型的音频对应什么设备。
1.AudioPolicyService
什么时候打开设备?
AudioPolicyService在系统开机时由init进程启动,在它的构造函数里,会读取设备的配置文件。配置文件里定义了设备支持哪些设备接口。目前Audio系统支持的音频设备接口分为3类:主音频设备,蓝牙A2DP音频和USB音频。读取完设备接口后,AudioPolicyService会通知AudioFlinger打开设备接口对应的音频输出通道。
音频对应什么设备?
要找到某种stream类型对应什么设备,AudioPolicyService会进行以下三个流程:
1.获取stream音频类型对应的策略
每种stream类型都有对应的路由策略,如下表:
STREAM_TYPE | STRATEGY |
---|---|
VOICE_CALL | STRATEGY_PHONE |
BLUETOOTH_SCO | STRATEGY_PHONE |
RING | STRATEGY_SONIFICATION |
ALARM | STRATEGY_SONIFICATION |
NOTIFICATION | STRATEGY_SONIFICATION_RESPECTFUL |
DTMF | STRATEGY_DTMF |
SYSTEM | STRATEGY_MEDIA |
TTS | STRATEGY_MEDIA |
MUSIC | STRATEGY_MEDIA |
DEFAULT(默认情况) | STRATEGY_MEDIA |
ENFORCED_AUDIBLE | STRATEGY_ENFORCED_AUDIBLE |
2.为策略匹配最佳的设备
按照一定的优先级匹配系统中已经存在的所有音频设备。
这个优先级因为策略的不同而有所不同。以STRATEGY_MEDIA为例,首先如果平台有蓝牙A2dp,并且蓝牙A2dp通道可以正常打开,没有挂起,当前也没有强制不使用A2dp,那么开始寻找合适的A2dp设备。之前通过一个mAvailableOutputDevices变量来保存所有可用的设备,然后先用这个变量与AUDIO_DEVICE_OUT_BLUETOOTH_A2DP做&操作,看是否支持该设备,若不支持,再通过mAvailableOutputDevice与AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES做&操作看是否支持,若还不支持继续与A2dp_Speaker匹配...如果这步匹配不成功,继续匹配第二级wired headphone,第三级wired headset,第四级usb accessory等直到匹配到一个设备。基本不不存在找不到设备的情况。
3.为设备选择最优的output
AudioPolicyService会把之前AudioFlinger打开的所有output(音频输出通道)存储到mOutputs键值对上。这时可以遍历这个键值对,寻找支持上一步得到的设备的output。因为每个output都支持若干个设备,所以通常得到支持一个设备的output不止一个,所以需要找一个合适的output。选择的过程就是遍历所有output,寻找一个与从AudioTrack的set函数传到AudioSystem::getOutput函数的一个flags参数 匹配度最大的一个output。这个匹配度就是一次“与”运算,然后保存遍历过程最大的一次与运算结果。flags中的每一个16进制位对应一个output特性,例如是否需要混音器,是否支持fast tracks。
AudioFlinger
具体与音频设备进行通信
音频硬件的抽象层的服务对象是AudioFlinger,这说明了AudioFlinger可以不用直接调用底层的音频驱动,另一方面,AudioFlinger的上层(包括和它同一层的MediaPlayerService)模块只需要与它通信就可以实现音频相关的功能。
在上面讲解AudioPolicyService的时候提到过,AudioPolicyService读取完设备接口后,会通知AudioFlinger打开设备接口对应的音频输出通道。具体的过程是调用AudioFlinger的openOutput方法。在该方法的内容可以分为以下几个:
1.打开设备通道
把上层传下的的flags变量传给HAL 层让它打开相关类型的输出流设备
2.新建或选择一个回放线程
回放线程的作用是不断读取AudioTrack传输的数据,然后把数据交给HAL去处理。
从 Audio HAL 中,我们通常看到如下 4 种输出流设备,分别对应着不同的播放场景:
primary_out:主输出流设备,用于铃声类声音输出,对应着标识为 AUDIO_OUTPUT_FLAG_PRIMARY 的音频流和一个 MixerThread 回放线程实例
low_latency:低延迟输出流设备,用于按键音、游戏背景音等对时延要求高的声音输出,对应着标识为 AUDIO_OUTPUT_FLAG_FAST 的音频流和一个 MixerThread 回放线程实例
deep_buffer:音乐音轨输出流设备,用于音乐等对时延要求不高的声音输出,对应着标识为 AUDIO_OUTPUT_FLAG_DEEP_BUFFER 的音频流和一个 MixerThread 回放线程实例
compress_offload:硬解输出流设备,用于需要硬件解码的数据输出,对应着标识为 AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD 的音频流和一个 OffloadThread 回放线程实例
一个PlaybackThread的输出对应一种设备。系统启动时,就已经打开 primary_out、low_latency、deep_buffer 这三种输出流设备,并创建对应的 PlaybackThread 了。
四.AudioTrack、AudioPolicyService和AudioFlinger的交互
前两小节分别介绍了AudioTrack、AudioPolicyService和AudioFlinger的作用。接着来看看它们之间实际是怎么共同实现音频的播放的。
AudioTrack和AudioFlinger在不同的进程,所以它们的通信需要跨进程。在AudioFlinger构造时,它会在ServerManager中注册,并以“media.audio_flinger”为服务名。同时,Android系统在AudioTrack与底层服务间提供了AudioSystem和AudioService。在AudioTrack的构造函数里,会调用AudioSystem的getOutput方法去获取输出通道。该getOutput方法实际是通过跨进程binder,通知AudioPolicyService的AudioPolicyManager来让AudioFlinger去打开输出标识对应的输出流设备并找到或创建相应的PlaybackThread。然后AudioFlinger会产生一个全局唯一的audio_io_handle_t值,这个值是作为PlaybackThread键值对的key与该PlaybackThread相对应的。接着把这个 audio_io_handle_t返回给AudioTrack,后续AudioTrack就可以利用这个值去找到对应的PlaybackThread。
拿到audio_io_handle_t后,AudioTrack需要继续跟AudioFlinger进行跨进程通信。通过把之前返回的audio_io_handle_t作为参数去调用binder的方法,来跨进程地在AudioFlinger中找到audio_io_handle_t对应的PlaybackThread,并且在PlaybackThread中新建一个音频流管理对象 track。track 构造时会分配一块匿名共享内存用于 AudioFlinger 与 AudioTrack 的数据交换。track是跟AudioTrack一一对应起来的,它提供了start,pause,stop等方法,TrackAudioTrack可以利用它来控制音频流。当然AudioTrack与AudioFlinger不在同进程,所以不是直接调用track,而是把track 的通讯代理 IAudioTrack 作为返回值返回给 AudioTrack。
上一步返回的IAudioTrack也是一个Binder服务,AudioTrack拿到它后,就可以直接获得PlaybackThread在创建track时创建的那一块匿名共享内存,然后直接把数据持续写入到这块内存当中。同时PlaybackThread也会持续地从这块内存当中读取数据并交给output通道去播放。
至此,已经对安卓的音频框架做了个大概的介绍,后面有时间希望可以写更多音视频相关的文章。