行业知识
Android Camera 预览尺寸的选取与旋转角度的设定
Camera 在音视频的世界中, 就像我们的眼睛一样, 是负责图像采集的入口, 要想实现一款高可用的 Camera, 我认为需要关注以下几个方面
- 相机的预览
- 相机的拍摄
- 相机的录制
OpenGL ES 主要用于图像的渲染, 因此这里主要聚焦于相机的预览, 想要让相机的数据呈现到我们屏幕上, 主要有如下几个步骤
- 相机预览帧尺寸的选取
- 相机预览帧旋转角度的设定
- 使用 OpenGL ES 渲染数据
可以看到每一步想要做好都是挺有挑战性的, 本篇主要分析 相机预览帧尺寸的选取 和 相机预览帧旋转角度的设定 这两个问题
若是使用 JetPack 组件 CameraX 的话, 基本上无需考虑这两个问题, 不过为了解析其实现思路, 这里先以 Camera1 为例, 后续会给出 CameraX 的实现代码
一. 相机预览帧尺寸的选取
如何选取预览尺寸?
在刚开始接触自定义相机的时候, 这个问题一度困扰着我, 是因为面对这个问题我一直无法给予一个很好的解决方案, 之前我的方案是, 交由用户传入一个预览的尺寸, 然后设计一个算法从相机中找寻与这个尺寸最为接近的, 这个方案看似挺合理的, 写 demo 时可以使用, 但却存在着如下的隐患
- 不同的手机支持的预览尺寸是不一样的, 尤其是在当下 3 摄 4 摄横行的手机市场上, 每个摄像头都有自己的特性, 如广角镜头、景深信息等, 指定一个合适的尺寸成本是非常高的
- 若我们在竖直情况下指定了 1080*1440 的尺寸, 当我们手机横置时, 要想保证合理性, 则需要让 API 使用者自己监听屏幕的旋转, 然后再反转尺寸, 这种 API 使用成本过于高昂
因此让外界指定预览尺寸的方式有些不太合理, 于是我们把目光转移到大厂的系统相机上, One UI 的系统相机如下
可以发现, 它们仅提供的是尺寸的比例, 它的优势如下
- 使用简单, 仅需要指定比例即可, 使用者无需考虑旋转的问题
- 相机的尺寸比例一般为 4:3, 16:9, 其他比例是在此基础上裁剪出来的
- 能够获取最佳预览效果, 同比例中选择最接近 View 宽高的, 可以避免相机输出的预览帧尺寸过高, 造成性能损耗, 引起预览卡顿
因此我们可以尝试采用外界指定比例, 在同比例中选取最接近预览控件尺寸的方式作为预览帧的采集的尺寸
若想实现上述的方式, 我们第一步要做的是对相机的预览尺寸按照比例进行归类, 方便从后续从中筛选出最合适的尺寸, 接下来我们便看看第一步的操作
一) 尺寸的归类
private final SizeMap mPreviewSizes = new SizeMap();// 变量所有的预览尺寸, 按照比例分类for (Camera.Size size : mCameraParams.getSupportedPreviewSizes()) { mPreviewSizes.add(new Size(size.width, size.height));}这里遍历了该 Camera 支持的所有预览尺寸, SizeMap.add 方法如下
class SizeMap { private final ArrayMap> mRatios = new ArrayMap(); /** * Add a new {@link Size} to this collection. * * @param size The size to add. * @return {@code true} if it is added, {@code false} if it already exists and is not added. */ public boolean add(Size size) { for (AspectRatio ratio : mRatios.keySet()) { if (ratio.matches(size)) { final SortedSet sizes = mRatios.get(ratio); if (sizes.contains(size)) { return false; } else { sizes.add(size); return true; } } } // None of the existing ratio matches the provided size; add a new key SortedSet sizes = new TreeSet(); sizes.add(size); mRatios.put(AspectRatio.of(size.getWidth(), size.getHeight()), sizes); return true; }}通过这种方式, 我们的 mPreviewSizes 便按照比例完成分类了, 接下来看看那尺寸的选取
二) 尺寸的选取
// 1. 获取用户期望的比例的集合SortedSet previewSizes = mPreviewSizes.sizes(aspectRatio);if (previewSizes == null) { // 1.1 用户期望的比例不存在, 选取获取默认比例 previewSizes = mPreviewSizes.sizes(chooseDefaultAspectRatio());}// 2. 选择最合适的预览帧尺寸final Size previewSize = chooseOptimalPreviewSize(previewSizes);// 参数注入mCameraParams.setPreviewSize(previewSize.getWidth(), previewSize.getHeight());这里有两个需要注意的点
- 当用户指定的比例不支持, 选取默认比例
- 从比例对应的尺寸集合中, 选择最合适的预览帧尺寸
接下来逐一解析这两个算法
1. 选取默认比例
private AspectRatio chooseDefaultAspectRatio() { AspectRatio result = null; for (AspectRatio ratio : mPreviewSizes.ratios()) { result = ratio; if (AspectRatio.DEFAULT.equals(ratio)) { break; } } return result;}关于选取默认的比例代码比较简单, 即遍历预览比例集合, 判断是否存在默认比例, 若存在则返回默认比例, 反之则选取比例集合中的最后一个
- 默认比例一般设置为 4:3 , 一般市面上所有相机都支持 4:3 的预览帧, 其支持的预览帧尺寸也较高
思考
当用户指定的比例不支持, 为什么不选取与用户设置最接近的比例呢?
为了回答这个问题, 先看看使用最接近比例算法最后选取的预览结果
E/TAG: desire aspect is 1:1, closely is 11:9 E/TAG: desired size is: [1080, 1080]; choose preview size is: [352x288]
可以看到从这个比例中选取的预览尺寸远低于 View 的尺寸, 从数据上看几乎会进行 3 倍的上采样, 即使上采样算法再优, 也难以保证预览的清晰度
我们无法控制用户心仪的比例, 因此当用户指定的比例不存在时, 采用指定的默认比例是能够保证预览清晰度的比较好的方式, 接下来看看如何从比例中选择最合适的预览尺寸
2. 选择最合适的预览尺寸
private Size chooseOptimalPreviewSize(SortedSet sizes) { // 1. 确定期望的宽高 int desiredWidth; int desiredHeight; // 横屏状态, 直接使用 view 的宽高 if (isLandscape(screenOrientationDegrees)) { desiredWidth = viewWidth; desiredHeight = viewHeight; } // 竖屏状态, 反转宽高 else { desiredWidth = viewHeight; desiredHeight = viewWidth; } // 2. 查找最合适的预览尺寸 Size result = null; for (Size size : sizes) {// sizes 已按照从小到大排序 result = size; // 选取宽高都比期望稍大的 if (desiredWidth
- 默认比例一般设置为 4:3 , 一般市面上所有相机都支持 4:3 的预览帧, 其支持的预览帧尺寸也较高
- 使用简单, 仅需要指定比例即可, 使用者无需考虑旋转的问题
上一篇:怎么添加彩云服务器设备?
下一篇:面试经典150题——环形链表