前言

笔者因为项目需要自定义相机,所以了解了一下 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. Vue2.0源码阅读笔记--双向绑定实现原理

    上一篇 文章 了解了Vue.js的生命周期.这篇分析Observe Data过程,了解Vue.js的双向数据绑定实现原理. 一.实现双向绑定的做法 前端MVVM最令人激动的就是双向绑定机制了,实现双向 ...

  2. framework各版本新特性(为面试准备)

    菜鸟D估计描述这些新特性的文章都是烂大街的货色,之所以拿出来分(e)享(xin)一下,有两个原因:1.当年面试的时候有人问到,我不知道该怎么回答:2.项目需要发布了,但是考虑到framework的版本 ...

  3. 第四章:JQuery选择器

    一.使用选择器的目的 从页面上的万万千千个元素中选择自己需要的. 二.选择器的分类 1.基本选择器 *:标签 *:类 *:ID 交集  AB   AB共同的部分    并集  A,B  AB使用相同的 ...

  4. 解读Java内部类

    一.基本概念: 顾名思义,内部类存在于外部类当中,依附于外部类.就像眼睛和脑袋的关系一样. 二.几点说明: 1.内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以 ...

  5. Spring 数据源

    1.使用org.springframework.jdbc.datasource.DriverManagerDataSource说明:DriverManagerDataSource建立连接是只要有连接就 ...

  6. HQL查询——查询返回对象类型分析

    关于HQL查询,我们可以结合hibernate的API文档,重点围绕org.hibernate.Query接口,分析其方法,此接口的实例对象是通过通过session.对象的creatQuery(Str ...

  7. SQLHelper帮助类_下(支持多数据库的封装)

    在上篇关于SQLHelper类中,主要针对SQLServer数据库进行的.在使用别的数据库,就要修改部分代码!所以今天就写一个支持多数据库的封装!主要用到枚举,读取config文件!接口的简单用法.获 ...

  8. js设置当前页面始终为框架最顶层

    使用iframe做的页面,当session失效时,登录页面会显示在iframe里面 解决办法判断登录页面是否为顶层页面,不是的话刷新顶层页面

  9. c++文件读取(一)---输入类使用和查找当前程序路径

    鉴于公司人员流动大,设计一个小的日志定位器,首先要读取日志文件 用输入输出流类操作文件比c的stdio更安全和容易,当然任何此类观点都是对大牛除外 首先是查找当前文件路径,如果用GetCurrentD ...

  10. 使用js在网页上记录鼠标划圈的小程序

    Spin-Wheel 实现鼠标在网页上转圈时记录转动圈数的小程序,每转一圈记录一次,同时要是顺时针方向的. 问题分析与实现 这个小程序的难点在于如何知道鼠标完成了一个转圈的动作,而且人工使用鼠标划圈时 ...