Android音频管理总结(个人笔记)

 

Android音频管理

Android音频概述

media player接口

Android系统的播放器NuPlayer

media extractor(高通的有自己的解封装模块mmparser)

ACodec

Android音频管理总结(个人笔记)_第1张图片

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 { ...}

其中 BnInterface 就是两个一起继承的意思,尖括号里的就是对应的接口类。

Bp

p 就是proxy 代理的意思。这个类存在的意义在于通过IPC机制去实现执行接口函数调用。同样 他也是一个BXXX对应一个IXXX 例如 BpSurfaceComposer 与 ISurfaceComposer

BpXXX的类是这样的,继承了IXXX

BpXXX是一个代理负责执行接口函数

AudioFlinger、AudioPolicyService和AudioPolicyManager之间的关系?

AudioFlinger和AudioPolicyService属于binder服务,而AudioPolicyManager是AudioPolicyService服务进程下的一个独立功能模块,该模块可以由厂家自行实现(但必须遵循aosp的接口定义),最后提供so库,由AudioPolicyService服务load进来调用即可。

Android音频管理总结(个人笔记)_第2张图片

三大类如何开始启动?

在/framework/av/media/audioserver/main_audioserver.cpp的main方法中,有他们的启动信息:

Android音频管理总结(个人笔记)_第3张图片

上面源码可以看出,AudioFlinger和AudioPolicyService虽然是Binder服务类,但是他们启动都是在同一个进程中,也就是说他们交互表面上是binder IPC交互,其实实质底层也是指针相互调用的;

AudioPolicyManager的启动

下面是AudioFlinger启动过程,主要是AudioFlinger如何与HAL(Hardware Abstraction Layer)建立起连接的Android音频管理总结(个人笔记)_第4张图片

为了改变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

Android音频管理总结(个人笔记)_第5张图片

PCM(Pulse-Code Modulation)脉冲调制编码

在音视频中,PCM是一种用数字表示采样模拟信号的方法。要将一段音频模拟信号转换为数字表示,包含如下三个步骤:

  1. Sampling(采样)

  2. Quantization(量化)

  3. 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)

FILE *file
int8_t *buffer;
file = fopen("PCM file path");
buffer = malloc(fileSize);
fread(buffer, sizeof(int8_t), fileSize / sizeof(int8_t), file);

音视频传输协议

SDP(会话描述协议)

(SessionDescription Protocol)

为会话通知、会话邀请和其它形式的多媒体会话初始化等目的提供了多媒体会话描述。

SDP 的设计宗旨是通用性,它可以应用于大范围的网络环境和应用程序,而不仅仅局限于组播会话目录,但 SDP 不支持会话内容或媒体编码的协商。

SDP 文本信息包括:

  1. 会话名称和意图;

  2. 会话持续时间;

  3. 构成会话的媒体;

  4. 有关接收媒体的信息(地址等)。

  5. 协议结构

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会话。

  1. SIP 用户代理 (UA) 是终端用户设备,如用于创建和管理 SIP 会话的移动电话、多媒体手持设备、PC、PDA 等。用户代理客户机发出消息。用户代理服务器对消息进行响应。

  2. SIP 注册服务器是包含域中所有用户代理的位置的数据库。在 SIP 通信中,这些服务器会检索参与方的 IP 地址和其他相关信息,并将其发送到 SIP 代理服务器。

  3. SIP 代理服务器接受 SIP UA 的会话请求并查询 SIP 注册服务器,获取收件方 UA 的地址信息。然后,它将会话邀请信息直接转发给收件方 UA(如果它位于同一域中)或代理服务器(如果 UA 位于另一域中)。

  4. SIP 重定向服务器允许 SIP 代理服务器将 SIP 会话邀请信息定向到外部域。SIP 重定向服务器可以与 SIP 注册服务器和 SIP 代理服务器同在一个硬件上。

对audio_policy_configuration.xml文件解析

audio_policy_configuration.xml文件对应C++实体类

configuration配置文件为音频audio的设备、流以及路由等配置文件,里面写明了audio音频部分有哪些设备、哪些流以及它们支持的编码、格式以及通道存储布局等等; 文件通常保存在odm/etc、/vendor/etc、/system/etc目录下,文件内容大致如下:


    
        Speaker
        Built-In Mic
        Built-In Back Mic
    
    Speaker
​
    
    
    
    ....
    

查看源码,在AudioPolicyManager初始化的时候,在方法deserialize AudioPolicy XmlConfig中,当解析正确完第一个configuration文件就会return,所以应该不会解析完所有的config文件;以上xml配置最终转化为以下c++类AudioPolicyConfig:

class AudioPolicyConfig {
    std::string mSource;   //为config字符串目录,一般在odm/etc、/vendor/etc、/system/etc下的audio_policy_configuration.xml
    HwModuleCollection &mHwModules;                 //保存了配置文件中所有的所有module标签集合,每个module标签对应一个HwModule类
    DeviceVector &mAvailableOutputDevices;          //attchedDevices标签中,设备名称名字和devicePort标签的tagName相同,且type中有OUT字眼的DeviceDescriptor实体类集合,如上Speaker
    DeviceVector &mAvailableInputDevices;           //同mAvailableOutputDevices一样,只不过type中有IN的DeviceDescriptor实体类集合,如上Built-In Mic
    sp &mDefaultOutputDevice;    // 保存defaultOutputDevice标签内名字和devicePort标签的tagName相同,如Speaker
}

module标签

每个module标签对应有自己的hal,也就是hal的源码实现都不一样,如primary、usb、a2dp等


    
        
        
    
    
    
    

module标签对应C++实体类HWModule

class HWModule {
    mName = "primary"
    mHalVersion = 3.0
    OutputProfileCollection mOutputProfiles; //mixport标签role为source类型,对应IOProfle实体类集合
    InputProfileCollection mInputProfiles;  //mixport标签role为sink的类型,对应IOProfle实体类集合
    DeviceVector mDeclaredDevices; //所有的deviceport标签,对应DeviceDescriptor实体类的集合
    AudioPortVector mPorts;  //所有的mixport,deviceport标签对应的实体类,因为IOProfle和DeviceDescriptor都继承了AudioPort,所以相当于这是一个AudioPort集合
    AudioRouteVector mRoutes;  //所有的route
}

MixPort标签

mixport标签可以理解为stream流,流配置了自己的格式、采样率以及mask,并且氛围输出、输入流


    
    
    

注意:一个mixPort标签可能有多个profile属性,也就是支持很多编码格式属性

每个mixport标签对应一个IOProfile实体类

class IOProfile : public AudioPort {
    /* *同时存活的流的最大数量,默认为1
     * 标签中flag会影响该值,如果role为sink,且flag标记为AUDIO_INPUT_FLAG_MMAP_NOIRQ,则赋值为0,表示无穷大
     * */
    int maxActiveCount;    
    /** 当前流支持的设备集合;
     * 如果是sink输入流,查找规则如下:
    * 1. 遍历其父类的成员mRoutes,因为是输入流,所以遍历mRoute集合中sink为自己的route,也就是找有哪些源source设备把数据传给自己。
    * 2. 找到route后,根据route中source保存的对象,且对象type是AUDIO_PORT_TYPE_DEVICE类型(就是devicesPort标签对应的实体类DeviceDescriptor)
    * 3. 把DeviceDescriptor保存在集合中,保存在以下mSupportedDevices中,作为其支持的设备;
    * 输出流,同理;最终的结果就是:
    * 作为输出流source,mSupportedDevices保存此流可以输出到对应的device,stream -> device
    * 作为输入流sink,mSupportedDevices保存了其他device能输出数据到此流,  device -> stream
    **/
    DeviceVector mSupportedDevices; 
}
class AudioPort {
    mName = "primary output"  //对应name
    (枚举,下同)audio_port_type_t mType = AUDIO_PORT_TYPE_MIX //此处固定
    audio_port_role_t mRole = AUDIO_PORT_ROLE_SOURCE/AUDIO_PORT_ROLE_SINK  //由config的role决定
    AudioProfileVector mProfiles; //AudioProfile的集合,对应mixport里面的多个profile
    /* *
     * 标签中flag会影响该值,flag中有INPUT和OUTPUT字眼,如果mixport的role为source,则会去枚举enum为
     * OutputFlagConverter::Table查找对比获取枚举值;反之则会去InputFlagConverter::Table去对比查找;
     * 最后将枚举值设置到这里来;如果role是source角色,则判断flag包含AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD
     * 离线加载,则flag |= AUDIO_OUTPUT_FLAG_DIRECT,最后在赋值
     * */
    mFlag = flags  
    sp mModule     //通过attach函数与HwModule绑定
    AudioRouteVector mRoutes //相关连的route标签集合,多个route里面可能都会包含同一个name的mixport,所以这里是集合
}
​

mixport内部的Profile标签

在解析以上标签至profile时,会单独创建AudioProfile,如上xml配置会创建:

class AudioProfile {
    mName = ""   //空串
    audio_format_t mFormat ; //format字符对应enum的枚举值,enum在TypeConverter.cpp的FormatConverte的mTable中
    ChannelsVector mChannelMasks = //同上,也是枚举值,而不是字符串,定义在OutputChannelConverter、InputChannelConverter和ChannelIndexConverter的mTable中
    SampleRateVector mSamplingRates = //同上
    //以下三个对应上面三位,如果三位都有值,则为false固定的,如果xml没有指定值,则为true表示是动态的值
    bool mIsDynamicFormat = false
    bool mIsDynamicChannels = false;
    bool mIsDynamicRate = false;
}

DevicePort标签

devicePort标签可以理解为一个device设备,设备也分output和input,但是不在像mixport那样以role来分,而是以type中有关键字“IN”和“OUT”来分,如下:


    
 

对应实体类DeviceDescriptor

class DeviceDescriptor  : public AudioPort, public AudioPortConfig {
    /* *AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES字符串对应的enum的整型值,
     *  enum定义在system下的audio-base.h,根据其字符串中就有OUT和IN两种类型
     *  audio_devices_t其实也是一个整型,判断一个device是in或者out也是根据这个判断的;
     *  role标志只是会在audioPort中的mRole保存
     * */
    audio_devices_t mDeviceType;
    String8 mTagName = "BT A2DP Headphones"
    FormatVector mEncodedFormats = Vector上面encodedFormats转换的枚举值
}
​
class AudioPort {
    mName = ""
    audio_port_type_t mType = AUDIO_PORT_TYPE_DEVICE  //固定值
    audio_port_role_t mRole = AUDIO_PORT_ROLE_SOURCE/AUDIO_PORT_ROLE_SINK  //由role决定
    AudioProfileVector mProfiles = //对应deviceport里面的多个profile标签,AudioProfile的集合,
    sp mModule = null //目前没有attach到HwModule上
    AudioRouteVector mRoutes //相关连的route标签集合,多个route里面可能都会包含同一个name的deviceport标签,所以这里是集合
}

同上MixPort一样,也会在解析内部profile标签,创建新的AudioProfile,如下:

class AudioProfile {
    mName = ""   空串
    audio_format_t mFormat;  //同上mixport中的audioprofile
    ChannelsVector mChannelMasks;
    SampleRateVector mSamplingRates;
    //对应上面三位,如果三位都有值,则为false固定的,如果xml没有指定值,则为true表示是动态的值
    bool mIsDynamicFormat = false
    bool mIsDynamicChannels = false;
    bool mIsDynamicRate = false;
}

route标签

route是把deviceport和mixport连接起来的路由,数据由一个stream输出到另一个device,或者从一个device输出到另一个stream;

对应的Audio类:

class AudioRoute {
    audio_route_type_t mType = AUDIO_ROUTE_MIX/AUDIO_ROUTE_MUX//根据type而定是互斥还是可融合
    sp mSink;  //所有的deviceport、mixport标签转化的实体类都保存到HwModule的mPorts成员了,所以是用name去mPorts里面查找;
    AudioPortVector mSources; //同上,只是source可能是多个,这里用集合保存
}

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,如下代码:

DeviceVector sourceDevices;
//input stream to sink device
for (const auto& route : stream->getRoutes()) {
    sp sink = route->getSink();
    if (sink == 0 || stream != sink) {
        ALOGE("%s: Invalid route attached to input stream", __FUNCTION__);
        continue;
    }
    //过滤route里面的source中的deviceport而不是mixport
    DeviceVector sourceDevicesForRoute = getRouteSourceDevices(route);
    if (sourceDevicesForRoute.isEmpty()) {
        ALOGE("%s: invalid source devices for %s", __FUNCTION__, stream->getName().string());
        continue;
    }
    sourceDevices.add(sourceDevicesForRoute);
}
​
DeviceVector HwModule::getRouteSourceDevices(const sp &route) const
{
    DeviceVector sourceDevices;
    for (const auto& source : route->getSources()) {
        //type在AudioPort里面,过滤得到deviceport而不是mixport
        if (source->getType() == AUDIO_PORT_TYPE_DEVICE) {
            sourceDevices.add(mDeclaredDevices.getDeviceFromTagName(source->getTagName()));
        }
    }
    return sourceDevices;
}
​

上面是一个sink输入流案例,查找规则如下:

  1. 遍历其父类的成员mRoutes,因为是输入流,所以遍历mRoute集合中sink为自己的route,也就是找有哪些源source设备把数据传给自己。

  2. 找到route后,根据route中source保存的对象,且对象type是AUDIO_PORT_TYPE_DEVICE类型(就是devicesPort标签对应的实体类DeviceDescriptor)

  3. 把DeviceDescriptor保存在集合中,保存在以下mSupportedDevices中,作为其支持的设备; 输出流,同理;最终的结果就是: 作为输出流source,mSupportedDevices保存此流可以输出到对应的device,stream -> device 作为输入流sink,mSupportedDevices保存了其他device能输出数据到此流, device -> stream

输出流source同理,就不在阐述了,最后层级依赖大致如下:

Android音频管理总结(个人笔记)_第6张图片

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,如下:

AUDIO_OUTPUT_FLAG_NON_BLOCKING
AUDIO_OUTPUT_FLAG_HW_AV_SYNC
AUDIO_OUTPUT_FLAG_TTS
AUDIO_OUTPUT_FLAG_RAW
AUDIO_OUTPUT_FLAG_SYNC
AUDIO_OUTPUT_FLAG_IEC958_NONAUDIO
AUDIO_OUTPUT_FLAG_DIRECT_PCM
AUDIO_OUTPUT_FLAG_MMAP_NOIRQ
AUDIO_OUTPUT_FLAG_VOIP_RX
AUDIO_OUTPUT_FLAG_INCALL_MUSIC

解析xml文件标签代码架构

这里不谈具体的解析过程,而是探讨Android源码中这块的设计框架,源码在/frameworks/av/services/audiopolicy/common/managerdefinitions/src/Serializer.cpp中,博主觉得它设计很精巧,使用template模板来减少大量的冗余代码,同时将各个模块类串联起来; 首先,它为mixport、deviceport所有标签分别创建单独的模块,如MixPortTraits,定义标签名字属性和解析方法:

struct MixPortTraits : public AndroidCollectionTraits
{
    static constexpr const char *tag = "mixPort";
    static constexpr const char *collectionTag = "mixPorts";
​
    struct Attributes
    {
        static constexpr const char *name = "name";
        static constexpr const char *role = "role";
        static constexpr const char *roleSource = "source"; /**< . */
        static constexpr const char *flags = "flags";
        static constexpr const char *maxOpenCount = "maxOpenCount";
        static constexpr const char *maxActiveCount = "maxActiveCount";
    };
​
    static Return deserialize(const xmlNode *cur, PtrSerializingCtx serializingContext);
    // Children: GainTraits
};
​

同时,也创建了deviceport的DevicePortTraits模块,但是deserialize方法形参和返回值均相同; 而Attributes则根据自己的标签内容定义,其他route、profile也有对应的独立模块,相互之间互不干扰;

其次,用一个模板函数将每个模块连接起来,如下:

template 
status_t deserializeCollection(const xmlNode *cur,
        typename Trait::Collection *collection,
        typename Trait::PtrSerializingCtx serializingContext)
​

使用deserializeCollection来发起调用,在函数内部用模板调用模块内部deserialize就串联起来了,这样看起来清晰易读,结构也分明,以后的设计可参考参考此类型设计,相互独立模块,又相互联系,具体的解析又是一致的场景

图解

Android音频管理总结(个人笔记)_第7张图片

 Android音频管理总结(个人笔记)_第8张图片

Android音频管理总结(个人笔记)_第9张图片

Android音频管理总结(个人笔记)_第10张图片

Android音频管理总结(个人笔记)_第11张图片

Android音频管理总结(个人笔记)_第12张图片

你可能感兴趣的:(android)