Android 用 camera2 API 自定义相机

作者:极光推送 时间:2023-04-30 11:29:37 

前言

笔者因为项目需要自定义相机,所以了解了一下 Android 关于 camera 这块的 API。Android SDK 21(LOLLIPOP) 开始已经弃用了之前的 Camera 类,提供了 camera2 相关 API,目前网上关于 camera2 API 介绍的资料比较少,笔者搜集网上资料,结合自己的实践,在这里做一个总结。

流程

因为 camera2 提供的接口比较多,虽然很灵活,但是也增加了使用的复杂度。首先来大致了解一下调用 camera2 的流程,方便我们理清思路。

Android 用 camera2 API 自定义相机

要显示相机捕捉的画面,只需要三步:初始化相机,预览,更新预览。也就是上图中左侧的部分。要实现这三步,需要用到的主要接口类和它们的作用步骤如上图右侧部分所示。下面就用代码来详解一下。

案例

首先创建一个相机界面:

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

来源:http://www.cnblogs.com/jpush88/p/6670724.html

标签:android,相机
0
投稿

猜你喜欢

  • 7种形式的Android Dialog使用实例

    2023-02-08 19:41:02
  • C#实现带消息数的App图标

    2022-03-08 11:30:52
  • Mybatis-Plus查询中如何排除标识字段

    2023-11-23 20:38:46
  • C#绘制中国象棋棋盘

    2021-05-27 15:08:44
  • Java数据结构之栈与队列实例详解

    2021-05-29 03:25:13
  • 详解java模板和回调机制

    2023-08-13 15:33:46
  • Netty 拆包沾包问题解决方案详解

    2023-09-01 21:58:50
  • windows定时器配置执行java jar文件的方法详解

    2023-04-17 16:40:21
  • 修改jar包package目录结构操作方法

    2021-12-31 13:46:45
  • Java 设计模式中的命令模式详情

    2023-11-15 23:25:33
  • 完成OSS.Http底层HttpClient重构封装 支持标准库

    2021-09-28 11:25:40
  • Spring实现默认标签解析流程

    2021-07-29 10:07:55
  • 详解Android中使用Notification实现进度通知栏(示例三)

    2023-10-06 22:36:10
  • Flutter通过Container实现时间轴效果

    2023-10-04 09:40:45
  • Spring Feign超时设置深入了解

    2022-01-17 10:39:06
  • C#实现顺序表(线性表)完整实例

    2022-06-04 15:42:31
  • spring AOP定义AfterThrowing增加处理实例分析

    2021-07-11 14:22:11
  • 简单实现Android应用的启动页

    2022-04-15 14:23:45
  • C#实现鼠标移动到曲线图上显示值的方法

    2023-06-01 00:56:06
  • java判断两个时间是不是同一天的方法

    2022-09-23 03:09:16
  • asp之家 软件编程 m.aspxhome.com