转载请注明出处: http://blog.csdn.net/lb377463323/article/details/52740411

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

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

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

1,定义TextureView作为预览界面

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

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

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

textureView.setSurfaceTextureListener(textureListener);

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

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

2,设置相机参数

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

private void setupCamera(int width, int height) {//获取摄像头的管理者CameraManagerCameraManager 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() {//获取摄像头的管理者CameraManagerCameraManager 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() {@Overridepublic void onOpened(CameraDevice camera) {mCameraDevice = camera;//开启预览startPreview();}
}

4,开启相机预览

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

private void startPreview() {SurfaceTexture mSurfaceTexture = mTextureView.getSurfaceTexture();//设置TextureView的缓冲区大小mSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());//获取Surface显示预览数据Surface mSurface = new Surface(mSurfaceTexture);try {//创建CaptureRequestBuilder,TEMPLATE_PREVIEW比表示预览请求mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);//设置Surface作为预览数据的显示界面mCaptureRequestBuilder.addTarget(mSurface);//创建相机捕获会话,第一个参数是捕获数据的输出Surface列表,第二个参数是CameraCaptureSession的状态回调接口,当它创建好后会回调onConfigured方法,第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行mCameraDevice.createCaptureSession(Arrays.asList(mSurface), new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(CameraCaptureSession session) {try {//创建捕获请求mCaptureRequest = mCaptureRequestBuilder.build();mPreviewSession = session;//设置反复捕获数据的请求,这样预览界面就会一直有数据显示mPreviewSession.setRepeatingRequest(mCaptureRequest, mSessionCaptureCallback, null);} catch (CameraAccessException e) {e.printStackTrace();}}@Overridepublic void onConfigureFailed(CameraCaptureSession session) {}}, null);} catch (CameraAccessException e) {e.printStackTrace();}
}

5,实现PreviewCallback

Camera2中并没有Camera1中的PreviewCallback接口,那怎么实现获取预览帧数据呢?答案就是使用ImageReader间接实现

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

private void setupImageReader() {//前三个参数分别是需要的尺寸和格式,最后一个参数代表每次最多获取几帧数据,本例的2代表ImageReader中最多可以获取两帧图像流mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),ImageFormat.JPEG, 2);//监听ImageReader的事件,当有图像流数据可用时会回调onImageAvailable方法,它的参数就是预览帧数据,可以对这帧数据进行处理mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader reader) {Image image = reader.acquireLatestImage();//我们可以将这帧数据转成字节数组,类似于Camera1的PreviewCallback回调的预览帧数据ByteBuffer buffer = image.getPlanes()[0].getBuffer();byte[] data = new byte[buffer.remaining()];buffer.get(data);image.close();}}, null);
}

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

然后我们在开启预览之前,设置ImageReader为输出Surface

setupImageReader();//获取ImageReader的Surface
Surface imageReaderSurface = mImageReader.getSurface();//CaptureRequest添加imageReaderSurface,不加的话就会导致ImageReader的onImageAvailable()方法不会回调
mCaptureRequestBuilder.addTarget(imageReaderSurface);//创建CaptureSession时加上imageReaderSurface,如下,这样预览数据就会同时输出到previewSurface和imageReaderSurface了
mCameraDevice.createCaptureSession(Arrays.asList(previewSurface, imageReaderSurface), new CameraCaptureSession.StateCallback() {}

关闭相机时别忘了关闭ImageReader

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初始化使用此尺寸

mCaptureSize = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new Comparator<Size>() {@Overridepublic int compare(Size lhs, Size rhs) {return Long.signum(lhs.getWidth() * lhs.getHeight() - rhs.getHeight() * rhs.getWidth());}});

创建保存图片的线程

public static class imageSaver implements Runnable {private Image mImage;public imageSaver(Image image) {mImage = image;}@Overridepublic 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();}}}}}

然后当ImageReader有数据时,通过此线程保存图片

//使用前面获取的拍照尺寸
mImageReader = ImageReader.newInstance(mCaptureSize.getWidth(), mCaptureSize.getHeight(),ImageFormat.JPEG, 2);
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader reader) {//执行图像保存子线程mCameraHandler.post(new imageSaver(reader.acquireNextImage()));}}, mCameraHandler);

然后开启预览创建CaptureSession时把ImageReader添加进去

mCameraDevice.createCaptureSession(Arrays.asList(previewSurface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {
}

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

private void capture() {try {//首先我们创建请求拍照的CaptureRequestfinal CaptureRequest.Builder mCaptureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);//获取屏幕方向int rotation = getWindowManager().getDefaultDisplay().getRotation();//设置CaptureRequest输出到mImageReadermCaptureBuilder.addTarget(mImageReader.getSurface());//设置拍照方向mCaptureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));//这个回调接口用于拍照结束时重启预览,因为拍照会导致预览停止CameraCaptureSession.CaptureCallback mImageSavedCallback = new CameraCaptureSession.CaptureCallback() {@Overridepublic void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {Toast.makeText(getApplicationContext(), "Image Saved!", Toast.LENGTH_SHORT).show();//重启预览restartPreview();}};//停止预览mCameraCaptureSession.stopRepeating();//开始拍照,然后回调上面的接口重启预览,因为mCaptureBuilder设置ImageReader作为target,所以会自动回调ImageReader的onImageAvailable()方法保存图片mCameraCaptureSession.capture(mCaptureBuilder.build(), mImageSavedCallback, null);} catch (CameraAccessException e) {e.printStackTrace();}}

重启预览的方法很简单了

 private void restartPreview() {try {//执行setRepeatingRequest方法就行了,注意mCaptureRequest是之前开启预览设置的请求mCameraCaptureSession.setRepeatingRequest(mCaptureRequest, null, mCameraHandler);} catch (CameraAccessException e) {e.printStackTrace();}}

代码地址(顺手给个Star啊):点击查看源码

Android Camera2教程之打开相机、开启预览、实现PreviewCallback、拍照相关推荐

  1. Android开发实践:掌握Camera的预览方向和拍照方向

    Android的Camera相关应用开发中,有一个必须搞清楚的知识点,就是Camera的预览方向和拍照方向,本文就重点讨论一下这个问题. 图像的Sensor方向:手机Camera的图像数据都是来自于摄 ...

  2. Android OTG 连接单反相机,相机无法预览无法拍照片问题修复记录

    一开始我用佳能相机链接手机后拍照预览是ok的,后面换成尼康后无法预览拍照,经过一段时间的调试终于解决 Android 手机连接尼康相机后无法实时预览照片问题.其实很简单设置模式问题,就两行行代码搞定: ...

  3. Android OTG 连接单反相机,相机无法预览已拍照片问题修复笔记

    花了两三天终于解决 Android 手机连接尼康相机后无法实时预览照片问题. 就两行行代码搞定 KLog.w("设置设备属性指令 = "+NikonApplicationMode) ...

  4. android camera 显示过程,Android Camera2 API显示已处理的预览图像

    澄清问题后编辑;最初的答案在底部 取决于您在哪里进行处理. 如果您正在使用RenderScript,则可以将Surface从SurfaceView或TextureView连接到分配(使用setSurf ...

  5. Android Camera2 教程 · 第三章 · 预览

    Android Camera2 教程 · 第三章 · 预览 DarylGo关注 Android Camera 上一章<Camera2 开启相机>我们学习了如何开启和关闭相机,接下来我们来学 ...

  6. android 相机预览的分辨率,Android开发 Camera2开发_2_预览分辨率或拍照分辨率的计算...

    前言 不管在Camera1或者Camera2在适配不同手机/不同使用场景的情况下都需要计算摄像头里提供的分辨率列表中最合适的那一个分辨率.所以在需要大量机型适配的app,是不建议不经过计算直接自定义分 ...

  7. Android Camera2 教程 · 第一章 · 概览

    从 Android 5.0 开始,Google 引入了一套全新的相机框架 Camera2(android.hardware.camera2)并且废弃了旧的相机框架 Camera1(android.ha ...

  8. Android Camera2 教程 · 第四章 · 拍照

    上一章<Camera2 预览>我们学习了如何配置预览,接下来我们来学习如何拍照. 阅读完本章,你将会学到以下几个知识点: 理解 Capture 工作流程 如何拍摄单张照片 如何连续拍摄多张 ...

  9. android平台下OpenGL ES 3.0使用GLSurfaceView对相机Camera预览实时处理

    OpenGL ES 3.0学习实践 android平台下OpenGL ES 3.0从零开始 android平台下OpenGL ES 3.0绘制纯色背景 android平台下OpenGL ES 3.0绘 ...

最新文章

  1. ehchache验证缓存过期的api_ASP.NET Core ResponseCache进行缓存操作
  2. 增强使用功能的Steam开源工具箱一枚
  3. tf.argmax tf2版本
  4. 打印杨辉三角--for循环
  5. 算法导论 c语言,算法导论 之 堆排序[C语言]
  6. 【译】zkSNARKs in a nutshell
  7. [Map 3D开发实战系列] Map Resource Explorer 之四-- Map3D开发中的WPF
  8. windows2008下 IIS7 HTTP 错误 404.2 - Not Found 解决方法(图文)
  9. python settings模块导入不了_无法导入设置“myproject.settings”(是否在sys.path上?):没有名为pinax的模块...
  10. React Navigation 导航栏样式调整+底部角标消息提示
  11. 前端框架 Bootstrap 4.5.2 发布
  12. 微软若“无故”解雇暴雪 CEO,将付 1500 万美元“分手费”
  13. 惠普台式电脑重装系统仍然启动不了,怎么办
  14. WebApi基于Token和签名的验证
  15. P6647 [CCC 2019] Tourism
  16. UPC 6615 Snuke Festival
  17. 扩展Redux——Store Enhancer
  18. PHP slideup,三级下拉菜单(slideDown/slideUp实现)
  19. wbin笔记本商务版博通机型装(原版黑苹果)单MacOS流程记录(备忘)
  20. 2021年全球与中国重型泥浆泵行业市场规模及发展前景分析

热门文章

  1. Windows环境安装MySQL ZIP Archive
  2. C++判断点是否在圆上
  3. Ubuntu: AppImage格式安装、卸载
  4. Mysql输错命令后如何退出
  5. android 来电解锁,带你解锁手机隐藏黑科技,极少人知道!
  6. JDBC的全称是什么?
  7. 【hadoop系列】Hadoop HDFS命令
  8. python中序列和列表区别细菌真菌病毒_生物信息中的Python 02 | 用biopython解析序列...
  9. 图片无损压缩(ubuntu 安装 )
  10. 【 2021 MathorCup杯大数据挑战赛 A题 二手车估价】初赛复赛总结、方案代码及论文