该博客主要记录在开发过程中所运用到的Api 和部分技术调用的记录,阅读时间:15分钟+ ,该博客记录内容相对简单,仅以用于开发过程记录。


说明

在我们前一篇文章中提到Camera,在开发中发现很多api 都已经不推荐使用,google给出的替代方案则就是我们今天的主角 Camera2 ,从5.0开始(API Level 21),可以完全控制Android设备相机的新api 。当然如果产品覆盖的还是有4.0版本的Android 用户的话,还是建议 使用Camera。但是在以前的Camera 中,对相机的手动控制都是通过更改系统才能实现,而且api也不友好。Camera2 这时针对这一点进行了管理的分离,Api会更加的友好,分工明确。

文章对应 Demo 地址google demo 地址文章参考地址 很感谢该篇文章作者的描述。

效果图

android 自定义textureview相机 android自定义相机开发_自定义Camera2

1. camera2 概念

android 自定义textureview相机 android自定义相机开发_自定义Camera2_02


相对于camera ,camera2 在Api上将拍照对象进行了独立,camera2采用pipeline的方式,将Camera 设备和 Android 设备连接起来,Android Device通过管道发送CaptureRequest拍照请求给Camera Device,Camera Device通过管道返回CameraMetadata数据给Android Device,这一切都发生在CameraCaptureSession的会话中。

2. camera2 API简要说明

android 自定义textureview相机 android自定义相机开发_ide_03

CameraCaptureSession:这是一个非常重要的API,当程序需要预览、拍照时,都通过该类的实例创建Session,控制预览的方法setRepeatingRequest();控制拍照的方法为capture()。

CameraDevices:提供一组静态属性信息,描述硬件设备以及设备的可用设置和输出参数。通过getCameraCharacteristics获得。

CameraManager:所有相机设备(CameraDevice)的管理者,要枚举,查询和打开可用的相机设备,用于打开和关闭系统摄像头,就获取CameraManager实例。

CaptureRequest:定义了相机设备捕获单个映像所需的所有捕获参数。该请求还列出了哪些配置的输出表面应该用作此捕获的目标。

CameraDevice:具有用于为给定用例创建请求构建器的工厂方法,针对应用程序正在运行的Android设备进行了优化,描述系统摄像头,类似于早期的Camera。

CameraRequest CameraRequest.Builder:当程序调用setRepeatingRequest()方法进行预览时,或调用capture()方法进行拍照时,都需要传入CameraRequest参数。CameraRequest代表了一次捕获请求,用于描述捕获图片的各种参数设置,程序对照片所做的各种控制,都通过CameraRequest参数进行设置。CameraRequest.Builder则负责生成CameraRequest对象。

CameraCharacteristics:描述摄像头的各种特性,我们可以通过CameraManager的getCameraCharacteristics(@NonNull String cameraId)方法来获取。

CaptureResult:描述拍照完成后的结果。

ImageReader :通过添加 PreviewRequestBuilder.addTarget(mImageReader.getSurface()); 可以在 OnImageAvailableListener 接口中实时获取 yuv 数据。

android 自定义textureview相机 android自定义相机开发_ide_04

3.Camera2接口使用的流程

android 自定义textureview相机 android自定义相机开发_Android_05


1.调用openCamera方法后会回调CameraDevice.StateCallback这个方法,在该方法里重写onOpened函数。

2.在onOpened方法中调用createCaptureSession,该方法又回调CameraCaptureSession.StateCallback方法。

3.CameraCaptureSession.StateCallback中重写onConfigured方法,设置setRepeatingRequest方法(也就是开启预览)。

4.setRepeatingRequest又会回调 CameraCaptureSession.CaptureCallback方法。

5.重写CameraCaptureSession.CaptureCallback中的onCaptureCompleted方法,result就是未经过处理的帧数据了。

4. 自定义Camera2

Camera2与Camera一样也有cameraId的概念,我们通过mCameraManager.getCameraIdList()来获取cameraId列表,然后通过mCameraManager.getCameraCharacteristics(id) 获取每个id对应摄像头的参数。

关于CameraCharacteristics里面的参数,主要用到的有以下几个:

LENS_FACING:前置摄像头(LENS_FACING_FRONT)还是后置摄像头(LENS_FACING_BACK)。

SENSOR_ORIENTATION:摄像头拍照方向。

FLASH_INFO_AVAILABLE:是否支持闪光灯。

CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL:获取当前设备支持的相机特性。

注:事实上,在各个厂商的的Android设备上,Camera2的各种特性并不都是可用的,需要通过characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)方法 来根据返回值来获取支持的级别,具体说来:

INFO_SUPPORTED_HARDWARE_LEVEL_FULL:全方位的硬件支持,允许手动控制全高清的摄像、支持连拍模式以及其他新特性。

INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED:有限支持,这个需要单独查询。

INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY:所有设备都会支持,也就是和过时的Camera API支持的特性是一致的。

利用这个INFO_SUPPORTED_HARDWARE_LEVEL参数,我们可以来判断是使用Camera还是使用Camera

通过上面的 原理的说明,大致流程可能还会有点模糊,我们直接对应上述逻辑开始代码。

4.1 界面布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/black"
    tools:context="cn.tongue.tonguecamera.ui.CameraActivity">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
		// 自定义 TextureView
        <cn.tongue.tonguecamera.view.AutoFitTextureView
            android:id="@+id/textureView_g"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </FrameLayout>

    <RelativeLayout
        android:id="@+id/homecamera_bottom_relative2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#00ffffff"
        android:layout_alignParentBottom="true">
		// 返回按钮
        <ImageView
            android:id="@+id/iv_back_g"
            android:layout_width="40dp"
            android:layout_height="30dp"
            android:scaleType="centerInside"
            android:layout_marginBottom="20dp"
            android:layout_marginStart="20dp"
            android:layout_centerVertical="true"
            android:background="@drawable/icon_back" />
		// 拍照按钮
        <ImageView
            android:id="@+id/img_camera_g"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:scaleType="centerInside"
            android:layout_marginBottom="20dp"
            android:layout_centerInParent="true"
            android:background="@drawable/camera" />

    </RelativeLayout>

</RelativeLayout>

通过上述布局我们会发现 camera2 中我们并没有继续选择 SurfaceView作为呈现图片的载体,这里选择的TextureView。

@RequiresApi(api = Build.VERSION_CODES.M)
    @Override
    public void onResume() {
        super.onResume();
        // 启动 HandlerThread ,后台维护一个 handler
        startBackgroundThread();
        // 存在关联则打开相机,没有则绑定事件
        if (mTextureView.isAvailable()) {
            openCamera(mTextureView.getWidth(), mTextureView.getHeight());
        } else {
        	// 绑定 载体监听事件
            mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
        }
    }

如果是第一次进入 就需要配置 相机相关配置

/**
     * 打开相机
     *
     * @param width 宽度
     * @param height 长度
     */
    @RequiresApi(api = Build.VERSION_CODES.M)
    private void openCamera(int width, int height) {
    	// 判断相机权限 6.0 以上的动态权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
            return;
        }
        // 配置 相机的 预览尺寸
        setUpCameraOutputs(width, height);
        // Matrix 转换配置为 mTextureView
        configureTransform(width, height);
        CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
        try {
            if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
                throw new RuntimeException("Time out waiting to lock camera opening.");
            }
            // 都配置完成后 打开相机 并绑定回调接口
            manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
        }
    }

上述代码中,setUpCameraOutputs() 方法主要进行 相机参数的设置(前后摄像头,闪光灯的配置),设置mImageReader 的成像格式及数据流的回调监听事件 OnImageAvailableListener,并且根据硬件数据 查看是否需要交换尺寸以获得相对于传感器的预览尺寸。

/**
     * CameraDevice 改变状态时候 调用
     */
    private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

		// 打开事件监听
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        @Override
        public void onOpened(@NonNull CameraDevice cameraDevice) {
            //打开相机时会调用此方法。 我们在这里开始相机预览。
            mCameraOpenCloseLock.release();
            mCameraDevice = cameraDevice;
            createCameraPreviewSession();
        }

		// 关闭监听
        @Override
        public void onDisconnected(@NonNull CameraDevice cameraDevice) {
            mCameraOpenCloseLock.release();
            cameraDevice.close();
            mCameraDevice = null;
        }

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

    };

从回调方法中,我们 通过 CameraCaptureSession 开始 预览图像,createCameraPreviewSession() 则是创建一个 相机预览。

/**
     * 创建一个新的相机预览
     */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void createCameraPreviewSession() {
        try {
            SurfaceTexture texture = mTextureView.getSurfaceTexture();
            //将默认缓冲区的大小配置为相机预览的大小。
            texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
            Surface surface = new Surface(texture);
            //  Camera2都是通过创建请求会话的方式进行调用的
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            // 使用Surface设置CaptureRequest.Builder
            mPreviewRequestBuilder.addTarget(surface);
            // 方法创建CaptureSession。
            mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.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,
                                        mCaptureCallback, mBackgroundHandler);
                            } catch (CameraAccessException e) {
                                e.printStackTrace();
                            }
                        }

                        @Override
                        public void onConfigureFailed(
                                @NonNull CameraCaptureSession cameraCaptureSession) {
                        }
                    }, null
            );
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

createCaptureRequest()方法里参数templateType代表了请求类型,请求类型一共分为六种,分别为:
TEMPLATE_PREVIEW:创建预览的请求
TEMPLATE_STILL_CAPTURE:创建一个适合于静态图像捕获的请求,图像质量优先于帧速率。
TEMPLATE_RECORD:创建视频录制的请求
TEMPLATE_VIDEO_SNAPSHOT:创建视视频录制时截屏的请求
TEMPLATE_ZERO_SHUTTER_LAG:创建一个适用于零快门延迟的请求。在不影响预览帧率的情况下最大化图像质量。
TEMPLATE_MANUAL:创建一个基本捕获请求,这种请求中所有的自动控制都是禁用的(自动曝光,自动白平衡、自动焦点)。

createCaptureSession()方法一共包含三个参数:
1.List outputs:我们需要输出到的Surface列表。
2.CameraCaptureSession.StateCallback callback:会话状态相关回调。
3.Handler handler:callback可以有多个(来自不同线程),这个handler用来区别那个callback应该被回调,一般写当前线程的Handler即可。

关于CameraCaptureSession.StateCallback里的回调方法
1.onConfigured(@NonNull CameraCaptureSession session); 摄像头完成配置,可以处理Capture请求了。
2.onConfigureFailed(@NonNull CameraCaptureSession session); 摄像头配置失败
3.onReady(@NonNull CameraCaptureSession session); 摄像头处于就绪状态,当前没有请求需要处理。

onActive(@NonNull CameraCaptureSession session); 摄像头正在处理请求。
onClosed(@NonNull CameraCaptureSession session); 会话被关闭
onSurfacePrepared(@NonNull CameraCaptureSession session, @NonNull Surface surface); Surface准备就绪
理解了这些东西,创建预览请求就十分简单了。

设置预览界面尺寸信息,Surface就把它与CaptureRequestBuilder对象关联,然后就是设置会话开始捕获画面。最后的回调CameraCaptureSession.CaptureCallback就给我们设置预览完成的逻辑处理

/**
     * ImageReader 的回调对象。 静止图像已准备好保存。
     */
    private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
            = new ImageReader.OnImageAvailableListener() {
        @Override
        public void onImageAvailable(ImageReader reader) {
            Log.e(TAG, "onImageAvailable:-------------------");
            Image image = reader.acquireLatestImage();
            //我们可以将这帧数据转成字节数组,类似于Camera1的PreviewCallback回调的预览帧数据
            ByteBuffer buffer = image.getPlanes()[0].getBuffer();
            byte[] data = new byte[buffer.remaining()];
            buffer.get(data);
            image.close();
//            mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
        }

    };

最后这里的方法 就是同 camera 中的 实时帧数据,这里我们可以在这里获取 静止图片的 帧数据,进行一些 图层 水印的处理。google 源码中 一直维系了一个mBackgroundHandler ,我们可以在这里发送 子线程任务reade.acquireNextImage方法获取 静止图片信息,将图片保存到本地文件夹中,基本流程完成后,我们只需要看一下如何触发 拍照即可。

/**
     * 锁定焦点设置
     */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void lockFocus() {
        try {
            // 相机锁定的方法 (设置相机对焦)
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
                    CameraMetadata.CONTROL_AF_TRIGGER_START);
            // mCaptureCallback 等待锁定  //修改状态
            mState = STATE_WAITING_LOCK;
            //发送对焦请求
            mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
                    mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

通过点击事件,调用相机锁定 ,设置 mCaptureSession.capture,

/**
     * 处理与jpg文件捕捉的事件监听(预览)
     */
    private CameraCaptureSession.CaptureCallback mCaptureCallback
            = new CameraCaptureSession.CaptureCallback() {

        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        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) {
                        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;
                }
                case STATE_WAITING_PRECAPTURE: {
                    Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                    if (aeState == null ||
                            aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE ||
                            aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {
                        mState = STATE_WAITING_NON_PRECAPTURE;
                    }
                    break;
                }
                case STATE_WAITING_NON_PRECAPTURE: {
                    Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                    if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
                        mState = STATE_PICTURE_TAKEN;
                        captureStillPicture();
                    }
                    break;
                }
                default:
                    break;
            }
        }

        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        @Override
        public void onCaptureProgressed(@NonNull CameraCaptureSession session,
                                        @NonNull CaptureRequest request,
                                        @NonNull CaptureResult partialResult) {
            process(partialResult);
        }

        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        @Override
        public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                       @NonNull CaptureRequest request,
                                       @NonNull TotalCaptureResult result) {
            process(result);
        }

    };
/**
     * 拍摄静止图片。 当我们得到响应时,应该调用此方法
     */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void captureStillPicture() {
        try {
            if (null == activity || null == mCameraDevice) {
                return;
            }
            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 = 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) {
                    Log.e(TAG, mFile.toString());
                    unlockFocus();
                }
            };

            mCaptureSession.stopRepeating();
            mCaptureSession.abortCaptures();
            mCaptureSession.capture(captureBuilder.build(), CaptureCallback, mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

此篇文章主要记录 camera2 自定义相机的简单流程,代码基本都来自于 Google 提供的代码,当然 如果有 Kotlin 的朋友,也可以在文章 开头进入Google github 地址查看Kotlin 版本。