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

效果展示

一.概述

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

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

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

二.变量定义

2.1 公共变量

//权限
public static final int REQUEST_CAMERA_PERMISSION = 1;

private String mCameraId;
private Size mPreviewSize;
public final int mMaxImages = 5;

//相机状态信号量
private Semaphore mCameraOpenCloseLock = new Semaphore(1);

2.2 摄像头相关变量

...

private TextureView mFrontTextureView;
private CameraCaptureSession mFrontCaptureSession;

private TextureView mBackTextureView;
private CameraCaptureSession mBackCaptureSession;

...

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

三.OpenCamera()

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

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

onResume()代码:

@Override
public void onResume() {
	super.onResume();

	if (mBackTextureView.isAvailable()) {
		openCamera(true, mBackTextureView.getWidth(), mBackTextureView.getHeight());
	} else {
		mBackTextureView.setSurfaceTextureListener(mBackSurfaceTextureListener);
	}

	if (mFrontTextureView.isAvailable()) {
		openCamera(false, mFrontTextureView.getWidth(), mFrontTextureView.getHeight());
	} else {
		mFrontTextureView.setSurfaceTextureListener(mFrontSurfaceTextureListener);
	}

	startBackgroundThread();
}

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

OpenCamera()代码:

private void openCamera(boolean isBack, int width, int height) {
    
	...
	
	if (isBack) {
		mCameraId = manager.getCameraIdList()[0];
        //预览size先写成固定值
		mPreviewSize = new Size(1440, 1080);

		mBackImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.YUV_420_888, mMaxImages);
		mBackImageReader.setOnImageAvailableListener(mOnImageAvailableListenerBack, mBackgroundHandler);

		Log.v(TAG, "openCamera mCameraId=" + mCameraId);
		manager.openCamera(mCameraId, mStateCallBack, mBackgroundHandler);
	} else {
		mCameraId = manager.getCameraIdList()[1];
        //预览size先写成固定值
		mPreviewSize = new Size(1080, 720);

		mFrontImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.YUV_420_888, mMaxImages);
		mFrontImageReader.setOnImageAvailableListener(mOnImageAvailableListenerFront, mFrontgroundHandler);

		Log.v(TAG, "openCamera mCameraId=" + mCameraId);
		manager.openCamera(mCameraId, mStateCallFront, mFrontgroundHandler);
	}
	
	...
	
}

四.createCaptureSession()

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

private void createCameraPreviewSession(boolean isBack) {
	try {
		if (isBack) {
			SurfaceTexture texture = mBackTextureView.getSurfaceTexture();
			assert texture != null;
			texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());

			ArrayList<Surface> surfaces = new ArrayList<Surface>();
			Surface surface = new Surface(texture);
			surfaces.add(surface);
			surfaces.add(mBackImageReader.getSurface());

			...

			mCameraDeviceBack.createCaptureSession(surfaces, mBackStateCallback, mBackgroundHandler);
		} else {
			SurfaceTexture texture = mFrontTextureView.getSurfaceTexture();
			assert texture != null;
			texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());

			ArrayList<Surface> surfaces = new ArrayList<Surface>();
			Surface surface = new Surface(texture);
			surfaces.add(surface);
			surfaces.add(mFrontImageReader.getSurface());

			...
			
			mCameraDeviceFront.createCaptureSession(surfaces, mFrontStateCallback, mFrontgroundHandler);
		}

	} catch (CameraAccessException e) {
		e.printStackTrace();
	}
}

五.setRepeatingRequest()

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

前摄:

CameraCaptureSession.StateCallback mFrontStateCallback = new CameraCaptureSession.StateCallback() {
	@Override
	public void onConfigured(CameraCaptureSession session) {
		Log.v(TAG, "CameraCaptureSession onConfigured");

        ...

		mFrontCaptureSession = session;
		try {
		
		    ...
			
			mFrontCaptureSession.setRepeatingRequest(mFrontPreviewRequest,
					mPreviewBackCallback, mBackgroundHandler);
		} catch (CameraAccessException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void onConfigureFailed(CameraCaptureSession session) {
		Log.v(TAG, "onConfigureFailed");
		showToast("onConfigureFailed");
	}
};

后摄:

CameraCaptureSession.StateCallback mBackStateCallback = new CameraCaptureSession.StateCallback() {

	@Override
	public void onConfigured(CameraCaptureSession session) {
		Log.v(TAG, "CameraCaptureSession onConfigured");
        
		...
          
		mBackCaptureSession = session;
		try {
		
		    ...
			
			mBackCaptureSession.setRepeatingRequest(mBackPreviewRequest,
					mPreviewFrontCallback, mFrontgroundHandler);
		} catch (CameraAccessException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void onConfigureFailed(CameraCaptureSession session) {
		Log.v(TAG, "onConfigureFailed");
		showToast("onConfigureFailed");
	}
};

六.注意

1.布局优化

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

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

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

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

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

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextureView
            android:id="@+id/texture_back"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>

        <TextureView
            android:id="@+id/texture_front"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>
    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

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

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/iv_background"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <com.android.cameraapp.UiUtil.AutoFitTextureView
        android:id="@+id/texture_back"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        tools:ignore="MissingConstraints" />

    <com.android.cameraapp.UiUtil.AutoFitTextureView
        android:id="@+id/texture_front"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        tools:ignore="MissingConstraints" />

</androidx.constraintlayout.widget.ConstraintLayout>

2.代码优化

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

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

七.结束语

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


http://www.niftyadmin.cn/n/5070218.html

相关文章

K8S:配置资源管理 Secret和configMap

文章目录 一.Secret1.Secret概念2.Secret的类型①kubernetes.io/service-account-token②opaque③kubernetes.io/dockerconfigjson④kubernetes.io/tls 3.secret的三种参数①tls②docker-registry③generic 4.Pod 的3种方式来使用secret5.Secret创建及案例&#xff08;1&#x…

【数据结构】手撕归并排序(含非递归)

目录 一&#xff0c;归并排序&#xff08;递归&#xff09; 1&#xff0c;基本思想 2&#xff0c;思路实现 二&#xff0c;归并排序&#xff08;非递归&#xff09; 1&#xff0c;思路实现 2&#xff0c;归并排序的特性总结&#xff1a; 一&#xff0c;归并排序&#xff0…

Dijkstra 邻接表表示算法 | 贪心算法实现--附C++/JAVA实现源码

以下是详细步骤。 创建大小为 V 的最小堆,其中 V 是给定图中的顶点数。最小堆的每个节点包含顶点编号和顶点的距离值。 以源顶点为根初始化最小堆(分配给源顶点的距离值为0)。分配给所有其他顶点的距离值为 INF(无限)。 当最小堆不为空时,执行以下操作: 从最小堆中提取…

解决spawn-fcgi:child exited with: 127/126/1报错

解决spawn-fcgi:child exited with: 126报错 执行文件的权限不够&#xff0c;如果是使用.sh文件进行执行的&#xff0c;首先对.sh文件进行权限修改 chmod 777 执行文件.sh 之后再对sh文件中所有执行spawn-fcgi的程序授予权限 比如&#xff1a; spawn-fcgi -a 127.0.0.1 -p 789…

高考有哪些东西没有考,但是却对人生发展至关重要的东西

情商合作能力沟通表达能力格局与胸怀胆识看人识人的能力

QSqlTableModel使用简介

QSqlTableModel可以和QTableView共同使用&#xff0c;只需对QSqlTableModel类操作就可以实现读写数据库&#xff0c; 同时将数据显示在tableview中&#xff0c;相同的更改tableview中的值也可以直接同步到数据库中。QSqlTableModel类使用注意&#xff1a; QSqlTableModel::setH…

JVM技术文档--JVM诊断调优工具Arthas--阿里巴巴开源工具--一文搞懂Arthas--快速上手--国庆开卷!!

​ Arthas首页 简介 | arthas Arthas官网文档 Arthas首页、文档和下载 - 开源 Java 诊断工具 - OSCHINA - 中文开源技术交流社区 阿丹&#xff1a; 之前聊过了一些关于JMV中的分区等等&#xff0c;但是有同学还是在后台问我&#xff0c;还有私信问我&#xff0c;学了这些…

linux虚拟机查看防火墙状态

linux虚拟机查看防火墙状态 在Linux虚拟机中&#xff0c;你可以通过以下几种方法查看防火墙状态&#xff1a; 查看iptables防火墙状态 对于使用iptables防火墙的Linux系统&#xff0c;可以使用以下命令查看防火墙状态&#xff1a; sudo iptables -L -v -n查看firewalld防火墙…