新的相机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格式。这个格式,输出非常流畅。