Android 使用 Camera2 完成预览和拍照
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 完成预览和拍照的更多相关文章
- Android使用Camera2获取预览数据
一.Camera2简介 Camera2是Google在Android 5.0后推出的一个全新的相机API,Camera2和Camera没有继承关系,是完全重新设计的,且Camera2支持的功能也更加丰 ...
- Android Camera2 预览,拍照,人脸检测并实时展现
https://www.jianshu.com/p/5414ba2b5508 背景 最近需要做一个人脸检测并实时预览的功能.就是边检测人脸,边在预览界面上框出来. 当然本人并不是专门做 ...
- Android Camera2获取预览尺寸和fps范围
升降摄像头安卓手机刚上市的时候,有些很流行的app刚打开时,前置摄像头就升起来了.好像就是出来看一眼然后又收回去. 虽然我们不调用拍照功能,只是为了获取相机的信息,也是可能让摄像头升起来的. Came ...
- Android仿微信图片上传,可以选择多张图片,缩放预览,拍照上传等
仿照微信,朋友圈分享图片功能 .可以进行图片的多张选择,拍照添加图片,以及进行图片的预览,预览时可以进行缩放,并且可以删除选中状态的图片 .很不错的源码,大家有需要可以下载看看 . 微信 微信 微信 ...
- Android平台之不预览获取照相机预览数据帧及精确时间截
在android平台上要获取预览数据帧是一件极其容易的事儿,但要获取每帧数据对应的时间截并不那么容易,网络上关于这方面的资料也比较少.之所以要获取时间截,是因为某些情况下需要加入精确时间轴才能解决问题 ...
- Android开发中遇到的问题(三)——eclipse创建android项目无法正常预览布局文件
一.问题描述 今天使用SDK Manager将Android SDK的版本更新到了Android 5.1的版本,eclipse创建android项目时,预览activity_main.xml文件时提示 ...
- Android 开发 Camera2开发_3_处理预览和拍照偏暗问题
通过调整曝光解决 参考:https://stackoverflow.com/questions/28429071/camera-preview-is-too-dark-in-low-light-and ...
- Android图片上传,可以选择多张图片,缩放预览,拍照上传等
仿照微信,朋友圈分享图片功能 .可以进行图片的多张选择,拍照添加图片,以及进行图片的预览,预览时可以进行缩放,并且可以删除选中状态的图片 .很不错的源码,大家有需要可以下载看看 . 微信 微信 微信 ...
- Camera实现预览、拍照
1.利用Intent方法实现拍照并保存 在菜单或按钮的选择操作中调用如下代码,开启系统自带Camera APP,并传递一个拍照存储的路径给系统应用程序,具体如下: imgPath = "/s ...
随机推荐
- java.util.logging jdk日志详解
jdk自带的日志,结构并不复杂,功能也能满足绝大部分功能.日志写入位置是开放的,只要继承了handler都可以接收日志的写入.handler本身依赖于LogRecord对象,该对象代表一个日志.Han ...
- Java并发辅助类的使用
目录 1.概述 2.CountdownLatch 2-1.构造方法 2-2.重要方法 2-3.使用示例 3.CyclicBarrier 3-1.构造方法 3-2.使用示例 4.Semaphore 4- ...
- [Ting's笔记Day1] Ruby on Rails练习- MacOS安装篇
千里之行,始于足下.喊了要学Ruby on Rails好久,今天终于要来迈向第一步:安装了! 一开始学习新的事物,主要就是跟着这个网页所说的步骤step by step. 很喜欢这个网页的设计流程,透 ...
- IntelliJ IDEA2017 激活方法 最新的(亲测可用)
IntelliJ IDEA2017 激活方法(亲测可用): 搭建自己的授权服务器,对大佬来说也很简单,我作为菜鸟就不说了,网上有教程. 我主要说第二种,现在,直接写入注册码,是不能成功激活的(如果你成 ...
- Django_Form验证(三)字段,字段的参数,widgets种类
Form工具是一个很强大的工具,所以他的功能不仅仅是上面2个例子,这里详细记录一下Form的其他功能 字段的一般参数,以CharFields为列子: user=fields.CharField( re ...
- selenium-java,selenium安装配置
准备材料 1.java jdk http://www.oracle.com/technetwork/java/javase/downloads/index.html 2.开发工具 https://ww ...
- [Java核心技术]第四章-对象与类(4.1-4.6总结)
4.1面向对象程序设计概述 OOP(面向对象编程Object Oriented Programming) OOP中数据第一位,算法第二位. 类 封装:关键在于不能让其他方法直接访问类的实例域,程序仅通 ...
- 改变this的指向问题;
用call()和apply()改变this的指向,那什么时候用this呢?(构造函数),那为什么要用构造函数呢?(为了生成对象). 1.解决函数内this指向的问题 (1)var that/_this ...
- 面试简单整理之JVM
194.说一下 jvm 的主要组成部分?及其作用? JVM内存分为“堆”.“栈”和“方法区”三个区域,分别用于存储不同的数据. 堆内存用于存储使用new关键字所创建的对象: 栈内存用于存储程序运行时在 ...
- 201771010134杨其菊《面向对象程序设计java》第七周学习总结
第七周学习总结 第一部分:理论知识 1.继承是面向对象程序设计(Object Oriented Programming-OOP)中软件重用的关键技术.继承机制使用已经定义的类作为基础建立新的类定义,新 ...