Android 11 balance 流程及原理

在高通的一個文檔上提到了安卓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

1. 設置界面

左右平衡設置界面
<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

那是誰在接收這個值呢?

2. setMasterBalance()

經過對 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過程就得找找個哪兒在使用該值了。

3. 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] $ 可用以下圖表示:

balance曲線
<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')

至此,其調節左右平衡的原理算是搞清楚了。

4. 調試

除前面提到的用命令行 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

5. 總結

  1. UI設置界面只是個數據存儲的過程,其值進行轉換到[-1.0, 1.0]並經過數據庫存儲,java層audio服務監聽到該值變化後經過 setMasterBalance() 接口最終存儲到AudioFlinger非複製方式的播放線程中;
  2. 對於不含fast mixer的播放線程,會在threadLoop()裏進行平衡的處理;
  3. 平衡處理的原理也很簡單,balance往哪邊,哪邊聲道不變,對另外一邊聲道乘以個係數(降音, mCurve(1-|balance|)),對非ramp方式該係數生成是個二次方的單調函數並歸一化到[0,1], 目前爲 $ mCurve(x) = x*(x+0.2)/1.2 $ 。
相關文章
相關標籤/搜索