您现在的位置是:首页 > 文章详情

Android Camera2 预览功能实现

日期:2018-11-20点击:306

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)等相关资料和解答

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

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

方便架构师解答问题

原文链接:https://yq.aliyun.com/articles/687875
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章