WHO
YUV数据百度一搜到处都是,在这里不在赘述,不不,还是赘述一下,毕竟下文还是需要一些理论,抛砖引玉一下:YUV是一种色彩空间,Y通道是指亮度,UV通道代表颜色差,没有UV图片数据就是黑白。衍生的YUV数据格式有很多,不同的采样和存储方式都会造成格式的差异。
在这里分享一篇很有启迪性的YUV数据介绍博客:
https://zhuanlan.zhihu.com/p/384455058 (谢谢大兄弟)
按照采样方式可以分为:
YUV 420,由 4 个 Y 分量共用一套 UV 分量(有损)
YUV 422,由 2 个 Y 分量共用一套 UV 分量(有损)
YUV 444,不共用,一个 Y 分量使用一套 UV 分量(无损)
按照存储通道可以分为:
Planar YUV 三个分量分开存放(Y、U、V三通道)
Semi-Planar Y 分量单独存放,UV 分量交错存放(Y、UV或VU两通道)
Packed YUV 三个分量全部交错存放(YUV或者YVU单通道)
本文主要就是介绍在imageReader回到YUV420_888数据,这个格式的数据使用的存储方式是Semi-Planar方式,也就是UV交错存放在同一个通道中。Image回到的数据实际上也是有三个planes,其中image.getPlanes()[0]是Y通道数据,image.getPlanes()[1]是UV交错数据,image.getPlanes()[2]是VU交错数据。看我们后期怎么搭配,如果planes 0和planes 1搭配就形成yuv420类型中的NV12格式数据,如果planes 0和planes 2搭配就是yuv420类型中的NV21格式数据。
WHY
为什么会有YUV数据转换为JPEG,如果你有camera开发基础的同仁,你一定会想要获取JPEG数据可以在实例化imagereader时就设置format为JPEG格式,然后就能在回调中直接获取JPEG,我的回答:我也是这样想的。没有必要还需要这样转一下,增加开发周期。但是当我们有实际需求场景时我们就不会这样想了,我之所以这样做就是开发过程发现获取JPEG会让预览有短暂卡顿,通过这种当时很好的改善了卡顿。当然这只是这个应用场景之一,我相信产品的需求会让这个功能发光发彩的。
WHRER
Imagereader回调中:public void onImageAvailable(ImageReader imageReader),当然你可以是任何yuv数据环境中;
WHAT/HOW
1、转换接口函数,实例化YuvImage,然后利用其compressToJpeg方法实现bitmap的输出。唯一遗憾的是YuvImage目前仅仅支持一种格式NV21。
private static Bitmap YuvTansformJpeg(byte[] data, int width, int hight, int quality) {
Log.i(TAG,"Yuv开始转换Jpeg");
try {
YuvImage image_jpeg =
new YuvImage(data,ImageFormat.NV21,width,hight,null);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
image_jpeg.compressToJpeg(new Rect(0, 0,width, hight), quality, stream);
Bitmap jpeg_bitmap =
BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.toByteArray().length);
Log.i(TAG,"Yuv转换Jpeg完成");
return jpeg_bitmap;
} catch (Exception e) {
Log.e(TAG,"failed :"+e);
}
return null;
}
2、数据组合函数,目的合并多个byte[ ],前面已经说过,需要构建NV12格式或者NV21格式数据,需要将不同通道数据合并,即多个byte[ ]数据合并。
private static byte[] byteMergerAll(byte[]... values) {
int length_byte = 0;
for (int i = 0; i < values.length; i++) {
length_byte += values[i].length;
}
byte[] all_byte = new byte[length_byte];
int countLength = 0;
for (int i = 0; i < values.length; i++) {
byte[] b = values[i];
System.arraycopy(b, 0, all_byte, countLength, b.length);
countLength += b.length;
}
return all_byte;
}
3、imagereader回调函数实现整体函数,该函数可以直接移植,支持YUV_420_888和JPEG格式回调,各位同仁可以根据需求修改。
private final ImageReader.OnImageAvailableListener imagereader_available
= new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader imageReader) {
Log.i(TAG,"开始回调图片数据!");
Image image = imageReader.acquireLatestImage();
int planes = image.getPlanes().length;
Log.i(TAG,"length of getPlanes:"+planes);
ByteBuffer[] buffers = new ByteBuffer[3];
int[] number_buffers = new int[3];
byte[][] bytes_plans = new byte[3][];
for (int i = 0; i < planes; i++) {
buffers[i] = image.getPlanes()[i].getBuffer();
number_buffers[i] = buffers[i].remaining();
Log.i(TAG,"测试相机 imagereader 回调数据大小 planes["+i+"]:"+number_buffers[i]);
bytes_plans[i] = new byte[number_buffers[i]];
buffers[i].get(bytes_plans[i]);
//System.out.println(Arrays.toString(bytes_plans[i]));
}
image.close();
if (planes > 1) {
//创建多个yuv文件
FileOutputStream Youtput = null;
FileOutputStream UVoutput = null;
FileOutputStream VUoutput = null;
FileOutputStream YVUoutput = null;
FileOutputStream YUVoutput = null;
FileOutputStream Jpegoutput = null;
try {
// output = new FileOutputStream(new File(getExternalFilesDir("yuv"), "yout.yuv"));
Youtput = new FileOutputStream("/sdcard/DCIM/Camera/y_out.yuv");
UVoutput = new FileOutputStream("/sdcard/DCIM/Camera/uv_out.yuv");
VUoutput = new FileOutputStream("/sdcard/DCIM/Camera/vu_out.yuv");
YVUoutput = new FileOutputStream("/sdcard/DCIM/Camera/yvu_out.yuv");
YUVoutput = new FileOutputStream("/sdcard/DCIM/Camera/yuv_out.yuv");
Jpegoutput = new FileOutputStream("/sdcard/DCIM/Camera/yuvTansformJpeg.jpeg");
//写入单个通道数据
Youtput.write(bytes_plans[0]);
UVoutput.write(bytes_plans[1]);
VUoutput.write(bytes_plans[2]);
//生成yuv数据 N12
YUVoutput.write(bytes_plans[0]);
YUVoutput.write(bytes_plans[1]);
//生成yvu数据 N21
YVUoutput.write(bytes_plans[0]);
YVUoutput.write(bytes_plans[2]);
byte[] yuv_buffer =byteMergerAll(bytes_plans[0],bytes_plans[2]);
Log.i(TAG,"yuv_buffer size is :"+yuv_buffer.length);
Youtput.close();
UVoutput.close();
VUoutput.close();
YVUoutput.close();
YUVoutput.close();
//yuv420转换为jpeg格式
if (yuv_buffer != null) {
Bitmap Jpeg_bitmap = YuvTansformJpeg(yuv_buffer,1920,1080,80);
if (Jpeg_bitmap != null) {
Log.i(TAG,"succeccful Jpeg_bitmap");
Jpeg_bitmap.compress(Bitmap.CompressFormat.JPEG,100,Jpegoutput);
image_view.setImageBitmap(Jpeg_bitmap);
Jpegoutput.flush();
Jpegoutput.close();
Log.i(TAG,"成功保存jpg文件");
}
}
} catch (FileNotFoundException e) {
Log.w(TAG, "failed", e);
} catch (IOException e) {
Log.w(TAG, "failed 2", e);
}
}
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes_plans[0], 0, number_buffers[0]);
if (bitmap != null)
image_view.setImageBitmap(bitmap);
Log.i(TAG,"回调图片数据完成!");
}
};
(谢谢,一起学习,欢迎交流)