Android Camera2 预览功能实现

简介: 1. 概述最近在做一些关于人脸识别的项目,需要用到 Android 相机的预览功能。网上查阅相关资料后,发现 Android 5.0 及以后的版本中,原有的 Camera API 已经被 Camera2 API 所取代。

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 实现相机预览的主要过程。

欢迎加入Android开发技术交流QQ群;701740775

本群提供Android高级开发资料、高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)等相关资料和解答

不懂得问题都可以在本群提出来 还会有职业生涯规划以及面试指导

进群修改群备注:开发年限-地区-经验

方便架构师解答问题

相关文章
|
4月前
|
XML 缓存 Android开发
Android开发,使用kotlin学习多媒体功能(详细)
Android开发,使用kotlin学习多媒体功能(详细)
131 0
|
4月前
|
安全 Linux Android开发
Android 安全功能
Android 安全功能
67 0
|
8天前
|
编解码 测试技术 Android开发
Android经典实战之用 CameraX 库实现高质量的照片和视频拍摄功能
本文详细介绍了如何利用CameraX库实现高质量的照片及视频拍摄功能,包括添加依赖、初始化、权限请求、配置预览与捕获等关键步骤。此外,还特别针对不同分辨率和帧率的视频拍摄提供了性能优化策略,确保应用既高效又稳定。
36 1
Android经典实战之用 CameraX 库实现高质量的照片和视频拍摄功能
|
18天前
|
图形学 Android开发
小功能⭐️Unity调用Android常用事件
小功能⭐️Unity调用Android常用事件
|
3月前
|
数据库 Android开发 数据安全/隐私保护
在 Android Studio 中结合使用 SQLite 数据库实现简单的注册和登录功能
在 Android Studio 中结合使用 SQLite 数据库实现简单的注册和登录功能
132 2
|
3月前
|
Android开发
Android中如何快速的实现RecycleView的拖动重排序功能
使用`ItemTouchHelper`和自定义`Callback`,在`RecyclerView`中实现拖动排序功能。定义`ItemTouchHelperAdapter`接口,`Adapter`实现它以处理`onItemMove`方法。`SimpleItemTouchHelperCallback`设置拖动标志,如`LEFT`或`RIGHT`(水平拖动),并绑定到`RecyclerView`以启用拖动。完成这些步骤后,即可实现拖放排序。关注公众号“AntDream”获取更多内容。
71 3
|
4月前
|
移动开发 监控 Android开发
构建高效Android应用:从内存优化到电池寿命代码之美:从功能实现到艺术创作
【5月更文挑战第28天】 在移动开发领域,特别是针对Android系统,性能优化始终是关键议题之一。本文深入探讨了如何通过细致的内存管理和电池使用策略,提升Android应用的运行效率和用户体验。文章不仅涵盖了现代Android设备上常见的内存泄漏问题,还提出了有效的解决方案,包括代码级优化和使用工具进行诊断。同时,文中也详细阐述了如何通过减少不必要的后台服务、合理管理设备唤醒锁以及优化网络调用等手段延长应用的电池续航时间。这些方法和技术旨在帮助开发者构建更加健壮、高效的Android应用程序。
|
3月前
|
存储 数据库 Android开发
在 Android Studio 中结合使用 SQLite 数据库实现简单的注册和登录功能
在 Android Studio 中结合使用 SQLite 数据库实现简单的注册和登录功能
66 0
|
4月前
|
Android开发 数据安全/隐私保护 iOS开发
ios和安卓测试包发布网站http://fir.im的注册与常用功能
ios和安卓测试包发布网站http://fir.im的注册与常用功能
85 0
ios和安卓测试包发布网站http://fir.im的注册与常用功能
|
4月前
|
机器学习/深度学习 人工智能 TensorFlow
安卓中的人工智能:集成机器学习功能
【4月更文挑战第14天】在数字化时代,人工智能与机器学习正驱动安卓平台的技术革新。谷歌的ML Kit和TensorFlow Lite为开发者提供了便捷的集成工具,使得应用能实现图像识别、文本转换等功能,提升用户体验。尽管面临数据隐私和安全性的挑战,但随着技术进步,更强大的AI功能将预示着移动端的未来,为开发者创造更多创新机遇。
53 4