手机Camera的前后摄闪光灯虽然是同一个feature,但是从软硬件任意角度来讲,都有着很大的区别。后摄闪光灯是有专门的物理空间支持的,就是手机的手电筒物理控件,当后摄开启闪光灯拍照时候,会触发俩次打闪,第一次会给200mA左右的脉冲电流,称为预闪,为的是获取当前环境下的亮度等信息;第二次会给800mA甚至更强的脉冲电流,称为主闪。反观前摄闪光啥也没有,仅仅靠屏幕补光来实现效果,视觉效果就是一张糊上去的白色的View。下面从代码的角度来研究一下闪光灯效果的实现。
1.后摄闪光应用层控件
应用层简单点就是添加闪光灯控件,下发闪光灯不同模式下相对应的tag,拍照时唤起底层的俩次打闪并进行处理。
@Overridepublic void init(IApp app,ICameraContext cameraContext,ISettingManager.SettingController settingController) {super.init(app, cameraContext, settingController);String value = mDataStore.getValue(FLASH_KEY, FLASH_DEFAULT_VALUE, getStoreScope());setValue(value);if (mFlashViewController == null) {mFlashViewController = new FlashViewController(this, app);}mStatusMonitor.registerValueChangedListener(KEY_CSHOT, mStatusChangeListener);// [Add for CCT tool] Receive keycode and enable/disable flash @{mKeyEventListener = mFlashViewController.getKeyEventListener();mApp.registerKeyEventListener(mKeyEventListener, IApp.DEFAULT_PRIORITY);IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);mActivity.registerReceiver(mBatteryLevelReceiver, filter);// @}}@Overridepublic void addViewEntry() {//前摄闪光灯模块用其他的控件处理if (!FRONT_CAMERA_ID.equals(((CameraAppUI)mAppUi).getCurrentCameraId())) {mFlashViewController.addQuickSwitchIcon();//滤镜模式下的闪光灯互斥处理if(isFilterOn()) {mFlashViewController.showQuickSwitchIcon(false);} else {mFlashViewController.showQuickSwitchIcon(getEntryValues().size() > 1);}//低电量情况闪光灯灰置处理if (mApp.hasLowBattery()) {onFlashValueChanged(FLASH_OFF_VALUE);if (mFlashViewController.mFlashEntryView != null) {mFlashViewController.mFlashEntryView.setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY);}} else {if (mFlashViewController.mFlashEntryView != null) {mFlashViewController.mFlashEntryView.setColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY);}}} else {mFlashViewController.removeQuickSwitchIcon();mFlashViewController.showQuickSwitchIcon(false);}}private ImageView initFlashEntryView() {Activity activity = mApp.getActivity();//相机界面显示的闪光灯图标RotateImageView view = (RotateImageView) activity.getLayoutInflater().inflate(R.layout.flash_icon, null);view.setOnClickListener(mFlashEntryListener);//点击闪光灯图标显示出的隐藏选项mFlashIndicatorView = (RotateImageView) activity.getLayoutInflater().inflate(R.layout.flash_indicator, null);return view;}/*** This listener used to monitor the flash quick switch icon click item.*/private final View.OnClickListener mFlashEntryListener = new View.OnClickListener() {public void onClick(View view) {//低电量不可点击final int lowBatteryWarningLevel = CameraUtil.getLowBatteryLevel(mApp);if (mFlash.barrtyLevel <= lowBatteryWarningLevel) {OnScreenHint.makeText(mApp.getActivity(), mApp.getActivity().getString(R.string.low_battery_disable_flash)).showToast();updateFlashEntryView(FLASH_OFF_VALUE);mFlash.onFlashValueChanged(FLASH_OFF_VALUE);mFlashEntryView.setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY);return;}//支持的闪光灯模式<=1不可点击if (mFlash.getEntryValues().size() <= 1) {return;}//点击闪光灯图标的处理if (mFlash.getEntryValues().size() > FLASH_ENTRY_LIST_SWITCH_SIZE) {initializeFlashChoiceView();updateChoiceView();//显示闪光灯隐藏选项mApp.getAppUi().showQuickSwitcherOption(mOptionLayout);} else {String value = mFlash.getEntryValues().get(FLASH_ENTRY_LIST_INDEX_0);if (value.equals(mFlash.getValue())) {value = mFlash.getEntryValues().get(FLASH_ENTRY_LIST_INDEX_1);}updateFlashEntryView(value);// Flash indicator no need to show now,would be enable later// updateFlashIndicator(value);mFlash.onFlashValueChanged(value);}}};//闪光灯隐藏选项的点击事件处理,支持on/auto/off三种选项private View.OnClickListener mFlashChoiceViewListener = new View.OnClickListener() {@Overridepublic void onClick(View view) {String value = "";if (mFlashAutoIcon == view) {value = FLASH_AUTO_VALUE;} else if (mFlashOnIcon == view) {value = FLASH_ON_VALUE;} else {value = FLASH_OFF_VALUE;}mApp.getAppUi().setHideQuickSwitchOptionByUser(true);mApp.getAppUi().hideQuickSwitcherOption();updateFlashEntryView(value);// Flash indicator no need to show now,would be enable later// updateFlashIndicator(value);mFlash.onFlashValueChanged(value);}};
应用层的UI设计没有技术含量,关键在于应用层和HAL之前的tag下发管理这一块,简单中隐藏着一丝不简单,老是让你有着被干扰的不痛快的感觉。
@Overridepublic void configCaptureRequest(CaptureRequest.Builder captureBuilder) {if (captureBuilder == null) {LogHelper.d(TAG, "[configCaptureRequest] captureBuilder is null");return;}if (mIsFlashSupported || mIsPanelFlashSupported) {updateFlashMode();captureBuilder.set(CaptureRequest.FLASH_MODE, mFlashMode);LogHelper.i(TAG, "[configCaptureRequest], mFlashMode = " + mFlashMode);}}private void updateFlashMode() {if (Flash.FLASH_ON_VALUE.equalsIgnoreCase(mFlash.getValue())) {if (mNeedChangeFlashModeToTorch ||mFlash.getCurrentModeType() == ICameraMode.ModeType.VIDEO) {mFlashMode = CameraMetadata.FLASH_MODE_TORCH;return;}// AE mode -> ON_ALWAYS_FLASH, flash mode value doesn't affectmFlashMode = CameraMetadata.FLASH_MODE_OFF;return;}if (Flash.FLASH_AUTO_VALUE.equalsIgnoreCase(mFlash.getValue())) {if (mNeedChangeFlashModeToTorch) {mFlashMode = CameraMetadata.FLASH_MODE_TORCH;LogHelper.d(TAG, "[updateFlashMode] change flash mode to torch");return;}// AE mode -> ON_AUTO_FLASH, flash mode value doesn't affectmFlashMode = CameraMetadata.FLASH_MODE_OFF;return;}// flash -> off, must need AE mode -> onmFlashMode = CameraMetadata.FLASH_MODE_OFF;}
在应用层对Flash的Tag管理中需要特别注意注释里面的AE mode -> ON_ALWAYS_FLASH 以及 AE mode -> ON_AUTO_FLASH, 有一说一,我对MTK原生Camera apk 里面长按屏幕触发AE/AF锁模式,保持对焦以及聚光状态的feature理解不太深,一直没有明白为什么触发AE mode要强行僭越Flash的管控机制,一直保持Flash开启状态;真心深恶痛觉以及有点烦这个设计,你对焦你聚光你就聚呗,一定时间的闪光灯开启然后去拍照是可以理解的,不过往往是提示框消失很久了,AE mode的状态还是保持长按触发不改变,导致用户拍照时候即使闪光灯未开启也有闪光灯效果。我认为,AE mode并不是老大,是否打闪的权利完全由Flash自己主宰,所以经我手的Camera apk对于这方面Flash和AE的管控, 我都给改了。
修改文件:ExposureCaptureRequestConfigure.java
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) {mAEMode = CameraMetadata.CONTROL_AE_MODE_ON;return;}//AE mode强制Flash效果mAEMode = CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH;// AE mode -> ON_ALWAYS_FLASH, flash mode value doesn't affectreturn;}if (FLASH_AUTO_VALUE.equalsIgnoreCase(flashValue)) {if (mNeedChangeFlashModeToTorch) {mAEMode = CameraMetadata.CONTROL_AE_MODE_ON;return;}//AE mode强制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 -> onmAEMode = CameraMetadata.CONTROL_AE_MODE_ON;}
2.后摄闪光灯拍照之后的底层处理
时间关系,这一部分我就不拎出来仔细讲了,之后应该会再出一版Flash有关的内容讲一下开启闪光灯功能底层拍照流程走的代码,以及为啥AE mode就能主宰Flash Mode的底层设定;甚至可能会通过代码研究一下Camera Tunning那一块根据预闪获得的环境参数,配合一定算法咋么调整拍出来的照片的RGB参数。今天就先根据一份log看一下底层的处理。
evaluteCaptureSetting是根据应用层下发的CaptureRequest得到的拍照要处理的feature;strategyMultiFramePlugin是拍照处理哥哥响应的多帧feature,很多feature并不是简单的拍一张图片,涉及到多帧图像的处理;updateCaptureDummyFrames这边就是拍照对多帧图片的一个处理,post了几张之类的,isFlashOn的标志位就是HAL层直接根据应用层下发的Flash mode、AE mode得到的相对应的值。log里面所有显示的内容都是可以通过代码来层层获取相对的行为以及处理方法。庞大而精巧,希望以后的时间里能抽丝剥茧的将这些都拎出来晒晒。
3.前摄闪光灯补光处理
应用层对前摄闪光灯的控件管理类似后摄,由于前摄补光功能完全是由应用层完成的,所以不涉及tag的下发,应用层要简单很多。主要内容就是补光功能,代码如下:
@Overrideprotected boolean doShutterButtonClick() {if (storageReady && isDeviceReady && mIsResumed) {//开起补光动画,白色的mCoreView//trigger capture animationstartCaptureAnimation();updateModeDeviceState(MODE_DEVICE_STATE_CAPTURING);mIDeviceController.updateGSensorOrientation(mIApp.getGSensorOrientation());if ("on".equals(getFrontFlashValue()) && mIApp.getAppUi().getCurrentCameraId().equals("1")) {mApp.getActivity().runOnUiThread(new Runnable() {@Overridepublic void run() {((CameraActivity)mIApp).setAlpha(0.8f);}});//开始拍照抓取补光效果的帧takeFrontFlashPicture();} else {mIDeviceController.takePicture(this);}}return true;}public void animationStart(AnimationType type, IAppUi.AnimationData data) {LogHelper.d(TAG, "Start animation type: " + type);switch (type) {case TYPE_CAPTURE:if ("on".equals(getFrontFlashValue()) && mAppUI.getCurrentCameraId().equals("1") && mAppUI.getVideoState() != VideoState.STATE_RECORDING) {if("Z3".equals(MODEL) || "Z4".equals(MODEL)){mCoverView.setBackgroundColor(Color.parseColor("#fff0e6"));}else{//将View背景绘成白色,便于补光mCoverView.setBackgroundColor(Color.WHITE);}} else {mCoverView.setBackgroundColor(Color.BLACK);}mCoverView.setVisibility(View.VISIBLE);//开始补光的动作playCaptureAnimation();break;default:break;}}private void playCaptureAnimation() {LogHelper.d(TAG, "playCaptureAnimation +");AnimatorSet captureAnimation =(AnimatorSet) AnimatorInflater.loadAnimator(mApp.getActivity(),R.animator.cature_anim);if ("on".equals(getFrontFlashValue()) && mAppUI.getCurrentCameraId().equals("1") && mAppUI.getVideoState() != VideoState.STATE_RECORDING) {captureAnimation =(AnimatorSet) AnimatorInflater.loadAnimator(mApp.getActivity(),R.animator.cature_anim_fill);}captureAnimation.setTarget(mCoverView);//补光动作结束事件的监听captureAnimation.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {super.onAnimationEnd(animation);mCoverView.setVisibility(View.GONE);mApp.getActivity().runOnUiThread(new Runnable() {@Overridepublic void run() {((CameraActivity)mApp).setAlpha(1.0f);}});}});captureAnimation.start();LogHelper.d(TAG, "playCaptureAnimation -");}