android surfaceview 大小_Android 使用Camera2 API采集视频数据

        Android 视频数据采集系列的最后一篇出炉了,和前两篇文章想比,这篇文章从系统API层面进行一些探索,涉及到的细节更多。初次接触 Camera2 API 会觉得它的使用有些繁琐,涉及到的类有些多,不过就像第一次使用Activity, Fragment 的API 一样,只要多加练习,熟练掌握这些 API 只是时间问题。

        Andrid 系统最初提供的操控相机的 API android.hardware.camera 现已弃用,新的API android.hardware.camera2 在andrid L 上开始使用,这里只讨论和学习 Camera2 的使用。根据谷歌官方的说法,重新设计 Camera API 的目的在于大幅提高应用对于 Android 设备上的相机子系统的控制能力,同时重新组织 API,提高其效率和可维护性。借助额外的控制能力,开发者可以更轻松地在 Android 设备上构建高品质的相机应用,这些应用可在多种产品上稳定运行,同时仍会尽可能使用设备专用算法来最大限度地提升质量和性能。

        关于Camera API 和 Camere API2 的对比,官方介绍 Camera2 的视频里没有详细说明,只是一笔带过,提到了以下两点:

    1. Camera API 不支持捕捉未被压缩的画面,或者不支持在新的硬件上运行,预览效率被限定在一到三秒每帧;

    2. Camere2 API 拍照的时间间隔更短,支持在多台相机上预览,可以直接加特效或者滤膜。

        Camera2 API 把摄像头设备建模为管道,该管道接收一个捕获单个帧的请求做为输入,输出一个捕获结果元数据包,以及该请求的一组输出图像缓冲区。这些请求包含有关帧的捕获和处理的所有配置信息,其中包括分辨率和像素格式,手动传感器、镜头和闪光灯控件,3A 操作模式,RAW 到 YUV 处理控件等。捕获单个帧的请求按顺序处理,并且多个请求可以同时进行,摄像头设备处理数据需要经过多道工序的加工处理,在大多数Android 设备上需要有多个正在运行的请求才能保持完整的帧率。

        完整的相机模型可以用下面一张图表示:

665c9c3de57b2c20a0076cf062df454e.png

        如果想全面了解相机模型的细节,这张图比较合适,可是这么多大大小小的框框和没见过的类对于我这样的小白不太友好,我们来看一张精简的图片:

4b1a17cabdb323e5fc090fae03fbc0ab.png

        第二张图是相机核心操作的模型图,其中出现的一些类 CameraDevice, CameraRequest等看起来有些陌生,不用着急待会儿给他们做一一介绍。

        为了方便理解,我们可以把摄像头设备看作一个工厂车间,捕获一张图片的过程看作车间里一道完整的流水线,把 Camera2 API 相关的类看作车间里的师傅们,这些师傅各司其职,协同工作完成捕获图像的任务。下面我们来看完成这个任务需要的师傅们。

        CameraManager,类似于LocationManager、ConnectivityManager ,是一个系统级别的服务管理器,负责枚举、查询和打开可用的相机设备。

        CameraDevice,是连接到 Android 设备的单个相机的表示形式,可以对以高帧速率捕获图像和后期处理进行细粒度控制。CameraDevice 描述了硬件设备以及该设备的可用设置和输出参数。这些信息通过 CameraCharacteristics 对象提供。

        CameraCharacteristics,用来描述 CameraDevice 的属性,比如支持的 JPEG 缩略图大小等, 这些属性对于给定的 CameraDevice 是固定的,可以通过CameraManager.getCameraCharacteristics 查询。

        CameraCaptureSession,是 CameraDevice 捕获图像的会话,用于捕获来自摄像机的图像或重新处理先前在同一会话中捕获的图像。创建 CameraCaptureSession 需要配置摄像头设备的内部管道并分配用于将图像发送到所需目标的内存缓冲区,是一项耗费资源的异步操作,可能需要几百毫秒。

        CaptureRequest,是从摄像机设备捕获单个图像所需的一组不变的设置,包含捕获硬件(传感器,镜头,闪光灯),处理管线,控制算法和输出缓冲区的配置。还包含捕获后的图像数据发送的目标 Surface 列表。

        CaptureResult,是 CameraDevice 处理 CaptureRequest之后产生的,从图像传感器捕获的单个图像结果的子集。包含捕获硬件(传感器,镜头,闪光灯),处理管线,控制算法和输出缓冲区的最终配置的子集。

        Image,是与媒体源(例如MediaCodec或CameraDevice)一起使用的单个完整图像缓冲区。Image 允许通过一个或多个 ByteBuffer 高效的直接访问 Image 中的像素数据。每个缓冲区数据封装在一个描述像素数据的 Plane中,由于这种直接访问的方式,Image不能直接用作UI资源。

        Image 通常是由硬件组件直接生成或使用的,是整个系统共享的有限资源,应在不再需要时及时关闭。例如,当使用 ImageReader 类从各种媒体源中读取图像时,一旦达到 ImageReader.getMaxImages 的数量限制,不关闭旧的 Image 对象将阻止新图像的可用性。

        ImageReader 人如其名,用来读取 Image 数据,也可以做为 Image 的存储缓冲区,允许应用程序直接访问渲染到 Surface 中的图像数据。CameraDevice 捕获的图像数据被封装在 Image 对象中,Surface 使用 ImageReader读取这些数据。使用 ImageReader 可以同时访问多个Imagge对象,发送到 ImageReader 的图像将排队等待,ImageReader 的工作方式类似于生产者消费者模式,直到之前的图像被访问取走,新的图像才能被存到队列里。由于内存限制,如果 ImageReader 未能以等于生产速率的速率获取和释放图像,则图像源为了尝试把图像渲染到 Surface上,最终将停止发送或者丢掉一些图像。

        SurfaceView,老熟人了,音视频开发中出镜率最高的 View 之一, 用于渲染 Camera 设备的预览画面和捕获的图像。

        DngCreator,用于将原始像素数据写入 DNG 文件的类。通常与CameraDevice 可用的 ImageFormat.RAW_SENSOR 缓冲区一起使用,或与应用程序生成的 Bayer-type 原始像素数据一起使用。

        DNG(Digital Negative)文件格式是 Adobe 公司发表的一种跨平台文件格式,旨在统一数码相机广泛使用的图像文件格式“RAW”。DNG 文件允许在用户定义的颜色空间中定义像素数据和关联的元数据,该元数据允许在后期处理期间将该像素数据转换为标准CIE XYZ颜色空间。

        以上就是车间里参与捕获图像的主要的师傅们,等师傅们准备就绪后,就可以开始动工了。从准备到捕获一张图像一共需要经过五个流程:

89dd216758ae8b45839d9a37d757863d.png

  1. 准备渲染图像的 SurfaceView

        整个捕获图像的过程中,SurfaceView 负责相机画面的渲染工作,它内部使用 Surface 来展示这些图像。Surface 的创建是一个异步操作,等 Surface 创建完毕后就可以进行下一步操作。

  1. 打开摄像头设备,初始化 CameraDevice

        这一步需要参与的类有 CameraManager、CameraId, 以及 CameraHandler。打开摄像头是一个耗时操作,为了不阻塞主线程,需要在新的线程里执行。告诉 CameraManager 要打开的摄像头ID(前置或者后置摄像头)以及执行该操作的handler, CameraManager 会通过接口回调通知你操作的结果,打开摄像头成功,或者错误,或者摄像头不可用。整个异步操作使用 kotlin 协程完成:

/** Opens the camera and returns the opened device (as the result of the suspend coroutine) */@ExperimentalCoroutinesApi@SuppressLint("MissingPermission")private suspend fun openCamera(    cameraManager: CameraManager,    cameraId: String,    cameraHandler: Handler): CameraDevice =   suspendCancellableCoroutine { cont ->     cameraManager.openCamera(cameraId, object : CameraDevice.StateCallback() {           override fun onOpened(camera: CameraDevice) = cont.resume(camera)          override fun onDisconnected(camera: CameraDevice) {              Log.w(TAG, "Camera $cameraId has been disconnected")              requireActivity().finish()          }          override fun onError(camera: CameraDevice, error: Int) {               val msg = when (error) {                   ERROR_CAMERA_DEVICE -> "Fatal device"                   ERROR_CAMERA_DISABLED -> "Device policy"                   ERROR_CAMERA_IN_USE -> "Camera in use"                   ERROR_CAMERA_SERVICE -> "Fatal service"                   ERROR_MAX_CAMERAS_IN_USE -> "Maximum cameras in use"                   else -> "Unknown"                }               val exception = RuntimeException("Camera $cameraId error:($error) $msg")               Log.e(TAG, exception.message, exception)               if (cont.isActive) cont.resumeWithException(exception)          }      }, cameraHandler) }

 3. 配置 CameraCaptureSession,开启图像预览

        摄像头成功打开以后,就可以调用设备的硬件和软件资源,创建图像预览了,这一步需要 CameraDevice、CameraSession、ImageReader 协作完成。ImageReader 被创建作为捕获静态图像的缓存,使用 CameraDevice 创建 CameraSession时,把用于预览图像的 SurfaceView 中的 Surface 和 ImageReader 中的 Surface 作为图像帧数据的接收者。调用 CameraSession.setRepeatingRequest()启动摄像头连续画面预览。

// Initialize an image reader which will be used to capture still photosval size = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!            .getOutputSizes(args.pixelFormat).maxBy { it.height * it.width }!!imageReader = ImageReader.newInstance(size.width, size.height, args.pixelFormat, IMAGE_BUFFER_SIZE)// Creates list of surfaces where the camera will output framesval targets = listOf(viewFinder.holder.surface, imageReader.surface)// Start a capture session using our open camera and list of surfaces where frames will gosession = createCaptureSession(camera, targets, cameraHandler)val captureRequest = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).apply {            addTarget(viewFinder.holder.surface)}// Keep sending the capture request as frequently as possible until the// session is torn down or session.stopRepeating() is calledsession.setRepeatingRequest(captureRequest.build(), null, cameraHandler)

        到这里,捕获图像前的准备工作已经完成,下面的操作就是等待用户按下捕获图像的按钮,拍摄图像和保存图像结果。

4. 捕获图像

        捕获图像的操作由 CameraCaptureSession 完成,它使用保存的 CameraDevice 创建一个 CaptureRequest.Builder 用来设置捕获图像的参数以及展示图像的 Surface, 把 CaptureRequest 和捕获图像后的回调函数 CameraCaptureSession.CaptureCallback 交给 CameraCaptureSession 后,它会通过 CaptureCallback 及时通知外界捕获图像的进度。这里使用 ImageReader 作为捕获图像的缓冲区,捕获完成后,CameraCaptureSession 返回捕获结果 TotalCaptureResult。捕获图像属于IO 密集型操作,同样需要异步实现:

/*** Helper function used to capture a still image using the [CameraDevice.TEMPLATE_STILL_CAPTURE]template.* It performs synchronization between the [CaptureResult] and the [Image] resulting* from the single capture, and outputs a [CombinedCaptureResult] object.*/private suspend fun takePhoto(): CombinedCaptureResult = suspendCoroutine { cont ->// Fulsh any images left in the image reader@Suppress("ControlFlowWithEmptyBody") while (imageReader.acquireNextImage() != null) {} // Start a new image queueval imageQueue = ArrayBlockingQueue(IMAGE_BUFFER_SIZE)imageReader.setOnImageAvailableListener({ reader ->      val image = reader.acquireNextImage()      Log.d(TAG, "Image available in queue:${image.timestamp}")      imageQueue.add(image)}, imageReaderHandler)val captureResult = session.device.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)    .apply { addTarget(imageReader.surface) }session.capture(captureResult.build(), object : CameraCaptureSession.CaptureCallback() {     override fun onCaptureStarted(         session: CameraCaptureSession,         request: CaptureRequest,         timestamp: Long,         frameNumber: Long     ) {         super.onCaptureStarted(session, request, timestamp, frameNumber)          // Start the animation to inform the user that capture begin          viewFinder.post(animationTask)        }       override fun onCaptureCompleted(           session: CameraCaptureSession,           request: CaptureRequest,           result: TotalCaptureResult       ) {       // Save capture result and other operations...        }, cameraHandler)}

5. 保存图像

        拿到捕获的图像后,到了最后一道工序,保存捕获的图像。如果图像的格式是 JPEG 或者 DEPTH_JPEG,直接保存图像的字节流,如果是原始格式 RAW_SENSOR,需要使用 DngCreator 把图像数据保存为跨平台的 DNG 格式,方便以后使用,保存成功以后返回一个 File 文件:

/** Helper function used to save a [CombinedCaptureResult] into a [File] */private suspend fun saveResult(result: CombinedCaptureResult): File = suspendCoroutine { cont ->   when (result.format) {       // When the format is JPEG or DEPTH JPEG we can simply save the bytes as-is       ImageFormat.JPEG, ImageFormat.DEPTH_JPEG -> {         val buffer = result.image.planes[0].buffer         val bytes = ByteArray(buffer.remaining()).apply { buffer.get(this) }         try {              val output = createFile(requireContext(), "jpg")              FileOutputStream(output).use { it.write(bytes) }              cont.resume(output)              } catch (exc: IOException) {                Log.e(TAG, "Unable to write JPEG image to file", exc)                cont.resumeWithException(exc)                }        }      // When the format is RAW we use the DngCreator utility library      ImageFormat.RAW_SENSOR -> {           val dngCreator = DngCreator(characteristics, result.metadata)           try {                val output = createFile(requireContext(), "dng")                FileOutputStream(output).use { dngCreator.writeImage(it, result.image) }                cont.resume(output)                } catch (exc: IOException) {                    Log.e(TAG, "Unable to write DNG image to file", exc)                    cont.resumeWithException(exc)                }            }       // No other formats are supported by this sample       else -> {           val exc = RuntimeException("Unknown image format: ${result.image.format}")           Log.e(TAG, exc.message, exc)           cont.resumeWithException(exc)       }    } }

        到这里就完成了使用 Camera2 API 捕获一张图像的任务,使用 Camera2 API 可以拿到图像的原始数据用作后期各种处理,并且对捕获图像的过程进行更细粒度的控制,获取完成的示例代码请移步 https://github.com/android/camera-sample 或者 https://github.com/Hiwensen/StreamingTour

        Android 设备采集视频数据系列也到此结束,三种方法:使用系统已安装的相机应用,使用 Jetpack  CameraX 库或者使用 Camera2 API,各有优缺点和不同的适用场景,总有一种能满足你的需求。文中只介绍了基本的捕获图像的用法,至于录制视频和更多功能,后期有更多时间了继续探索。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/374186.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java生成随机字符串

学习java comparable特性时候,定义如下Student类,需要需要随机添加学生姓名以及学号和成绩,这是java如何随机生成名字,根据我的查询,我找到目前java库支持两种方法。 1. org.apache.commons.lang3.RandomStringUtils类…

使用SharedPreferenes存取数据

//使用SharedPreference存储数据 public void on(View view){     //获取用户名和密码     String nameeditText1.getText().toString();     String numbereditText2.getText().toString();     //判断checkBox是否为勾选      CheckBox box(CheckBox…

使用Java VisualVM分析您的应用程序

当您需要发现应用程序的哪个部分消耗更多的CPU或内存时,必须使用探查器执行此操作。 默认情况下,Sun JDK中附带的一个探查器是Java VisualVM。 这个事件探查器非常简单易用,功能强大。 在这篇文章中,我们将看到如何安装它并使用它…

ArcSDE for SQL Server安装及在ArcMap中创建ArcSDE连接

ArcSDE for SQL Server安装及在ArcMap中创建ArcSDE连接 原文:ArcSDE for SQL Server安装及在ArcMap中创建ArcSDE连接安装ArcSDE for SQL Server,最后一步成功后的界面如下:在ArcMap中创建ArcSDE连接,截图如下:posted on 2016-08-0…

java反射main方法参数注意

public class ReflectMethodMain {public static void main(String[] args) throws Exception {Method methodMainTestArgument.class.getMethod("main", String[].class);/*jdk 1.4之前的版本不支持可变参数,对组类的参数会自动拆包然后用来匹配数据参数…

python调用c函数传字符串参数_Python使用ctypes模块调用DLL函数之传递数值、指针与字符串参数...

在Python语言中,可以使用ctypes模块调用其它如C语言编写的动态链接库DLL文件中的函数,在提高软件运行效率的同时,也可以充分利用目前市面上各种第三方的DLL库函数,以扩充Python软件的功能及应用领域,减少重复编写代码、…

沁恒CH554 KEIL环境搭建

首先下载WCHISPTool_Setup.exe http://www.wch.cn/products/CH554.html 123这三个可下载的都下吧,后面开发都要用的 安装好后运行,菜单栏上,功能->添加WCH MCU到KEIL器件库 这时候在KEIL安装目录里面的UV4文件夹下可以看到wch.cdb的文件…

【CV论文阅读】Rank Pooling for Action Recognition

这是期刊论文的版本,不是会议论文的版本。看了论文之后,只能说,太TM聪明了。膜拜~~ 视频的表示方法有很多,一般是把它看作帧的序列。论文提出一种新的方法去表示视频,用ranking function的参数编码视频的帧序列。它使用…

java不要在常量和变量中出现易混淆的字母

public class proposal {public static void main(String[] args) {long i1l; System.out.println("i的两倍是:"(ii));System.out.println("i的两倍是:"ii); //注意此处和上面的有很大的区别} }输出:211/** 注意:* …

VS2019 WPF制作OTA上位机(一)新建工程

首先创建新项目,文件 -> 新建 -> 项目 下拉菜单选择C#和Window,选择WPF应用程序,下一步 输入项目名,下一步 这里选择.NET 5.0,也可以选择其他的,个人习惯.NET,点击创建 这时候出现初始…

户籍恢复需要体检吗_脑梗死后脚麻能恢复吗?需要多久能恢复呢?

脑梗死之后脚部麻木,这个有一部分是能够恢复的,但是相对而言,恢复的时间比较长,在临床当中出现脚麻主要是因为梗死破坏了患者的感觉神经中枢从而造成。脚部感觉麻木,瘙痒或者是有蚂蚁在上面爬的感觉。而且有的更加严重…

Alpha版本测试报告

一、测试计划 Alpha版本即将发布,我们组织队员进行这一版本的测试。 测试主要针对两方面:浏览器兼容性和功能完善性。 测试分兼容性测试与功能完善性两部分,兼容性测试分Windows操作系统、Linux系操作系统、Mac OS X操作系统以及手机端Androi…

在J2SE应用程序中模拟CDI的会话和请求范围

我们目前正在考虑将Naked Objects框架重构为使用JSR-330(依赖注入)和面向EE的老大哥JSR-299(CDI)。 使用香草JSR-330是不费吹灰之力的,但是我们想利用JSR-299中的一些不错的功能(例如事件和装饰器&#xff…

VS2019 WPF制作OTA上位机(二)获取bin文件路径

OTA升级是通过无线通信远程把bin文件内容传输到单片机,完成升级。 因此上位机需要获取bin文件的路径,读取bin文件内容,将内容分割依次发送(因为单片机的接收缓存不会开得和bin文件一样大(十几K甚至几十K)&a…

java 线程“生产/消费”模型1

/*资源类*/ public class ShareValue {private int total;public ShareValue(int total){this.totaltotal;}//生产void putValue(int value){totalvalue;}//消费资源int getValue(int value){if(total-value>0){total-value;}else{valuetotal;total0;System.out.println(&qu…

PHP - 代码分离

总代码&#xff1a; <?php/** Version&#xff1a;1.0* CreateTime&#xff1a;2015年11月11日* Author&#xff1a;HF_Ultrastrong*///引入公共文件,在公共文件中创建&#xff0c;相对于项目的绝对路径require dirname(__FILE__)./includes/common.inc.php; ?&…

原生js封装table表格操作,获取任意行列td,任意单行单列方法

V1.001更新增加findTable-min.js 本次更新&#xff0c;优化了代码性能方面&#xff0c;增加了部分新功能&#xff0c;可以获取多个table表格批量操作。 考虑到本人后面的项目中可能涉及到大量的表格操作&#xff0c;提前先封了 一个简单的操作方法&#xff0c;日后再加完善&…

Spring陷阱:事务测试被认为是有害的

Spring杀手级功能之一是容器内集成测试 。 尽管EJB多年来一直缺乏此功能&#xff08;Java EE 6终于解决了这个问题&#xff0c;但是我还没测试过&#xff09;&#xff0c;但是Spring从一开始就允许您从Web层开始&#xff0c;通过所有服务来测试整个堆栈。到数据库的方式。 数据…

python xlwt写入已有表_Python中,添加写入数据到已经存在的Excel文件

1.安装xlrd、xlwt、xlutilshttps://pypi.org/project/xlutils/pip安装&#xff1a;cmd下输入&#xff1a;pip install xlrd #读取exclepip install xlwt #写入exclepip install xlutils #操作 Excel 文件的实用工具&#xff0c;如复制、分割、筛选等2.代码主要部分实现import x…

java线程“生产/消费”模型2

/* 资源类 */ class ShareValue {private int total;//判断对象是否为空private boolean isEmptytrue;//判断对象是否已满private boolean isFulltrue;public ShareValue(int total) {this.total total;if(total>0) isEmptyfalse;if(total<1000) isFullfalse;}/** sync…