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 源码
github:camera2preview
4. 参考
- https://github.com/googlesamples/android-camera2basic