本文以SnapdragonCamera为例,分析骁龙Camera的拍照流程,其实现与camera2大致相同。
首先将SnapdragonCamera源码倒入android studio, 具体操作,可查看如何用Android Studio调试Android源码一文。打开camera,点击拍照,ShutterButton类的performClick()方法会被调用。(如何定位点击拍照会调用ShutterButton类的performClick()方法,请参考使用Android Studio中的HierarchyViewer 及UI Automator Viewer定位当前UI界面的代码位置。)
@Override
public boolean performClick() {
boolean result = super.performClick();
if (mListener != null && getVisibility() == View.VISIBLE) {
mListener.onShutterButtonClick();
}
return result;
}
按Ctrl+Alt+B 看onShutterButtonClick()方法是在哪里实现的。如下图,有四个地方调用,由于我们只分析拍照流程,只用查看PhotoModule即可。
在PhotoModule中,onShutterButtonClick()的实现如下
@Override
public synchronized void onShutterButtonClick() {
if ((mCameraDevice == null)
|| mPaused || mUI.collapseCameraControls()
|| !mUI.mMenuInitialized
|| (mCameraState == SWITCHING_CAMERA)
|| (mCameraState == PREVIEW_STOPPED)
|| (mCameraState == LONGSHOT)
|| (null == mFocusManager)) return;
.
.
.
if (seconds > 0) {
String zsl = mPreferences.getString(CameraSettings.KEY_ZSL,
mActivity.getString(R.string.pref_camera_zsl_default));
mUI.overrideSettings(CameraSettings.KEY_ZSL, zsl);
mUI.startCountDown(seconds, playSound);
} else {
mSnapshotOnIdle = false;
initiateSnap();//关键方法
}
}
为了便于查看,我们省略掉一些条件判断的代码,最终调用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 == null,SelfieThread中的
selfieThread.start(); //mFocusManager.doSnap()方法被调用
}
} else {
mFocusManager.doSnap();//关键方法
}
}
接着查看doSnap()方法
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();//关键方法,没有聚焦时调用
}
}
我们分析第一种情况,第二种情况,涉及callShutterButtonFocus()的调用流程,这里暂不做分析,接着我们看PhotoModule中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
|| mActivity.getMediaSaveService() == null
|| mActivity.getMediaSaveService().isQueueFull()) {
return false;
}
...
... //省略相关参数设置,及条件判断代码
...
if (mCameraState == LONGSHOT) {
mLongShotCaptureCountLimit = SystemProperties.getInt(
"persist.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 != CameraInfo.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;
}
其中关键代码为mCameraDevice.takePicture(),拍照后会回调JpegPictureCallback中的onPictureTaken()方法。将从底层返回的数据进行处理。
@Override
public void onPictureTaken(byte [] jpegData, CameraProxy camera) {
mUI.stopSelfieFlash();
mUI.enableShutter(true);
if (mUI.isPreviewCoverVisible()) {
// When take picture request is sent before starting preview, onPreviewFrame()
// callback doesn't happen so removing preview cover here, instead.
mUI.hidePreviewCover();
}
if (mInstantCaptureSnapShot == true) {
Log.v(TAG, "Instant capture picture taken!");
mInstantCaptureSnapShot = false;
}
if (mPaused) {
return;
}
if (mIsImageCaptureIntent) {
if (!mRefocus) {
stopPreview();
}
} else if (mSceneMode == CameraUtil.SCENE_MODE_HDR) {
mUI.showSwitcher();
mUI.setSwipingEnabled(true);
}
mReceivedSnapNum = mReceivedSnapNum + 1;
mJpegPictureCallbackTime = System.currentTimeMillis();
if(mSnapshotMode == CameraInfo.CAMERA_SUPPORT_MODE_ZSL) {
Log.v(TAG, "JpegPictureCallback : in zslmode");
mParameters = mCameraDevice.getParameters();
mBurstSnapNum = mParameters.getInt("num-snaps-per-shutter");
}
Log.v(TAG, "JpegPictureCallback: Received = " + mReceivedSnapNum +
"Burst count = " + mBurstSnapNum);
// If postview callback has arrived, the captured image is displayed
// in postview callback. If not, the captured image is displayed in
// raw picture callback.
if (mPostViewPictureCallbackTime != 0) {
mShutterToPictureDisplayedTime =
mPostViewPictureCallbackTime - mShutterCallbackTime;
mPictureDisplayedToJpegCallbackTime =
mJpegPictureCallbackTime - mPostViewPictureCallbackTime;
} else {
mShutterToPictureDisplayedTime =
mRawPictureCallbackTime - mShutterCallbackTime;
mPictureDisplayedToJpegCallbackTime =
mJpegPictureCallbackTime - mRawPictureCallbackTime;
}
Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "
+ mPictureDisplayedToJpegCallbackTime + "ms");
mFocusManager.updateFocusUI(); // Ensure focus indicator is hidden.
boolean needRestartPreview = !mIsImageCaptureIntent
&& !mPreviewRestartSupport
&& (mCameraState != LONGSHOT)
&& (mSnapshotMode != CameraInfo.CAMERA_SUPPORT_MODE_ZSL)
&& (mReceivedSnapNum == mBurstSnapNum);
if (needRestartPreview) {
setupPreview();
if (CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE.equals(
mFocusManager.getFocusMode())) {
mCameraDevice.cancelAutoFocus();
}
} else if ((mReceivedSnapNum == mBurstSnapNum)
&& (mCameraState != LONGSHOT)){
mFocusManager.resetTouchFocus();
if (CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE.equals(
mFocusManager.getFocusMode())) {
mCameraDevice.cancelAutoFocus();
}
mUI.resumeFaceDetection();
if (!mIsImageCaptureIntent) {
setCameraState(IDLE);
}
startFaceDetection();
}
mLastPhotoTakenWithRefocus = mRefocus;
if (mRefocus) {
final String[] NAMES = { "00.jpg", "01.jpg", "02.jpg", "03.jpg",
"04.jpg", "DepthMapImage.y", "AllFocusImage.jpg" };
try {
FileOutputStream out = mActivity.openFileOutput(NAMES[mReceivedSnapNum - 1],
Context.MODE_PRIVATE);
out.write(jpegData, 0, jpegData.length);
out.close();
} catch (Exception e) {
}
}
if (!mRefocus || (mRefocus && mReceivedSnapNum == 7)) {
ExifInterface exif = Exif.getExif(jpegData);
int orientation = Exif.getOrientation(exif);
if(mCameraId == CameraHolder.instance().getFrontCameraId()) {
IconListPreference selfieMirrorPref = (IconListPreference) mPreferenceGroup
.findPreference(CameraSettings.KEY_SELFIE_MIRROR);
if (selfieMirrorPref != null && selfieMirrorPref.getValue() != null &&
selfieMirrorPref.getValue().equalsIgnoreCase("enable")) {
jpegData = flipJpeg(jpegData);
exif = Exif.getExif(jpegData); //将图片信息存入Exif中
exif.addOrientationTag(orientation);
}
}
if (!mIsImageCaptureIntent) {
// Burst snapshot. Generate new image name.
if (mReceivedSnapNum > 1) {
mNamedImages.nameNewImage(mCaptureStartTime, mRefocus);
}
// Calculate the width and the height of the jpeg.
Size s = mParameters.getPictureSize();
int width, height;
if ((mJpegRotation + orientation) % 180 == 0) {
width = s.width;
height = s.height;
} else {
width = s.height;
height = s.width;
}
String pictureFormat = mParameters.get(KEY_PICTURE_FORMAT);
if (pictureFormat != null && !pictureFormat.equalsIgnoreCase(PIXEL_FORMAT_JPEG)) {
// overwrite width and height if raw picture
String pair = mParameters.get(KEY_QC_RAW_PICUTRE_SIZE);
if (pair != null) {
int pos = pair.indexOf('x');
if (pos != -1) {
width = Integer.parseInt(pair.substring(0, pos));
height = Integer.parseInt(pair.substring(pos + 1));
}
}
}
NamedEntity name = mNamedImages.getNextNameEntity();
String title = (name == null) ? null : name.title;
long date = (name == null) ? -1 : name.date;
// Handle debug mode outputs
if (mDebugUri != null) {
// If using a debug uri, save jpeg there.
saveToDebugUri(jpegData);
// Adjust the title of the debug image shown in mediastore.
if (title != null) {
title = DEBUG_IMAGE_PREFIX + title;
}
}
if (title == null) {
Log.e(TAG, "Unbalanced name/data pair");
} else {
if (date == -1) {
date = mCaptureStartTime;
}
if (mHeading >= 0) {
// heading direction has been updated by the sensor.
ExifTag directionRefTag = exif.buildTag(
ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
ExifInterface.GpsTrackRef.MAGNETIC_DIRECTION);
ExifTag directionTag = exif.buildTag(
ExifInterface.TAG_GPS_IMG_DIRECTION,
new Rational(mHeading, 1));
exif.setTag(directionRefTag);
exif.setTag(directionTag);
}
String mPictureFormat = mParameters.get(KEY_PICTURE_FORMAT);
mActivity.getMediaSaveService().addImage(
jpegData, title, date, mLocation, width, height,
orientation, exif, mOnMediaSavedListener,
mContentResolver, mPictureFormat);//将图片数据写入到文件和数据库中
if (mRefocus && mReceivedSnapNum == 7) {
mUI.showRefocusToast(mRefocus);
}
}
// Animate capture with real jpeg data instead of a preview frame.
if (mCameraState != LONGSHOT) {
Size pic_size = mParameters.getPictureSize();
if ((pic_size.width <= 352) && (pic_size.height<= 288)) {
mUI.setDownFactor(2); //Downsample by 2 for CIF & below
} else {
mUI.setDownFactor(4);
}
if (mAnimateCapture) {
mUI.animateCapture(jpegData);
}
} else {
// In long shot mode, we do not want to update the preview thumbnail
// for each snapshot, instead, keep the last jpeg data and orientation,
// use it to show the final one at the end of long shot.
mLastJpegData = jpegData;
mLastJpegOrientation = orientation;
}
} else {
stopPreview();
mJpegImageData = jpegData;
if (!mQuickCapture) {
mUI.showCapturedImageForReview(jpegData, orientation, false);
} else {
onCaptureDone();
}
}
if(!mLongshotActive) {
mActivity.updateStorageSpaceAndHint(
new CameraActivity.OnStorageUpdateDoneListener() {
@Override
public void onStorageUpdateDone(long storageSpace) {
mUI.updateRemainingPhotos(--mRemainingPhotos);
}
});
} else {
mUI.updateRemainingPhotos(--mRemainingPhotos);
}
long now = System.currentTimeMillis();
mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
Log.v(TAG, "mJpegCallbackFinishTime = "
+ mJpegCallbackFinishTime + "ms");
if (mReceivedSnapNum == mBurstSnapNum) {
mJpegPictureCallbackTime = 0;
}
if (mHiston && (mSnapshotMode ==CameraInfo.CAMERA_SUPPORT_MODE_ZSL)) {
mActivity.runOnUiThread(new Runnable() {
public void run() {
if (mGraphView != null) {
mGraphView.setVisibility(View.VISIBLE);
mGraphView.PreviewChanged();
}
}
});
}
if (mSnapshotMode == CameraInfo.CAMERA_SUPPORT_MODE_ZSL &&
mCameraState != LONGSHOT &&
mReceivedSnapNum == mBurstSnapNum &&
!mIsImageCaptureIntent) {
cancelAutoFocus();
}
}
}
其中, mActivity.getMediaSaveService().addImage();方法将返回的图片数据存入文件和数据库中。MediaSaveService中的addImage()方法如下
public void addImage(final byte[] data, String title, long date, Location loc,
int width, int height, int orientation, ExifInterface exif,
OnMediaSavedListener l, ContentResolver resolver, String pictureFormat) {
if (isQueueFull()) {
Log.e(TAG, "Cannot add image when the queue is full");
return;
}
ImageSaveTask t = new ImageSaveTask(data, title, date,
(loc == null) ? null : new Location(loc),
width, height, orientation, exif, resolver, l, pictureFormat);
mMemoryUse += data.length;
if (isQueueFull()) {
onQueueFull();
}
t.execute();
}
最后通过ImageSaveTask异步任务,实现图片的存储,接着看ImageSaveTask中的doInBackground()方法
@Override
protected Uri doInBackground(Void... v) {
if (width == 0 || height == 0) {
// Decode bounds
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, options);
width = options.outWidth;
height = options.outHeight;
}
return Storage.addImage(
resolver, title, date, loc, orientation, exif, data, width, height, pictureFormat);
}
最后调用Storage.addImage()方法
// Save the image with a given mimeType and add it the MediaStore.
public static Uri addImage(ContentResolver resolver, String title, long date,
Location location, int orientation, ExifInterface exif, byte[] jpeg, int width,
int height, String mimeType) {
String path = generateFilepath(title, mimeType);
int size = writeFile(path, jpeg, exif, mimeType); //存文件
// Try to get the real image size after add exif.
File f = new File(path);
if (f.exists() && f.isFile()) {
size = (int) f.length();
}
return addImage(resolver, title, date, location, orientation,
size, path, width, height, mimeType);//存数据库
}
存完之后调用ImageSaveTask中的onPostExecute()方法,通知图片已经存完。
@Override
protected void onPostExecute(Uri uri) {
if (listener != null) listener.onMediaSaved(uri);
boolean previouslyFull = isQueueFull();
mMemoryUse -= data.length;
if (isQueueFull() != previouslyFull) onQueueAvailable();
}
以上是对拍照流程的简单分析,只是大致做了梳理,有很多细节为涉及,以后再慢慢讨论。