Android音视频开发入门(四)

Android音视频开发入门(四)

      • 任务目标
      • NV21简单介绍
      • 对camera2 API中的类的简单说明
        • CameraManager
        • CameraDevice
        • CaptureRequest
        • CameraCaptureSession
      • 实现过程
        • 1.首先创建一个执行Camera的线程和Handler
        • 2.获取相机的cameraId和预览尺寸
        • 3.打开摄像头
        • 4.实现mStateCallback监听
        • 5.设置ImageReader用于拍照获取数据
        • 6.开始预览
        • 7.拍照
        • 8.停止预览
        • 9.获取NV21数据
      • 参考资料

任务目标

本篇的任务目标是使用Camera API进行视频的采集,分别用SurfaceView和TextureView预览Camera数据,并获取到NV21数据回调。
因为camera api在5.0之后标记过时了,所以这儿使用的是camera2 api来实现这些功能。对于使用camera api预览数据在之前一篇博文Camera API笔记中有简单的介绍。也可以参考这篇文章。

NV21简单介绍

NV21是Android Camera默认的图像格式。什么是NV21格式呢?NV21是一种YUV(又称YCbCr) 图像格式. Y表示亮度值(Luminance),V和U分别代表蓝色(Cb)和红色(Cr)色度(Chrominance)。 一个2×2的像素, 4个Y对应1个V和1个U。

对camera2 API中的类的简单说明

CameraManager

一个用于检测、表征、连接 CameraDevices 的系统服务管理。官网是这样介绍的 A system service manager for detecting, characterizing, and connecting to CameraDevices.

  1. CameraManager实例必须通过带 CameraManager.class 参数的 Context.getSystemService(Class) 方法或者带 Context.CAMERA_SERVICE 参数的 Context.getSystemService(String) 方法获得;
  2. cameraId 可通过 getCameraIdList() 得到,注意区分前后摄像头;
  3. 设备信息可通过 CameraCharacteristics getCameraCharacteristics(String cameraId) 拿到;
  4. 打开摄像头 openCamera(String cameraId, CameraManager.StateCallback callback, Handler handler),StateCallback是接收设备状态的更新的回调,比如后面的cameraDevice就是通过stateCallback的onOpen()回调中拿到,Handler 表示打开摄像头的工作在具体哪个handler的looper中,也就是在哪个线程中执行,若为null则在当前线程

CameraDevice

代表具体连接到那个摄像头。
打开成功在CameraDevice.StateCallback.onOpened(CameraDevice) 回调中可获得 CameraDevice 实例,然后可以通过调用CameraDevice.createCaptureSession(SessionConfiguration)和CameraDevice.createCaptureRequest(int)来设置摄像机设备捕获图像的会话回调和请求。

CaptureRequest

一个捕获请求,可通过 CaptureRequest.Builder 的build() 方法获得。
可通过addTarget(Surface)方法添加一个目标显示,之后才能在SurfaceView、TextureView或ImageReader显示。其他属性设置通过set(Key key, T value) 方法。

CameraCaptureSession

相机捕获会话,可以通过其进行相机的预览与拍照等。
可以通过为creationCaptureSession() 方法提供一组输出显示来创建一个CamerCaptureSession,调用setRepeatingRequest() 方法开始预览,调用capture()方法进行拍照。

实现过程

1.首先创建一个执行Camera的线程和Handler

	private void startCameraThread() {
        mCameraThread = new HandlerThread("CameraThread");
        mCameraThread.start();
        mCameraHandler = new Handler(mCameraThread.getLooper());
	}

2.获取相机的cameraId和预览尺寸

	/**
     * 得到相机id和预览大小
     */
    private void generalCameraIdAndSize(int width, int height) {
        try {
            for (String cameraId : mCameraManager.getCameraIdList()) {
                //查询摄像头属性
                CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId);
                Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
                //默认打开后置摄像头
                if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
                    continue;
                }
                //获取StreamConfigurationMan,它管理着摄像头支持的输出格式和尺寸
                StreamConfigurationMap configurationMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                if (configurationMap == null) {
                    continue;
                }
                //获取预览尺寸
                mPreviewSize = chooseOptimalSize(configurationMap.getOutputSizes(SurfaceTexture.class), width, height);
                mCameraId = cameraId;
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

	private Size chooseOptimalSize(Size[] outputSizes, int width, int height) {
        List sizeList = new ArrayList<>();
        for (Size size : outputSizes) {
            if (width > height) {
                if (size.getWidth() > width && size.getHeight() > height) {
                    sizeList.add(size);
                }
            } else {
                if (size.getWidth() > height && size.getHeight() > width) {
                    sizeList.add(size);
                }
            }
        }
        if (sizeList.size() > 0) {
            return Collections.min(sizeList, new Comparator() {
                @Override
                public int compare(Size o1, Size o2) {
                    return Long.signum(o1.getWidth() * o1.getHeight() - o2.getWidth() * o2.getHeight());
                }
            });
        }
        return outputSizes[0];
    }

3.打开摄像头

	try {
            mCameraManager.openCamera(mCameraId, mStateCallback, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

4.实现mStateCallback监听

	/**
     * 创建时的状态回调
     */
    private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            mCameraDevice = camera;
            //在开启预览前设置
            setupImageReader();
            if (isSurfaceView) {
                startSurfaceViewPreview();
            } else {
                startTextureViewPreview();
            }
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            if (mCameraDevice != null) {
                mCameraDevice.close();
                mCameraDevice = null;
            }
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            if (mCameraDevice != null) {
                mCameraDevice.close();
                mCameraDevice = null;
            }
            finish();
        }
    };

5.设置ImageReader用于拍照获取数据

	/**
     * 设置ImageReader
     */
    private void setupImageReader() {
        //前面三个参数分别是需要的尺寸和格式,最后一个是一次获取几帧数据
        mImageReaderPreview = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.JPEG, 2);
        //监听ImageReader事件,当有可用的图像流数据时回调,它的参数就是预览帧数据
        mImageReaderPreview.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader reader) {
                //获取捕获的照片数据
                Image image = reader.acquireLatestImage();
                mCameraHandler.post(new ImageSaver(image, mFile));
            }
        }, null);
    }

6.开始预览

SurfaceView

	/**
     * SurfaceView开始预览
     */
    private void startSurfaceViewPreview() {
        //获取输出的surface
        Surface surface = mSurfaceView.getHolder().getSurface();
        try {
            final CaptureRequest.Builder captureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            //添加输出的surface
            captureRequest.addTarget(surface);
            captureRequest.addTarget(mImageReaderPreview.getSurface());
            //创建CameraCaptureSession时加上mImageReaderPreview.getSurface(),这样预览数据就同时输出到两个surface了
            mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReaderPreview.getSurface()), new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    mCameraCaptureSession = session;
                    //自动对焦
                    captureRequest.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                    CaptureRequest request = captureRequest.build();
                    try {
                        mCameraCaptureSession.setRepeatingRequest(request, null, mCameraHandler);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession session) {

                }
            }, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

TextureView

	/**
     * TextureView开始预览
     */
    private void startTextureViewPreview() {
        SurfaceTexture texture = mTextureView.getSurfaceTexture();
        texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
        Surface surface = new Surface(texture);
        try {
            final CaptureRequest.Builder captureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            captureRequest.addTarget(surface);
            captureRequest.addTarget(mImageReaderPreview.getSurface());
            mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReaderPreview.getSurface()), new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    mCameraCaptureSession = session;
                    CaptureRequest request = captureRequest.build();
                    try {
                        mCameraCaptureSession.setRepeatingRequest(request, null, mCameraHandler);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession session) {

                }
            }, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

7.拍照

	/**
     * 拍照,点击之前请先预览
     */
    public void takePhoto(View view) {
        //创建请求拍照的CaptureRequest
        try {
            final CaptureRequest.Builder captureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            //获取屏幕方向
            int rotation = getWindowManager().getDefaultDisplay().getRotation();
            //设置CaptureRequest输出到ImageReader
            captureRequestBuilder.addTarget(mImageReaderPreview.getSurface());
            if (isSurfaceView) {
                captureRequestBuilder.addTarget(mSurfaceView.getHolder().getSurface());
            } else {
                captureRequestBuilder.addTarget(new Surface(mTextureView.getSurfaceTexture()));
            }
            //设置拍照方向
            captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));
            //拍照完成后重启预览,因为拍照后会导致预览停止
            final CameraCaptureSession.CaptureCallback mCaptureCallback = new CameraCaptureSession.CaptureCallback() {
                @Override
                public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                               @NonNull CaptureRequest request,
                                               @NonNull TotalCaptureResult result) {
                    //重启预览
                    try {
                        mCameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, mCameraHandler);
                        Toast.makeText(CameraPreviewActivity.this, "拍照成功", Toast.LENGTH_SHORT).show();
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
                }
            };
            //停止预览
            mCameraCaptureSession.stopRepeating();
            //调用拍照方法
            mCameraCaptureSession.capture(captureRequestBuilder.build(), mCaptureCallback, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

8.停止预览

	/**
     * 停止预览
     */
    private void stopPreview() {
        if (mCameraCaptureSession != null) {
            mCameraCaptureSession.close();
            mCameraCaptureSession = null;
        }
        if (mCameraDevice != null) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
        if (mImageReaderPreview != null) {
            mImageReaderPreview.close();
            mImageReaderPreview = null;
        }
    }
    /**
     * 停止camera线程和其handler
     */
    private void stopCameraThread() {
        mCameraThread.quitSafely();
        try {
            mCameraThread.join();
            mCameraThread = null;
            mCameraHandler = null;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

9.获取NV21数据

这里是官方Demo中的一段,我理解的是camera2 API 没有PreviewCallback回调,通过image获取的字节数组就相当于camera1的PreviewCallback回调预览数据,不知道对不对,如有错误请告知。

	/**
     * Saves a JPEG {@link Image} into the specified {@link File}.
     */
    private static class ImageSaver implements Runnable {

        /**
         * The JPEG image
         */
        private final Image mImage;
        /**
         * The file we save the image into.
         */
        private final File mFile;

        ImageSaver(Image image, File file) {
            mImage = image;
            mFile = file;
        }

        @Override
        public void run() {
            //我们可以将这帧数据转成字节数组,类似于Camera1的PreviewCallback回调的预览帧数据
            ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
            byte[] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);
            FileOutputStream output = null;
            try {
                output = new FileOutputStream(mFile);
                output.write(bytes);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                mImage.close();
                if (null != output) {
                    try {
                        output.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }

至此,预览及采集数据完成。代码已上传Github

参考资料

官方文档
Camera2 API 采集视频并SurfaceView、TextureView 预览

你可能感兴趣的:(Android音视频入门)