Android Camera2 使用總結

最近在作自定義相機相關的項目,網上查了資料都是有關android.hardware.Camera的資料,開始使用的才發現這個類已經廢棄了。Android 5.0(21)以後android.hardware.Camera就被廢棄了,取而代之的是全新的android.hardware.Camera2Android 5.0對拍照API進行了全新的設計,新增了全新設計的Camera v2 API,這些API不只大幅提升了Android系統拍照的功能,還能支持RAW照片輸出,甚至容許程序調整相機的對焦模式、曝光模式、快門等。html

demo 地址https://github.com/Hemumu/WallpaperDemoandroid

Camera2主要的類說明git

  • CameraManager:攝像頭管理器。這是一個全新的系統管理器,專門用於檢測系統攝像頭、打開系統攝像頭。除此以外,調用CameraManagergetCameraCharacteristics(String)方法便可獲取指定攝像頭的相關特性。
  • CameraCharacteristics:攝像頭特性。該對象經過CameraManager來獲取,用於描述特定攝像頭所支持的各類特性。
  • CameraDevice:表明系統攝像頭。該類的功能相似於早期的Camera類。
  • CameraCaptureSession:這是一個很是重要的API,當程序須要預覽、拍照時,都須要先經過該類的實例建立Session。並且無論預覽仍是拍照,也都是由該對象的方法進行控制的,其中控制預覽的方法爲setRepeatingRequest();控制拍照的方法爲capture()
  • CameraRequestCameraRequest.Builder:當程序調用setRepeatingRequest()方法進行預覽時,或調用capture()方法進行拍照時,都須要傳入CameraRequest參數。CameraRequest表明了一次捕獲請求,用於描述捕獲圖片的各類參數設置,好比對焦模式、曝光模式……總之,程序須要對照片所作的各類控制,都經過CameraRequest參數進行設置。CameraRequest.Builder則負責生成CameraRequest對象。

開啓相機預覽github

開啓相機請必定添加相關的相機權限,判斷6.0之後添加動態權限的獲取。若是相機預覽出現黑屏多半就是由於沒有相機權限而致使的微信

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />

首頁咱們要設置相機相關的參數網絡

CameraManager manager = (CameraManager) ctx.getSystemService(Context.CAMERA_SERVICE);
        try {
            //獲取可用攝像頭列表
            for (String cameraId : manager.getCameraIdList()) {
                //獲取相機的相關參數
                CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
                // 不使用前置攝像頭。
                Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
                if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
                    continue;
                }
                StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                if (map == null) {
                    continue;
                }
                // 檢查閃光燈是否支持。
                Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
                mFlashSupported = available == null ? false : available;
                mCameraId = cameraId;
                Log.e(TAG," 相機可用 ");
                return;
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        } catch (NullPointerException e) {
            //不支持Camera2API
        }
    }

經過getSystemService(Context.CAMERA_SERVICE);拿到了CameraManager 返回當前可用的相機列表,在這裏你能夠選擇使用前置仍是後置攝像頭。CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);能夠拿到當前相機的相關參數,在這裏你能夠進行相關的參數檢查,例如檢查閃光燈是否支持等。在這裏咱們拿到當前相機的cameraId後面使用。session

拿到cameraId咱們就能夠調用CameraManageropenCamera(String cameraId, CameraDevice.StateCallback callback, Handler handler)打開相機了ide

CameraManager manager = (CameraManager) ctx.getSystemService(Context.CAMERA_SERVICE);
        try {
            //打開相機預覽
            manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
        }

添加 CameraDevice.StateCallback 監聽post

咱們須要對相機狀態就行監聽,以便在相機狀態發生改變的時候作相應的操做。openCamera(String cameraId, CameraDevice.StateCallback callback, Handler handler)CameraDevice.StateCallback就是對相機的狀態改變的Callback學習

private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

        @Override
        public void onOpened(@NonNull CameraDevice cameraDevice) {
            mCameraDevice = cameraDevice;
            //建立CameraPreviewSession
            createCameraPreviewSession();
        }

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

        @Override
        public void onError(@NonNull CameraDevice cameraDevice, int error) {
            cameraDevice.close();
            mCameraDevice = null;
        }

    };

建立 CameraCaptureSession

onOpened()中咱們能夠拿到CameraDevice對象,在相機打開後須要建立CameraCaptureSession
CameraCaptureSession是什麼呢?因爲Camera2是一套全新的API,因此它引用了管道的概念將安卓設備和攝像頭之間聯通起來,系統向攝像頭髮送 Capture 請求,而攝像頭會返回 CameraMetadata。這一切創建在一個叫做 CameraCaptureSession的會話中。以下圖:

 

2086682-e68d187e1240bfc5.png


圖片來自http://wiki.jikexueyuan.com/project/android-actual-combat-skills/android-hardware-camera2-operating-guide.html

 

這裏咱們須要預覽相機的內容就須要建立CameraCaptureSession向相機發送Capture請求預覽相機內容

/**
     * 爲相機預覽建立新的CameraCaptureSession
     */
    private void createCameraPreviewSession() {


        try {
            //設置了一個具備輸出Surface的CaptureRequest.Builder。
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            mPreviewRequestBuilder.addTarget(mSurfaceHolder.getSurface());
            //建立一個CameraCaptureSession來進行相機預覽。
            mCameraDevice.createCaptureSession(Arrays.asList(mSurfaceHolder.getSurface()),
                    new CameraCaptureSession.StateCallback() {

                        @Override
                        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                            // 相機已經關閉
                            if (null == mCameraDevice) {
                                return;
                            }
                            // 會話準備好後,咱們開始顯示預覽
                            mCaptureSession = cameraCaptureSession;
                            try {
                                // 自動對焦應
                                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                                        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                                // 閃光燈
                                setAutoFlash(mPreviewRequestBuilder);
                                // 開啓相機預覽並添加事件
                                mPreviewRequest = mPreviewRequestBuilder.build();
                                //發送請求
                                mCaptureSession.setRepeatingRequest(mPreviewRequest,
                                        null, mBackgroundHandler);
                                Log.e(TAG," 開啓相機預覽並添加事件");
                            } catch (CameraAccessException e) {
                                e.printStackTrace();
                            }
                        }

                        @Override
                        public void onConfigureFailed(
                                @NonNull CameraCaptureSession cameraCaptureSession) {
                            Log.e(TAG," onConfigureFailed 開啓預覽失敗");
                        }
                    }, null);
        } catch (CameraAccessException e) {
            Log.e(TAG," CameraAccessException 開啓預覽失敗");
            e.printStackTrace();
        }
    }

首先咱們建立了一個CaptureRequest 上面說過了咱們須要跟相機通訊只有經過CameraCaptureSession。而要和CameraCaptureSession通訊就是發送請求。這裏咱們至關於在建立請求的一些參數。
createCaptureRequest(int); 也有不少參數可選。這裏咱們發送的是CameraDevice.TEMPLATE_PREVIEW也就是告訴相機咱們只須要預覽。更多參數以下

詳細信息能夠參考官網的API文檔。

調用建立方法createCaptureSession(List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler handler) 第一參數就是咱們須要輸出到的Surface列表,這裏咱們能夠輸出到一個SurfaceView中或者TextureView中。第二參數是對建立過程的一個回調方法,當onConfigured回調的時候說明CameraCaptureSession建立成功了。如今咱們能夠向CameraCaptureSession發送前面建立的好的預覽相機請求了。調用mCaptureSession.setRepeatingRequest(mPreviewRequest,null, mBackgroundHandler); 這樣咱們就開啓的相機的預覽,在剛纔添加的輸出Surface對應的控件中咱們能夠看到攝像頭的預覽內容了。

拍照

當咱們須要拍照而且獲得相應的照片數據的時候和開啓相機預覽相同的操做,咱們只須要向CameraCaptureSession發送咱們建立好的請求就行,就像咱們請求網絡數據同樣,封裝好參數直接告訴CameraCaptureSession須要作什麼由它去和相機創建通訊並執行相應的操做。

對焦

/**
     * 將焦點鎖定爲靜態圖像捕獲的第一步。(對焦)
     */
    private void lockFocus() {
        try {
            // 相機對焦
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
                    CameraMetadata.CONTROL_AF_TRIGGER_START);
            // 修改狀態
            mState = STATE_WAITING_LOCK;
            mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
                    mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

CameraCaptureSession發送對焦請求,而且對對焦是否成功進行監聽,在mCaptureCallback中對回調進行處理

/**
     * 處理與JPEG捕獲有關的事件
     */
    private CameraCaptureSession.CaptureCallback mCaptureCallback
            = new CameraCaptureSession.CaptureCallback() {

        //處理
        private void process(CaptureResult result) {
            switch (mState) {
                case STATE_PREVIEW: {
                    //預覽狀態
                    break;
                }

                case STATE_WAITING_LOCK: {
                    //等待對焦
                    Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
                    if (afState == null) {
                        captureStillPicture();
                    } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState ||
                            CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {
                        // CONTROL_AE_STATE can be null on some devices
                        Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                        if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
                            mState = STATE_PICTURE_TAKEN;
                            //對焦完成 
                            captureStillPicture();  
                        } else {
                            runPrecaptureSequence();
                        }
                    }
                    break;
                }
                
                
                }
            }
        }

拍攝圖片

對焦完成後咱們就能夠向CameraCaptureSession發送請求能夠拍照了

/**
     * 
     * 拍攝靜態圖片。
     */
    private void captureStillPicture() {
        try {
            if ( null == mCameraDevice) {
                return;
            }
            // 這是用來拍攝照片的CaptureRequest.Builder。
            final CaptureRequest.Builder captureBuilder =
                    mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            captureBuilder.addTarget(mImageReader.getSurface());

            // 使用相同的AE和AF模式做爲預覽。
            captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                    CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            setAutoFlash(captureBuilder);
            // 方向
            int rotation = this.getWindowManager().getDefaultDisplay().getRotation();
            captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));

            CameraCaptureSession.CaptureCallback CaptureCallback
                    = new CameraCaptureSession.CaptureCallback() {
                @Override
                public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                               @NonNull CaptureRequest request,
                                               @NonNull TotalCaptureResult result) {
                    showToast("Saved: " + mFile);
                    Log.d(TAG, mFile.toString());
                    unlockFocus();
                }
            };
            //中止連續取景
            mCaptureSession.stopRepeating();
            //捕獲圖片
            mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

相信看到這裏代碼已經不復雜了,組裝好咱們的請求而後用CameraCaptureSession發送這個請求就能夠了。這裏須要注意的是咱們怎麼拿到圖片數據呢? 這裏要說回在建立CameraCaptureSession時參數不是有一個輸出的Surface列表麼,在列表中添加一個ImageReaderSurface用戶獲取圖片數據

mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),null).....

ImageReader中對圖片獲取就行監聽

mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);

private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
            = new ImageReader.OnImageAvailableListener() {

        @Override
        public void onImageAvailable(ImageReader reader) {
            //當圖片可獲得的時候獲取圖片並保存
            mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
        }

    };

調用reader.acquireNextImage()咱們就能夠拿到當前的圖片數據了。拍完後咱們須要解鎖焦點讓相機回到預覽狀態,一樣的咱們發送請求就能夠了

/**
     * 解鎖焦點
     */
    private void unlockFocus() {
        try {
            // 重置自動對焦
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
                    CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
            setAutoFlash(mPreviewRequestBuilder);
            mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
                    mBackgroundHandler);
            // 將相機恢復正常的預覽狀態。
            mState = STATE_PREVIEW;
            // 打開連續取景模式
            mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback,
                    mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

效果

GIF.gif

以前看了鴻神推送的Android 仿火螢視頻桌面 神奇的LiveWallPaper 一文中提到了用相機來作壁紙也就是透明屏幕,項目地址https://github.com/songixan/Wallpaper 查看源碼發現仍是用的舊的CameraAPI,因此我在Demo中用Camera2API作了透明屏幕,有興趣的能夠去看下。 ** PS:後來在同事的小米2S(5.1.1)中測試發現出錯了,初步猜想是分辨率的緣由,目前正在解決中。有問題你們能夠私信我 謝謝~**

到此Camera2的學習就結束了,一樣的若是你想用相機就行拍攝視頻也是如此,用CameraCaptureSession發送相應的請求了就能夠了,你們有興趣能夠作一作視頻的拍攝。如今作微信的小10秒小視頻拍攝也很簡單了,思路很簡單當用戶按下拍攝按鈕用CameraCaptureSession發送拍攝視頻的請求,鬆開手指的時候相機恢復到預覽狀態。so easy~ 你們有興趣能夠嘗試下。

拍攝視頻的Demohttps://github.com/googlesamples/android-Camera2Video

Tinks android.hardware.camera2 使用指南

相關文章
相關標籤/搜索