Android Camera2数据处理小结
由于项目需求变更,需要使用Camera2以支持跨页面访问相机,在这里记录一下对其数据处理和速度优化的思路
数据格式-YUV_420_888
相较于原本的Camera,Camera2的各方面都变得复杂,同样在数据这一块,相对于原本的NV21和YV12(这里记个坑,MX3的Camera1对YV12实际是不支持的,绿屏警告)等YUV420格式,它提供了一个混合型格式,即YUV_420_888。
先来看看API描述(详见ImageFormat.YUV_420_888):
Multi-plane Android YUV 420 format
This format is a generic YCbCr format, capable of describing any 4:2:0
chroma-subsampled planar or semiplanar buffer (but not fully interleaved),
with 8 bits per color sample.
Images in this format are always represented by three separate buffers
of data, one for each color plane. Additional information always
accompanies the buffers, describing the row stride and the pixel stride
for each plane.
The order of planes in the array returned by
{@link android.media.Image#getPlanes() Image#getPlanes()} is guaranteed such that
plane #0 is always Y, plane #1 is always U (Cb), and plane #2 is always V (Cr).
The Y-plane is guaranteed not to be interleaved with the U/V planes
(in particular, pixel stride is always 1 in
{@link android.media.Image.Plane#getPixelStride() yPlane.getPixelStride()}).
The U/V planes are guaranteed to have the same row stride and pixel stride
(in particular,
{@link android.media.Image.Plane#getRowStride() uPlane.getRowStride()}
== {@link android.media.Image.Plane#getRowStride() vPlane.getRowStride()} and
{@link android.media.Image.Plane#getPixelStride() uPlane.getPixelStride()}
== {@link android.media.Image.Plane#getPixelStride() vPlane.getPixelStride()};
).
简单来说,这就是一个混合的YCbCr格式,无论是何种YUV420格式,他都用三个平面将Y U V分量分开存储,具体到Image类中,即Image#getPlanes()获得的平面数组,其中plane[0]存放Y分量,plane[[1]]存放U分量,plane[[2]]存放V分量,可以看出,这样的设计对Java层的数据处理是非常友好的,我们不需要关心其具体数据格式,只需要根据参数考虑如何获取三个分量的数据。
话是这么说,但实际处理过程并不是那么友好,根据相机返回的具体格式的不同,实际在Image中存储的方式也是不同的,我们以YV12和NV21这两个Camera1中泛用的格式举例:
PixelStride&RowStride
这里先了解一下Plane中的两个相关属性:
PixelStride顾名思义,指像素跨度,即在一个平面中,U/V分量的取值间隔,这里我们可以知道即使UV分量分别存储在不同平面中,他们也不一定是连续存储的(即PixelStride不总是为1)
RowStride则是行跨度,说白了就是每行存放的数据量。
YV12
YV12是平面(Planar)存储的YUV420格式,即Y U V三个分量分开且连续的存储(PixelStride = 1),以Camera1的回调数据为例,我们可以理解为在一个byte[]数组中,分量以YYYYYYYYVVUU的形式存储,三个分量间没有交叉。在Image的Plane中,同样体现为此,以1920x1080的YV12数据为例
Y分量----- PixelStride = 1 RowStride = 1920 size = 2073600 YYYYYYYY
V分量----- PixelStride = 1 RowStride = 960 size = 518400 VV
U分量----- PixelStride = 1 RowStride = 960 size = 518400 UU
注:size 为 buffer.remaining()
根据我本人项目的需求,是需要数据转为I420格式,这种情况再为简单不过,直接拷贝数据即可(这里我只使用libyuv的I420Copy尝试过,java中直接拷贝没有试过)
NV21
NV21是半平面(Semi-Planar)存储的YUV420格式,即Y分量单独连续存储,UV分量交叉存储(PixelStride = 2),以Camera1的回调数据为例,我们可以理解为在一个byte[]数组中,分量以YYYYYYYYVUVU的形式存储,而在Image中,由于YUV_420_888格式强制分离了UV分量,我们可以分别从plane[1]和plane[2]中获取U、V分量,这时候的平面属性如下
Y分量----- PixelStride = 1 RowStride = 1920 size = 2073600 YYYYYYYY
V分量----- PixelStride = 2 RowStride = 1920 size = 1036800 V0V0
U分量----- PixelStride = 2 RowStride = 1920 size = 1036800 U0U0
注:这里的0只是占位符,不表示具体数据
可以看到这里的UV分量并不是连续存储的,因此取值不能直接拷贝,以U平面为例大致为如下逻辑:
//注意我这里没有考虑行跨度,只是举个栗子
int pixelStride = planes[2].getPixelStride(); //U平面步长
ByteBuffer buffer = planes[2].getBuffer();
if (null == buffer) return null;
byte[] uPlane = new byte[buffer.