Android Camera2實踐指南

Camera2 API中主要涉及以下幾個關鍵類:

  • CameraManager:攝像頭管理器,用於打開和關閉系統攝像頭
  • CameraCharacteristics:描述攝像頭的各種特性,我們可以通過CameraManager的getCameraCharacteristics(@NonNull String cameraId)方法來獲取。
  • CameraDevice:描述系統攝像頭,類似於早期的Camera。
  • CameraCaptureSession:Session類,當需要拍照、預覽等功能時,需要先創建該類的實例,然後通過該實例裏的方法進行控制(例如:拍照 capture())。
  • CaptureRequest:描述了一次操作請求,拍照、預覽等操作都需要先傳入CaptureRequest參數,具體的參數控制也是通過CameraRequest的成員變量來設置。
  • CaptureResult:描述拍照完成後的結果。

Camera2拍照流程如下所示:

在這裏插入圖片描述

1、CameraCharacteristics

Camera2與Camera一樣也有cameraId的概念,我們通過mCameraManager.getCameraIdList()來獲取cameraId列表,然後通過mCameraManager.getCameraCharacteristics(id)
獲取每個id對應攝像頭的參數。

關於CameraCharacteristics裏面的參數,主要用到的有以下幾個:

  • LENS_FACING:前置攝像頭(LENS_FACING_FRONT), 後置攝像頭(LENS_FACING_BACK)。
  • SENSOR_ORIENTATION:攝像頭拍照方向。
  • FLASH_INFO_AVAILABLE:是否支持閃光燈。
  • CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL:獲取當前設備支持的相機特性。

注:事實上,在各個廠商的的Android設備上,Camera2的各種特性並不都是可用的,需要通過characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)方法
來根據返回值來獲取支持的級別,具體說來:

  • INFO_SUPPORTED_HARDWARE_LEVEL_FULL:全方位的硬件支持,允許手動控制全高清的攝像、支持連拍模式以及其他新特性。
  • INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED:有限支持,這個需要單獨查詢。
  • INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY:所有設備都會支持,也就是和過時的Camera API支持的特性是一致的。

利用這個INFO_SUPPORTED_HARDWARE_LEVEL參數,我們可以來判斷是使用Camera還是使用Camera2,具體方法如下:

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static boolean hasCamera2(Context mContext) {
    if (mContext == null) return false;
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return false;
    try {
        CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
        String[] idList = manager.getCameraIdList();
        boolean notFull = true;
        if (idList.length == 0) {
            notFull = false;
        } else {
            for (final String str : idList) {
                if (str == null || str.trim().isEmpty()) {
                    notFull = false;
                    break;
                }
                final CameraCharacteristics characteristics = manager.getCameraCharacteristics(str);

                final int supportLevel = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
                if (supportLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
                    notFull = false;
                    break;
                }
            }
        }
        return notFull;
    } catch (Throwable ignore) {
        return false;
    }
}

更多CameraCharacteristics參數,可以參見CameraCharacteristics官方文檔

2、打開相機

  1. 打開相機之前,我們首先要獲取CameraManager,然後獲取相機列表,進而獲取各個攝像頭(主要是前置攝像頭和後置攝像頭)的參數。
 mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
try {
    final String[] ids = mCameraManager.getCameraIdList();
    numberOfCameras = ids.length;
    for (String id : ids) {
        final CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(id);
        final int orientation = characteristics.get(CameraCharacteristics.LENS_FACING);
        if (orientation == CameraCharacteristics.LENS_FACING_FRONT) {
            faceFrontCameraId = id;
            faceFrontCameraOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
            frontCameraCharacteristics = characteristics;
        } else {
            faceBackCameraId = id;
            faceBackCameraOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
            backCameraCharacteristics = characteristics;
        }
    }
} catch (Exception e) {
    Log.e(TAG, "Error during camera initialize");
}

打開相機主要調用的是mCameraManager.openCamera(currentCameraId, stateCallback, backgroundHandler)方法,如你所見,它有三個參數:

  • String cameraId:攝像頭的唯一ID。
  • CameraDevice.StateCallback callback:攝像頭打開的相關回調。
  • Handler handler:StateCallback需要調用的Handler,我們一般可以用當前線程的Handler。
 mCameraManager.openCamera(currentCameraId, stateCallback, backgroundHandler);

上面我們提到了CameraDevice.StateCallback,它是攝像頭打開的一個回調,定義了打開,關閉以及出錯等各種回調方法,我們可以在
這些回調方法裏做對應的操作。

private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
    @Override
    public void onOpened(@NonNull CameraDevice cameraDevice) {
        //獲取CameraDevice
        mcameraDevice = cameraDevice;
    }

    @Override
    public void onDisconnected(@NonNull CameraDevice cameraDevice) {
        //關閉CameraDevice
        cameraDevice.close();

    }

    @Override
    public void onError(@NonNull CameraDevice cameraDevice, int error) {
        //關閉CameraDevice
        cameraDevice.close();
    }
};

3、關閉相機

通過上面的描述,關閉就很簡單了。

//關閉CameraDevice
cameraDevice.close();

4、開啓預覽

Camera2都是通過創建請求會話的方式進行調用的,具體說來:

  1. 調用mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)方法創建CaptureRequest,調用
  2. mCameraDevice.createCaptureSession()方法創建CaptureSession。
CaptureRequest.Builder createCaptureRequest(@RequestTemplate int templateType)

createCaptureRequest()方法裏參數templateType代表了請求類型,請求類型一共分爲六種,分別爲:

  • TEMPLATE_PREVIEW:創建預覽的請求
  • TEMPLATE_STILL_CAPTURE:創建一個適合於靜態圖像捕獲的請求,圖像質量優先於幀速率。
  • TEMPLATE_RECORD:創建視頻錄製的請求
  • TEMPLATE_VIDEO_SNAPSHOT:創建視視頻錄製時截屏的請求
  • TEMPLATE_ZERO_SHUTTER_LAG:創建一個適用於零快門延遲的請求。在不影響預覽幀率的情況下最大化圖像質量。
  • TEMPLATE_MANUAL:創建一個基本捕獲請求,這種請求中所有的自動控制都是禁用的(自動曝光,自動白平衡、自動焦點)。
createCaptureSession(@NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler)

createCaptureSession()方法一共包含三個參數:

  • List outputs:我們需要輸出到的Surface列表。
  • CameraCaptureSession.StateCallback callback:會話狀態相關回調。
  • Handler handler:callback可以有多個(來自不同線程),這個handler用來區別那個callback應該被回調,一般寫當前線程的Handler即可。

關於CameraCaptureSession.StateCallback裏的回調方法:

  • onConfigured(@NonNull CameraCaptureSession session); 攝像頭完成配置,可以處理Capture請求了。
  • onConfigureFailed(@NonNull CameraCaptureSession session); 攝像頭配置失敗
  • onReady(@NonNull CameraCaptureSession session); 攝像頭處於就緒狀態,當前沒有請求需要處理。
  • onActive(@NonNull CameraCaptureSession session); 攝像頭正在處理請求。
  • onClosed(@NonNull CameraCaptureSession session); 會話被關閉
  • onSurfacePrepared(@NonNull CameraCaptureSession session, @NonNull Surface surface); Surface準備就緒

理解了這些東西,創建預覽請求就十分簡單了。

previewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
previewRequestBuilder.addTarget(workingSurface);

//注意這裏除了預覽的Surface,我們還添加了imageReader.getSurface()它就是負責拍照完成後用來獲取數據的
mCameraDevice.createCaptureSession(Arrays.asList(workingSurface, imageReader.getSurface()),
        new CameraCaptureSession.StateCallback() {
            @Override
            public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                cameraCaptureSession.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler);
            }

            @Override
            public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                Log.d(TAG, "Fail while starting preview: ");
            }
        }, null);

可以發現,在onConfigured()裏調用了cameraCaptureSession.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler),這樣我們就可以
持續的進行預覽了。

注:上面我們說了添加了imageReader.getSurface()它就是負責拍照完成後用來獲取數據,具體操作就是爲ImageReader設置一個OnImageAvailableListener,然後在它的onImageAvailable()
方法裏獲取。

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

 };

5、關閉預覽

關閉預覽就是關閉當前預覽的會話,結合上面開啓預覽的內容,具體實現如下:

if (captureSession != null) {
    captureSession.close();
    try {
        captureSession.abortCaptures();
    } catch (Exception ignore) {
    } finally {
        captureSession = null;
    }
}

6、拍照

拍照具體來說分爲三步:

  1. 對焦
try {
    //相機對焦
    previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
    //修改狀態
    previewState = STATE_WAITING_LOCK;
    //發送對焦請求
    captureSession.capture(previewRequestBuilder.build(), captureCallback, backgroundHandler);
} catch (Exception ignore) {
}

我們定義了一個CameraCaptureSession.CaptureCallback來處理對焦請求返回的結果。

private CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {

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

    @Override
    public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                   @NonNull CaptureRequest request,
                                   @NonNull TotalCaptureResult result) {
            //等待對焦
            final 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
                    || CaptureResult.CONTROL_AF_STATE_INACTIVE == afState
                    || CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN == afState) {
                Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                if (aeState == null ||
                        aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
                    previewState = STATE_PICTURE_TAKEN;
                    //對焦完成,進行拍照
                    captureStillPicture();
                } else {
                    runPreCaptureSequence();
                }
            }
    }
};
  1. 拍照

我們定義了一個captureStillPicture()來進行拍照。

private void captureStillPicture() {
    try {
        if (null == mCameraDevice) {
            return;
        }
        
        //構建用來拍照的CaptureRequest
        final CaptureRequest.Builder captureBuilder =
                mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
        captureBuilder.addTarget(imageReader.getSurface());

        //使用相同的AR和AF模式作爲預覽
        captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
        //設置方向
        captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getPhotoOrientation(mCameraConfigProvider.getSensorPosition()));

        //創建會話
        CameraCaptureSession.CaptureCallback CaptureCallback = new CameraCaptureSession.CaptureCallback() {
            @Override
            public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                           @NonNull CaptureRequest request,
                                           @NonNull TotalCaptureResult result) {
                Log.d(TAG, "onCaptureCompleted: ");
            }
        };
        //停止連續取景
        captureSession.stopRepeating();
        //捕獲照片
        captureSession.capture(captureBuilder.build(), CaptureCallback, null);

    } catch (CameraAccessException e) {
        Log.e(TAG, "Error during capturing picture");
    }
}
  1. 取消對焦

拍完照片後,我們還要解鎖相機焦點,讓相機恢復到預覽狀態。

try {
    //重置自動對焦
    previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
    captureSession.capture(previewRequestBuilder.build(), captureCallback, backgroundHandler);
    //相機恢復正常的預覽狀態
    previewState = STATE_PREVIEW;
    //打開連續取景模式
    captureSession.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler);
} catch (Exception e) {
    Log.e(TAG, "Error during focus unlocking");
}

7、開始視頻錄製


//先關閉預覽,因爲需要添加一個預覽輸出的Surface,也就是mediaRecorder.getSurface()
closePreviewSession();

//初始化MediaRecorder,設置相關參數
if (preparemediaRecorder()) {

    final SurfaceTexture texture = Camera2Manager.this.texture;
    texture.setDefaultBufferSize(videoSize.getWidth(), videoSize.getHeight());

    try {
        //構建視頻錄製aptureRequest
        previewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
        final List<Surface> surfaces = new ArrayList<>();

        //設置預覽Surface
        final Surface previewSurface = workingSurface;
        surfaces.add(previewSurface);
        previewRequestBuilder.addTarget(previewSurface);

        //設置預覽輸出Surface
        workingSurface = mediaRecorder.getSurface();
        surfaces.add(workingSurface);
        previewRequestBuilder.addTarget(workingSurface);

        mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
            @Override
            public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                captureSession = cameraCaptureSession;

                previewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
                try {
                    //持續發送Capture請求,實現實時預覽。
                    captureSession.setRepeatingRequest(previewRequestBuilder.build(), null, backgroundHandler);
                } catch (Exception e) {
                }

                try {
                    //開始錄像
                    mediaRecorder.start();
                } catch (Exception ignore) {
                    Log.e(TAG, "mediaRecorder.start(): ", ignore);
                }

                isVideoRecording = true;

                uiHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        cameraVideoListener.onVideoRecordStarted(videoSize);
                    }
                });
            }

            @Override
            public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                Log.d(TAG, "onConfigureFailed");
            }
        }, backgroundHandler);
    } catch (Exception e) {
        Log.e(TAG, "startVideoRecord: ", e);
    }
}

關於MediaRecorder上面講Camera的時候我們就已經說過,這裏不再贅述。

以上便是視頻錄製的全部內容,就是簡單的API使用,還是比較簡單的。

8、結束視頻錄製

結束視頻錄製主要也是關閉會話以及釋放一些資源,具體說來:

  1. 關閉預覽會話
  2. 停止mediaRecorder
  3. 釋放mediaRecorder
//關閉預覽會話
if (captureSession != null) {
    captureSession.close();
    try {
        captureSession.abortCaptures();
    } catch (Exception ignore) {
    } finally {
        captureSession = null;
    }
}

//停止mediaRecorder
if (mediaRecorder != null) {
    try {
        mediaRecorder.stop();
    } catch (Exception ignore) {
    }
}

//釋放mediaRecorder
try {
    if (mediaRecorder != null) {
        mediaRecorder.reset();
        mediaRecorder.release();
    }
} catch (Exception ignore) {

} finally {
    mediaRecorder = null;
}

以上便是android5.0之後Camera2實踐的相關內容

發佈了13 篇原創文章 · 獲贊 2 · 訪問量 7075
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章