当前位置: 移动技术网 > 移动技术>移动开发>Android > Android 用 camera2 API 自定义相机

Android 用 camera2 API 自定义相机

2019年07月24日  | 移动技术网移动技术  | 我要评论

前言

笔者因为项目需要自定义相机,所以了解了一下 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();
 }

这样就完成了自定义相机第一步,源码地址请戳这里。下载地址:

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持移动技术网!

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网