Camera2 API 之 SurfaceView、TextureView、CameraManager、CameraDevice詳解

從5.0開始(API Level 21),可以完全控制安卓設備相機的新api Camera2(android.hardware.Camera2)被引入了進來。在以前的Camera api(android.hardware.Camera)中,對相機的手動控制需要更改系統才能實現,而且api看着方便,但是不好管理。不過老的Camera API在5.0上已經過時(依然兼容),如今Android推薦使用Camera2採集視頻,藉着寫這篇記錄的過程,熟悉和理解Camera2流程。

專用名詞

YUV
一種顏色編碼的方法,在舊Camera API 常用的是NV21和YV12,可以轉成RGB編碼。

//獲取Camera2 支持的顏色編碼
StreamConfigurationMap map = characteristics.get( 
                             CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
map.getOutputFormats();

CameraManager
Camera2中負責管理、查詢攝像頭信息、打開可用的攝像頭,這也是Camera2 API2與api1的區別之一:

  • 可以通過調用Context.getSystemService(java.lang.String)方法來獲取一個CameraManager的實例;
  • cameraId 通過 getCameraIdList() 枚舉得到,代表選擇使用哪個攝像頭;
  • 設備信息通過 CameraCharacteristics getCameraCharacteristics(String cameraId) 可拿到;
  • 打開攝像頭 openCamera(String cameraId, CameraManager.StateCallback callback, Handler handler),StateCallback是接收設備狀態的更新的回調,比如後面的cameraDevice就是通過stateCallback的onOpen()回調中拿到,Handler 表示打開攝像頭的工作在具體哪個handler的looper中,也就是在哪個線程中執行,若爲null則在當前線程;
 CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);

CameraDevice
具體的攝像頭,提供一組屬性信息,描述硬件設備以及設備的可用設置和參數。

  1. CameraDevice是在CameraManager Open Camera後,通過CameraDevice.StateCallback的回調中拿到的,是個異步的過程;
  2. createCaptureRequest() 創建CaptureRequest.Builder,CaptureRequest.Builder負責創建各種捕獲圖像的請求 CaptureRequest;
  3. createCaptureSession() 負責創建捕獲圖像的會話CameraCaptureSession;
manager.openCamera(mCameraId, stateCallback, mHandler);//打開Camera
/**攝像頭狀態的監聽*/
    private CameraDevice.StateCallback stateCallback = new CameraDevice. StateCallback()
    {
        // 攝像頭被打開時觸發該方法
        @Override
        public void onOpened(CameraDevice cameraDevice){
            CameraDemoActivity.this.cameraDevice = cameraDevice;
            // 開始預覽
            takePreview();
        }

        // 攝像頭斷開連接時觸發該方法
        @Override
        public void onDisconnected(CameraDevice cameraDevice)
        {
            CameraDemoActivity.this.cameraDevice.close();
            CameraDemoActivity.this.cameraDevice = null;
        }
        // 打開攝像頭出現錯誤時觸發該方法
        @Override
        public void onError(CameraDevice cameraDevice, int error)
        {
            cameraDevice.close();
        }
    };

CaptureRequest

一次捕獲請求,通過CaptureRequest.Builder的build()創建,其實請求參數也是通過Buider來設置:
CaptureRequest.Builder常用的方法:

  • addTarget(Surface outputTarget) 將surface添加到輸出列表中,纔可以顯示在SurfaceView、TextureView或者輸出到ImageReader中;
  • set(Key< T> key, T value) 設置其他屬性;
//創建預覽請求
            mCaptureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            // 設置自動對焦模式
            mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, 3);
            //設置Surface作爲預覽數據的顯示界面
            mCaptureRequestBuilder.addTarget(mSurface);

此處用一段設置閃光燈的代碼進行說明:

 private void openFlash(){
        mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
        mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
        //設置反覆捕獲數據的請求,這樣預覽界面就會一直有數據顯示
        try {
            mPreviewSession.setRepeatingRequest(mCaptureRequest, null, mHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

CameraCaptureSession
捕獲的會話Session,預覽、拍照,都由該它進行控制的。

  • cameraCaptureSession是通過CameraDevice的 createCaptureSession(List< Surface>, CameraCaptureSession.StateCallback, Handler) 創建;
  • 拍照 capture(CaptureRequest, CameraCaptureSession.CaptureCallback, Handler);
  • 預覽 setRepeatingRequest(CaptureRequest, CameraCaptureSession.CaptureCallback, Handler);
//創建相機捕獲會話,第一個參數是捕獲數據的輸出Surface列表,第二個參數是CameraCaptureSession的狀態回調接口,當它創建好後會回調onConfigured方法,第三個參數用來確定Callback在哪個線程執行,爲null的話就在當前線程執行
            cameraDevice.createCaptureSession(Arrays.asList(mSurface,imageReader.getSurface()),new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(CameraCaptureSession session) {
                    Log.d("onConfigured","onConfigured");
                    try {
                        //開始預覽
                        mCaptureRequest = mCaptureRequestBuilder.build();
                        mPreviewSession = session;
                        //設置反覆捕獲數據的請求,這樣預覽界面就會一直有數據顯示
                        mPreviewSession.setRepeatingRequest(mCaptureRequest, null, mHandler);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void onConfigureFailed(CameraCaptureSession session) {
                    Log.d("onConfigureFailed","onConfigureFailed");
                }
            }, null);

在寫代碼的時候發現上面幾個callback弄不清楚

  • open Camera中的CameraManager.StateCallback 是 camera 創建過程中狀態回調;
  • createCaptureSession中的CameraCaptureSession.StateCallback 是session創建過程中的狀態回調;
  • capture 或 setRepeatingRequest的CameraCaptureSession.CaptureCallback 是在預覽或者拍照request請求之後的回調;

發現還有一個Handle一直跟着走,究竟是什麼東西呢?

 //很多過程都變成了異步的了,所以這裏需要一個子線程的looper
    private void initLooper() {
        mThreadHandler = new HandlerThread("CAMERA2");
        mThreadHandler.start();
        mHandler = new Handler(mThreadHandler.getLooper());
    }

TextureView預覽

 SurfaceTexture mSurfaceTexture = surfaceView.getSurfaceTexture();
        //設置TextureView的緩衝區大小
        mSurfaceTexture.setDefaultBufferSize(1920, 1080);
        //獲取Surface顯示預覽數據
        Surface mSurface = new Surface(mSurfaceTexture);

SurfaceView預覽

 Surface surface = mSurfaceHolder.getSurface();

CameraCharacteristics
直接上代碼:

 /**設置攝像頭的參數*/
    private void setCameraCharacteristics(CameraManager manager)
    {
        try
        {
            // 獲取指定攝像頭的特性
            CameraCharacteristics characteristics
                    = manager.getCameraCharacteristics(mCameraId);
            // 獲取攝像頭支持的配置屬性
            StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            // 獲取攝像頭支持的最大尺寸
            Size largest = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.YUV_420_888)),new CompareSizesByArea());
            List<Size> sizeList = Arrays.asList(map.getOutputSizes(ImageFormat.JPEG));
            for (Size s:sizeList) {
                Log.d(" Camera "," picturesize w " + s.getWidth() + " picturesize h = " + s.getHeight());
            }
            // 創建一個ImageReader對象,用於獲取攝像頭的圖像數據
            imageReader = ImageReader.newInstance(mWidth, mHeight, ImageFormat.JPEG, 2);
            //設置獲取圖片的監聽
            imageReader.setOnImageAvailableListener(imageAvailableListener,mHandler);
            // 獲取最佳的預覽尺寸
            previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), mWidth, mHeight, largest);
            Log.d(" Camera"," previewSize w " + previewSize.getWidth() + " previewSize h = " + previewSize.getHeight()  + " mWidth = " + mWidth + " mHeight = " +mHeight);
        }
        catch (CameraAccessException e)
        {
            e.printStackTrace();
        }
        catch (NullPointerException e)
        {
        }
    }
    private static Size chooseOptimalSize(Size[] choices
            , int width, int height, Size aspectRatio)
    {
        // 收集攝像頭支持的大過預覽Surface的分辨率
        List<Size> bigEnough = new ArrayList<>();
        int w = aspectRatio.getWidth();
        int h = aspectRatio.getHeight();
        for (Size option : choices)
        {
            if (option.getHeight() == option.getWidth() * h / w &&
                    option.getWidth() >= width && option.getHeight() >= height)
            {
                bigEnough.add(option);
            }
        }
        // 如果找到多個預覽尺寸,獲取其中面積最小的
        if (bigEnough.size() > 0)
        {
            return Collections.min(bigEnough, new CompareSizesByArea());
        }
        else
        {
            //沒有合適的預覽尺寸
            return choices[0];
        }
    }


    // 爲Size定義一個比較器Comparator
    public static class CompareSizesByArea implements Comparator<Size>
    {
        @Override
        public int compare(Size lhs, Size rhs)
        {
            // 強轉爲long保證不會發生溢出
            return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
                    (long) rhs.getWidth() * rhs.getHeight());
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章