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部分錄音的策略問題,最後評估了一下,放棄了這種方式,這個問題也只能等以後有機會再看了!