Sprd 9832A Android6.0 FM模式下的几种Audio路径以及非原生FM的音量调节
一.FM三种模式下的Audio路径
先放两张图:
图一:sprd9832A的Audio部分的框图
图二:三种FM模式Audio路径框图
下面说的内容,会用到上面这两张图。
模式1:Line_in-Codec Loop(模拟FM)
先看一下,AudioTester上面显示的框图(下图主机处于耳机模式“Headset”),图三:
可以看到,蓝色的路径,FM的信号是从Codec进来之后,直接又从Codec播放出去;红色的路径,FM信号从Codec给到主控,这个是录音的通道,对应文章开始图二中红色箭头的路径;
再看下图,详细给出了Codec的模块的路径,图四:
可以看到,FM的信号是从“AIL/AIR”接口输入,类似于MIC的输入,然后通过“FM record path”给到主控录音,通过“FM playback patch”直接输出。
在图三AudioTester工具中,看到的Codec部分可以调节增益的地方,在上图有清晰的体现;VBC部分可以调节增益的地方,在 图一和图二 中也有清晰的体现。
模式2:Line_in-VBC-Codec Loop(模拟FM)
看一下,AudioTester上面显示的框图(下图主机处于耳机模式“Headset”),图五:
可以看到,和“模式1”对比,主要差别是,蓝色的播放路径,先经过了“VBC”模块,然后才进入Codec播放出去,对应文章开始图二中棕色箭头的路径。
然后在“VBC”模块中,多了一个可以调节增益的地方“ST”,可以在图一和图二中看到“ST”所处的位置,更详细的看下图红框处, 图六 :
所以,其实图五中的“ST”就是“VBC”单元中的“Side Tone”模块,这个模块有15个音量等级,文章后面会讲到。
模式3: Digital FM-VBC-Codec Loop(数字FM)
再看一下,AudioTester上面显示的框图(下图主机处于耳机模式“Headset”),图七:
可以看到,和之前两种模式对比,FM的输入源改变了,直接数字信号输入,不经过Codec了,对应文章开始图二中蓝色箭头的路径。
二.非原生FM的音量调节
我做的车载项目,使用的是“模式3”这种数字FM,因为FM的输出音源,都不会经过系统,所以,系统里面音源的音量调节对于FM没有作用。比如,当收听FM的时候,同时有导航播报,此时需要减小FM音量,让导航音量正常出来,这种情况。
一开始,我调了Codec这边的RX_PGA和CG的增益,这个是调节耳机通道的模拟输出增益,发现导航和FM的音量同时都减小了,并且这种修改,无法在程序运行时动态修改,这样是不行的。
然后,我看到“ST”这里,这里FM的音源输入之后,还没有混入系统音源输出,并且看到这个“ST”有15个level等级,都可以调节增益,如下图, 图八 :
我猜测,这里就是调节FM音量的地方,但是,当我用工具调节了这里的增益之后,并没有效果,我估计,程序里面应该有一个可以调节这里的接口,可能因为没有使用原生FM,没有调用到这个接口,所以工具调节这里没有作用。
查看原生FM代码,找到如下代码片段:
public boolean setVolume(int volume) {
boolean value = false;
Log.d(TAG, "setVolume FM_Volume=" + volume);
if (mIsDeviceOpen) {
if (0 == volume) {
mFmManager.setMute(true);
} else {
mFmManager.setMute(false);
}
mAudioManager.setParameter("FM_Volume", "" + volume);
mFmManager.setVolume(volume);
value = true;
}
return value;
}
其中有这一行mAudioManager.setParameter("FM_Volume", "" + volume);
,追踪关键字FM_Volume,找到如下函数:
static int adev_set_parameters(struct audio_hw_device *dev, const char *kvpairs)
{
...
ret = str_parms_get_str(parms, "FM_Volume", value, sizeof(value));
...
SetAudio_gain_fmradio(adev,fm_gain);
...
}
找到函数SetAudio_gain_fmradio
,如下:
static int SetAudio_gain_fmradio(struct tiny_audio_device *adev, uint32_t gain)
{
audio_pga_apply(adev->pga,gain,"digital-fm");
return 0;
}
可以看到,上面函数audio_pga_apply
设置了“digital-fm”的增益,再追下去:
int audio_pga_apply(struct audio_pga *pga, int val, const char *name)
{
struct pga_profile *profile = NULL;
int i;
if (!pga) {
ALOGE("PGA is NULL");
return -1;
}
ALOGD("'%s' apply", name);
profile = profile_get_by_name(pga, name);
if (!profile) {
ALOGE("Profile name '%s' is not exists", name);
return -1;
}
for (i = 0; i < profile->length; i++) {
audio_pga_set(profile->item[i].ctl, (val >> profile->item[i].bit),
attribute_get_by_ctl(&pga->attribute, profile->item[i].ctl));
}
return 0;
}
这里可以看到,这个函数是通过“name”去设置不同的模块的增益,可以看到源码里有很多用这个函数设置增益的地方,比如:
//设置speaker输出增益
audio_pga_apply(adev->pga,pga_gain_nv->dac_pga_gain_l,"speaker-l");
audio_pga_apply(adev->pga,pga_gain_nv->dac_pga_gain_r,"speaker-r");
//设置MIC输入增益
audio_pga_apply(adev->pga,pga_gain_nv->adc_pga_gain_l,"capture-l");
audio_pga_apply(adev->pga,pga_gain_nv->adc_pga_gain_r,"capture-r");
看到这里,现在还不能确定,上面的程序接口是否可以调整“Side Tone”模块的增益,也没有资料去确认这一点,只能直接调用一下这个接口测试一下了。
在FM里加入测试接口,调动mAudioManager.setParameter("FM_Volume", "" + volume);
这个方法,volume值先给了0~100,然后在函数audio_pga_apply
里加了log打印,编译测试。
结果OK,每次调用mAudioManager.setParameter
的时候,就会调用audio_pga_apply
函数,其参数name = "digital-fm"
,然后“volume”值的有效范围是0~16,0的话直接FM静音,之后有15个音量等级,和 图八 显示的“Side Tone”等级吻合。
我推测的结论是,mAudioManager.setParameter("FM_Volume", "" + volume);
最终设置的就是“Side Tone”模块的音量等级的增益,不过由于没有原厂支持,也没有更详细的资料,所以,没法再去百分百确认,不过,我需要的功能是可以实现了!
遗留问题点
虽然使用mAudioManager.setParameter("FM_Volume", "" + volume);
可以调节FM音量,但是操作比较麻烦,尤其和导航混音的时候,所以本来想设置mAudioManager.setParameter("FM_Volume", "" + 0);
,让FM的playback路径静音,然后通过record路径把FM音源录入系统,再像播放音乐一样播放出来,这样修改了之后,FM确实可以播放,和导航的音源在系统里混音之后,可以很方便的调节彼此的音量,但是,发现,MIC不起作用了,看图二可以看到,FM的录音和MIC的录音是从同一个路径进去的,所以系统可能在FM录音的时候,忽略了MIC的声音或者禁止了MIC,这个应该属于系统Audio部分录音的策略问题,最后评估了一下,放弃了这种方式,这个问题也只能等以后有机会再看了!