天天看點

android Camera2 API使用詳解

原文: android Camera2 API使用詳解 由于最近需要使用相機拍照等功能,鑒于老舊的相機API問題多多,而且新的裝置都是基于安卓5.0以上的,于是本人決定研究一下安卓5.0新引入的Camera2 API 來實作  Camera2API位址 首先我們來熟悉一下官方給的這幾個圖:

android Camera2 API使用詳解

這裡引用了管道的概念将安卓裝置和攝像頭之間聯通起來,系統向攝像頭發送

Capture 請求,而攝像頭會傳回 CameraMetadata。這一切建立在一個叫作 CameraCaptureSession 的會話中。

android Camera2 API使用詳解

其中 CameraManager 是那個站在高處統管所有攝像投裝置(CameraDevice)的管理者,而每個 CameraDevice 自己會負責建立 CameraCaptureSession 以及建立 CaptureRequest。CameraCharacteristics 是 CameraDevice 的屬性描述類,非要做個對比的話,那麼它與原來的 CameraInfo 有相似性。 類圖中有着三個重要的 callback,雖然這增加了閱讀代碼的難度,但是你必須要習慣,因為這是新包的風格。其中 CameraCaptureSession.CaptureCallback

将處理預覽和拍照圖檔的工作,需要重點對待。這些類是如何互相配合的?下面是簡單的流程圖。

android Camera2 API使用詳解

有了這三張圖,那麼接下來就好了了解了,我們按照拍照流程的訓示,來吧整個過程走一遍

1首先定義一個SufaceView 用來實作預覽照片用

surfaceView=(SurfaceView)

findViewById(R.id.surfaceView);

surfaceHolder.addCallback(new SurfaceHolder.Callback() {
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        initCamera();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

    }
});      
其中initCamera2()方法是用來初始化相機的方法      
2擷取Camera ID,該ID是用來打開相機的關鍵,一般後置攝像頭是0,前置攝像頭是1,這裡我們選擇後置攝像頭做詳解      
mCameraID = "" + CameraCharacteristics.LENS_FACING_BACK;//後攝像頭      
3通過Camera ID 來打開攝像頭,這裡我們需要使用CamerManager,這是類是一個管理服務類,值得注意的是,打開攝像頭是一個相當複雜的過程,不能直接在主線程中直接執行,其核心代碼為:      
HandlerThread handlerThread=new HandlerThread("Camera2");
handlerThread.start();
childHandler=new Handler(handlerThread.getLooper());
mainHandler=new Handler(getMainLooper());      
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { //可以在這裡處理拍照得到的臨時照片 例如,寫入本地
    @Override
    public void onImageAvailable(ImageReader reader) {
        mCameraDevice.close();
        surfaceView.setVisibility(View.GONE);
        iv_show.setVisibility(View.VISIBLE);
        // 拿到拍照照片資料
        Image image = reader.acquireNextImage();
        ByteBuffer buffer = image.getPlanes()[0].getBuffer();
        byte[] bytes = new byte[buffer.remaining()];
        buffer.get(bytes);//由緩沖區存入位元組數組
        final Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
        if (bitmap != null) {
            iv_show.setImageBitmap(bitmap);
        }
    }
}, mainHandler);      
cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
        return;
    }
    //打開攝像頭
    cameraManager.openCamera(mCameraID, stateCallback, mainHandler);
} catch (CameraAccessException e) {
    e.printStackTrace();
}      

注意打開攝像頭需要權限:

<uses-permission android:name="android.permission.CAMERA"></uses-permission>      
4 開啟相機後有一個回調,stateCallback,該回調是用來傳回相機是否正常打開的狀态的      
private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
    @Override
    public void onOpened(CameraDevice camera) {//打開攝像頭
        mCameraDevice = camera;
        //開啟預覽
        takePreview();
    }

    @Override
    public void onDisconnected(CameraDevice camera) {//關閉攝像頭
        if (null != mCameraDevice) {
            mCameraDevice.close();
            MainActivity.this.mCameraDevice = null;
        }
    }

    @Override
    public void onError(CameraDevice camera, int error) {//發生錯誤
        Toast.makeText(MainActivity.this, "攝像頭開啟失敗", Toast.LENGTH_SHORT).show();
    }
};      
5 相機開啟成功後,執行回調中的onOpen方法,在該方法中,我們實作讓圖像顯示在界面上      
/**
 * 開始預覽
 */
private void takePreview() {
    try {
        // 建立預覽需要的CaptureRequest.Builder
        final CaptureRequest.Builder previewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        // 将SurfaceView的surface作為CaptureRequest.Builder的目标
        previewRequestBuilder.addTarget(surfaceHolder.getSurface());
        // 建立CameraCaptureSession,該對象負責管理處理預覽請求和拍照請求
        mCameraDevice.createCaptureSession(Arrays.asList(surfaceHolder.getSurface(), mImageReader.getSurface()), new CameraCaptureSession.StateCallback() // ③
        {
            @Override
            public void onConfigured(CameraCaptureSession cameraCaptureSession) {
                if (null == mCameraDevice) return;
                // 當攝像頭已經準備好時,開始顯示預覽
                mCameraCaptureSession = cameraCaptureSession;
                try {
                    // 自動對焦
                    previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                    // 打開閃光燈
                    previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
                    // 顯示預覽
                    CaptureRequest previewRequest = previewRequestBuilder.build();
                    mCameraCaptureSession.setRepeatingRequest(previewRequest, null, childHandler);
                } catch (CameraAccessException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
                Toast.makeText(MainActivity.this, "配置失敗", Toast.LENGTH_SHORT).show();
            }
        }, childHandler);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}      
6,預覽完成後,接下來就是拍照了,注意某些手機的攝像頭會和正常手機的攝像頭另類,成像會成180度的倒立像,比如nexus 5x,這時候隻需要設定      
rORIENTATIONS的值來調整角度就可以      
/**
 * 拍照
 */
private void takePicture() {
    if (mCameraDevice == null) return;
    // 建立拍照需要的CaptureRequest.Builder
    final CaptureRequest.Builder captureRequestBuilder;
    try {
        captureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
        // 将imageReader的surface作為CaptureRequest.Builder的目标
        captureRequestBuilder.addTarget(mImageReader.getSurface());
        // 自動對焦
        captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
        // 自動曝光
        captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
        // 擷取手機方向
        int rotation = getWindowManager().getDefaultDisplay().getRotation();
        // 根據裝置方向計算設定照片的方向
        captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation)+rORIENTATIONS);
        //拍照
        CaptureRequest mCaptureRequest = captureRequestBuilder.build();
        mCameraCaptureSession.capture(mCaptureRequest, null, childHandler);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}      

繼續閱讀