三种Camera的运用与封装

前语

省流:先别慌别跑,本文只是涉及到 Android API 的运用以及封装,还没有涉及到专业音视频领域(PS:说的如同我会似的),咱们假如想看哪一种 Camera API 直接点击分类检查即可。代码比较多全文阅读的话大约20分钟。

正文部分

运用到相机咱们都不生疏,许多应用都会运用到 Camera 硬件,一般是用于摄影或许录制视频,这都是很常见的功用。

一般来咱们都是怎样运用的呢?说假如没有 UI 的约束或逻辑的约束,只是单纯的摄影或录制视频,咱们其实能够经过 intent 的办法直接发动体系的摄影或录制 Activity 就能结束,这是最便利的办法。也就不需求此文了。

可是总有那么一些应用,那么一些需求,要么是需求自界说的 相机UI页面,要么是事务逻辑需求 ,一般咱们就需求自己结束 Camera 的逻辑,比方显现预览页面,对回调的数据帧进行人脸判断? 进行图片辨认? 或许需求自界说摄影与录制逻辑单击摄影长按录制? 或许是对特效/滤镜视频的录制?

已然离不开 Camera API 的运用,那么本文就从应用的视点出发怎样运用 Camera API 结束想要的作用。

那么需求具体到运用哪一种 Camera 呢?这个咱们也能听说过,咱们现在有三种 API 能够运用,别离是 Camera , Camera2 , CameraX ??? 谷歌是不是傻,为什么要增加咱们开发者的工作量,搞这么复杂我怎样用嘛!

一、Camera的宿世今生

其实主要是为了兼容性与安全性考虑,这儿简略的总结一下:

Camera API 是允许应用程序直接与相机硬件交互的旧版 android.hardware.Camera 类在底层经过驱动程序直接访问相机硬件。

它供给了根本的操控与图片捕捉能力,可是这种办法具有一定的约束和设备兼容性问题。所以 Android 团队决定在 Android 5.0 引进全新的相机 API ,即Camera2 API。

Camera2 API引进了一种新的架构,应用程序经过 CameraManager 与体系 相机服务 进行通讯,并运用CameraDevice、CameraCaptureSession等目标来操控相机功用。这种办法供给了更精密的操控和更高的功用,并处理了旧版API存在的一些约束。

其实这样很好,直接操作变为经过服务通讯,而且它供给更精密的操控和更高的功用。到此的话应该没什么问题,为什么还要推出 CameraX 呢?

虽然 Camera2 API 供给了许多优势,但运用它依然需求编写许多的代码来处理各种情况和设备兼容性。为了简化相机开发流程并进步跨设备兼容性,Google 推出了 CameraX 库

而 CameraX 库则是在 Camera2 API 的基础上进行封装和简化,以供给更一致和易用的相机接口。它笼统了底层的相机相关逻辑和设备差异,使开发者能够以统一的办法编写相机代码,而无需关心特定设备兼容性的细节。它供给了更高级别的API,使开发者能够更轻松地结束相机功用,一起坚持跨设备兼容性。

所以其实咱们用 Camera2 能结束的作用都能经过 CameraX 更简略的与便利的结束,所以个人也是比较引荐运用 CameraX 。

下面就把三种 Camera API 的怎样运用与怎样封装都写一遍。

二、Camera1 的运用与封装

一般来说咱们运用 Camera1 的时候,咱们都是运用 SurfaceView 或 TextureView 来承载预览画面。他们在此场景下的区别便是 TextureView 能够用一些动画结束自适应,裁剪布局等作用。

这儿咱们简略的 SurfaceView 来演示怎样运用:

先创立 SurfaceView 并设置监听,然后添加到咱们指定的容器中:

    public View initCamera(Context context) {
        mSurfaceView = new SurfaceView(context);
        mContext = context;
        mSurfaceView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        mSurfaceHolder = mSurfaceView.getHolder();
        mSurfaceHolder.addCallback(new CustomCallBack());
        mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        return mSurfaceView;
    }

在 SurfaceView 的回调中,咱们初始化 Camera

    private class CustomCallBack implements SurfaceHolder.Callback {
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            initCamera();
        }
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        }
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            releaseAllCamera();
        }
    }
      private void initCamera() {
        if (mCamera != null) {
            releaseAllCamera();
        }
        //翻开摄像头
        try {
            mCamera = Camera.open();
        } catch (Exception e) {
            e.printStackTrace();
            releaseAllCamera();
        }
        if (mCamera == null)
            return;
        //设置摄像头参数
        setCameraParams();
        try {
            mCamera.setDisplayOrientation(90);   //设置拍摄方向为90度(竖屏)
            mCamera.setPreviewDisplay(mSurfaceHolder);
            mCamera.startPreview();
            mCamera.unlock();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (RuntimeException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

这儿需求额外处理预览的方向与预览尺度,代码太多了,鄙人面的东西类中放出。

上面的代码只是最简略的运用,关于切换前后镜头,镜像展现,预览方向,旋转视点,回调处理等等一系列的操作运用起来很是麻烦,所以这儿贴一个自用的东西类统一管理他们。

先界说一个接口回调:

public interface CameraListener {
    /**
     * 当翻开时履行
     *
     * @param camera             相机实例
     * @param cameraId           相机ID
     * @param displayOrientation 相机预览旋转视点
     * @param isMirror           是否镜像显现
     */
    void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror);
    /**
     * 预览数据回调
     *
     * @param data   预览数据
     * @param camera 相机实例
     */
    void onPreview(byte[] data, Camera camera);
    /**
     * 当相机封闭时履行
     */
    void onCameraClosed();
    /**
     * 当出现反常时履行
     *
     * @param e 相机相关反常
     */
    void onCameraError(Exception e);
    /**
     * 特点变化时调用
     *
     * @param cameraID           相机ID
     * @param displayOrientation 相机旋转方向
     */
    void onCameraConfigurationChanged(int cameraID, int displayOrientation);
}

然后便是咱们的东西类,这儿直接贴出:

public class CameraHelper implements Camera.PreviewCallback {
    private Camera mCamera;
    private int mCameraId;
    private Point previewViewSize;
    private View previewDisplayView;
    private Camera.Size previewSize;
    private Point specificPreviewSize;
    private int displayOrientation = 0;
    private int rotation;
    private int additionalRotation;
    private boolean isMirror = false;
    private Integer specificCameraId = null;
    private CameraListener cameraListener;   //自界说监听回调
    private CameraHelper(Builder builder) {
        previewDisplayView = builder.previewDisplayView;
        specificCameraId = builder.specificCameraId;
        cameraListener = builder.cameraListener;
        rotation = builder.rotation;
        additionalRotation = builder.additionalRotation;
        previewViewSize = builder.previewViewSize;
        specificPreviewSize = builder.previewSize;
        if (builder.previewDisplayView instanceof TextureView) {
            isMirror = builder.isMirror;
        } else if (isMirror) {
            throw new RuntimeException("mirror is effective only when the preview is on a textureView");
        }
    }
    public void init() {
        if (previewDisplayView instanceof TextureView) {
            ((TextureView) this.previewDisplayView).setSurfaceTextureListener(textureListener);
        } else if (previewDisplayView instanceof SurfaceView) {
            ((SurfaceView) previewDisplayView).getHolder().addCallback(surfaceCallback);
        }
        if (isMirror) {
            previewDisplayView.setScaleX(-1);
        }
    }
    public String start() {
        String firstSize = null;
        String finalSize = null;
        synchronized (this) {
            if (mCamera != null) {
                return null;
            }
            //相机数量为2则翻开1,1则翻开0,相机ID 1为前置,0为后置
            mCameraId = Camera.getNumberOfCameras() - 1;
            //若指定了相机ID且该相机存在,则翻开指定的相机
            if (specificCameraId != null && specificCameraId <= mCameraId) {
                mCameraId = specificCameraId;
            }
            //没有相机
            if (mCameraId == -1) {
                if (cameraListener != null) {
                    cameraListener.onCameraError(new Exception("camera not found"));
                }
                return null;
            }
            //敞开相机
            if (mCamera == null) {
                mCamera = Camera.open(mCameraId);
            }
            //获取预览方向
            displayOrientation = getCameraOri(rotation);
            mCamera.setDisplayOrientation(displayOrientation);
            try {
                Camera.Parameters parameters = mCamera.getParameters();
                parameters.setPreviewFormat(ImageFormat.NV21);
                //预览巨细设置
                previewSize = parameters.getPreviewSize();
                firstSize = previewSize.width + " - " + previewSize.height;
                List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
                if (supportedPreviewSizes != null && supportedPreviewSizes.size() > 0) {
                    previewSize = getBestSupportedSize(supportedPreviewSizes, previewViewSize);
                    finalSize = previewSize.width + " - " + previewSize.height;
                }
                YYLogUtils.w("Base Preview Size ,Width:" + previewSize.width + " height:" + previewSize.height);
                parameters.setPreviewSize(previewSize.width, previewSize.height);
                //对焦形式设置
                List<String> supportedFocusModes = parameters.getSupportedFocusModes();
                if (supportedFocusModes != null && supportedFocusModes.size() > 0) {
                    if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
                        parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
                    } else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
                        parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
                    } else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
                        parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
                    }
                }
                //Camera 装备结束,设置回去
                mCamera.setParameters(parameters);
                //绑定到 TextureView 或 SurfaceView
                if (previewDisplayView instanceof TextureView) {
                    mCamera.setPreviewTexture(((TextureView) previewDisplayView).getSurfaceTexture());
                } else {
                    mCamera.setPreviewDisplay(((SurfaceView) previewDisplayView).getHolder());
                }
                //发动预览并设置预览回调
                mCamera.setPreviewCallback(this);
                mCamera.startPreview();
                if (cameraListener != null) {
                    cameraListener.onCameraOpened(mCamera, mCameraId, displayOrientation, isMirror);
                }
            } catch (Exception e) {
                if (cameraListener != null) {
                    cameraListener.onCameraError(e);
                }
            }
        }
        return "firstSize :" + firstSize + " finalSize:" + finalSize;
    }
    private int getCameraOri(int rotation) {
        int degrees = rotation * 90;
        switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
            default:
                break;
        }
        additionalRotation /= 90;
        additionalRotation *= 90;
        degrees += additionalRotation;
        int result;
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(mCameraId, info);
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;
        } else {
            result = (info.orientation - degrees + 360) % 360;
        }
        return result;
    }
    public void stop() {
        synchronized (this) {
            if (mCamera == null) {
                return;
            }
            mCamera.setPreviewCallback(null);
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
            if (cameraListener != null) {
                cameraListener.onCameraClosed();
            }
        }
    }
    public boolean isStopped() {
        synchronized (this) {
            return mCamera == null;
        }
    }
    public void release() {
        synchronized (this) {
            stop();
            previewDisplayView = null;
            specificCameraId = null;
            cameraListener = null;
            previewViewSize = null;
            specificPreviewSize = null;
            previewSize = null;
        }
    }
    /**
     * 依据 Camera 获取支撑的宽高,获取到最适合的预览宽高
     */
    private Camera.Size getBestSupportedSize(List<Camera.Size> sizes, Point previewViewSize) {
        if (sizes == null || sizes.size() == 0) {
            return mCamera.getParameters().getPreviewSize();
        }
        Camera.Size[] tempSizes = sizes.toArray(new Camera.Size[0]);
        Arrays.sort(tempSizes, new Comparator<Camera.Size>() {
            @Override
            public int compare(Camera.Size o1, Camera.Size o2) {
                if (o1.width > o2.width) {
                    return -1;
                } else if (o1.width == o2.width) {
                    return o1.height > o2.height ? -1 : 1;
                } else {
                    return 1;
                }
            }
        });
        sizes = Arrays.asList(tempSizes);
        Camera.Size bestSize = sizes.get(0);
        float previewViewRatio;
        if (previewViewSize != null) {
            previewViewRatio = (float) previewViewSize.x / (float) previewViewSize.y;
        } else {
            previewViewRatio = (float) bestSize.width / (float) bestSize.height;
        }
        if (previewViewRatio > 1) {
            previewViewRatio = 1 / previewViewRatio;
        }
        boolean isNormalRotate = (additionalRotation % 180 == 0);
        for (Camera.Size s : sizes) {
            if (specificPreviewSize != null && specificPreviewSize.x == s.width && specificPreviewSize.y == s.height) {
                return s;
            }
            if (isNormalRotate) {
                if (Math.abs((s.height / (float) s.width) - previewViewRatio) < Math.abs(bestSize.height / (float) bestSize.width - previewViewRatio)) {
                    bestSize = s;
                }
            } else {
                if (Math.abs((s.width / (float) s.height) - previewViewRatio) < Math.abs(bestSize.width / (float) bestSize.height - previewViewRatio)) {
                    bestSize = s;
                }
            }
        }
        return bestSize;
    }
    public List<Camera.Size> getSupportedPreviewSizes() {
        if (mCamera == null) {
            return null;
        }
        return mCamera.getParameters().getSupportedPreviewSizes();
    }
    public List<Camera.Size> getSupportedPictureSizes() {
        if (mCamera == null) {
            return null;
        }
        return mCamera.getParameters().getSupportedPictureSizes();
    }
    @Override
    public void onPreviewFrame(byte[] nv21, Camera camera) {
        if (cameraListener != null && nv21 != null) {
            cameraListener.onPreview(nv21, camera);
        }
    }
    /**
     * TextureView 的监听回调
     */
    private TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
//            start();
            if (mCamera != null) {
                try {
                    mCamera.setPreviewTexture(surfaceTexture);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
        }
        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
            stop();
            return false;
        }
        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
        }
    };
    /**
     * SurfaceView 的监听回调
     */
    private SurfaceHolder.Callback surfaceCallback = new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            if (mCamera != null) {
                try {
                    mCamera.setPreviewDisplay(holder);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        }
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            stop();
        }
    };
    /**
     * 切换摄像头方向
     */
    public void changeDisplayOrientation(int rotation) {
        if (mCamera != null) {
            this.rotation = rotation;
            displayOrientation = getCameraOri(rotation);
            mCamera.setDisplayOrientation(displayOrientation);
            if (cameraListener != null) {
                cameraListener.onCameraConfigurationChanged(mCameraId, displayOrientation);
            }
        }
    }
    /**
     * 翻转前后摄像镜头
     */
    public boolean switchCamera() {
        if (Camera.getNumberOfCameras() < 2) {
            return false;
        }
        // cameraId ,0为后置,1为前置
        specificCameraId = 1 - mCameraId;
        stop();
        start();
        return true;
    }
    /**
     * 运用构建者形式创立,装备Camera
     */
    public static final class Builder {
        //预览显现的view,现在仅支撑surfaceView和textureView
        private View previewDisplayView;
        //是否镜像显现,只支撑textureView
        private boolean isMirror;
        //指定的相机ID
        private Integer specificCameraId;
        //事情回调
        private CameraListener cameraListener;
        //屏幕的长宽,在挑选最佳相机比例时用到
        private Point previewViewSize;
        //屏幕的方向,一般传入getWindowManager().getDefaultDisplay().getRotation()的值即可
        private int rotation;
        //指定的预览宽高,若体系支撑则会以这个预览宽高进行预览
        private Point previewSize;
        //额外的旋转视点(用于适配一些定制设备)
        private int additionalRotation;
        public Builder() {
        }
        //必须要绑定到SurfaceView或许TextureView上
        public Builder previewOn(View view) {
            if (view instanceof SurfaceView || view instanceof TextureView) {
                previewDisplayView = view;
                return this;
            } else {
                throw new RuntimeException("you must preview on a textureView or a surfaceView");
            }
        }
        public Builder isMirror(boolean mirror) {
            isMirror = mirror;
            return this;
        }
        public Builder previewSize(Point point) {
            previewSize = point;
            return this;
        }
        public Builder previewViewSize(Point point) {
            previewViewSize = point;
            return this;
        }
        public Builder rotation(int rotation) {
            rotation = rotation;
            return this;
        }
        public Builder additionalRotation(int rotation) {
            additionalRotation = rotation;
            return this;
        }
        public Builder specificCameraId(Integer id) {
            specificCameraId = id;
            return this;
        }
        public Builder cameraListener(CameraListener listener) {
            cameraListener = listener;
            return this;
        }
        public CameraHelper build() {
            if (previewViewSize == null) {
                throw new RuntimeException("previewViewSize is null, now use default previewSize");
            }
            if (cameraListener == null) {
                throw new RuntimeException("cameraListener is null, callback will not be called");
            }
            if (previewDisplayView == null) {
                throw new RuntimeException("you must preview on a textureView or a surfaceView");
            }
            //build的时候趁便履行初始化
            CameraHelper cameraHelper = new CameraHelper(this);
            cameraHelper.init();
            return cameraHelper;
        }
    }
}

咱们运用起来就很简略了:

仍是初始化 SurfaceView 而且添加到指定的布局容器中:

    public View initCamera(Context context) {
        mSurfaceView = new SurfaceView(context);
        mContext = context;
        mSurfaceView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        mSurfaceView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mSurfaceView.post(() -> {
                    setupCameraHelper();
                });
                mSurfaceView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        });
        return mSurfaceView;
    }
    private void setupCameraHelper() {
        cameraHelper = new CameraHelper.Builder()
                .previewViewSize(new Point(mSurfaceView.getMeasuredWidth(), mSurfaceView.getMeasuredHeight()))
                .rotation(((Activity) mContext).getWindowManager().getDefaultDisplay().getRotation())
                .specificCameraId(Camera.CameraInfo.CAMERA_FACING_BACK)
                .isMirror(false)
                .previewOn(mSurfaceView) //预览容器 引荐TextureView
                .cameraListener(mCameraListener) //设置自界说的监听器
                .build();
        cameraHelper.start();
    }

一切的逻辑就在回调中处理:

 //自界说监听
    private CameraListener mCameraListener = new CameraListener() {
        @Override
        public void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror) {
            YYLogUtils.w("CameraListener - onCameraOpened");
            //你能够运用 MediaRecorder 去录制视频
            mMediaRecorder = new MediaRecorder();
            ...
        }
        @Override
        public void onPreview(byte[] data, Camera camera) {
            // nv21 数据
            // 你也能够用 MediaCodec 自己编码去录制视频
        }
        @Override
        public void onCameraClosed() {
            YYLogUtils.w("CameraListener - onCameraClosed");
        }
        @Override
        public void onCameraError(Exception e) {
            YYLogUtils.w("CameraListener - onCameraError");
        }
        @Override
        public void onCameraConfigurationChanged(int cameraID, int displayOrientation) {
            YYLogUtils.w("CameraListener - onCameraConfigurationChanged");
        }
    };

停止与开释的逻辑

    @Override
    public void releaseAllCamera() {
        cameraHelper.stop();
        cameraHelper.release();
    }

作用:

Android录制视频,三种Camera的使用与预览及其简单封装

细啊,真的是太细了,代码太详细了。

三、Camera2 的运用与封装

Camera2 的运用是经过服务获取的,运用起来相对步骤多一些。

这儿咱们运用 TextureView 来承载画面:

    public View initCamera(Context context) {
        mTextureView = new TextureView(context);
        mContext = context;
        mTextureView.setLayoutParams(new ViewGroup.LayoutParams(720, 1280));
        mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
        return mTextureView;
    }

相同的逻辑,咱们在 TextureView 的回调中发动 Camera2 :

    private TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
            // 当TextureView可用时,翻开摄像头
            YYLogUtils.w("当TextureView可用时,width:" + width + " height:" + height);
            openCamera(width, height);
        }
        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
            // 当TextureView尺度改变时,更新预览尺度
            configureTransform(width, height);
        }
        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
            // 当TextureView毁掉时,开释资源
            return true;
        }
        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture texture) {
            // 监听纹路更新事情
        }
    };
    private void openCamera(int width, int height) {
        // 获取相机管理器
        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
        // 设置自界说的线程处理
        HandlerThread handlerThread = new HandlerThread("Camera2Manager");
        handlerThread.start();
        mBgHandler = new Handler(handlerThread.getLooper());
        try {
            //获取到相机信息并赋值
            getCameraListCameraCharacteristics(width,height);
            YYLogUtils.w("翻开的摄像头id:" + mCurrentCameraId);
            // 翻开摄像头
            mCameraManager.openCamera(mCurrentCameraId, new CameraDevice.StateCallback() {
                @Override
                public void onOpened(CameraDevice cameraDevice) {
                    // 当摄像头翻开时,创立预览会话
                    mCameraDevice = cameraDevice;
                    createCameraPreviewSession(mBgHandler, mTextureView.getWidth(), mTextureView.getHeight());
                }
                @Override
                public void onDisconnected(CameraDevice cameraDevice) {
                    if (mCameraDevice != null) {
                        mCameraDevice.close();
                        mCameraDevice = null;
                    }
                }
                @Override
                public void onError(CameraDevice cameraDevice, int error) {
                    if (mCameraDevice != null) {
                        mCameraDevice.close();
                        mCameraDevice = null;
                    }
                }
            }, mBgHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

这儿咱们运用的 mBgHandler 咱们运用的主线程,其实在子线程中更好,这儿只用作展现而已(具体的后面东西类会给出)。

咱们继续往下走,翻开相机之后需求创立预览的会话,创立预览Surface,而且在预览会话回调中建议预览恳求:

  /**
     * 创立相机预览会话
     */
    private void createCameraPreviewSession(Handler handler, int width, int height) {
        try {
            // 获取SurfaceTexture并设置默许缓冲区巨细
            SurfaceTexture texture = mTextureView.getSurfaceTexture();
            texture.setDefaultBufferSize(mTextureView.getWidth(), mTextureView.getHeight());
            // 创立预览Surface
            Surface surface = new Surface(texture);
            // 创立CaptureRequest.Builder并设置预览Surface为方针
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            mPreviewRequestBuilder.addTarget(surface);
            // 创立ImageReader并设置回调
            YYLogUtils.w("创立ImageReader并设置回调,width:" + width + " height:" + height);
//            mImageReader = ImageReader.newInstance(mTextureView.getWidth(), mTextureView.getHeight(), ImageFormat.JPEG, 1);
            mImageReader = ImageReader.newInstance(width, height, ImageFormat.YUV_420_888, 2);
            mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, handler);
            // 将ImageReader的Surface添加到CaptureRequest.Builder中
            Surface readerSurface = mImageReader.getSurface();
            mPreviewRequestBuilder.addTarget(readerSurface);
            // 创立预览会话
            mCameraDevice.createCaptureSession(Arrays.asList(surface, readerSurface), mSessionCallback, handler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
    private CameraCaptureSession.StateCallback mSessionCallback = new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
            // 预览会话已创立成功,开端预览
            mPreviewSession = cameraCaptureSession;
            updatePreview();
        }
        @Override
        public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
            ToastUtils.INSTANCE.makeText(mContext, "Failed to create camera preview session");
        }
    };
    private void updatePreview() {
        try {
            // 设置主动对焦形式
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            // 构建预览恳求
            mPreviewRequest = mPreviewRequestBuilder.build();
            // 发送预览恳求
            mPreviewSession.setRepeatingRequest(mPreviewRequest, null, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
    private ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
        @Override
        public void onImageAvailable(ImageReader reader) {
            // YUV_420 格局
            Image image = reader.acquireLatestImage();
            saveImage(bytes);
            image.close();
        }
    };

能够看到,Camera2 的根本运用比起 Camera1 来说复杂了不少,这儿仍是省掉了一些旋转视点,预览方向,最佳尺度等逻辑的代码(后面东西类会给出)。所以难怪谷歌要出 CameraX,自己都看不下去了呗。

当然,咱们封装一下之后运用起来也是很简略的,看我的东西类。

这儿别离不同的实例供给,一种是根本的预览,一种是AllSize的裁剪形式,一种是供给了 ImageReader 帧回调的终极形式。

这儿真的不能悉数贴出来,不然代码太多,咱们能够看文章底部的源码检查。所以这儿贴出的是要害代码:

由于 Camera2 的许多操作都引荐在子线程处理,这儿先界说子线程的 Loop 与 Handler :

public abstract class BaseMessageLoop {
    private volatile MsgHandlerThread mHandlerThread;
    private volatile Handler mHandler;
    private String mName;
    public BaseMessageLoop(Context context, String name) {
        mName = name;
    }
    public MsgHandlerThread getHandlerThread() {
        return mHandlerThread;
    }
    public Handler getHandler() {
        return mHandler;
    }
    public void Run() {
        Quit();
        //LogUtil.v(TAG,  mName + " HandlerThread Run");
        synchronized (this) {
            mHandlerThread = new MsgHandlerThread(mName);
            mHandlerThread.start();
            mHandler = new Handler(mHandlerThread.getLooper(), mHandlerThread);
        }
    }
    public void Quit() {
        //LogUtil.v(TAG,  mName + " HandlerThread Quit");
        synchronized (this) {
            if (mHandlerThread != null) {
                mHandlerThread.quit();
            }
            if (mHandler != null) {
                mHandler.removeCallbacks(mHandlerThread);
            }
            mHandlerThread = null;
            mHandler = null;
        }
    }
    ...
}

根本的预览供给:

public class BaseCommonCameraProvider extends BaseCameraProvider {
    protected Activity mContext;
    protected String mCameraId;
    protected Handler mCameraHandler;
    private final BaseMessageLoop mThread;
    protected CameraDevice mCameraDevice;
    protected CameraCaptureSession session;
    protected AspectTextureView[] mTextureViews;
    protected CameraManager cameraManager;
    protected OnCameraInfoListener mCameraInfoListener;
    public interface OnCameraInfoListener {
        void getBestSize(Size outputSizes);
        void onFrameCannback(Image image);
        void initEncode();
        void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height);
    }
    public void setCameraInfoListener(OnCameraInfoListener cameraInfoListener) {
        this.mCameraInfoListener = cameraInfoListener;
    }
    protected BaseCommonCameraProvider(Activity mContext) {
        this.mContext = mContext;
        mThread = new BaseMessageLoop(mContext, "camera") {
            @Override
            protected boolean recvHandleMessage(Message msg) {
                return false;
            }
        };
        mThread.Run();
        mCameraHandler = mThread.getHandler();
        cameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
    }
    protected String getCameraId(boolean useFront) {
        try {
            for (String cameraId : cameraManager.getCameraIdList()) {
                CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
                int cameraFacing = characteristics.get(CameraCharacteristics.LENS_FACING);
                if (useFront) {
                    if (cameraFacing == CameraCharacteristics.LENS_FACING_FRONT) {
                        return cameraId;
                    }
                } else {
                    if (cameraFacing == CameraCharacteristics.LENS_FACING_BACK) {
                        return cameraId;
                    }
                }
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        return null;
    }
    protected Size getCameraBestOutputSizes(String cameraId, Class clz) {
        try {
            //拿到支撑的悉数Size,并从大到小排序
            CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
            StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            List<Size> sizes = Arrays.asList(configs.getOutputSizes(clz));
            Collections.sort(sizes, new Comparator<Size>() {
                @Override
                public int compare(Size o1, Size o2) {
                    return o1.getWidth() * o1.getHeight() - o2.getWidth() * o2.getHeight();
                }
            });
            Collections.reverse(sizes);
            YYLogUtils.w("all_sizes:" + sizes);
            //去除一些不合适的预览尺度
            List<Size> suitableSizes = new ArrayList();
            for (int i = 0; i < sizes.size(); i++) {
                Size option = sizes.get(i);
                if (textureViewSize.getWidth() > textureViewSize.getHeight()) {
                    if (option.getWidth() >= textureViewSize.getWidth() && option.getHeight() >= textureViewSize.getHeight()) {
                        suitableSizes.add(option);
                    }
                } else {
                    if (option.getWidth() >= textureViewSize.getHeight() && option.getHeight() >= textureViewSize.getWidth()) {
                        suitableSizes.add(option);
                    }
                }
            }
            YYLogUtils.w("suitableSizes:" + suitableSizes);
            //获取最小占用的Size
            if (!suitableSizes.isEmpty()) {
                return suitableSizes.get(suitableSizes.size() - 1);
            } else {
                //反常情况下只能找默许的了
                return sizes.get(0);
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        return null;
    }
    protected List<Size> getCameraAllSizes(String cameraId, int format) {
        try {
            CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
            StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            return Arrays.asList(configs.getOutputSizes(format));
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        return null;
    }
    protected void releaseCameraDevice(CameraDevice cameraDevice) {
        if (cameraDevice != null) {
            cameraDevice.close();
            cameraDevice = null;
        }
    }
    protected void releaseCameraSession(CameraCaptureSession session) {
        if (session != null) {
            session.close();
            session = null;
        }
    }
    protected void releaseCamera() {
        releaseCameraDevice(mCameraDevice);
        releaseCameraSession(session);
    }
}

对内部的一些监听与回调做了抽取,内部对切换镜头,支撑的Size挑选,等做了一些结束,这样简略的就能结束预览的操作。

咱们常用的应该是能够裁切拉伸的 TextureView , 这儿独自对它做一个预览的处理:

/**
 * 只用于预览,没有帧回调
 */
public class Camera2AllSizeProvider extends BaseCommonCameraProvider {
    private CaptureRequest.Builder mPreviewBuilder;
    private Size outputSize;
    public Camera2AllSizeProvider(Activity mContext) {
        super(mContext);
        Point displaySize = new Point();
        mContext.getWindowManager().getDefaultDisplay().getSize(displaySize);
        screenSize = new Size(displaySize.x, displaySize.y);
    }
    private void initCamera() {
        mCameraId = getCameraId(false);//默许运用后置相机
        //获取指定相机的输出尺度列表,降序排序
        outputSize = getCameraBestOutputSizes(mCameraId, SurfaceTexture.class);
        //初始化预览尺度
        previewSize = outputSize;
        YYLogUtils.w("previewSize,width:" + previewSize.getWidth() + "height:" + previewSize.getHeight());
        if (mCameraInfoListener != null) {
            mCameraInfoListener.getBestSize(outputSize);
        }
    }
    int index = 0;
    /**
     * 相关并初始化TextTure
     */
    public void initTexture(AspectTextureView... textureViews) {
        mTextureViews = textureViews;
        int size = textureViews.length;
        for (AspectTextureView aspectTextureView : textureViews) {
            aspectTextureView.setSurfaceTextureListener(new Camera2SimpleInterface.SimpleSurfaceTextureListener() {
                @Override
                public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                    textureViewSize = new Size(width, height);
                    YYLogUtils.w("textureViewSize,width:" + textureViewSize.getWidth() + "height:" + textureViewSize.getHeight());
                    YYLogUtils.w("screenSize,width:" + screenSize.getWidth() + "height:" + screenSize.getHeight());
                    if (++index == size) {
                        initCamera();
                        openCamera();
                    }
                }
            });
        }
    }
    @SuppressLint("MissingPermission")
    private void openCamera() {
        try {
            cameraManager.openCamera(mCameraId, mStateCallback, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
    private final CameraDevice.StateCallback mStateCallback = new Camera2SimpleInterface.SimpleCameraDeviceStateCallback() {
        @Override
        public void onOpened(CameraDevice camera) {
            mCameraDevice = camera;
            MediaCodecList allMediaCodecLists = new MediaCodecList(-1);
            MediaCodecInfo avcCodecInfo = null;
            for (MediaCodecInfo mediaCodecInfo : allMediaCodecLists.getCodecInfos()) {
                if (mediaCodecInfo.isEncoder()) {
                    String[] supportTypes = mediaCodecInfo.getSupportedTypes();
                    for (String supportType : supportTypes) {
                        if (supportType.equals(MediaFormat.MIMETYPE_VIDEO_AVC)) {
                            avcCodecInfo = mediaCodecInfo;
                            Log.d("TAG", "编码器称号:" + mediaCodecInfo.getName() + "  " + supportType);
                            MediaCodecInfo.CodecCapabilities codecCapabilities = avcCodecInfo.getCapabilitiesForType(MediaFormat.MIMETYPE_VIDEO_AVC);
                            int[] colorFormats = codecCapabilities.colorFormats;
                            for (int colorFormat : colorFormats) {
                                switch (colorFormat) {
                                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV411Planar:
                                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV411PackedPlanar:
                                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
                                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
                                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
                                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
                                        Log.d("TAG", "支撑的格局::" + colorFormat);
                                        break;
                                }
                            }
                        }
                    }
                }
            }
            //依据什么Size来展现PreView
            startPreviewSession(previewSize);
        }
    };
    public void startPreviewSession(Size size) {
        YYLogUtils.w("startPreviewSession 真实的Size,width:" + size.getWidth() + " height:" + size.getHeight());
        try {
            releaseCameraSession(session);
            mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            List<Surface> outputs = new ArrayList<>();
            for (AspectTextureView aspectTextureView : mTextureViews) {
                //设置预览巨细与展现的裁剪形式
                aspectTextureView.setScaleType(AspectInterface.ScaleType.FIT_CENTER);
                aspectTextureView.setSize(size.getHeight(), size.getWidth());
                SurfaceTexture surfaceTexture = aspectTextureView.getSurfaceTexture();
                surfaceTexture.setDefaultBufferSize(size.getWidth(), size.getHeight());
                Surface previewSurface = new Surface(surfaceTexture);
                mPreviewBuilder.addTarget(previewSurface);
                outputs.add(previewSurface);
            }
            mCameraDevice.createCaptureSession(outputs, mStateCallBack, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
    private final CameraCaptureSession.StateCallback mStateCallBack = new Camera2SimpleInterface.SimpleStateCallback() {
        @Override
        public void onConfigured(CameraCaptureSession session) {
            try {
                Camera2AllSizeProvider.this.session = session;
                //设置摄影前继续主动对焦
                mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                CaptureRequest request = mPreviewBuilder.build();
                session.setRepeatingRequest(request, null, mCameraHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
    };
    public void closeCamera() {
        releaseCamera();
        if (mCameraDevice != null) {
            mCameraDevice.close();
        }
    }
}

关于预览的回调,咱们能够再做一个独自的实例供给:

/**
 * 在预览的基础上加入ImageReader的帧回调,能够用于编码H264视频流等操作
 */
public class Camera2ImageReaderProvider extends BaseCommonCameraProvider {
    private CaptureRequest.Builder mPreviewBuilder;
    protected ImageReader mImageReader;
    private Size outputSize;
    public Camera2ImageReaderProvider(Activity mContext) {
        super(mContext);
        Point displaySize = new Point();
        mContext.getWindowManager().getDefaultDisplay().getSize(displaySize);
        screenSize = new Size(displaySize.x, displaySize.y);
        YYLogUtils.w("screenSize,width:" + screenSize.getWidth() + "height:" + screenSize.getHeight(), "Camera2ImageReaderProvider");
    }
    private void initCamera() {
        mCameraId = getCameraId(false);//默许运用后置相机
        //获取指定相机的输出尺度列表,降序排序
        outputSize = getCameraBestOutputSizes(mCameraId, SurfaceTexture.class);
        //初始化预览尺度
        previewSize = outputSize;
        YYLogUtils.w("previewSize,width:" + previewSize.getWidth() + "height:" + previewSize.getHeight(), "Camera2ImageReaderProvider");
        if (mCameraInfoListener != null) {
            mCameraInfoListener.getBestSize(outputSize);
        }
    }
    int index = 0;
    /**
     * 相关并初始化TextTure
     */
    public void initTexture(AspectTextureView... textureViews) {
        mTextureViews = textureViews;
        int size = textureViews.length;
        for (AspectTextureView aspectTextureView : textureViews) {
            aspectTextureView.setSurfaceTextureListener(new Camera2SimpleInterface.SimpleSurfaceTextureListener() {
                @Override
                public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
                    textureViewSize = new Size(width, height);
                    YYLogUtils.w("textureViewSize,width:" + textureViewSize.getWidth() + "height:" + textureViewSize.getHeight(), "Camera2ImageReaderProvider");
                    if (mCameraInfoListener != null) {
                        mCameraInfoListener.onSurfaceTextureAvailable(surfaceTexture, width, height);
                    }
                    if (++index == size) {
                        initCamera();
                        openCamera();
                    }
                }
            });
        }
    }
    //初始化编码格局
    public void initEncord() {
        if (mCameraInfoListener != null) {
            mCameraInfoListener.initEncode();
        }
    }
    @SuppressLint("MissingPermission")
    private void openCamera() {
        try {
            cameraManager.openCamera(mCameraId, mStateCallback, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
    private final CameraDevice.StateCallback mStateCallback = new Camera2SimpleInterface.SimpleCameraDeviceStateCallback() {
        @Override
        public void onOpened(CameraDevice camera) {
            mCameraDevice = camera;
            initEncord();
            MediaCodecList allMediaCodecLists = new MediaCodecList(-1);
            MediaCodecInfo avcCodecInfo = null;
            for (MediaCodecInfo mediaCodecInfo : allMediaCodecLists.getCodecInfos()) {
                if (mediaCodecInfo.isEncoder()) {
                    String[] supportTypes = mediaCodecInfo.getSupportedTypes();
                    for (String supportType : supportTypes) {
                        if (supportType.equals(MediaFormat.MIMETYPE_VIDEO_AVC)) {
                            avcCodecInfo = mediaCodecInfo;
                            Log.d("TAG", "编码器称号:" + mediaCodecInfo.getName() + "  " + supportType);
                            MediaCodecInfo.CodecCapabilities codecCapabilities = avcCodecInfo.getCapabilitiesForType(MediaFormat.MIMETYPE_VIDEO_AVC);
                            int[] colorFormats = codecCapabilities.colorFormats;
                            for (int colorFormat : colorFormats) {
                                switch (colorFormat) {
                                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV411Planar:
                                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV411PackedPlanar:
                                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
                                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
                                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
                                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
                                        Log.d("TAG", "支撑的格局::" + colorFormat);
                                        break;
                                }
                            }
                        }
                    }
                }
            }
            //依据什么Size来展现PreView
            startPreviewSession(previewSize);
        }
    };
    public void startPreviewSession(Size size) {
        YYLogUtils.w("真实的预览Size,width:" + size.getWidth() + " height:" + size.getHeight(), "Camera2ImageReaderProvider");
        try {
            releaseCameraSession(session);
            mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            List<Surface> outputs = new ArrayList<>();
            for (AspectTextureView aspectTextureView : mTextureViews) {
                //设置预览巨细与展现的裁剪形式
                aspectTextureView.setScaleType(AspectInterface.ScaleType.FIT_CENTER);
                aspectTextureView.setSize(size.getHeight(), size.getWidth());
                SurfaceTexture surfaceTexture = aspectTextureView.getSurfaceTexture();
                surfaceTexture.setDefaultBufferSize(size.getWidth(), size.getHeight());
                Surface previewSurface = new Surface(surfaceTexture);
                mPreviewBuilder.addTarget(previewSurface);
                outputs.add(previewSurface);
            }
            //这儿的回调监听
            mImageReader = ImageReader.newInstance(size.getWidth(), size.getHeight(), ImageFormat.YUV_420_888, 10);
            mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mCameraHandler);
            Surface readerSurface = mImageReader.getSurface();
            mPreviewBuilder.addTarget(readerSurface);
            outputs.add(readerSurface);
            mCameraDevice.createCaptureSession(outputs, mStateCallBack, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
    private ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
        @Override
        public void onImageAvailable(ImageReader reader) {
            Image image = reader.acquireLatestImage();
            if (image == null) {
                return;
            }
            if (mCameraInfoListener != null) {
                mCameraInfoListener.onFrameCannback(image);
            }
            image.close();
        }
    };
    private final CameraCaptureSession.StateCallback mStateCallBack = new Camera2SimpleInterface.SimpleStateCallback() {
        @Override
        public void onConfigured(CameraCaptureSession session) {
            try {
                Camera2ImageReaderProvider.this.session = session;
                //设置摄影前继续主动对焦
                mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
//                mPreviewBuilder.set(CaptureRequest.JPEG_ORIENTATION, 90);
                CaptureRequest request = mPreviewBuilder.build();
                session.setRepeatingRequest(request, null, mCameraHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
    };
    public void closeCamera() {
        releaseCamera();
        if (mImageReader != null) {
            mImageReader.close();
        }
        if (mCameraDevice != null) {
            mCameraDevice.close();
        }
    }
}

关于 Camera2 的尺度,其实咱们要了解的是三种尺度,当时屏幕尺度,当时 textureView 尺度,以及当时预览的尺度。

比较容易混淆的便是 textureView 尺度和预览的尺度,一个是显现控件的尺度,一个是 Camera 支撑的预览尺度,他们的巨细可能相同,但更多的可能是不同,宽高比例也可能不同 ,所以咱们才需求居中裁剪或居中展现的办法来预览画面,上文关于这三种尺度的界说与运用都有详细的注释。

上文的代码中还省掉了一些非要害的类与回调,有兴趣能够去源码中检查。(本文结尾有链接)

怎样运用? 界说结束之后咱们就能够来一个简略的运用示例:

这儿以 H264 的编码为例:

 fun setupCamera(activity: Activity, container: ViewGroup) {
        file = File(CommUtils.getContext().externalCacheDir, "${System.currentTimeMillis()}-record.h264")
        if (!file.exists()) {
            file.createNewFile()
        }
        if (!file.isDirectory) {
            outputStream = FileOutputStream(file, true)
        }
        val textureView = AspectTextureView(activity)
        textureView.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
        mCamera2Provider = Camera2ImageReaderProvider(activity)
        mCamera2Provider?.initTexture(textureView)
        mCamera2Provider?.setCameraInfoListener(object :
            BaseCommonCameraProvider.OnCameraInfoListener {
            override fun getBestSize(outputSizes: Size?) {
                mPreviewSize = outputSizes
            }
            override fun onFrameCannback(image: Image) {
                if (isRecording) {
                    // 运用C库获取到I420格局,对应 COLOR_FormatYUV420Planar
                    val yuvFrame = yuvUtils.convertToI420(image)
                    // 与MediaFormat的编码格局宽高对应
                    val yuvFrameRotate = yuvUtils.rotate(yuvFrame, 90)
                    // 用于测验RGB图片的回调预览
                    bitmap = Bitmap.createBitmap(yuvFrameRotate.width, yuvFrameRotate.height, Bitmap.Config.ARGB_8888)
                    yuvUtils.yuv420ToArgb(yuvFrameRotate, bitmap!!)
                    mBitmapCallback?.invoke(bitmap)
                    // 旋转90度之后的I420格局添加到同步队列
                    val bytesFromImageAsType = yuvFrameRotate.asArray()
                    //运用Java东西类转化Image目标为NV21格局,对应 COLOR_FormatYUV420Flexible
//                    val bytesFromImageAsType = getBytesFromImageAsType(image, Camera2ImageUtils.YUV420SP)
                    originVideoDataList.offer(bytesFromImageAsType)
                }
            }
            override fun initEncode() {
                mediaCodecEncodeToH264()
            }
            override fun onSurfaceTextureAvailable(surfaceTexture: SurfaceTexture?, width: Int, height: Int) {
                this@VideoH264RecoderUtils.surfaceTexture = surfaceTexture
            }
        })
        container.addView(textureView)
    }

由于这儿返回的是 YUV420 ,所以这儿咱们需求转化为 I420 或 NV21 格局。这儿我别离展现了 C 库转为 I420(YUV420) , Java 库转化为 NV21(YUV420SP) 格局。

然后咱们就能把 I420 与 NV21 这两种咱们常见的格局编码为 H264 文件,而怎样编码反倒不是今日的主题了,只能说办法太多了,这儿先略过。

只需求这一个 setupCamera 办法,就能结束绑定,咱们在 Activity 中运用即可一行代码设置进去就能够了。

class RecoderVideo1Activity : BaseActivity() {
    override fun init() {
       val flContainer = findViewById<FrameLayout>(R.id.fl_container)
       val videoRecodeUtils = VideoH264RecoderUtils()
       videoRecodeUtils.setupCamera(this, container)
   }
}

作用:

Android录制视频,三种Camera的使用与预览及其简单封装

四、CameraX 的运用与封装

比较前面两种 API 的运用,CameraX 的运用就简略多了,网上也许多 CameraX 的运用教程,这儿咱们就快速过一下。

咱们初始化 PreviewView 目标,然后添加到咱们指定的容器中。

    public View initCamera(Context context) {
        mPreviewView = new PreviewView(context);
        mContext = context;
        mPreviewView.setScaleType(PreviewView.ScaleType.FIT_CENTER);
        mPreviewView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        mPreviewView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if (mPreviewView.isShown()) {
                    startCamera();
                }
                mPreviewView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        });
        return mPreviewView;
    }

其次咱们就能够发动相机并绑定到生命周期。

 private void startCamera() {
        //获取屏幕的分辨率
        DisplayMetrics displayMetrics = new DisplayMetrics();
        mPreviewView.getDisplay().getRealMetrics(displayMetrics);
        //获取宽高比
        int screenAspectRatio = aspectRatio(displayMetrics.widthPixels, displayMetrics.heightPixels);
        int rotation = mPreviewView.getDisplay().getRotation();
        ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(mContext);
        cameraProviderFuture.addListener(() -> {
            try {
                //获取相机信息
                mCameraProvider = cameraProviderFuture.get();
                //镜头挑选
                mLensFacing = getLensFacing();
                mCameraSelector = new CameraSelector.Builder().requireLensFacing(mLensFacing).build();
                //预览目标
                Preview.Builder previewBuilder = new Preview.Builder()
                        .setTargetAspectRatio(screenAspectRatio)
                        .setTargetRotation(rotation);
                Preview preview = previewBuilder.build();
                preview.setSurfaceProvider(mPreviewView.getSurfaceProvider());
                //录制视频目标
                mVideoCapture = new VideoCapture.Builder()
                        .setTargetAspectRatio(screenAspectRatio)  //设置高宽比
                        .setAudioRecordSource(MediaRecorder.AudioSource.MIC) //设置音频源麦克风
                        .setTargetRotation(rotation)
                        //视频帧率
                        .setVideoFrameRate(30)
                        //bit率
                        .setBitRate(3 * 1024 * 1024)
                        .build();
//                ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
//                        .setTargetAspectRatio(screenAspectRatio)
//                        .setTargetRotation(rotation)
//                        .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
//                        .build();
//
//                // 在每一帧上应用颜色矩阵
//                imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor(), new MyAnalyzer(mContext));
                //敞开CameraX
                mCameraProvider.unbindAll();
                if (mContext instanceof FragmentActivity) {
                    FragmentActivity fragmentActivity = (FragmentActivity) mContext;
                    mCameraProvider.bindToLifecycle(fragmentActivity, mCameraSelector, preview, mVideoCapture/*,imageAnalysis*/);
                }
            } catch (ExecutionException | InterruptedException e) {
                e.printStackTrace();
            }
        }, ContextCompat.getMainExecutor(mContext));
    }

这儿咱们运用了四个用例,预览,摄影,录制,剖析。可是咱们注释了剖析用例,因为录制与剖析这两个用例是不能一起运用的。

录制视频的话,咱们就能直接运用录制用例的办法就能录制,这是最简略的。

    public void startCameraRecord() {
        if (mVideoCapture == null) return;
        VideoCapture.OutputFileOptions outputFileOptions = new VideoCapture.OutputFileOptions.Builder(getOutFile()).build();
        mVideoCapture.startRecording(outputFileOptions, mExecutorService, new VideoCapture.OnVideoSavedCallback() {
            @Override
            public void onVideoSaved(@NonNull VideoCapture.OutputFileResults outputFileResults) {
                YYLogUtils.w("视频保存成功,outputFileResults:" + outputFileResults.getSavedUri());
                if (mCameraCallback != null) mCameraCallback.takeSuccess();
            }
            @Override
            public void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause) {
                YYLogUtils.e(message);
            }
        });
    }

内部一些镜头挑选,比例挑选等代码省掉了,鄙人面的东西类中会给出。

能够看到虽然 CameraX 的运用已经是够简略的了,可是由于都是一些重复的代码,咱们仍是能够对其做一些封装,代码如下:

class CameraXController {
    private var mCameraProvider: ProcessCameraProvider? = null
    private var mLensFacing = 0
    private var mCameraSelector: CameraSelector? = null
    private var mVideoCapture: VideoCapture? = null
    private var mCameraCallback: ICameraCallback? = null
    private val mExecutorService = Executors.newSingleThreadExecutor()
    //初始化 CameraX 相关装备
    fun setUpCamera(context: Context, surfaceProvider: Preview.SurfaceProvider) {
        //获取屏幕的分辨率与宽高比
        val displayMetrics = context.resources.displayMetrics
        val screenAspectRatio = aspectRatio(displayMetrics.widthPixels, displayMetrics.heightPixels)
        val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
        cameraProviderFuture.addListener({
            mCameraProvider = cameraProviderFuture.get()
            //镜头挑选
            mLensFacing = lensFacing
            mCameraSelector = CameraSelector.Builder().requireLensFacing(mLensFacing).build()
            //预览目标
            val preview: Preview = Preview.Builder()
                .setTargetAspectRatio(screenAspectRatio)
                .build()
            preview.setSurfaceProvider(surfaceProvider)
            //录制视频目标
            mVideoCapture = VideoCapture.Builder()
                .setTargetAspectRatio(screenAspectRatio)
                .setAudioRecordSource(MediaRecorder.AudioSource.MIC) //设置音频源麦克风
                //视频帧率
                .setVideoFrameRate(30)
                //bit率
                .setBitRate(3 * 1024 * 1024)
                .build()
            //绑定到页面
            mCameraProvider?.unbindAll()
            val camera = mCameraProvider?.bindToLifecycle(
                context as LifecycleOwner,
                mCameraSelector!!,
                mVideoCapture,
                preview
            )
            val cameraInfo = camera?.cameraInfo
            val cameraControl = camera?.cameraControl
        }, ContextCompat.getMainExecutor(context))
    }
    //依据屏幕宽高比设置预览比例为4:3仍是16:9
    private fun aspectRatio(widthPixels: Int, heightPixels: Int): Int {
        val previewRatio = Math.max(widthPixels, heightPixels).toDouble() / Math.min(widthPixels, heightPixels).toDouble()
        return if (Math.abs(previewRatio - 4.0 / 3.0) <= Math.abs(previewRatio - 16.0 / 9.0)) {
            AspectRatio.RATIO_4_3
        } else {
            AspectRatio.RATIO_16_9
        }
    }
    //优先挑选哪一个摄像头镜头
    private val lensFacing: Int
        private get() {
            if (hasBackCamera()) {
                return CameraSelector.LENS_FACING_BACK
            }
            return if (hasFrontCamera()) {
                CameraSelector.LENS_FACING_FRONT
            } else -1
        }
    //是否有后摄像头
    private fun hasBackCamera(): Boolean {
        if (mCameraProvider == null) {
            return false
        }
        try {
            return mCameraProvider!!.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA)
        } catch (e: CameraInfoUnavailableException) {
            e.printStackTrace()
        }
        return false
    }
    //是否有前摄像头
    private fun hasFrontCamera(): Boolean {
        if (mCameraProvider == null) {
            return false
        }
        try {
            return mCameraProvider!!.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA)
        } catch (e: CameraInfoUnavailableException) {
            e.printStackTrace()
        }
        return false
    }
    // 开端录制
    fun startCameraRecord(outFile: File) {
        mVideoCapture ?: return
        val outputFileOptions: VideoCapture.OutputFileOptions = VideoCapture.OutputFileOptions.Builder(outFile).build()
        mVideoCapture!!.startRecording(outputFileOptions, mExecutorService, object : VideoCapture.OnVideoSavedCallback {
            override fun onVideoSaved(outputFileResults: VideoCapture.OutputFileResults) {
                YYLogUtils.w("视频保存成功,outputFileResults:" + outputFileResults.savedUri)
                mCameraCallback?.takeSuccess()
            }
            override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) {
                YYLogUtils.e(message)
            }
        })
    }
    // 停止录制
    fun stopCameraRecord(cameraCallback: ICameraCallback?) {
        mCameraCallback = cameraCallback
        mVideoCapture?.stopRecording()
    }
    // 开释资源
    fun releseAll() {
        mVideoCapture?.stopRecording()
        mExecutorService.shutdown()
        mCameraProvider?.unbindAll()
        mCameraProvider?.shutdown()
        mCameraProvider = null
    }
}

对与封装之后的东西类来说,运用起来就更简略了:

 override fun initCamera(context: Context): View {
        mPreviewView = PreviewView(context)
        mContext = context
        mPreviewView.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
        mPreviewView.viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                if (mPreviewView.isShown) {
                    startCamera()
                }
                mPreviewView.viewTreeObserver.removeOnGlobalLayoutListener(this)
            }
        })
        return mPreviewView
    }
    private fun startCamera() {
        cameraXController.setUpCamera(mContext, mPreviewView.surfaceProvider)
    }

假如想敞开视频录制:

    override fun startCameraRecord() {
        cameraXController.startCameraRecord(outFile)
    }

一起 CameraX 自身就自带裁剪功用,也不需求咱们自界说 TextureView 结束了。

居中显现与居中裁剪的作用如下:

Android录制视频,三种Camera的使用与预览及其简单封装

Android录制视频,三种Camera的使用与预览及其简单封装

太好了,运用起来真的是超级简略,爱了爱了。

总结

本文关于常见的三种 Camera API 做了示例代码及其关于别离进行封装。关于运用哪一种 Camera 结束作用,咱们能够自行挑选。

假如我想要回调 NV21 的数据,其实我会挑选 Camera1 ,因为它本身返回便是这个格局不需求转化,假如我想要回调 I420 格局,我会挑选 Camera2 或 CameraX ,横竖需求转化,他们更便利,特别是合作 libyuv 库,功率会更高。

假如想结束录制视频的功用呢?假如是普通的录制我会挑选 CameraX 自带的录制视频功用,愈加的简略便利。假如是特效的录制,那么我会挑选 Camera2 或 CameraX 合作 GLSurfaceView 结束。

我个人比较喜爱 CameraX ,因为咱们不是专业做相机的,关于一些简略的预览与录制的需求来说,CameraX 封装的蛮好用的,运用还简略,兼容性还很高,用起来本钱也小。(个人观点,比较主观)

本文中假如贴出的代码有不全的,能够点击源码翻开项目进行检查,传送门。一起你也能够关注我的开源项目,后续会继续更新。

关于怎样录制视频的问题,有哪几种办法?,优缺点? 后期会独自再出文章做出介绍。

常规,我如有解说不到位或讹夺的地方,希望同学们能够指出。有疑问也能够谈论区沟通。

假如有关几种 Camera 的运用或对其的一些封装对你有协助的话,还请点赞支撑一下哦,你的支撑是我最大的动力啦。

Ok,这一期就此结束。

Android录制视频,三种Camera的使用与预览及其简单封装