乐趣区

关于android:Android-11-balance-流程及原理

在高通的一个文档上提到了安卓 10 Audio 引入了一个 balance 性能(因为窃密起因, 具体文档名应该也不让贴吧),文档内容也简略,就提了下设置界面和 dumpsys 查看值,

那就钻研下这个货色安卓咋实现的及其原理吧。

<!– more –>

[Platform:Android 11]
http://aosp.opersys.com/xref/…

Balance 其实是用于设置左右均衡的,当初手机上立体声喇叭也多起来了,说直观点成果就是设置左右喇叭音量大小的。

另外说下音量均衡这个性能在车机上也有需要,联合前后淡化 (Fade),可实现声场的成果。
为此谷歌引入了 AudioControl,通过 setBalanceTowardRight() setFadeTowardFront() 这两个接口来设置左右均衡,前后淡化达到设置声场成果。
相干的材料可看下 https://source.android.google…
不过呢, 这两个接口在 HAL 层时须要芯片厂商实现, 也就是说芯片厂商可能实现了也可能没实现, 比方高通 8155 HAL 层就没实现该性能。

1. 设置界面


<center> 图 1. 左右均衡设置界面 </center>

在下面的界面中,把条拖到最右边,则声音齐全调到左侧;同样,把条拖到最左边,则声音齐全调到右侧。
下面拖动条的值目前为 [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 曲线数学形容为

$$
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')

至此,其调节左右均衡的原理算是搞清楚了。

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 $。
退出移动版