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、打開相機
- 打開相機之前,我們首先要獲取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都是通過創建請求會話的方式進行調用的,具體說來:
- 調用mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)方法創建CaptureRequest,調用
- 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、拍照
拍照具體來說分爲三步:
- 對焦
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();
}
}
}
};
- 拍照
我們定義了一個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");
}
}
- 取消對焦
拍完照片後,我們還要解鎖相機焦點,讓相機恢復到預覽狀態。
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、結束視頻錄製
結束視頻錄製主要也是關閉會話以及釋放一些資源,具體說來:
- 關閉預覽會話
- 停止mediaRecorder
- 釋放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實踐的相關內容