AndroidR上展锐平台实现长按拍照键连拍的功能

某客户定制需求:长按拍照键实现连拍功能。
我们先来跟进下单拍的流程,连拍与单拍流程重叠度很高,只是连拍在单拍的照片callback回来之后,在立即下发下一次的拍照请求。
View的click流程就不说了,点击拍照键后,点击事件会回调到PhotoModule的onShutterButtonClick方法中。
在这个方法中会做拍照前的逻辑判断,比如当前对否设置了拍照倒计时,如果有的话,就转到倒计时的逻辑中,延时拍照。我们这里不考虑这些设置,后面就是调用FocusOverlayManager的 focusAndCapture()。这个方法我们应该挺熟悉的,我们之前有篇文章专门介绍预览聚焦的流程,这里在拍照之前也去确认了当前是否已经是聚焦OK的状态,否则我们会拍出模糊的照片。

FocusOverlayManager下的focusAndCapture方法

public void focusAndCapture(CameraCapabilities.FocusMode currentFocusMode) {
        if (!mInitialized) {
            return;
        }
        if(isAFLock && mState != STATE_FOCUSING){
            capture();
            return;
        }

        /**
         * SPRD:fix bug 473602 add for half-press @{
        if (!needAutoFocusCall(currentFocusMode)) {
         */
        if (!needAutoFocusCall(currentFocusMode) && mState != STATE_FOCUSING) {
        /**
         * @}
         */
            // Focus is not needed.
            Log.i(TAG, "Focus is not needed.");
            capture();
        } else if (mState == STATE_SUCCESS || mState == STATE_FAIL) {
            // Focus is done already.
            Log.i(TAG, "Focus is done already.");
            capture();
        } else if (mState == STATE_FOCUSING) {
            // Still focusing and will not trigger snap upon finish.
            Log.i(TAG, "till focusing and will not trigger snap upon finish.");
            mState = STATE_FOCUSING_SNAP_ON_FINISH;
        } else if (mState == STATE_IDLE) {
            autoFocusAndCapture();
        }
    }

我们假定当前是已经在预览时聚焦OK的状态,一般情况下在预览阶段就已经聚焦好了,那么会在调用capture方法。

private void capture() {
    if (mListener.capture()) {
        if (!mFocusRing.isAEAFDraging()) {
            mState = STATE_IDLE;
        }
        mHandler.removeMessages(RESET_TOUCH_FOCUS);
    }
}

capture方法啥也没干,又把流程转到mListener(PhotoModule)中去了。

PhotoModule的capture方法前半部分做一些mCameraSettings的更新,最后就下发takePicture的流程

@Override
public boolean capture() {
   Log.i(TAG, "capture");
   mBurstCaptureCountOnCanceled = -1;//SPRD:fix bug1085123
   if(!mBurstWorking && mIsContinousCaptureFinish){
       Log.i(TAG,"capture cancel");
       mAppController.getCameraAppUI().setBottomPanelLeftRightClickable(true);
       mAppController.getCameraAppUI().setSwipeEnabled(true);
       mBurstNotShowShutterButton = false;
       return false;
   }
   // If we are already in the middle of taking a snapshot or the image
   // save request is full then ignore.
   if (mCameraDevice == null || mCameraState == SNAPSHOT_IN_PROGRESS
           || mCameraState == SWITCHING_CAMERA) {
       return false;
   }
   setCameraState(SNAPSHOT_IN_PROGRESS);

   mCaptureStartTime = System.currentTimeMillis();

   mPostViewPictureCallbackTime = 0;
   mJpegImageData = null;


   /* SPRD: add hide shutter animation after capture when HDR is on
   final boolean animateBefore = (mSceneMode == CameraCapabilities.SceneMode.HDR);
   if (animateBefore) {
       animateAfterShutter();
   }
   */
   updateFilterType();
   /* SPRD: fix bug672841 add for cancel burst when focusing state, burst can not stop*/
   if (!mIsImageCaptureIntent) {
       updateParametersBurstCount();
       mContinuousCaptureCount = getContinuousCount();
   }
   /* @} */
   Location loc = null;
   if (mIsImageCaptureIntent && !mActivity.getThirdLocationPermission()) {
       Log.i(TAG,"third app call camera, has not location premission");
       CameraUtil.setGpsParameters(mCameraSettings, null);
   } else {
       loc = mActivity.getLocationManager().getCurrentLocation();
       Log.i(TAG,"location info:"+loc);
       CameraUtil.setGpsParameters(mCameraSettings, loc);
   }
   boolean isHasEnteredBeauty = mDataModule.getBoolean(Keys.KEY_CAMERA_BEAUTY_ENTERED);
   /* SPRD:fix bug 823475 clear ae corrdinate before capture when after taf @ */
   if (mFocusManager != null) {
       mFocusManager.updateFocusState();
       setMeteringAreasIfSupported();
   }
   /* @} */
   if(isAutoHdr() && isHdrScene){
       Log.e(TAG,"auto hdr detect scene is hdr so change the scene to hdr");
       mCameraSettings.setSceneMode(CameraCapabilities.SceneMode.HDR);
   }
   if (mSmileCapture) {
       mCameraSettings.setSmileCapture(1);
       mSmileCapture = false;
   }
   mCameraDevice.applySettings(mCameraSettings);

   // Set JPEG orientation. Even if screen UI is locked in portrait, camera
   // orientation should
   // still match device orientation (e.g., users should always get
   // landscape photos while
   // capturing by putting device in landscape.)
   Characteristics info = mActivity.getCameraProvider()
           .getCharacteristics(mCameraId);
   int sensorOrientation = info.getSensorOrientation();
   int deviceOrientation = mAppController.getOrientationManager()
           .getDeviceOrientation().getDegrees();
   boolean isFrontCamera = info.isFacingFront();
   mJpegRotation = CameraUtil.getImageRotation(sensorOrientation,
           deviceOrientation, isFrontCamera);
   Log.i(TAG, " sensorOrientation = " + sensorOrientation
           + " ,deviceOrientation = " + deviceOrientation
           + " isFrontCamera = " + isFrontCamera);
   mCameraDevice.setJpegOrientation(mJpegRotation);
   Log.i(TAG, "takePicture start!");
   isHdrPicture = isAutoHdr() && isHdrScene;
   if (mReceiver != null) {
       //mActivity.unregisterReceiver(mReceiver);
       mActivity.unRegisterMediaBroadcastReceiver();
       mReceiver = null;
   }

   mIsHdrPicture = true;
   mIsFirstCallback = true;
   mFirstHasStartCapture = false;
   mIsNormalHdrDone = false;//SPRD:fix bug784774
   if (inBurstMode()){
       doCaptureSpecialContinue();
   } else {
       doCaptureSpecial();
   }
   
   //开始拍照啦
   mCameraDevice.takePicture(mHandler,
   /**
    * SPRD: fix bug462021 remove capture animation
    * 
    * @{ new ShutterCallback(!animateBefore),
    */
   new ShutterCallback(CameraUtil.isCaptureAnimatationEnable() && !isBurstCapture() && !isAudioCapture() &&!isCameraFrontFacing()),//SPRD:fix bug1154938/1137366/1162992/1201491
   /**
    * @}
    */
   mRawPictureCallback, mPostViewPictureCallback, new JpegPictureCallback(loc));

   /**
    * SPRD: fix bug 473462 add for burst capture
    * mNamedImages.nameNewImage(mCaptureStartTime);
    */

   mFaceDetectionStarted = false;
   return true;
}

我们关注最后的 mCameraDevice.takePicture(…),重点来了,该方法是在AndroidCamera2AgentImpl中的 AndroidCamera2ProxyImpl 实现的。
这个方法比较长,分为两个部分,
1, 构建一个 CaptureAvailableListener 类型的 picListener 对象,作为照片数据的回调对象

// TODO: We never call raw or postview
final CaptureAvailableListener picListener =
    new CaptureAvailableListener() {
@Override
public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request,
                             long timestamp, long frameNumber) {
    // SPRD
    Log.i(TAG,"AppFw takePicture onCaptureStarted");

    if (shutter != null) {
        handler.post(new Runnable() {
            @Override
            public void run() {
                /*
                 * SPRD @{
                 * Original Code
                 *
                if (mShutterSoundEnabled) {
                    mNoisemaker.play(MediaActionSound.SHUTTER_CLICK);
                }
                 */
                shutter.onShutter(AndroidCamera2ProxyImpl.this);
            }});
    }
}

@Override
public void onImageAvailable(ImageReader reader) {
    // 第一部分,处理Burst相关的
    if (mPicListener != null && !mBurstCaptureCanceled) {
        if (mBurstHasCaptureCount < mBurstMaxCaptureCount) {
            Log.i(TAG, "AppFw takePicture onImageAvailable mBurstHasCaptureCount = "
                    + mBurstHasCaptureCount);
            try {
                mDispatchThread.runJob(new Runnable() {
                    @Override
                    public void run() {
                        if (mBurstCaptureCanceled) {
                            Log.i(TAG,"burst capture canceled");
                            return;
                        }
                        mCameraState.waitForStates(
                                ~(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE - 1));
                        mBurstHasCaptureCount++;//SPRD:fix bug756490
                        mCameraHandler.obtainMessage(CameraActions.CAPTURE_PHOTO, mPicListener)
                                .sendToTarget();
                    }
                });
            } catch (RuntimeException ex) {
                mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex);
            }
        }
    }
    Log.i(TAG,"AppFw takePicture onImageAvailable");
	//第二部分,处理上报的图片数据
    try (Image image = reader.acquireNextImage()) {
        if (!mNeedThumb && jpeg != null) {
            ByteBuffer buffer = image.getPlanes()[0].getBuffer();
            final byte[] pixels = new byte[buffer.remaining()];
            buffer.get(pixels);
            handler.post(new Runnable() {
                @Override
                public void run() {
                    jpeg.onPictureTaken(pixels, AndroidCamera2ProxyImpl.this);
                }});
        } else {
            onImageAvailableWithThumb(image, handler, jpeg);
        }
    }
}};

在该回调对象的 onImageAvailable 方法中,就是处理图片数据。onImageAvailable也分为两个部分。第一部分的if里面,我们看到Burst相关内容,没错,这就是处理连拍相关的逻辑的,我们先暂时略过,稍后会再回来讲这部分。
在第二部分中,我们看到一个很熟悉的方法onPictureTaken,这就是我们在PhotoModule中经常看到的图片数据的回调方法,在onPictureTaken中处理图片命名、保存到本地等逻辑。

2, CaptureAvailableListener 的回调对象我们看完了,在来看 takePicture 的第二部分。

try {
    mPicListener = picListener;
    mBurstMaxCaptureCount = mLastSettings.mBurstNumber;
    Log.i(TAG, "AppFw takePicture mBurstMaxCaptureCount now is " + mBurstMaxCaptureCount);
    if(takePictureRunnable == null) {
        Log.i(TAG, "AppFw takePicture new runJob");
        takePictureRunnable = new Runnable() {
            @Override
            public void run() {
                Log.i(TAG, "AppFw takePicture runJob");
                if (mBurstMaxCaptureCount > 1) {
                    mBurstHasCaptureCount = 0;// reset capture count, in case of didn't call
                    // cancelBurstCapture
                    mBurstHasCaptureCount++;
                    mBurstCaptureCanceled = false;
                }
                // Wait until PREVIEW_ACTIVE or better
                mCameraState.waitForStates(~(com.android.ex.camera2.portability.AndroidCamera2AgentImpl.AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE - 1));

                // SPRD : Add for bug 657472 Save normal hdr picture
                if(mLastSettings.getNormalHdrModeEnable() == 1){
                    mCameraHandler.obtainMessage(CameraActions.CAPTURE_HDR_PHOTO, mPicListener).sendToTarget();
                } else if (mNeedThumb) {
                    mCameraHandler.obtainMessage(CameraActions.CAPTURE_PHOTO_WITH_THUMB, mPicListener).sendToTarget();
                } else if (mIsVideMode) {
                    mCameraHandler.obtainMessage(CameraActions.CAPTURE_PHOTO_WITH_SNAP, mPicListener).sendToTarget();
                }else{
                    mCameraHandler.obtainMessage(CameraActions.CAPTURE_PHOTO, mPicListener).sendToTarget();
                }
                /* @} */
            }
        };
    }
    mDispatchThread.runJob(takePictureRunnable);
} catch (RuntimeException ex) {
    takePictureRunnable = null;
    mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex);
}

又看到熟悉的Hander发送消息,这次发送的是 CAPTURE_PHOTO ,看名称就知道 拍照 的消息。并且注意到我们前面的 CaptureAvailableListener picListener 作为handler消息的obj了。

接下来我们就看下,CAPTURE_PHOTO 消息的处理。

case CameraActions.CAPTURE_PHOTO: {

  final CaptureAvailableListener listener =
          (CaptureAvailableListener) msg.obj;
  if (mLegacyDevice || mIsYuvSensor || mCameraProxy.mLastSettings.mBurstNumber == 30 ||//SPRD: fix bug962989
mCameraProxy.mLastSettings.mBurstNumber == 20 ||
          (mPersistentSettings.matches(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF)) ||
          ((mCurrentAeState == CaptureResult.CONTROL_AE_STATE_CONVERGED || mCurrentAeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED) &&//SPRD:fix bug1193947
          !mPersistentSettings.matches(CaptureRequest.CONTROL_AE_MODE,
                  CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH) &&
          !mPersistentSettings.matches(CaptureRequest.FLASH_MODE,
                  CaptureRequest.FLASH_MODE_SINGLE)))
          {

      // SPRD
      Log.i(TAG,"AppFw:   CameraActions.CAPTURE_PHOTO, mLegacyDevice = " + mLegacyDevice + " mIsYuvSensor = " + mIsYuvSensor);
      mCaptureReader.setOnImageAvailableListener(listener, /*handler*/this);
      try {
          mSession.capture(
                  mPersistentSettings.createRequest(mCamera,
                          CameraDevice.TEMPLATE_STILL_CAPTURE,
                          mCaptureReader.getSurface()),
                  listener, /*handler*/this);
      } catch (CameraAccessException ex) {
          Log.e(TAG, "Unable to initiate immediate capture", ex);
      }
  } 

我们看到 if 的判断很复杂,但是我们最终还是进入到这个if条件里面了,并且我们前面讲的 CaptureAvailableListener listener 也从消息里面取出来了 (CaptureAvailableListener) msg.obj;

然后就是通过在创建会话时得到的mSession对象,去调用CameraCaptureSession的capture方法,并将CaptureAvailableListener 作为回调对象传下去。

到这里,我们的拍照流程算是讲完了。我们将上拍照流程总结为一个时序图
在这里插入图片描述

好了,拍照流程讲完了,下面我们来说下连拍的逻辑。

我们前面说过 CaptureAvailableListener picListener 这个回调,是用来接收拍照数据的,其中的onImageAvailable方法中有关于Burst的处理。我们再贴下代码

if (mPicListener != null && !mBurstCaptureCanceled) {
    if (mBurstHasCaptureCount < mBurstMaxCaptureCount) {
        Log.i(TAG, "AppFw takePicture onImageAvailable mBurstHasCaptureCount = "
                + mBurstHasCaptureCount);
        try {
            mDispatchThread.runJob(new Runnable() {
                @Override
                public void run() {
                    if (mBurstCaptureCanceled) {
                        Log.i(TAG,"burst capture canceled");
                        return;
                    }
                    mCameraState.waitForStates(
                            ~(AndroidCamera2StateHolder.CAMERA_PREVIEW_ACTIVE - 1));
                    mBurstHasCaptureCount++;//SPRD:fix bug756490
                    mCameraHandler.obtainMessage(CameraActions.CAPTURE_PHOTO, mPicListener)
                            .sendToTarget();
                }
            });
        } catch (RuntimeException ex) {
            mCameraAgent.getCameraExceptionHandler().onDispatchThreadException(ex);
        }
    }
}

我们看到,如果这个if条件能满足,就会在发送一个 CAPTURE_PHOTO 消息去拍照。这其实就是连拍的逻辑,在前一张图片的数据callbakc回来之后,再去下发一个新的 CAPTURE_PHOTO 拍照请求,从而实现连拍。

那么,如何才能满足这个if条件呢?

我们看到有 mBurstCaptureCanceled 和 mBurstMaxCaptureCount 变量。如果满足连拍条件,mBurstCaptureCanceled = false;
在该类中搜索, mBurstCaptureCanceled 默认为true,只有如下位置会置为false。

 mBurstMaxCaptureCount = mLastSettings.mBurstNumber;
 if (mBurstMaxCaptureCount > 1) {
    mBurstHasCaptureCount = 0;// reset capture count, in case of didn't call
     // cancelBurstCapture
     mBurstHasCaptureCount++;
     mBurstCaptureCanceled = false;
 }

看来关键是 mLastSettings.mBurstNumber;的值了,在SprdCameraSettings中有唯一方法设置mBurstNumber的值

public void setBurstPicNum(int count) {
	mBurstNumber = count;
}

在全局搜一把 setBurstPicNum 方法的调用。
果然又来到了PhotoModule中

private void updateParametersBurstCount() {
    mCameraSettings.setBurstPicNum(!mBurstWorking ? 1 : MAX_BURST_COUNT);
}

如果 mBurstWorking = true,那么 setBurstPicNum传入的就是MAX_BURST_COUNT值了,而 mBurstWorking = true的条件之一便是当前是 inBurstMode()返回true。

public boolean inBurstMode() {
        return getModuleTpye() == DreamModule.AUTO_PHOTO_MODULE && mLongPressed; 
    }

mLongPressed,长按拍照键条件。
所以只有长按拍照键,才会设置 mLastSettings.mBurstNumber 为 MAX_BURST_COUNT,在AndroidCamera2AgentImpl中才会设置 mBurstCaptureCanceled = false,才会在前一张图片数据callback之后立即下发下一次的 CAPTURE_PHOTO 拍照请求。

拍照和连拍的逻辑大部分都是一样的,只是连拍会设置SprdCameraSettings的mBurstNumber 值,在AndroidCamera2AgentImpl中会自动连续下发拍照请求。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值