美文网首页camera2android_camera
android使用camera2的Api 实现拍照录视频的功能

android使用camera2的Api 实现拍照录视频的功能

作者: heheworld | 来源:发表于2019-07-03 10:56 被阅读0次
    • 背景:在视频录制的同时,对视频帧数据进行人脸识别检测,需要用到预览返回的nv21格式数据,
      android.hardware.Camera中的Camera.PreviewCallback接口是不支持同时录制视频和预览回调获取nv21数据的。
      查了查android.hardware.camera2倒是支持的,CaptureRequest.Builder分别添加预览的surface,以及mediaRecorder的surface,即可达到效果。

    应用camera2需要注意,camera2 仅支持android 5.0( LOLLIPOP 21 )及以上的版本,如果要兼容之前的,可能得写分支了。

    github连接

    常用类解释
    CameraManager ,用来检测,描绘,连接相机的相机管理类, 类似于WindowManager,ActivityManager的系统级服务
    获取方式也一致,如下:

    Context.getSystemService(Context.CAMERA_SERVICE);
    

    CameraCharacteristics,描述相机设备的属性类,可以通过它获取到相机对一些功能的支持情况。
    demo中用到的一些属性

    //获取到相机支持的各种输出size
    StreamConfigurationMap map = characteristics.get(
                       CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
    map.getOutputSizes(ImageFormat.JPEG) //照片输出尺寸
    map.getOutputSizes(SurfaceTexture.class)//预览支持尺寸
    map.getOutputSizes(MediaRecorder.class)//录制的视频支持尺寸
    characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);//是否支持闪光灯
    characteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);//是否支持自动对焦
    characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);//硬件传感器角度
    

    CameraDevice,相机类
    获取方式:CameraManager打开指定相机成功后的回调方法里。

    CameraManager.openCamera(cameraId, deviceCallback, backgroudHandler);
    

    CaptureRequest.Builder,capture请求builder类,用来生成capture请求。
    获取方式:

    /**
         * templateType 类别 如下几个
         * TEMPLATE_PREVIEW 预览
         * TEMPLATE_RECORD 录制视频
         * TEMPLATE_STILL_CAPTURE 拍照
         * TEMPLATE_VIDEO_SNAPSHOT //没用到 igonre
         * TEMPLATE_MANUAL  //手动,貌似需要硬件是full级别才能全支持,没详细了解
    **/
    CaptureRequest.Builder builder = CameraDevice.createCaptureRequest(@RequestTemplate int templateType);
    

    demo主要用到以下几个

    //1. 自动聚焦相关:
    CaptureRequest.Builder.set(CaptureRequest.CONTROL_AF_MODE,
                        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
    //2.自动曝光相关         
    CaptureRequest.Builder.set(CaptureRequest.CONTROL_AE_MODE,
                        CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
    //3.预览放大缩小
    CaptureRequest.Builder.set(CaptureRequest.SCALER_CROP_REGION, region);
    //4.自动控制模式
    CaptureRequest.Builder.set(CaptureRequest.CONTROL_MODE,CameraMetadata.CONTROL_MODE_AUTO);
    //5.手动触发对焦
    CaptureRequest.Builder.set(CaptureRequest.CONTROL_AF_TRIGGER,CameraMetadata.CONTROL_AF_TRIGGER_START);
    //6.手动触发曝光
    CaptureRequest.Builder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
    

    CameraCaptureSession,相机capture事务,获取或者处理相机图像类,
    当session创建成功后,会一直处于激活状态,直到该相机创建了新的session,或者相机关闭。
    因为createCaptureSession是较耗时的操作,大概需要几百毫秒的时间,所以是异步实现。

    获取方式 :相机调用createCaptureSession方法,在CameraCaptureSession.StateCallback回调中获取
    code如下:

    /**
    * 创建session请求
    **/
            CameraDevice.createCaptureSession(List<Surface> outputs,
                @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler
            );
    
    /**
    *回调onConfigured中获取到session
    **/
    private CameraCaptureSession.StateCallback callback= new CameraCaptureSession.StateCallback() {
            @Override
            public void onConfigured(@NonNull CameraCaptureSession session) {
                previewSession = session;
                //.....  
            }
        };
    

    ImageReader, 图像处理类,直接获取到渲染到surface对象上的图像数据
    拍照,预览数据的获取
    获取方式:

    1. 拍照相关
    //初始化
     ImageReader  jpegReader = ImageReader.newInstance(width, height,
                        ImageFormat.JPEG, 1); //返回格式是JPEG格式
                jpegReader.setOnImageAvailableListener(
                        jpegImageLister, backgroudHandler);
    //获取到数据
    ImageReader.OnImageAvailableListener jpegImageLister = new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader reader) {
                //照片处理流程
                Image image = reader.acquireLatestImage();
    
            }
        };
    2. 预览相关
    ImageReader nv21Reader = ImageReader.newInstance(width, height, ImageFormat.YUV_420_888, 2);
    nv21Reader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
                @Override
                public void onImageAvailable(ImageReader reader) {
                    //nv21格式处理相关
                    Image image = reader.acquireLatestImage();
                    if (image != null) {
                        image.close();
                    }
                }
            }, backgroudHandler);
    

    创建一个预览的session示例

    private void createCameraPreviewSession() throws CameraAccessException {
    
            SurfaceTexture texture = textureView.getSurfaceTexture();
            assert texture != null;
            texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
            Surface surface = new Surface(texture);
            mPreviewRequestBuilder
                    = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            mPreviewRequestBuilder.addTarget(surface);
            // capture到的image数据会输出到 surface, jpegReader.getSurface() 这两个指定的surfaces中
            cameraDevice.createCaptureSession(Arrays.asList(surface, jpegReader.getSurface()),
                    previewStateCallback, null
            );
        }
    
    

    预览返回数据转NV21代码

    /**
         * 根据image对象,转成人脸引擎需要的nv21格式数组
         *
         * @param image
         * @param colorFormat
         * @return
         */
        private byte[] getDataFromImage(Image image, int colorFormat) {
    
            long startTime = System.currentTimeMillis();
            if (colorFormat != COLOR_FormatI420 && colorFormat != COLOR_FormatNV21) {
                throw new IllegalArgumentException("only support COLOR_FormatI420 " + "and COLOR_FormatNV21");
            }
            if (!isImageFormatSupported(image)) {
                throw new RuntimeException("can't convert Image to byte array, format " + image.getFormat());
            }
            Rect crop = image.getCropRect();
            int format = image.getFormat();
            int width = crop.width();
            int height = crop.height();
            Image.Plane[] planes = image.getPlanes();
            byte[] data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8];
            byte[] rowData = new byte[planes[0].getRowStride()];
    
            int channelOffset = 0;
            int outputStride = 1;
            for (int i = 0; i < planes.length; i++) {
                switch (i) {
                    case 0:
                        channelOffset = 0;
                        outputStride = 1;
                        break;
                    case 1:
                        if (colorFormat == COLOR_FormatI420) {
                            channelOffset = width * height;
                            outputStride = 1;
                        } else if (colorFormat == COLOR_FormatNV21) {
                            channelOffset = width * height + 1;
                            outputStride = 2;
                        }
                        break;
                    case 2:
                        if (colorFormat == COLOR_FormatI420) {
                            channelOffset = (int) (width * height * 1.25);
                            outputStride = 1;
                        } else if (colorFormat == COLOR_FormatNV21) {
                            channelOffset = width * height;
                            outputStride = 2;
                        }
                        break;
                }
                ByteBuffer buffer = planes[i].getBuffer();
                int rowStride = planes[i].getRowStride();
                int pixelStride = planes[i].getPixelStride();
                int shift = (i == 0) ? 0 : 1;
                int w = width >> shift;
                int h = height >> shift;
                buffer.position(rowStride * (crop.top >> shift) + pixelStride * (crop.left >> shift));
                for (int row = 0; row < h; row++) {
                    int length;
                    if (pixelStride == 1 && outputStride == 1) {
                        length = w;
                        buffer.get(data, channelOffset, length);
                        channelOffset += length;
                    } else {
                        length = (w - 1) * pixelStride + 1;
                        buffer.get(rowData, 0, length);
                        for (int col = 0; col < w; col++) {
                            data[channelOffset] = rowData[col * pixelStride];
                            channelOffset += outputStride;
                        }
                    }
                    if (row < h - 1) {
                        buffer.position(buffer.position() + rowStride - length);
                    }
                }
    //            if (VERBOSE)
    //            Log.v(TAG, "Finished reading data from plane " + i);
            }
            return data;
        }
    

    效果图(本想做gif,但录屏的同时会相机也在录制视频,报start failed: -38异常,就没搞)


    1.jpg 2.jpg 4.jpg

    代码几个问题

    1. 相机屏幕适配只做了竖屏
      2.在小米4,红米note上录屏的时预览画面会压扁,没解决

    参考:
    Camera2Basic
    这个官方demo有时因为设备支持原因,导致拍照点击无响应,再点就好了。可以跳过ae,af这些回调直接进行拍照。
    Camera2Video
    这个官方demo连续拍视频的时候会异常,在重拍前调用session的abortCaptures,stopRepeating方法可以避免异常。
    CameraView
    UI相关的东西参考的这里,封装的很好,代码思路清晰,耦合度低。

    相关文章

      网友评论

        本文标题:android使用camera2的Api 实现拍照录视频的功能

        本文链接:https://www.haomeiwen.com/subject/xnvuxctx.html