• Android:实现手机前后摄像头预览同开


    效果展示

    一.概述

    本博文讲解如何实现手机前后两颗摄像头同时预览并显示

    我之前博文《OpenGLES:GLSurfaceView实现Android Camera预览》对单颗摄像头预览做过详细讲解,而前后双摄实现原理其实也并不复杂,粗糙点说就是把单摄像头预览流程写两遍

    与之前博文中使用GLSurfaceView实现相机预览不同,这次前后双摄使用TextureView来完成

    二.变量定义

    2.1 公共变量

    1. //权限
    2. public static final int REQUEST_CAMERA_PERMISSION = 1;
    3. private String mCameraId;
    4. private Size mPreviewSize;
    5. public final int mMaxImages = 5;
    6. //相机状态信号量
    7. private Semaphore mCameraOpenCloseLock = new Semaphore(1);

    2.2 摄像头相关变量

    1. ...
    2. private TextureView mFrontTextureView;
    3. private CameraCaptureSession mFrontCaptureSession;
    4. private TextureView mBackTextureView;
    5. private CameraCaptureSession mBackCaptureSession;
    6. ...

    两个CaptureSession、两个TextureView(也就是同时两个Surface)

    三.OpenCamera()

    在 onResume() 中先判断 TextureView 状态是否 Available()

    • 如果是就 OpenCamera()
    • 如果不是就设置 SurfaceTexture 监听,在 onSurfaceTextureAvailable() 监听回调中再OpenCamera()

    onResume()代码:

    1. @Override
    2. public void onResume() {
    3. super.onResume();
    4. if (mBackTextureView.isAvailable()) {
    5. openCamera(true, mBackTextureView.getWidth(), mBackTextureView.getHeight());
    6. } else {
    7. mBackTextureView.setSurfaceTextureListener(mBackSurfaceTextureListener);
    8. }
    9. if (mFrontTextureView.isAvailable()) {
    10. openCamera(false, mFrontTextureView.getWidth(), mFrontTextureView.getHeight());
    11. } else {
    12. mFrontTextureView.setSurfaceTextureListener(mFrontSurfaceTextureListener);
    13. }
    14. startBackgroundThread();
    15. }

    OpenCamera()时需要判断当前打开的是哪颗摄像头,然后走各自对应的流程

    OpenCamera()代码:

    1. private void openCamera(boolean isBack, int width, int height) {
    2. ...
    3. if (isBack) {
    4. mCameraId = manager.getCameraIdList()[0];
    5. //预览size先写成固定值
    6. mPreviewSize = new Size(1440, 1080);
    7. mBackImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.YUV_420_888, mMaxImages);
    8. mBackImageReader.setOnImageAvailableListener(mOnImageAvailableListenerBack, mBackgroundHandler);
    9. Log.v(TAG, "openCamera mCameraId=" + mCameraId);
    10. manager.openCamera(mCameraId, mStateCallBack, mBackgroundHandler);
    11. } else {
    12. mCameraId = manager.getCameraIdList()[1];
    13. //预览size先写成固定值
    14. mPreviewSize = new Size(1080, 720);
    15. mFrontImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.YUV_420_888, mMaxImages);
    16. mFrontImageReader.setOnImageAvailableListener(mOnImageAvailableListenerFront, mFrontgroundHandler);
    17. Log.v(TAG, "openCamera mCameraId=" + mCameraId);
    18. manager.openCamera(mCameraId, mStateCallFront, mFrontgroundHandler);
    19. }
    20. ...
    21. }

    四.createCaptureSession()

    OpenCamera()之后,分别为前后摄创建CaptureSession

    1. private void createCameraPreviewSession(boolean isBack) {
    2. try {
    3. if (isBack) {
    4. SurfaceTexture texture = mBackTextureView.getSurfaceTexture();
    5. assert texture != null;
    6. texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
    7. ArrayList surfaces = new ArrayList();
    8. Surface surface = new Surface(texture);
    9. surfaces.add(surface);
    10. surfaces.add(mBackImageReader.getSurface());
    11. ...
    12. mCameraDeviceBack.createCaptureSession(surfaces, mBackStateCallback, mBackgroundHandler);
    13. } else {
    14. SurfaceTexture texture = mFrontTextureView.getSurfaceTexture();
    15. assert texture != null;
    16. texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
    17. ArrayList surfaces = new ArrayList();
    18. Surface surface = new Surface(texture);
    19. surfaces.add(surface);
    20. surfaces.add(mFrontImageReader.getSurface());
    21. ...
    22. mCameraDeviceFront.createCaptureSession(surfaces, mFrontStateCallback, mFrontgroundHandler);
    23. }
    24. } catch (CameraAccessException e) {
    25. e.printStackTrace();
    26. }
    27. }

    五.setRepeatingRequest()

    createCaptureSession()之后,在前后摄各自的状态回调StatCallback中调用setRepeatingRequest()启动预览。

    前摄:

    1. CameraCaptureSession.StateCallback mFrontStateCallback = new CameraCaptureSession.StateCallback() {
    2. @Override
    3. public void onConfigured(CameraCaptureSession session) {
    4. Log.v(TAG, "CameraCaptureSession onConfigured");
    5. ...
    6. mFrontCaptureSession = session;
    7. try {
    8. ...
    9. mFrontCaptureSession.setRepeatingRequest(mFrontPreviewRequest,
    10. mPreviewBackCallback, mBackgroundHandler);
    11. } catch (CameraAccessException e) {
    12. e.printStackTrace();
    13. }
    14. }
    15. @Override
    16. public void onConfigureFailed(CameraCaptureSession session) {
    17. Log.v(TAG, "onConfigureFailed");
    18. showToast("onConfigureFailed");
    19. }
    20. };

    后摄:

    1. CameraCaptureSession.StateCallback mBackStateCallback = new CameraCaptureSession.StateCallback() {
    2. @Override
    3. public void onConfigured(CameraCaptureSession session) {
    4. Log.v(TAG, "CameraCaptureSession onConfigured");
    5. ...
    6. mBackCaptureSession = session;
    7. try {
    8. ...
    9. mBackCaptureSession.setRepeatingRequest(mBackPreviewRequest,
    10. mPreviewFrontCallback, mFrontgroundHandler);
    11. } catch (CameraAccessException e) {
    12. e.printStackTrace();
    13. }
    14. }
    15. @Override
    16. public void onConfigureFailed(CameraCaptureSession session) {
    17. Log.v(TAG, "onConfigureFailed");
    18. showToast("onConfigureFailed");
    19. }
    20. };

    六.注意

    1.布局优化

    本篇博文最开始,展示了两种前后双摄效果

    第一种是分屏显示,前后摄预览各占1/2,但是画面有压缩

    第二种是重合显示,前后摄预览重合在一起,画面没有压缩,但是有部分区域重叠覆盖

    两种不同的显示方式,其实只是两个TextureView在布局文件中不同的配置

    (1).第一种是在两个TextureView控件外加了一层LinearLayout控件

    1. "1.0" encoding="utf-8"?>
    2. <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    3. android:layout_width="match_parent"
    4. android:layout_height="match_parent"
    5. android:orientation="vertical">
    6. <LinearLayout
    7. android:layout_width="match_parent"
    8. android:layout_height="match_parent"
    9. android:orientation="vertical">
    10. <TextureView
    11. android:id="@+id/texture_back"
    12. android:layout_width="match_parent"
    13. android:layout_height="0dp"
    14. android:layout_weight="1"/>
    15. <TextureView
    16. android:id="@+id/texture_front"
    17. android:layout_width="match_parent"
    18. android:layout_height="0dp"
    19. android:layout_weight="1"/>
    20. LinearLayout>
    21. androidx.constraintlayout.widget.ConstraintLayout>

    (2).第二种去掉了LinearLayout,且在源生TextureView基础上略微封装了一个自定义的AutoFitTextureView,自动适配传入的显示区域宽高

    1. "1.0" encoding="utf-8"?>
    2. <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    3. android:layout_width="match_parent"
    4. android:layout_height="match_parent"
    5. android:orientation="vertical">
    6. <ImageView
    7. android:id="@+id/iv_background"
    8. android:layout_width="match_parent"
    9. android:layout_height="match_parent" />
    10. <com.android.cameraapp.UiUtil.AutoFitTextureView
    11. android:id="@+id/texture_back"
    12. android:layout_width="wrap_content"
    13. android:layout_height="wrap_content"
    14. tools:ignore="MissingConstraints" />
    15. <com.android.cameraapp.UiUtil.AutoFitTextureView
    16. android:id="@+id/texture_front"
    17. android:layout_width="wrap_content"
    18. android:layout_height="wrap_content"
    19. tools:ignore="MissingConstraints" />
    20. androidx.constraintlayout.widget.ConstraintLayout>

    2.代码优化

    如果看到这里,证明你已经跟随博文实现了前后双摄,回过头来看代码,会发现比较简单粗糙,就是博文开始时所述,将单个摄像头预览开启流程重复了一遍

    这样的代码不简洁也不美观,且不易于扩展,可以使用工厂模式将功能代码抽象成一个Camera2Proxy,这一过程就不在此复述了。

    七.结束语

    前后双摄的实现过程和关键代码讲解到此结束

  • 相关阅读:
    青岛大学数据结构与算法——第2章
    vue 学习 -- day36(分析工程结构)
    测试行业面临的问题及RunnerGo在工具层面如何解决的
    win10系统怎样分区,win10固态硬盘怎么分区
    思科-路由器的命令
    一文了解Spark引擎的优势及应用场景
    能助我拿 3 家大厂 offer 的神级 Java 面试宝典,你值得拥有
    【游戏引擎Easy2D】一篇打通引擎基础类型,Point+Size+String+Color(RGBA),可适用于普通题目和引擎详解
    PVTV2--Pyramid Vision TransformerV2学习笔记
    设计模式(一)——单例模式(Singleton)
  • 原文地址:https://blog.csdn.net/geyichongchujianghu/article/details/133588759