FFmpeg对于Android平台的视频输入设备API有一定的支持,但是比较局限,只支持Android N以上的版本,低版本无法使用。
其原因是因为FFmpeg采用的是Android N推出的native层camera API来实现的(NDK中的libcamera2ndk.so),而并非采用JNI的方式来调用Java层camera API。
Android平台视频录制API 对于视频录制API,Android平台上有两套API实现,一套是老版的Camera1,另一套是Android L之后推出的Camera2。
Camera1使用起来较为简单,但是功能相对较少,不支持多纹理输出。Camera2的API功能上虽然更加强大,但是API设计的非常底层化,不利于理解,并且YUV_420_888的数据格式,国内各大产商在实现上留下的坑太多。
Jetpack组件中,Google推出了全新的CameraX组件,对Camera1和Camera2进行了统一的封装,使得API更加简单易用。
Camera1 获取Camera设备信息
Camera.getNumberOfCameras() : 获取Camera设备数量
index : 索引就是后续会使用的CameraID
Camera.CameraInfo : Camera设备信息
Camera.getCameraInfo(index, info) : 获取指定索引的Camera设备信息,存储到info对象中
Camera.CameraInfo.facing : 相机面对的方向,前置还是后置 CAMERA_FACING_BACK or CAMERA_FACING_FRONT.
Camera.CameraInfo.orientation : 相机图像的方向,获取到的图像需要顺时针旋转该角度才能正常显示。值为0、90、180、270。(因为我们拿手机一般是竖着拿,但是摄像头可能是向左横着或向右横着被安装的)
Camera.open : 获取指定索引位置的Camera设备实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 for (int index = 0 ; index < android.hardware.Camera.getNumberOfCameras(); ++index) { android.hardware.Camera.CameraInfo info = new android .hardware.Camera.CameraInfo(); android.hardware.Camera.getCameraInfo(index, info); String facing = (info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT) ? "front" : "back" ; final android.hardware.Camera camera; try { camera = android.hardware.Camera.open(cameraId); } catch (RuntimeException e) { callback.onFailure(FailureType.ERROR, e.getMessage()); return ; } }
设置采集参数 设置采集的尺寸、格式、对焦、闪光灯、白平衡等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 final android.hardware.Camera.Parameters parameters = camera.getParameters();final List<String> focusModes = parameters.getSupportedFocusModes();parameters.setPreviewFpsRange(framerate.min, framerate.max); parameters.setPreviewSize(width, height); parameters.setPreviewFormat(ImageFormat.NV21); if (parameters.isVideoStabilizationSupported()) { parameters.setVideoStabilization(true ); } if (focusModes.contains(android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) { parameters.setFocusMode(android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); } camera.setParameters(parameters); camera.setDisplayOrientation(0 );
下面的代码就是设置相机预览显示的图像角度根据activity显示的方向来调整
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public static void setCameraDisplayOrientation (Activity activity, int cameraId, android.hardware.Camera camera) { android.hardware.Camera.CameraInfo info = new android .hardware.Camera.CameraInfo(); android.hardware.Camera.getCameraInfo(cameraId, info); int rotation = activity.getWindowManager().getDefaultDisplay() .getRotation(); int degrees = 0 ; switch (rotation) { case Surface.ROTATION_0: degrees = 0 ; break ; case Surface.ROTATION_90: degrees = 90 ; break ; case Surface.ROTATION_180: degrees = 180 ; break ; case Surface.ROTATION_270: degrees = 270 ; break ; } int result; if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { result = (info.orientation + degrees) % 360 ; result = (360 - result) % 360 ; } else { result = (info.orientation - degrees + 360 ) % 360 ; } camera.setDisplayOrientation(result); }
设置输出 获取输出数据有两种方式,一种是设置一个SurfaceTexture采用纹理进行接收;另一种是通过设置回调直接接收byte array数据。
Texture格式 SurfaceTexture有两种方式获取,一种是通过系统的SurfaceView或者TextureView来获取,另一种就是自建OES纹理,通过构造函数new。
通过系统的View来获取的SurfaceTexture,相机设备将数据写入后,系统View会自动取出数据更新显示。
自己创建的SurfaceTexture可以通过设置OnFrameAvailableListener来监听数据的变更,当相机设备写入数据时,会触发回调,在回调中使用updateTexImage从队列中取出一个数据绑定到OES纹理中,使用OES纹理来进行绘制显示或者编码输入等操作。1 2 3 4 5 6 7 try { camera.setPreviewTexture(getSurfaceTexture()); } catch (IOException | RuntimeException e) { camera.release(); callback.onFailure(FailureType.ERROR, e.getMessage()); return ; }
ByteArray格式 对于ByteArray格式的回调设置,又有两种方式,一种是setPreviewCallback方法,另一种是setPreviewCallbackWithBuffer方法。
第一种onPreviewFrame回调方法会在每一帧数据准备好了就调用,回调时机是不可控的。
第二种方式是在需要在前一帧的onPreviewFrame方法中调用addCallbackBuffer方法,下一帧的onPreviewFrame才会调用,同时addCallbackBuffer方法的参数的byte数据就是每一帧的原数据。
1 2 3 4 5 6 7 camera.setPreviewCallback(new Camera .PreviewCallback() { @Override public void onPreviewFrame (byte [] data, Camera camera) { } });
1 2 3 4 5 6 7 8 9 10 11 12 final int frameSize = captureFormat.frameSize();final ByteBuffer buffer = ByteBuffer.allocateDirect(frameSize);camera.addCallbackBuffer(buffer.array()); camera.setPreviewCallbackWithBuffer(new android .hardware.Camera.PreviewCallback() { @Override public void onPreviewFrame (final byte [] data, android.hardware.Camera callbackCamera) { camera.addCallbackBuffer(data); } }
采集
startPreview : 开启预览,数据开始回调,更改采集参数后需要重新开启预览才生效
stopPreview : 停止预览1 2 3 4 5 6 7 8 try { camera.startPreview(); } catch (RuntimeException e) { } camera.stopPreview(); camera.release();
Camera2 获取Camera设备信息 Camera设备的获取需要通过系统服务来操作。
getCameraIdList : 获取camera设备列表
getCameraCharacteristics : 获取指定camera设备参数集,通过对应的KEY从参数集中获取到各种参数值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 this .cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);String[] cameraIdList = cameraManager.getCameraIdList(); for (String cameraId : cameraIdList) { CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId); String facing = characteristics.get(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_BACK; try { cameraManager.openCamera(cameraId, new CameraStateCallback (), cameraThreadHandler); } catch (CameraAccessException e) { return ; } }
camera设备状态回调,在回调中可以开启一次会话,在会话中可以进行数据的请求1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 private class CameraStateCallback extends CameraDevice .StateCallback { private String getErrorDescription (int errorCode) { switch (errorCode) { case CameraDevice.StateCallback.ERROR_CAMERA_DEVICE: return "Camera device has encountered a fatal error." ; case CameraDevice.StateCallback.ERROR_CAMERA_DISABLED: return "Camera device could not be opened due to a device policy." ; case CameraDevice.StateCallback.ERROR_CAMERA_IN_USE: return "Camera device is in use already." ; case CameraDevice.StateCallback.ERROR_CAMERA_SERVICE: return "Camera service has encountered a fatal error." ; case CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE: return "Camera device could not be opened because" + " there are too many other open camera devices." ; default : return "Unknown camera error: " + errorCode; } } @Override public void onDisconnected (CameraDevice camera) { } @Override public void onError (CameraDevice camera, int errorCode) { } @Override public void onOpened (CameraDevice camera) { cameraDevice = camera; try { camera.createCaptureSession( Arrays.asList(surface), new CaptureSessionCallback (), cameraThreadHandler); } catch (CameraAccessException e) { return ; } } @Override public void onClosed (CameraDevice camera) { } }
设置采集参数 采集会话创建成功后,可以设置采集的参数,然后创建采集请求,进行数据的采集
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 private class CaptureSessionCallback extends CameraCaptureSession .StateCallback { @Override public void onConfigureFailed (CameraCaptureSession session) { } @Override public void onConfigured (CameraCaptureSession session) { captureSession = session; try { final CaptureRequest.Builder captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); captureRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, new Range <Integer>(framerate.min,framerate.max)); captureRequestBuilder.set( CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); captureRequestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false ); chooseStabilizationMode(captureRequestBuilder); chooseFocusMode(captureRequestBuilder); captureRequestBuilder.addTarget(surface); session.setRepeatingRequest( captureRequestBuilder.build(), new CameraCaptureCallback (), cameraThreadHandler); } catch (CameraAccessException e) { reportError("Failed to start capture request. " + e); return ; } } private void chooseStabilizationMode (CaptureRequest.Builder captureRequestBuilder) { final int [] availableOpticalStabilization = cameraCharacteristics.get( CameraCharacteristics.LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION); if (availableOpticalStabilization != null ) { for (int mode : availableOpticalStabilization) { if (mode == CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE_ON) { captureRequestBuilder.set(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE, CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE_ON); captureRequestBuilder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE, CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_OFF); Logging.d(TAG, "Using optical stabilization." ); return ; } } } final int [] availableVideoStabilization = cameraCharacteristics.get( CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES); for (int mode : availableVideoStabilization) { if (mode == CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON) { captureRequestBuilder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE, CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON); captureRequestBuilder.set(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE, CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE_OFF); Logging.d(TAG, "Using video stabilization." ); return ; } } Logging.d(TAG, "Stabilization not available." ); } private void chooseFocusMode (CaptureRequest.Builder captureRequestBuilder) { final int [] availableFocusModes = cameraCharacteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES); for (int mode : availableFocusModes) { if (mode == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO) { captureRequestBuilder.set( CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO); Logging.d(TAG, "Using continuous video auto-focus." ); return ; } } Logging.d(TAG, "Auto-focus is not available." ); } }
设置输出 Camera2中数据的输出都是通过设置Surface来接收,可以同时设置多个Surface,Surface可以通过SurfaceTexture来创建。
采用系统的SurfaceView、TextureView生成的Surface,设置给Camera,对相机采集的数据进行预览显示
采用系统的ImageReader生成的Surface来接收指定格式的图像数据
采用自己创建的SurfaceTexture来接收数据,通过设置OnFrameAvailableListener来监听数据的变更,当相机设备写入数据时,会触发回调,在回调中使用updateTexImage从队列中取出一个数据绑定到OES纹理中,使用OES纹理来进行绘制显示或者编码输入等操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 try { camera.createCaptureSession( Arrays.asList(surface), new CaptureSessionCallback (), cameraThreadHandler); } catch (CameraAccessException e) { return ; } captureRequestBuilder.addTarget(surface); session.setRepeatingRequest( captureRequestBuilder.build(), new CameraCaptureCallback (), cameraThreadHandler);
ImageReader ImageReader是跟随Camera2出来的,用来接收相机采集的数据,在拍照时可以设置ImageFormat.JPEG格式,如果想要获取原始数据,则需要设置成ImageFormat.YUV_420_888。
ImageFormat.YUV_420_888格式,是指YUV_420格式,其内部具体格式可以是NV12、NV21、YV12、I420中的任何一种,也就是说如果设置的是YUV_420_888格式,则设备返回的数据格式需要根据其返回的具体数据去判断。(这点在国内手机上各不相同,有些个别低端手机给的数据不符合官方文档上的要求)
对于ImageReader来说,最最重要的就是理解好YUV_420_888格式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ImageReader imageReader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 2 );Surface surface = imageReader.getSurface();imageReader.setOnImageAvailableListener(new ImageReader .OnImageAvailableListener() { @Override public void onImageAvailable (ImageReader reader) { Image image = reader.acquireNextImage(); image.close(); } }, mCameraHandler);