在高通的一個文檔上提到了安卓10 Audio引入了一個balance功能(由於保密緣由,具體文檔名應該也不讓貼吧),文檔內容也簡單,就提了下設置界面和dumpsys查看值,java
那就研究下這個東西安卓咋實現的及其原理吧。android
<!-- more -->c++
[Platform:Android 11]
http://aosp.opersys.com/xref/...shell
Balance 實際上是用於設置左右平衡的,如今手機上立體聲喇叭也多起來了,說直觀點效果就是設置左右喇叭音量大小的。數據庫
另外說下音量平衡這個功能在車機上也有需求,結合先後淡化(Fade),可實現聲場的效果。
爲此谷歌引入了AudioControl,經過 setBalanceTowardRight() setFadeTowardFront() 這兩個接口來設置左右平衡,先後淡化達到設置聲場效果。
相關的資料可看下 https://source.android.google...
不過呢, 這兩個接口在HAL層時須要芯片廠商實現, 也就是說芯片廠商可能實現了也可能沒實現, 好比高通8155 HAL層就沒實現該功能。promise
<center>圖1. 左右平衡設置界面</center>app
在上面的界面中,把條拖到最左邊,則聲音徹底調到左側;一樣,把條拖到最右邊,則聲音徹底調到右側。
上面拖動條的值目前爲[0, 200],以後會映射到[-1.0f, 1.0f]存到數據庫,
從代碼上看還作了點貼心的處理, 即在中央 +/- 6 時設爲中間的值。dom
拖動條關鍵代碼:ide
packages/apps/Settings/src/com/android/settings/accessibility/BalanceSeekBar.java public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser) { // Snap to centre when within the specified threshold // mSnapThreshold 目前爲6, 也就是中間+/-6位置時調爲中間 if (progress != mCenter && progress > mCenter - mSnapThreshold && progress < mCenter + mSnapThreshold) { progress = mCenter; seekBar.setProgress(progress); // direct update (fromUser becomes false) } // 把0~200映射到 -1.0f~1.0f final float balance = (progress - mCenter) * 0.01f; // 最後設置到了數據庫裏 Settings.System.putFloatForUser(mContext.getContentResolver(), Settings.System.MASTER_BALANCE, balance, UserHandle.USER_CURRENT); }
咱們也可直接用命令行調節其值函數
# MASTER_BALANCE 定義 # frameworks/base/core/java/android/provider/Settings.java public static final String MASTER_BALANCE = "master_balance"; # 命令行設置 master balance adb shell settings put system master_balance 值 # 命令行獲取 master balance adb shell settings get system master_balance
那是誰在接收這個值呢?
經過對 MASTER_BALANCE 搜索,發現其在 AudioService 構造函數裏,會new 一個 SettingsObserver 對象,該類專門用於AudioService 監聽Settings數據庫,當 MASTER_BALANCE 值有變化時,調用 updateMasterBalance() --> AudioSystem.setMasterBalance() 更新,
也就是說其實AudioServer其實也是經過AudioSystem進一步往下設置的。
frameworks/base/services/core/java/com/android/server/audio/AudioService.java public AudioService(Context context, AudioSystemAdapter audioSystem, SystemServerAdapter systemServer) { ... // AudioService 建立 SettingsObserver對象 mSettingsObserver = new SettingsObserver(); private class SettingsObserver extends ContentObserver { SettingsObserver() { ... // SettingsObserver 構造函數裏對 MASTER_BALANCE 進行監聽 mContentResolver.registerContentObserver(Settings.System.getUriFor( Settings.System.MASTER_BALANCE), false, this); ... } @Override public void onChange(boolean selfChange) { ... // 當監聽的數據有變化時, 調用該函數更新 master balance // 須要說一下的是當 開機和AudioServer死了重啓時也會調該函數設置balance值給AudioFlinger. updateMasterBalance(mContentResolver); ... } private void updateMasterBalance(ContentResolver cr) { // 獲取值 final float masterBalance = System.getFloatForUser( cr, System.MASTER_BALANCE, 0.f /* default */, UserHandle.USER_CURRENT); ... // 經過AudioSystem設置下去 if (AudioSystem.setMasterBalance(masterBalance) != 0) { Log.e(TAG, String.format("setMasterBalance failed for %f", masterBalance)); } }
AudioSystem最終會設置到AudioFlinger裏,這中間的過程比較簡單,無非是繞來繞去的一些binder調用,不熟悉的就看下我列的流程就好了。
frameworks/base/media/java/android/media/AudioSystem.java setMasterBalance() + --> JNI + android_media_AudioSystem_setMasterBalance() / android_media_AudioSystem.cpp + AudioSystem::setMasterBalance(balance) + setMasterBalance() / AudioSystem.cpp + const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger(); + af->setMasterBalance(balance) // 調用AudioFlinger的setMasterBalance + setMasterBalance() / AudioFlinger.cpp + mPlaybackThreads.valueAt(i)->setMasterBalance(balance); + mMasterBalance.store(balance);
在AudioFlinger裏,會先進行權限,參數合法性,是否和以前設置相同等檢查,最終經過for循環設置給播放線程,
須要注意的是,duplicating線程被略過了,也就是說 master balance對 duplicating 播放方式無效。
Tips: duplicating爲複製播放,經常使用於藍牙和喇叭同時播放鈴聲。
frameworks/av/services/audioflinger/AudioFlinger.cpp status_t AudioFlinger::setMasterBalance(float balance) { ... // 權限檢查 // check calling permissions if (!settingsAllowed()) { ... // 參數合法性檢查 // check range if (isnan(balance) || fabs(balance) > 1.f) { ...// 是否和以前的值相同 // short cut. if (mMasterBalance == balance) return NO_ERROR; mMasterBalance = balance; for (size_t i = 0; i < mPlaybackThreads.size(); i++) { // 若是是 duplicating的,不處理 if (mPlaybackThreads.valueAt(i)->isDuplicating()) { continue; } // 調用線程的設置方法 mPlaybackThreads.valueAt(i)->setMasterBalance(balance); } return NO_ERROR; }
熟悉audio的知道,Android將playback thread又分爲了fast thread, mixer thread, direct thread等線程,以實現快速,混音,直接offload播放等目的,因此每種播放線程的 setMasterBalance() 以及後續的 balance處理都有可能不同,咱們這裏以典型的 mixer thread爲例進行分析,其他的方式如有用到可本身看看代碼。
PlaybackThread 裏將該值存了起來,就結束了
frameworks/av/services/audioflinger/Threads.cpp void AudioFlinger::PlaybackThread::setMasterBalance(float balance) { mMasterBalance.store(balance); } Threads裏mMasterBalance定義,爲原子類型 frameworks/av/services/audioflinger/Threads.h std::atomic<float> mMasterBalance{};
mMasterBalance爲一原子類型,其存儲/讀取方法爲store()/load(),setMasterBalance()最終用store()將balance值存了起來,要想繼續看balance過程就得找找個哪兒在使用該值了。
使用mMasterBalance的地方也有好幾個,咱們也以PlaybackThread進行分析,direct方式有須要能夠本身看看。
PlaybackThread的threadLoop()是音頻處理的一個主要的函數,代碼也很長,主要作的工做爲 事件處理,準備音軌,混音,音效鏈處理,以及咱們這要說的左右平衡處理,最後將數據寫入到HAL,別的流程有興趣的能夠研究研究,本文主要看下balance處理。
bool AudioFlinger::PlaybackThread::threadLoop() {...// 循環處理,一直到線程須要退出 for (int64_t loopCount = 0; !exitPending(); ++loopCount) {...// 事件處理 processConfigEvents_l(); ...// 準備音軌 mMixerStatus = prepareTracks_l(&tracksToRemove); ...// 混音 threadLoop_mix(); ...// 音效鏈處理 effectChains[i]->process_l(); ...// 左右平衡處理 if (!hasFastMixer()) { // Balance must take effect after mono conversion. // We do it here if there is no FastMixer. // mBalance detects zero balance within the class for speed (not needed here). // 讀取balance值並經過setBalance()方法賦給audio_utils::Balance mBalance.setBalance(mMasterBalance.load()); // 對buffer進行平衡處理 mBalance.process((float *)mEffectBuffer, mNormalFrameCount); } ...// 將處理完的數據寫入到HAL ret = threadLoop_write(); ... } ... } mBalance 定義 frameworks/av/services/audioflinger/Threads.h audio_utils::Balance mBalance;
從上面代碼看到,若是線程裏有Fast Mixer的話,那麼不會作平衡處理,而後引進了個新類 audio_utils::Balance 專門進行平衡處理,有關的方法爲 setBalance() process(), 從直覺上以爲看了 process()函數就能明白其原理了,那咱們就先看下該函數。
system/media/audio_utils/Balance.cpp void Balance::process(float *buffer, size_t frames) { // 值在中間和單聲道不作處理 if (mBalance == 0.f || mChannelCount < 2) { return; } if (mRamp) { ... // ramp處理 // ramped balance for (size_t i = 0; i < frames; ++i) { const float findex = i; for (size_t j = 0; j < mChannelCount; ++j) { // better precision: delta * i // 改變balance後首次調process會進行ramp處理 *buffer++ *= mRampVolumes[j] + mDeltas[j] * findex; } } ... } // 非ramp方式處理 // non-ramped balance for (size_t i = 0; i < frames; ++i) { for (size_t j = 0; j < mChannelCount; ++j) { // 對傳入的buffer每一個聲道乘以某個係數 *buffer++ *= mVolumes[j]; } } }
process() 中對balance在中間和單聲道狀況都不作處理,而後又分爲了ramp和非ramp方式,這兩個方式都是對傳入的buffer每一個聲道都乘以了某個係數。咱們主要是關心非ramp方式 *buffer++ *= mVolumes[j];
, 接下來就看下其 mVolumes[j],即左右聲道係數是多少?
爲了搞清楚其mVolumes的值,須要回頭再看下其 setBalance() 方法,
system/media/audio_utils/Balance.cpp void Balance::setBalance(float balance) {...// 有效性檢查,代碼略過 // 單聲道不處理 if (mChannelCount < 2) { // if channel count is 1, mVolumes[0] is already set to 1.f return; // and if channel count < 2, we don't do anything in process(). } // 常見的雙聲道方式處理 // Handle the common cases: // stereo and channel index masks only affect the first two channels as left and right. if (mChannelMask == AUDIO_CHANNEL_OUT_STEREO || audio_channel_mask_get_representation(mChannelMask) == AUDIO_CHANNEL_REPRESENTATION_INDEX) { // 計算左右聲道平衡係數 computeStereoBalance(balance, &mVolumes[0], &mVolumes[1]); return; } // 聲道大於2 處理 // For position masks with more than 2 channels, we consider which side the // speaker position is on to figure the volume used. float balanceVolumes[3]; // left, right, center // 計算左右聲道平衡係數 computeStereoBalance(balance, &balanceVolumes[0], &balanceVolumes[1]); // 中間固定 balanceVolumes[2] = 1.f; // center TODO: consider center scaling. for (size_t i = 0; i < mVolumes.size(); ++i) { mVolumes[i] = balanceVolumes[mSides[i]]; } }
setBalance()裏對單聲道,雙聲道,多聲道進行了處理,其中單聲道係數固定爲1.f;雙聲道和多聲道都會調用 computeStereoBalance() 計算其左右平衡係數;多聲道目前應該還沒作好,其中間爲固定值1.f。
終於來到了關鍵的左右聲道係數計算函數了!
void Balance::computeStereoBalance(float balance, float *left, float *right) const { if (balance > 0.f) { // balance往右狀況 *left = mCurve(1.f - balance); *right = 1.f; } else if (balance < 0.f) { // balance往左狀況 *left = 1.f; *right = mCurve(1.f + balance); } else { // balance在中間 *left = 1.f; *right = 1.f; } // Functionally: // *left = balance > 0.f ? mCurve(1.f - balance) : 1.f; // *right = balance < 0.f ? mCurve(1.f + balance) : 1.f; }
計數係數時:
balance往右,右聲道固定1.f, 左聲道爲 mCurve(1.f - balance);
balance往左,左聲道固定1.f, 右聲道爲 mCurve(1.f + balance);
也就是說,
balance往哪邊,哪邊的音量固定爲1.f,另外一邊乘以係數 mCurve(1.f - |balance|) (balance∈[-1.0, 1.0])
接下來繼續看下mCurve曲線,
system/media/audio_utils/include/audio_utils/Balance.h class Balance { public: /** * \brief Balance processing of left-right volume on audio data. * * Allows processing of audio data with a single balance parameter from [-1, 1]. * For efficiency, the class caches balance and channel mask data between calls; * hence, use by multiple threads will require caller locking. * * \param ramp whether to ramp volume or not. * \param curve a monotonic increasing function f: [0, 1] -> [a, b] * which represents the volume steps from an input domain of [0, 1] to * an output range [a, b] (ostensibly also from 0 to 1). * If [a, b] is not [0, 1], it is normalized to [0, 1]. * Curve is typically a convex function, some possible examples: * [](float x) { return expf(2.f * x); } * or * [](float x) { return x * (x + 0.2f); } */ explicit Balance( bool ramp = true, std::function<float(float)> curve = [](float x) { return x * (x + 0.2f); }) // 曲線函數 : mRamp(ramp) , mCurve(normalize(std::move(curve))) { } // mCurve作了normalize處理 // mCurve 定義 const std::function<float(float)> mCurve; // monotone volume transfer func [0, 1] -> [0, 1]
其實其函數註釋裏都寫得很清楚了,我也貼出了註釋部分,mCurve是一個function, 並作了歸一化處理,讓其區間和值都落在[0, 1]上,該function爲一個單調遞增的函數,目前採用的是 x * (x + 0.2f)
, 固然你也能夠採用別的函數。
normalize 是一個模板,其註釋也寫得很清楚了,可看下,
/** * \brief Normalizes f: [0, 1] -> [a, b] to g: [0, 1] -> [0, 1]. * * A helper function to normalize a float volume function. * g(0) is exactly zero, but g(1) may not necessarily be 1 since we * use reciprocal multiplication instead of division to scale. * * \param f a function from [0, 1] -> [a, b] * \return g a function from [0, 1] -> [0, 1] as a linear function of f. */ template<typename T> static std::function<T(T)> normalize(std::function<T(T)> f) { const T f0 = f(0); const T r = T(1) / (f(1) - f0); // reciprocal multiplication if (f0 != T(0) || // must be exactly 0 at 0, since we promise g(0) == 0 fabs(r - T(1)) > std::numeric_limits<T>::epsilon() * 3) { // some fudge allowed on r. // 咱們採用的函數x * (x + 0.2f),fabs(r - T(1)) > .. 爲true, 會進到這裏來 return [f, f0, r](T x) { return r * (f(x) - f0); }; } // no translation required. return f; }
咱們採用的函數知足 fabs(r - T(1)) > std::numeric_limits<T>::epsilon() * 3
條件,因此也會作歸一化處理,即採用 r * (f(x) - f0)
, 結合起來,mCurve 曲線數學描述爲
$$ f(x) = x^2 + 0.2 \times x; \\ mCurve(x) = {\frac{1.0}{f(1)-f(0)}} \times {(f(x)-f(0))} = {\frac{1.0}{1.2} \times f(x)} $$
也即
$$ \mathbf{mCurve(x) = {\frac{(x^2 + 0.2x)}{1.2}}, x\in[0.0, 1.0], y\in[0.0, 1.0]} $$
1.2 爲歸一化係數。
$ mCurve(1.f - |balance|), balance\in[-1.0, 1.0] $ 可用以下圖表示:
<center>圖2. Balance曲線圖</center>
該圖若是顯示有問題,也用在線matlab查看,打開下面的網址,而後輸入下面的內容
https://octave-online.net/ x = [-1 : 0.1: 1]; z = 1 - abs(x) y = (z.^2 + 0.2 * z)/1.2; plot(x, y, 'r') xlabel('balance') ylabel('Y') title('Balance Curve')
至此,其調節左右平衡的原理算是搞清楚了。
除前面提到的用命令行 adb shell settings put system master_balance
改變其值外,咱們還能夠dump看其是否生效
$ adb shell dumpsys media.audio_flinger // mixer類型的某個線程 Output thread 0x7c19757740, name AudioOut_D, tid 1718, type 0 (MIXER): ... Thread throttle time (msecs): 6646 AudioMixer tracks: Master mono: off // balance值 Master balance: 0.500000 (balance 0.5 channelCount 2 volumes: 0.291667 1) // Offload (direct)類型的某個線程 Output thread 0x7c184b3000, name AudioOut_20D, tid 10903, type 4 (OFFLOAD): ... Suspended frames: 0 Hal stream dump: // balance值 Master balance: 0.500000 Left: 0.291667 Right: 1.000000