Android 使用 Camera2 完成預覽和拍照


Android API 21新增了Camera2,這與之前的camera架構完全不同,使用起來也比較復雜,但是功能變得很強大。

在講解開啟預覽之前,首先需要了解camera2的幾個比較重要的類:

  • CameraManager: 管理手機上的所有攝像頭設備,它的作用主要是獲取攝像頭列表和打開指定的攝像頭
  • CameraDevice: 具體的攝像頭設備,它有一系列參數(預覽尺寸、拍照尺寸等),可以通過CameraManager的getCameraCharacteristics()方法獲取。它的作用主要是創建CameraCaptureSession和CaptureRequest
  • CameraCaptureSession: 相機捕獲會話,用於處理拍照和預覽的工作(很重要)
  • CaptureRequest: 捕獲請求,定義輸出緩沖區以及顯示界面(TextureView或SurfaceView)等

下面梳理一下使用Camera2進行預覽和拍照的主要流程:

1、定義TextureView作為預覽界面

在布局文件中加入TextureView控件,然后實現其監聽事件

textureView = (TextureView) findViewById(R.id.textureView);

然后我們可以在OnResume()方法中設置監聽SurefaceTexture的事件

textureView.setSurfaceTextureListener(textureListener);

當SurefaceTexture准備好后會回調SurfaceTextureListener 的onSurfaceTextureAvailable()方法:

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

2、設置相機參數

為了更好地預覽,我們根據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);
            //根據TextureView的尺寸設置預覽尺寸
            mPreviewSize = getOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height);
            mCameraId = cameraId;
            break;
        }
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

3、開啟相機

Camera2中打開相機也需要通過CameraManager類:

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

實現StateCallback 接口,當相機打開后會回調onOpened方法,在這個方法里面開啟預覽

private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
    @Override
    public void onOpened(CameraDevice camera) {
        mCameraDevice = camera;
        //開啟預覽
        startPreview();
    }
}

4、開啟相機預覽

我們使用TextureView顯示相機預覽數據,Camera2的預覽和拍照數據都是使用CameraCaptureSession會話來請求的

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

5、實現PreviewCallback

Camera2沒有提供Camera中的PreviewCallback,那么我們如何實現預覽幀數據呢?在Camera2中提供了CameraCaptureSession.CaptureCallback:

private CameraCaptureSession.CaptureCallback mPreviewCaptureCallback = new CameraCaptureSession.CaptureCallback() {
    @Override
    public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {

    }

    @Override
    public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) {

    }
};

6、實現拍照操作

Camera2拍照是通過ImageReader來實現的,首先先做些准備工作,比如設置拍照參數,如方向、尺寸等

private static final SparseIntArray ORIENTATION = new SparseIntArray();
    static {
        ORIENTATION.append(Surface.ROTATION_0, 90);
        ORIENTATION.append(Surface.ROTATION_90, 0);
        ORIENTATION.append(Surface.ROTATION_180, 270);
        ORIENTATION.append(Surface.ROTATION_270, 180);
    }

創建一個ImageReader,並監聽它的事件:

private void setupImageReader() {
    //前三個參數分別是需要的尺寸和格式,最后一個參數代表每次最多獲取幾幀數據
    mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.JPEG, 1);
    //監聽ImageReader的事件,當有圖像流數據可用時會回調onImageAvailable方法,它的參數就是預覽幀數據,可以對這幀數據進行處理
    mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
        @Override
        public void onImageAvailable(ImageReader reader) {
            Log.i(TAG, "Image Available!");
            Image image = reader.acquireLatestImage();
            // 開啟線程異步保存圖片
            new Thread(new ImageSaver(image)).start();
        }
    }, null);
}

注意:一定要調用reader.acquireLatestImage()和close()方法,否則預覽畫面就會卡住.

創建保存圖片的線程

public static class ImageSaver implements Runnable {
        private Image mImage;
        public ImageSaver(Image image) {
            mImage = image;
        }
        @Override
        public void run() {
            ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
            byte[] data = new byte[buffer.remaining()];
            buffer.get(data);
            mImageFile = new File(Environment.getExternalStorageDirectory() + "/DCIM/myPicture.jpg");
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream(mImageFile);
                fos.write(data, 0 ,data.length);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                mImageFile = null;
                if (fos != null) {
                    try {
                        fos.close();
                        fos = null;
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

現在准備工作做好了,還需要響應點擊拍照事件,我們設置點擊拍照按鈕調用capture()方法,capture()方法即實現拍照:

private void capture() {
    try {
        //首先我們創建請求拍照的CaptureRequest
        final CaptureRequest.Builder mCaptureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
        //獲取屏幕方向
        int rotation = getWindowManager().getDefaultDisplay().getRotation();

        mCaptureBuilder.addTarget(mPreviewSurface);
        mCaptureBuilder.addTarget(mImageReader.getSurface());

        //設置拍照方向
        mCaptureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));

        //停止預覽
        mCaptureSession.stopRepeating();

        //開始拍照,然后回調上面的接口重啟預覽,因為mCaptureBuilder設置ImageReader作為target,所以會自動回調ImageReader的onImageAvailable()方法保存圖片
        CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {

            @Override
            public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                repeatPreview();
            }
        };

        mCaptureSession.capture(mCaptureBuilder.build(), captureCallback, null);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

 

示例代碼:https://github.com/renhui/RHCamera2

Google官方Demo:https://github.com/googlearchive/android-Camera2Basic

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM