camera2
Camera2 YUV420_888
Android Camera2数据处理小结
使用camera2设置全屏TextureView却不显示为全屏的解决办法
概述
一个 Android 设备可以有多个摄像头。每个摄像头是一个 CameraDevice, 一个CameraDevice可以同时输出多个流。可用于预览,拍照或录像,这些流相当于并行管道,处理来自相机的原始帧,并行处理意味着可能存在性能限制,具体取决于 CPU、GPU 或其他设备的可用处理能力。如果管道跟不上传入的帧,它就会开始丢帧。
每个管道都有自己的输出格式。传入的原始数据通过与每个管道关联的隐式逻辑自动转换为适当的输出格式。
可以使用CameraDevice来创建该CameraDevice特定的 CameraCaptureSession指定相机属性,例如自动对焦、光圈、效果和曝光。由于硬件限制,在任何给定时间只能在相机传感器中激活单个配置。
一个CameraCaptureSession 绑定到 CameraDevice 创建会话后,将无法添加或删除管道。
CameraCharacteristics.LENS_FACING
- CameraMetadata.LENS_FACING_FRONT 相机设备面向与设备屏幕相同的方向(前置)。
- CameraMetadata.LENS_FACING_BACK 相机设备面向与设备屏幕相反的方向(后置)。
- CameraMetadata.LENS_FACING_EXTERNAL 摄像头设备是一个外部摄像头,相对于设备的屏幕没有固定的朝向(外部)。
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP
相机设备支持的所有输出格式(以及相应格式的大小)的列表。 这还包含每个格式/大小组合的最小帧持续时间和停顿持续时间,可用于在提交多个捕获时计算有效帧速率。
CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES
此相机设备支持的帧速率范围列表。 单位:每秒帧数 (FPS)
CameraCharacteristics.SENSOR_ORIENTATION
输出图像需要旋转的顺时针角度,以使其在设备屏幕上以其原始方向直立。
单位:顺时针旋转的度数;总是 90 的倍数
有效值范围: 0、90、180、270
CameraCaptureSession
相机捕获会话,用于处理拍照和预览的工作 多个相机流可以组合成一个CameraCaptureRequest
TEMPLATE_PREVIEW
预览捕获模板 针对低延迟进行了优化
TEMPLATE_STILL_CAPTURE
高质量的图像
TEMPLATE_RECORD
稳定的帧速率
仅用于预览的话,SurfaceView会自动更新,不需要设置回调,想要得到每一帧则需要添加回调
CaptureRequest
捕获请求,定义输出缓冲区以及显示界面(TextureView或SurfaceView)等
输出类型
是指帧的编码格式。可能的值为 PRIV、YUV、JPEG 和 RAW
选择应用程序的输出类型时,如果目标是最大限度地提高兼容性,则可 ImageFormat.YUV_420_888 用于帧分析和 ImageFormat.JPEG 静止图像。
对于预览和录制的场景,你可能会被使用 SurfaceView,TextureView,MediaRecorder, MediaCodec ,或 RenderScript.Allocation 。在这些情况下,请勿指定图像格式。为了兼容性,无论内部使用的实际格式如何,它都将被视为ImageFormat.PRIVATE 。
输出尺寸
StreamConfigurationMap.getOutputSizes()列出所有可用的输出大小
硬件级别
- CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
- CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL,
- CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
- CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
- CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
LEGACY是最低的硬件级别
ImageReader.OnImageAvailableListener
从ImageReader的队列中获取下一个Image。 如果没有新图像可用,则返回null
Image image = reader.acquireNextImage();
从ImageReader的队列中获取最新的Image ,删除旧images 。 如果没有新图像可用,则返回null 。
Image image = reader.acquireLatestImage();
Camera2 API可以用的有JPEG,YUV_420_888,PRIVATE
其他格式不能用的原因:
YUV_422_888,YUV_444_888,FLEX_RGB_888,FLEX_RGBA_8888
Invalid format specified 39,40,41,42
RGB_565,YV12,Y8,NV16,YUY2,DEPTH_JPEG,RAW_SENSOR,RAW_PRIVATE,RAW10,RAW12,DEPTH16,DEPTH_POINT_CLOUD,HEIC
Session 0: Failed to create capture session; configuration failed
JPEG
较慢,一般用于拍照
private ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
//获取最新的一帧的Image
Image image = reader.acquireLatestImage();
//因为是ImageFormat.JPEG格式,所以 image.getPlanes()返回的数组只有一个,也就是第0个。
ByteBuffer byteBuffer = image.getPlanes()[0].getBuffer();
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
//ImageFormat.JPEG格式直接转化为Bitmap格式。
Bitmap temp = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
//因为摄像机数据默认是横的,所以需要旋转90度。
Bitmap newBitmap = BitmapUtil.rotateBitmap(temp, 90);
//抛出去展示或存储。
mOnGetBitmapInterface.getABitmap(newBitmap);
//一定需要close,否则不会收到新的Image回调。
image.close();
}
};
YUV_420_888
private ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
byte[] y;
byte[] u;
byte[] v;
byte[] i420;
byte[] yv12;
byte[] nv21;
byte[] nv12;
@Override
public void onImageAvailable(ImageReader reader) {
//获取最新的一帧的Image
Image image = reader.acquireLatestImage();
MyLogUtil.e(TAG, "width: "+image.getWidth());//1920
MyLogUtil.e(TAG, "height: "+image.getHeight();//1080
MyLogUtil.e(TAG, "format: "+image.getFormat();//ImageFormat.YUV_420_888 =35
MyLogUtil.e(TAG, "timeStamp: "+ image.getTimestamp();//时间戳
if (nv21 == null) {
nv21 = new byte[width * height * 3 / 2];
}
if (nv12 == null) {
nv12 = new byte[width * height * 3 / 2];
}
if (i420 == null) {
i420 = new byte[width * height * 3 / 2];
}
if (yv12 == null) {
yv12 = new byte[width * height * 3 / 2];
}
if (y == null) {
y = new byte[image.getPlanes()[0].getBuffer().remaining()];
u = new byte[image.getPlanes()[1].getBuffer().remaining()];
v = new byte[image.getPlanes()[2].getBuffer().remaining()];
}
// 从image里获取三个plane
Image.Plane[] planes = image.getPlanes();
for (int i = 0; i < planes.length; i++) {
ByteBuffer iBuffer = planes[i].getBuffer();
int iSize = iBuffer.remaining();
MyLogUtil.e(TAG, "pixelStride " + planes[i].getPixelStride());
MyLogUtil.e(TAG, "rowStride " + planes[i].getRowStride());
MyLogUtil.e(TAG, "buffer Size " + iSize);
MyLogUtil.e(TAG, "Finished reading data from plane " + i);
}
planes[0].getBuffer().get(y);
planes[1].getBuffer().get(u);
planes[2].getBuffer().get(v);
// MyLogUtil.e(TAG, "y.length" + y.length);//2073600
// MyLogUtil.e(TAG, "u.length" + u.length);//1036799
// MyLogUtil.e(TAG, "v.length" + v.length);//1036799
//width * height=2073600 width * height/2=1036800 width * height/4=518400
int pixelStride = planes[1].getPixelStride();
if (pixelStride == 1) {//420p,i420/yv12
if(imageFormat == ImageFormat.YV12){
//i420
System.arraycopy(y, 0, i420, 0, y.length);
System.arraycopy(u, 0, i420, y.length, u.length);
System.arraycopy(v, 0, i420, y.length + u.length, v.length);
YUVUtil.convertI420ToNV21(i420, nv21, width, height);
}else {
//yv12
System.arraycopy(y, 0, yv12, 0, y.length);
System.arraycopy(v, 0, yv12, y.length, v.length);
System.arraycopy(u, 0, yv12, y.length + v.length, u.length);
YUVUtil.convertYV12ToI420(yv12,i420,width,height);
YUVUtil.convertI420ToNV21(i420, nv21, width, height);
}
} else if (pixelStride == 2) {//420sp,nv21/nv12
//nv21
if(imageFormat == ImageFormat.NV21){
System.arraycopy(y, 0, nv21, 0, y.length);
System.arraycopy(v, 0, nv21, y.length, v.length);
}else {
//nv12
System.arraycopy(y, 0, nv12, 0, y.length);
System.arraycopy(u, 0, nv12, y.length, u.length);
YUVUtil.convertNV12ToI420(nv12, i420, width, height);
YUVUtil.convertI420ToNV21(i420, nv21, width, height);
}
}
//一定需要close,否则不会收到新的Image回调。
image.close();
}
};
pixelStride: 像素跨度,在一个平面中,U/V分量的取值间隔
rowStride: 行跨度,每行存放的数据量
YUV420SP(Semi-Planar 半平面)
- NV21 YYYYYYYYVUVU
- NV12 YYYYYYYYUVUV
format: 35
width: 1920
height: 1080
planes[0] y: pixelStride 1
planes[0] y: rowStride 1920
planes[0] y: buffer size 2073600
planes[1] u: pixelStride 2
planes[1] u: rowStride 1920
planes[1] u: buffer size 1036799
planes[2] v: pixelStride 2
planes[2] v: rowStride 1920
planes[2] v: buffer size 1036799
-
plane[0] 是Y数据,从rowStride是1920和 pixelStride是1,可知每行1920个像素且Y数据之间无间隔,从buffer size / rowStride = 1080 Y数据有1080行。
-
plane[1] 是U数据,rowStride 是1920, rowStride是2 ,说明每行1920个像素中每两个连续的U之间隔了一个像素,buffer中索引为: 0 , 2 , 4, 6, 8 … 是U数据,即步长为2。 每行实际的U数据只占1/2 ,buffer size / rowStride = 540 只有540行,说明纵向采样也是1/2 ,但buffer size 是 plane[0]的 1/2而不是1/4, 连续的U之间到底存储了什么数据,才使得buffer size 变为plane[0]的1/2了?
-
plane[2] 是V数据,rowStride 是1920, rowStride是2 ,说明每行1920个像素中每两个连续的V之间隔了一个像素,buffer中索引为: 1 , 3 , 5, 7, 9 … 是V数据,即步长为2。 每行实际的V数据只占1/2 ,buffer size / rowStride = 540 只有540行,说明纵向采样也是1/2 ,但buffer size 是 plane[0]的 1/2而不是1/4, 连续的U之间到底存储了什么数据,才使得buffer size 变为plane[0]的1/2了?
实际上:
-
plane[1] : UVUVUVUVUVUVUVUV...
-
plane[2] : VUVUVUVUVUVUVUVU... 这就是为什么plane[1]和plane[2]的buffer size 是plane[0]的1/2而不是1/4的原因
结论
- plane[0] + plane[1] 可得NV12
- plane[0] + plane[2] 可得NV21
YUV420P(Planar 平面)
- YV12 YYYYYYYYVVUU
- YU12(I420) YYYYYYYYUUVV
format: 35
width: 1920
height: 1080
planes[0] y: pixelStride 1
planes[0] y: rowStride 1920
planes[0] y: buffer size 2073600
planes[1] u: pixelStride 1
planes[1] u: rowStride 960
planes[1] u: buffer size 518400
planes[2] v: pixelStride 1
planes[2] v: rowStride 960
planes[2] v: buffer size 518400
-
plane[0]表示Y,rowStride是1920 ,其pixelStride是1 ,说明Y存储时中间无间隔,每行1920个像素全是Y值,buffer size / rowStride= 1080可知Y有1080行。
-
plane[1]表示U,rowStride是960 ,其pixelStride也是1,说明连续的U之间没有间隔,每行只存储了960个数据,buffer size 是 plane[0]的1/4 ,buffer size / rowStride = 540 可知U有540行,对于U来说横纵都是1/2采样。
-
plane[2]表示V,rowStride是960 ,其pixelStride也是1,说明连续的V之间没有间隔,每行只存储了960个数据,buffer size 是 plane[0]的1/4 ,buffer size / rowStride = 540 可知V有540行,对于V来说横纵都是1/2采样。