Android API 21新增了Camera2,这与之前的camera架构完全不同,使用起来也比较复杂,但是功能变得很强大。

在讲解开启预览之前,首先需要了解camera2的几个比较重要的类:

  • CameraManager: 管理手机上的所有摄像头设备,它的作用主要是获取摄像头列表和打开指定的摄像头
  • CameraDevice: 具体的摄像头设备,它有一系列参数(预览尺寸、拍照尺寸等),可以通过CameraManager的getCameraCharacteristics()方法获取。它的作用主要是创建CameraCaptureSession和CaptureRequest
  • CameraCaptureSession: 相机捕获会话,用于处理拍照和预览的工作(很重要)
  • CaptureRequest: 捕获请求,定义输出缓冲区以及显示界面(TextureView或SurfaceView)等

下面梳理一下使用Camera2进行预览和拍照的主要流程:

1、定义TextureView作为预览界面

在布局文件中加入TextureView控件,然后实现其监听事件

textureView = (TextureView) findViewById(R.id.textureView);

然后我们可以在OnResume()方法中设置监听SurefaceTexture的事件

textureView.setSurfaceTextureListener(textureListener);

当SurefaceTexture准备好后会回调SurfaceTextureListener 的onSurfaceTextureAvailable()方法:

TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
//当SurefaceTexture可用的时候,设置相机参数并打开相机
setupCamera(width, height);
openCamera();
}
};

2、设置相机参数

为了更好地预览,我们根据TextureView的尺寸设置预览尺寸,Camera2中使用CameraManager来管理摄像头

private void setupCamera(int width, int height) {
//获取摄像头的管理者CameraManager
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
//遍历所有摄像头
for (String cameraId: manager.getCameraIdList()) {
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
//默认打开后置摄像头
if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT)
continue;
//获取StreamConfigurationMap,它是管理摄像头支持的所有输出格式和尺寸
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
//根据TextureView的尺寸设置预览尺寸
mPreviewSize = getOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height);
mCameraId = cameraId;
break;
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}

3、开启相机

Camera2中打开相机也需要通过CameraManager类:

private void openCamera() {
//获取相机的管理者CameraManager
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
//检查权限
try {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
return;
}
//打开相机,第一个参数指示打开哪个摄像头,第二个参数stateCallback为相机的状态回调接口,第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行
manager.openCamera(mCameraId, stateCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}

实现StateCallback 接口,当相机打开后会回调onOpened方法,在这个方法里面开启预览

private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice camera) {
mCameraDevice = camera;
//开启预览
startPreview();
}
}

4、开启相机预览

我们使用TextureView显示相机预览数据,Camera2的预览和拍照数据都是使用CameraCaptureSession会话来请求的

 private void startPreview() {
setupImageReader();
SurfaceTexture mSurfaceTexture = textureView.getSurfaceTexture();
//设置TextureView的缓冲区大小
mSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
//获取Surface显示预览数据
mPreviewSurface = new Surface(mSurfaceTexture);
try {
getPreviewRequestBuilder();
//创建相机捕获会话,第一个参数是捕获数据的输出Surface列表,第二个参数是CameraCaptureSession的状态回调接口,当它创建好后会回调onConfigured方法,第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行
mCameraDevice.createCaptureSession(Arrays.asList(mPreviewSurface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession session) {
mCaptureSession = session;
repeatPreview();
} @Override
public void onConfigureFailed(CameraCaptureSession session) { }
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}

5、实现PreviewCallback

Camera2没有提供Camera中的PreviewCallback,那么我们如何实现预览帧数据呢?在Camera2中提供了CameraCaptureSession.CaptureCallback:

private CameraCaptureSession.CaptureCallback mPreviewCaptureCallback = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { } @Override
public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) { }
};

6、实现拍照操作

Camera2拍照是通过ImageReader来实现的,首先先做些准备工作,比如设置拍照参数,如方向、尺寸等

private static final SparseIntArray ORIENTATION = new SparseIntArray();
static {
ORIENTATION.append(Surface.ROTATION_0, 90);
ORIENTATION.append(Surface.ROTATION_90, 0);
ORIENTATION.append(Surface.ROTATION_180, 270);
ORIENTATION.append(Surface.ROTATION_270, 180);
}

创建一个ImageReader,并监听它的事件:

private void setupImageReader() {
//前三个参数分别是需要的尺寸和格式,最后一个参数代表每次最多获取几帧数据
mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.JPEG, 1);
//监听ImageReader的事件,当有图像流数据可用时会回调onImageAvailable方法,它的参数就是预览帧数据,可以对这帧数据进行处理
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Log.i(TAG, "Image Available!");
Image image = reader.acquireLatestImage();
// 开启线程异步保存图片
new Thread(new ImageSaver(image)).start();
}
}, null);
}

注意:一定要调用reader.acquireLatestImage()和close()方法,否则预览画面就会卡住.

创建保存图片的线程

public static class ImageSaver implements Runnable {
private Image mImage;
public ImageSaver(Image image) {
mImage = image;
}
@Override
public void run() {
ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
mImageFile = new File(Environment.getExternalStorageDirectory() + "/DCIM/myPicture.jpg");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(mImageFile);
fos.write(data, 0 ,data.length);
} catch (IOException e) {
e.printStackTrace();
} finally {
mImageFile = null;
if (fos != null) {
try {
fos.close();
fos = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

现在准备工作做好了,还需要响应点击拍照事件,我们设置点击拍照按钮调用capture()方法,capture()方法即实现拍照:

private void capture() {
try {
//首先我们创建请求拍照的CaptureRequest
final CaptureRequest.Builder mCaptureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
//获取屏幕方向
int rotation = getWindowManager().getDefaultDisplay().getRotation(); mCaptureBuilder.addTarget(mPreviewSurface);
mCaptureBuilder.addTarget(mImageReader.getSurface()); //设置拍照方向
mCaptureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation)); //停止预览
mCaptureSession.stopRepeating(); //开始拍照,然后回调上面的接口重启预览,因为mCaptureBuilder设置ImageReader作为target,所以会自动回调ImageReader的onImageAvailable()方法保存图片
CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() { @Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
repeatPreview();
}
}; mCaptureSession.capture(mCaptureBuilder.build(), captureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}

示例代码:https://github.com/renhui/RHCamera2

Google官方Demo:https://github.com/googlearchive/android-Camera2Basic

Android 使用 Camera2 完成预览和拍照的更多相关文章

  1. Android使用Camera2获取预览数据

    一.Camera2简介 Camera2是Google在Android 5.0后推出的一个全新的相机API,Camera2和Camera没有继承关系,是完全重新设计的,且Camera2支持的功能也更加丰 ...

  2. Android Camera2 预览,拍照,人脸检测并实时展现

    https://www.jianshu.com/p/5414ba2b5508 背景     最近需要做一个人脸检测并实时预览的功能.就是边检测人脸,边在预览界面上框出来.     当然本人并不是专门做 ...

  3. Android Camera2获取预览尺寸和fps范围

    升降摄像头安卓手机刚上市的时候,有些很流行的app刚打开时,前置摄像头就升起来了.好像就是出来看一眼然后又收回去. 虽然我们不调用拍照功能,只是为了获取相机的信息,也是可能让摄像头升起来的. Came ...

  4. Android仿微信图片上传,可以选择多张图片,缩放预览,拍照上传等

    仿照微信,朋友圈分享图片功能 .可以进行图片的多张选择,拍照添加图片,以及进行图片的预览,预览时可以进行缩放,并且可以删除选中状态的图片 .很不错的源码,大家有需要可以下载看看 . 微信 微信 微信 ...

  5. Android平台之不预览获取照相机预览数据帧及精确时间截

    在android平台上要获取预览数据帧是一件极其容易的事儿,但要获取每帧数据对应的时间截并不那么容易,网络上关于这方面的资料也比较少.之所以要获取时间截,是因为某些情况下需要加入精确时间轴才能解决问题 ...

  6. Android开发中遇到的问题(三)——eclipse创建android项目无法正常预览布局文件

    一.问题描述 今天使用SDK Manager将Android SDK的版本更新到了Android 5.1的版本,eclipse创建android项目时,预览activity_main.xml文件时提示 ...

  7. Android 开发 Camera2开发_3_处理预览和拍照偏暗问题

    通过调整曝光解决 参考:https://stackoverflow.com/questions/28429071/camera-preview-is-too-dark-in-low-light-and ...

  8. Android图片上传,可以选择多张图片,缩放预览,拍照上传等

    仿照微信,朋友圈分享图片功能 .可以进行图片的多张选择,拍照添加图片,以及进行图片的预览,预览时可以进行缩放,并且可以删除选中状态的图片 .很不错的源码,大家有需要可以下载看看 . 微信 微信 微信 ...

  9. Camera实现预览、拍照

    1.利用Intent方法实现拍照并保存 在菜单或按钮的选择操作中调用如下代码,开启系统自带Camera APP,并传递一个拍照存储的路径给系统应用程序,具体如下: imgPath = "/s ...

随机推荐

  1. Spring事务隔离级别和传播性

    事务的隔离级别也分为四种: read uncommited(读未提交). read commited(读提交). read repeatable(读重复). serializable(序列化), 这四 ...

  2. Java语法 [常识1]

    1. Java 语言采用的是双字节Unicode 编码 . 2. 标识符就是变量.常量.方法[函数].枚举.类.接口等由写代码的猴子们制定的名字.构成标识符的字母均有一定的规范,Java语言中的命名规 ...

  3. ionic+cordova填坑

    1.命令行更新 cordova,ionic.nodejs ,npm,bower等到新版本,不要在vs中更新 2.程序突然白屏 因为拷贝其他程序到js中,乱码 :a模拟器打开 f12看控制台错误解决 3 ...

  4. 面试加分项---HashMap底层实现原理

    想必大家都知道HashSet和HashMap之间的关系,HashSet是依赖于HashMap的,HashSet集合就是HashMap的key所组成的集合,我们都知道HashMap的value是可以重复 ...

  5. ansible自动化运维详细教程及playbook详解

    前言 当下有许多的运维自动化工具( 配置管理 ),例如:Ansible.SaltStack.Puppet.Fabric 等. Ansible 一种集成 IT 系统的配置管理.应用部署.执行特定任务的开 ...

  6. [Ting's笔记Day5]在部署到Heroku之前,将Rails项目从SQLite设定为PostgreSQL

    前情提要: Paas(平台及服务)公司Heroku是个可以把我们写好的App部署到网际网络的好地方.而本篇是我从自己的上一篇文章:将Ruby on Rails项目部署到Heroku遇到的问题,当时困扰 ...

  7. azkaban 配置邮件

    1.配置邮件请在azkaban-web-server中进行配置:如下图:      /opt/azkaban/azkaban/azkaban-web-server/build/install/azka ...

  8. django + nginx + uwsgi

    server{ listen ; server_name 0.0.0.0; charset utf-; access_log /var/log/nginx/access.log main; locat ...

  9. Java 后台验证的工具类

    Java 后台验证的工具类 public class ValidationUtil {         //手机号     public static String mobile = "^( ...

  10. VS2015 提示 无法启动 IIS Express Web 服务器

    好久没有写东西了,不是没的写,是没时间了,今天快下班了,正好遇到这个一个问题,我就记录下来,以防忘记. 我定义了一个项目,Demo代码也写好了,然后,我们就把写好的项目代码加入到了源代码管理工具里.然 ...