Android 11 音頻平衡(balance)流程及原理


轉載:https://segmentfault.com/a/1190000039667283?utm_source=tag-newest  

  Balance 其實是用於設置左右平衡的,現在手機上立體聲喇叭也多起來了,說直觀點效果就是設置左右喇叭音量大小的。另外說下音量平衡這個功能在車機上也有需求,結合前后淡化(Fade),可實現聲場的效果。為此谷歌引入了AudioControl,通過 setBalanceTowardRight() setFadeTowardFront() 這兩個接口來設置左右平衡,前后淡化達到設置聲場效果。相關的資料可看下 https://source.android.google...不過這兩個接口在HAL層時需要芯片廠商實現。

1. 設置界面

                                                  圖1. 左右平衡設置界面

  在上面的界面中,把條拖到最左邊,則聲音完全調到左側;同樣,把條拖到最右邊,則聲音完全調到右側。上面拖動條的值目前為[0, 200],之后會映射到[-1.0f, 1.0f]存到數據庫,從代碼上看還做了點貼心的處理, 即在中央 +/- 6 時設為中間的值。

拖動條關鍵代碼:

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 曲線數學描述為:

 

 

 也即:

 

 

 

1.2 為歸一化系數。

mCurve(1.f - |balance|), balance\in[-1.0, 1.0]mCurve(1.fbalance),balance[1.0,1.0] 可用如下圖表示:

                              圖2. Balance曲線圖

該圖如果顯示有問題,也用在線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.2mCurve(x)=x(x+0.2)/1.2 。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM