前言

上篇文章,我们已经用 Camera1 实现了预览和拍照的功能,但也说到,在API21的时候,Camera1已经被标注为弃用,因为它的API功能和灵活性满足不了现在日益复杂的相机开发了,所以在 API21之后,引入了 Camera2 。

从功能来讲,Camera2 废弃了 Camera1 的框架,它支持更多的功能,比如:

  • 获取更多的帧(预览/拍照)信息,以及每一帧的参数配置
  • 支持更多的图片格式(yuv/raw)等
  • 一些新特性

效果图

一、概念

1. Pipeline
Camera2 的 API 模型被设计成一个 Pipeline(管道),它按顺序处理每一帧的请求并返回请求结果给客户端。下面这张来自官方的图展示了 Pipeline 的工作流程,我们会通过一个简单的例子详细解释这张图。

为了解释上面的示意图,假设我们想要同时拍摄两张不同尺寸的图片,并且在拍摄的过程中闪光灯必须亮起来。整个拍摄流程如下:

创建一个用于从 Pipeline 获取图片的 CaptureRequest
修改 CaptureRequest 的闪光灯配置,让闪光灯在拍照过程中亮起来。
创建两个不同尺寸的 Surface 用于接收图片数据,并且将它们添加到 CaptureRequest 中。
发送配置好的 CaptureRequest 到 Pipeline 中等待它返回拍照结果。
一个新的 CaptureRequest 会被放入一个被称作 Pending Request Queue 的队列中等待被执行,当 In-Flight Capture Queue 队列空闲的时候就会从 Pending Request Queue 获取若干个待处理的 CaptureRequest,并且根据每一个 CaptureRequest 的配置进行 Capture操作。最后我们从不同尺寸的 Surface 中获取图片数据并且还会得到一个包含了很多与本次拍照相关的信息的 CaptureResult,流程结束。

2. Supported Hardware Level
相机功能的强大与否和硬件息息相关,不同厂商对 Camera2 的支持程度也不同,所以 Camera2 定义了一个叫做 Supported Hardware Level 的重要概念,其作用是将不同设备上的 Camera2 根据功能的支持情况划分成多个不同级别以便开发者能够大概了解当前设备上 Camera2 的支持情况。截止到 Android P 为止,从低到高一共有 LEGACY、LIMITED、FULL 和 LEVEL_3 四个级别:

  • LEGACY:向后兼容的级别,处于该级别的设备意味着它只支持 Camera1 的功能,不具备任何 Camera2 高级特性。
  • LIMITED:除了支持 Camera1 的基础功能之外,还支持部分 Camera2 高级特性的级别。
  • FULL:支持所有 Camera2 的高级特性。
  • LEVEL_3:新增更多 Camera2 高级特性,例如 YUV 数据的后处理等。

想了解更多,请查看 Android Camera2 教程 · 第一章 · 概览

二、相机预览

要注意的是,Camera2 与 Camera1 是两个不同的框架,不要被 Camera1 的思想禁锢,把它当做新知识学习即可,一个相机的流程图如下:

1. 在清单文件中,添加权限:

 <uses-permission android:name="android.permission.CAMERA" /><!-- 支持相机才能运行 --><uses-featureandroid:name="android.hardware.camera"android:required="true" /><uses-feature android:name="android.hardware.camera.autofocus" />

2. 布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><com.hjq.bar.TitleBarandroid:id="@+id/title_bar"android:layout_width="match_parent"android:background="@color/teal_200"android:layout_height="?android:attr/actionBarSize"app:title="Camera2 使用"app:titleStyle="bold"app:titleSize="18sp"app:backButton="false"app:titleColor="@color/white"/><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><TextureViewandroid:id="@+id/surface"android:layout_width="0dp"android:layout_height="0dp"app:layout_constraintTop_toTopOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintDimensionRatio="H,3:4"/><Buttonandroid:id="@+id/btn_trans"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintTop_toBottomOf="@id/surface"app:layout_constraintTop_toTopOf="parent"app:layout_constraintStart_toStartOf="parent"android:layout_margin="10dp"android:text="切换摄像头"/><Buttonandroid:id="@+id/btn_takePhoto"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintTop_toBottomOf="@id/surface"app:layout_constraintStart_toStartOf="parent"app:layout_constraintEnd_toEndOf="parent"android:text="拍照"/><ImageViewandroid:id="@+id/image"android:layout_width="100dp"android:layout_height="0dp"app:layout_constraintTop_toTopOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintDimensionRatio="H,3:4"/></androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

3. 获取相机信息
从上面的流程图支持,我们需要通过 CameraManagergetCameraCharacteristics() 方法,来获取相机的信息;CameraManager 是一个负责查询和建立相机连接的系统服务,它的功能不多,主要如下:

  • 将相机信息装载到 CameraCharacteristics 中。
  • 根据指定的相机 ID 连接相机
  • 提供将闪光灯设置为手电筒的快捷方式
 /*** 初始化相机和配置相关属性*/private void initCamera() {try {//获取相机服务 CameraManagermCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);//遍历设备支持的相机 ID ,比如前置,后置等String[] cameraIdList = mCameraManager.getCameraIdList();for (String cameraId : cameraIdList) {// 拿到装在所有相机信息的  CameraCharacteristics 类CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId);//拿到相机的方向,前置,后置,外置Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);if (facing != null) {//后置摄像头if (facing == CameraCharacteristics.LENS_FACING_BACK) {mBackCameraId = cameraId;mBackCameraCharacteristics = characteristics;}else if (facing == CameraCharacteristics.LENS_FACING_FRONT){//前置摄像头mFrontCameraId = cameraId;mFrontCameraCharacteristics = characteristics;}mCameraId = cameraId;}//是否支持 Camera2 的高级特性Integer level = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);/*** 不支持 Camera2 的特性*/if (level == null || level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY){//  Toast.makeText(this, "您的手机不支持Camera2的高级特效", Toast.LENGTH_SHORT).show();//   break;}}} catch (CameraAccessException e) {e.printStackTrace();}}

4. 打开摄像头

 /*** 打开摄像头**/@SuppressLint("MissingPermission")private void openCamera(int width, int height) {//判断不同摄像头,拿到 CameraCharacteristicsCameraCharacteristics characteristics = mCameraId.equals(mBackCameraId) ? mBackCameraCharacteristics : mFrontCameraCharacteristics;//拿到配置的mapStreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);//获取摄像头传感器的方向mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);//获取预览尺寸Size[] previewSizes = map.getOutputSizes(SurfaceTexture.class);//获取最佳尺寸Size bestSize = getBestSize(width, height, previewSizes);/*** 配置预览属性* 与 Cmaera1 不同的是,Camera 是把尺寸信息给到 Surface (SurfaceView 或者 ImageReader),* Camera 会根据 Surface 配置的大小,输出对应尺寸的画面;* 注意摄像头的 width > height ,而我们使用竖屏,所以宽高要变化一下*/mTextureView.getSurfaceTexture().setDefaultBufferSize(bestSize.getHeight(),bestSize.getWidth());/*** 设置图片尺寸,这里图片的话,选择最大的分辨率即可*/Size[] sizes = map.getOutputSizes(ImageFormat.JPEG);Size largest = Collections.max(Arrays.asList(sizes),new CompareSizesByArea());//设置imagereader,配置大小,且最大Image为 1,因为是 JPEGmImageReader = ImageReader.newInstance(largest.getWidth(),largest.getHeight(),ImageFormat.JPEG,1);//拍照监听mImageReader.setOnImageAvailableListener(new ImageAvailable(),null);try {//打开摄像头,监听数据mCameraManager.openCamera(mCameraId,new CameraDeviceCallback(),null);} catch (CameraAccessException e) {e.printStackTrace();}}

在用 mCameraManager.openCamera() 打开摄像头之前,我们通过

//拿到配置的map
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);

而与 Camera1 不同的是,Camera2 会根据 Surface 配置的大小,输出对应尺寸的画面,所以这里设置 mTextureView 的大小即可,注意摄像头的 width > height ,而我们使用竖屏,所以宽高要变化一下。

接着设置图片的尺寸,这里当然是越轻越好了,所以选择最大尺寸即可。I

最后调用 mCameraManager.openCamera() ,它有三个参数:

  • cameraIdCameraID,比如前置、后置和外置
  • CameraDevice.StateCallback :当连接到相机时,该回调就会被调用,生成 CameraDevice
  • handler : 调用 CameraDevice.StateCallbackHandler,传 null,则调用主线程,建议传入 HandlerThread 的hander,毕竟这种都是耗时的。

5. CameraDevice
CameraDevice 代表当前连接的相机设备,它的职责有以下四个:

  • 根据指定的参数创建 CameraCaptureSession
  • 根据指定的模板创建 CaptureRequest
  • 关闭相机设备
  • 监听相机设备的状态,例如断开连接、开启成功和开启失败等
    class CameraDeviceCallback extends CameraDevice.StateCallback{@Overridepublic void onOpened(@NonNull CameraDevice camera) {mCameraDevice = camera;//此时摄像头已经打开,可以预览了createPreviewPipeline(camera);}@Overridepublic void onDisconnected(@NonNull CameraDevice camera) {camera.close();}@Overridepublic void onError(@NonNull CameraDevice camera, int error) {camera.close();}}

在 onOpened 创建我们 CaptureRequest ,配置相机参数信息,如下:

/*** 创建 Session*/private void createPreviewPipeline(CameraDevice cameraDevice){try {//创建作为预览的 CaptureRequst.builderfinal CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);Surface surface = new Surface(mTextureView.getSurfaceTexture());//添加 surface 容器captureBuilder.addTarget(surface);// 创建CameraCaptureSession,该对象负责管理处理预览请求和拍照请求,这个必须在创建 Seesion 之前就准备好,传递给底层用于皮遏制 pipelinecameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession session) {mCameraCaptureSession = session;try {//设置自动聚焦captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);//设置自动曝光captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);//创建 CaptureRequestCaptureRequest build = captureBuilder.build();//设置预览时连续捕获图片数据session.setRepeatingRequest(build,null,null);}catch (Exception e){}}@Overridepublic void onConfigureFailed(@NonNull CameraCaptureSession session) {Toast.makeText(MainActivity.this, "配置失败", Toast.LENGTH_SHORT).show();}},null);} catch (Exception e) {e.printStackTrace();}}

在开启预览之前,我们需要先创建 CaptureRequest ,上面已经说过 CaptureRequest 是向 CameraCaptureSession 提交 Capture 请求的信息载体,内部包含了本次的 Capture 参数配置和接受图像数据的 Surface。

CaptureRequest 可以配置的信息非常多,比如图像格式、图像分辨率、聚焦、闪光灯控制等,可以说绝大部分配置都是通过 CaptureRequest 配置的。

上面通过 cameraDevice.createCaptureRequest() 来创建一个 CaptureRequest.Builder 对象,其中createCaptureRequest() 方法的参数是 templateType 用于指定哪种模板,Camera2 根据不同场景,为我们配置了一些常用的参数模板:

  • TEMPLATE_PREVIEW:适用于配置预览的模板
  • TEMPLATE_RECORD:适用于视频录制的模板。
  • TEMPLATE_STILL_CAPTURE:适用于拍照的模板。
  • TEMPLATE_VIDEO_SNAPSHOT:适用于在录制视频过程中支持拍照的模板。
  • TEMPLATE_MANUAL:适用于希望自己手动配置大部分参数的模板。

这里我们需要一个预览的 CaptureRequest ,所以选择 TEMPLATE_PREVIEW的模板。

接着,需要设置要承载图像数据的 Surface,我们用到两个,一个是 TextureView 用来预览的,一个是 ImageReader 用来拍照的。

最后,通过 cameraDevice.createCaptureSession() 创建 CameraCaptureSession ,然后再配置一下聚焦和曝光的配置,就可以把 CaptureRequest 通过 Session 发送给底层了:

         // 创建CameraCaptureSession,该对象负责管理处理预览请求和拍照请求,这个必须在创建 Seesion 之前就准备好,传递给底层用于配置 pipelinecameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession session) {mCameraCaptureSession = session;try {//设置自动聚焦captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);//设置自动曝光captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);//创建 CaptureRequestCaptureRequest build = captureBuilder.build();//设置预览时连续捕获图片数据session.setRepeatingRequest(build,null,null);}catch (Exception e){}}@Overridepublic void onConfigureFailed(@NonNull CameraCaptureSession session) {Toast.makeText(MainActivity.this, "配置失败", Toast.LENGTH_SHORT).show();}},null);

6. 开启和关闭预览
Camera2 中,本质上是不断的重复 Captrue 的过程,每一次 Capture 都会把预览的数据输出到对应的 Surface 中,所以,为了达到预览的效果,需要使用:

    session.setRepeatingRequest(build,null,null);

它的三个参数如下:

  • request : 在不断重复执行 Capture 时使用的 CaptureRequest对象

  • callback :监听每一次 Capture 状态的 CameraCaptureSession.CaptureCallback 对象,例如 onCaptureStarted() 意味着一次 Capture 的开始,而 onCaptureCompleted() 意味着一次 Capture 的结束。

  • handler :用于执行 CameraCaptureSession.CaptureCallback 的Handler 对象,传null为主线程,也可以使用其他线程的 Handler

关闭预览

  //停止预览mCameraCaptureSession.stopRepeating();

三、拍照

拍照其实也是一个 Captrue,这样的话,我们就可以再创建一个 CaptureRequest 去执行拍照的就可以了,代码如下:

     try {//创建一个拍照的 sessionfinal CaptureRequest.Builder captureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);//设置装在图像数据的 SurfacecaptureRequest.addTarget(mImageReader.getSurface());//聚焦captureRequest.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);//自动曝光captureRequest.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);// 获取设备方向int rotation = getWindowManager().getDefaultDisplay().getRotation();// 根据设备方向计算设置照片的方向captureRequest.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));// 先停止预览mCameraCaptureSession.stopRepeating();mCameraCaptureSession.capture(captureRequest.build(), new CameraCaptureSession.CaptureCallback() {@Overridepublic void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {super.onCaptureCompleted(session, request, result);try {//拍完之后,让它继续可以预览CaptureRequest.Builder captureRequest1 = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);captureRequest1.addTarget(new Surface(mTextureView.getSurfaceTexture()));mCameraCaptureSession.setRepeatingRequest(captureRequest1.build(),null,null);} catch (CameraAccessException e) {e.printStackTrace();}}},null);} catch (CameraAccessException e) {e.printStackTrace();}

1. 保存图片
在 Camrea2 中,Imageread 是获取图像数据的一个重要途径,我们可以通过它获取各种各样格式的图像数据,比如 JPEG、YUV和 RAW 等。通过 ImageReader.newInstance() 方法创建 ImageReader 对象,如下:

//拍照监听
mImageReader.setOnImageAvailableListener(new ImageAvailable(),null);

2. ImageReader
在 Camrea2 中,Imageread 是获取图像数据的一个重要途径,我们可以通过它获取各种各样格式的图像数据,比如 JPEG、YUV和 RAW 等。通过 ImageReader.newInstance() 方法创建 ImageReader 对象,如下:

//设置imagereader,配置大小,且最大Image为 1,因为是 JPEG
mImageReader = ImageReader.newInstance(largest.getWidth(),largest.getHeight(),ImageFormat.JPEG,1);
mImageReader.setOnImageAvailableListener(new ImageAvailable(),null);

3. 获取图片

         FileOutputStream fos = null;Image image = null;try {fos = new FileOutputStream(file);//获取捕获的照片数据image = imageReader.acquireLatestImage();//拿到所有的 Plane 数组Image.Plane[] planes = image.getPlanes();//由于是 JPEG ,只需要获取下标为 0 的数据即可ByteBuffer buffer = planes[0].getBuffer();data = new byte[buffer.remaining()];//把 bytebuffer 的数据给 byte数组buffer.get(data);Bitmap bitmap = BitmapFactory.decodeByteArray(data,0,data.length);//旋转图片if (mCameraId.equals(mFrontCameraId)){bitmap = BitmapUtils.rotate(bitmap,270);bitmap = BitmapUtils.mirror(bitmap);}else{bitmap = BitmapUtils.rotate(bitmap,90);}bitmap.compress(Bitmap.CompressFormat.JPEG,100,fos);fos.flush();return bitmap;}catch (Exception e){Log.d(TAG, "zsr doInBackground: "+e.toString());}finally {CloseUtils.close(fos);//记得关闭 imageif (image != null) {image.close();}}return null;

【Android -- 相机】Camera2 实现拍照 预览功能相关推荐

  1. Android 使用Camera2 实现拍照录像的功能

    职场小白迷上优美句子: 还是电影  <无问西东>中的台词,这句有点感人: 沈光耀的妈妈对沈光耀说:"当初你离家千里,来到这个地方读书,你父亲和我都没有反对过,因为,是我们想你,能 ...

  2. android 录像 视频大小,Android相机 – 录制视频时预览放大

    我一直试图弄清楚一段时间,但由于某种原因,当我开始使用相机录制视频时,预览放大.我从以下示例中获取以下代码: @Override public void surfaceChanged(SurfaceH ...

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

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

  4. Android实现文档在线预览功能

    前段时间,项目中有个需求是实现文档预览的功能,Android要实现该功能要比IOS复杂的多,下面就我调研的情况,总结一下,供大家参考: 一.WebView 网页显示 该方式类似于ios的实现方式,使用 ...

  5. 使用Camera2实现预览功能

    前言 首先需要知道的是该博客只是简单的将摄像头打开并进行预览的一个操作,对于横屏竖屏切换的一个判断处理并没有实现,后续会进行完善,但是不会在这个博客中进行说明. 其次在编写之前应该对整个预览过程用到的 ...

  6. iOS学习:调用相机,选择图片上传,带预览功能

    iOS学习:调用相机,选择图片上传,带预览功能 发表于2年前(2013-05-30 21:38)   阅读( 18194) | 评论( 16) 27人收藏此文章,我要收藏 赞3 8月22日珠海 OSC ...

  7. Android 9.0 三方app whatsapp 拍照预览模糊

    之前在进行项目开发时,有碰到 Android 9.0 三方app whatsapp 拍照预览分辨率过低 现象,但是拍照出来的照片是清晰的, 查看log发现priview-size过低导致预览模糊,而p ...

  8. 上传身份证照片js_html+css+js 实现拍照预览上传图片功能

    前言:我们在做网页时经常会需要有上传图片的需求,可能是选择图片或者拍照上传,如果简单的使用这种方式虽然也能实现功能,但用户体验上可能会差了一些,所以本文记录了使用css+js实现图片选中后的预览及压缩 ...

  9. android打开预览文件格式,Android中文档预览功能的实现思路及问题

    Andriod中的文档在线查看功能,类似于网易邮箱大师中的附件预览功能,要求在app内直接打开office文档.pdf文档等. 思路一:后台统一转换文档格式,安卓端只预览一种格式文档. 在后台将off ...

最新文章

  1. 在linux中加用户,Ubuntu使用教程——在Ubuntu中添加用户
  2. Java删除文件夹和文件
  3. Android开发中遇到的bug
  4. Kubernetes集群部署
  5. 使用heroku进行免费分布式运算.Vs.AWS
  6. 图表达相关书书籍调研
  7. string_view理解与用法(二)
  8. 自定义键盘码_无线+矮轴≤299?ikbc S200 2.4G 机械键盘测评
  9. C语言解析日志,存储数据到伯克利DB
  10. 大数据分析必须要会的python函数操作!!!
  11. 玩一下易语言 和字有多种读音,注定了它的重要性!!
  12. 手机号中间四位星号显示
  13. 20160319中艺收盘总结
  14. 如何写好一份专利交底书?
  15. 126.单词接龙II
  16. 微pe工具箱 系统安装教程_装系统必备 微PE工具箱制作启动U盘教程
  17. 从零开始设计一款APP之Android设计规范篇
  18. 微信公号DIY:一小时搭建微信聊天机器人
  19. 完美兼容IE_Opera_Firefox等主流浏览器的锁定表格的 表头以及表头列
  20. 一些工具 covim, Topcoat, Runscope, Ghost

热门文章

  1. 赛格威机器人路萌中国首秀 开发者计划今年将在国内落地
  2. Flask项目之手机端租房网站的实战开发(一)
  3. zblog php 点赞,zblog文章下面添加点赞等表情的方法(使用畅言实验室)
  4. 基于51单片机的简易减法器设计制作
  5. echarts 图例 两行展示
  6. IDE和SATA双硬盘安装全攻略
  7. 如何用css实现左右翻页效果图,如何利用CSS3实现3D翻书效果
  8. python 打卡记录代码_利用Python实现对考勤打卡数据处理的总结
  9. android ps2 模拟器,手机目前安卓系统PS2模拟器最好用的是一款叫做呆萌PS2模拟器的APP!这一款竟然是国产的!...
  10. 新技术表明通过观察灯泡振动可进行窃听,以后聊天得拉窗帘