Android音频管理
Android音频概述
media player接口
Android系统的播放器NuPlayer
media extractor(高通的有自己的解封装模块mmparser)
ACodec
ACodec消息机制:
ACodec有一个BaseState(基类)和派生出来的其他State,如 UninitializedState,LoadedToIdleState, ExecutingState等。当有消息过来时,如果派生类有重写的方法,则会调到重写的方法,如果没有,则会调到BaseState的方法。
ACodec继承自AHierarchicalStateMachine类,该类用于将收到的消息传递给哪个state。
ACodec收到的消息分两种,一种是MediaCodec传过来的,对应onMessageReceived方法;另一种是OMXComponent传过来的,对应onOMXMessage方法。而onOMXMessage里面又分了4种情况来调用不同的方法。(EVENT、EMPTY_BUFFER_DONE、FILL_BUFFER_DONE和FRAME_RENDERED)
NuPlayer
Android2.3时引入流媒体框架,而流媒体框架的核心是NuPlayer。在之前的版本中一般认为Local Playback就用Stagefrightplayer+Awesomeplayer,流媒体用NuPlayer。Android4.0之后HttpLive和RTSP协议开始使用NuPlayer播放器,Android5.0(L版本)之后本地播放也开始使用NuPlayer播放器。 Android7.0(N版本)则完全去掉了Awesomeplayer。 通俗点说,NuPlayer是AOSP中提供的多媒体播放框架,能够支持本地文件、HTTP(HLS)、RTSP等协议的播放,通常支持H.264、H.265/HEVC、AAC编码格式,支持MP4、MPEG-TS封装。
在实现上NuPlayer和Awesomeplayer不同,NuPlayer基于StagefrightPlayer的基础类构建,利用了更底层的ALooper/AHandler机制来异步地处理请求,ALooper列队消息请求,AHandler中去处理,所以有更少的Mutex/Lock在NuPlayer中。Awesomeplayer中利用了omxcodec而NuPlayer中利用了Acodec。
RTSP框架
实时流协议RTSP(RealTimeStreamingProtocol)是由该协议定义了一对多应用程序如何有效地通过IP网络传送多媒体数据。RTSP在体系结构上位于RTP和RTCP之上,它使用TCP或RTP完成数据传输。HTTP与RTSP相比,HTTP传送HTML,而RTP传送的是多媒体数据。HTTP请求由客户机发出,服务器作出响应;使用RTSP时,客户机和服务器都可以发出请求,即RTSP可以是双向的。
实时流协议(RTSP)是应用级协议,控制实时数据的发送。RTSP提供了一个可扩展框架,使实时数据,如音频与视频,的受控、点播成为可能。数据源包括现场数据与存储在剪辑中数据。该协议目的在于控制多个数据发送连接,为选择发送通道,如UDP、组播UDP与TCP,提供途径,并为选择基于RTP上发送机制提供方法。
mmap
mmap, 函数名为memory map, 即地址的映射, 是一种内存映射文件的方法,将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。
Binder机制
IBinder与Binder关系
IBinder是接口,你看前面有个I,理解为接口,他的实现类必须自己编写代码逻辑来实现功能。 Binder是实现了IBinder的具体实现类,他具有具体的功能,继承了Binder的类就是IBinder对象了。
Bn
n 就是native,这是一个怎样的类? 我们继承它的原因是为了实现一个接口,具体点说就是一个BnXXX对应一个IXXX。比如BnSurfaceComposer 对应 ISurfaceComposer
I 其实就是interface(接口)的意思。
class BnSurfaceComposer: public BnInterface<ISurfaceComposer> { ...}
其中 BnInterface<ISurfaceComposer> 就是两个一起继承的意思,尖括号里的就是对应的接口类。
Bp
p 就是proxy 代理的意思。这个类存在的意义在于通过IPC机制去实现执行接口函数调用。同样 他也是一个BXXX对应一个IXXX 例如 BpSurfaceComposer 与 ISurfaceComposer
BpXXX的类是这样的,继承了IXXX
BpXXX是一个代理负责执行接口函数
AudioFlinger、AudioPolicyService和AudioPolicyManager之间的关系?
AudioFlinger和AudioPolicyService属于binder服务,而AudioPolicyManager是AudioPolicyService服务进程下的一个独立功能模块,该模块可以由厂家自行实现(但必须遵循aosp的接口定义),最后提供so库,由AudioPolicyService服务load进来调用即可。
三大类如何开始启动?
在/framework/av/media/audioserver/main_audioserver.cpp的main方法中,有他们的启动信息:
上面源码可以看出,AudioFlinger和AudioPolicyService虽然是Binder服务类,但是他们启动都是在同一个进程中,也就是说他们交互表面上是binder IPC交互,其实实质底层也是指针相互调用的;
AudioPolicyManager的启动
下面是AudioFlinger启动过程,主要是AudioFlinger如何与HAL(Hardware Abstraction Layer)建立起连接的
为了改变AudlioManager的工作方式。 我们首先需要获得一个AudlioManager的实例
Android AudioFocus音频焦点机制
我们android系统里面会安装各种多媒体软件,如果不制定一个有效合理的规则,各个应用各自为政,那么可能就会出现各种播放器、软件的混音。音频焦点机制规定某一时刻只能有一个应用获取到声音的焦点,这个时候就可以发出声音。当然,在这个应用获取到焦点之前,需要通知其他所用的应用失去焦点。
requestAudioFocus方法有三个参数 第一个参数:OnAudioFocusChangeListener ,此为一个监听控制器,通过这个监听器可以知道自己获取到焦点或者失去焦点。 第二个参数:streamType音频流类型,焦点获得之后的数据传输类型,这个参数不会影响焦点机制,不同的音频流类型同样遵守一个焦点机制。 第三个参数:durationHint,获得焦点的时间长短,定义了四种类型 a、AUDIOFOCUS_GAIN //长时间获得焦点,此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS b、AUDIOFOCUS_GAIN_TRANSIENT //短暂性获得焦点,用完应立即释放,此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS_TRANSIENT c、AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK //短暂性获得焦点并降音,可混音播放,此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK d、AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE //短暂性获得焦点,录音或者语音识别,此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS_TRANSIENT 我们通常使用前面三种类型就可以了。
在OnAudioFocusChangeListener 的实现:
流程分析
调用AudioManager请求焦点,并在重构方法里面判断参数合法值,然后注册监听,通过Binder通信,和系统服务AudioService通信。我们看到OnAudioFocusChangeListener这个回调监听并没有发送给AudioService,取而代之的是mAudioFocusDispatcher这个参数作为和跨进程回调的桥梁。
set volume方法
getStreamMinVolume getStreamMaxVolume
SoundPool
SoundPool类是一 种实用工具类,它允许您将一组声音载到内存中。以便能够快速播放。循不播放,并具有不同的左右音量级别。(预先加载)
AudioPolicyService
PCM(Pulse-Code Modulation)脉冲调制编码
在音视频中,PCM是一种用数字表示采样模拟信号的方法。要将一段音频模拟信号转换为数字表示,包含如下三个步骤:
-
Sampling(采样)
-
Quantization(量化)
-
Coding(编码)
PCM数据常用量化指标
采样率(Sample rate):每秒钟采样多少次,以Hz为单位。详见:采样率(Sample rate)一节。
位深度(Bit-depth):表示用多少个二进制位来描述采样数据,一般为16bit。详见:Quantization(量化)一节。
字节序:表示音频PCM数据存储的字节序是大端存储(big-endian)还是小端存储(little-endian),为了数据处理效率的高效,通常为小端存储。
声道数(channel number):当前PCM文件中包含的声道数,是单声道(mono)、双声道(stereo)?此外还有5.1声道等。
采样数据是否有符号(Sign):要表达的就是字面上的意思,需要注意的是,使用有符号的采样数据不能用无符号的方式播放。
以FFmpeg中常见的PCM数据格式s16le为例:它描述的是有符号16位小端PCM数据。(s表示有符号,16表示位深,le表示小端存储。)
读取PCM数据流(Stream)
音视频传输协议
SDP(会话描述协议)
(SessionDescription Protocol)
为会话通知、会话邀请和其它形式的多媒体会话初始化等目的提供了多媒体会话描述。
SDP 的设计宗旨是通用性,它可以应用于大范围的网络环境和应用程序,而不仅仅局限于组播会话目录,但 SDP 不支持会话内容或媒体编码的协商。
SDP 文本信息包括:
-
会话名称和意图;
-
会话持续时间;
-
构成会话的媒体;
-
有关接收媒体的信息(地址等)。
-
协议结构
RTP(传输层协议)
(Real-time Transport Protocol)
详细说明了在互联网上传递音频和视频的标准数据包格式。
RTP协议常用于流媒体系统(配合RTCP协议),视频会议和一键通(Push to Talk)系统(配合H.323或SIP),使它成为IP电话产业的技术基础。RTP协议和RTP控制协议RTCP一起使用,而且它是建立在UDP协议上的。
RTCP(实时传输控制协议)
(Real-time Transport Control Protocol)
RTCP为RTP媒体流提供信道外(out-of-band)控制。RTCP本身并不传输数据,但和RTP一起协作将多媒体数据打包和发送。
RTCP的主要功能是为RTP所提供的服务质量(Quality of Service)提供反馈。
RTMP(实时信息传输协议)
(Real Time Message Protocol)
应用层的协议,用来解决多媒体数据传输流的多路复用(Multiplexing)和分包(packetizing)的问题。被Flash用于对象,视频,音频的传输.这个协议建立在TCP协议或者轮询HTTP协议之上。
RTMP是应用层协议,是要靠底层可靠的传输层协议(通常是TCP)来保证信息传输的可靠性的。
SIP(会话初始协议)
(Session Initiation Protocol)
SIP会话使用多达四个主要组件:SIP用户代理、SIP注册服务器、SIP代理服务器和SIP重定向服务器。这些系统通过传输包括了SDP 协议(用于定义消息的内容和特点)的消息来完成SIP会话。
-
SIP 用户代理 (UA) 是终端用户设备,如用于创建和管理 SIP 会话的移动电话、多媒体手持设备、PC、PDA 等。用户代理客户机发出消息。用户代理服务器对消息进行响应。
-
SIP 注册服务器是包含域中所有用户代理的位置的数据库。在 SIP 通信中,这些服务器会检索参与方的 IP 地址和其他相关信息,并将其发送到 SIP 代理服务器。
-
SIP 代理服务器接受 SIP UA 的会话请求并查询 SIP 注册服务器,获取收件方 UA 的地址信息。然后,它将会话邀请信息直接转发给收件方 UA(如果它位于同一域中)或代理服务器(如果 UA 位于另一域中)。
-
SIP 重定向服务器允许 SIP 代理服务器将 SIP 会话邀请信息定向到外部域。SIP 重定向服务器可以与 SIP 注册服务器和 SIP 代理服务器同在一个硬件上。
对audio_policy_configuration.xml文件解析
audio_policy_configuration.xml文件对应C++实体类
configuration配置文件为音频audio的设备、流以及路由等配置文件,里面写明了audio音频部分有哪些设备、哪些流以及它们支持的编码、格式以及通道存储布局等等; 文件通常保存在odm/etc、/vendor/etc、/system/etc目录下,文件内容大致如下:
查看源码,在AudioPolicyManager初始化的时候,在方法deserialize AudioPolicy XmlConfig中,当解析正确完第一个configuration文件就会return,所以应该不会解析完所有的config文件;以上xml配置最终转化为以下c++类AudioPolicyConfig:
module标签
每个module标签对应有自己的hal,也就是hal的源码实现都不一样,如primary、usb、a2dp等
module标签对应C++实体类HWModule
MixPort标签
mixport标签可以理解为stream流,流配置了自己的格式、采样率以及mask,并且氛围输出、输入流
注意:一个mixPort标签可能有多个profile属性,也就是支持很多编码格式属性
每个mixport标签对应一个IOProfile实体类
mixport内部的Profile标签
在解析以上标签至profile时,会单独创建AudioProfile,如上xml配置会创建:
DevicePort标签
devicePort标签可以理解为一个device设备,设备也分output和input,但是不在像mixport那样以role来分,而是以type中有关键字“IN”和“OUT”来分,如下:
对应实体类DeviceDescriptor
同上MixPort一样,也会在解析内部profile标签,创建新的AudioProfile,如下:
route标签
route是把deviceport和mixport连接起来的路由,数据由一个stream输出到另一个device,或者从一个device输出到另一个stream;
对应的Audio类:
configuration配置文件中关键点理解
devicePort和mixport如何通过route串联
route路由决定了哪些mixport的流数据可以传到devicePort的设备里,建立他们之间的连接关系;在代码中的体现就是通过mixport标签对应的实体类IOProfile,在IOProfile里面有一个mSupportedDevices成员,它是一个DeviceDescriptor集合类型,意思也就是IOProfile支持的设备集合,这些设备集合可以把音频数据传递给IOProfile或IOProfile可以把数据传给device。
IOProfile是如何找到他的DeviceDescriptor的?
主要是通过route标签对应AudioRoute,只要route标签内,不管sink或source内容只要有自己的名字,就把这条route保存到自己IOProfile的mRoutes中去,最后通过遍历mRoute来查找自己支持的设备DeviceDescriptor,如下代码:
上面是一个sink输入流案例,查找规则如下:
-
遍历其父类的成员mRoutes,因为是输入流,所以遍历mRoute集合中sink为自己的route,也就是找有哪些源source设备把数据传给自己。
-
找到route后,根据route中source保存的对象,且对象type是AUDIO_PORT_TYPE_DEVICE类型(就是devicesPort标签对应的实体类DeviceDescriptor)
-
把DeviceDescriptor保存在集合中,保存在以下mSupportedDevices中,作为其支持的设备; 输出流,同理;最终的结果就是: 作为输出流source,mSupportedDevices保存此流可以输出到对应的device,stream -> device 作为输入流sink,mSupportedDevices保存了其他device能输出数据到此流, device -> stream
输出流source同理,就不在阐述了,最后层级依赖大致如下:
MixPort中的flag
AUDIO_OUTPUT_FLAG | Description |
---|---|
AUDIO_OUTPUT_FLAG_PRIMARY | 表示音频流需要输出到主输出设备,一般用于铃声类声音 |
AUDIO_OUTPUT_FLAG_DIRECT | 表示音频流直接输出到音频设备,不需要软件混音,一般用于 HDMI 设备声音输出 |
AUDIO_OUTPUT_FLAG_FAST | 表示音频流需要快速输出到音频设备,一般用于按键音、游戏背景音等对时延要求高的场景 |
AUDIO_OUTPUT_FLAG_DEEP_BUFFER | 表示音频流输出可以接受较大的时延,一般用于音乐、视频播放等对时延要求不高的场景 |
AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD | 表示音频流没有经过软件解码,需要输出到硬件解码器,由硬件解码器进行解码 |
在TypeConveter的OutputFlagConverter和InputFlagConverter还有定义的很多flag,如下:
解析xml文件标签代码架构
这里不谈具体的解析过程,而是探讨Android源码中这块的设计框架,源码在/frameworks/av/services/audiopolicy/common/managerdefinitions/src/Serializer.cpp中,博主觉得它设计很精巧,使用template模板来减少大量的冗余代码,同时将各个模块类串联起来; 首先,它为mixport、deviceport所有标签分别创建单独的模块,如MixPortTraits,定义标签名字属性和解析方法:
同时,也创建了deviceport的DevicePortTraits模块,但是deserialize方法形参和返回值均相同; 而Attributes则根据自己的标签内容定义,其他route、profile也有对应的独立模块,相互之间互不干扰;
其次,用一个模板函数将每个模块连接起来,如下:
使用deserializeCollection<MixPortTraits>来发起调用,在函数内部用模板调用模块内部deserialize就串联起来了,这样看起来清晰易读,结构也分明,以后的设计可参考参考此类型设计,相互独立模块,又相互联系,具体的解析又是一致的场景
图解