從5.0開始(API Level 21),可以完全控制安卓設備相機的新api Camera2(android.hardware.Camera2)被引入了進來。在以前的Camera api(android.hardware.Camera)中,對相機的手動控制需要更改系統才能實現,而且api看着方便,但是不好管理。不過老的Camera API在5.0上已經過時(依然兼容),如今Android推薦使用Camera2採集視頻,藉着寫這篇記錄的過程,熟悉和理解Camera2流程。
專用名詞
YUV
一種顏色編碼的方法,在舊Camera API 常用的是NV21和YV12,可以轉成RGB編碼。
//獲取Camera2 支持的顏色編碼
StreamConfigurationMap map = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
map.getOutputFormats();
CameraManager
Camera2中負責管理、查詢攝像頭信息、打開可用的攝像頭,這也是Camera2 API2與api1的區別之一:
- 可以通過調用Context.getSystemService(java.lang.String)方法來獲取一個CameraManager的實例;
- cameraId 通過 getCameraIdList() 枚舉得到,代表選擇使用哪個攝像頭;
- 設備信息通過 CameraCharacteristics getCameraCharacteristics(String cameraId) 可拿到;
- 打開攝像頭 openCamera(String cameraId, CameraManager.StateCallback callback, Handler handler),StateCallback是接收設備狀態的更新的回調,比如後面的cameraDevice就是通過stateCallback的onOpen()回調中拿到,Handler 表示打開攝像頭的工作在具體哪個handler的looper中,也就是在哪個線程中執行,若爲null則在當前線程;
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
CameraDevice
具體的攝像頭,提供一組屬性信息,描述硬件設備以及設備的可用設置和參數。
- CameraDevice是在CameraManager Open Camera後,通過CameraDevice.StateCallback的回調中拿到的,是個異步的過程;
- createCaptureRequest() 創建CaptureRequest.Builder,CaptureRequest.Builder負責創建各種捕獲圖像的請求 CaptureRequest;
- createCaptureSession() 負責創建捕獲圖像的會話CameraCaptureSession;
manager.openCamera(mCameraId, stateCallback, mHandler);//打開Camera
/**攝像頭狀態的監聽*/
private CameraDevice.StateCallback stateCallback = new CameraDevice. StateCallback()
{
// 攝像頭被打開時觸發該方法
@Override
public void onOpened(CameraDevice cameraDevice){
CameraDemoActivity.this.cameraDevice = cameraDevice;
// 開始預覽
takePreview();
}
// 攝像頭斷開連接時觸發該方法
@Override
public void onDisconnected(CameraDevice cameraDevice)
{
CameraDemoActivity.this.cameraDevice.close();
CameraDemoActivity.this.cameraDevice = null;
}
// 打開攝像頭出現錯誤時觸發該方法
@Override
public void onError(CameraDevice cameraDevice, int error)
{
cameraDevice.close();
}
};
CaptureRequest
一次捕獲請求,通過CaptureRequest.Builder的build()創建,其實請求參數也是通過Buider來設置:
CaptureRequest.Builder常用的方法:
- addTarget(Surface outputTarget) 將surface添加到輸出列表中,纔可以顯示在SurfaceView、TextureView或者輸出到ImageReader中;
- set(Key< T> key, T value) 設置其他屬性;
//創建預覽請求
mCaptureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
// 設置自動對焦模式
mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, 3);
//設置Surface作爲預覽數據的顯示界面
mCaptureRequestBuilder.addTarget(mSurface);
此處用一段設置閃光燈的代碼進行說明:
private void openFlash(){
mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
//設置反覆捕獲數據的請求,這樣預覽界面就會一直有數據顯示
try {
mPreviewSession.setRepeatingRequest(mCaptureRequest, null, mHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
CameraCaptureSession
捕獲的會話Session,預覽、拍照,都由該它進行控制的。
- cameraCaptureSession是通過CameraDevice的 createCaptureSession(List< Surface>, CameraCaptureSession.StateCallback, Handler) 創建;
- 拍照 capture(CaptureRequest, CameraCaptureSession.CaptureCallback, Handler);
- 預覽 setRepeatingRequest(CaptureRequest, CameraCaptureSession.CaptureCallback, Handler);
//創建相機捕獲會話,第一個參數是捕獲數據的輸出Surface列表,第二個參數是CameraCaptureSession的狀態回調接口,當它創建好後會回調onConfigured方法,第三個參數用來確定Callback在哪個線程執行,爲null的話就在當前線程執行
cameraDevice.createCaptureSession(Arrays.asList(mSurface,imageReader.getSurface()),new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession session) {
Log.d("onConfigured","onConfigured");
try {
//開始預覽
mCaptureRequest = mCaptureRequestBuilder.build();
mPreviewSession = session;
//設置反覆捕獲數據的請求,這樣預覽界面就會一直有數據顯示
mPreviewSession.setRepeatingRequest(mCaptureRequest, null, mHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(CameraCaptureSession session) {
Log.d("onConfigureFailed","onConfigureFailed");
}
}, null);
在寫代碼的時候發現上面幾個callback弄不清楚
- open Camera中的CameraManager.StateCallback 是 camera 創建過程中狀態回調;
- createCaptureSession中的CameraCaptureSession.StateCallback 是session創建過程中的狀態回調;
- capture 或 setRepeatingRequest的CameraCaptureSession.CaptureCallback 是在預覽或者拍照request請求之後的回調;
發現還有一個Handle一直跟着走,究竟是什麼東西呢?
//很多過程都變成了異步的了,所以這裏需要一個子線程的looper
private void initLooper() {
mThreadHandler = new HandlerThread("CAMERA2");
mThreadHandler.start();
mHandler = new Handler(mThreadHandler.getLooper());
}
TextureView預覽
SurfaceTexture mSurfaceTexture = surfaceView.getSurfaceTexture();
//設置TextureView的緩衝區大小
mSurfaceTexture.setDefaultBufferSize(1920, 1080);
//獲取Surface顯示預覽數據
Surface mSurface = new Surface(mSurfaceTexture);
SurfaceView預覽
Surface surface = mSurfaceHolder.getSurface();
CameraCharacteristics
直接上代碼:
/**設置攝像頭的參數*/
private void setCameraCharacteristics(CameraManager manager)
{
try
{
// 獲取指定攝像頭的特性
CameraCharacteristics characteristics
= manager.getCameraCharacteristics(mCameraId);
// 獲取攝像頭支持的配置屬性
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
// 獲取攝像頭支持的最大尺寸
Size largest = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.YUV_420_888)),new CompareSizesByArea());
List<Size> sizeList = Arrays.asList(map.getOutputSizes(ImageFormat.JPEG));
for (Size s:sizeList) {
Log.d(" Camera "," picturesize w " + s.getWidth() + " picturesize h = " + s.getHeight());
}
// 創建一個ImageReader對象,用於獲取攝像頭的圖像數據
imageReader = ImageReader.newInstance(mWidth, mHeight, ImageFormat.JPEG, 2);
//設置獲取圖片的監聽
imageReader.setOnImageAvailableListener(imageAvailableListener,mHandler);
// 獲取最佳的預覽尺寸
previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), mWidth, mHeight, largest);
Log.d(" Camera"," previewSize w " + previewSize.getWidth() + " previewSize h = " + previewSize.getHeight() + " mWidth = " + mWidth + " mHeight = " +mHeight);
}
catch (CameraAccessException e)
{
e.printStackTrace();
}
catch (NullPointerException e)
{
}
}
private static Size chooseOptimalSize(Size[] choices
, int width, int height, Size aspectRatio)
{
// 收集攝像頭支持的大過預覽Surface的分辨率
List<Size> bigEnough = new ArrayList<>();
int w = aspectRatio.getWidth();
int h = aspectRatio.getHeight();
for (Size option : choices)
{
if (option.getHeight() == option.getWidth() * h / w &&
option.getWidth() >= width && option.getHeight() >= height)
{
bigEnough.add(option);
}
}
// 如果找到多個預覽尺寸,獲取其中面積最小的
if (bigEnough.size() > 0)
{
return Collections.min(bigEnough, new CompareSizesByArea());
}
else
{
//沒有合適的預覽尺寸
return choices[0];
}
}
// 爲Size定義一個比較器Comparator
public static class CompareSizesByArea implements Comparator<Size>
{
@Override
public int compare(Size lhs, Size rhs)
{
// 強轉爲long保證不會發生溢出
return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
(long) rhs.getWidth() * rhs.getHeight());
}
}