- 校对者:nanjingboy, gs666
这篇文章是当前关于 Android 相机介绍中最新的一篇,我们之前介绍过相机阵列和相机会话和请求。
多个相机流的使用场景
一个相机应用可能希望同时使用多个帧流,在某些情况下不同的流甚至需要不同的帧分辨率或像素格式;以下是一些典型使用场景:
- 录像:一个流用于预览,另一个用于并编码保存成文件
- 扫描条形码:一个流用于预览,另一个用于条形码检测
- 计算摄影学:一个流用于预览,另一个用于人脸或场景的检测
正如我们在之前的文章中讨论的那样,当我们处理帧时,存在较大的性能成本,并且这些成本在并行流 / 流水线处理中还会成倍增长。
CPU、GPU 和 DSP 这样的资源可以利用框架的重新处理能力,但是像内存这样的资源需求将线性增长。
每次请求对应多个目标
通过执行某种官方程序,多相机流可以整合成一个 CaptureRequest,此代码段表明了如何使用一个流开启相机会话进行相机预览并使用另一个流进行图像处理:
val session: CameraCaptureSession = … // from CameraCaptureSession.StateCallback
// 我们将使用预览捕获模板来组合流,因为
// 它针对低延迟进行了优化; 用于高质量的图像时使用
// TEMPLATE_STILL_CAPTURE,用于高速和稳定的帧速率时使用
// TEMPLATE_RECORD
val requestTemplate = CameraDevice.TEMPLATE_PREVIEW
val combinedRequest = session.device.createCaptureRequest(requestTemplate)
// Link the Surface targets with the combined request
combinedRequest.addTarget(previewSurface)
combinedRequest.addTarget(imReaderSurface)
// 在我们的样例中,SurfaceView 会自动更新。
// ImageReader 有自己的回调,我们必须监听,以检索帧
// 所以不需要为捕获请求设置回调
session.setRepeatingRequest(combinedRequest.build(), null, null)
如果你正确配置了目标 surfaces,则此代码将仅生成满足 StreamComfigurationMap.GetOutputMinFrameDuration(int, Size) 和 StreamComfigurationMap.GetOutputStallDuration(int, Size) 确定的最小 FPS 流。实际表现还会因机型而异,Android 给了我们一些保证,可以根据输出类型,输出大小和硬件级别三个变量来支持特定组合。使用不支持的参数组合可能会以低帧率工作,甚至不能工作,触发其中一个故障回调。文档非常详细地描述了保证工作的内容,强烈推荐完整阅读,我们在此将介绍基础知识。
输出类型
输出类型指的是帧编码格式,文档描述中支持的类型有 PRIV、YUV、JEPG 和 RAW。文档很好的解释了它们:
PRIV 指的是使用了 StreamConfigurationMap.getOutputSizes(Class) 获取可用尺寸的任何目标,没有直接的应用程序可见格式
YUV 指的是目标 surface 使用了 ImageFormat.YUV_420_888 编码格式
JPEG 指的是 ImageFormat.JPEG 格式
RAW 指的是 ImageFormat.RAW_SENSOR 格式
当选择应用程序的输出类型时,如果目标是使兼容性最大化,推荐使用 ImageFormat.YUV_420_888 做帧分析并使用 ImageFormat.JPEG 保存图像。对于预览和录像传感器来说,你可能会用一个 SurfaceView
、TextureView
、MediaRecorder
、MediaCodec
或者 RenderScript.Allocation
。在这些情况下,不指定图像格式,出于兼容性目的,它将被计为 ImageFormat.PRIVATE(不管它的实际格式是什么)。去查看设备支持的格式可以使用如下代码:
val characteristics: CameraCharacteristics = …
val supportedFormats = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).outputFormats
输出大小
我们调用 StreamConfigurationMap.getOutputSizes() 可列出所有可用的输出大小,但随着兼容性的发展,我们只需要关心两种:PREVIEW 和 MAXIMUM。我们可以将这种大小视为上限;如果文档中说的 PREVIEW 的大小有效,那么任何比 PREVIEW 尺寸小的都可以,MAXIMUM 同理。这有一个文档的相关摘录:
对于尺寸最大的列,PREVIEW 意味着适配屏幕的最佳尺寸,或 1080p(1920x1080),以较小者为准。RECORD 指的是相机支持的最大分辨率由 CamcorderProfile 确定。MAXIMUM 还指 StreamConfigurationMap.getOutputSizes(int)中相机设备对该格式或目标的最大输出分辨率。
注意,可用的输出尺寸取决于选择的格式。给定 CameraCharacteristics,我们可以像这样查询可用的输出尺寸:
val characteristics: CameraCharacteristics = …
val outputFormat: Int = … // 比如 ImageFormat.JPEG
val sizes = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
.getOutputSizes(outputFormat)
在相机预览和录像的使用场景中,我们应该使用目标类来确定支持的大小,因为文件格式将由相机框架自身处理:
val characteristics: CameraCharacteristics = …
val targetClass: Class = … // 比如 SurfaceView::class.java
val sizes = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
.getOutputSizes(targetClass)
获取到 MAXIMUM 的尺寸很简单——只需要将输出尺寸排序然后返回最大的:
fun getMaximumOutputSize(
characteristics: CameraCharacteristics, targetClass: Class, format: Int? = null):
Size {
val config = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
// 如果提供图像格式,请使用它来确定支持的大小;否则使用目标类
val allSizes = if (format == null)
config.getOutputSizes(targetClass) else config.getOutputSizes(format)
return allSizes.sortedWith(compareBy { it.height * it.width }).reversed()[0]
}
获取 PREVIEW 的尺寸就需要动下脑子了。回想一下,PREVIEW 指的是适配屏幕的最佳尺寸,或者 1080p (1920x1080),取较小者。请记住,长宽比可能与屏幕的不匹配,如果我们打算全屏显示,我们需要显示黑边或者裁剪。为了获取到正确的预览尺寸,我们需要对比可用的输出尺寸和显示尺寸,同时考虑到可以旋转显示。在这段代码里,我们还封装了一个辅助类 SmartSize
用来横简单的比较尺寸大小:
class SmartSize(width: Int, height: Int) {
var size = Size(width, height)
var long = max(size.width, size.height)
var short = min(size.width, size.height)
}
fun getDisplaySmartSize(context: Context): SmartSize {
val windowManager = context.getSystemService(
Context.WINDOW_SERVICE) as WindowManager
val outPoint = Point()
windowManager.defaultDisplay.getRealSize(outPoint)
return SmartSize(outPoint.x, outPoint.y)
}
fun getPreviewOutputSize(
context: Context, characteristics: CameraCharacteristics, targetClass: Class,
format: Int? = null): Size {
// 比较哪个更小:屏幕尺寸还是 1080p
val hdSize = SmartSize(1080, 720)
val screenSize = getDisplaySmartSize(context)
val hdScreen = screenSize.long >= hdSize.long || screenSize.short >= hdSize.short
val maxSize = if (hdScreen) screenSize else hdSize
// 如果提供图像格式,请使用它来确定支持的大小;否则使用目标类
val config = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
val allSizes = if (format == null)
config.getOutputSizes(targetClass) else config.getOutputSizes(format)
// 获取可用尺寸并按面积从最大到最小排序
val validSizes = allSizes
.sortedWith(compareBy { it.height * it.width })
.map { SmartSize(it.width, it.height) }.reversed()
// 然后,获得小于或等于最大尺寸的最大输出尺寸
return validSizes.filter {
it.long <= maxSize.long && it.short <= maxSize.short }[0].size
}
硬件层次
要决定运行时可用能力,相机应用需要的最重要的信息是支持的硬件级别。再一次,我们可以从此文档学习:
支持的硬件级别是摄像机设备功能的上层描述,汇总出多种功能到一个字段中。每一等级相比前一等级都新增了一些功能,并且始终是上一级别的超集。等级的顺序是 LEGACY < LIMITED < FULL < LEVEL_3。
使用 CameraCharacteristics 对象,我们可以使用单个语句检索硬件级别:
val characteristics: CameraCharacteristics = …
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后
在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-A0v0SIF6-1712679240764)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!