经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » Android » 查看文章
Android Camera2 实现预览功能
来源:jb51  时间:2018/11/22 9:59:02  对本文有异议

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 相机权限设置

  1. <uses-permission android:name="android.permission.CAMERA" />

2.2 App 布局

•activity_main.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. android:id="@+id/container"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. android:background="#000"
  8. tools:context=".MainActivity">
  9. </FrameLayout>
  10. •fragment_camera.xml
  11. <?xml version="1.0" encoding="utf-8"?>
  12. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  13. xmlns:tools="http://schemas.android.com/tools"
  14. android:layout_width="match_parent"
  15. android:layout_height="match_parent"
  16. tools:context=".CameraFragment">
  17. <com.lightweh.camera2preview.AutoFitTextureView
  18. android:id="@+id/textureView"
  19. android:layout_width="wrap_content"
  20. android:layout_height="wrap_content"
  21. android:layout_centerVertical="true"
  22. android:layout_centerHorizontal="true" />
  23. </RelativeLayout>

2.3 相机自定义View

  1. public class AutoFitTextureView extends TextureView {
  2. private int mRatioWidth = 0;
  3. private int mRatioHeight = 0;
  4. public AutoFitTextureView(Context context) {
  5. this(context, null);
  6. }
  7. public AutoFitTextureView(Context context, AttributeSet attrs) {
  8. this(context, attrs, 0);
  9. }
  10. public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
  11. super(context, attrs, defStyle);
  12. }
  13. public void setAspectRatio(int width, int height) {
  14. if (width < 0 || height < 0) {
  15. throw new IllegalArgumentException("Size cannot be negative.");
  16. }
  17. mRatioWidth = width;
  18. mRatioHeight = height;
  19. requestLayout();
  20. }
  21. @Override
  22. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  23. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  24. int width = MeasureSpec.getSize(widthMeasureSpec);
  25. int height = MeasureSpec.getSize(heightMeasureSpec);
  26. if (0 == mRatioWidth || 0 == mRatioHeight) {
  27. setMeasuredDimension(width, height);
  28. } else {
  29. if (width < height * mRatioWidth / mRatioHeight) {
  30. setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
  31. } else {
  32. setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
  33. }
  34. }
  35. }
  36. }

2.4 动态申请相机权限

  1. public class MainActivity extends AppCompatActivity {
  2. private static final int REQUEST_PERMISSION = 1;
  3. @Override
  4. protected void onCreate(Bundle savedInstanceState) {
  5. super.onCreate(savedInstanceState);
  6. setContentView(R.layout.activity_main);
  7. if (hasPermission()) {
  8. if (null == savedInstanceState) {
  9. setFragment();
  10. }
  11. } else {
  12. requestPermission();
  13. }
  14. }
  15. @Override
  16. public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
  17. if (requestCode == REQUEST_PERMISSION) {
  18. if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
  19. setFragment();
  20. } else {
  21. requestPermission();
  22. }
  23. } else {
  24. super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  25. }
  26. }
  27. // 权限判断,当系统版本大于23时,才有必要判断是否获取权限
  28. private boolean hasPermission() {
  29. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  30. return checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
  31. } else {
  32. return true;
  33. }
  34. }
  35. // 请求相机权限
  36. private void requestPermission() {
  37. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  38. if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
  39. Toast.makeText(MainActivity.this, "Camera permission are required for this demo", Toast.LENGTH_LONG).show();
  40. }
  41. requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_PERMISSION);
  42. }
  43. }
  44. // 启动相机Fragment
  45. private void setFragment() {
  46. getSupportFragmentManager()
  47. .beginTransaction()
  48. .replace(R.id.container, CameraFragment.newInstance())
  49. .commitNowAllowingStateLoss();
  50. }
  51. }

2.5 开启相机预览

首先,在onResume()中,我们需要开启一个 HandlerThread,然后利用该线程的 Looper 对象构建一个 Handler 用于相机回调。

  1. @Override
  2. public void onResume() {
  3. super.onResume();
  4. startBackgroundThread();
  5.  
  6. // When the screen is turned off and turned back on, the SurfaceTexture is
  7. // already available, and "onSurfaceTextureAvailable" will not be called. In
  8. // that case, we can open a camera and start preview from here (otherwise, we
  9. // wait until the surface is ready in the SurfaceTextureListener).
  10. if (mTextureView.isAvailable()) {
  11. openCamera(mTextureView.getWidth(), mTextureView.getHeight());
  12. } else {
  13. mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
  14. }
  15. }
  16. private void startBackgroundThread() {
  17. mBackgroundThread = new HandlerThread("CameraBackground");
  18. mBackgroundThread.start();
  19. mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
  20. }

同时,在 onPause() 中有对应的 HandlerThread 关闭方法。

当屏幕关闭后重新开启,SurfaceTexture 已经就绪,此时不会触发 onSurfaceTextureAvailable 回调。因此,我们判断 mTextureView 如果可用,则直接打开相机,否则等待 SurfaceTexture 回调就绪后再开启相机。

  1. private void openCamera(int width, int height) {
  2. if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA)
  3. != PackageManager.PERMISSION_GRANTED) {
  4. return;
  5. }
  6. setUpCameraOutputs(width, height);
  7. configureTransform(width, height);
  8. Activity activity = getActivity();
  9. CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
  10. try {
  11. if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
  12. throw new RuntimeException("Time out waiting to lock camera opening.");
  13. }
  14. manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
  15. } catch (CameraAccessException e) {
  16. e.printStackTrace();
  17. } catch (InterruptedException e) {
  18. throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
  19. }
  20. }

开启相机时,我们首先判断是否具备相机权限,然后调用 setUpCameraOutputs 函数对相机参数进行设置(包括指定摄像头、相机预览方向以及预览尺寸的设定等),接下来调用 configureTransform 函数对预览图片的大小和方向进行调整,最后获取 CameraManager 对象开启相机。因为相机有可能会被其他进程同时访问,所以在开启相机时需要加锁。

  1. private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
  2. @Override
  3. public void onOpened(@NonNull CameraDevice cameraDevice) {
  4. mCameraOpenCloseLock.release();
  5. mCameraDevice = cameraDevice;
  6. createCameraPreviewSession();
  7. }
  8. @Override
  9. public void onDisconnected(@NonNull CameraDevice cameraDevice) {
  10. mCameraOpenCloseLock.release();
  11. cameraDevice.close();
  12. mCameraDevice = null;
  13. }
  14. @Override
  15. public void onError(@NonNull CameraDevice cameraDevice, int error) {
  16. mCameraOpenCloseLock.release();
  17. cameraDevice.close();
  18. mCameraDevice = null;
  19. Activity activity = getActivity();
  20. if (null != activity) {
  21. activity.finish();
  22. }
  23. }
  24. };

相机开启时还会指定相机的状态变化回调函数 mStateCallback,如果相机成功开启,则开始创建相机预览会话。

  1. private void createCameraPreviewSession() {
  2. try {
  3. // 获取 texture 实例
  4. SurfaceTexture texture = mTextureView.getSurfaceTexture();
  5. assert texture != null;
  6. // 设置 TextureView 缓冲区大小
  7. texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
  8. // 获取 Surface 显示预览数据
  9. Surface surface = new Surface(texture);
  10. // 构建适合相机预览的请求
  11. mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
  12. // 设置 surface 作为预览数据的显示界面
  13. mPreviewRequestBuilder.addTarget(surface);
  14. // 创建相机捕获会话用于预览
  15. mCameraDevice.createCaptureSession(Arrays.asList(surface),
  16. new CameraCaptureSession.StateCallback() {
  17. @Override
  18. public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
  19. // 如果相机关闭则返回
  20. if (null == mCameraDevice) {
  21. return;
  22. }
  23. // 如果会话准备好则开启预览
  24. mCaptureSession = cameraCaptureSession;
  25. try {
  26. // 自动对焦
  27. mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
  28. CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
  29. mPreviewRequest = mPreviewRequestBuilder.build();
  30. // 设置反复捕获数据的请求,预览界面一直显示画面
  31. mCaptureSession.setRepeatingRequest(mPreviewRequest,
  32. null, mBackgroundHandler);
  33. } catch (CameraAccessException e) {
  34. e.printStackTrace();
  35. }
  36. }
  37. @Override
  38. public void onConfigureFailed(
  39. @NonNull CameraCaptureSession cameraCaptureSession) {
  40. showToast("Failed");
  41. }
  42. }, null
  43. );
  44. } catch (CameraAccessException e) {
  45. e.printStackTrace();
  46. }
  47. }

以上便是 Camera2 API 实现相机预览的主要过程。

3. Demo 源码

Github:Camera2Preview

4. 参考

https://github.com/googlesamples/android-Camera2Basic  

总结

以上所述是小编给大家介绍的Android Camera2 实现预览功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对w3xue网站的支持!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号