前言

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

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

作者:KenChoi - 极光

原文:Android 用 camera2 API 自定义相机

知乎专栏:极光日报

Android 用 camera2 API 自定义相机的更多相关文章

  1. Android L Camera2 API 使用实例程序汇总

    在网上发现几个使用Camera API2开发的实例程序,总结一下方便后续参考: 1.Camera2 Basic : https://github.com/googlesamples/android-C ...

  2. Android Multimedia框架总结(十四)Camera框架初识及自定义相机案例

    转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/52738492 前言:国庆节告一段 ...

  3. android 自定义相机

    老规矩,先上一下项目地址:GitHub:https://github.com/xiangzhihong/CameraDemo 方式: 调用Camera API 自定义相机 调用系统相机 由于需求不同, ...

  4. Android 5.0 API

    Android 5.0 (LOLLIPOP) 为用户和应用开发者提供了新功能.本文旨在介绍其中最值得关注的新 API. 如果您有已发布的应用,请务必看一看 Android 5.0 行为变更,了解您的应 ...

  5. Android 5.0 API新增和改进

    开始开发 要构建 Android 5.0 版应用,您必须先下载 Android SDK,然后使用 SDK 管理器下载 Android 5.0 SDK 平台和系统映像. 更新您的目标 API 级别 要进 ...

  6. Android Camera API/Camera2 API 相机预览及滤镜、贴纸等处理

    Android Lollipop 添加了Camera2 API,并将原来的Camera API标记为废弃了.相对原来的Camera API来说.Camera2是又一次定义的相机 API,也重构了相机 ...

  7. Android相机使用(系统相机、自定义相机、大图片处理)

    本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显示出来,该例子也会涉及到Android加载大图片时候的处理(避免OOM),还有简要提一下有些人Surf ...

  8. Android自定义相机拍照、图片裁剪的实现

    最近项目里面又要加一个拍照搜题的功能,也就是用户对着不会做的题目拍一张照片,将照片的文字使用ocr识别出来,再调用题库搜索接口搜索出来展示给用户,类似于小猿搜题.学霸君等app. 其实Android提 ...

  9. Android调用系统相机、自定义相机、处理大图片

    Android调用系统相机和自定义相机实例 本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显示出来,该例子也会涉及到Android加载大图片时候的处理 ...

随机推荐

  1. 【webpack】-- 自动刷新

    前端需要频繁的修改js和样式,且需要根据浏览器的页面效果不断的做调整:而且往往我们的开发目录和本地发布目录不是同一个,修改之后需要发布一下:另外一点就是并不是所有的效果都可以直接双击页面就能看到,我们 ...

  2. 利用nginx concat模块合并js css

    前言: nginx-http-concat模块是由淘宝网开发的,现在淘宝网合并js和css正是用这个模块,如链接http://a.tbcdn.cn/??s/kissy/1.2.0/kissy-min. ...

  3. SQL Server-聚焦深入理解死锁以及避免死锁建议(三十三)

    前言 终于进入死锁系列,前面也提到过我一直对隔离级别和死锁以及如何避免死锁等问题模棱两可,所以才鼓起了重新学习SQL Server系列的勇气,本节我们来讲讲SQL Server中的死锁,看到许多文章都 ...

  4. Java的容器类小结

    Java的容器类其实就是集合类,只是为了不与Collection接口混淆,国内基本翻译为容器类. 容器类分为两种:独立元素序列的Collection和键值对的Map. Collection主要有如下几 ...

  5. Java数据结构之Set学习总结

    前言: 前面介绍了Java的数据结构List.Map,今天抽空学习总结一下另一种数据结构Set. Set介绍 Set相对于List.Map是最简单的一种集合.集合中的对象不按特定的方式排序,并且没有重 ...

  6. 学生选课数据库SQL语句45道练习题整理及mysql常用函数(20161019)

    学生选课数据库SQL语句45道练习题: 一.            设有一数据库,包括四个表:学生表(Student).课程表(Course).成绩表(Score)以及教师信息表(Teacher).四 ...

  7. .net core 部署 centos7 初试

    最近抽时间搞搞.net core,这方面已经有很多先驱人物贡献了好多文章了,今天沿着前辈们走的坑自己再淌一下...... 注意开头说的使用centos7,我电脑之前全是6.几的版本,然后就一头雾水的搞 ...

  8. 一种抛弃GPS的中近距离高精度无线同步新方案

    目前,对于需要做同步数据采集的项目,大家不约而同的选用GPS作同步源,用GPS的秒脉冲作同步基准.对于是1000米内的多采集点的应用来说,这是一种浪费. 目前福州慧聚通信技术有限公司推出一款无线同步数 ...

  9. .dll 无法查找或者打开PDB文件

    https://www.baidu.com/link?url=XBkzyMPU8bmyHSKAvBde6955fX2ecFJXfk8D44_VCuH_4U04E0bHFjk8D2_mXSqgjcUyQ ...

  10. Class.getResourceAsStream()与ClassLoader.getResourceAsStream()的区别

    Class.getResourceAsStream() 会指定要加载的资源路径与当前类所在包的路径一致. 例如你写了一个MyTest类在包com.test.mycode 下,那么MyTest.clas ...