Android Camera2 预览功能实现
1. 概述
最近在做一些关于人脸识别的项目,需要用到 Android 相机的预览功能。网上查阅相关资料后,发现 Android 5.0 及以后的版本中,原有的 Camera API 已经被 Camera2 API 所取代。
全新的 Camera2 在 Camera 的基础上进行了改造,大幅提升了 Android 系统的拍照功能。它通过以下几个类与方法来实现相机预览时的工作过程:
- CameraManager :摄像头管理器,主要用于检测系统摄像头、打开系统摄像头等;
- CameraDevice : 用于描述系统摄像头,可用于关闭相机、创建相机会话、发送拍照请求等;
- CameraCharacteristics :用于描述摄像头所支持的各种特性;
- CameraCaptureSession :当程序需要预览、拍照时,都需要先通过 CameraCaptureSession 来实现。该会话通过调用方法 setRepeatingRequest() 实现预览;
- CameraRequest :代表一次捕获请求,用于描述捕获图片的各种参数设置;
- CameraRequest.Builder :负责生成 CameraRequest 对象。
2. 相机预览
下面通过源码来讲解如何使用 Camera2 来实现相机的预览功能。
2.1 相机权限设置
<uses-permission android:name="android.permission.CAMERA" />
2.2 App 布局
- activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000"
tools:context=".MainActivity">
</FrameLayout>
- fragment_camera.xml
<?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"
tools:context=".CameraFragment">
<com.lightweh.camera2preview.AutoFitTextureView
android:id="@+id/textureView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>
2.3 相机自定义View
public class AutoFitTextureView extends TextureView {
private int mRatioWidth = 0;
private int mRatioHeight = 0;
public AutoFitTextureView(Context context) {
this(context, null);
}
public AutoFitTextureView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setAspectRatio(int width, int height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Size cannot be negative.");
}
mRatioWidth = width;
mRatioHeight = height;
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (0 == mRatioWidth || 0 == mRatioHeight) {
setMeasuredDimension(width, height);
} else {
if (width < height * mRatioWidth / mRatioHeight) {
setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
} else {
setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
}
}
}
}
2.4 动态申请相机权限
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_PERMISSION = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (hasPermission()) {
if (null == savedInstanceState) {
setFragment();
}
} else {
requestPermission();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
if (requestCode == REQUEST_PERMISSION) {
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
setFragment();
} else {
requestPermission();
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
// 权限判断,当系统版本大于23时,才有必要判断是否获取权限
private boolean hasPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
} else {
return true;
}
}
// 请求相机权限
private void requestPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
Toast.makeText(MainActivity.this, "Camera permission are required for this demo", Toast.LENGTH_LONG).show();
}
requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_PERMISSION);
}
}
// 启动相机Fragment
private void setFragment() {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.container, CameraFragment.newInstance())
.commitNowAllowingStateLoss();
}
}
2.5 开启相机预览
首先,在onResume()中,我们需要开启一个 HandlerThread,然后利用该线程的 Looper 对象构建一个 Handler 用于相机回调。
@Override
public void onResume() {
super.onResume();
startBackgroundThread();
// When the screen is turned off and turned back on, the SurfaceTexture is
// already available, and "onSurfaceTextureAvailable" will not be called. In
// that case, we can open a camera and start preview from here (otherwise, we
// wait until the surface is ready in the SurfaceTextureListener).
if (mTextureView.isAvailable()) {
openCamera(mTextureView.getWidth(), mTextureView.getHeight());
} else {
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
}
}
private void startBackgroundThread() {
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
同时,在 onPause() 中有对应的 HandlerThread 关闭方法。
当屏幕关闭后重新开启,SurfaceTexture 已经就绪,此时不会触发 onSurfaceTextureAvailable 回调。因此,我们判断 mTextureView 如果可用,则直接打开相机,否则等待 SurfaceTexture 回调就绪后再开启相机。
private void openCamera(int width, int height) {
if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
return;
}
setUpCameraOutputs(width, height);
configureTransform(width, height);
Activity activity = getActivity();
CameraManager manager = (CameraManager) activity.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 函数对相机参数进行设置(包括指定摄像头、相机预览方向以及预览尺寸的设定等),接下来调用 configureTransform 函数对预览图片的大小和方向进行调整,最后获取 CameraManager 对象开启相机。因为相机有可能会被其他进程同时访问,所以在开启相机时需要加锁。
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@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;
Activity activity = getActivity();
if (null != activity) {
activity.finish();
}
}
};
相机开启时还会指定相机的状态变化回调函数 mStateCallback,如果相机成功开启,则开始创建相机预览会话。
private void createCameraPreviewSession() {
try {
// 获取 texture 实例
SurfaceTexture texture = mTextureView.getSurfaceTexture();
assert texture != null;
// 设置 TextureView 缓冲区大小
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
// 获取 Surface 显示预览数据
Surface surface = new Surface(texture);
// 构建适合相机预览的请求
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
// 设置 surface 作为预览数据的显示界面
mPreviewRequestBuilder.addTarget(surface);
// 创建相机捕获会话用于预览
mCameraDevice.createCaptureSession(Arrays.asList(surface),
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);
mPreviewRequest = mPreviewRequestBuilder.build();
// 设置反复捕获数据的请求,预览界面一直显示画面
mCaptureSession.setRepeatingRequest(mPreviewRequest,
null, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(
@NonNull CameraCaptureSession cameraCaptureSession) {
showToast("Failed");
}
}, null
);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
以上便是 Camera2 API 实现相机预览的主要过程。
3. Demo 源码
4. 参考
- https://github.com/googlesamples/android-Camera2Basic
本文链接:https://www.cnblogs.com/lightweh/p/9994927.html,转载请注明出处。
Android Camera2 预览功能实现的更多相关文章
- Android Camera2 预览,拍照,人脸检测并实时展现
https://www.jianshu.com/p/5414ba2b5508 背景 最近需要做一个人脸检测并实时预览的功能.就是边检测人脸,边在预览界面上框出来. 当然本人并不是专门做 ...
- 【腾讯优测干货分享】Android 相机预览方向及其适配探索
本文来自于腾讯bugly开发者社区,未经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/583ba1df25d735cd2797004d 由于Android系统的开放策略 ...
- Java实现office文档与pdf文档的在线预览功能
最近项目有个需求要java实现office文档与pdf文档的在线预览功能,刚刚接到的时候就觉得有点难,以自己的水平难以在三四天做完.压力略大.后面查找百度资料.以及在同事与网友的帮助下,四天多把它做完 ...
- 分离与继承的思想实现图片上传后的预览功能:ImageUploadView
本文要介绍的是网页中常见的图片上传后直接在页面生成小图预览的实现思路,考虑到该功能有一定的适用性,于是把相关的逻辑封装成了一个ImageUploadView组件,实际使用效果可查看下一段的git效果图 ...
- 【小月博客】用HTML5的File API做上传图片预览功能
前段时间做了一个项目,涉及到上传本地图片以及预览的功能,正好之前了解过 html5(点击查看更多关于web前端的有关资源) 可以上传本地图片,然后再网上看了一些demo结合自己的需求,终于搞定了.(P ...
- 如何通过js实现图片预览功能
一.效果预览 效果图: 二.实现代码: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" &quo ...
- C#实现打印与打印预览功能
C#实现打印与打印预览功能的思路及代码. 在windows应用程序中文档的打印是一项非常重要的功能,在以前一直是一个非常复杂的工作,Microsoft .Net Framework的打印功能都以组件的 ...
- js实现FileUpload选择图片后预览功能
当asp.net的FileUpload选择一个图片后不需要上传就能显示出图片的预览功能, 代码: <%@ Page Language="C#" AutoEventWireup ...
- nodejs实现本地上传图片并预览功能(express4.0+)
Express为:4.13.1 multyparty: 4.1.2 代码主要实现本地图片上传到nodejs服务器的文件下,通过取图片路径进行图片预览 写在前面:计划实现图片上传预览功能,但是本地图片 ...
随机推荐
- storm集群架构
一.storm何许人也? Storm 是Twitter的一个开源框架.Storm一个分布式的.容错的实时计算系统,它被托管在GitHub上,遵循 Eclipse Public License 1.0. ...
- [Swift]LeetCode798. 得分最高的最小轮调 | Smallest Rotation with Highest Score
Given an array A, we may rotate it by a non-negative integer K so that the array becomes A[K], A[K+1 ...
- [Swift]LeetCode962. 最大宽度坡 | Maximum Width Ramp
Given an array A of integers, a ramp is a tuple (i, j) for which i < j and A[i] <= A[j]. The ...
- 使用Task
http://www.cnblogs.com/Charltsing/p/taskpoolthread.html task默认对线程的调度是逐步增加的,连续多次运行并发线程,会提高占用的线程数,而等若干 ...
- 8.Django缓存和信号
缓存 由于Django是动态网站,所有每次请求均会去数据进行相应的操作,当程序访问量大时,耗时必然会更加明显,最简单解决方式是使用:缓存,缓存将某个views的返回值保存至内存或者memcache中, ...
- Dockerfile指令介绍
FROM:指定基础镜像 在Dockerfile中FROM是必备的指令,用于指定基础的镜像. FROM centos:latest LABEL:指定镜像标签 LABEL指令用来指定镜像的标签. 格式: ...
- tomcat的catalina.out日志按自定义时间日式进行分割
使用cronolog对tomcat的日志进行自定义日期格式的切割,方便日志的整理和遇到问题日志的排查! 1.安装cronolog工具1.1 下载 cronolog 地址:网上很多下载地址这里就不在累赘 ...
- Android_ FragmentTabHost切换Fragment时避免重复加载UI
使用FragmentTabHost时,Fragment之间切换时每次都会调用onCreateView方法,导致每次Fragment的布局都重绘,无法保持Fragment原有状态. 解决办法:在Frag ...
- SpringCloud Feign的分析
Feign是一个声明式的Web Service客户端,它使得编写Web Serivce客户端变得更加简单.我们只需要使用Feign来创建一个接口并用注解来配置它既可完成. @FeignClient(v ...
- centos 7 linux系统安装 mysql5.7.17(glibc版)
前言:经过一天半的折腾,终于把 mysql 5.7.17 版本安装上了 centos 7 系统上,把能参考的博客几乎都看了一遍,终于发现这些细节问题,然而翻了无数的文章,基本上都没有提到这些,所以小生 ...