Android Camera2(一)概述

1,164 阅读7分钟

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 静止图像。 对于预览和录制的场景,你可能会被使用  SurfaceViewTextureViewMediaRecorder, 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可以用的有JPEGYUV_420_888PRIVATE 其他格式不能用的原因:

  1. YUV_422_888YUV_444_888FLEX_RGB_888FLEX_RGBA_8888
        Invalid format specified 39404142
  1. RGB_565YV12Y8NV16YUY2DEPTH_JPEGRAW_SENSORRAW_PRIVATERAW10RAW12DEPTH16DEPTH_POINT_CLOUDHEIC
        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采样。