1. 程式人生 > >Android Camera模組解析之拍照

Android Camera模組解析之拍照

最近學習Android的camera模組,本文先介紹一下camera2的api,然後給出android camera拍照的例子,講解一下camera 拍照的原因知識,與大家共勉。

  • camera2 介紹
  • android camera拍照功能介紹

一、camera2 介紹



  • Camera api部分:
    frameworks/base/core/java/android/hardware/camera2
  • Camera JNI部分:
    frameworks/base/core/jni/android_hardware_Camera.cpp
    編譯選項在目錄下的Android.bp
    make libandroid_runtime -j1
  • Camera UI庫部分:
    frameworks/av/camera/
    編譯選項在目錄下的Android.bp
    make libcamera_client -j1
  • Camera服務部分:
    frameworks/av/services/camera/libcameraservice/
    編譯選項在目錄下的Android.mk
    make libcameraservice -j1
  • Camera HAL部分:
    hardware/qcom/camera/


android.hardware.camera2開發包給開發者提供了一個操作相機的開發包,是api-21提供的,用於替代之前的Camera操作控類。該軟體包將攝像機裝置建模為管道,它接收捕獲單個幀的輸入請求,根據請求捕獲單個影象,然後輸出一個捕獲結果元資料包,以及一組請求的輸出影象緩衝區。請求按順序處理,多個請求可以立即進行。由於相機裝置是具有多個階段的管道,因此需要在移動中處理多個捕捉請求以便在大多數Android裝置上保持完全幀率。

  • TextureView可用於顯示內容流。這樣的內容流可以例如是視訊或OpenGL場景。內容流可以來自應用程式的程序以及遠端程序。
  • TextureView只能在硬體加速視窗中使用。在軟體中渲染時,TextureView將不會繪製任何內容。
  • TextureView不會建立單獨的視窗,但表現為常規檢視。這一關鍵差異允許TextureView移動,轉換和使用動畫
  • 之後,應用程式需要構建CaptureRequest,在捕獲單個圖片的時候,這些request請求需要定義一些請求的引數。
  • 一旦設定了請求,就可以將其傳遞到活動捕獲會話,以進行一次捕獲或無休止地重複使用。兩種方法還具有接受用作突發捕獲/重複突發的請求列表的變體。重複請求的優先順序低於捕獲,因此在配置重複請求時通過capture()提交的請求將在當前重複(突發)捕獲的任何新例項開始捕獲之前捕獲。
  • 處理完請求後,攝像機裝置將生成一個TotalCaptureResult
    物件,該物件包含有關捕獲時攝像機裝置狀態的資訊以及使用的最終設定。如果需要舍入或解決相互矛盾的引數,這些可能與請求有所不同。相機裝置還將一幀影象資料傳送到請求中包括的每個輸出表面。這些是相對於輸出CaptureResult非同步生成的,有時基本上稍晚。

根據camera2的工作示意圖,畫出下面的camera類關係圖。下面使用camera功能的時候會詳細介紹一下camera2 的api的主要功能。

 

camera2 api關係圖.jpg

二、android camera拍照功能介紹

2.1 設定camera preview預覽頁面

我們開啟android camera一般會出現一個預覽頁面,通過這個預覽頁面,我麼呼叫攝像頭,將前方的景放在這個預覽介面中,然後移動camera,預覽介面會隨之出現變化,實現這個預覽介面的view,一般有兩種選擇,SurfaceView或者TextureView,上面我們也介紹了兩種view之間的區別,本文我們選擇TextureView,因為TextureView設定動畫比較方便,我們移動或者旋轉手機的時候,TextureView也應用相應的旋轉,這樣符合使用者的體驗。
可以通過設定TextureView.SurfaceTextureListener來對TextureView代表的surface監聽。

    private final TextureView.SurfaceTextureListener mSurfaceTextureListener
            = new TextureView.SurfaceTextureListener() {

        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
            openCamera(width, height);
        }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
            configureTransform(width, height);
        }

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
            return true;
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture texture) {
        }

    };

幾個監聽事件監聽當前surface的狀態。onSurfaceTextureAvailable表示當前的surface狀態可用,onSurfaceTextureSizeChanged表示當前的surface大小正在調整。

2.2 開啟相機

    private void openCamera(int width, int height) {
        if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {
            requestCameraPermission();
            return;
        }
        setUpCameraOutputs(width, height);
        configureTransform(width, height);
        Activity activity = getActivity();
        CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
        try {
            if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
                throw new RuntimeException("Time out waiting to lock camera opening.");
            }
            manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
        }
    }
  • 檢查當前camera許可權。
  • 設定相機當前屬性和輸出圖片等等。
  • 設定TextureView 選裝和移動的動畫屬性等等。
  • 使用CameraManager例項開啟相機

2.3 相機開啟設定回撥

在執行manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);方法的時候,傳入了三個引數:

  • mCameraId表示當前攝像頭的標識,我們手機中有好多個攝像頭,最新版的Mate20手機有3個後置攝像頭和1個前置攝像,可以通過manager.getCameraIdList()來獲取當前的cameraId集合。
  • StateCallback是CameraDevice.StateCallback,這是表示相機裝置當前狀態的回撥。
    下面StateCallback的眾多回調錶示當前相機的狀態,相機如果開啟的話應該執行什麼操作,相機如果斷開連線的話應該執行什麼操作等等。
  • 傳入的Handler處理camera當前的訊息。
    private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

        @Override
        public void onOpened(@NonNull CameraDevice cameraDevice) {
            // This method is called when the camera is opened.  We start camera preview here.
            mCameraOpenCloseLock.release();
            mCameraDevice = cameraDevice;
            createCameraPreviewSession();
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice cameraDevice) {
            mCameraOpenCloseLock.release();
            cameraDevice.close();
            mCameraDevice = null;
        }

        @Override
        public void onError(@NonNull CameraDevice cameraDevice, int error) {
            mCameraOpenCloseLock.release();
            cameraDevice.close();
            mCameraDevice = null;
            Activity activity = getActivity();
            if (null != activity) {
                activity.finish();
            }
        }

    };

mCameraOpenCloseLock是一個訊號量,一旦相機在openCamera到真正開啟這段時間,相機必須被獨佔,其他執行緒不能介入處理,不然會出現執行緒錯亂。一旦相機呈現出下一個狀態,就可以釋放這個訊號量了。

private Semaphore mCameraOpenCloseLock = new Semaphore(1);

2.4 建立camera預覽session

    private void createCameraPreviewSession() {
        try {
            SurfaceTexture texture = mTextureView.getSurfaceTexture();
            assert texture != null;

            // We configure the size of default buffer to be the size of camera preview we want.
            texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());

            // This is the output Surface we need to start preview.
            Surface surface = new Surface(texture);

            // We set up a CaptureRequest.Builder with the output Surface.
            mPreviewRequestBuilder
                    = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            mPreviewRequestBuilder.addTarget(surface);

            // Here, we create a CameraCaptureSession for camera preview.
            mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
                    new CameraCaptureSession.StateCallback() {

                        @Override
                        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                            // The camera is already closed
                            if (null == mCameraDevice) {
                                return;
                            }

                            // When the session is ready, we start displaying the preview.
                            mCaptureSession = cameraCaptureSession;
                            try {
                                // Auto focus should be continuous for camera preview.
                                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                                        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                                // Flash is automatically enabled when necessary.
                                setAutoFlash(mPreviewRequestBuilder);

                                // Finally, we start displaying the camera preview.
                                mPreviewRequest = mPreviewRequestBuilder.build();
                                mCaptureSession.setRepeatingRequest(mPreviewRequest,
                                        mCaptureCallback, mBackgroundHandler);
                            } catch (CameraAccessException e) {
                                e.printStackTrace();
                            }
                        }

                        @Override
                        public void onConfigureFailed(
                                @NonNull CameraCaptureSession cameraCaptureSession) {
                            showToast("Failed");
                        }
                    }, null
            );
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
  • 設定SurfaceTexture快取大小。
  • 通過mCameraDevice.createCaptureRequest獲取CaptureResult.Builder物件,這個物件要和之前的Surface繫結,表示當前的capture請求是輸出到這個surface上的。
  • mCameraDevice.createCaptureSession 建立capture session,在CameraCaptureSession.StateCallback的onConfigured回撥函式中,就是對當前camera移動的回撥,一旦移動,會觸發這個回撥,然後在回撥中作出相應的改變。

2.5 拍照

    private void takePicture() {
        lockFocus();
    }

    /**
     * Lock the focus as the first step for a still image capture.
     */
    private void lockFocus() {
        try {
            // This is how to tell the camera to lock focus.
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
                    CameraMetadata.CONTROL_AF_TRIGGER_START);
            // Tell #mCaptureCallback to wait for the lock.
            mState = STATE_WAITING_LOCK;
            mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
                    mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

Capture image的過程主要在 mCaptureSession.capture函式中,下面會從這個函式著手闡釋一下camera capture的原理。

2.6 關閉相機

    private void closeCamera() {
        try {
            mCameraOpenCloseLock.acquire();
            if (null != mCaptureSession) {
                mCaptureSession.close();
                mCaptureSession = null;
            }
            if (null != mCameraDevice) {
                mCameraDevice.close();
                mCameraDevice = null;
            }
            if (null != mImageReader) {
                mImageReader.close();
                mImageReader = null;
            }
        } catch (InterruptedException e) {
            throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
        } finally {
            mCameraOpenCloseLock.release();
        }
    }

主要釋放camera相關的資源,手機中camera的資源具有獨佔性,如果在用完了不釋放的話,會造成別人使用的時候camera被佔用,session不關閉的,會造成嚴重的記憶體洩露。
大家也可以參考具體的原始碼https://github.com/googlesamples/andr