Android 音频(Audio)架构
Posted 无意的青月
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 音频(Audio)架构相关的知识,希望对你有一定的参考价值。
一、概述
android 的音频硬件抽象层 (HAL) 可将 android.media 中特定于音频的较高级别的框架 API 连接到底层音频驱动程序和硬件。本部分介绍了有关提升性能的实现说明和提示。
Android 音频架构定义了音频功能的实现方式,并指出实现中所涉及的相关源代码。
应用框架
应用框架包含应用代码,该代码使用 android.media API 与音频硬件进行交互。在内部,此代码会调用相应的 JNI 粘合类来访问与音频硬件互动的原生代码。
- 源代码目录:frameworks/base/media/java/android/media/
- AudioManager:音频管理器,包括音量管理、AudioFocus管理、音频设备管理、模式管理;
- 录音:AudioRecord、MediaRecorder;
- 播放:AudioTrack、MedaiPlayer、SoundPool、ToneGenerator;
- 编解码:MediaCodec,音视频数据 编解码接口。
JNI
与 android.media 关联的 JNI 代码会调用较低级别的原生代码来访问音频硬件。JNI 位于 frameworks/base/core/jni/ 和 frameworks/base/media/jni 中。
Native framework 原生框架
原生框架提供相当于 android.media 软件包的原生软件包,它调用 Binder IPC 代理来访问媒体服务器的音频专属服务。 原生框架代码位于 frameworks/av/media/libmedia 中。
原生框架代码位于 frameworks/av/media/libmedia 或frameworks/av/media/libaudioclient中(不同版本,位置有所改变)。
Binder IPC
Binder IPC 代理用于促进跨越进程边界的通信。代理位于 frameworks/av/media/libmedia 中,并以字母“I”开头。
Audio Server 媒体服务器
Audio系统在Android中负责音频方面的数据流传输和控制功能,也负责音频设备的管理。这个部分作为Android的Audio系统的输入/输出层次,一般负责播放PCM声音输出和从外部获取PCM声音,以及管理声音设备和设置(注意:解码功能不在这里实现,在android系统里音频视频的解码是opencore或stagefright完成的,在解码之后才调用音频系统的接口,创建音频流并播放)。Audio服务在Android N(7.0)之前存在于mediaserver中,Android N开始以audioserver形式存在,这些音频服务是与HAL 实现进行交互的实际代码。媒体服务器位于 frameworks/av/services/audioflinger 和frameworks/av/services/audiopolicy中。
Audio服务包含AudioFlinger 和AudioPolicyService:
- AudioFlinger:主要负责音频流设备的管理以及音频流数据的处理传输,⾳量计算,重采样、混⾳、⾳效等。
- AudioPolicyService:主要负责⾳频策略相关,⾳量调节⽣效,设备选择,⾳频通路选择等。
HAL
HAL 定义了音频服务会调用且您必须实现才能使音频硬件正常运行的标准接口。音频 HAL 接口位于 hardware/libhardware/include/hardware 中。详情可参阅 audio.h。
内核驱动程序
音频驱动程序用于同您的硬件和 HAL 实现进行交互。可以使用高级 Linux 声音架构 (ALSA)、开放声音系统 (OSS) 或自定义驱动程序(HAL 与驱动程序无关)。
注意:如果使用的是 ALSA,建议将 external/tinyalsa 用于驱动程序的用户部分,因为它具有兼容的许可(标准的用户模式库已获得 GPL 许可)。
二、音频系统架构的演进
一个好的系统架构,需要尽可能地降低上层与具体硬件的耦合,这既是操作系统的设计目的,对于音频系统也是如此。音频系统的雏形框架可以简单的用下图来表示:
在这个图中,除去Linux本身的Audio驱动外,整个Android音频实现都被看成了User。因而我们可以认为Audio Driver就是上层与硬件间的“隔离板”。但是如果单纯采用上图所示的框架来设计音频系统,对上层应用使用音频功能是不小的负担,显然Android开发团队还会根据自身的实际情况来进一步细化“User”部分。具体该怎么细化呢?如果是让我们去细化我们该怎么做呢?
首先作为一个操作系统要对外提供可用的API,供应用开发者调用。APP开发者开发的应用我们称APP,我们提供的API姑且叫Framework。如果Framework直接和驱动交互有什么问题呢?
-
首先是耦合问题,接口和实现耦合,硬件层有任何变动都需要接口层适配,我们增加一层硬件适配层;
-
资源统一管理的问题,如果多个APP调用相同API使用硬件资源,改怎么分配?增加统一资源管理器,其实就是对应Android系统的Audio Lib层。
细化后我们发现,整个结构对应的就就是Android的几个层次结构,包括应用层、framework层、库层以及HAL层,如下图所示:
2.1 Lib层
framework层的大多数类,其实只是应用程序使用Android库文件的“中介”,它只是个壳子。因为Android应用采用java语言编写,它们需要最直接的java接口的支持,如果我们的Android系统支持另一种语言的运行时,那么可以提供另一种语言的接口支持(比如Go),这就是framework层存在的意义之一。但是作为“中介”,它们并不会真正去实现具体的功能,或者只实现其中的一部分功能,而把主要重心放在核心库中来完成。比如上面的AudioTrack、AudioRecorder、MediaPlayer和MediaRecorder等等在库中都能找到相对应的类,这些多数是C++语言编写的。
我们再从另一个线索来思考这个问题:我们提供的API供应用层调用,那么这个API最终运行在应用的进程中。如果多个应用同时使用这个功能就会冲突;再一个允许任何一个进程操作硬件也是个危险的行为。那么真相就浮出了水面:我们需要一个有权限管理和硬件交互的进程,需要调用某个硬件服务必须和我这个服务打交道。这就是Android系统的很常用的C/S结构以及Binder存在的主要原因。Android系统中的Server就是一个个系统服务,比如ServiceManager、LocationManagerService、ActivityManagerService等等,以及管理图像合成的SurfaceFlinger,和今天我们今天介绍的音频服务AudioFlinger和AudioPolicyService。它们的代码放置在frameworks/av/services/audioflinger,生成的最主要的库叫做libaudioflinger。
这里也提到了分析源码除以模块为线索外的另一种线索以进程为线索。库并不代表一个进程,但是进程则依赖于库来运行。虽然有的类是在同一个库中实现的,但并不代表它们会在同一个进程中被调用。比如AudioFlinger和AudioPolicyService都驻留于名为mediaserver的系统进程中;而AudioTrack/AudioRecorder和MediaPlayer/MediaRecorder只是应用进程的一部分,它们通过binder服务来与其它audioflinger等系统进程通信
2.2 HAL层
硬件抽象层顾名思义为适配不同硬件而独立封装的一层,音频硬件抽象层的任务是将AudioFlinger/AudioPolicyService真正地与硬件设备关联起来,但又必须提供灵活的结构来应对变化。
从设计上来看,硬件抽象层是AudioFlinger直接访问的对象。这里体现了两方面的考虑:
-
一方面AudioFlinger并不直接调用底层的驱动程序;
-
另一方面,AudioFlinger上层(包括和它同一层的MediaPlayerService)的模块只需要与它进行交互就可以实现音频相关的功能了。
AudioFlinger和HAL是整个架构解耦的核心层,通过HAL层的audio.primary等库抹平音频设备间的差异,无论硬件如何变化,不需要大规模地修改上层实现,保证系统对外暴露的上层API不需要修改,达成高内聚低耦合。而对厂商而言,在定制时的重点就是如何在这部分库中进行高效实现了。
举个例子,以前Android系统中的Audio系统依赖于ALSA-lib,但后期就变为了tinyalsa,这样的转变不应该对上层造成破坏。因而Audio HAL提供了统一的接口来定义它与AudioFlinger/AudioPolicyService之间的通信方式,这就是audio_hw_device、audio_stream_in及audio_stream_out等等存在的目的,这些Struct数据类型内部大多只是函数指针的定义,是一个个句柄。当AudioFlinger/AudioPolicyService初始化时,它们会去寻找系统中最匹配的实现(这些实现驻留在以audio.primary.*,audio.a2dp.*为名的各种库中)来填充这些“壳”,可以理解成是一种“多态”的实现。
三、Linux平台下的两种主要的音频驱动架构接收
3.1 OSS(Open Sound System)
早期Linux版本采用的是OSS框架,它也是Unix及类Unix系统中广泛使用的一种音频体系。OSS既可以指OSS接口本身,也可以用来表示接口的实现。OSS的作者是Hannu Savolainen,就职于4Front Technologies公司。由于涉及到知识产权问题,OSS后期的支持与改善不是很好,这也是Linux内核最终放弃OSS的一个原因。
另外,OSS在某些方面也遭到了人们的质疑,比如:
- 对新音频特性的支持不足;
- 缺乏对最新内核特性的支持等等。
当然,OSS做为Unix下统一音频处理操作的早期实现,本身算是比较成功的。它符合“一切都是文件”的设计理念,而且做为一种体系框架,其更多地只是规定了应用程序与操作系统音频驱动间的交互,因而各个系统可以根据实际的需求进行定制开发。总的来说,OSS使用了如下表所示的设备节点:
设备节点 | 说明 |
---|---|
/dev/dsp | 向此文件写数据à输出到外放Speaker向此文件读数据à从Microphone进行录音 |
/dev/mixer | 混音器,用于对音频设备进行相关设置,比如音量调节 |
/dev/midi00 | 第一个MIDI端口,还有midi01,midi02等等 |
/dev/sequencer | 用于访问合成器(synthesizer),常用于游戏等效果的产生 |
3.2 ALSA(Advanced Linux Sound Architecture)
ALSA是Linux社区为了取代OSS而提出的一种框架,是一个源代码完全开放的系统(遵循GNU GPL和GNU LGPL)。ALSA在Kernel 2.5版本中被正式引入后,OSS就逐步被排除在内核之外。当然,OSS本身还是在不断维护的,只是不再为Kernel所采用而已。
ALSA相对于OSS提供了更多,也更为复杂的API接口,因而开发难度相对来讲加大了一些。为此,ALSA专门提供了一个供开发者使用的工具库,以帮助他们更好地使用ALSA的API。根据官方文档的介绍,ALSA有如下特性:
- 高效支持大多数类型的audio interface(不论是消费型或者是专业型的多声道声卡)
- 高度模块化的声音驱动
- SMP及线程安全(thread-safe)设计
- 在用户空间提供了alsa-lib来简化应用程序的编写
- 与OSS API保持兼容,这样子可以保证老的OSS程序在系统中正确运行
ALSA主要由下表所示的几个部分组成:
Eliment | Description |
---|---|
alsa-driver | 内核驱动包 |
alsa-tools | 包含一系列工具程序 |
alsa-utils | 包含了很多使用的小程序,比如alsactl:用于保存设备设置amixer:是一个命令行程序,用于音量和其他声音控制alsamixer的ncurses版acconnect和aseqview:制作MIDI连接,以及检查已经连接的端口列表,aplay和arecord:两个命令行程序,分别用于播放和录制多种格式的音频 |
alsa-plugins | 插件包,比如jack,pulse,maemo |
alsa-oss | 用于兼容OSS的模拟包 |
pyalsa | 用于编译python版本的alsa lib |
alsa-lib | 用户空间函数 |
alsa-firmware | 音频固件支持包 |
Alsa主要的文件节点如下: |
- Information Interface (/proc/asound)
- Control Interface (/dev/snd/controlCX)
- Mixer Interface (/dev/snd/mixerCXDX)
- PCM Interface (/dev/snd/pcmCXDX)
- Raw MIDI Interface (/dev/snd/midiCXDX)
- Sequencer Interface (/dev/snd/seq)
- Timer Interface (/dev/snd/timer)
Android的TinyALSA是基于Linux ALSA基础改造而来。一看“Tiny”这个词,我们应该能猜到这是一个ALSA的缩减版本。实际上在Android系统的其它地方也可以看到类似的做法——既想用开源项目,又嫌工程太大太繁琐,怎么办?那就只能瘦身了,于是很多Tiny-XXX就出现了。
在早期版本中,Android系统的音频架构主要是基于ALSA的,其上层实现可以看做是ALSA的一种“应用”。后来可能是由于ALSA所存在的一些不足,Android后期版本开始不再依赖于ALSA提供的用户空间层的实现。HAL层最终依赖alsa-lib库与驱动层交互。
Android Audio知识梳理 看完这一篇就够了!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~zzZ
文章目录
前言
Audio是安卓里面非常重要的模块,对于学习安卓开发不管是做APP或是系统层以及BSP的同行都可以进行学习,拓宽知识维度,为以后的工作提供便利。
本文语言通俗易懂,内容由简入繁,看不懂的地方可以反复琢磨,多多动脑思考。
坚持看完哦!
一、Audio基础
1.音频基础属性
音频指人耳可以听到的声音频率在 20HZ~20kHz 之间的声波。
声音的三要素:
1、音量(Volume)
也叫做响度(Loudness),人耳对声音强弱的主观感觉就是响度,响度和声波 振动的幅度有关。
2、音调(Pitch)
人耳对声音高低的感觉称为音调(也叫音频),音调主要与声波的频率有关。声波的频率高,则音调也高。
3、音色(Quality)
不同的发声体由于其材料、结构不同,则发出声音的音色也不同。
2.音频格式
音频格式是指要在计算机内播放或是处理音频文件,是对声音文件进行数、模转换的过程。
常见音频格式:
1、WAV
是微软公司专门为Windows开发的一种标准数字音频文件,该文件能记录各种单声道或立体声的声音信息,并能保证声音不失真。但是,占用空间太大。
2、MIDI
MIDI是Musical Instrument Digital Interface的缩写,又称作乐器数字接口,是数字音乐/电子合成乐器的统一国际标准。
MIDI 传输的不是声音信号, 而是音符、控制参数等指令, 它指示MIDI 设备要做什么,怎么做, 如演奏哪个音符、多大音量等。它们被统一表示成MIDI 消息(MIDI Message) 。
3、MP3
MP3是利用人耳对高频声音信号不敏感的特性,将时域波形信号转换成频域信号,并划分成多个频段,对不同的频段使用不同的压缩率,对高频加大压缩比(甚至忽略信号)对低频信号使用小压缩比,保证信号不失真。从而将声音用1∶10甚至1∶12的压缩率压缩。
这种压缩方式的全称叫MPEG Audio Player3,简称为MP3。
4、AAC
出现于1997年,基于MPEG-2的音频编码技术。由Fraunhofer IIS、杜比实验室、AT&T、索尼等公司共同开发,目的是取代MP3格式。
AAC,全称Advanced Audio Coding,是一种专为声音数据设计的文件压缩格式。与MP3不同,它采用了全新的算法进行编码,更加高效,具有更高的“性价比”。利用AAC格式,可使人感觉声音质量没有明显降低的前提下,更加小巧。苹果ipod、诺基亚手机支持AAC格式的音频文件。
3.音频处理
日常生活中我们听到的声波波形信号都是时间连续的,我们称这种信号为模拟信号,模拟信号需要量化成数字信号(离散、不连续的)以后才能被我们的计算机识别。
1、音频的量化
可以简单分为五个步骤:
(1)模拟信号采集
现实生活中的声音表现为连续的、平滑的波形,其横坐标为时间轴,纵坐标表示声音的强弱。
(2)采样
按照一定的时间间隔在连续的波上进行采样取值。
(3)量化
将采样得到的值进行量化处理,也就是给纵坐标定一个刻度,记录下每个采样的纵坐标的值。
(4)编码
将每个量化后的样本值转换成二进制编码,可以看到模拟信号经过采样、量化、编码后形成的二进制序列就是数字音频信号。
(5)数字信号
将所有样本二进制编码连起来存储在计算机上就形成了数字信号。
2、音频处理参数
(1)采样率
单位时间内对模拟信号的采样次数,也就是采样频率,采样频率越高,声音的还原就越真实越自然,当然数据量就越大。
我们日常生活中常见的采样率:
5kHz:仅能满足人们讲话的声音质量
8KHz:电话所用采样率, 对于人的说话已经足够
22.05KHz:达到 FM 广播的声音品质(适用于语音和中等品质的音乐)
44.1KHz:最常见的采样率标准,理论上的 CD 音质界限,可以达到很好的听觉效果
48KHz:比 CD 音质更加精确一些
对于高于 48KHz 的采样频率人耳已无法辨别出来了,所以在电脑上没有多少使用价值。
(2)采样位数
每个采样点能够表示的数据范围,用多少个 bit 表示。采样位数通常有 8 bits 或 16 bits 两种,采样位数越大,所能记录声音的变化度就越细腻,相应的数据量就越大。8 位字长量化(低品质)和 16 位字长量化(高品质),16 bit 是最常见的采样精度。
采样位数也被叫做采样精度、量化级、量化数据位数等。
比较一下,一段相同的音乐信息,16位声卡能把它分为64K个精度单位进行处理,而8位声卡只能处理256个精度单位, 造成了较大的信号损失,最终的采样效果自然是无法相提并论的。
(3)通道数
为了播放声音时能够还原真实的声场,在录制声音时在前后左右几个不同的方位同时获取声音,每个方位的声音就是一个声道。声道数是声音录制时的音源数量或回放时相应的扬声器数量,有单声道、双声道、多声道。
(4)码率
码率高低直接影响音质,码率高音质好,码率低音质差。
码率就是数据传输时单位时间传送的数据位数,一般我们用的单位是kbps即千位每秒。
就是一种音乐每秒播放的数据量,单位用bit表示,也就是二进制位。 bps就是比特率。一个字节相当于8个二进制位。也就是说128bps的4分钟的歌曲的文件大小是这样计算的:
码率 = 采样率 * 采样位数 * 声道数
(128/8)460=3840kB=3.8MB
(5)帧
音频数据是流式的,本身没有明确的一帧帧的概念,在实际的应用中,为了音频算法处理/传输的方便,一般约定俗成取 2.5ms~60ms 为单位的数据量为一帧音频。这个时间被称之为 “采样时间”,其长度没有特别的标准,它是根据编解码器和具体应用的需求来决定的。
音频帧的播放时间 = 一个AAC帧对应的采样样本的个数 / 采样频率(单位为s)。
音频在量化得到二进制的码字后,需要进行变换,而变换(MDCT)是以块为单位(block)进行的,一个块由多个(120或128)样本组成。而一帧内会包含一个或者多个块。帧的常见大小有960、1024、2048、4096等。一帧记录了一个声音单元,它的长度是样本长度和声道数的乘积。
例如,最常见的音频格式 MP3 的数据通常由两部分组成,一部分为 “ID3” 用来存储歌名、演唱者、专辑、音轨数等信息,另一部分为音频数据。音频数据部分以帧(frame)为单位存储,每个音频都有自己的帧头,每个帧头中存储了采样率等解码必须的信息,所以每一个帧都可以独立于文件存在和播放,这个特性加上高压缩比使得 MP3 文件成为了音频流播放的主流格式。
最后给出一个公式:
文件时长 = (文件总大小 - 头信息)/ (采样频率 * 采样位数 * 通道数 / 8)
3、音频编码
从信息论的观点来看,描述信源的数据是信息和数据冗余之和,即:数据=信息+数据冗余。音频信号在时域和频域上具有相关性,也即存在数据冗余。将音频作为一个信源,音频编码的实质是减少音频中的冗余。
根据编码方式的不同,音频编码技术分为三种:波形编码、参数编码和混合编码。
一般来说,波形编码的话音质量高,但编码率也很高;参数编码的编码率很低,产生的合成语音的音质不高;混合编码使用参数编码技术和波形编码技术,编码率和音质介于它们之间。
PCM:Pulse Code Modulation,脉冲编码调制。
ADPCM:Adaptive Differential Pulse Code Modulation,自适应差分脉冲编码调制。
SB-ADPCM:Sub band Adaptive Differential Pulse Code Modulation,子带-自适应差分脉冲编码调制。
LPC:Linear Predictive Coding,线性预测编码。
CELPC:Code Excited Linear Predictive Coding,码激励线性预测编码。
VSELPC:Vector Sum Excited Linear Predictive Coding,矢量和激励线性预测编码。
RPE-LTP:Regular Pulse Excited-Long Term Predictive,规则脉冲激励长时预测。
LD-CELP:Low Delay-Code Excited Linear Predictive,低时延码激励线性预测。
MPE:Multi-Pulse Excited,多脉冲激励。
PSTN:Public Switched Telephone Network,公共交换电话网。
ISDN:Integrated Services Digital Network,综合业务数字网。
二、整体架构
1.概述
Audio系统在Android中负责音频方面的数据流传输和控制功能,也负责音频设备的管理。这个部分作为Android的Audio系统的输入/输出层次,一般负责播放PCM声音输出和从外部获取PCM声音,以及管理声音设备和设置,注意,解码功能不在这里实现,在android系统里音频视频的解码是 opencore 或 stagefright 完成的,在解码之后才调用音频系统的接口,创建音频流并播放。
1、功能
·音频管理:负责音量调节、音频设备选择、响铃模式选择等;
·声音播放:负责一个音频流的创建、参数设置、播放、暂停、释放;
·声音录音:负责一个录音音轨的创建、管理;
·声音音效:负责控制声音的效果。
(Android 系统对audio的实现是比较复杂的,但实现的方法还是对音频系统的抽象)
2、服务
Audio是安卓系统中一个非常重要的模块,所有音频的操作都要经过它。它不仅为用户提供服务,而且还为应用开发者提供服务。要时刻记住所做的一切开发工作都是为了别人能更好的使用这个模块,服务好别人,并且保证我们的应用和系统能够稳定运行,但做到这些也不是一件简单的事。
从系统开机的那一刻起,Audio系统服务就随之启动,伴随其运行。当被需要时,要求实时做出回应来,给底层和用户一个合理的“交代”。
比如说:用户听歌时觉得音量过小,就要按手机上音量+键,Audio系统需要处理好用户的这次请求,做到准确,迅速。
再比如说,汽车正在播放歌曲,来了导航播报,多媒体需要和导航混音并且降低音量,然后再来蓝牙电话,多媒体和导航都需要停止播放,这些都是需要Audio系统来协调处理。
2.Audio架构
android 音频播放从app开始:
在framework层创建播放器
在audio library层做音频流和输出流控制
在Hal层将音频数据写入到输出设备进行声音输出。
其中audio library层是音频处理的核心。
App --> Frameworks --> Audio Library --> HAL
音频播放/录音流程图:
Audio系统的各层次介绍:
1、JAVA接口层
·AudioManager.java
为APP提供音量控制和响铃模式控制等功能,是音频管理器。
·AudioTrack .java
为APP提供管理和播放一个音频流的功能(必须是PCM流,因为不提供解码功能)。
·AudioRecord.java
为APP提供录音功能。
·AudioEffect.java
为APP提供控制音效的功能。
2、JAVA服务层
音频在java层的服务只有AudioService,他实现了IAudioService接口,AudioManager 是这个服务的客户端。
AudioService服务向下调用AudioSystem.java,AudioSystem.java再通过JNI调用到本地层。
Q1:AudioTrack 、AudioRecord和AudioEffect的调用情况?
他们是直接调用本地接口的。
Q2:为什么AudioTrack 、AudioRecord和AudioEffect没有java服务层?
这是因为一般的音频文件比如mp3、ogg等都是压缩的音频流,需要经过解码才能得到pcm数据流,解码是opencore或stagefright完成的,一般是播放器的服务,比如MediaPlayer,先解码音频文件,然后去创建AudioTrack、设置音效等,然后播放音乐。这些内容的实现一般是在本地服务层,所以AudioTrack 、AudioRecord和AudioEffect的服务层在本地层的AudioFlinger里实现就够了。
3、JNI层
Android的Audio部分通过JNI向Java层提供接口,在Java层可以通过JNI接口完成Audio系统的大部分操作。Audio JNI部分的代码路径为:frameworks/base/core/jni。
其中,主要实现的3个文件为:android_media_AudioSystem.cpp、android_media_Audio Track.cpp和android_media_AudioRecord.cpp,它们分别对应了Android Java框架中的3个类的支持:
·android.media.AudioSystem:负责Audio系统的总体控制;
·android.media.AudioTrack:负责Audio系统的输出环节;
·android.media.AudioRecorder:负责Audio系统的输入环节。
提示:
现在基本都用HIDL代替了。
4、NATIVE层
Native层的代码和逻辑是整个音频模块的核心!!!
Android的Audio系统的核心框架在media库中提供,对上面主要实现AudioSystem、AudioTrack和AudioRecorder三个类,这其实是Audio系统的本地接口层。
AudioFlinger提供了IAudioFlinger类接口,在这个类中,可以获得IAudioTrack和IAudioRecorder两个接口,分别用于声音的播放和录制。AudioTrack和AudioRecorder分别通过调用IAudioTrack和IAudioRecorder来实现。
Native层采用了C/S的架构方式,AudioTrack是属于Client端的,AudioFlinger和AudioPolicyService是属于服务端的,AudioFlinger是唯一可以调用HAL层接口的地方,而AudioPolicyService主要是对一些音频策略的选择和一些逻辑调用的中转。
5、硬件抽象层(HAL)
HAL 定义了音频服务会调用且您必须实现才能使音频硬件正常运行的标准接口
android audio framework中的audio flinger是通过操作audio hal层对间接的对底层设备进行操作的。(音频数据的读写以及各种参数的设定)。
音频 HAL 由以下接口组成:
hardware/libhardware/include/hardware/audio.h 表示音频设备的主函数。
hardware/libhardware/include/hardware/audio_effect.h 表示可应用于音频的效果,如缩混、回音或噪音抑制。
现在HIDL用的很广泛了,HAL层也不用做一些重复的开发了,减轻了一些负担。
6、驱动层
Audio的驱动是基于Linux标准的音频驱动:OSS(Open Sound System)或者ALSA(Advanced Linux Sound Architecture)驱动程序来实现的。
手机上用的也有用TINYALSA,是ALSA的精简版,主要功能都在。
三、重要模块
1.概述
之前了解了安卓整个Audio系统的分层,整体架构是怎么样的,大概在心里也已经有了一些初步的轮廓。现在开始细化,挑选出几个重要的模块,逐个分析,找出其中的联系,并且知道其工作原理,目的就算达到了。
在这几个重要的类中,有提供给apk开发者使用的接口,还有核心的几个Audio系统服务。
当我们要使用Audio的API时,要回想一下第一章提到过的那几个音频参数的具体含义。
2.AudioTrack
AudioTrack是最灵活、高级的音频API,用它可以实现将原始音频数据直接送到硬件的操作。是管理和播放单一音频资源的类,仅仅能播放已经解码的PCM流,用于PCM音频流的回放。
想使用好这个API播放音频数据,可以分为五个步骤:
1、 配置基本参数
(1)StreamType音频流类型:最主要的几种STREAM
AudioManager.STREAM_MUSIC:用于音乐播放的音频流。
AudioManager.STREAM_SYSTEM:用于系统声音的音频流。
AudioManager.STREAM_RING:用于电话铃声的音频流。
AudioManager.STREAM_VOICE_CALL:用于电话通话的音频流。
AudioManager.STREAM_ALARM:用于警报的音频流。
AudioManager.STREAM_NOTIFICATION:用于通知的音频流。
AudioManager.STREAM_BLUETOOTH_SCO:用于连接到蓝牙电话的手机音频流。
AudioManager.STREAM_SYSTEM_ENFORCED:在某些国家实施的系统声音的音频流。
AudioManager.STREAM_DTMF:DTMF音调的音频流。
AudioManager.STREAM_TTS:文本到语音转换(TTS)的音频流。
(2)MODE模式(static和stream两种)
AudioTrack.MODE_STREAM:
STREAM的意思是由用户在应用程序通过write方式把数据一次一次得写到AudioTrack中。
AudioTrack.MODE_STATIC:
STATIC就是数据一次性交付给接收方。
(3)采样率:mSampleRateInHz
采样率 (MediaRecoder 的采样率通常是8000Hz AAC的通常是44100Hz。 设置采样率为44100,目前为常用的采样率,官方文档表示这个值可以兼容所有的设置)
(4)通道数目:mChannelConfig
声道设置:android支持双声道立体声和单声道。MONO单声道,STEREO立体声.
CHANNEL_OUT_MONO;
CHANNEL_OUT_STEREO。
(5)音频量化位数:mAudioFormat(只支持8bit和16bit两种)
ENCODING_PCM_16BIT;
ENCODING_PCM_8BIT;
采样大小越大,那么信息量越多,音质也越高,现在主流的采样大小都是16bit,在低质量的语音传输的时候8bit足够了。
2、获取最小缓冲区大小
根据采样率,采样精度,单双声道来得到frame的大小。
计算最小缓冲区:
AudioTrack.getMinBufferSize(mSampleRateInHz,mChannelConfig, mAudioFormat);
注意,按照数字音频的知识,这个算出来的是一秒钟buffer的大小。**
3、创建AudioTrack对象
取到mMinBufferSize后,我们就可以创建一个AudioTrack对象了。它的构造函数原型是:
public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode);
构造函数里面的参数我们之前都讲过了,所以创建时根据需求自行指定即可。
4、获取PCM文件,转成DataInputStream
根据存放PCM的路径获取到PCM文件。
File file = new File(path);
mDis = new DataInputStream(new FileInputStream(file));
5、开启/停止播放
(1)开始播放
先把音频数据放到AudioTrack获取到的缓冲中。
readCount = mDis.read(MinBufferSize);
先调用AudioTrack.play();
之后就可以写数据了,AudioTrack.write(MinBufferSize, 0, readCount);
(2)停止播放
停止播放音频数据,如果是STREAM模式,会等播放完最后写入buffer的数据才会停止。如果立即停止,要调用pause()方法,然后调用flush方法,会舍弃还没有播放的数据。
(3)释放资源
AudioTrack.release();
3.AudioRecord
看完AudioTrack了,再看AudioRecord的话就会非常简单。
一般情况下录音实现的简单流程如下:(实现不难,不具体说了)
1、创建一个数据流。
2、构造一个AudioRecord对象,其中需要的最小录音缓存buffer大小可以通getMinBufferSize方法得到。如果buffer容量过小,将导致对象构造的失败。
3、初始化一个buffer,该buffer大于等于AudioRecord对象用于写声音数据的buffer大小。
4、开始录音。
5、从AudioRecord中读取声音数据到初始化buffer,将buffer中数据导入数据流。
6、停止录音。
7、关闭数据流。
4.AudioManager
AudioManager实现了AudioSystem类与JAVA层系统服务AudioService的封装,主要作用为向应用程序层提供Audio系统的控制接口。提供了丰富的API让开发者对应用的音量和铃声模式等进行控制以及访问。主要内容涉及到音频流、声音、蓝牙、扩音器、耳机等等。
常用功能:
1、音量控制
调音量有两个比较重要的接口。
(1)adjustStreamVolume(int streamType, int direction, int flags);
这个方法主要是用于按键调音,音量+/-。按照规定好的步进值和方向调整指定流的音量大小。
streamType:是你要调节的流。比如说,音乐,铃声,闹钟等。
direction:调节的方向,也就是增大/减小,或者保持不变。(ADJUST_LOWER;
ADJUST_RAISE;ADJUST_SAME)。
flags:常用就是是否在调节音量时显示UI。
(2)setStreamVolume(int streamType, int index, int flags);
这函数是可以直接设置特定流的音量值,主要是用在系统设置中进行调节。
这个函数和上一个就一个参数不同,但是很简单。
index代表的就是设置的音量值。
2、静音控制
(1) setMasterMute(boolean mute, int flags);
这个函数的作用是设置全局静音,一直设置到BSP。
参数一个是mute的布尔值,另一个flags之前说过。
(2)setStreamMute(int streamType, boolean state);
设置指定流静音,其内部静音实现就是在FW把该流的音量设置为0。
3、音频模式/状态
AudioMode用于描述整个Audio部分的当前状态。
MODE_NORMAL:正常状态(默认)。
MODE_RINGTONE:响铃状态(来电)。
MODE_IN_CALL:呼叫电话状态(拨出未接通)。
MODE_IN_COMMUNICATION:电话状态(接通)。
这些状态的改变会影响音频数据在Audio各输出设备中的路由选择。
还有各种铃声状态也都很常用:
RINGER_MODE_NORMAL:正常模式,可以听到铃声。
RINGER_MODE_SILENT:静音模式,无铃声,无震动。
RINGER_MODE_VIBRATE:震动模式,无铃声,有震动。
4、音频焦点
(1)目的:
AudioFocus机制是为解决系统中Audio资源的竞争问题而引入的。
(2)要求:
需要所有参与Audio竞争的竞争者主动去遵循,Audio Focus机制才能更好的工作。
(3)申请焦点:
现在只说安卓8.0以上的接口。
public int requestAudioFocus(@NonNull AudioFocusRequest focusRequest);
可以看到参数只有一个AudioFocusRequest,那么重点肯定就在这里边了,其中一定设置了许多参数,包括音源类型、监听器等。
所以先看这个AudioFocusRequest。
AudioFocusRequest类是一个封装有关音频焦点请求的信息的类。
看个例子更加直观:
mFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setWillPauseWhenDucked(true)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(mListener, mHandler)
.setAudioAttributes(mAttribute)
.build();
AUDIOFOCUS_GAIN:当前声音的唯一的音频来源,持续多长时间未知,结束后也不希望恢复另一个音频流。
AUDIOFOCUS_GAIN_TRANSIENT:应用程序暂时从当前所有者那里获取焦点,音频播放完后恢复以前的音频播放, 表示请求焦点时已经有人在持有焦点
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:和上面的一样临时获取焦点,但他可以在拥有焦点期间,另一个应用可以降低音量继续播放,俗称 躲闪,
AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:也是临时请求焦点,在拥有焦点期间不希望设备播放任何内容。就是在持有焦点期间,在有请求焦点时禁止把焦点分给其他人。
setWillPauseWhenDucked():声明应用程序在音频回避方面的预期行为,也就是说如果我不希望系统自动给我降低音量,而是想自己暂停音频相关的工作,可以调这个函数取消系统的默认行为。这样通过监听音频焦点变化,来自己处理。
setAcceptsDelayedFocusGain():这个是为了能够延迟获取到焦点的必须条件,但是同时也必须要设置AudioManager.OnAudioFocusChangeListener才能得知何时获取到焦点。
setOnAudioFocusChangeListener():设置音频焦点变化监听器。值得一提的是这个方法有个重载的方法,有一个重载方法有两个参数,第二个参数为Handler对象,看到Handler应该明白了,是为了使用它的消息队列来顺序处理这个回调。
setAudioAttributes(): 这个方法是用来描述app的使用情况。这方法需要传入一个AudioAttributes对象,这个对象也是使用Builder模式来构造。
例如使用AudioAttributes.Builder.setUsage()来描述使用这个音频来干什么,我们可以传入一个AudioAttributes.USAGE_MEDIA来表明用这个音频来作为媒体文件来播放,也可以传入一个AudioAttributes.USAGE_ALARM来表明用这个来作为闹铃。
(4)释放焦点:
public int abandonAudioFocus(OnAudioFocusChangeListener l);
参数就是之前说过的音频焦状态变化监听器。
5.AudioService
AudioService这个系统服务包含或者使用了几乎所有与音频有关的内容,AudioService是音频系统在java层的大本营
1、介绍
(1)音频系统在java层中基本上不参与数据流的,AudioService这个系统服务包含或者使用了几乎所有与音频有关的内容,所以说AudioService是音频系统在java层的大本营。
(2)AudioManager拥有AudioService的Bp端,是AudioService在客户端的一个代理,几乎所有客户端对AudioManager进行的请求,最终都会交由AudioService实现。
(3)AudioService的功能实现依赖于AudioSystem类,AudioSystem无法实例化,它是java层到native层的代理,AudioService通过它与AudioPolicyService以及AudioFlinger进行通信。
2、音量管理
(1)AudioService音量管理的核心是VolumeStreamState,它保存了一个流类型所有的音量信息。
(2)VolumeStreamState保存了运行时的音量信息,而音量的生效则是在底层AudioFlinger完成的。
所以进行音量设置需要做两件事情:更新VolumeStreamState存储的音量值,设置音量到Audio底层系统。
6.AudioSystem
AudioSystem在Audio系统中属于一个承上启下的类,在java和native都有它,所以我们在上层调用的很多功能都经过它,再经过它流转到native服务。
1、介绍
(1)AudioSystem是Audio子系统面向framework的接口,这里面有很多一竿子戳到底的函数。同样,Audio子系统内部也往往使用AudioSystem进行通信,比如AF和APM。
(2)AudioService通过对AudioSystem进行函数调用与Audio系统进行通信。
(3)AudioSystem和AudioFlinger以及AudioPolicyService的双向通信机制。
7.AudioPolicyService
我们知道,在 Audio系统中,核心的两块是 AudioPolicy 和 AudioFlinger,其中 AudioPolicy 相当于军师的角色,专门来制定 Audio 播放时的相关的策略及设定相关的参数,而 AudioFlinger 相当于将军,会根据军师的策略来执行。
所以重点都在这两个服务上,那么就先看AudioPolicyService这个负责“指挥“的服务,最好的办法就是看它启动时都分别干了什么。
启动流程分析:
1、AudioServer
(1)main_audioserver.cpp会编译成可执行文件audioserver,在主线程中,主要是监控子进程的状态,而在它的子进程中分别调用 AudioFlinger::instantiate() 和 AudioPolicyService::instantiate(),分别对AudioFlinger 和 AudioPolicyService 进行初始化。
(2)同样会被它初始化的还有语音识别 SoundTriggerHwService::instantiate()和 VR 语音模块 VRAudioServiceNative::instantiate(),不过这些不是我们目前的重点,后续再看。
具体代码如下:
xref: /frameworks/av/media/audioserver/main_audioserver.cpp
int main(int argc __unused, char **argv)
// TODO: update with refined parameters
limitProcessMemory(
"audio.maxmem", /* "ro.audio.maxmem", property that defines limit */
(size_t)512 * (1 << 20), /* SIZE_MAX, upper limit in bytes */
20 /* upper limit as percentage of physical RAM */);
signal(SIGPIPE, SIG_IGN);
bool doLog = (bool) property_get_bool("ro.test_harness", 0);
pid_t childPid;
// FIXME The advantage of making the process containing media.log service the parent process of
// the process that contains the other audio services, is that it allows us to collect more
// detailed information such as signal numbers, stop and continue, resource usage, etc.
// But it is also more complex. Consider replacing this by independent processes, and using
// binder on death notification instead.
if (doLog && (childPid = fork()) != 0)
// media.log service
//prctl(PR_SET_NAME, (unsigned long) "media.log", 0, 0, 0);
// unfortunately ps ignores PR_SET_NAME for the main thread, so use this ugly hack
strcpy(argv[0], "media.log");
sp<ProcessState> proc(ProcessState::self());
MediaLogService::instantiate();
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
......
sp<ProcessState> proc(ProcessState::self());
sp<IServiceManager> sm = defaultServiceManager();
ALOGI("ServiceManager: %p", sm.get());
AudioFlinger::instantiate();
AudioPolicyService::instantiate();
// AAudioService should only be used in OC-MR1 and later.
// And only enable the AAudioService if the system MMAP policy explicitly allows it.
// This prevents a client from misusing AAudioService when it is not supported.
aaudio_policy_t mmapPolicy = property_get_int32(AAUDIO_PROP_MMAP_POLICY,
AAUDIO_POLICY_NEVER);
if (mmapPolicy == AAUDIO_POLICY_AUTO || mmapPolicy == AAUDIO_POLICY_ALWAYS)
AAudioService::instantiate();
SoundTriggerHwService::instantiate();
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
2、AudioPolicyService::instantiate()
首先注册一个名为 media.audio_policy 的服务,在注册服务过程中会首先调用 AudioPolicyService::onFirstRef() 函数,它是我们 AudioPolicySerivce 启动时的核心代码。
3、AudioPolicyService::onFirstRef()
在该代码中,主要工作如下:
(1)创建了三个AudioCommandThread,名字分别为"ApmTone",“ApmAudio”,“ApmOutput”。
(2)实例化 AudioPolicyClient 对象
(3)初始化 AudioPolicyManager ,传参就是 AudioPolicyClient对象。
(4)AudioPolicyEffects 音效初始化。
void AudioPolicyService::onFirstRef()
Mutex::Autolock _l(mLock);
// start tone playback thread,用于播放tone音,tone是音调的意思
mTonePlaybackThread = new AudioCommandThread(String8("ApmTone"), this);
// start audio commands thread,用于执行audio命令
mAudioCommandThread = new AudioCommandThread(String8("ApmAudio"), this);
// start output activity command thread,用于执行audio输出命令
mOutputCommandThread = new AudioCommandThread(String8("ApmOutput"), this);
//实例化AudioPolicyClient对象
mAudioPolicyClient = new AudioPolicyClient(this);
//实例化AudioPolicyManager对象
mAudioPolicyManager = createAudioPolicyManager(mAudioPolicyClient);
// load audio processing modules
//初始化音效相关
sp<AudioPolicyEffects>audioPolicyEffects = new AudioPolicyEffects();
mAudioPolicyEffects = audioPolicyEffects;
4、在 AudioPolicyManager 初始化过程中
(1)解析Audio 配置文件,audio_policy.xml 和 audio_policy.conf,解析配置文件中当前系统所支持的输出设备mAvailableOutputDevices、输入设备mAvailableInputDevices、默认输出设备mDefaultOutputDevice。
(2)调用 config.setDeafult() 初始化 mHwModules ,配置 Module 的名字为 “primary”,配置默认输出设备为 AUDIO_DEVICE_OUT_SPEAKER,默认输入设备为 AUDIO_DEVICE_IN_BUILTIN_MIC。
(3)通过 getName() 加载模块,调用loadHwModule_l 函数初始化 HwModule 模块,调用 openDevice 初始化 hardware 层的audio配置,hardware 层会返回给上层 Audio 操作方法,及初始化 输入链表 streams_input_cfg_list 和输出流链表 streams_output_cfg_list ,
(4)初始化默认音量解级参数,包括 通话、系统、铃声、闹钟、通知、蓝牙、拨号盘等音量等级。
(5)给每一个输入 和 输出设备,都分配一个线程,及对应的输入 和 输出 stream 流,根据 audio类型调用不同的函数(Playback 、 OFFLOAD 、 DIRECT 类型等。
(6) 最终所有的信息保存在 HwModule[ ] 中,将所有的输出设备保存在 mDeviceForStrategy[ ] 中。
至此,所有的输入设备和输出设备 都有对应的stream及单独的 thread 。
5、AudioPolicyEffects 音效初始化
解析audio_effects.conf 文件,得到并加载 系统支持的音效库。初始化各个音效对应的参数,将各音效和 对应的输入 和输出流绑定在一起,这样,当上层要初始化使用音效时,就会在对应的threadloop中调用process_l音效处理函数。创建一个AudioFlinger客户端,将 effect 和 AudioFlinger 客户端绑定在一起。
注:
声音通路配置,sources 是声音输信端口,sink 是声音输出端口
播放:source(MixerPort)->sink(DevicePort)
录音:source(DevicePort)->sink(MixerPort)
(借用几张图片)
加载完系统定义的所有音频接口,并生成相应的数据对象,如👇图所示:
打开音频输出后,在AudioFlinger与AudioPolicyService中的表现形式如👇图:
6、总结
打开音频输出时创建一个audio_stream_out通道,并创建AudioStreamOut对象以及新建PlaybackThread播放线程。
打开音频输入时创建一个audio_stream_in通道,并创建AudioStreamIn对象以及创建RecordThread录音线程。
8.AudioFlinger
AudioFlinger 是音频系统策略的执行者,负责音频流设备的管理及音频流数据的处理传输,所以 AudioFlinger 也被认为是 Android 音频系统的引擎。
最近的版本AudioFlinger这一块变得更加模块化了,把其中的子类提取出为独立的文件,而AudioFlinger.cpp只包含对外提供的服务接口了。另外相比以前,增加更多的功能特性。先来看看这些被提取出来的独立的文件,有个印象。
AudioResampler.cpp:
重采样处理类,可进行采样率转换和声道转换;由录制线程 AudioFlinger::RecordThread 直接使用。
AudioMixer.cpp:
混音处理类,包括重采样、音量调节、声道转换等,其中的重采样复用了 AudioResampler;由回放线程 AudioFlinger::MixerThread 直接使用。
Effects.cpp:
音效处理类。
Tracks.cpp:
音频流管理类,可控制音频流的状态,如 start、stop、pause。
Threads.cpp:
回放线程和录制线程类;回放线程从 FIFO 读取回放数据并混音处理,然后写数据到输出流设备;录制线程从输入流设备读取录音数据并重采样处理,然后写数据到 FIFO。
AudioFlinger.cpp:
AudioFlinger 对外提供的服务接口。
AudioFlinger 对外提供的主要的服务接口如下:
可以归纳出 AudioFlinger 响应的服务请求主要有:
① 获取硬件设备的配置信息
② 音量调节
③ 静音操作
④ 音频模式切换
⑤ 音频参数设置
⑥ 输入输出流设备管理
⑦ 音频流管理
现在,我们介绍了AudioFlinger它的各个子类以及它作为服务自身对外提供的接口,之前在说AudioPolicyService的时候也知道了它们都是在audioserver里启动的,所以我们要看AudioFlinger这个服务时启动时都做了什么。
AudioFlinger::instantiate() 调用如下:
1、注册 AudioFlinger 服务名为 “media.audio_flinger”。
2、调用 AudioFlinger 构造函数。在构造函数中,主要是做一下初始化,初始化了 mDevicesFactoryHal 设备接口 和 mEffectsFactoryHal 音效接口。
3、调用 AudioFlinger::onFirstRef 函数。在其中构造一个 PatchPanel 对象;修改 mode 为 AUDIO_MODE_NORMAL。
AudioFlinger 服务启动后,其他进程可以通过 ServiceManager 来获取其代理对象 IAudioFlinger,通过 IAudioFlinger 可以向 AudioFlinger 发出各种服务请求,从而完成自己的音频业务。
看来它在初始化时做的工作没有AudioPolicyService那么复杂,但是AudioFlinger有着更重要的工作,我们继续来看。
AndioFlinger 作为 Android 的音频系统引擎,重任之一是负责输入输出流设备的管理及音频流数据的处理传输,这是由回放线程(PlaybackThread 及其派生的子类)和录制线程(RecordThread)进行的。
我们简单看看回放线程和录制线程类关系:
ThreadBase:PlaybackThread 和 RecordThread 的基类。
RecordThread:录制线程类,由 ThreadBase 派生。
PlaybackThread:回放线程基类,同由 ThreadBase 派生。
MixerThread:混音回放线程类,由 PlaybackThread 派生,负责处理标识为 AUDIO_OUTPUT_FLAG_PRIMARY、AUDIO_OUTPUT_FLAG_FAST、AUDIO_OUTPUT_FLAG_DEEP_BUFFER 的音频流,MixerThread 可以把多个音轨的数据混音后再输出。
DirectOutputThread:直输回放线程类,由 PlaybackThread 派生,负责处理标识为 AUDIO_OUTPUT_FLAG_DIRECT 的音频流,不需要软件混音,直接输出到音频设备即可。
DuplicatingThread:复制回放线程类,由 MixerThread 派生,负责复制音频流数据到其他输出设备,使用场景如主声卡设备、蓝牙耳机设备、USB 声卡设备同时输出。
OffloadThread:硬解回放线程类,由 DirectOutputThread 派生,负责处理标识为 AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD 的音频流,这种音频流未经软件解码的(一般是 MP3、AAC 等格式的数据),需要输出到硬件解码器,由硬件解码器解码成 PCM 数据。
从 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 回放线程实例。
其中 primary_out 设备是必须声明支持的,而且系统启动时就已经打开 primary_out 设备并创建好对应的 MixerThread 实例。其他类型的输出流设备并非必须声明支持的,主要是看硬件上有无这个能力。
下图简单描述 AudioTrack、PlaybackThread、输出流设备三者的对应关系:
输出流设备决定了它对应的 PlaybackThread 是什么类型,只有支持了该类型的输出流设备,那么该类型的 PlaybackThread 才有可能被创建。
应用进程与 AudioFlinger 并不在一个进程上,这就需要 AudioFlinger 提供音频流管理功能,并提供一套通讯接口可以让应用进程跨进程控制 AudioFlinger 中的音频流状态。
AudioFlinger 音频流管理由 AudioFlinger::PlaybackThread::Track 实现,Track 与 AudioTrack 是一对一的关系,一个 AudioTrack 创建后,那么 AudioFlinger 会创建一个 Track 与之对应,PlaybackThread 与 AudioTrack/Track 是一对多的关系,一个 PlaybackThread 可以挂着多个 Track。
AudioTrack 创建后,AudioPolicyManager 根据 AudioTrack 的输出标识和流类型,找到对应的输出流设备和 PlaybackThread(如果没有找到的话,则系统会打开对应的输出流设备并新建一个 PlaybackThread),然后创建一个 Track 并挂到这个 PlaybackThread 下面。
PlaybackThread 有两个私有成员向量与此强相关:
mTracks:该 PlaybackThread 创建的所有 Track 均添加保存到这个向量中。
mActiveTracks:只有需要播放(设置了 ACTIVE 状态)的 Track 会添加到这个向量中。
PlaybackThread 会从该向量上找到所有设置了 ACTIVE 状态的 Track,把这些 Track 数据混音后写到输出流设备。
音频流控制最常用的三个接口:
AudioFlinger::PlaybackThread::Track::start:开始播放:把该 Track 置 ACTIVE 状态,然后添加到 mActiveTracks 向量中,最后调用 AudioFlinger::PlaybackThread::broadcast_l() 告知 PlaybackThread 情况有变。
AudioFlinger::PlaybackThread::Track::stop:停止播放:把该 Track 置 STOPPED 状态,最后调用 AudioFlinger::PlaybackThread::broadcast_l() 告知 PlaybackThread 情况有变。
AudioFlinger::PlaybackThread::Track::pause:暂停播放:把该 Track 置 PAUSING 状态,最后调用 AudioFlinger::PlaybackThread::broadcast_l() 告知 PlaybackThread 情况有变。
AudioFlinger::PlaybackThread::threadLoop() 得悉情况有变后,调用 prepareTracks_l() 重新准备音频流和混音器:ACTIVE 状态的 Track 会添加到 mActiveTracks,此外的 Track 会从 mActiveTracks 上移除出来,然后重新准备 AudioMixer。
可见这三个音频流控制接口是非常简单的,主要是设置一下 Track 的状态,然后发个事件通知 PlaybackThread 就行,复杂的处理都在AudioFlinger::PlaybackThread::threadLoop() 中了。
总结:
AudioFlinger里面涉及和包含的东西很多很多,在这里只是冰山一角,没有深入到具体的代码中去看,只是给大家介绍了一下其工作原理。
四、项目实例(汽车)
1.前言
目前移动操作系统主要分为Android和IOS两大阵营,IOS系统从出现的时候就给出了人们很惊艳的感觉,其内部细节的打磨以及用户体验都是有口皆碑。而安卓在一经推出到现在也一直是人们“讨论”的对象,主要是一开始的系统生硬,BUG居多,交互不够人性化此等印象在人们的脑海中先入为主。但是代码开源,linux内核这两个重要的原因给了安卓系统庞大的成长空间和生态基础。
时光荏苒,移动互联网成为世界科技的主旋律,谷歌开始重视起来自己当时收购的这个“玩意“,不断更迭,不断优化,不断成长。世界已经不是那个世界了,安卓系统成为了我们生活中必不可少的组成部分,在移动设备上(例如手机)发光发热了近十年光景,但是它的使命还没有结束。
汽车,属于传统产业,如今人们对它的要求也日益提高,自动驾驶概念炒的火热,互联网公司当然亦不会放过这次机会。
安卓系统的加入也仿佛是理所当然,谷歌公司更加确切了未来的方向,面向汽车提供了大量的功能接口,而在Audio模块里面,就有这么一个新加入的“成员”。
2.CarAudio
CarAudioService,是谷歌专门为汽车音频这块专门建的一个服务,处理一些汽车上专门的设置和项目定制化的一些东西。
但是,由于刚刚推出还不是很成熟,因此每个版本变更时Car这部分都会有很大变化,为了方便理解,基于Android9.0(P)简单分析下CarAudio这部分。
先来看看这个服务的启动流程:
1、SystemServer
startOtherServices()中启动CarService,代码如下:
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE))
Android Audio知识梳理 看完这一篇就够了!