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

以上是對拍照流程的簡單分析,只是大致做了梳理,有很多細節爲涉及,以後再慢慢討論。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章