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 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…

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的参数编码视频的帧序列。它使用…

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

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

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

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

Alpha版本测试报告

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

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

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

linux更改用户名_破旧安卓手机第二春,在安卓手机上使用Linux_deploy运行Linux

由于服务器位于国外,害怕被墙掉导致数据丢失,所以在本地写了脚本每小时从服务器上导出并下载到本地。但是电脑不可能二十四小时开机,所以很想买一个树莓派4玩玩。但是太贵辽,还好搜索到了Android运行Linux的方法,下面记…

MCUXpress IDE常用设置

NXP的开发工具Xpress是基于eclipse制作的,我们如果需要设置一些东西可以直接搜索eclipse是怎么设置的。 1、字体大小 搜索eclipse字体大小,菜单Window > Preference 而Xpress是汉化了的,英语不好的同学可能懵逼,其实就是菜单栏…

C语言中空格符、空字符、字符数组结束符、换行、回车的区别

空格符和空字符是不一样的,在ASCII里面,空格(space)符号的ASCII码是32,而空字符是0, 2个是完全不一样的2个字符 空字符 一般来描述一个字符串的结尾,其实是控制符的一种,但不能理解为没有字符,应该理解为代表什么都没有的字符.好比回车0x0A和换行0x0D虽然不显示,但是也是控制字…

anaconda如何卸载库_小白必看!Anaconda安装全攻略

本文作者:戴 雯文字编辑:方 言技术总编:张馨月爬虫俱乐部云端课程来袭!爬虫俱乐部将于2020年8月25日至28日在线上举行Stata数据分析法律与制度专题训练营,主要是为了让学员掌握Stata软件进阶操作,涉及…

iOS开发网络篇—文件的上传

说明:文件上传使用的时POST请求,通常把要上传的数据保存在请求体中。本文介绍如何不借助第三方框架实现iOS开发中得文件上传。 由于过程较为复杂,因此本文只贴出部分关键代码。 主控制器的关键代码: YYViewController.m 1 #import…

var模型的matlab实现_Eviews中VAR模型的操作、脉冲响应分析和方差分解的实现

打开文件所在位置,获取数据。选中变量右键open打开var操作EViews,在VAR对象的工具栏中选择“View”|“Lag Structure”|“AR Roots Table/ AR Roots Graph”选项,得到AR根的表和图。结果显示:VAR模型所有根模的倒数都小于1,即都在…

结构体、枚举类型

一、结构体 结构体:就是一个自定义的集合,里面可以放各种类型的元素,用法大体跟集合一样。 1、定义的方法: struct student { public int nianling; public int fenshu; public string name; public string sex; public int sum; …

NXP KW38开发杂记(一)MCUXpress 运行进入NMI_Handler

这里是大佬的具体分析过程,感兴趣可以看看 https://www.cnblogs.com/wenhao-Web/p/13618703.html 解决办法: 在startup_mkw38a4.c文件里,定位到Flash_Config {0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFE}; 把最后一个参数0xFFFFFFFE改…

25个让Java程序员更高效的Eclipse插件

Eclipse提供了一个可扩展插件的开发系统。这就使得Eclipse在运行系统之上可以实现各种功能。这些插件也不同于其他的应用(插件的功能是最难用代码实现的)。拥有合适的Eclipse插件是非常重要的,因为它们能让Java开发者们无缝的开发基于J2EE和服…

NXP KW38蓝牙开发(一)入门第一课:官网蓝牙广播和连接例程,NMI禁止

首先要下载开发使用的IDE:MCUXpresso IDE 下载链接: 进入nxp的官网,搜索KW38 向下翻看,找到Xpresso,点击进入 习惯使用IAR开发的同学也可以下IAR版本,这里以Xpresso为例 下载好后安装,一路默…

views 多个文件夹 netcore_.NET Core中的使用Kestrel服务器理解及应用

Kestrel是一个基于libuv的跨平台.NET Core web服务器,libuv是一个跨平台的异步I/O库。ASP.NET Core模板项目使用Kestrel作为默认的web服务器。Kestrel支持以下功能:HTTPS用于启用不透明升级的WebSockets位于Nginx之后的高性能Unix socketsKestrel 被.NET…