Android 之 Camera2

從Android 5.0開始,Google 引入了一套全新的相機框架 Camera2(android.hardware.camera2)並且廢棄了舊的相機框架 Camera1(android.hardware.Camera)。在API架構方面, Camera2和之前的Camera有很大區別, APP和底層Camera之前可以想象成用管道方式連接,接下來先了解下Camera2的相關類。

CameraManager類:攝像頭的管理類,主要是用於檢測、打開系統的攝像頭;

CameraCharacteristics類:相機的特徵類,例如是否可以支持自動調焦,是否支持閃光燈等等;(一般包含以下幾種常用的參數:LENS_FACING:拿到攝像頭的方向(LENS_FACING_FRONT是前攝像頭,LENS_FACING_BACK是後攝像頭)
SENSOR_ORIENTATION:獲取攝像頭拍照的方向位置
SCALER_AVAILABLE_MAX_DIGITAL_ZOOM:獲取最大的數字調焦值(Zoom的最大值)
FLASH_INFO_AVAILABLE:支不支持閃光燈
LENS_INFO_MINIMUM_FOCUS_DISTANCE:獲取最小的調焦距離)

CameraDevice類:相機設備,類似以往的Camera類,可以使用reateCaptureRequest (int templateType)方法創建CaptureRequest.Builder,一般templateType的常見參數如下:TEMPLATE_PREVIEW :預覽
TEMPLATE_RECORD:拍攝視頻
TEMPLATE_STILL_CAPTURE:拍照
TEMPLATE_VIDEO_SNAPSHOT:創建視視頻錄製時截屏的請求

CameraCaptureSession類:用於創建預覽、拍照的Session類,程序中通過創造一個CameraCaptureSession,在安卓端和相機硬件端建立管道,從而可以獲取拍攝的圖片信息,可以通過它的setRepeatingRequest()方法來控制預覽界面。在創造一個會議時,會回調兩個接口——StateCallback:處理session建立成功和失敗的情況,通常在這裏會進行預覽的一些初始化設置。CaptureCallback:捕獲圖像成功、失敗、進行時等情況的處理;

CameraRequest類:用於控制預覽和拍照參數,例如:對焦模式,曝光模式,zoom參數等等。從圖像傳感器捕獲單個圖像的結果的子集。包含捕獲硬件(傳感器,鏡頭,閃存),處理流水線,控制算法和輸出緩衝區的最終配置的一個子集。

瞭解完了Camera2的相關類,接下來了解一個Camera開發過程中必不可少的控件----TextureView
TextureView是在Android 4.0引入的,主要用於承載顯示數據流的View, 如本地Camera採集的預覽數據流和視頻通話模塊從網絡包裏解出實時視頻數據流。TextureView還有個比較好的特性就是可以當做普通的View控件使用,在佈局、動畫和變換(平移、縮放、旋轉等)中非常方便。

總的來說,就是我們只要抓住一條會話通道,就可以通過這條通道傳送CameraRequest請求預覽、拍照和錄像了。流程大致如下:在調用onCamera方法後會回調CameraDevice.StateCallback這個方法,在這個方法裏面重寫onOpend函數,在onOpend方法中調用createCaptureSession,該方法又回調CameraCaptureSession.StateCallback方法,在該方法中重寫onConfigured方法,設置setRepeatingRequest方法(即開始圖蘭圖像),setRepeatingRequest又會回調CameraCaptureSession.CaptureCallback方法,重寫CameraCaptureSession.CaptureCallback方法中的onCaptureCompleted方法,最後result就是未經過處理的原始數據了。

接下來就來使用Camera2來實現採集手機攝像頭圖像的一個小Demo吧。

在佈局中添加一個TextureView控件,用來採集攝像頭的預覽數據,在打開攝像頭之前,一定要記得申請權限,一定要記得申請權限,一定要記得申請權限,重要的事情說三遍,如果不知道如何動態申請權限的,可以查看我之前的博客Android 6.0動態申請權限
其次是定義一個TextureView作爲一個預覽的界面,然後一定要實現它的監聽事件

        TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
            @Override
            public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                //當SurefaceTexture可用的時候,設置相機參數並打開相機
                setupCamera(width, height);
                configureTransform(width, height);
                openCamera();
            }

            @Override
            public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
                //當SurefaceTexture狀態改變時調用此方法
            }

            @Override
            public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                //當SurefaceTexture銷燬時調用此方法
                return false;
            }

            @Override
            public void onSurfaceTextureUpdated(SurfaceTexture surface) {
                //當SurefaceTexture狀態更新時調用此方法
            }
        };
        textureView.setSurfaceTextureListener(textureListener);

setupCamera這個方法主要是根據TextureView來設定相對於的尺寸大小,Camera2中使用CameraManager來管理攝像頭,代碼如下:

    private void setupCamera(int width, int height) {
        // 獲取攝像頭的管理者CameraManager
        CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
        try {
            // 遍歷所有攝像頭
            for (String cameraId : manager.getCameraIdList()) {
                CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
                // 默認打開後置攝像頭 - 忽略前置攝像頭
                if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT){
                    continue;
                }
                // 獲取StreamConfigurationMap,它是管理攝像頭支持的所有輸出格式和尺寸
                StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                mPreviewSize = getOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height);
                cameraID = cameraId;
                break;
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

接着是打開攝像頭,也是需要用到CameraManager管理類,manager.openCamera()這個方法的第三個參數是指在哪個線程執行,null就是爲主線程

 private void openCamera() {
        CameraManager manager = (CameraManager) getSystemService(CAMERA_SERVICE);
        //檢查權限
        try {
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(MainActivity.this,"請檢查權限是否打開",Toast.LENGTH_LONG).show();
                return;
            }
            //打開相機,第一個參數指示打開哪個攝像頭,第二個參數stateCallback爲相機的狀態回調接口,第三個參數用來確定Callback在哪個線程執行,爲null的話就在當前線程執行
            manager.openCamera(cameraID, stateCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

當相機打開之後會回調接口StateCallback裏面的方法

 //實現StateCallback 接口,當相機打開後會回調onOpened方法
    private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            //打開攝像頭
            mCameraDevice = camera;
            //開啓預覽
            startPreview();
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            //關閉攝像頭
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            //發生錯誤
        }
    };

接着就是攝像頭的圖像預覽了

    private void startPreview() {
        setupImageReader();
        SurfaceTexture mSurfaceTexture = textureView.getSurfaceTexture();
        //設置TextureView的緩衝區大小
        mSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
        //獲取Surface顯示預覽數據
        mPreviewSurface = new Surface(mSurfaceTexture);
        try {
            getPreviewRequestBuilder();
            //創建相機捕獲會話,第一個參數是捕獲數據的輸出Surface列表,第二個參數是CameraCaptureSession的狀態回調接口,當它創建好後會回調onConfigured方法,第三個參數用來確定Callback在哪個線程執行,爲null的話就在當前線程執行
            mCameraDevice.createCaptureSession(Arrays.asList(mPreviewSurface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(CameraCaptureSession session) {
                    mCaptureSession = session;
                    repeatPreview();
                }

                @Override
                public void onConfigureFailed(CameraCaptureSession session) {

                }
            }, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

創建預覽請求的Builder

    // 創建預覽請求的Builder(TEMPLATE_PREVIEW表示預覽請求)
    private void getPreviewRequestBuilder() {
        try {
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        //設置預覽的顯示界面
        mPreviewRequestBuilder.addTarget(mPreviewSurface);
        MeteringRectangle[] meteringRectangles = mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AF_REGIONS);
        if (meteringRectangles != null && meteringRectangles.length > 0) {
            Log.e("LEE", "PreviewRequestBuilder: AF_REGIONS=" + meteringRectangles[0].getRect().toString());
        }
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
    }

設置反覆捕獲數據的請求,這樣預覽界面就會一直有數據顯示

    private void repeatPreview() {
        mPreviewRequestBuilder.setTag(TAG_PREVIEW);
        mPreviewRequest = mPreviewRequestBuilder.build();
        //設置反覆捕獲數據的請求,這樣預覽界面就會一直有數據顯示
        try {
            mCaptureSession.setRepeatingRequest(mPreviewRequest, captureCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

這些就是Camera2中使用到的比較重要的一些方法了,剩下的一些代碼我就不貼出來了,有需要的話可以到github上下載一下源碼就可以跑起來啦Git傳送門

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