前言
项目中有个需求是在边录制视频的时候实时分析视频中的人脸情况,在camera1中预览时有个回调PreviewCallback的onPreviewFrame方法中能获取每一帧的数据,但是一旦开始录制视频,就不会走onPreviewFrame方法。查阅资料看到很多人说使用camera2能实现上述需求,但是也没找到现成的,所以自己琢磨了一番之后总算是实现了,特此记录。
new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera)
{
//TODO对帧数据进行处理
}
}
正文
话不多说,进入正题,实现上述需求主要就是使用ImageReader对象
mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),
ImageFormat.YUV_420_888, 2);
创建ImageReader对像
ImageReader ir = ImageReader.newInstance(int width, int height, int format, int maxImages);
参数:
width:默认图像的宽度像素
height:默认图像的高度像素
format:图像的格式
maxImages:用户想要读图像的最大数量
ImageReader类的主要操作:
- getSurface() //得到一个表面,可用于生产这个ImageReader图像
- acquireLatestImage() //从ImageReader的队列获得最新的图像,放弃旧的图像。
- acquireNextImage() //从ImageReader的队列获取下一个图像
- getMaxImages() //最大数量的图像
- getWidth() //每个图像的宽度,以像素为单位。
- getHeight() //每个图像的高度,以像素为单位。
- getImageFormat() //图像格式。
- close() //释放与此ImageReader相关的所有资源。用完记得关
设置监听
mImageReader.setOnImageAvailableListener(
new OnImageAvailableListenerImpl(), mBackgroundHandler);
我这里使用的是usb单目摄像头,所以图像格式我使用的是ImageFormat.YUV_420_888,而且我最后需要得到nv21来识别图像中的人脸,下面的方法就是处理Image 得到一个nv21的数据
private class OnImageAvailableListenerImpl implements ImageReader.OnImageAvailableListener {
private byte[] y;
private byte[] u;
private byte[] v;
private byte[] nv21;
private ReentrantLock lock = new ReentrantLock();
private Object mImageReaderLock = 1;//1 available,0 unAvailable
@Override
public void onImageAvailable(ImageReader reader) {
Image image = reader.acquireNextImage();
if (image == null) {
return;
}
synchronized (mImageReaderLock) {
if (!mImageReaderLock.equals(1)) {
Logger.v(TAG, "--- image not available,just return!!!");
image.close();
return;
}
if (ImageFormat.YUV_420_888 == image.getFormat()) {
Image.Plane[] planes = image.getPlanes();
lock.lock();
if (y == null) {
y = new byte[planes[0].getBuffer().limit() - planes[0].getBuffer().position()];
u = new byte[planes[1].getBuffer().limit() - planes[1].getBuffer().position()];
v = new byte[planes[2].getBuffer().limit() - planes[2].getBuffer().position()];
}
if (image.getPlanes()[0].getBuffer().remaining() == y.length) {
planes[0].getBuffer().get(y);
planes[1].getBuffer().get(u);
planes[2].getBuffer().get(v);
if (nv21 == null) {
nv21 = new byte[planes[0].getRowStride() * mPreviewSize.getHeight() * 3 / 2];
}
if (nv21 != null && (nv21.length != planes[0].getRowStride() * mPreviewSize.getHeight() * 3 / 2)) {
return;
}
// 回传数据是YUV422
if (y.length / u.length == 2) {
ImageUtil.yuv422ToYuv420sp(y, u, v, nv21, planes[0].getRowStride(), mPreviewSize.getHeight());
}
// 回传数据是YUV420
else if (y.length / u.length == 4) {
ImageUtil.yuv420ToYuv420sp(y, u, v, nv21, planes[0].getRowStride(), mPreviewSize.getHeight());
}
//调用人脸分析算法,绘制人脸信息
dealWithFace(nv21);
}
lock.unlock();
}
}
image.close(); //一定不能掉了
}
}
实现上述需求相当于有三个操作,预览,录像,分析图片,所以需要给mPreviewBuilder添加三个surface,然后开启录制,刷新预览就可以了
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
List<Surface> surfaces = new ArrayList<>();
// Set up Surface for the camera preview
Surface previewSurface = new Surface(texture);
surfaces.add(previewSurface);
mPreviewBuilder.addTarget(previewSurface);
// Set up Surface for the MediaRecorder
Surface recorderSurface = mMediaRecorder.getSurface();
surfaces.add(recorderSurface);
mPreviewBuilder.addTarget(recorderSurface);
//分析图片
Surface imageSurface = mImageReader.getSurface();
surfaces.add(imageSurface);
mPreviewBuilder.addTarget(imageSurface);
先写到这里吧,文末会放出源码
未完待续…