接上篇博文https://blog.csdn.net/weixin_38160277/article/details/102969875
上次讲到Camrea预览,这次要讲Camrea的拍照
大家还记得第一篇博客讲App启动的时候讲到的CameraActivity里面的setModuleFromIndex()函数吗,这个就是视频,拍照,全景等模块切换的函数,该函数通过moduleIndex来切换不同的模块,看代码:
/**
* Sets the mCurrentModuleIndex, creates a new module instance for the given
* index an sets it as mCurrentModule.
*/
private void setModuleFromIndex(int moduleIndex) {
mCameraPhotoModuleRootView.setVisibility(View.GONE);
mCameraVideoModuleRootView.setVisibility(View.GONE);
mCameraPanoModuleRootView.setVisibility(View.GONE);
mCameraCaptureModuleRootView.setVisibility(View.GONE);
mCurrentModuleIndex = moduleIndex;
switch (moduleIndex) {
case ModuleSwitcher.VIDEO_MODULE_INDEX:
if(mVideoModule == null) {
mVideoModule = new VideoModule();
mVideoModule.init(this, mCameraVideoModuleRootView);
} else {
mVideoModule.reinit();
}
mCurrentModule = mVideoModule;
mCameraVideoModuleRootView.setVisibility(View.VISIBLE);
break;
case ModuleSwitcher.PHOTO_MODULE_INDEX:
if(mPhotoModule == null) {
mPhotoModule = new PhotoModule();
mPhotoModule.init(this, mCameraPhotoModuleRootView);
} else {
mPhotoModule.reinit();
}
mCurrentModule = mPhotoModule;
mCameraPhotoModuleRootView.setVisibility(View.VISIBLE);
break;
case ModuleSwitcher.WIDE_ANGLE_PANO_MODULE_INDEX:
if(mPanoModule == null) {
mPanoModule = new WideAnglePanoramaModule();
mPanoModule.init(this, mCameraPanoModuleRootView);
}
mCurrentModule = mPanoModule;
mCameraPanoModuleRootView.setVisibility(View.VISIBLE);
break;
case ModuleSwitcher.CAPTURE_MODULE_INDEX:
if(mCaptureModule == null) {
mCaptureModule = new CaptureModule();
mCaptureModule.init(this, mCameraCaptureModuleRootView);
} else {
mCaptureModule.reinit();
}
mCurrentModule = mCaptureModule;
mCameraCaptureModuleRootView.setVisibility(View.VISIBLE);
break;
case ModuleSwitcher.PANOCAPTURE_MODULE_INDEX:
final Activity activity = this;
if(!PanoCaptureProcessView.isSupportedStatic()) {
this.runOnUiThread(new Runnable() {
public void run() {
RotateTextToast.makeText(activity, "Panocapture library is missing", Toast.LENGTH_SHORT).show();
}
});
mCurrentModuleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX;
//Let it fall through to photo module
} else {
if (mPano2Module == null) {
mPano2Module = new PanoCaptureModule();
mPano2Module.init(this, mCameraPanoModuleRootView);
}
mCurrentModule = mPano2Module;
mCameraPanoModuleRootView.setVisibility(View.VISIBLE);
break;
}
case ModuleSwitcher.LIGHTCYCLE_MODULE_INDEX: //Unused module for now
case ModuleSwitcher.GCAM_MODULE_INDEX: //Unused module for now
default:
// Fall back to photo mode.
if(mPhotoModule == null) {
mPhotoModule = new PhotoModule();
mPhotoModule.init(this, mCameraPhotoModuleRootView);
} else {
mPhotoModule.reinit();
}
mCurrentModule = mPhotoModule;
mCameraPhotoModuleRootView.setVisibility(View.VISIBLE);
break;
}
}
这里默认是进入到PhotoModule的也就是拍照模块,我们再来看看拍照的代码在哪里?打开PhotoModule.java,我们直接找一下有没有capture(),果然有,look:
@Override
public boolean capture() {
// 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
|| mCameraState == PREVIEW_STOPPED
|| mActivity.getMediaSaveService() == null
|| mActivity.getMediaSaveService().isQueueFull()) {
return false;
}
mCaptureStartTime = System.currentTimeMillis();
mPostViewPictureCallbackTime = 0;
mJpegImageData = null;
final boolean animateBefore = (mSceneMode == CameraUtil.SCENE_MODE_HDR);
if(mHiston) {
if (mSnapshotMode != CameraInfoWrapper.CAMERA_SUPPORT_MODE_ZSL) {
mHiston = false;
mCameraDevice.setHistogramMode(null);
}
mActivity.runOnUiThread(new Runnable() {
public void run() {
if(mGraphView != null)
mGraphView.setVisibility(View.INVISIBLE);
}
});
}
if (animateBefore) {
animateAfterShutter();
}
if (mCameraState == LONGSHOT) {
mCameraDevice.setLongshot(true);
}
// Set rotation and gps data.
int orientation = mOrientation;
mJpegRotation = CameraUtil.getJpegRotationForCamera1(mCameraId, orientation);
String pictureFormat = mParameters.get(KEY_PICTURE_FORMAT);
Location loc = getLocationAccordPictureFormat(pictureFormat);
//这里是线程安全的,同一时间Camera只允许一个进程访问
synchronized (mCameraDevice) {
mParameters.setRotation(mJpegRotation);
CameraUtil.setGpsParameters(mParameters, loc);
if (mRefocus) {
mParameters.set(CameraSettings.KEY_QC_LEGACY_BURST,
CameraSettings.KEY_QC_RE_FOCUS_COUNT);
} else {
mParameters.remove(CameraSettings.KEY_QC_LEGACY_BURST);
}
// Unlock AE&AWB, if they continue
// to be locked during snapshot, then
// side effects could be triggered w.r.t.
// flash.
mFocusManager.setAeAwbLock(false);
setAutoExposureLockIfSupported();
setAutoWhiteBalanceLockIfSupported();
//设置Camrea参数,什么曝光量啊,色彩值啊,锐度啊都用Camrea的SetParameters里面设置,你只需要把这些参数封装好传给Camera就行
mCameraDevice.setParameters(mParameters);
mParameters = mCameraDevice.getParameters();
}
try {
mBurstSnapNum = mParameters.getInt("num-snaps-per-shutter");
}catch (NumberFormatException ex){
mBurstSnapNum = 1;
}
mReceivedSnapNum = 0;
mPreviewRestartSupport = PersistUtil.isPreviewRestartEnabled();
mPreviewRestartSupport &= CameraSettings.isInternalPreviewSupported(
mParameters);
mPreviewRestartSupport &= (mBurstSnapNum == 1);
// Restart is needed if HDR is enabled
mPreviewRestartSupport &= !CameraUtil.SCENE_MODE_HDR.equals(mSceneMode);
mPreviewRestartSupport &= PIXEL_FORMAT_JPEG.equalsIgnoreCase(
pictureFormat);
// We don't want user to press the button again while taking a
// multi-second HDR photo. For longshot, no need to disable.
if (mCameraState != LONGSHOT) {
mUI.enableShutter(false);
}
if (!isShutterSoundOn()) {
mCameraDevice.enableShutterSound(false);
} else {
mCameraDevice.enableShutterSound(!mRefocus);
}
mSaveBokehXmp = mIsBokehMode && mDepthSuccess;
if (mCameraState == LONGSHOT) {
mLongShotCaptureCountLimit = SystemProperties.getInt(
"persist.sys.camera.longshot.shotnum", 0);
mLongShotCaptureCount = 1;
if(mLongshotSave) {
//调用Camera的takePicture方法传入回调函数,拍照完成会回调这些函数并传递图片信息过来给App存储和编辑
mCameraDevice.takePicture(mHandler,
new LongshotShutterCallback(),
mRawPictureCallback, mPostViewPictureCallback,
new LongshotPictureCallback(loc));
} else {
mCameraDevice.takePicture(mHandler,
new LongshotShutterCallback(),
mRawPictureCallback, mPostViewPictureCallback,
new JpegPictureCallback(loc));
}
} else {
mCameraDevice.takePicture(mHandler,
new ShutterCallback(!animateBefore),
mRawPictureCallback, mPostViewPictureCallback,
new JpegPictureCallback(loc));
setCameraState(SNAPSHOT_IN_PROGRESS);
}
mNamedImages.nameNewImage(mCaptureStartTime, mRefocus);
if (mSnapshotMode != CameraInfoWrapper.CAMERA_SUPPORT_MODE_ZSL) {
mFaceDetectionStarted = false;
}
UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
UsageStatistics.ACTION_CAPTURE_DONE, "Photo", 0,
UsageStatistics.hashFileName(mNamedImages.mQueue.lastElement().title + ".jpg"));
return true;
}
代码跟踪到这里,你也许会有疑问,这个capture()函数是何时调用执行的?这当然是在按下快门键的时候调用的。接下来就跟踪一下快门键按钮,找到ShutterButton.java
/**
* A button designed to be used for the on-screen shutter button.
* It's currently an {@code ImageView} that can call a delegate when the
* pressed state changes.
*/
public class ShutterButton extends RotateImageView {
private class LongClickListener implements View.OnLongClickListener {
public boolean onLongClick(View v) {
...
return false;
}
}
....
public interface OnShutterButtonListener {
/**
* Called when a ShutterButton has been pressed.
*
* @param pressed The ShutterButton that was pressed.
*/
void onShutterButtonFocus(boolean pressed);
void onShutterButtonClick();
void onShutterButtonLongClick();
}
...
private OnShutterButtonListener mListener;
....
public void setOnShutterButtonListener(OnShutterButtonListener listener) {
mListener = listener;
setOnLongClickListener(mLongClick);
}
@Override
public boolean performClick() {
boolean result = super.performClick();
if (mListener != null && getVisibility() == View.VISIBLE) {
mListener.onShutterButtonClick();
}
return result;
}
....
}
可看到它是一个自定义的ImageView来的里面有个OnShutterButtonListener 接口,这个接口是用来规定快门键点击事件的。现在来看下谁实现了这个接口
public class PhotoModule
implements CameraModule,
PhotoController,
FocusOverlayManager.Listener,
CameraPreference.OnPreferenceChangedListener,
ShutterButton.OnShutterButtonListener,
MediaSaveService.Listener,
OnCountDownFinishedListener,
LocationManager.Listener,
SensorEventListener, MakeupLevelListener {
可见就是PhotoModule本身实现了这个接口,那接下来就看下它的实现方法
//当快门按钮点击的时候会调用到这个函数
@Override
public synchronized void onShutterButtonClick() {
.....
initiateSnap();
....
}
private void initiateSnap()
{
if(mPreferences.getString(CameraSettings.KEY_SELFIE_FLASH,
mActivity.getString(R.string.pref_selfie_flash_default))
.equalsIgnoreCase("on") &&
mCameraId == CameraHolder.instance().getFrontCameraId()) {
mUI.startSelfieFlash();
if(selfieThread == null) {
selfieThread = new SelfieThread();
selfieThread.start();
}
} else {
mFocusManager.doSnap();
}
}
//FocusOverlayManager类
public void doSnap() {
if (!mInitialized) return;
// If the user has half-pressed the shutter and focus is completed, we
// can take the photo right away. If the focus mode is infinity, we can
// also take the photo.
if (!needAutoFocusCall() || (mState == STATE_SUCCESS || mState == STATE_FAIL)) {
capture();
} else if (mState == STATE_FOCUSING) {
// Half pressing the shutter (i.e. the focus button event) will
// already have requested AF for us, so just request capture on
// focus here.
mState = STATE_FOCUSING_SNAP_ON_FINISH;
} else if (mState == STATE_IDLE) {
// We didn't do focus. This can happen if the user press focus key
// while the snapshot is still in progress. The user probably wants
// the next snapshot as soon as possible, so we just do a snapshot
// without focusing again.
capture();
}
}
private void capture() {
if (mListener.capture()) {
mState = STATE_IDLE;
mHandler.removeMessages(RESET_TOUCH_FOCUS);
}
}
代码跟踪到这里mListener.capture()这行代码是干嘛的捏,请看mListener是什么玩意:
public interface Listener {
public void autoFocus();
public void cancelAutoFocus();
public boolean capture();
public void startFaceDetection();
public void stopFaceDetection();
public void setFocusParameters();
}
它是一个接口,定义在FocusOverlayManager类里面,那么谁实现了这个接口呢
look,又是PhotoModule
public class PhotoModule
implements CameraModule,
PhotoController,
FocusOverlayManager.Listener,
CameraPreference.OnPreferenceChangedListener,
ShutterButton.OnShutterButtonListener,
MediaSaveService.Listener,
OnCountDownFinishedListener,
LocationManager.Listener,
SensorEventListener, MakeupLevelListener {
看下它的实现方法capture()
@Override
public boolean capture() {
// 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
|| mCameraState == PREVIEW_STOPPED
|| mActivity.getMediaSaveService() == null
|| mActivity.getMediaSaveService().isQueueFull()) {
return false;
}
mCaptureStartTime = System.currentTimeMillis();
mPostViewPictureCallbackTime = 0;
mJpegImageData = null;
final boolean animateBefore = (mSceneMode == CameraUtil.SCENE_MODE_HDR);
if(mHiston) {
if (mSnapshotMode != CameraInfoWrapper.CAMERA_SUPPORT_MODE_ZSL) {
mHiston = false;
mCameraDevice.setHistogramMode(null);
}
mActivity.runOnUiThread(new Runnable() {
public void run() {
if(mGraphView != null)
mGraphView.setVisibility(View.INVISIBLE);
}
});
}
if (animateBefore) {
animateAfterShutter();
}
if (mCameraState == LONGSHOT) {
mCameraDevice.setLongshot(true);
}
// Set rotation and gps data.
int orientation = mOrientation;
mJpegRotation = CameraUtil.getJpegRotationForCamera1(mCameraId, orientation);
String pictureFormat = mParameters.get(KEY_PICTURE_FORMAT);
Location loc = getLocationAccordPictureFormat(pictureFormat);
synchronized (mCameraDevice) {
mParameters.setRotation(mJpegRotation);
CameraUtil.setGpsParameters(mParameters, loc);
if (mRefocus) {
mParameters.set(CameraSettings.KEY_QC_LEGACY_BURST,
CameraSettings.KEY_QC_RE_FOCUS_COUNT);
} else {
mParameters.remove(CameraSettings.KEY_QC_LEGACY_BURST);
}
// Unlock AE&AWB, if they continue
// to be locked during snapshot, then
// side effects could be triggered w.r.t.
// flash.
mFocusManager.setAeAwbLock(false);
setAutoExposureLockIfSupported();
setAutoWhiteBalanceLockIfSupported();
mCameraDevice.setParameters(mParameters);
mParameters = mCameraDevice.getParameters();
}
try {
mBurstSnapNum = mParameters.getInt("num-snaps-per-shutter");
}catch (NumberFormatException ex){
mBurstSnapNum = 1;
}
mReceivedSnapNum = 0;
mPreviewRestartSupport = PersistUtil.isPreviewRestartEnabled();
mPreviewRestartSupport &= CameraSettings.isInternalPreviewSupported(
mParameters);
mPreviewRestartSupport &= (mBurstSnapNum == 1);
// Restart is needed if HDR is enabled
mPreviewRestartSupport &= !CameraUtil.SCENE_MODE_HDR.equals(mSceneMode);
mPreviewRestartSupport &= PIXEL_FORMAT_JPEG.equalsIgnoreCase(
pictureFormat);
// We don't want user to press the button again while taking a
// multi-second HDR photo. For longshot, no need to disable.
if (mCameraState != LONGSHOT) {
mUI.enableShutter(false);
}
if (!isShutterSoundOn()) {
mCameraDevice.enableShutterSound(false);
} else {
mCameraDevice.enableShutterSound(!mRefocus);
}
mSaveBokehXmp = mIsBokehMode && mDepthSuccess;
if (mCameraState == LONGSHOT) {
mLongShotCaptureCountLimit = SystemProperties.getInt(
"persist.sys.camera.longshot.shotnum", 0);
mLongShotCaptureCount = 1;
if(mLongshotSave) {
mCameraDevice.takePicture(mHandler,
new LongshotShutterCallback(),
mRawPictureCallback, mPostViewPictureCallback,
new LongshotPictureCallback(loc));
} else {
mCameraDevice.takePicture(mHandler,
new LongshotShutterCallback(),
mRawPictureCallback, mPostViewPictureCallback,
new JpegPictureCallback(loc));
}
} else {
mCameraDevice.takePicture(mHandler,
new ShutterCallback(!animateBefore),
mRawPictureCallback, mPostViewPictureCallback,
new JpegPictureCallback(loc));
setCameraState(SNAPSHOT_IN_PROGRESS);
}
mNamedImages.nameNewImage(mCaptureStartTime, mRefocus);
if (mSnapshotMode != CameraInfoWrapper.CAMERA_SUPPORT_MODE_ZSL) {
mFaceDetectionStarted = false;
}
UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
UsageStatistics.ACTION_CAPTURE_DONE, "Photo", 0,
UsageStatistics.hashFileName(mNamedImages.mQueue.lastElement().title + ".jpg"));
return true;
}
是不是似曾相识,哈哈,对又绕回来了,这个就是开始的时候讲的那个capture(),有点晕了吧,现在来理一下:
当快门按钮ShutterButton按下时会回调OnShutterButtonListener的onShutterButtonClick()方法,而实现该接口的PhotoModule类的onShutterButtonClick()会被执行,接着又调用到了FocusOverlayManager的doSnap() ,而doSnap() 又调用到了FocusOverlayManager.Listener的capture()函数,PhotoModule正是FocusOverlayManager.Listener的实现类,所以最终PhotoModule的capture()被执行,调用系统层api实现拍照功能。
讲到这里可能大家会郁闷,怎么那么多的接口,就不能直白一点吗?说实话我也郁闷,但是用接口写的好处就是结构清晰,写大型项目就需要这样的。当然你也可以直白一点写,但是项目大了就会出问题的。
好了,本博到此结束,下篇我还会接着讲Camera的其他功能流程,敬请期待。
谢谢大家,祝你工作顺利,生活愉快