Android 音频框架记录
基于android P 高通平台阅读与记录, 只是整体从上层应用到底层驱动的简单介绍,主要根据audio 的open,find和write 三个流程介绍音频框架。
1音频结构

framework:android 的应用框架层
media:主要负责媒体扫描,播放和录音,和媒体控制等类。在播放和录音这块都是基于底层audiotrack 和audiorecord来实现的。
audio:主要负责音频的流输出输入,声音大小,以及音频相关的参数操作。
Media jni & audio jni :主要用于封装和衔接java层部分。
frameworks/av/media/libaudioclient:音频客户端部分,也包含一些binder的接口文件
AudioSystem.cpp:用于AudioFlinger 和AudioPolicyService的一些函数封装的工作,以static静态的方式修饰函数。
AudioTrackShared.cpp:是客户端AudioTrack与服务端Track的共享内存类,用于传递音频数据和音频参数传递。
AudioTrack.cpp: 音频播放的客户端
AudioRecord.cpp:音频录音的客户端
frameworks/av/services/audiopolicy:android的音频策略模块,用于决定音频在哪个模块哪个设备上播放和录音的策略管理。
managerdefinitions:关于音频策略配置的数据模型,通过Serializer.cpp解析xml填充数据。
AudioPolicyManager.cpp:音频数据策略管理,比较核心的一个类
AudioPolicyService.cpp: 音频策略服务,关于音频策略的具体实现在AudioPolicyManager.cpp。
frameworks/av/services/audioflinger:音频输出输入的工作区域
AudioFlinger.cpp:音频的设备和流的具体执行控制者
Threads.cpp:音频读写音频的工作线程,内部包含PlaybackThread MixerThread DirectOutputThread OffloadThread线程
FastMixer.cpp:加快音频缓存数据。
Tracks.cpp:与客户端AudioTrack 对应的服务端binder对象,中间传递使用共享内存的方式共享一个结构体audio_track_cblk_t
AudioHwDevice.cpp:音频设备的客户端,持有DeviceHalInterface的hal设备接口
AudioStreamOut.cpp:音频流的客户端,持有StreamOutHalInterface的hal输出流接口
media/libaudiohal/4.0:属于与hal层对接的模块
local:兼容老的android 版本的hal模块,属于本地直接连接hal模块
hidl:使用hidl的绑定方式连接hal模块,相当于跨进程通讯
hardware/interfaces/audio/core/all-versions/default/include/core/all-versions/default:音频hidl的服务端
hal: hal层
system/bt/audio_a2dp_hw/src: 蓝牙a2dp 的hal 模块
hardware/qcom/audio/hal: primary的hal模块
external/tinyalsa: tinyalsa的客户端
kernel: kernel内核层
kernel/msm-4.14/sound/core:tinyalsa的内核代码
sm6150.c : 高通平台代码,适配tinyalsa接口
2音频焦点
2.1内部音频焦点
内部焦点请求就是指一般的音频焦点请求方式使用这里就不做详细解说,大概理一下结构

每个app在播放音频时都应该遵循AudioFocus的规则,使用AudioManager.RequestAudioFocus方法请求焦点。如果请求成功则可以播放,反之则不能播放。在失去焦点和恢复焦点的时候时通过监听回调函数onAudioFocusChange得到对应的操作状态。
2.2外部音频焦点
外部焦点是基于AudioFocus的机制情况下扩展的一个代理模块,比较有代表性的是车载多媒体系统中,音频的管理比手机较为复杂,在不改手机源有的焦点策略管理的基础上,就有了外部音频焦点这个概念,客户端的请求和回调保持不变,把焦点请求策略的核心部分代理给famewrok外部模块实现,例如CarService。

如图上所述,客户端的接口未变,只是多了一个CarService 和AudioService交互,把请求焦点和反馈焦点状态的都转移给CarService 处理。
3 Audioflinger
这是音频框架中核心模块, 具体负责音频的设备打开,数据流的传输工作。
在这里以输出流为例。

从app到最后kernel,简单的呈现音频数据的流向。
其中在MixerThread中会把多个track的音频数据做混音处理
在tinyalsa会根据传递的device 和flag 得出usecase,最后得到相应的card id和device id。
通过card id和service id找到对音的音频设备,并其传入数据。
4 Audiopolicy
audiopolicy是音频的一个策略管理,主要负责通过配置文件和代码初始化后得到配置数据;用于决策音频数据进入具体的输出通道
4.1音频静态配置策略
如图中描述音频配置的数据结构

通过AudioPolciyManager初始化的时候,通过 Serializer.cpp解析audio_policy_configuration.xml得到HwModule 的集合。
xml与数据模型对应关系如下表格
| xml | class | 描述 |
|---|---|---|
| module | HwModule | 模块 |
| mixPort | IOProfile | IO概述AUDIO_PORT_ROLE_SOURCE |
| profile | AudioProfile | 音频的格式参数 |
| devicePort | DeviceDescriptor | 设备描述AUDIO_PORT_ROLE_SINK |
| gain | AudioGain | 音频的增益 |
| route | AudioRoute | 音频的路线 |
| attachedDevices | DeviceDescriptor | 绑定模块的设备 |
| defaultOutputDevice | DeviceDescriptor | 默认的输出设备 |
补充一下:
IOProfile:可以理解记录了当前所能支持的设备,这个是通过 Serializer.cpp里的AudioRoute解析获取的。
DeviceDescriptor:设备的描述,记录设备的地址和设备类型,并与 IOProfile都继承了AudioPort。
AudioRoute:代表音频的线路, 可以连接 IOProfile和DeviceDescriptor的关系
4.2音频动态配置策略

基本上关于动态配置策略的是意图如上,先从Car Service 注册相关配置规则,在等音频应用播放的时候创建Track,到AudioFlinger 需要获取output ,在AudioPolicyManager 内部拿到之前注册的动态配置规则获取相应的output id。
4.3音频打开设备流程
音频初始化的时候会根据audio_policy_configuration.xml解析得到的数据打开对应的模块和设备
通过遍历HwModule,获取IOProfile并作为参数创建SwAudioOutputDescriptor,在做open来打开音频设备获取output 的id。
这里是以primary模块为例,使用hidl方式访问hal层。

4.4音频寻找设备流程
音频播放的时候会根据streamtype和attributes 寻找对应的strategy,在根据strategy寻找对应device,在根据device寻找对应的output。根据ooutput找到对应PlaybackThread,通过PlaybackThread创建服务端对应的track与客户端的AudioTrack传递音频数据和参数调整。

4.5 AudioTrack 的start流程
在AudoTrack构造的时候会获得服务端的一个代理类mAudioTrack,这个对象的接口是IAudioTrack,由TrackHandle作为服务端,并内部持有一个Track的对象。
所以AudioTrack 在调用 start 时会调用服务端TrackHandle的start方法

图中在SwAudioOutputDescriptor会调用mProfile->curActiveCount++;,代表当前激活的流加一,具体可以看下面源码注释
frameworks/av/services/audiopolicy/common/managerdefinitions/include/IOProfile.h
// Number of streams currently active for this profile. This is not the number of active clients
// (AudioTrack or AudioRecord) but the number of active HAL streams.
uint32_t curActiveCount;
在看PlaybackThread类的addTrack_l方法,此方法在调用mActiveTracks.add(track);把track导入mActiveTracks这个集合中,代表激活状态的track集合。
最后一个地方在AudioTrackServerProxy类的start方法,内部通过原子操作更新mStoopLast状态,代表更新最后一次停止的位置,
void AudioTrackServerProxy::start()
{
mStopLast = android_atomic_acquire_load(&mCblk->u.mStreaming.mStop);
}
4.6 AudioMixer 与FastMixer
这两个类主要为了在线程MixerThread 做混音工作, FastMixer是利用空间换时间的方式多开启一个线程在做数据传输。

图中上面是当initFastMixer当为真的时候会使用FastMixer的快速混合通道流程,当initFastMixer为假的是只走普通的AudioMixer混合通道流程。
audio_utils_fifo:是一个环形内存控件。
下面关于AudioBufferProvider相关类图:

Track:代表与客户端AudioTrack对应的代理类
SourceAudioBufferProvider:是把突通的Track通过AudioMixer混音后的数据通过MonoPipe写入缓存,SourceAudioBufferProvider内部的MonoPipeReader类读取对应的缓存。
如下是在在MixerThread线程与FastMixer线程之间传递Track信息需要的状态队列FastMixerStateQueue,内部队列元素FastMixerState会包含需要混音的状态数据,其中有一个结构体FastTrack记录数据的提供者,ExtendedAudioBufferProvider* mBufferProvider;
// Represents the state of a fast track
struct FastTrack {
FastTrack();
/*virtual*/ ~FastTrack();
ExtendedAudioBufferProvider* mBufferProvider; // must be NULL if inactive, or non-NULL if active
VolumeProvider* mVolumeProvider; // optional; if NULL then full-scale
audio_channel_mask_t mChannelMask; // AUDIO_CHANNEL_OUT_MONO or AUDIO_CHANNEL_OUT_STEREO
audio_format_t mFormat; // track format
int mGeneration; // increment when any field is assigned
};
Track和SourceAudioBufferProvider都是继承于ExtendedAudioBufferProvider。
查看FastTrack内部的mBufferProvider是一个ExtendedAudioBufferProvider类,他可能是Track,也可能是SourceAudioBufferProvider。这里表示普通的Track会通过MixerThread内部的AudioMixer 混合数据写入SourceAudioBufferProvider,在SourceAudioBufferProvider和快速标识Track通过FastMixer内部的AudioMixer混合写入数据到AudioStreamOut在写入hal层等等。
4.7 AudioPatch 与 PatchPanel
这主要是描述音频的线路配置,可以一个音频数据对应多个设备,也可以 一个设备对应一个音频接收方,也可以设备对应设备
TODO:由于接触的平台代码都不支持,只有一些接口框架,无法把流程串起来,再次就不做扩展介绍了,后续如果相关平台在补充。
可以参考在Android5.0上Audio Patch和Patch Panel的一些分析
5 tinyalsa
tinyalsa是android 根据alsa裁减的简化版。
tinyalsa源码位于android源码目录下external/tinyalsa,包含了四个命令,分别是tinymix,tinycap, tinyplay,tinymeminfo和一个库libtinyalsa.so
使用mmm命令编译,mmm external/tinyalsa
相关目录及文件
/dev/snd/ 系统下control设备管理、pcm设备都在此目录下
/proc/asound/ 声卡相关信息可以在此目录下找到,命令:cat /proc/asound/cards可以查看系统下所有声卡及其ID
5.1 tinyalsa初始化
这里以高通平台sm6150为例在驱动文件sm6150.c 探测方法msm_asoc_machine_probe开始初始化声卡和pcm设备
如图:

/dev/snd/ 系 pcm设备 的命名规则是pcmC0D1p pcmC0D1c 这种格式,具体在代码kernel/msm-4.14/sound/core/pcm.c的snd_pcm_new_stream方法 有定义
int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)
{
省略...
dev_set_name(&pstr->dev, "pcmC%iD%i%c", pcm->card->number, pcm->device,
stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c');
省略...
return 0;
}
EXPORT_SYMBOL(snd_pcm_new_stream);
在代码中可以看出pcm设备的命名是以pcmC%iD%i%c来定义的。 第一个参数代表card,第二个参数代表device,第三个参数代表是playback还是capture。
下面是三个参数的由来:
Card:从上面时序图中init.c 的方法snd_card_new
int snd_card_new(struct device *parent, int idx, const char *xid,
struct module *module, int extra_size,
struct snd_card **card_ret)
{
struct snd_card *card;
int err;
if (snd_BUG_ON(!card_ret))
return -EINVAL;
*card_ret = NULL;
if (extra_size < 0)
extra_size = 0;
card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);
省略...
if (idx < 0) /* first check the matching module-name slot */
idx = get_slot_from_bitmask(idx, module_slot_match, module);
if (idx < 0) /* if not matched, assign an empty slot */
idx = get_slot_from_bitmask(idx, check_empty_slot, module);
省略...
card->number = idx;
省略...
err = kobject_set_name(&card->card_dev.kobj, "card%d", idx);
if (err < 0)
goto __error;
snprintf(card->irq_descr,
Android音频框架与策略管理详解

本文详细探讨了Android音频框架的结构,从应用层的AudioTrack和AudioRecord到底层驱动的交互过程。重点介绍了AudioFlinger的音频设备打开、数据传输,以及AudioPolicy的静态和动态配置策略。此外,还讲解了tinyalsa在音频设备初始化和数据匹配查找中的作用。音频焦点管理部分,包括内部和外部音频焦点的实现。通过对整个音频流程的剖析,揭示了Android音频系统的复杂性和灵活性。
最低0.47元/天 解锁文章
2543

被折叠的 条评论
为什么被折叠?



