• 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:
       /**
         * rief 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 是一个模板,其注释也写得很清楚了,可看下,

        /**
         * rief 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]
         * 
    eturn 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|), balancein[-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 。
  • 相关阅读:
    leetcode Simplify Path
    leetcode Evaluate Reverse Polish Notation
    leetcode Swap Nodes in Pairs
    leetcode MinStack
    leetcode length of the last word
    empty能否代替isset?
    thinkphp框架的路径
    PHP 反射类的简单使用!
    在windows下配置redis扩展
    phpmyadmin的windows下和linux下的安装。
  • 原文地址:https://www.cnblogs.com/blogs-of-lxl/p/14603347.html
Copyright © 2020-2023  润新知