摘要:在Android9中Audio模块调用Hal一般有两种路径:
1.通过AudioFlinger调用到Hal,这是众多原生接口走的路径,一般我们不会在这里增加方法,但是会修改这里面的方法
2.如果是车机,则可以通过CarAudioService.java中的接口直接跳过JNI和Native层直接到达Hal,完成一些需要硬件支持的工作,一般我们会将新增的非原生方法增加到这里面

一、通过AudioFlinger调用Hal的流程

以AudioRecord.java中的getMinBufferSize接口为例,这个接口用于获得录音所需要的最小缓冲区大小,因为不同设备底层硬件不同,所以获得这个数值需要底层硬件的支持,但是在这之前,我们先来看一下上层模块是如何加载底层Hal模块的

1.音频模块获得Hal层句柄的方式

AudioFlinger作为Audio模块中和Hal沟通的门户,我们来看一下它是如何拿到Hal层的句柄的,在AudioPolicyService.cpp的onFirstef()方法中会构造AudioPolicyManager,在AudioPolicyManager的构造函数中会执行AudioPolicyManager::loadConfig()和AudioPolicyManager::initialize() 方法

先来看AudioPolicyManager::loadConfig()方法:

void AudioPolicyManager::loadConfig() {#ifdef USE_XML_AUDIO_POLICY_CONF  //一般都会定义这个宏,以便加载自定义的配置文件if (deserializeAudioPolicyXmlConfig(getConfig()) != NO_ERROR) {#elseif ((ConfigParsingUtils::loadConfig(AUDIO_POLICY_VENDOR_CONFIG_FILE, getConfig()) != NO_ERROR)&& (ConfigParsingUtils::loadConfig(AUDIO_POLICY_CONFIG_FILE, getConfig()) != NO_ERROR)) {#endifALOGE("could not load audio policy configuration file, setting defaults");getConfig().setDefault();}
}

在deserializeAudioPolicyXmlConfig中,会将audio_policy_configuration.xml存入fileNames数组中:

#define AUDIO_POLICY_XML_CONFIG_FILE_NAME "audio_policy_configuration.xml"
......
fileNames.push_back(AUDIO_POLICY_XML_CONFIG_FILE_NAME);

然后遍历fileNames数组中的XML文件并解析:

    for (const char* fileName : fileNames) {for (int i = 0; i < kConfigLocationListSize; i++) {PolicySerializer serializer;snprintf(audioPolicyXmlConfigFile, sizeof(audioPolicyXmlConfigFile),"%s/%s", kConfigLocationList[i], fileName);//在这里解析,解析后的信息保存在config中ret = serializer.deserialize(audioPolicyXmlConfigFile, config);if (ret == NO_ERROR) {return ret;}}}

这些从audio_policy_configuration.xml解析出的信息中最重要的就是module name,它将作为后面打开的so库的名字的一部分,因为Android为了保护硬件厂商的代码,会将代码实现以so库的形式出现在HAL层

解析后出来的信息封装为AudioPolicyConfig并保存在的mHwModulesAll变量中,这个变量其实是AudioPolicyManager的变量,在构造AudioPolicyConfig的时候也传了进去,所以在AudioPolicyManager中也可以访问

接着来看AudioPolicyManager::initialize()方法:
在initialize中会遍历mHwModulesAll数组,尝试根据module name加载硬件模块:

hwModule->setHandle(mpClientInterface->loadHwModule(hwModule->getName()));

调用到AudioPolicyClientImpl.cpp中的loadHwModule:

audio_module_handle_t AudioPolicyService::AudioPolicyClient::loadHwModule(const char *name)
{sp<IAudioFlinger> af = AudioSystem::get_audio_flinger();if (af == 0) {ALOGW("%s: could not get AudioFlinger", __func__);return AUDIO_MODULE_HANDLE_NONE;}return af->loadHwModule(name);
}

再调用到AudioFlinger.cpp中的loadHwModule,AudioFlinger会调用:

mDevicesFactoryHal->openDevice

接下来的调用流程是:
AudioFlinger.cpp -> DevicesFactoryHalHybrid.cpp -> DevicesFactoryHalHidl.cpp -> DevicesFactory.impl.h
这里面有三点需要稍微讲一下:

  1. 在AudioFlinger.cpp中通过mDevicesFactoryHal调用到DevicesFactoryHalHybrid.cpp,在创建mDevicesFactoryHal实例时使用DevicesFactoryHalInterface::create()静态方法:
sp<DevicesFactoryHalInterface> DevicesFactoryHalInterface::create() {if (hardware::audio::V4_0::IDevicesFactory::getService() != nullptr) {return new V4_0::DevicesFactoryHalHybrid();}if (hardware::audio::V2_0::IDevicesFactory::getService() != nullptr) {return new DevicesFactoryHalHybrid();}return nullptr;
}

可以看到先判断了V4_0是否可用,可用即返回,所以在audio模块里面的调用全部都在V4_0下而不是V2_0

  1. DevicesFactoryHalHidl.cpp -> DevicesFactory.impl.h的调用过程涉及到了HIDL的使用,中间跳过了几个文件,其实我们没必要深究HIDL的调用流程,毕竟都是Android写好的东西,会用就行了,如果想知道完整的调用流程,可以自己打印个调用栈看一下,关于如何打印调用栈可以参考这篇文章:Android9 C/C++打印调用栈的方法
  2. 在DevicesFactoryHalHidl.cpp的openDevice函数中,第二个参数以lambda的方式传入:
status_t DevicesFactoryHalHidl::openDevice(const char *name, sp<DeviceHalInterface> *device) {if (mDevicesFactory == 0) return NO_INIT;Result retval = Result::NOT_INITIALIZED;Return<void> ret = mDevicesFactory->openDevice(name,//注意这里传入的第二个参数[&](Result r, const sp<IDevice>& result) {retval = r;if (retval == Result::OK) {*device = new DeviceHalHidl(result);}});if (ret.isOk()) {if (retval == Result::OK) return OK;else if (retval == Result::INVALID_ARGUMENTS) return BAD_VALUE;else return NO_INIT;}return FAILED_TRANSACTION;
}

关于lambda表达很难一句话讲清楚,这里只要知道使用lambda表达式可以让我们把 “function” 当做是 “data” 一样传递,也就是说传入的第二个参数是一个函数,其入参是(Result r, const sp<IDevice>& result),在后续的函数中将会使用这个lambda函数

接着看DevicesFactory.impl.h中openDevice的实现:

#ifdef AUDIO_HAL_VERSION_4_0
Return<void> DevicesFactory::openDevice(const hidl_string& moduleName, openDevice_cb _hidl_cb) {if (moduleName == AUDIO_HARDWARE_MODULE_ID_PRIMARY) {return openDevice<PrimaryDevice>(moduleName.c_str(), _hidl_cb);}return openDevice(moduleName.c_str(), _hidl_cb);
}
......
#endif

我们可以看一下openDevice函数接收的第二个参数类型openDevice_cb在头文件中是怎么定义的:

using openDevice_cb = std::function<void(::android::hardware::audio::V4_0::Result retval, const ::android::sp<::android::hardware::audio::V4_0::IDevice>& result)>;

openDevice_cb期望接收一个std::function函数,印证了我们的解释,同时也说明了

创建lambda函数的一个原因是有人创建了一个希望接受(lambda函数)参数的函数

接着往下调用:

template <class DeviceShim, class Callback>
Return<void> DevicesFactory::openDevice(const char* moduleName, Callback _hidl_cb) {audio_hw_device_t* halDevice;Result retval(Result::INVALID_ARGUMENTS);sp<DeviceShim> result;//调用loadAudioInterface,得到audio_hw_device_t结构体指针,这个函数非常重要int halStatus = loadAudioInterface(moduleName, &halDevice);if (halStatus == OK) {result = new DeviceShim(halDevice);retval = Result::OK;} else if (halStatus == -EINVAL) {retval = Result::NOT_INITIALIZED;}//还记得传入的lambda函数吗?这里终于用到了!//将得到的halDevice(audio_hw_device_t结构体)包装了一下返回上层_hidl_cb(retval, result);return Void();
}

接着看至关重要的loadAudioInterface函数:

// static
int DevicesFactory::loadAudioInterface(const char* if_name, audio_hw_device_t** dev) {const hw_module_t* mod;int rc;ALOGD("loadHwModule() loadAudioInterface module is : %s", if_name);//调用hardware.c中的函数,这是Android打开硬件模块的通用方法rc = hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID, if_name, &mod);if (rc) {ALOGE("%s couldn't load audio hw module %s.%s (%s)", __func__, AUDIO_HARDWARE_MODULE_ID,if_name, strerror(-rc));goto out;}//调用hw_module_methods_t中的open方法打开硬件rc = audio_hw_device_open(mod, dev);if (rc) {ALOGE("%s couldn't open audio hw device in %s.%s (%s)", __func__, AUDIO_HARDWARE_MODULE_ID,if_name, strerror(-rc));goto out;}if ((*dev)->common.version < AUDIO_DEVICE_API_VERSION_MIN) {ALOGE("%s wrong audio hw device version %04x", __func__, (*dev)->common.version);rc = -EINVAL;audio_hw_device_close(*dev);goto out;}return OK;out:*dev = NULL;return rc;
}

这里要重点说明两个函数:

  1. hw_get_module_by_class
    这个函数是hardware.c中提供的函数,任何Android平台的硬件模块都通过这个函数加载。入参有两个:AUDIO_HARDWARE_MODULE_ID = “audio”,if_name = “primary”(因策略而异),它会根据传入的字符尝试补全为so库名称,然后在系统中查找已经注册的so库,若找到则调用load打开共享库,根据固定符号HAL_MODULE_INFO_SYM查找结构体hw_module_t,获取硬件结构地址
  2. audio_hw_device_open
    调用hw_module_methods_t中的open方法打开硬件,在open时传入hw_device_t二级指针,将模块的操作函数保存在hw_device_t中,实现与硬件的交互

如愿以偿,我们终于得到了audio模块的硬件结构体!
调用刚刚在DevicesFactory::openDevice里提到的lambda函数_hidl_cb(retval, result),将封装为DeviceHalHidl的就构体一路传回上层,在AudioFlinger中转换为DeviceHalInterface(DeviceHalHidl的父类),然后又被进一步封装为AudioHwDevice,并被保存在mAudioHwDevs容器中:mAudioHwDevs.add(handle, new AudioHwDevice(handle, name, dev, flags))
这个handle是取出AudioHwDevice的键,AudioFlinger::loadHwModule函数的返回值就是这个handle

让我们重返AudioPolicyManager::initialize:
在得到硬件模块在AudioFlinger的mAudioHwDevs容器中的键值handle后,调用hwModule->setHandle将其设置为hwModule的一个属性,然后将hwModule放入mHwModules数组中,之后分别遍历hwModule中包含的mOutputProfiles(输出模块)和mInputProfiles(输入模块),并为每一个输出/输入模块创建一个SwAudioOutputDescriptor,随后调用outputDesc->open函数:

for (const auto& outProfile : hwModule->getOutputProfiles()) {......//新建SwAudioOutputDescriptorsp<SwAudioOutputDescriptor> outputDesc = new SwAudioOutputDescriptor(outProfile,mpClientInterface);const DeviceVector &supportedDevices = outProfile->getSupportedDevices();const DeviceVector &devicesForType = supportedDevices.getDevicesFromType(profileType);String8 address = devicesForType.size() > 0 ? devicesForType.itemAt(0)->mAddress: String8("");audio_io_handle_t output = AUDIO_IO_HANDLE_NONE;//调用open函数status_t status = outputDesc->open(nullptr, profileType, address,AUDIO_STREAM_DEFAULT, AUDIO_OUTPUT_FLAG_NONE, &output);......
}

这个函数主要完成两件事:

  1. 调用到Hal层打开硬件模块:outHwDev->openOutputStream,最终会调用到Hal层厂家提供的函数,例如:adev_open_output_stream
  2. 为每个输出/输入模块创建一个工作线程:
sp<MmapPlaybackThread> thread =new MmapPlaybackThread(this, *output, outHwDev, outputStream,devices, AUDIO_DEVICE_NONE, mSystemReady);

注意在open的时候传入了一个audio_io_handle_t类型的参数output,证明audio_io_handle_t和工作线程是一一对应的,以后会根据audio_io_handle_t找到对应的工作线程

2.getMinBufferSize的调用流程(原生接口调用Hal流程)

懒得啰嗦,直接上一个UML图吧,这个流程可以纯看代码看出来,也可以直接在C++和C层打印两个调用栈就一目了然了

3.原生Hal的常见修改方法

正如上文所说的,在原生的Hal中不会增加方法,但是会修改方法(至少在车机领域是这样的),其中最常见的就是按照底层需求修改声道数和采样率等参数,网上也有比较多这样的文章,具体的修改方法先按下不表,有时间专门写一篇文章来讲解

二、通过CarAudioService调用Hal的流程

CarAudioService实际上是属于Car模块的,通过它可以直接跳过JNI和Native层,直接和Hal层取得联系,这全都归功于HIDL工具的强大功能。

1.HIDL工具——访问Hal的快速通道

为什么HIDL工具可以使CarAudioService直接访问Hal层?
简单来说,在hardware/interfaces/目录下正确编写并增加.hal文件后,利用hidl-gen就会生成新增的hal模块的编译脚本,以/hardware/interfaces/automotive/audiocontrol/1.0目录下的Android.bp为例,在其中有一句话:gen_java: true,这句话会使编译此Hal模块后在out目录下生成一个Java文件,其名称一般和Hal模块的名称相同,这里是IAudioControl.java,在这个文件里面会包含一个getAudioControl方法,只要利用这个方法,就可以获取Hal层的句柄

没错,这些都是Hidl工具自动生成的,这么方便的工具如何为我所用呢?可以参考这篇文章:Android HIDL学习

接下来只要在CarAudioService.java中引入这个包:

import android.hardware.automotive.audiocontrol.V1_0.IAudioControl;

同时在CarAudioService.java的编译脚本中引入AudioControl模块:

LOCAL_STATIC_JAVA_LIBRARIES += \android.hidl.base-V1.0-java \android.hardware.automotive.audiocontrol-V1.0-java \  //就在这里!android.hardware.automotive.vehicle-V2.0-java \vehicle-hal-support-lib \car-frameworks-service \car-systemtest \com.android.car.procfsinspector-client \

大功告成,现在CarAudioService.java会直接通过Hidl工具生成的文件,最终调用到AudioControl.cpp

2.CarAudioService中Hal的修改方法

在车机开发中,我们常常把新增加的方法写到这里,例如我现在需要一个设置音效的接口名叫setBand

  1. 首先需要在AudioControl.cpp真正实现这个方法
  2. 然后在IAudioControl.hal中增加正方法:setBand(int32_t card, int32_t band, int32_t value) generates (int32_t ret);这样在编译后的文件中就会包含这个方法
  3. 之后在CarAudioService.java中加入能访问该方法的接口,模仿原有的接口即可:
    public int setBand(int card, int band, int value) {synchronized (mImplLock) {enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);final IAudioControl audioControlHal = getAudioControl();if (audioControlHal != null) {try {int ret = audioControlHal.setBand(card, band, value);Log.d(CarLog.TAG_AUDIO, "setBand card = "+card + ", band = " + band + ", value = " + value + ", ret = " + ret);return ret;} catch (RemoteException e) {Log.e(CarLog.TAG_AUDIO, "setBand failed", e);}}return -1;}}
  1. 最后要将接口暴露给App,在CarAudioManager.java和ICarAudio.aidl中加入该接口即可

关于AIDL,其实和HIDL异曲同工,如果有需要的话可以参考这篇文章:Android Binder过程详细解析及AIDL工具原理分析

Android9 Audio模块Hal层加载流程及修改方法相关推荐

  1. Linux模块加载流程及如何让系统开机自动加载模块

    Linux模块加载 Linux系统加载哪些内核模块,和配置文件有关系. 模块保存在/lib/modules/下. 使用/etc/modules-load.d/来配置系统启动时加载哪些模块. 使用/et ...

  2. camera驱动开机加载流程

    camera驱动 图片不好上传,可以跟着源码去看,MT6735下面的 也可以查看有道云链接:点击打开链接 一.模块加载函数 模块加载函数位于kd_sensorlist.c文件中,kd_sensorli ...

  3. QT程序启动加载流程简介

    1. QT应用程序启动加载流程简介 1.1      QWS与QPA启动客户端程序区别 1.1.1   QWS(Qt Window System)介绍 QWS(Qt Windows System)是Q ...

  4. linux Pci字符驱动基本加载流程

    今天有朋友问我linux系统Pci字符驱动加载流程,简单整理了一下,顺便做个记录. 首先说下需要包含的头文件: 一个完整的字符驱动一般包含下面这些头文件: #include <linux/typ ...

  5. Android SIM卡识别加载流程

    文章目录 总述 代码路径 UICC框架 SIM卡识别加载流程 日志分析举例 总述 本文基于Android N(Android 7) 首先要知道SIM卡一般是挂载在CP侧(MODEM侧)的,由MODEM ...

  6. Launcher启动流程加载流程学习

     声明: 图片本来是有的 涉及到有些代码不能示人没有贴上,不过仅文字说也足够了,请广大老爷们自行下载源码参看流程分析阅读. 目录 一.认识Launcher: 1 1.功能 1 2.样式 2 3.And ...

  7. AngularJS 初始化加载流程

    一.AngularJS 初始化加载流程 1.浏览器载入HTML,然后把它解析成DOM. 2.浏览器载入angular.js脚本. 3.AngularJS等到DOMContentLoaded事件触发. ...

  8. android内存加载dex,安卓8.1版本dex加载流程笔记--第一篇:oatfile,oatdexfile与dexfile...

    本帖最后由 L剑仙 于 2020-3-1 18:53 编辑 看雪发一遍了,在52再发一次 菜鸟最近初学脱壳,必须得先搞明白dex的加载流程,才能搞懂哪里到了脱壳的时机.n6装的8.1,最近跟了一遍8. ...

  9. JVM源码阅读-本地库加载流程和原理

    前言 本文主要研究OpenJDK中JVM源码中涉及到native本地库的加载流程和原理的部分.主要目的是为了了解本地库是如何被加载到虚拟机,以及是如何找到并执行本地库里的本地方法,以及JNI的 JNI ...

最新文章

  1. Linux的常用的命令
  2. C++ Priority Queues(优先队列)
  3. 斯坦福python中文分词stanza
  4. HTML的xmlns的作用
  5. 单片机c语言怎样添加自定义头文件,单片机C语言编程与或|头文件常见问题
  6. 网易交互设计师微专业C5 交互设计测试与评估
  7. 用POP3获取邮箱邮件内容(完整C#源码)
  8. java nginx报502,Nginx 502错误排查及解决办法
  9. xml保存图片和读取图片(一)
  10. typecho+handsome美化
  11. 让博客Docker化,轻松上手Docker
  12. C89和C99标准对比
  13. 电脑自动开机是什么原因
  14. 硅计算机的原理,量子计算机工作原理揭秘
  15. html 如何完美的显示图片,不拉伸图片,完整显示等等。
  16. (C++)1~10000质数表
  17. 零样本分割系列论文(2)Open-Vocabulary Instance Segmentation via Robust Cross-Modal Pseudo-Labeling
  18. 金山词霸字典转换工具
  19. 自动下载mnist数据集并解压分类为jpg图片
  20. RHCA-RH318 V4.1-RHV虚拟化

热门文章

  1. C语言考试题--星号直角三角形输出求解
  2. BarManager控件介绍
  3. Oracle 的内存参数配置
  4. !!!有奖竞猜!!!运行以下程序,会出现什么问题?为什么?(一个C++的基础题)
  5. PHP研发对接第三方接口常用的一些方法函数
  6. 计算机会操作软件怎么填,人是一台计算机,每个人都在写程序
  7. 我连对象都没有,你却让我搞对象存储?
  8. python输入学生姓名_Python练习题:由用户输入学生学号与姓名,数据用字典存储,最终输出学生信息(按学号由小到大显示)。...
  9. Jmeter操作手册
  10. Linux命令总结思维导图