Camera2 API 之 SurfaceView、TextureView、CameraManager、CameraDevice详解

从5.0开始(API Level 21),可以完全控制安卓设备相机的新api Camera2(android.hardware.Camera2)被引入了进来。在以前的Camera api(android.hardware.Camera)中,对相机的手动控制需要更改系统才能实现,而且api看着方便,但是不好管理。不过老的Camera API在5.0上已经过时(依然兼容),如今Android推荐使用Camera2采集视频,借着写这篇记录的过程,熟悉和理解Camera2流程。

专用名词

YUV
一种颜色编码的方法,在旧Camera API 常用的是NV21和YV12,可以转成RGB编码。

//获取Camera2 支持的颜色编码
StreamConfigurationMap map = characteristics.get( 
                             CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
map.getOutputFormats();

CameraManager
Camera2中负责管理、查询摄像头信息、打开可用的摄像头,这也是Camera2 API2与api1的区别之一:

  • 可以通过调用Context.getSystemService(java.lang.String)方法来获取一个CameraManager的实例;
  • cameraId 通过 getCameraIdList() 枚举得到,代表选择使用哪个摄像头;
  • 设备信息通过 CameraCharacteristics getCameraCharacteristics(String cameraId) 可拿到;
  • 打开摄像头 openCamera(String cameraId, CameraManager.StateCallback callback, Handler handler),StateCallback是接收设备状态的更新的回调,比如后面的cameraDevice就是通过stateCallback的onOpen()回调中拿到,Handler 表示打开摄像头的工作在具体哪个handler的looper中,也就是在哪个线程中执行,若为null则在当前线程;
 CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);

CameraDevice
具体的摄像头,提供一组属性信息,描述硬件设备以及设备的可用设置和参数。

  1. CameraDevice是在CameraManager Open Camera后,通过CameraDevice.StateCallback的回调中拿到的,是个异步的过程;
  2. createCaptureRequest() 创建CaptureRequest.Builder,CaptureRequest.Builder负责创建各种捕获图像的请求 CaptureRequest;
  3. createCaptureSession() 负责创建捕获图像的会话CameraCaptureSession;
manager.openCamera(mCameraId, stateCallback, mHandler);//打开Camera
/**摄像头状态的监听*/
    private CameraDevice.StateCallback stateCallback = new CameraDevice. StateCallback()
    {
        // 摄像头被打开时触发该方法
        @Override
        public void onOpened(CameraDevice cameraDevice){
            CameraDemoActivity.this.cameraDevice = cameraDevice;
            // 开始预览
            takePreview();
        }

        // 摄像头断开连接时触发该方法
        @Override
        public void onDisconnected(CameraDevice cameraDevice)
        {
            CameraDemoActivity.this.cameraDevice.close();
            CameraDemoActivity.this.cameraDevice = null;
        }
        // 打开摄像头出现错误时触发该方法
        @Override
        public void onError(CameraDevice cameraDevice, int error)
        {
            cameraDevice.close();
        }
    };

CaptureRequest

一次捕获请求,通过CaptureRequest.Builder的build()创建,其实请求参数也是通过Buider来设置:
CaptureRequest.Builder常用的方法:

  • addTarget(Surface outputTarget) 将surface添加到输出列表中,才可以显示在SurfaceView、TextureView或者输出到ImageReader中;
  • set(Key< T> key, T value) 设置其他属性;
//创建预览请求
            mCaptureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            // 设置自动对焦模式
            mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, 3);
            //设置Surface作为预览数据的显示界面
            mCaptureRequestBuilder.addTarget(mSurface);

此处用一段设置闪光灯的代码进行说明:

 private void openFlash(){
        mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
        mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
        //设置反复捕获数据的请求,这样预览界面就会一直有数据显示
        try {
            mPreviewSession.setRepeatingRequest(mCaptureRequest, null, mHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

CameraCaptureSession
捕获的会话Session,预览、拍照,都由该它进行控制的。

  • cameraCaptureSession是通过CameraDevice的 createCaptureSession(List< Surface>, CameraCaptureSession.StateCallback, Handler) 创建;
  • 拍照 capture(CaptureRequest, CameraCaptureSession.CaptureCallback, Handler);
  • 预览 setRepeatingRequest(CaptureRequest, CameraCaptureSession.CaptureCallback, Handler);
//创建相机捕获会话,第一个参数是捕获数据的输出Surface列表,第二个参数是CameraCaptureSession的状态回调接口,当它创建好后会回调onConfigured方法,第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行
            cameraDevice.createCaptureSession(Arrays.asList(mSurface,imageReader.getSurface()),new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(CameraCaptureSession session) {
                    Log.d("onConfigured","onConfigured");
                    try {
                        //开始预览
                        mCaptureRequest = mCaptureRequestBuilder.build();
                        mPreviewSession = session;
                        //设置反复捕获数据的请求,这样预览界面就会一直有数据显示
                        mPreviewSession.setRepeatingRequest(mCaptureRequest, null, mHandler);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void onConfigureFailed(CameraCaptureSession session) {
                    Log.d("onConfigureFailed","onConfigureFailed");
                }
            }, null);

在写代码的时候发现上面几个callback弄不清楚

  • open Camera中的CameraManager.StateCallback 是 camera 创建过程中状态回调;
  • createCaptureSession中的CameraCaptureSession.StateCallback 是session创建过程中的状态回调;
  • capture 或 setRepeatingRequest的CameraCaptureSession.CaptureCallback 是在预览或者拍照request请求之后的回调;

发现还有一个Handle一直跟着走,究竟是什么东西呢?

 //很多过程都变成了异步的了,所以这里需要一个子线程的looper
    private void initLooper() {
        mThreadHandler = new HandlerThread("CAMERA2");
        mThreadHandler.start();
        mHandler = new Handler(mThreadHandler.getLooper());
    }

TextureView预览

 SurfaceTexture mSurfaceTexture = surfaceView.getSurfaceTexture();
        //设置TextureView的缓冲区大小
        mSurfaceTexture.setDefaultBufferSize(1920, 1080);
        //获取Surface显示预览数据
        Surface mSurface = new Surface(mSurfaceTexture);

SurfaceView预览

 Surface surface = mSurfaceHolder.getSurface();

CameraCharacteristics
直接上代码:

 /**设置摄像头的参数*/
    private void setCameraCharacteristics(CameraManager manager)
    {
        try
        {
            // 获取指定摄像头的特性
            CameraCharacteristics characteristics
                    = manager.getCameraCharacteristics(mCameraId);
            // 获取摄像头支持的配置属性
            StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            // 获取摄像头支持的最大尺寸
            Size largest = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.YUV_420_888)),new CompareSizesByArea());
            List<Size> sizeList = Arrays.asList(map.getOutputSizes(ImageFormat.JPEG));
            for (Size s:sizeList) {
                Log.d(" Camera "," picturesize w " + s.getWidth() + " picturesize h = " + s.getHeight());
            }
            // 创建一个ImageReader对象,用于获取摄像头的图像数据
            imageReader = ImageReader.newInstance(mWidth, mHeight, ImageFormat.JPEG, 2);
            //设置获取图片的监听
            imageReader.setOnImageAvailableListener(imageAvailableListener,mHandler);
            // 获取最佳的预览尺寸
            previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), mWidth, mHeight, largest);
            Log.d(" Camera"," previewSize w " + previewSize.getWidth() + " previewSize h = " + previewSize.getHeight()  + " mWidth = " + mWidth + " mHeight = " +mHeight);
        }
        catch (CameraAccessException e)
        {
            e.printStackTrace();
        }
        catch (NullPointerException e)
        {
        }
    }
    private static Size chooseOptimalSize(Size[] choices
            , int width, int height, Size aspectRatio)
    {
        // 收集摄像头支持的大过预览Surface的分辨率
        List<Size> bigEnough = new ArrayList<>();
        int w = aspectRatio.getWidth();
        int h = aspectRatio.getHeight();
        for (Size option : choices)
        {
            if (option.getHeight() == option.getWidth() * h / w &&
                    option.getWidth() >= width && option.getHeight() >= height)
            {
                bigEnough.add(option);
            }
        }
        // 如果找到多个预览尺寸,获取其中面积最小的
        if (bigEnough.size() > 0)
        {
            return Collections.min(bigEnough, new CompareSizesByArea());
        }
        else
        {
            //没有合适的预览尺寸
            return choices[0];
        }
    }


    // 为Size定义一个比较器Comparator
    public static class CompareSizesByArea implements Comparator<Size>
    {
        @Override
        public int compare(Size lhs, Size rhs)
        {
            // 强转为long保证不会发生溢出
            return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
                    (long) rhs.getWidth() * rhs.getHeight());
        }
    }
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值