目录
一、概述
二、方式一:上层传递参数
三、方式二: 底层改变AudioPolicy
四、总结
一、概述
要想了解Android如何选择音频的输出/入的整个流程,最基本的需要了解清楚AudioPolicy和AudioFinger的功能和关系,以便能够更深入的学习整个Audio框架。
这里只是对实际应用中的功能需求做出方法论。
因为器件测试,OEM功能定制等方面的需求,通常会需要根据不同的usecase来改变最终的输出/入设备。以下给出来两种方式来实现需求,但两种方式各有优劣,笔者最后也会根据自己的工作经历总结不同方式适用的情景。
关于Android Audio系统介绍可以看底部推荐扩展阅读的两个博客,笔者个人觉得很不错。
ps: 如果读者没有做过Android底层的开发也可以做一些了解
二、方式一: 上层传递参数
这种方式可以通过底层的定制为上层提供接口,apk可以直接调用相应的接口来直接达到打开指定的设备的目的。
设计思路:Java层直接使用原生的接口AudioManager.setParameters()
向下层传递指定的参数,底层在audio_extn.c
中实现相关函数去接收参数,再在手机相应的平台的platform.c
中的设置输入/出的函数中调用之前写好的函数即可
整个思路很简单,但是没有实际的例子有说服力。
假定一个功能需求:在录音时,apk传入参数"MIC_CHOOSE=1"
使得设备最终使用双麦克录音。
首先,由于是直接调用原生的接口AudioManager.setParameters("MIC_CHOOSE=1")
所以我们这里暂且不用关注这个参数是怎么从Java层一直传递下来的,只需要知道Android中可以在hardware/qcom/audio/hal/audio_extn/audio_extn.c
中获取所有通过这个接口传递下来的参数,如果使用的是高通平台。
- 在
hardware/qcom/audio/hal/audio_extn/audio_extn.h
头文件中声明两个函数,用于保存和读取获取到的参数。
//获取保存好的上层传递来的参数
int audio_extn_get_mic_choose_parameters(void);
//接收并保存上层传来的参数
void audio_extn_set_mic_choose_parameters(struct str_parms *parms);
- 在
hardware/qcom/audio/hal/audio_extn/audio_extn.c
中实现上面在头文件中声明的两个函数。可是在这之前,要考虑在什么地方去保存我们获取到的参数,以便之后读取呢?
查看这个C文件后,发现在一个static的结构体audio_extn_module
中已经定义了性质相同的一些变量,那么我们也就按照原生的设计逻辑来,在这个结构体中定义变量mic_choose
来保存获取的参数。
struct audio_extn_module {
...
int mic_choose;
};
同时需要在对该结构体初始化的地方也对我们定义的变量进行初始化:
static struct audio_extn_module aextnmod = {
...
.mic_choose = 0,
};
紧接着就是对头文件中的两个函数实现:
int audio_extn_get_mic_choose_parameters(void)
{
ALOGD("%s: mic_choose:%d", __func__, aextnmod.mic_choose);
return aextnmod.mic_choose;
}
void audio_extn_set_mic_choose_parameters(struct str_parms *parms)
{
int ret;
char value[32] = {0};
ret = str_parms_get_str(parms, "SET_MIC_CHOOSE", value, sizeof(value));
ALOGD("mic_choose_ret:%d", ret);
if (ret >= 0) {
if (strcmp(value, "1") == 0){//接收到的参数为1,表示使用双麦克录音
aextnmod.mic_choose = 1;
}
}
ALOGD("%s: mic_choose:%d, value:%s", __func__, aextnmod.mic_choose, value);
}
到这里,可能读者会想,要在什么地方去调用上面这个函数去获取传递来的参数呢?Android已经设计好了一个函数,void audio_extn_set_parameters(struct audio_device *adev, struct str_parms *parms)
在其中加入所有需要获取AudioManager.setParameters()传递来的参数的函数,就可以接收我们想要的参数。至于这个函数是谁调用的,何时调用,这里暂不讨论,感兴趣的读者可以自行阅读源码。
void audio_extn_set_parameters(struct audio_device *adev, struct str_parms *parms) {
audio_extn_set_anc_parameters(adev, parms);
audio_extn_set_mic_choose_parameters(parms);//我们自己定义的函数
audio_extn_set_fluence_parameters(adev, parms);
audio_extn_set_afe_proxy_parameters(adev, parms);
audio_extn_fm_set_parameters(adev, parms);
...
}
以上做的都是准备工作,最后一步,需要在最终选择设备的地方去加入我们的逻辑实现目的。
- 在
hardware/qcom/audio/hal/msm8996/platform.c
(假设手机使用的是高通的msm8996平台)中在选择输入设备的方法中的录音case中插入我们的代码使得最终使用的设备为双麦克
snd_device_t platform_get_input_snd_device(void *platform, audio_devices_t out_device) {
...
else if(source == AUDIO_SOURCE_MIC){//使用mic录音的case
...
int mic_choose = 0;
mic_choose = audio_extn_get_mic_choose_parameters();
if (mic_choose == 1){ //将snd_device赋值为双麦克
snd_device = SND_DEVICE_IN_HANDSET_DMIC;
}
ALOGD("%s: mic_choose (%s)", __func__, device_table[snd_device]);
}
}
这里需要注意,在不同的OEM代码中,对于双麦克定义的设备名称可能会有不同,例如还有定义为SND_DEVICE_IN_HANDSET_STEREO_DMIC
的,这里只是一个参考。
三、方式二: 底层改变AudioPolicy(不推荐)
我们知道在什么usecase下要使用什么设备,是由AudioPolicy来决定的,所以,通过改变其中的选择策略可以实现我们的目的。
我们可以在AudioPolicy的核心文件之一Engine.cpp
中找到录音的usecase去做相应的改动。
文件路径
frameworks/av/services/audiopolicy/enginedefault/src/Engine.cpp
这里需要读者先弄清楚AudioPolicy中strategy的概念,之后再在Engine::getDeviceForStrategyInt()
中修改相应的Strategy选择的设备。
这里就不贴代码了,有需求的可以留言。
四、总结
看完了两种修改音频设备的方式,读者心里可能对于两种方式的优劣有了一些想法,笔者在这里只是从工作实践中比较一下两种方式如何取舍:
- AudioManager.setParameters() 传递参数的方法更为灵活,如果需求有变更,修改代码的量也极小。最大的优点笔者认为有两个:1. 不改动原生的AudioPolicy逻辑,可以很大程度降低因此引起的bug数量; 2. 使用原生的方式就能够为上层提供调用接口,大大降低工作量。
适用场景: 器件测试,apk特殊需求 - 修改AudioPolicy的方式如果工程师对整个Policy逻辑不熟悉会很大程度地引起很多莫名其妙的bug,所以笔者并不推荐这种方式。
适用场景:系统层面的功能需求
写在最后
在实际项目中对于每一个新需求的开发,要养成一个好习惯:
加入项目控制或宏控制!!!
加入项目控制或宏控制!!!
加入项目控制或宏控制!!!
重要的事说三遍!!!