標籤:
新的相機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格式。這個格式,輸出非常流暢。
關於使用Android新版Camera即Camera2的使用介紹 暨解決Camera.PreviewCallback和MediaRecorder無法同時進行