android使用camera2的Api 实现拍照录视频的功能
- 背景:在视频录制的同时,对视频帧数据进行人脸识别检测,需要用到预览返回的nv21格式数据,
android.hardware.Camera中的Camera.PreviewCallback接口是不支持同时录制视频和预览回调获取nv21数据的。
查了查android.hardware.camera2倒是支持的,CaptureRequest.Builder分别添加预览的surface,以及mediaRecorder的surface,即可达到效果。
应用camera2需要注意,camera2 仅支持android 5.0( LOLLIPOP 21 )及以上的版本,如果要兼容之前的,可能得写分支了。
常用类解释
CameraManager ,用来检测,描绘,连接相机的相机管理类, 类似于WindowManager,ActivityManager的系统级服务
获取方式也一致,如下:
Context.getSystemService(Context.CAMERA_SERVICE);
CameraCharacteristics,描述相机设备的属性类,可以通过它获取到相机对一些功能的支持情况。
demo中用到的一些属性
//获取到相机支持的各种输出size
StreamConfigurationMap map = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
map.getOutputSizes(ImageFormat.JPEG) //照片输出尺寸
map.getOutputSizes(SurfaceTexture.class)//预览支持尺寸
map.getOutputSizes(MediaRecorder.class)//录制的视频支持尺寸
characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);//是否支持闪光灯
characteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);//是否支持自动对焦
characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);//硬件传感器角度
CameraDevice,相机类
获取方式:CameraManager打开指定相机成功后的回调方法里。
CameraManager.openCamera(cameraId, deviceCallback, backgroudHandler);
CaptureRequest.Builder,capture请求builder类,用来生成capture请求。
获取方式:
/**
* templateType 类别 如下几个
* TEMPLATE_PREVIEW 预览
* TEMPLATE_RECORD 录制视频
* TEMPLATE_STILL_CAPTURE 拍照
* TEMPLATE_VIDEO_SNAPSHOT //没用到 igonre
* TEMPLATE_MANUAL //手动,貌似需要硬件是full级别才能全支持,没详细了解
**/
CaptureRequest.Builder builder = CameraDevice.createCaptureRequest(@RequestTemplate int templateType);
demo主要用到以下几个
//1. 自动聚焦相关:
CaptureRequest.Builder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
//2.自动曝光相关
CaptureRequest.Builder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
//3.预览放大缩小
CaptureRequest.Builder.set(CaptureRequest.SCALER_CROP_REGION, region);
//4.自动控制模式
CaptureRequest.Builder.set(CaptureRequest.CONTROL_MODE,CameraMetadata.CONTROL_MODE_AUTO);
//5.手动触发对焦
CaptureRequest.Builder.set(CaptureRequest.CONTROL_AF_TRIGGER,CameraMetadata.CONTROL_AF_TRIGGER_START);
//6.手动触发曝光
CaptureRequest.Builder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
CameraCaptureSession,相机capture事务,获取或者处理相机图像类,
当session创建成功后,会一直处于激活状态,直到该相机创建了新的session,或者相机关闭。
因为createCaptureSession是较耗时的操作,大概需要几百毫秒的时间,所以是异步实现。
获取方式 :相机调用createCaptureSession方法,在CameraCaptureSession.StateCallback回调中获取
code如下:
/**
* 创建session请求
**/
CameraDevice.createCaptureSession(List<Surface> outputs,
@NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler
);
/**
*回调onConfigured中获取到session
**/
private CameraCaptureSession.StateCallback callback= new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
previewSession = session;
//.....
}
};
ImageReader, 图像处理类,直接获取到渲染到surface对象上的图像数据
拍照,预览数据的获取
获取方式:
1. 拍照相关
//初始化
ImageReader jpegReader = ImageReader.newInstance(width, height,
ImageFormat.JPEG, 1); //返回格式是JPEG格式
jpegReader.setOnImageAvailableListener(
jpegImageLister, backgroudHandler);
//获取到数据
ImageReader.OnImageAvailableListener jpegImageLister = new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
//照片处理流程
Image image = reader.acquireLatestImage();
}
};
2. 预览相关
ImageReader nv21Reader = ImageReader.newInstance(width, height, ImageFormat.YUV_420_888, 2);
nv21Reader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
//nv21格式处理相关
Image image = reader.acquireLatestImage();
if (image != null) {
image.close();
}
}
}, backgroudHandler);
创建一个预览的session示例
private void createCameraPreviewSession() throws CameraAccessException {
SurfaceTexture texture = textureView.getSurfaceTexture();
assert texture != null;
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
Surface surface = new Surface(texture);
mPreviewRequestBuilder
= cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewRequestBuilder.addTarget(surface);
// capture到的image数据会输出到 surface, jpegReader.getSurface() 这两个指定的surfaces中
cameraDevice.createCaptureSession(Arrays.asList(surface, jpegReader.getSurface()),
previewStateCallback, null
);
}
预览返回数据转NV21代码
/**
* 根据image对象,转成人脸引擎需要的nv21格式数组
*
* @param image
* @param colorFormat
* @return
*/
private byte[] getDataFromImage(Image image, int colorFormat) {
long startTime = System.currentTimeMillis();
if (colorFormat != COLOR_FormatI420 && colorFormat != COLOR_FormatNV21) {
throw new IllegalArgumentException("only support COLOR_FormatI420 " + "and COLOR_FormatNV21");
}
if (!isImageFormatSupported(image)) {
throw new RuntimeException("can't convert Image to byte array, format " + image.getFormat());
}
Rect crop = image.getCropRect();
int format = image.getFormat();
int width = crop.width();
int height = crop.height();
Image.Plane[] planes = image.getPlanes();
byte[] data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8];
byte[] rowData = new byte[planes[0].getRowStride()];
int channelOffset = 0;
int outputStride = 1;
for (int i = 0; i < planes.length; i++) {
switch (i) {
case 0:
channelOffset = 0;
outputStride = 1;
break;
case 1:
if (colorFormat == COLOR_FormatI420) {
channelOffset = width * height;
outputStride = 1;
} else if (colorFormat == COLOR_FormatNV21) {
channelOffset = width * height + 1;
outputStride = 2;
}
break;
case 2:
if (colorFormat == COLOR_FormatI420) {
channelOffset = (int) (width * height * 1.25);
outputStride = 1;
} else if (colorFormat == COLOR_FormatNV21) {
channelOffset = width * height;
outputStride = 2;
}
break;
}
ByteBuffer buffer = planes[i].getBuffer();
int rowStride = planes[i].getRowStride();
int pixelStride = planes[i].getPixelStride();
int shift = (i == 0) ? 0 : 1;
int w = width >> shift;
int h = height >> shift;
buffer.position(rowStride * (crop.top >> shift) + pixelStride * (crop.left >> shift));
for (int row = 0; row < h; row++) {
int length;
if (pixelStride == 1 && outputStride == 1) {
length = w;
buffer.get(data, channelOffset, length);
channelOffset += length;
} else {
length = (w - 1) * pixelStride + 1;
buffer.get(rowData, 0, length);
for (int col = 0; col < w; col++) {
data[channelOffset] = rowData[col * pixelStride];
channelOffset += outputStride;
}
}
if (row < h - 1) {
buffer.position(buffer.position() + rowStride - length);
}
}
// if (VERBOSE)
// Log.v(TAG, "Finished reading data from plane " + i);
}
return data;
}
效果图(本想做gif,但录屏的同时会相机也在录制视频,报start failed: -38异常,就没搞)
1.jpg 2.jpg 4.jpg
代码几个问题
- 相机屏幕适配只做了竖屏
2.在小米4,红米note上录屏的时预览画面会压扁,没解决
参考:
Camera2Basic
这个官方demo有时因为设备支持原因,导致拍照点击无响应,再点就好了。可以跳过ae,af这些回调直接进行拍照。
Camera2Video
这个官方demo连续拍视频的时候会异常,在重拍前调用session的abortCaptures,stopRepeating方法可以避免异常。
CameraView
UI相关的东西参考的这里,封装的很好,代码思路清晰,耦合度低。