Android实现Camera2预览和拍照效果
程序员文章站
2023-11-28 21:40:28
简介
网上对于 camera2 的介绍有很多,在 github 上也有很多关于 camera2 的封装库,但是对于那些库,封装性太强,有时候我们仅仅是需要个简简单单的拍照...
简介
网上对于 camera2 的介绍有很多,在 github 上也有很多关于 camera2 的封装库,但是对于那些库,封装性太强,有时候我们仅仅是需要个简简单单的拍照功能而已,因此,自定义一个 camera 使之变得轻量级那是非常重要的了。(本文并非重复造*, 而是在于学习 camera2api 的基本功能, 笔记之。)
学习要点:
使用 android camera2 api 的基本功能。
迭代连接到设备的所有相机的特征。
显示相机预览和拍摄照片。
camera2 api 为连接到 android 设备的各个相机设备提供了一个界面。 它替代了已弃用的 camera 类。
- 使用 getcameraidlist 获取所有可用摄像机的列表。 然后,您可以使用 getcameracharacteristics,并找到适合您需要的最佳相机(前 / 后面,分辨率等)。
- 创建一个 cameradevice.statecallback 的实例并打开相机。 当相机打开时,准备开始相机预览。
- 使用 textureview 显示相机预览。 创建一个 cameracapturesession 并设置一个重复的 capturerequest。
- 静像拍摄需要几个步骤。 首先,需要通过更新相机预览的 capturerequest 来锁定相机的焦点。
- 然后,以类似的方式,需要运行一个预捕获序列。之后,它准备拍摄一张照片。 创建一个新的 capturerequest 并调用 [capture] 。
完成后,别忘了解锁焦点。
实现效果
环境
sdk>21
camera2 类图
代码实现
camerapreview.java
/** * created by shenhua on 2017-10-20-0020. * email shenhuanet@126.com */ public class camerapreview extends textureview { private static final string tag = "camerapreview"; private static final sparseintarray orientations = new sparseintarray();//从屏幕旋转转换为jpeg方向 private static final int max_preview_width = 1920;//camera2 api 保证的最大预览宽高 private static final int max_preview_height = 1080; private static final int state_preview = 0;//显示相机预览 private static final int state_waiting_lock = 1;//焦点锁定中 private static final int state_waiting_pre_capture = 2;//拍照中 private static final int state_waiting_non_pre_capture = 3;//其它状态 private static final int state_picture_taken = 4;//拍照完毕 private int mstate = state_preview; private int mratiowidth = 0, mratioheight = 0; private int msensororientation; private boolean mflashsupported; private semaphore mcameraopencloselock = new semaphore(1);//使用信号量 semaphore 进行多线程任务调度 private activity activity; private file mfile; private handlerthread mbackgroundthread; private handler mbackgroundhandler; private size mpreviewsize; private string mcameraid; private cameradevice mcameradevice; private capturerequest.builder mpreviewrequestbuilder; private capturerequest mpreviewrequest; private cameracapturesession mcapturesession; private imagereader mimagereader; static { orientations.append(surface.rotation_0, 90); orientations.append(surface.rotation_90, 0); orientations.append(surface.rotation_180, 270); orientations.append(surface.rotation_270, 180); } public camerapreview(context context) { this(context, null); } public camerapreview(context context, attributeset attrs) { this(context, attrs, 0); } public camerapreview(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); mfile = new file(getcontext().getexternalfilesdir(null), "pic.jpg"); } @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); } } } public void onresume(activity activity) { this.activity = activity; startbackgroundthread(); //当activity或fragment onresume()时,可以冲洗打开一个相机并开始预览,否则,这个surface已经准备就绪 if (this.isavailable()) { opencamera(this.getwidth(), this.getheight()); } else { this.setsurfacetexturelistener(msurfacetexturelistener); } } public void onpause() { closecamera(); stopbackgroundthread(); } public void setoutputdir(file file) { this.mfile = file; } public void setaspectratio(int width, int height) { if (width < 0 || height < 0) { throw new illegalargumentexception("size can't be negative"); } mratiowidth = width; mratioheight = height; requestlayout(); } public void setautoflash(capturerequest.builder requestbuilder) { if (mflashsupported) { requestbuilder.set(capturerequest.control_ae_mode, capturerequest.control_ae_mode_on_auto_flash); } } public void takepicture() { lockfocus(); } private void startbackgroundthread() { mbackgroundthread = new handlerthread("camerabackground"); mbackgroundthread.start(); mbackgroundhandler = new handler(mbackgroundthread.getlooper()); } private void stopbackgroundthread() { mbackgroundthread.quitsafely(); try { mbackgroundthread.join(); mbackgroundthread = null; mbackgroundhandler = null; } catch (interruptedexception e) { e.printstacktrace(); } } /** * 处理生命周期内的回调事件 */ private final textureview.surfacetexturelistener msurfacetexturelistener = new textureview.surfacetexturelistener() { @override public void onsurfacetextureavailable(surfacetexture texture, int width, int height) { opencamera(width, height); } @override public void onsurfacetexturesizechanged(surfacetexture texture, int width, int height) { configuretransform(width, height); } @override public boolean onsurfacetexturedestroyed(surfacetexture texture) { return true; } @override public void onsurfacetextureupdated(surfacetexture texture) { } }; /** * 相机状态改变回调 */ private final cameradevice.statecallback mstatecallback = new cameradevice.statecallback() { @override public void onopened(@nonnull cameradevice cameradevice) { mcameraopencloselock.release(); log.d(tag, "相机已打开"); 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; if (null != activity) { activity.finish(); } } }; /** * 处理与照片捕获相关的事件 */ private cameracapturesession.capturecallback mcapturecallback = new cameracapturesession.capturecallback() { private void process(captureresult result) { switch (mstate) { case state_preview: { break; } case state_waiting_lock: { integer afstate = result.get(captureresult.control_af_state); if (afstate == null) { capturestillpicture(); } else if (captureresult.control_af_state_focused_locked == afstate || captureresult.control_af_state_not_focused_locked == afstate) { integer aestate = result.get(captureresult.control_ae_state); if (aestate == null || aestate == captureresult.control_ae_state_converged) { mstate = state_picture_taken; capturestillpicture(); } else { runprecapturesequence(); } } break; } case state_waiting_pre_capture: { integer aestate = result.get(captureresult.control_ae_state); if (aestate == null || aestate == captureresult.control_ae_state_precapture || aestate == capturerequest.control_ae_state_flash_required) { mstate = state_waiting_non_pre_capture; } break; } case state_waiting_non_pre_capture: { integer aestate = result.get(captureresult.control_ae_state); if (aestate == null || aestate != captureresult.control_ae_state_precapture) { mstate = state_picture_taken; capturestillpicture(); } break; } } } @override public void oncaptureprogressed(@nonnull cameracapturesession session, @nonnull capturerequest request, @nonnull captureresult partialresult) { process(partialresult); } @override public void oncapturecompleted(@nonnull cameracapturesession session, @nonnull capturerequest request, @nonnull totalcaptureresult result) { process(result); } }; /** * 在确定相机预览大小后应调用此方法 * * @param viewwidth 宽 * @param viewheight 高 */ private void configuretransform(int viewwidth, int viewheight) { if (null == mpreviewsize || null == activity) { return; } int rotation = activity.getwindowmanager().getdefaultdisplay().getrotation(); matrix matrix = new matrix(); rectf viewrect = new rectf(0, 0, viewwidth, viewheight); rectf bufferrect = new rectf(0, 0, mpreviewsize.getheight(), mpreviewsize.getwidth()); float centerx = viewrect.centerx(); float centery = viewrect.centery(); if (surface.rotation_90 == rotation || surface.rotation_270 == rotation) { bufferrect.offset(centerx - bufferrect.centerx(), centery - bufferrect.centery()); matrix.setrecttorect(viewrect, bufferrect, matrix.scaletofit.fill); float scale = math.max( (float) viewheight / mpreviewsize.getheight(), (float) viewwidth / mpreviewsize.getwidth()); matrix.postscale(scale, scale, centerx, centery); matrix.postrotate(90 * (rotation - 2), centerx, centery); } else if (surface.rotation_180 == rotation) { matrix.postrotate(180, centerx, centery); } this.settransform(matrix); } /** * 根据mcameraid打开相机 */ private void opencamera(int width, int height) { setupcameraoutputs(width, height); configuretransform(width, height); cameramanager manager = (cameramanager) getcontext().getsystemservice(context.camera_service); try { if (!mcameraopencloselock.tryacquire(2500, timeunit.milliseconds)) { throw new runtimeexception("time out waiting to lock camera opening."); } if (activitycompat.checkselfpermission(activity, manifest.permission.camera) != packagemanager.permission_granted) { // todo: consider calling // activitycompat#requestpermissions // here to request the missing permissions, and then overriding // public void onrequestpermissionsresult(int requestcode, string[] permissions, // int[] grantresults) // to handle the case where the user grants the permission. see the documentation // for activitycompat#requestpermissions for more details. return; } manager.opencamera(mcameraid, mstatecallback, mbackgroundhandler); } catch (cameraaccessexception e) { e.printstacktrace(); } catch (interruptedexception e) { throw new runtimeexception("interrupted while trying to lock camera opening.", e); } } /** * 关闭相机 */ private void closecamera() { try { mcameraopencloselock.acquire(); if (null != mcapturesession) { mcapturesession.close(); mcapturesession = null; } if (null != mcameradevice) { mcameradevice.close(); mcameradevice = null; } if (null != mimagereader) { mimagereader.close(); mimagereader = null; } } catch (interruptedexception e) { throw new runtimeexception("interrupted while trying to lock camera closing.", e); } finally { mcameraopencloselock.release(); } } /** * 设置相机相关的属性或变量 * * @param width 相机预览的可用尺寸的宽度 * @param height 相机预览的可用尺寸的高度 */ @suppresswarnings("suspiciousnamecombination") private void setupcameraoutputs(int width, int height) { cameramanager manager = (cameramanager) getcontext().getsystemservice(context.camera_service); try { for (string cameraid : manager.getcameraidlist()) { cameracharacteristics characteristics = manager.getcameracharacteristics(cameraid); // 在这个例子中不使用前置摄像头 integer facing = characteristics.get(cameracharacteristics.lens_facing); if (facing != null && facing == cameracharacteristics.lens_facing_front) { continue; } streamconfigurationmap map = characteristics.get(cameracharacteristics.scaler_stream_configuration_map); if (map == null) { continue; } size largest = collections.max(arrays.aslist(map.getoutputsizes(imageformat.jpeg)), new comparesizesbyarea()); mimagereader = imagereader.newinstance(largest.getwidth(), largest.getheight(), imageformat.jpeg, /*maximages*/2); mimagereader.setonimageavailablelistener( monimageavailablelistener, mbackgroundhandler); int displayrotation = activity.getwindowmanager().getdefaultdisplay().getrotation(); // noinspection constantconditions msensororientation = characteristics.get(cameracharacteristics.sensor_orientation); boolean swappeddimensions = false; switch (displayrotation) { case surface.rotation_0: case surface.rotation_180: if (msensororientation == 90 || msensororientation == 270) { swappeddimensions = true; } break; case surface.rotation_90: case surface.rotation_270: if (msensororientation == 0 || msensororientation == 180) { swappeddimensions = true; } break; default: log.e(tag, "display rotation is invalid: " + displayrotation); } point displaysize = new point(); activity.getwindowmanager().getdefaultdisplay().getsize(displaysize); int rotatedpreviewwidth = width; int rotatedpreviewheight = height; int maxpreviewwidth = displaysize.x; int maxpreviewheight = displaysize.y; if (swappeddimensions) { rotatedpreviewwidth = height; rotatedpreviewheight = width; maxpreviewwidth = displaysize.y; maxpreviewheight = displaysize.x; } if (maxpreviewwidth > max_preview_width) { maxpreviewwidth = max_preview_width; } if (maxpreviewheight > max_preview_height) { maxpreviewheight = max_preview_height; } mpreviewsize = chooseoptimalsize(map.getoutputsizes(surfacetexture.class), rotatedpreviewwidth, rotatedpreviewheight, maxpreviewwidth, maxpreviewheight, largest); int orientation = getresources().getconfiguration().orientation; if (orientation == configuration.orientation_landscape) { setaspectratio(mpreviewsize.getwidth(), mpreviewsize.getheight()); } else { setaspectratio(mpreviewsize.getheight(), mpreviewsize.getwidth()); } boolean available = characteristics.get(cameracharacteristics.flash_info_available); mflashsupported = available == null ? false : available; mcameraid = cameraid; return; } } catch (cameraaccessexception e) { e.printstacktrace(); } catch (nullpointerexception e) { log.e(tag, "设备不支持camera2"); } } /** * 获取一个合适的相机预览尺寸 * * @param choices 支持的预览尺寸列表 * @param textureviewwidth 相对宽度 * @param textureviewheight 相对高度 * @param maxwidth 可以选择的最大宽度 * @param maxheight 可以选择的最大高度 * @param aspectratio 宽高比 * @return 最佳预览尺寸 */ private static size chooseoptimalsize(size[] choices, int textureviewwidth, int textureviewheight, int maxwidth, int maxheight, size aspectratio) { list<size> bigenough = new arraylist<>(); list<size> notbigenough = new arraylist<>(); int w = aspectratio.getwidth(); int h = aspectratio.getheight(); for (size option : choices) { if (option.getwidth() <= maxwidth && option.getheight() <= maxheight && option.getheight() == option.getwidth() * h / w) { if (option.getwidth() >= textureviewwidth && option.getheight() >= textureviewheight) { bigenough.add(option); } else { notbigenough.add(option); } } } if (bigenough.size() > 0) { return collections.min(bigenough, new comparesizesbyarea()); } else if (notbigenough.size() > 0) { return collections.max(notbigenough, new comparesizesbyarea()); } else { log.e(tag, "couldn't find any suitable preview size"); return choices[0]; } } /** * 为相机预览创建新的cameracapturesession */ private void createcamerapreviewsession() { try { surfacetexture texture = this.getsurfacetexture(); assert texture != null; // 将默认缓冲区的大小配置为想要的相机预览的大小 texture.setdefaultbuffersize(mpreviewsize.getwidth(), mpreviewsize.getheight()); surface surface = new surface(texture); mpreviewrequestbuilder = mcameradevice.createcapturerequest(cameradevice.template_preview); mpreviewrequestbuilder.addtarget(surface); // 我们创建一个 cameracapturesession 来进行相机预览 mcameradevice.createcapturesession(arrays.aslist(surface, mimagereader.getsurface()), 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); setautoflash(mpreviewrequestbuilder); mpreviewrequest = mpreviewrequestbuilder.build(); mcapturesession.setrepeatingrequest(mpreviewrequest, mcapturecallback, mbackgroundhandler); } catch (cameraaccessexception e) { e.printstacktrace(); } } @override public void onconfigurefailed(@nonnull cameracapturesession cameracapturesession) { } }, null); } catch (cameraaccessexception e) { e.printstacktrace(); } } /** * 从指定的屏幕旋转中检索照片方向 * * @param rotation 屏幕方向 * @return 照片方向(0,90,270,360) */ private int getorientation(int rotation) { return (orientations.get(rotation) + msensororientation + 270) % 360; } /** * 锁定焦点 */ private void lockfocus() { try { // 如何通知相机锁定焦点 mpreviewrequestbuilder.set(capturerequest.control_af_trigger, camerametadata.control_af_trigger_start); // 通知mcapturecallback等待锁定 mstate = state_waiting_lock; mcapturesession.capture(mpreviewrequestbuilder.build(), mcapturecallback, mbackgroundhandler); } catch (cameraaccessexception e) { e.printstacktrace(); } } /** * 解锁焦点 */ private void unlockfocus() { try { mpreviewrequestbuilder.set(capturerequest.control_af_trigger, camerametadata.control_af_trigger_cancel); setautoflash(mpreviewrequestbuilder); mcapturesession.capture(mpreviewrequestbuilder.build(), mcapturecallback, mbackgroundhandler); mstate = state_preview; mcapturesession.setrepeatingrequest(mpreviewrequest, mcapturecallback, mbackgroundhandler); } catch (cameraaccessexception e) { e.printstacktrace(); } } /** * 拍摄静态图片 */ private void capturestillpicture() { try { if (null == activity || null == mcameradevice) { return; } final capturerequest.builder capturebuilder = mcameradevice.createcapturerequest(cameradevice.template_still_capture); capturebuilder.addtarget(mimagereader.getsurface()); capturebuilder.set(capturerequest.control_af_mode, capturerequest.control_af_mode_continuous_picture); setautoflash(capturebuilder); // 方向 int rotation = activity.getwindowmanager().getdefaultdisplay().getrotation(); capturebuilder.set(capturerequest.jpeg_orientation, getorientation(rotation)); cameracapturesession.capturecallback capturecallback = new cameracapturesession.capturecallback() { @override public void oncapturecompleted(@nonnull cameracapturesession session, @nonnull capturerequest request, @nonnull totalcaptureresult result) { toast.maketext(getcontext(), "saved: " + mfile, toast.length_short).show(); log.d(tag, mfile.tostring()); unlockfocus(); } }; mcapturesession.stoprepeating(); mcapturesession.abortcaptures(); mcapturesession.capture(capturebuilder.build(), capturecallback, null); } catch (cameraaccessexception e) { e.printstacktrace(); } } /** * 运行precapture序列来捕获静止图像 */ private void runprecapturesequence() { try { // 设置拍照参数请求 mpreviewrequestbuilder.set(capturerequest.control_ae_precapture_trigger, capturerequest.control_ae_precapture_trigger_start); mstate = state_waiting_pre_capture; mcapturesession.capture(mpreviewrequestbuilder.build(), mcapturecallback, mbackgroundhandler); } catch (cameraaccessexception e) { e.printstacktrace(); } } /** * 比较两者大小 */ private static class comparesizesbyarea implements comparator<size> { @override public int compare(size lhs, size rhs) { return long.signum((long) lhs.getwidth() * lhs.getheight() - (long) rhs.getwidth() * rhs.getheight()); } } /** * imagereader的回调对象 */ private final imagereader.onimageavailablelistener monimageavailablelistener = new imagereader.onimageavailablelistener() { @override public void onimageavailable(imagereader reader) { mbackgroundhandler.post(new imagesaver(reader.acquirenextimage(), mfile)); } }; /** * 将捕获到的图像保存到指定的文件中 */ private static class imagesaver implements runnable { private final image mimage; private final file mfile; imagesaver(image image, file file) { mimage = image; mfile = file; } @override public void run() { bytebuffer buffer = mimage.getplanes()[0].getbuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); fileoutputstream output = null; try { output = new fileoutputstream(mfile); output.write(bytes); } catch (ioexception e) { e.printstacktrace(); } finally { mimage.close(); if (null != output) { try { output.close(); } catch (ioexception e) { e.printstacktrace(); } } } } } }
mainactivity.java
public class mainactivity extends appcompatactivity { camerapreview cameraview; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); cameraview = (camerapreview) findviewbyid(r.id.cameraview); } @override protected void onresume() { super.onresume(); cameraview.onresume(this); } @override protected void onpause() { cameraview.onpause(); super.onpause(); } public void takepic(view view) { cameraview.takepicture(); } }
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.constraintlayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/constraintlayout" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/black" tools:context="com.shenhua.ocr.activity.main2activity"> <com.shenhua.ocr.widget.camerapreview android:id="@+id/cameraview" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <button android:layout_width="70dp" android:layout_height="70dp" android:layout_gravity="center" android:background="@drawable/ic_capture_200px" android:onclick="takepic" android:text="take" app:layout_constraintbottom_tobottomof="@id/constraintlayout" app:layout_constraintend_toendof="@id/constraintlayout" app:layout_constraintstart_tostartof="@id/constraintlayout" app:layout_constrainttop_totopof="@id/cameraview" app:layout_constraintvertical_bias="0.97" /> </android.support.constraint.constraintlayout>
资源文件 ic_capture_200px.xml
<vector android:height="24dp" android:viewportheight="1024.0" android:viewportwidth="1024.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> <path android:fillcolor="#03a9f4" android:pathdata="m512,512m-393.8,0a393.8,393.8 0,1 0,787.7 0,393.8 393.8,0 1,0 -787.7,0z"/> <path android:fillcolor="#03a9f4" android:pathdata="m512,1024c229.2,1024 0,794.8 0,512s229.2,0 512,0s512,229.2 512,512 -229.2,512 -512,512zm512,984.6c261,0 472.6,-211.6 472.6,-472.6s773,39.4 512,39.4 39.4,251 39.4,512s211.6,472.6 472.6,472.6z"/> </vector>
其它
manifest 权限:
<uses-permission android:name="android.permission.camera" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" />
android6.0 运行时权限未贴出。(注意:为了方便读者手机端阅读,本文代码部分的成员变量使用了行尾注释,在正常编程习惯中,请使用 /* / 注释。)
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。