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();
 }

这样就完成了自定义相机第一步,源码地址请戳这里。下载地址:http://xiazai.jb51.net/201704/yuanma/camera2-master_jb51.rar

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

(0)

相关推荐

  • Android中关于自定义相机预览界面拉伸问题

    关于自定义相机预览界面拉伸问题 1.导致主要变形的原因是Camera预览界面旋转的角度和摄像头挂载的角度不同导致的 2.我们的Activity设置的方向是竖屏,这是手机的自然方向 所以宽比高短 3.角度:所谓屏幕和摄像头的角度,指的是相对于自然方向旋转过的角度,根据旋转角度即可获知当前的方向 4.假如说:手机是竖屏的情况下, 自然角度为0,但是Camera逆时针旋转90度,那咱们设置顺时针旋转90度,就正常 .手机是横屏的情况下Camera返回为0度 ,如果设置顺时针旋转90度,就回旋转 怎么设

  • Android自定义照相机详解

    几乎每个APP都会用的相机功能,下面小编把内容整理分享到我们平台,供大家参考,感兴趣的朋友一起学习吧! 启动相机的两种方式 1.直接启动系统相机 <code class="hljs avrasm"> Intent intent = new Intent(); intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); startActivity(intent);</code> 或者指定返回图片的名称mCurrentPho

  • Android自定义相机实现自动对焦和手动对焦

    Android自定义相机实现自动对焦和手动对焦: 不调用系统相机,因为不同的机器打开相机呈现的界面不统一也不能满足需求. 所以为了让程序在不同的机器上呈现出统一的界面,并且可以根据需求进行布局,做了此demo. 程序实现代码如下: import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.la

  • Android自定义照相机倒计时拍照

    自定义拍照会用到SurfaceView控件显示照片的预览区域,以下是布局文件: 两个TextView是用来显示提示信息和倒计时的秒数的 相关教程:Android开发从相机或相册获取图片裁剪 Android启动相机拍照并返回图片 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools&qu

  • Android自定义相机界面的实现代码

    我们先实现拍照按钮的圆形效果哈,Android开发中,当然可以找美工人员设计图片,然后直接拿进来,不过我们可以自己写代码实现这个效果哈,最常用的的是用layout-list实现图片的叠加,我们这个layout命名为btn_take_photo.xml,这是一个自定义的drawable文件,所以按照规范,我们要将它放在drawable文件夹里. 注意:drawable文件夹一般是来放自定义的drawable文件的,可以将它看成自己写的背景样式等等哦 解释代码: layer-list里面放3个ite

  • Android自定义相机实现定时拍照功能

    这篇博客为大家介绍Android自定义相机,并且实现倒计时拍照功能. 首先自定义拍照会用到SurfaceView控件显示照片的预览区域,以下是布局文件: activity_main.xml <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="m

  • Android 自定义相机及分析源码

    Android 自定义相机及分析源码 使用Android 系统相机的方法: 要想让应用有相机的action,咱们就必须在清单文件中做一些声明,好让系统知道,如下 <intent-filter> <action android:name="android.intent.action.IMAGE_CAPTURE" /> <category android:name="android.intent.category.DEFAULT" />

  • Android实用控件自定义逼真相机光圈View

    最近手机界开始流行双摄像头,大光圈功能也应用而生.所谓大光圈功能就是能够对照片进行后期重新对焦,其实现的原理主要是对拍照期间获取的深度图片与对焦无穷远的图像通过算法来实现重新对焦的效果. 在某双摄手机的大光圈操作界面有个光圈的操作图标,能够模拟光圈调节时的真实效果,感觉还不错,于是想着实现该效果.现在把我的实现方法贡献给大家,万一你们公司也要做双摄手机呢?( ̄┰ ̄*) 首先,百度一下光圈图片,观察观察,就可以发现其关键在于计算不同的光圈值时各个光圈叶片的位置.为了计算简便,我以六个直边叶片的光圈

  • Android自定义照相机Camera出现黑屏的解决方法

    本文实例讲述了Android自定义照相机Camera出现黑屏的解决方法.分享给大家供大家参考,具体如下: 对于一些手机,像HTC,当自定义Camera时,调用Camera.Parameters的 parameters.setPreviewSize(width, height)方法时,如果width和height为奇数情况下,则会出现黑屏现象,解决办法可参考SDK提供的ApiDemos中关于Camera的 例子: List<Size> sizes = parameters.getSupporte

  • Android自定义组件获取本地图片和相机拍照图片

    iOS中有封装好的选择图片后长按出现动画删除效果,效果如下 而Android找了很久都没有找到有这样效果的第三方组件,最后懒得找了还是自己实现这效果吧 选择图片后还可对图片进行剪裁 当然,代码中还有很多不完善的地方,我接下来会继续完善这个组件的 已经上传到开源社区,欢迎大家来Star啊~ Demo源码:传送门 设计中的碰到的一些问题和解决思路 1.如何让加号图片显示在GridView最后面 首先在调用GridAdapter构造方法时就加载加号图片 /** * 图片适配器 * @param con

随机推荐