Android 骁龙Camera拍照流程梳理

本文以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();
        }

以上是对拍照流程的简单分析,只是大致做了梳理,有很多细节为涉及,以后再慢慢讨论。
 

猜你喜欢

转载自blog.csdn.net/pshiping2014/article/details/82178261