最近遇到幾個與AudioEffect相關的問題,在此記錄下作爲一個記憶總結
android系統中如果想使用自己開發或者第三方的音效算法,有一種比較簡單的辦法就是放到hal層的out_write接口中,這樣做優點是簡單,方便快速集成。還有標準的做法就是做成android標準的音效接口,上層應用就可以像使用android自帶的音效一樣來調用自己所添加的自定義音效。
如何實現自定義的音效庫以及AudioEffect的構造流程分析,已經有不少寫的很好的文章,可以參考以下博客
AudioEffect構造流程跟蹤 & 音效庫實現(native側)
https://blog.csdn.net/wkw1125/article/details/65632960
Android Effect 解析
https://blog.csdn.net/kuang_tian_you/article/details/83510713
這裏主要記錄我遇到的幾個問題,我看很少有文章講到,在此做個記錄
之前講到音效集成自己放在hal層或是在送往alsa輸出之前做處理這種方法簡單同時也能保證系統輸出的音頻數據都能經過音效算法處理。做成android音效標準接口就有需要注意的地方了,先來看下android audioeffect 處理的地方,在audioflinger的playbackthread中PlaybackThread::threadLoop()
這就意味中必須是meplayer或是audiotrack播放的音頻流才能獲得音效作用,但是對於一些android TV來說,很多方案商的TV In的音頻數據比如HDMI IN、AV IN並不通過audiotrack去播放,例如google專門爲android TV設計的TIF架構,走的是audiopatch機制,audio in到 audio out的處理基本上在hal層就幹完了,不經過audiotrack 那麼如何使用auudioeffect音效呢,這樣就能想到如果音效不在audioflinger那一層處理,而是也在hal層去處理,這個功能就完成了。可以看到audioeffect在構造的過程中音效enable時會更新一個狀態機
在playbackthread裏音效process實時更新狀態
EffectModule::updateState中調用start_l(),裏面有個接口
可以看到addEffectToHal_l會根據是否有EFFECT_FLAG_TYPE_PRE_PROC和EFFECT_FLAG_TYPE_POST_PROC的flag來判斷是否將音效add到hal層
hal層對應實現addEffect這個接口 同時在寫到alsa之前調用process即有了音效處理的作用
這就意味着在構造音效庫的時候就要指定這些flag,舉例如下:
const effect_descriptor_t AvlDescriptor={
{0x4a959f5c,0xe33a,0x4df2,0x8c3f,{0x30,0x66,0xf9,0x27,0x5e,0xdf}},
{0x08246a2a,0xb2d3,0x4621,0xb804,{0x42,0xc9,0xb4,0x78,0xeb,0x9d}},
EFFECT_CONTROL_API_VERSION,
EFFECT_FLAG_TYPE_POST_PROC | EFFECT_FLAG_DEVICE_IND | EFFECT_FLAG_NO_PROCESS | EFFECT_FLAG_OFFLOAD_SUPPORTED,
AVL_CUP_LOAD_ARM9E,
AVL_MEM_USAGE,
"Avl",
"xxxx",
};
EFFECT_FLAG_NO_PROCESS這個flag也很重要,這個flag聲明之後 audioflinger那一層將會直接bypass處理不會經過音效作用,具體代碼在EffectModule::process()裏
同時我們也注意到這個狀態機的更新在playbackthread的process loop裏,所以創建音效時需要保證playbackthread在running的狀態,因爲playbackthread在android啓動後沒有音頻播放會進入stabdby狀態,所以爲了保證音效創建成功,創建的同時可以播放一段空的audiotrack以保證playbackthread是active的。
在這裏我們所有的音效都是全局的,即創建音效時指定的sessionId爲AUDIO_SESSION_OUTPUT_MIX 這樣系統創建一次音效,音效鏈將會一直綁定在audioflinger上,整個系統將一直有音效作用,而不必像一般內度應用那樣創建的時候獲取mediaplayer或是audiotrack的sessionId
參數audioSession,相同audioSession ID的AudioTrack和MediaPlayer共享Audio Effect,這是android的接口註釋
這裏需要注意的是一旦有應用使用非全局的sessionId,並且enable之後會導致我們設置的全局音效被禁用,來看看這部分代碼:
進去checkSuspendOnEffectEnabled會調用到
void AudioFlinger::ThreadBase::checkSuspendOnEffectEnabled(const sp<EffectModule>& effect,
bool enabled,
audio_session_t sessionId)
{
Mutex::Autolock _l(mLock);
checkSuspendOnEffectEnabled_l(effect, enabled, sessionId);
}
void AudioFlinger::ThreadBase::checkSuspendOnEffectEnabled_l(const sp<EffectModule>& effect,
bool enabled,
audio_session_t sessionId)
{
if (mType != RECORD) {
// suspend all effects in AUDIO_SESSION_OUTPUT_MIX when enabling any effect on
// another session. This gives the priority to well behaved effect control panels
// and applications not using global effects.
// Enabling post processing in AUDIO_SESSION_OUTPUT_STAGE session does not affect
// global effects
if ((sessionId != AUDIO_SESSION_OUTPUT_MIX) && (sessionId != AUDIO_SESSION_OUTPUT_STAGE)) {
setEffectSuspended_l(NULL, enabled, AUDIO_SESSION_OUTPUT_MIX);
}
}
sp<EffectChain> chain = getEffectChain_l(sessionId);
if (chain != 0) {
chain->checkSuspendOnEffectEnabled(effect, enabled);
}
}
可以看到當sessionId不是全局的時候
void AudioFlinger::ThreadBase::setEffectSuspended_l(
const effect_uuid_t *type, bool suspend, audio_session_t sessionId)
{
sp<EffectChain> chain = getEffectChain_l(sessionId);
if (chain != 0) {
if (type != NULL) {
chain->setEffectSuspended_l(type, suspend);
} else {
chain->setEffectSuspendedAll_l(suspend);
}
}
updateSuspendedSessions_l(type, suspend, sessionId);
}
setEffectSuspended_l會disable到其他的音效,所以對於系統集成音效來說,所有音效的使用盡量都使用全局的sessionId