从上往下认识安卓音频框架

一.概述

音频系统是Android系统的一个重要组成部分,本文章将会从上往下(Applicaiton到Framework)介绍安卓系统中的音频框架。为了减少大篇代码的枯燥,本文章以介绍音频框架的设计思想和原理为主,尽量地不贴代码。

在Android音频框架中,主要以下面部分组成:

  • Application:音频应用,如音乐播放器,录音机,收音机等。

  • Framework java层:

    AudioTrack:负责回放数据的输出,属于应用框架 API 类

    AudioRecord:负责录音数据的采集,属于应用框架 API 类

    AudioSystem: 负责音频事务的综合管理,属于应用框架 API 类

  • Framework Libraries:

    AudioTrack:负责回放数据的输出,属于本地框架 API 类

    AudioRecord:负责录音数据的采集,属于本地框架 API 类

    AudioSystem: 负责音频事务的综合管理,属于本地框架 API 类

    AudioPolicyService:音频策略的制定者,负责音频设备切换的策略抉择、音量调节策略等

    AudioFlinger:音频策略的执行者,负责输入输出流设备的管理及音频流数据的处理传输

    整个框架主要模块如下图:

Android音频框架.png

二.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的作用。接着来看看它们之间实际是怎么共同实现音频的播放的。

音频框架通信.png

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通道去播放。

至此,已经对安卓的音频框架做了个大概的介绍,后面有时间希望可以写更多音视频相关的文章。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,194评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,058评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,780评论 0 346
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,388评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,430评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,764评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,907评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,679评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,122评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,459评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,605评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,270评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,867评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,734评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,961评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,297评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,472评论 2 348

推荐阅读更多精彩内容