摘要:在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) {
#else
if ((ConfigParsingUtils::loadConfig(AUDIO_POLICY_VENDOR_CONFIG_FILE, getConfig()) != NO_ERROR)
&& (ConfigParsingUtils::loadConfig(AUDIO_POLICY_CONFIG_FILE, getConfig()) != NO_ERROR)) {
#endif
ALOGE("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
这里面有三点需要稍微讲一下:
- 在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
- DevicesFactoryHalHidl.cpp -> DevicesFactory.impl.h的调用过程涉及到了HIDL的使用,中间跳过了几个文件,其实我们没必要深究HIDL的调用流程,毕竟都是Android写好的东西,会用就行了,如果想知道完整的调用流程,可以自己打印个调用栈看一下,关于如何打印调用栈可以参考这篇文章:Android9 C/C++打印调用栈的方法
- 在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;
}
这里要重点说明两个函数:
- 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,获取硬件结构地址 - 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()) {
......
//新建SwAudioOutputDescriptor
sp<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);
......
}
这个函数主要完成两件事:
- 调用到Hal层打开硬件模块:
outHwDev->openOutputStream
,最终会调用到Hal层厂家提供的函数,例如:adev_open_output_stream
- 为每个输出/输入模块创建一个工作线程:
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
- 首先需要在
AudioControl.cpp
真正实现这个方法 - 然后在
IAudioControl.hal
中增加正方法:setBand(int32_t card, int32_t band, int32_t value) generates (int32_t ret);
这样在编译后的文件中就会包含这个方法 - 之后在
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;
}
}
- 最后要将接口暴露给App,在CarAudioManager.java和ICarAudio.aidl中加入该接口即可
关于AIDL,其实和HIDL异曲同工,如果有需要的话可以参考这篇文章:Android Binder过程详细解析及AIDL工具原理分析