最近被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这些内容的处理,把它搞得服服帖帖,明明白白的