Android 8.1 DisplayPowerController(五) 自动调节亮度(2)——算法

作者:FightFightFight 
来源:CSDN 
原文:https://blog.csdn.net/FightFightFight/article/details/83718273 

在上一篇文章中,对自动背光的流程做了总结,在本篇中,将对自动背光涉及到的一些算法进行分析总结。

1.采集光强缓冲区
AmbientLightRingBuffer类是一个用于存储采集到的光照强度和对应时间点的数据结构。在自动背光控制器中,实例化了两个AmbientLightRingBuffer对象:

//包含所有光照样例的AmbientLightRingBuffer对象
mAmbientLightRingBuffer =
    new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizon);
//只包含在初始时间范围周期的光照样例的AmbientLightRingBuffer对象
mInitialHorizonAmbientLightRingBuffer =
    new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizon);

mAmbientLightRingBuffer是包含所有光照样例的AmbientLightRingBuffer对象,mInitialHorizonAmbientLightRingBuffer只包含在初始采光时间范围周期的光照样例的AmbientLightRingBuffer对象,这个初始采光时间范围周期是指当LSensor启用开始计时,到一个采光时间周期,采光时间周期mAmbientLightHorizon则在配置文件中读取:

<!--一个采光周期为10s-->
<integer name="config_autoBrightnessAmbientLightHorizon">10000</integer>

下面我们从它的构造方法开始,来分析缓冲区的实现原理和其中包括的算法。

1.1.构造方法
AmbientLightRingBuffer的构造方法如下:

    private static final class AmbientLightRingBuffer {
        private static final float BUFFER_SLACK = 1.5f;
        private float[] mRingLux;
        private long[] mRingTime;
        private int mCapacity;

    //缓冲区中第一个数据位置
        private int mStart;
        //缓冲区中下一个槽口位置
        private int mEnd;
        //缓冲区中光照样例的数量
        private int mCount;

        public AmbientLightRingBuffer(long lightSensorRate, int ambientLightHorizon) {
        //缓冲区容量
            mCapacity = (int) Math.ceil(ambientLightHorizon * BUFFER_SLACK / lightSensorRate);
            //存储Lux值的数组
            mRingLux = new float[mCapacity];
        //存储时间的数组
            mRingTime = new long[mCapacity];
        }

在构造方法中,初始化了两个数组分别用来存储时间和Lux值,而这两个数组的长度,是根据采集光照样例的时间范围周期 * BUFFER_SLACK/LSensor事件率得到的。其实也可以这样理解:总时间 / 每次上报数据的时间 = 时间周期内上报事件的个数 = 缓冲区大小。

1.2.向缓冲区中push光照样例
将上报的LSensor数据记录到缓冲区时,使用push()方法,该方法如下:

        public void push(long time, float lux) {
            //标记要存储的位置
            int next = mEnd;
            if (mCount == mCapacity) {
                //如果缓冲区已满,则将进行扩容
                int newSize = mCapacity * 2;
                //初始化两个新的数组,大小为原来数组大小的2倍
                float[] newRingLux = new float[newSize];
                long[] newRingTime = new long[newSize];
                //数组长度减去第一个元素位置=原数组的长度
                int length = mCapacity - mStart;
                //将旧数组中内容拷贝到新数组中
                //将mRingLux数组从mStart位起,拷贝到newRingLux中,放在0位置起,拷贝length个数据
                System.arraycopy(mRingLux, mStart, newRingLux, 0, length);
                System.arraycopy(mRingTime, mStart, newRingTime, 0, length);

                //如果原数组中第一个数据的索引不为0
                if (mStart != 0) {
                    //将mRingLux数组从0位起,拷贝到newRingLux中,放在length位置起,拷贝mStart个数据
                    //即如果mStart不为0,将mStart前的数据放在了最后
                    System.arraycopy(mRingLux, 0, newRingLux, length, mStart);
                    System.arraycopy(mRingTime, 0, newRingTime, length, mStart);
                }
                mRingLux = newRingLux;
                mRingTime = newRingTime;

                next = mCapacity;
                mCapacity = newSize;
                mStart = 0;
            }
            //记录时间和Lux
            mRingTime[next] = time;
            mRingLux[next] = lux;
            mEnd = next + 1;//下一个槽口
            //如果到达数组尾端,则将mEnd置为0,在下一次进入后,由于mCount == mCapacity,因此又会将mEnd置为next+1
            if (mEnd == mCapacity) {
                mEnd = 0;
            }
            //记录数+1
            mCount++;
        }


在这个方法中,会将LSensor上报的值和时间点记录到缓冲区中,此处有两个地方的计算方式如下:

1.如果当前缓冲区已满,则对其进行扩容,新的容量是原来容量的2倍,实际上是重新初始化了两个数组,并将旧数组中的元素从保存的第一个元素起,拷贝到了新数组中,从0索引开始存放数据。
2.如果mStart不为0,说明缓冲区数组中存储元素的起始位置不为0(被prune()方法操作过),所以0-mStart之间的记录已被标记为删除,则在拷贝时,将旧数组中0-mStart之间的元素拷贝到新数组中,从length索引开始存放。
根据以上两个计算方式,从而形成了类似环的环形缓冲区。

1.3.从缓冲区中移除某个时间前的数据
每当LSensor收到上报数据后,会通过push()方法记录到缓冲区,而对据此次上报时间一个采光时间周期前的数据,则进行删除,该方法如下:


        public void prune(long horizon) {
            //如果当前缓冲区中无数据,直接返回
            if (mCount == 0) {
                return;
            }

            while (mCount > 1) {
                //表示第二个有效元素位置
                int next = mStart + 1;
                //如果第二个元素置位值大于等于容量值,说明整个缓冲区中只有一个元素,索引为mStart.
                if (next >= mCapacity) {
                    next -= mCapacity;
                }
                //如果第二个有效元素的时间点大于传入的时间,则停止
                if (mRingTime[next] > horizon) {
                    break;
                }
                //重置第一个有效元素索引
                mStart = next;
                //数量值-1
                mCount -= 1;
            }

            //如果第一个有效元素时间小于传入的时间,则重置第一个有效元素的时间值
            if (mRingTime[mStart] < horizon) {
                mRingTime[mStart] = horizon;
            }
        }

下图简单表示了push()和prune()方法调用时缓冲区状态示例:


1.4.缓冲区获取索引值
缓冲区提供了getTime(int index)和getLux(int index)方法,根据参数index得到缓冲区中对应的数据,然而,由于缓冲区中数据的存储是根据缓冲区内部的变量mStart标记存储的起始位置,因此,需要将index进行转换,使用offsetOf(int index)转换,从而得到正确的位置:

        private int offsetOf(int index) {
            if (index >= mCount || index < 0) {
                throw new ArrayIndexOutOfBoundsException(index);
            }
            //mStart表示第一个元素的位置
            index += mStart;
            //如果index >= mCapacity,直接index = index -mCapacity
            if (index >= mCapacity) {
                index -= mCapacity;
            }
            return index;
        }
    }

1.5.缓冲区中其他方法
其他方法比较简单,如下:

        //获取Lux值
        public float getLux(int index) {
            return mRingLux[offsetOf(index)];
        }

        //获取时间值
        public long getTime(int index) {
            return mRingTime[offsetOf(index)];
        }
    //清空缓冲区
        public void clear() {
            mStart = 0;
            mEnd = 0;
            mCount = 0;
        }


2.计算Lux值
在上一篇文章中分析自动背光流程时,对最终Lux值的计算直接略过了,只提到它是通过calculateAmbientLux()方法进行计算后,将结果通过setAmbientLux()方法设置给了代表当前Lux值的全局变量mAmbientLux。因此我们这里对这两个方法进行分析。

2.1.calculateAmbientLux()方法计算Lux值
该方法如下:

    private float calculateAmbientLux(long now, long horizon) {
        final int N = mAmbientLightRingBuffer.size();
        if (N == 0) {
            Slog.e(TAG, "calculateAmbientLux: No ambient light readings available");
            return -1;
        }

        // Find the first measurement that is just outside of the horizon.
        //表示计算lux值时的左边界([endIndex,N-1])
        int endIndex = 0;
        //当前时间-时长范围,即前horizon秒的时间点
        final long horizonStartTime = now - horizon;
        //计算左边界,其为大于指定时间的第一个元素的索引值
        //为何从i+1开始呢?如从i开始,必然有第一个元素 <=horizonStartTime,从而获得的endIndex大了1.
        for (int i = 0; i < N-1; i++) {
            if (mAmbientLightRingBuffer.getTime(i + 1) <= horizonStartTime) {
                endIndex++;
            } else {
                break;
            }
        }
        float sum = 0;
        float totalWeight = 0;
        //用于计算权重的常亮
        long endTime = AMBIENT_LIGHT_PREDICTION_TIME_MILLIS;
        for (int i = N - 1; i >= endIndex; i--) {
            //得到缓冲区中记录的LSensor事件时间
            long eventTime = mAmbientLightRingBuffer.getTime(i);
            if (i == endIndex && eventTime < horizonStartTime) {
                // If we're at the final value, make sure we only consider the part of the sample
                // within our desired horizon.
                eventTime = horizonStartTime;
            }
            //缓存区中记录的时间-当前时间,肯定小于等于零
            final long startTime = eventTime - now;
            //计算权重
            float weight = calculateWeight(startTime, endTime);
            //得到缓冲区中记录的Lux值
            float lux = mAmbientLightRingBuffer.getLux(i);
            //累计每次得到的权重值
            totalWeight += weight;
            //累计每次得到的(Lux值 * 权重值)
            sum += mAmbientLightRingBuffer.getLux(i) * weight;
            endTime = startTime;
        }
        //加权平均
        return sum / totalWeight;
    }


在以上方法中可以看出,最终的Lux值,是在缓冲区中,时间点在now-horizon ~ now范围中的lux值加权平均获得。如在自动背光打开的情况下,亮屏开启LSensor后,会走calculateAmbientLux(time, AMBIENT_LIGHT_SHORT_HORIZON_MILLIS)流程,AMBIENT_LIGHT_SHORT_HORIZON_MILLIS值为2000,也就是,亮屏后立即调整背光时,使用前2s内的数据进行加权平均得到Lux值,并进一步得到亮度值。

计算权重的公式如下:

    private float calculateWeight(long startDelta, long endDelta) {
        return weightIntegral(endDelta) - weightIntegral(startDelta);
    }

    // Evaluates the integral of y = x + mWeightingIntercept. This is always positive for the
    // horizon we're looking at and provides a non-linear weighting for light samples.
    private float weightIntegral(long x) {
        return x * (x * 0.5f + mWeightingIntercept);
    }

可以看到计算权重的公式为一个一元二次方式,顶点为0,开口向上,因此,可以保证是权重值一个正值。

2.1.setAmbientLux()设置Lux值
当得到Lux值后,通过setAmbientLux()将Lux值赋给全局变量:


    private void setAmbientLux(float lux) {

        //将lux赋值给表示当前lux的全局变量mAmbientLux
        mAmbientLux = lux;
        //环境亮度阈值,用于使屏幕变亮或变暗的Lux值
        mBrighteningLuxThreshold = mDynamicHysteresis.getBrighteningThreshold(lux);
        mDarkeningLuxThreshold = mDynamicHysteresis.getDarkeningThreshold(lux);
    }

在以上方法的最后,通过HysteresisLevel和当前Lux值获取Lux阀值,超过或小于两个阀值时,将会更新屏幕的亮度,这部分会在下面分析到。

3.Spline样条插值
当获取到Lux值后,我们来看看是如何计算要显示的亮度的。

在updateAutoBrightness()方法中,首先根据如下逻辑获得了一个value值:

//根据当前的Lux值得到样条曲线中的value
        float value = mScreenAutoBrightnessSpline.interpolate(mAmbientLux);

我们要暂时停到这里,来看看mScreenAutoBrightnessSpline这个对象了。

mScreenAutoBrightnessSpline是MonotoneCubicSpline对象,表示一个单调的三次样条曲线,这里我们来看下他的创建过程。

在DisplayPowerController构造方法中:

int[] lux = resources.getIntArray(
    com.android.internal.R.array.config_autoBrightnessLevels);
int[] screenBrightness = resources.getIntArray(
    com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
Spline screenAutoBrightnessSpline = createAutoBrightnessSpline(lux, screenBrightness);

在以上逻辑中,从config.xml文件中获取了两个数组,这两个数组对于自动背光来说是非常重要,因为他们分别定义了Lux的级别和对应Lux的亮度。从配置文件中看,Lux 始终比screenBrightness少1.

需要注意的是,这些数组值需要厂商各自去根据硬件环境进行配置,在Google原生代码中,这些数组为空。

再来看看createAutoBrightnessSpline()方法:

    private static Spline createAutoBrightnessSpline(int[] lux, int[] brightness) {
        try {
            final int n = brightness.length;
            //初始化两个数组,分别存储Lux值和value/255的值
            float[] x = new float[n];
            float[] y = new float[n];
            //将y[0]设置为第0个亮度值
            y[0] = normalizeAbsoluteBrightness(brightness[0]);
            //将x[i]设置为lux[i-1]元素,y[i]设置为(value/brightness[i])
         //由于lux.length = brightness.length -1.故x[0]没有存储元素
            for (int i = 1; i < n; i++) {
                x[i] = lux[i - 1];
                y[i] = normalizeAbsoluteBrightness(brightness[i]);
            }

            Spline spline = Spline.createSpline(x, y);
            return spline;
        } catch (IllegalArgumentException ex) {
            Slog.e(TAG, "Could not create auto-brightness spline.", ex);
            return null;
        }
    }

在这个方法中,将从配置文件中得到的Lux Level数组和brightness level数组作为参数,并将两数组中的元素复制到了新的数组中,并利用该数组创建Spline对象,继续:

    public static Spline createSpline(float[] x, float[] y) {
        if (!isStrictlyIncreasing(x)) {
            throw new IllegalArgumentException("The control points must all have strictly "
                    + "increasing X values.");
        }

        //是否单调递增
        if (isMonotonic(y)) {
            return createMonotoneCubicSpline(x, y);
        } else {
            return createLinearSpline(x, y);
        }
    }

这个方法中,判断数组x中的值、即Lux值是否严格单调递增,然后进一步执行。根据配置文件,Lux值递增,所以调用下一个方法createMonotoneCubicSpline(),创建得到MonotoneCubicSpline对象。

    public static Spline createMonotoneCubicSpline(float[] x, float[] y) {
        return new MonotoneCubicSpline(x, y);
    }


紧接着看MonotoneCubicSpline的构造方法:


    public static class MonotoneCubicSpline extends Spline {
        private float[] mX;
        private float[] mY;
        private float[] mM;

        public MonotoneCubicSpline(float[] x, float[] y) {
            if (x == null || y == null || x.length != y.length || x.length < 2) {
                throw new IllegalArgumentException("There must be at least two control "
                        + "points and the arrays must be of equal length.");
            }

            final int n = x.length;
            //初始化数组,d数组用来存储相邻两点之间的斜率,m数组存储两个相邻d数组中的平均值
            float[] d = new float[n - 1]; // could optimize this out
            float[] m = new float[n];

            // Compute slopes of secant lines between successive points.
            //得到每个相邻Lux Level值之间曲线的斜率
            for (int i = 0; i < n - 1; i++) {
                float h = x[i + 1] - x[i];
                if (h <= 0f) {
                    throw new IllegalArgumentException("The control points must all "
                            + "have strictly increasing X values.");
                }
                d[i] = (y[i + 1] - y[i]) / h;
            }

            // Initialize the tangents as the average of the secants.
            //相邻斜率平均值
            m[0] = d[0];
            for (int i = 1; i < n - 1; i++) {
                m[i] = (d[i - 1] + d[i]) * 0.5f;
            }
            m[n - 1] = d[n - 2];

            // Update the tangents to preserve monotonicity.
            for (int i = 0; i < n - 1; i++) {
                if (d[i] == 0f) { // successive Y values are equal
                    m[i] = 0f;
                    m[i + 1] = 0f;
                } else {
                    float a = m[i] / d[i];
                    float b = m[i + 1] / d[i];
                    if (a < 0f || b < 0f) {
                        throw new IllegalArgumentException("The control points must have "
                                + "monotonic Y values.");
                    }
                    //a和b平方和的二次方根
                    float h = (float) Math.hypot(a, b);
                    if (h > 3f) {
                        float t = 3f / h;
                        m[i] *= t;
                        m[i + 1] *= t;
                    }
                }
            }

            mX = x;//lux level值
            mY = y;//bright level/255的值
            mM = m;//平均斜率
        }

在以上方法中,涉及的算法目前还不是非常清晰,不过还是可以确定,其成员数组mX、mY、mM分别表示存储Lux Level值、(brightness Level/255)值和由以上两值组成的曲线相邻两点之间的斜率。

4.最终的亮度值
现在,让我们回到亮度的计算中,当自动背光控制器中得到当前Lux值时,通过mScreenAutoBrightnessSpline.interpolate(mAmbientLux)得到一个value值,该方法如下:

        @Override
        public float interpolate(float x) {
            final int n = mX.length;
            if (Float.isNaN(x)) {
                return x;
            }
            //当传入Lux值< mX中第一个元素时,使用mY[0]
            if (x <= mX[0]) {
                return mY[0];
            }
            //当传入Lux值 > mX中最后一个元素时,使用mY的最后一个元素
            if (x >= mX[n - 1]) {
                return mY[n - 1];
            }

            int i = 0;
            //遍历Lux值,若恰好相等,则直接return
            while (x >= mX[i + 1]) {
                i += 1;
                if (x == mX[i]) {
                    return mY[i];
                }
            }

            // Perform cubic Hermite spline interpolation.
            //进行三次Hermite样条插值计算
            float h = mX[i + 1] - mX[i];
            float t = (x - mX[i]) / h;
            return (mY[i] * (1 + 2 * t) + h * mM[i] * t) * (1 - t) * (1 - t)
                    + (mY[i + 1] * (3 - 2 * t) + h * mM[i + 1] * (t - 1)) * t * t;
        }

在以上方法中,复杂的是最后的三次Hermite样条插值计算,这个我目前也不清楚,先不深入分析了,总之从这里就会得到一个值,用value表示。
得到value值后,它并不是最终的亮度值,因为还需要进行一个操作才能得到最终的亮度值。

我们在上一篇文章中说过,当打开自动背光后,拖动亮度条,实际上不是调节的亮度值,而是自动背光调节值mScreenAutoBrightnessAdjustment,正是通过这个值,和得到的value进行一个简单计算,得到了最终的亮度值:

    private void updateAutoBrightness(boolean sendUpdate) {
        if (USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT
                && mScreenAutoBrightnessAdjustment != 0.0f) {
            //得到adjGamma值
            final float adjGamma = MathUtils.pow(mScreenAutoBrightnessAdjustmentMaxGamma,
                    Math.min(1.0f, Math.max(-1.0f, -mScreenAutoBrightnessAdjustment)));
            gamma *= adjGamma;
        }
        if (gamma != 1.0f) {//表示gamma发生变化
            final float in = value;
            //再次计算value = value的gamma次方
            value = MathUtils.pow(value, gamma);
        }

        //新的亮度值=value * 255
        int newScreenAutoBrightness =
                clampScreenBrightness(Math.round(value * PowerManager.BRIGHTNESS_ON));

}

对于亮度计算的算法就分析到这里,这里再对上面提到的两个数组再次进行下说明。
config.xml中的config_autoBrightnessLevels和config_autoBrightnessLcdBacklightValues数组对于自动背光来说非常重要,若不配置它,自动背光功能将失效。它们的定义如下:


   <!-- Array of light sensor LUX values to define our levels for auto backlight brightness support.
        The N entries of this array define N  1 zones as follows:

        Zone 0:        0 <= LUX < array[0]
        Zone 1:        array[0] <= LUX < array[1]
        ... 
        Zone N:        array[N - 1] <= LUX < array[N]
        Zone N + 1     array[N] <= LUX < infinity

        Must be overridden in platform specific overlays --> 
   <integer-array name="config_autoBrightnessLevels">
       <item>1</item>
       <item>40</item>
       <item>100</item>
       <item>325</item>
       <item>600</item>
       <item>1250</item>
       <item>2200</item>
       <item>4000</item>
       <item>10000</item>
   </integer-array>

   <!-- Array of output values for LCD backlight corresponding to the LUX values
        in the config_autoBrightnessLevels array.  This array should have size one greater
        than the size of the config_autoBrightnessLevels array.
        This must be overridden in platform specific overlays --> 
   <integer-array name="config_autoBrightnessLcdBacklightValues">
       <item>11</item>   <!-- 0-1 --> 
       <item>22</item>   <!-- 1-40 --> 
       <item>47</item>   <!-- 40-100 --> 
       <item>61</item>   <!-- 100-325 --> 
       <item>84</item>   <!-- 325-600 --> 
       <item>107</item>  <!-- 600-1250 --> 
       <item>154</item>  <!-- 1250-2200 --> 
       <item>212</item>  <!-- 2200-4000 --> 
       <item>245</item>  <!-- 4000-10000 --> 
       <item>255</item>  <!-- 10000+ --> 
   </integer-array>


结合以上配置值和流程我们可以知道,假设自动背光不使用调节值mScreenAutoBrightnessAdjustment,则:

1.当mAmibentLux <= config_autoBrightnessLevels[0]时,背光值直接采用config_autoBrightnessLcdBacklightValues[0];
2.当mAmibentLux >= config_autoBrightnessLevels[size-1]时,背光值直接采用config_autoBrightnessLcdBacklightValues[size-1];
3.当config_autoBrightnessLevels[0] < mAmibentLux <= config_autoBrightnessLevels[size-1]时,背光参考值通过三次样条插值计算获得。
4.HysteresisLevel计算Lux阀值
HysteresisLevel是一个计算Lux阀值的帮助类,当Lux值发生变化后,到达何值时更新亮度,就是通过这个帮助类计算的,而在这个类内部计算阀值时,又使用到了配置文件中的三个配置数组。下面就来看看它的实现原理。

在DisplayPowerController中的构造方法中,获得了HysteresisLevel的实例,并作为参数传递给了自动背光控制器,其实例化代码如下:

int[] brightLevels = resources.getIntArray(
        com.android.internal.R.array.config_dynamicHysteresisBrightLevels);
int[] darkLevels = resources.getIntArray(
        com.android.internal.R.array.config_dynamicHysteresisDarkLevels);
int[] luxLevels = resources.getIntArray(
        com.android.internal.R.array.config_dynamicHysteresisLuxLevels);
HysteresisLevels dynamicHysteresis = new HysteresisLevels(
        brightLevels, darkLevels, luxLevels);

这三个数组中,前两个数组中的元素分别用于计算明亮Lux阀值、昏暗Lux阀值的比例值,最后一个数组中的元素则用来和当前Lux作比较,得到一个索引值,以确定前两个数组中的元素。这三个数组定义时,前两数组的长度必须比后一个数组长度大1。
进入它的构造方法:


    public HysteresisLevels(int[] brightLevels, int[] darkLevels, int[] luxLevels) {
        if (brightLevels.length != darkLevels.length || darkLevels.length != luxLevels.length + 1) {
            throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
        }
        mBrightLevels = setArrayFormat(brightLevels, 1000.0f);
        mDarkLevels = setArrayFormat(darkLevels, 1000.0f);
        mLuxLevels = setArrayFormat(luxLevels, 1.0f);
    }

在构造方法中,将三个数组中的各个元素通过setArrayFormat()方法处理后,分别赋给了三个全局变量,setArrayFormat():

    private float[] setArrayFormat(int[] configArray, float divideFactor) {
        float[] levelArray = new float[configArray.length];
        for (int index = 0; levelArray.length > index; ++index) {
            levelArray[index] = (float)configArray[index] / divideFactor;
        }
        return levelArray;
    }

从以上逻辑中也可以看出,该方法只是进行了(float)configArray[index] / divideFactor操作。

通过对HysteresisLevel构造方法的分析,对它有了大致的了解,现在来看看它是如何计算Lux阀值的。

在自动背光控制器中,获取Lux阀值方式如下:

    private void setAmbientLux(float lux) {
        //将lux赋值给表示当前lux的全局变量mAmbientLux
        mAmbientLux = lux;
        //环境亮度阈值,用于使屏幕变亮或变暗的Lux值
        mBrighteningLuxThreshold = mDynamicHysteresis.getBrighteningThreshold(lux);
        mDarkeningLuxThreshold = mDynamicHysteresis.getDarkeningThreshold(lux);
    }


那么就来看看以上两个方法:


    /**
     * Return the brightening hysteresis threshold for the given lux level.
     */
    public float getBrighteningThreshold(float lux) {
        //将lux和mLuxLevels中的元素进行比较,得到一个索引值index,然后返回mBrightLevels[index]
        float brightConstant = getReferenceLevel(lux, mBrightLevels);
        //阀值的计算公式
        float brightThreshold = lux * (1.0f + brightConstant);
        return brightThreshold;
    }

    /**
     * Return the darkening hysteresis threshold for the given lux level.
     */
    public float getDarkeningThreshold(float lux) {
        //将lux和mLuxLevels中的元素进行比较,得到一个索引值index,然后返回mDarkLevels[index]
        float darkConstant = getReferenceLevel(lux, mDarkLevels);
        float darkThreshold = lux * (1.0f - darkConstant);
        return darkThreshold;
    }

在以上方法中,可以看出Lux阀值的计算公式为分别如下:

mBrighteningLuxThreshold = lux * (1.0f + brightConstant);
mDarkeningLuxThreshold = lux * (1.0f - darkConstant);

getReferenceLevel()方法返回了一个亮度比例值,该方法如下:

    private float getReferenceLevel(float lux, float[] referenceLevels) {
        int index = 0;
        //如果index小于mLuxLevels的长度且当前Lux值大于等于mLuxLevels[index],则index加1.
        //当index等于mLuxLevels.length,说明已经lux值大于等于mLuxLevels[length-1]最后一个元素,此时break
        //当lux值小于mLuxLevels[index]时,break。
        while (mLuxLevels.length > index && lux >= mLuxLevels[index]) {
            ++index;
        }
        return referenceLevels[index];
    }

在以上方法中,根据当前Lux值和mLuxLevels数组中的元素进行比较,从而确定一个index值,利用这个index值,得到mBrightLevels[index]或mDarkLevels[index],从而套用公式计算阀值。而在这个方法中可以看出,当前Lux值、mLuxLevels、index的关系如下:

条件    得到的index值
lux < mLuxLevels[0]    0
mLuxLevels[n-1] <= lux < mLuxLevels[n]( 0 < n < MAX)    n
…    …
lux > mLuxLevels[MAX]    MAX+1
一般情况下,对于以上提到的三个数组,并不需要厂商再进行配置,都是使用原生的数据,Google原生提供数据如下:

<integer-array name="config_dynamicHysteresisBrightLevels">
    <item>100</item>
</integer-array>
<integer-array name="config_dynamicHysteresisDarkLevels">
    <item>200</item>
</integer-array>
<integer-array name="config_dynamicHysteresisLuxLevels">
</integer-array>

至此,关于自动背光涉及到的一些算法及原理,算是分析了一遍了。虽有些如样条插值等算法未领悟到其目的,不过还是对这个功能有一个较清晰的认识了。

整个自动背光流程时序图如下:

此外,android P中自动背光机制相比Android O有较大的变化,在之后会对Android P进行总结。

总结
以下是自动背光调节中的一些常用配置值:

1.改变环境光后,自动背光调节时间:
<!--进入明亮环境时,4s后调节-->
<integer name="config_autoBrightnessBrighteningLightDebounce">4000</integer>
<!--进入昏暗环境时,8s后调节-->
<integer name="config_autoBrightnessDarkeningLightDebounce">8000</integer>

2.打开自动背光调节后,取消拖动亮度条调节功能:
//frameworks/base/services/core/java/com/android/server/display/AutomaticBrightnessController.java
private static final boolean USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT = true;//false将取消

3.开启自动背光后,是否在亮屏时立即调整背光:
<bool name="config_autoBrightnessResetAmbientLuxAfterWarmUp">true</bool>

4.是否配置自动背光功能:
<bool name="config_automatic_brightness_available">true</bool>

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值