Android 用 camera2 API 自定义相机
前言
笔者因为项目需要自定义相机,所以了解了一下 android 关于 camera 这块的 api。android sdk 21(lollipop) 开始已经弃用了之前的 camera 类,提供了 camera2 相关 api,目前网上关于 camera2 api 介绍的资料比较少,笔者搜集网上资料,结合自己的实践,在这里做一个总结。
流程
因为 camera2 提供的接口比较多,虽然很灵活,但是也增加了使用的复杂度。首先来大致了解一下调用 camera2 的流程,方便我们理清思路。
要显示相机捕捉的画面,只需要三步:初始化相机,预览,更新预览。也就是上图中左侧的部分。要实现这三步,需要用到的主要接口类和它们的作用步骤如上图右侧部分所示。下面就用代码来详解一下。
案例
首先创建一个相机界面:
activity_camera.xml
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <textureview android:id="@+id/camera_texture_view" android:layout_width="match_parent" android:layout_height="match_parent" /> <imagebutton android:id="@+id/capture_ib" android:layout_width="60dp" android:layout_height="60dp" android:layout_marginbottom="10dp" android:layout_gravity="bottom|center" android:background="@drawable/send_pres"/> </linearlayout>
界面很简单,只有一个 texureview 和一个按钮。
接下来在 activity 中初始化并显示相机捕捉的画面。
首先要解决的一个问题就是画面拉伸的问题。
要解决这个问题,首先要从 textureview 下手。
cameraactivity.java
mtextureview.setsurfacetexturelistener(new textureview.surfacetexturelistener() { @override public void onsurfacetextureavailable(surfacetexture surfacetexture, int width, int height) { mwidth = width; mheight = height; getcameraid(); opencamera(); } @override public void onsurfacetexturesizechanged(surfacetexture surfacetexture, int i, int i1) { } @override public boolean onsurfacetexturedestroyed(surfacetexture surfacetexture) { return false; } @override public void onsurfacetextureupdated(surfacetexture surfacetexture) { } });
在 onsurfacetextureavailable 中初始化相机。通过 cameramanager 对象 opencamera,这正是流程图中 init 步骤中的第一步。opencamera 有三个参数,第一个是 string 类型的 cameraid,第二个是 cameradevice.statecallback,第三个是 handler。这里我们要声明一个 statecallback:
private cameradevice.statecallback mcameradevicestatecallback = new cameradevice.statecallback() { @override public void onopened(cameradevice cameradevice) { mcameradevice = cameradevice; createcamerapreview(); } @override public void ondisconnected(cameradevice cameradevice) { mcameradevice.close(); mcameradevice = null; } @override public void onerror(cameradevice cameradevice, int i) { mcameradevice.close(); mcameradevice = null; } };
可以看到,在 camera 准备完毕之后就可以创建预览界面了。解决画面拉伸的问题就是要为预览界面设置一个合适比例的 surfacetexture buffer size。
private void createcamerapreview() { try { surfacetexture texture = mtextureview.getsurfacetexture(); assert texture != null; cameracharacteristics characteristics = mcameramanager.getcameracharacteristics(mcameraid); streamconfigurationmap map = characteristics.get(cameracharacteristics.scaler_stream_configuration_map); int deviceorientation = getwindowmanager().getdefaultdisplay().getorientation(); int totalrotation = sensortodevicerotation(characteristics, deviceorientation); boolean swaprotation = totalrotation == 90 || totalrotation == 270; int rotatedwidth = mwidth; int rotatedheight = mheight; if (swaprotation) { rotatedwidth = mheight; rotatedheight = mwidth; } mpreviewsize = getpreferredpreviewsize(map.getoutputsizes(surfacetexture.class), rotatedwidth, rotatedheight); texture.setdefaultbuffersize(mpreviewsize.getwidth(), mpreviewsize.getheight()); log.e("cameraactivity", "optimalsize width: " + mpreviewsize.getwidth() + " height: " + mpreviewsize.getheight()); ...
这里根据当前设备及传感器的旋转角度来判断是否交换宽高值,然后通过 cameracharacteristics 来得到最适合当前大小比例的宽高,然后把这个宽高设置给 surfacetexture 。
private size getpreferredpreviewsize(size[] sizes, int width, int height) { list<size> collectorsizes = new arraylist<>(); for (size option : sizes) { if (width > height) { if (option.getwidth() > width && option.getheight() > height) { collectorsizes.add(option); } } else { if (option.getheight() > width && option.getwidth() > height) { collectorsizes.add(option); } } } if (collectorsizes.size() > 0) { return collections.min(collectorsizes, new comparator<size>() { @override public int compare(size s1, size s2) { return long.signum(s1.getwidth() * s1.getheight() - s2.getwidth() * s2.getheight()); } }); } return sizes[0]; } 这里 sizes 是相机返回的支持的分辨率,从我们传递的参数找找到一个最接近的分辨率。 接下来就要通过 capturerequest.builder以及 cameracapturesession.statecallback 来创建及更新预览界面: ... surface surface = new surface(texture); mbuilder = mcameradevice.createcapturerequest(cameradevice.template_preview); // 设置预览对象 mbuilder.addtarget(surface); mcameradevice.createcapturesession(arrays.aslist(surface), new cameracapturesession.statecallback() { @override public void onconfigured(cameracapturesession cameracapturesession) { if (null == mcameradevice) { return; } msession = cameracapturesession; mbuilder.set(capturerequest.control_mode, camerametadata.control_mode_auto); try { // 不停地将捕捉的画面更新到 textureview msession.setrepeatingrequest(mbuilder.build(), msessioncapturecallback, mbackgroundhandler); } catch (cameraaccessexception e) { e.printstacktrace(); } } @override public void onconfigurefailed(cameracapturesession cameracapturesession) { toast.maketext(cameraactivity.this, "camera configuration change", toast.length_short).show(); } }, null); } catch (cameraaccessexception e) { e.printstacktrace(); }
这样就完成了自定义相机第一步,源码地址请戳这里。下载地址:
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持!