Android9 Audio模块Hal层加载流程及修改方法

摘要:在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
这里面有三点需要稍微讲一下:

  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()) {
		......
			//新建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);
		......
}

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

  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层打印两个调用栈就一目了然了
Android9 Audio模块Hal层加载流程及修改方法

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工具原理分析

上一篇:HTML5学习(四)---Web表单


下一篇:JSON结构说明