新的相機API也就是Camera2是在Android 5.0引進的。通常情況下,我們都是使用Android舊的相機API,縱然在Android Studio里老是提示已經廢棄,但是只要都能用,也就沒必要單獨為了使用新的API而寫兩套代碼。那為什么要介紹Camera2的使用呢?一切問題的根源都是多樣化的需求引起的,特別是在Android領域,兼容性問題更是層出不窮。經常會碰到,其他手機都可以,怎么就這個不行……
我也是跟大家一樣,碰到了一個跟相機有關的兼容性問題。我們APP在進行活體識別的時候,除了要進行每個frame的檢測同時也要進行當前活體檢測的視頻錄制,使用的都是舊的相機API。在多樣化機型測試下,我們發現在紅米Note2和魅族MX5下,無法正常的同時進行活體檢測和視頻錄制,換得更技術一點的說法就是,在舊的API下,Camera.PreviewCallback和MediaRecorder不能同時進行。怎么辦?google 來波search,你會發現,然並卵……剛開始,我們還專門聯系了魅族的相機開發人員,以為會有什么比較“魅族化”的方案,結果他們直接回了一句:平台相關,MX5不支持錄像輸出的同時提供預覽數據。怎么辦?砍需求?這種關鍵流程,都是經過法務部門專門審核過的,那能說砍就砍。我們一邊申請是否可以砍掉這個需求,同時也依然繼續研究怎么解決這個問題~
我們發現這兩款不能同時進行的手機都是5.0以上的,於是我就想,也許新的Camera2有可能解決的~下面開始進行專業技術干貨解說模式……
當我們要學着使用某個新的API時,最好是直接到官網去找reference,然后盡量科學上網。Android的大部分API示例,都在https://github.com/googlesamples里面,這次提到的關於Camera2的使用,當然也是從那里下載下來的,源碼地址如下:
https://github.com/googlesamples/android-Camera2Basic以及https://github.com/googlesamples/android-Camera2Video。但是googlesamples里面的代碼都是比較原始的代碼。
我們需要靜下心來分析相機使用的過程:
1、首先一定得判斷權限,是否有權利使用相機;
2、通過什么方式連上相機設備;
3、拿到相機設備后怎么進行錄像;
4、如何在錄像的過程中監聽到每一幀的數據;
一、關於檢查權限就不說了,這里補充一句有一個類叫ActivityCompat,大家如果以前沒用過可以看一下,是v4包下的類。
二、Camera2打開相機設備的方式跟老的不一樣,以前直接就new一個就open了,比較直接。現在把camera當做一種服務去對待,要申請,而申請的方式如下,
private void openCamera() {
if (isOpened) {
return;
}
isOpened = true;
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
String cameraId = manager.getCameraIdList()[0];//這個可能會有很多個,但是通常都是兩個,第一個是后置,第二個是前置;
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
assert map != null;
imageDimension = map.getOutputSizes(SurfaceTexture.class)[0];
manager.openCamera(cameraId, new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice camera) {
G.i("onOpened");
createCameraPreview(camera);
}
@Override
public void onDisconnected(CameraDevice camera) {
G.i("onDisconnected");
camera.close();
}
@Override
public void onError(CameraDevice camera, int error) {
G.e("onError -> " + error);
camera.close();
}
}, handlerHelper.getBackgroundHandler());//這個指定其后台運行,如果直接UI線程也可以,直接填null;
G.i("open Camera " + cameraId);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
三、拿到相機設備的回調就是如上代碼的 public void onOpened(CameraDevice camera) 方法,此時的cameraDevice就是我們可以完全使用的Camera。拿到相機以后,就開始創建預覽即preview。
protected void createCameraPreview(final CameraDevice cameraDevice) { try { if (null == cameraDevice) { G.i("updatePreview error, return"); return; } setUpImageReader(); setUpMediaRecorder(); final CaptureRequest.Builder captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); SurfaceTexture texture = textureView.getSurfaceTexture(); texture.setDefaultBufferSize(imageDimension.getWidth(), imageDimension.getHeight()); Surface textureSurface = new Surface(texture); Surface recorderSurface = mMediaRecorder.getSurface(); Surface imageSurface = imageReader.getSurface(); captureRequestBuilder.addTarget(textureSurface); captureRequestBuilder.addTarget(recorderSurface); captureRequestBuilder.addTarget(imageSurface); List<Surface> surfaceList = Arrays.asList(textureSurface, recorderSurface, imageSurface); cameraDevice.createCaptureSession(surfaceList, new CameraCaptureSession.StateCallback() {//配置要接受圖像的surface @Override public void onConfigured(CameraCaptureSession cameraCaptureSession) { captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); try { cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, handlerHelper.getBackgroundHandler());//成功配置后,便開始進行相機圖像的監聽 } catch (CameraAccessException e) { e.printStackTrace(); } mMediaRecorder.start(); } @Override public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { ToastUtils.show("Configuration change"); } }, handlerHelper.getBackgroundHandler()); } catch (CameraAccessException e) { e.printStackTrace(); } } private void setUpMediaRecorder() { mMediaRecorder = new MediaRecorder(); mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); mMediaRecorder.setProfile(getCamcorderProfile()); mMediaRecorder.setOutputFile(new File(getExternalCacheDir(), System.currentTimeMillis() + ".mp4").getAbsolutePath()); try { mMediaRecorder.prepare(); } catch (IOException e) { e.printStackTrace(); } } private void setUpImageReader() { imageReader = ImageReader.newInstance(imageDimension.getWidth(), imageDimension.getHeight(), ImageFormat.YUV_420_888, 10); imageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { Image image = reader.acquireLatestImage(); if (image != null) { image.close(); } G.i("onImageAvailable"); } }, handlerHelper.getBackgroundHandler()); }
由於camera2的api使用的方式是,相機設備可以向意多個surface進行圖像的輸出。如上代碼,通過cameraDevice.createCaptureSession(....)方法去配置要輸出的surface,任意一個沒有被配置過的surface在使用的時候都會報錯。同時有回調通知,是否配置成功,成功以后便可以開始啟動圖像的輸出監聽,即cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, handlerHelper.getBackgroundHandler());其中captureRequestBuilder就是配置相機屬性以及添加那些已經成功配置過了surface,至於怎么接收相機的圖像,便有各個surface的所有者自己去定義。這里使用到的是MediaRecorder和ImageReader,一個是為了錄像,一個是為了所謂的監聽PreviewCallback。
注意:使用ImageReader的時候會比較卡,特別是如果使用JPEG的格式的話,因為使用JPEG,ImageReader需要進行額外的處理。我為了使回調與舊的PreviewCallback一樣使用了ImageFormat.YUV_420_888格式。這個格式,輸出非常流暢。
