Camera:Exposure / AE mode

    最近被Flash,Exposure-AE,Focus-AF,LongExposure这几个模式搞得焦头烂额, 很多小伙伴在使用手机对准物体拍照时,点击手机界面,会发现点哪相机就会对哪个点有个对焦效果(Focus-AF);如果闪光灯开启的话,还会有一个打闪的效果(Flash);拖动太阳还会有调整亮度的效果(Exposure-AE);这些feature对使用者来说是使用最频繁的,对开发者来讲也是最难处理,联系最多最头大的几个模块了。今天,先把Exposure/AE拎出来在应用层角度讲一下它的处理。

1.Exposure滑动事件以及UI调整处理

以下代码为对点击以及滑动事件改变曝光亮度的处理

@Override
    public boolean onDown(MotionEvent event) {
        //按压屏幕的点击事件
        return false;
    }

    @Override
    public boolean onUp(MotionEvent event) {
        //检测到手离开屏幕
        if (!isEnvironmentReady()) {
            return false;
        }
        if (!mExposureViewController.needUpdateExposureView()) {
            return false;
        }
        mExposureViewController.onTrackingTouch(false);
        return false;
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        return false;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        //手指在屏幕上滑动事件的处理
        if (!isEnvironmentReady()) {
            return false;
        }
        if (!mExposureViewController.needUpdateExposureView()) {
            return false;
        }
        float deltaY = e2.getY() - e1.getY();
        float deltaX = e2.getX() - e1.getX();
        //手机横屏或竖屏状态下滑动值的解析
        if (Math.abs(deltaX) > Math.abs(deltaY)) {
            if (mCompensationOrientation == 90 || mCompensationOrientation == 270) {
                //横屏状态,x轴改变量作为亮度改变值
                mExposureViewController.onVerticalScroll(e2, distanceX);
                return true;
            }
        } else {
            if (mCompensationOrientation == 0 || mCompensationOrientation == 180) {
                //竖屏状态,y轴改变量作为亮度改变值
                mExposureViewController.onVerticalScroll(e2, distanceY);
                return true;
            }
        }
        return false;
    }

下一部分代码为曝光调节杆的处理

protected void onTrackingTouch(boolean start) {
        if (mEvSeekBarChangedListener == null) {
            return;
        }
        if (start) {
            mEvSeekBarChangedListener.onStartTrackingTouch(mEvSeekbar);
        } else {
            mEvSeekBarChangedListener.onStopTrackingTouch(mEvSeekbar);
        }
    }

    protected void onVerticalScroll(MotionEvent event, float delta) {
        if (event.getPointerCount() == ONE_FINGER) {
            //开始更新调节杆
            updateEvProgressbar(delta);
        }
    }

    private void updateEvProgressbar(float delta) {
        int update = extractDeltaScale(delta, mEvSeekbar);
        if (mLastProgress == update) {
            return;
        } else {
            mLastProgress = update;
        }
        mEvSeekbar.setProgressDrawable(new ColorDrawable(Color.WHITE));
        mEvSeekbar.setProgress(update);
    }

    //该函数为将手指在屏幕滑动的坐标差转换为滑动杆的改变幅度
    private int extractDeltaScale(float deltaY, SeekBar seekbar) {
        int y = (int) deltaY;
        float scale;
        float progress = seekbar.getProgress();
        final int max = seekbar.getMax();
        if (mOrientation == 0 || mOrientation == 90) {
            scale = (float) (y) / (float) sAvailableSpace;
            progress += scale * max;
        }

        if (mOrientation == 180 || mOrientation == 270) {
            scale = (float) (-y) / (float) sAvailableSpace;
            progress += scale * max;
        }

        if (progress > max) {
            progress = max;
        } else if (progress < 0) {
            progress = 0;
        }
        return (int) progress;
    }

    //该函数为改变一下曝光调节杆的转态,将区分调节中和调节完的不同状态
    private void onEvChanged(boolean start) {
        LogHelper.d(TAG, "[onEvChanged] " + start);
        mEvChangeStartNotified = start;
        if (start) {
            mListener.onTrackingTouchStatusChanged(true);
            mEvSeekbar.getProgressDrawable().setAlpha(1);
        } else {
            mListener.onTrackingTouchStatusChanged(false);
            mEvSeekbar.getProgressDrawable().setAlpha(0);
        }
    }

    private VerticalSeekBar.OnSeekBarChangeListener mEvSeekBarChangedListener =
            new VerticalSeekBar.OnSeekBarChangeListener() {
                //该函数是当选中调整杆滑动时触发的更新曝光调节杆的方法,之前的更新可保证在屏幕任意区域滑动改变曝光调节杆
                @Override
                public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
                    if (mListener != null) {
                        if (i < 0 || i > seekBar.getMax()) {
                            return;
                        }
                        int evRange = sMaxEv - sMinEv;
                        if (evRange == 0) {
                            return;
                        }
                        float ev = (i + (seekBar.getMax() * sMinEv / evRange)) /
                                (seekBar.getMax() / evRange);
                        int currentEv = Math.round(ev);
                        if (currentEv != mLastEv) {
                            mLastEv = currentEv;
                            //更新调节之后所其他模式所能获取的曝光值
                            mListener.onExposureViewChanged(mLastEv);
                            if (!mEvChangeStartNotified && mEvSeekbar.getProgressDrawable()
                                    .getAlpha() != 0) {
                                onEvChanged(true);
                            }
                        }
                    }
                }

                @Override
                public void onStartTrackingTouch(SeekBar seekBar) {
                }

                @Override
                public void onStopTrackingTouch(SeekBar seekBar) {
                    if (mEvChangeStartNotified) {
                        onEvChanged(false);
                    }
                }
            };

}

2.AE mode调节

private void updateAeMode() {
        if (mAEMode == CameraMetadata.CONTROL_AE_MODE_ON_EXTERNAL_FLASH) {
            return;
        }
        setOriginalAeMode();
    }

    private void setOriginalAeMode() {
        String flashValue = mExposure.getCurrentFlashValue();
        if (FLASH_ON_VALUE.equalsIgnoreCase(flashValue)) {
            if (mNeedChangeFlashModeToTorch ||
                    mExposure.getCurrentModeType() == ICameraMode.ModeType.VIDEO) {
                //这个tag值仅仅会在Video模式下选择Flash On转态才会下发
                mAEMode = CameraMetadata.CONTROL_AE_MODE_ON;
                return;
            }
            //Flash为ON的情况下大多数AE mode下发的是这个tag
            mAEMode = CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH;
            // AE mode -> ON_ALWAYS_FLASH, flash mode value doesn't affect
            return;
        }
        if (FLASH_AUTO_VALUE.equalsIgnoreCase(flashValue)) {
            if (mNeedChangeFlashModeToTorch) {
                //这个转态也是仅仅会在Video模式下Flash 为Auto时下发这个tag
                mAEMode = CameraMetadata.CONTROL_AE_MODE_ON;
                return;
            }
            //Flash为Auto时大多数情况下发的AE mode为CONTROL_AE_MODE_ON_AUTO_FLASH
            // AE mode -> ON_AUTO_FLASH, flash mode value doesn't affect
            mAEMode = CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH;
            return;
        }
        // flash -> off, must need AE mode -> on
        mAEMode = CameraMetadata.CONTROL_AE_MODE_ON;
    }

3.Exposure下发到底层的所有tag

 @Override
    public void configCaptureRequest(CaptureRequest.Builder captureBuilder) {
        if (captureBuilder == null) {
            LogHelper.d(TAG, "[configCaptureRequest] captureBuilder is null");
            return;
        }
        String shutterValue = mExposure.getCurrentShutterValue();
        LogHelper.d(TAG, "[configCaptureRequest, shutterValue " + shutterValue);
        if (CameraUtil.isStillCaptureTemplate(captureBuilder)
                && shutterValue != null && !SHUTTER_AUTO_VALUE.equals(shutterValue)) {
            mAEMode = CameraMetadata.CONTROL_AE_MODE_OFF;
        } else {
            updateAeMode();
        }

        addBaselineCaptureKeysToRequest(captureBuilder);
    }

private void addBaselineCaptureKeysToRequest(CaptureRequest.Builder builder) {
        //下发之前根据录像/拍照模式下Flash是否开启的状态更新过的AE MODE
        builder.set(CaptureRequest.CONTROL_AE_MODE, mAEMode);
        int exposureCompensationIndex = -1;
        if (mIsAeLockAvailable != null && mIsAeLockAvailable) {
            //CONTROL_AE_LOCK的状态是由Focus聚焦来控制的,下节将Foucus时再提这一点
            builder.set(CaptureRequest.CONTROL_AE_LOCK, mAeLock);
        }
        if (mExposureCompensationStep != 0) {
            exposureCompensationIndex = (int) (mCurrentEv / mExposureCompensationStep);
            //这边下发的exposureCompensationIndex值即为调节曝光调节杆后新值经过处理的
            builder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, exposureCompensationIndex);
        }

        //这个下发的tag CONTROL_AE_PRECAPTURE_TRIGGER目的是标明我的下发是拍照前的下发,底层效果工程师会根据接收到的这个值,进行一轮效果的调试
        builder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
                CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE);
    }

4.根据从底层传上来的tag值设置Camera效果工程师客制化的曝光值,然后进行曝光条件下的拍照

private void checkAeStateTodoCustomizedCapture(CaptureRequest request,
                                                   TotalCaptureResult result) {
        List<CaptureResult.Key<?>> keyList = result.getKeys();
        for (CaptureResult.Key<?> key : keyList) {
            if (key.getName().equals(FLASH_KEY_CUSTOMIZED_RESULT)) {
                byte[] value = result.get(mKeyFlashCustomizedResult);
                if (value == null) {
                    return;
                }
                LogHelper.d(TAG, "[checkAeStateTodoCustomizedCapture], value[0]: " + value[0]);
                if (value[0] == mLastCustomizedValue) {
                    // do not update result every times
                    return;
                }
                if (value[0] == CAM_FLASH_CUSTOMIZED_RESULT_NON_PANEL[0]) {
                    if (!mExternelCaptureTriggered && (mAeState != null &&
                            mAeState == CaptureResult.CONTROL_AE_STATE_CONVERGED)) {
                        LogHelper.d(TAG, "[checkAeStateTodoStandardCapture] go to capture with " +
                                "mAeState : " + mAeState);
                        mExposure.capture();
                        mExternelCaptureTriggered = true;
                    }
                } else if (value[0] == CAM_FLASH_CUSTOMIZED_RESULT_PRE_FLASH[0]) {
                    mExposure.setPanel(true, PRE_FLASH_BRIGHTNESS);
                } else if (value[0] == CAM_FLASH_CUSTOMIZED_RESULT_MAIN_FLASH[0]) {
                    if (!mExternelCaptureTriggered) {
                        mExposure.setPanel(true, MAIN_FLASH_BRIGHTNESS);
                        mExposure.capture();
                        mExternelCaptureTriggered = true;
                    }
                }
                mLastCustomizedValue = value[0];
                break;
            }
        }
    }


protected void setPanel(boolean on, int brightness) {
        LogHelper.d(mTag, "[setPanel] to " + on + ",brightness = " + brightness);
        mActivity.runOnUiThread(new Runnable() {
            @Override
            public void run() {

                mIsPanelOn = on;
                IWindowManagerExt wm = IWindowManagerExt.getWindowManagerService();
                if (on) {
                    mCurrESSLEDMinStep = PictureQuality.getMinStepOfESSLED();
                    mCurrESSOLEDMinStep = PictureQuality.getMinStepOfESSOLED();
                    LogHelper.d(mTag, "[setPanel] mCurrESSLEDMinStep " + mCurrESSLEDMinStep +
                            ",mCurrESSOLEDMinStep " + mCurrESSOLEDMinStep);
                    wm.setAnimationScale(ANIMATOR_DURATION_SCALE_SELECTOR, DEFAULT_VALUE);
                    PictureQuality.setMinStepOfESSLED(LED_BRIGHTNESS);
                    PictureQuality.setMinStepOfESSOLED(LED_BRIGHTNESS);
                    mAppUi.updateBrightnessBackGround(true);
                    WindowManager.LayoutParams lp = mApp.getActivity().getWindow().getAttributes();
                    lp.screenBrightness = brightness * 1.0f / 255;
                    mApp.getActivity().getWindow().setAttributes(lp);
                } else {
                    wm.setAnimationScale(ANIMATOR_DURATION_SCALE_SELECTOR, mPrevBright);
                    PictureQuality.setMinStepOfESSLED(mCurrESSLEDMinStep);
                    PictureQuality.setMinStepOfESSOLED(mCurrESSOLEDMinStep);
                    WindowManager.LayoutParams lp = mApp.getActivity().getWindow().getAttributes();
                    lp.screenBrightness = mDefaultBrightNess * 1.0f / 255;
                    mApp.getActivity().getWindow().setAttributes(lp);
                    mAppUi.updateBrightnessBackGround(false);
                }
            }
        });
    }

5.接收到底层传上来的数据,且触发拍照之后,Exposure还需要做一些拍照前的处理。

//接收到底层传上来的值,触发拍照
protected void capture() {
        mActivity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mAppUi.triggerShutterButtonClick(EXPOSURE_SHUTTER_PRIORITY);
            }
        });
    }


@Override
    public boolean onShutterButtonClick() {
        if (!isEnvironmentReady()) {
            return false;
        }
        // no need to do Ae preTrigger when video shutter button click
        if (ICameraMode.ModeType.VIDEO.equals(mCurrentModeType)) {
            return false;
        }
        if (mLastModeState != ICameraMode.MODE_DEVICE_STATE_PREVIEWING) {
            return false;
        }

        if (mExposureListener != null && mExposureListener.checkTodoCapturAfterAeConverted()) {
            LogHelper.d(mTag, "[onShutterButtonClick] need do capture after AE converted");
            return true;
        } else {
            return false;
        }
    }


//此时需要根据底层传上来的参数以及Flash、Focus的状态进行不同的效果参数的拍照
@Override
    public boolean checkTodoCapturAfterAeConverted() {
        switch (mFlow) {
            case FLASH_FLOW_NO_FLASH:
                return false;
            case FLASH_FLOW_NORMAL:
                if (CameraUtil.hasFocuser(mCameraCharacteristics)) {
                    return false;
                }
                doNormalCapture();
                return true;
            case FLASH_FLOW_PANEL_STANDARD:
                doStandardCapture();
                return true;
            case FLASH_FLOW_PANEL_CUSTOMIZATION:
                mExternelCaptureTriggered = false;
                doCustomizedCapture();
                return true;
            default:
                return false;
        }
    }



 private void doNormalCapture() {
        if (!needAePreTriggerAndCapture()) {
            mExposure.capture();
            return;
        }
        if (mAePreTriggerAndCaptureEnabled) {
            sendAePreTriggerCaptureRequest();
        } else {
            LogHelper.w(TAG, "[doNormalCapture] sendAePreTriggerCaptureRequest is ignore because" +
                    "the last ae PreTrigger is not complete");
        }
    }


    /**
     * When the front camera supported CameraMetadata#CONTROL_AE_MODE_ON_EXTERNAL_FLASH,better
     * font-facing flash support via an app drawing a bring white screen by reducing tatal capture
     * time.
     */
    private void doStandardCapture() {
        String flashValue = mExposure.getCurrentFlashValue();
        LogHelper.d(TAG, "[doStandardCapture] with flash = " + flashValue);
        switch (flashValue) {
            case FLASH_ON_VALUE:
                captureStandardPanel();
                break;
            case FLASH_AUTO_VALUE:
                captureStandardWithFlashAuto();
                break;
            case FLASH_OFF_VALUE:
                mExposure.capture();
                break;
            default:
                LogHelper.w(TAG, "[doStandardCapture] error flash value" + flashValue);
        }
    }


    private void doCustomizedCapture() {
        String flashValue = mExposure.getCurrentFlashValue();
        LogHelper.d(TAG, "[doCustomizedCapture] with flash = " + flashValue);
        switch (flashValue) {
            case FLASH_ON_VALUE:
                sendAePreTriggerCaptureRequest();
                break;
            case FLASH_AUTO_VALUE:
                captureCustomizedWithFlashAuto();
                break;
            case FLASH_OFF_VALUE:
                mExposure.capture();
                break;
            default:
                LogHelper.w(TAG, "[doCustomizedCapture] error flash value" + flashValue);
        }
    }

    /**
     * Request preview capture stream with auto focus trigger cycle.
     */
    private void sendAePreTriggerCaptureRequest() {
        // Step 1: Request single frame CONTROL_AF_TRIGGER_START.
        //这边根据mDevice2Requester.getRepeatingTemplateType()拿到的builder即为拍照的builder,意为下发的tag将对拍照得到的帧进行处理
        CaptureRequest.Builder builder = mDevice2Requester
                .createAndConfigRequest(mDevice2Requester.getRepeatingTemplateType());
        if (builder == null) {
            LogHelper.w(TAG, "[sendAePreTriggerCaptureRequest] builder is null");
            return;
        }
        //这边可对比之前的Exposure下发Tag进行理解,要拍照,要处理,要盘它了
        builder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
                CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
        // Step 2: Call repeatingPreview to update mControlAFMode.
        Camera2CaptureSessionProxy sessionProxy = mDevice2Requester.getCurrentCaptureSession();
        LogHelper.d(TAG, "[sendAePreTriggerCaptureRequest] sessionProxy " + sessionProxy);
        try {
            if (sessionProxy != null) {
                LogHelper.d(TAG, "[sendAePreTriggerCaptureRequest] " +
                        "CONTROL_AE_PRECAPTURE_TRIGGER_START");
                mAePreTriggerAndCaptureEnabled = false;
                //上述的tag全部下发下去了
                sessionProxy.capture(builder.build(), mPreviewCallback, null);
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

6.总结

    其实Flash、Exposure、Focus应用层的处理虽相互交织,但是理解与处理起来还算得心应手,真正复杂的是底层对下发的这些Tag的处理,我随便举个例子,Flash下发的tag是FLASH_MODE_OFF,但是实际拍照时候打闪了,因为啥呢?因为AE下发的是AE_MODE_ON_ALWAYS_FLASH,其中还不包括底层根据这些参数对效果的处理,对聚焦的处理,层层叠叠,啊,头...秃...

     之后应该会在应用层把聚焦的处理盘一盘,一定会找时间好好理一下底层的AE、AF、Flash这些内容的处理,把它搞得服服帖帖,明明白白的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值