Android Camera2 使用总结

最近在作自定义相机相关的项目,网上查了资料都是有关android.hardware.Camera的资料,开始使用的才发现这个类已经废弃了。Android 5.0(21)以后android.hardware.Camera就被废弃了,取而代之的是全新的android.hardware.Camera2Android 5.0对拍照API进行了全新的设计,新增了全新设计的Camera v2 API,这些API不只大幅提升了Android系统拍照的功能,还能支持RAW照片输出,甚至容许程序调整相机的对焦模式、曝光模式、快门等。html

demo 地址https://github.com/Hemumu/WallpaperDemoandroid

Camera2主要的类说明git

  • CameraManager:摄像头管理器。这是一个全新的系统管理器,专门用于检测系统摄像头、打开系统摄像头。除此以外,调用CameraManagergetCameraCharacteristics(String)方法便可获取指定摄像头的相关特性。
  • CameraCharacteristics:摄像头特性。该对象经过CameraManager来获取,用于描述特定摄像头所支持的各类特性。
  • CameraDevice:表明系统摄像头。该类的功能相似于早期的Camera类。
  • CameraCaptureSession:这是一个很是重要的API,当程序须要预览、拍照时,都须要先经过该类的实例建立Session。并且无论预览仍是拍照,也都是由该对象的方法进行控制的,其中控制预览的方法为setRepeatingRequest();控制拍照的方法为capture()
  • CameraRequestCameraRequest.Builder:当程序调用setRepeatingRequest()方法进行预览时,或调用capture()方法进行拍照时,都须要传入CameraRequest参数。CameraRequest表明了一次捕获请求,用于描述捕获图片的各类参数设置,好比对焦模式、曝光模式……总之,程序须要对照片所作的各类控制,都经过CameraRequest参数进行设置。CameraRequest.Builder则负责生成CameraRequest对象。

开启相机预览github

开启相机请必定添加相关的相机权限,判断6.0之后添加动态权限的获取。若是相机预览出现黑屏多半就是由于没有相机权限而致使的微信

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />

首页咱们要设置相机相关的参数网络

CameraManager manager = (CameraManager) ctx.getSystemService(Context.CAMERA_SERVICE);
        try {
            //获取可用摄像头列表
            for (String cameraId : manager.getCameraIdList()) {
                //获取相机的相关参数
                CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
                // 不使用前置摄像头。
                Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
                if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
                    continue;
                }
                StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                if (map == null) {
                    continue;
                }
                // 检查闪光灯是否支持。
                Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
                mFlashSupported = available == null ? false : available;
                mCameraId = cameraId;
                Log.e(TAG," 相机可用 ");
                return;
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        } catch (NullPointerException e) {
            //不支持Camera2API
        }
    }

经过getSystemService(Context.CAMERA_SERVICE);拿到了CameraManager 返回当前可用的相机列表,在这里你能够选择使用前置仍是后置摄像头。CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);能够拿到当前相机的相关参数,在这里你能够进行相关的参数检查,例如检查闪光灯是否支持等。在这里咱们拿到当前相机的cameraId后面使用。session

拿到cameraId咱们就能够调用CameraManageropenCamera(String cameraId, CameraDevice.StateCallback callback, Handler handler)打开相机了ide

CameraManager manager = (CameraManager) ctx.getSystemService(Context.CAMERA_SERVICE);
        try {
            //打开相机预览
            manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
        }

添加 CameraDevice.StateCallback 监听post

咱们须要对相机状态就行监听,以便在相机状态发生改变的时候作相应的操做。openCamera(String cameraId, CameraDevice.StateCallback callback, Handler handler)CameraDevice.StateCallback就是对相机的状态改变的Callback学习

private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

        @Override
        public void onOpened(@NonNull CameraDevice cameraDevice) {
            mCameraDevice = cameraDevice;
            //建立CameraPreviewSession
            createCameraPreviewSession();
        }

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

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

    };

建立 CameraCaptureSession

onOpened()中咱们能够拿到CameraDevice对象,在相机打开后须要建立CameraCaptureSession
CameraCaptureSession是什么呢?因为Camera2是一套全新的API,因此它引用了管道的概念将安卓设备和摄像头之间联通起来,系统向摄像头发送 Capture 请求,而摄像头会返回 CameraMetadata。这一切创建在一个叫做 CameraCaptureSession的会话中。以下图:

 

2086682-e68d187e1240bfc5.png


图片来自http://wiki.jikexueyuan.com/project/android-actual-combat-skills/android-hardware-camera2-operating-guide.html

 

这里咱们须要预览相机的内容就须要建立CameraCaptureSession向相机发送Capture请求预览相机内容

/**
     * 为相机预览建立新的CameraCaptureSession
     */
    private void createCameraPreviewSession() {


        try {
            //设置了一个具备输出Surface的CaptureRequest.Builder。
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            mPreviewRequestBuilder.addTarget(mSurfaceHolder.getSurface());
            //建立一个CameraCaptureSession来进行相机预览。
            mCameraDevice.createCaptureSession(Arrays.asList(mSurfaceHolder.getSurface()),
                    new CameraCaptureSession.StateCallback() {

                        @Override
                        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                            // 相机已经关闭
                            if (null == mCameraDevice) {
                                return;
                            }
                            // 会话准备好后,咱们开始显示预览
                            mCaptureSession = cameraCaptureSession;
                            try {
                                // 自动对焦应
                                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                                        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                                // 闪光灯
                                setAutoFlash(mPreviewRequestBuilder);
                                // 开启相机预览并添加事件
                                mPreviewRequest = mPreviewRequestBuilder.build();
                                //发送请求
                                mCaptureSession.setRepeatingRequest(mPreviewRequest,
                                        null, mBackgroundHandler);
                                Log.e(TAG," 开启相机预览并添加事件");
                            } catch (CameraAccessException e) {
                                e.printStackTrace();
                            }
                        }

                        @Override
                        public void onConfigureFailed(
                                @NonNull CameraCaptureSession cameraCaptureSession) {
                            Log.e(TAG," onConfigureFailed 开启预览失败");
                        }
                    }, null);
        } catch (CameraAccessException e) {
            Log.e(TAG," CameraAccessException 开启预览失败");
            e.printStackTrace();
        }
    }

首先咱们建立了一个CaptureRequest 上面说过了咱们须要跟相机通讯只有经过CameraCaptureSession。而要和CameraCaptureSession通讯就是发送请求。这里咱们至关于在建立请求的一些参数。
createCaptureRequest(int); 也有不少参数可选。这里咱们发送的是CameraDevice.TEMPLATE_PREVIEW也就是告诉相机咱们只须要预览。更多参数以下

详细信息能够参考官网的API文档。

调用建立方法createCaptureSession(List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler handler) 第一参数就是咱们须要输出到的Surface列表,这里咱们能够输出到一个SurfaceView中或者TextureView中。第二参数是对建立过程的一个回调方法,当onConfigured回调的时候说明CameraCaptureSession建立成功了。如今咱们能够向CameraCaptureSession发送前面建立的好的预览相机请求了。调用mCaptureSession.setRepeatingRequest(mPreviewRequest,null, mBackgroundHandler); 这样咱们就开启的相机的预览,在刚才添加的输出Surface对应的控件中咱们能够看到摄像头的预览内容了。

拍照

当咱们须要拍照而且获得相应的照片数据的时候和开启相机预览相同的操做,咱们只须要向CameraCaptureSession发送咱们建立好的请求就行,就像咱们请求网络数据同样,封装好参数直接告诉CameraCaptureSession须要作什么由它去和相机创建通讯并执行相应的操做。

对焦

/**
     * 将焦点锁定为静态图像捕获的第一步。(对焦)
     */
    private void lockFocus() {
        try {
            // 相机对焦
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
                    CameraMetadata.CONTROL_AF_TRIGGER_START);
            // 修改状态
            mState = STATE_WAITING_LOCK;
            mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
                    mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

CameraCaptureSession发送对焦请求,而且对对焦是否成功进行监听,在mCaptureCallback中对回调进行处理

/**
     * 处理与JPEG捕获有关的事件
     */
    private CameraCaptureSession.CaptureCallback mCaptureCallback
            = new CameraCaptureSession.CaptureCallback() {

        //处理
        private void process(CaptureResult result) {
            switch (mState) {
                case STATE_PREVIEW: {
                    //预览状态
                    break;
                }

                case STATE_WAITING_LOCK: {
                    //等待对焦
                    Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
                    if (afState == null) {
                        captureStillPicture();
                    } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState ||
                            CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {
                        // CONTROL_AE_STATE can be null on some devices
                        Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                        if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
                            mState = STATE_PICTURE_TAKEN;
                            //对焦完成 
                            captureStillPicture();  
                        } else {
                            runPrecaptureSequence();
                        }
                    }
                    break;
                }
                
                
                }
            }
        }

拍摄图片

对焦完成后咱们就能够向CameraCaptureSession发送请求能够拍照了

/**
     * 
     * 拍摄静态图片。
     */
    private void captureStillPicture() {
        try {
            if ( null == mCameraDevice) {
                return;
            }
            // 这是用来拍摄照片的CaptureRequest.Builder。
            final CaptureRequest.Builder captureBuilder =
                    mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            captureBuilder.addTarget(mImageReader.getSurface());

            // 使用相同的AE和AF模式做为预览。
            captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                    CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            setAutoFlash(captureBuilder);
            // 方向
            int rotation = this.getWindowManager().getDefaultDisplay().getRotation();
            captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));

            CameraCaptureSession.CaptureCallback CaptureCallback
                    = new CameraCaptureSession.CaptureCallback() {
                @Override
                public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                               @NonNull CaptureRequest request,
                                               @NonNull TotalCaptureResult result) {
                    showToast("Saved: " + mFile);
                    Log.d(TAG, mFile.toString());
                    unlockFocus();
                }
            };
            //中止连续取景
            mCaptureSession.stopRepeating();
            //捕获图片
            mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

相信看到这里代码已经不复杂了,组装好咱们的请求而后用CameraCaptureSession发送这个请求就能够了。这里须要注意的是咱们怎么拿到图片数据呢? 这里要说回在建立CameraCaptureSession时参数不是有一个输出的Surface列表么,在列表中添加一个ImageReaderSurface用户获取图片数据

mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),null).....

ImageReader中对图片获取就行监听

mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);

private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
            = new ImageReader.OnImageAvailableListener() {

        @Override
        public void onImageAvailable(ImageReader reader) {
            //当图片可获得的时候获取图片并保存
            mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
        }

    };

调用reader.acquireNextImage()咱们就能够拿到当前的图片数据了。拍完后咱们须要解锁焦点让相机回到预览状态,一样的咱们发送请求就能够了

/**
     * 解锁焦点
     */
    private void unlockFocus() {
        try {
            // 重置自动对焦
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
                    CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
            setAutoFlash(mPreviewRequestBuilder);
            mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
                    mBackgroundHandler);
            // 将相机恢复正常的预览状态。
            mState = STATE_PREVIEW;
            // 打开连续取景模式
            mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback,
                    mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

效果

GIF.gif

以前看了鸿神推送的Android 仿火萤视频桌面 神奇的LiveWallPaper 一文中提到了用相机来作壁纸也就是透明屏幕,项目地址https://github.com/songixan/Wallpaper 查看源码发现仍是用的旧的CameraAPI,因此我在Demo中用Camera2API作了透明屏幕,有兴趣的能够去看下。 ** PS:后来在同事的小米2S(5.1.1)中测试发现出错了,初步猜想是分辨率的缘由,目前正在解决中。有问题你们能够私信我 谢谢~**

到此Camera2的学习就结束了,一样的若是你想用相机就行拍摄视频也是如此,用CameraCaptureSession发送相应的请求了就能够了,你们有兴趣能够作一作视频的拍摄。如今作微信的小10秒小视频拍摄也很简单了,思路很简单当用户按下拍摄按钮用CameraCaptureSession发送拍摄视频的请求,松开手指的时候相机恢复到预览状态。so easy~ 你们有兴趣能够尝试下。

拍摄视频的Demohttps://github.com/googlesamples/android-Camera2Video

Tinks android.hardware.camera2 使用指南

相关文章
相关标签/搜索