news 2026/10/13 3:06:50

CameraView 引擎与预览详解:Engine 与 Preview 的选择、配置与底层实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CameraView 引擎与预览详解:Engine 与 Preview 的选择、配置与底层实现
  • 移动开发
  • 音视频

【免费下载链接】CameraView

📸 A well documented, high-level Android interface that makes capturing pictures and videos easy, addressing all of the common issues and needs. Real-time filters, gestures, watermarks, frame processing, RAW, output of any size.

项目地址:https://gitcode.com/gh_mirrors/ca/CameraView
点击查看免费下载

CameraView 通过两套可插拔的抽象来对接相机硬件:Engine(相机引擎,决定用哪套系统相机 API 读取传感器数据)与Preview(预览实现,决定把预览流渲染到哪种 View 上)。本文基于仓库文档 previews.md 展开,结合 CameraView、引擎与预览相关源码,讲清这两组枚举的取值、默认值、回退策略与生效时机,帮助你在布局 XML 或代码中做出正确的组合,并理解 GL 预览为何是默认推荐方案。

一、Engine:CAMERA1 与 CAMERA2 的选择

CameraView 可以通过经典的CAMERA1接口,也可以通过较新的CAMERA2接口(Android 5.0 / API 21 引入)与相机传感器交互。由于CAMERA2引擎较新,测试与反馈相对较少,因此启用它的前提是同时打开实验特性开关app:cameraExperimental="true";在 Lollipop 之前的设备上,引擎会强制回退为Engine.CAMERA1。

EngineAPI Level说明
Engine.CAMERA1全部经过充分测试,可靠性高。目前支持完整的特性集
Engine.CAMERA2API 21+实验性,但将是未来的重点方向。部分新控制项可能仅在此引擎下可用

1.1 枚举定义与默认值

引擎由枚举 Engine 表达,两个值携带 XML 映射用的整型编码:

public enum Engine implements Control { /** Camera1 based engine. */ CAMERA1(0), /** * Camera2 based engine. For API versions older than 21, * the system falls back to {@link #CAMERA1}. */ CAMERA2(1); final static Engine DEFAULT = CAMERA1; ... }

从源码结构看,DEFAULT = CAMERA1:不设置任何属性时,CameraView 默认走 CAMERA1 引擎。Control接口是 CameraView 所有控制项(flash、mode、engine、preview 等)的共同契约,因此Engine也能通过CameraView#set(Control)/get(Class)通用方法读写。

1.2 引擎的实际实例化:实验开关 + SDK 版本双重条件

真正把Engine枚举翻译成具体引擎类的逻辑在 CameraView.instantiateCameraEngine():

if (mExperimental && engine == Engine.CAMERA2 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return new Camera2Engine(callback); } else { mEngine = Engine.CAMERA1; return new Camera1Engine(callback); }

三个条件必须同时满足才会返回Camera2Engine:

  1. mExperimental为 true——它来自 XML 属性cameraExperimental(CameraView.java#L195 读取,属性声明见 attrs.xml#L170,默认false),也可以运行时用setExperimental(boolean)修改(CameraView.java#L899-L901);
  2. 用户选择的引擎是Engine.CAMERA2;
  3. 设备 API 级别 ≥ 21。

否则不仅返回Camera1Engine,还会把mEngine字段改回Engine.CAMERA1——即文档所说"on devices older than Lollipop, the engine will always beEngine.CAMERA1",回退后getEngine()反映的也是实际生效的引擎,而不是你最初请求的值。

两个引擎的具体实现分别位于 Camera1Engine 与 Camera2Engine,二者都继承自 CameraBaseEngine,后者封装了与具体 API 无关的公共逻辑(帧管理器、Overlay、控制项参数等)。

二、Previews:三种预览实现

CameraView 支持多种预览实现,可通过 XML 属性cameraPreview或代码中的Preview控制项配置。所有预览在任意 Engine 下都受支持——预览层只关心"把输入流画到哪里",与传感器数据来自哪套 API 无关。

默认值为 OpenGL 的GL_SURFACE,官方强烈建议保持默认以获得全部功能;只有资深开发者才会基于特定权衡选择其他方案。

Preview底层载体说明
Preview.SURFACESurfaceView可能更省电,但不适合动态布局变化等场景;不支持视频快照
Preview.TEXTURETextureView更好一些。要求硬件加速开启;不支持视频快照
Preview.GL_SURFACEGLSurfaceView推荐。支持视频快照、水印/叠加层、实时滤镜

GL 预览还有一个额外收益:更高效的图片快照路径。它规避了 v1 时代的一系列脏活——OOM 风险、运行时旋转图像、手动读取 EXIF 等,并且这些图片快照在录像过程中同样可以拍摄。

2.1 枚举定义与 XML 编码

Preview 枚举的三个值与attrs.xml中的 XML 枚举一一对应:

// Preview.java SURFACE(0), // SurfaceView 实现,Not recommended. TEXTURE(1), // TextureView 实现,Stable,但不支持 video snapshots 等特性 GL_SURFACE(2); // GLSurfaceView 实现,官方注释中即为 "the best engine available" final static Preview DEFAULT = GL_SURFACE;

对应 attrs.xml#L82-L86 的声明:

<attr name="cameraPreview" format="enum"> <enum name="surface" value="0" /> <enum name="texture" value="1" /> <enum name="glSurface" value="2" /> </attr>

XML 属性的读取发生在 ControlParser:array.getInteger(R.styleable.CameraView_cameraPreview, Preview.DEFAULT.value()),未声明时回退到GL_SURFACE。

2.2 预览的实际实例化:硬件加速回退链

CameraView.instantiatePreview() 展示了三种预览的构造与一条容易被忽略的回退链:

switch (preview) { case SURFACE: return new SurfaceCameraPreview(context, container); case TEXTURE: { if (isHardwareAccelerated()) { // TextureView is not supported without hardware acceleration. return new TextureCameraPreview(context, container); } } case GL_SURFACE: default: { mPreview = Preview.GL_SURFACE; return new GlCameraPreview(context, container); } }

要点:

  • TEXTURE在硬件加速关闭时不会抛错,而是静默落穿到GL_SURFACE分支,并把mPreview改写为实际生效值。这与 Engine 的 API 21 回退策略是同一设计思想:保证预览总能启动,getPreview()返回真实状态。
  • 预览不在构造函数中创建,而是在 onAttachedToWindow() 中惰性实例化。源码注释解释得很直接:isHardwareAccelerated()只有在 View 附加到窗口之后才返回真实值,所以必须等 attach 再判断、再建预览。
  • 每种预览对应一个独立实现类,均继承抽象基类 CameraPreview。

2.3 CameraPreview 泛型契约:输入流与输出 Surface

基类 CameraPreview 的注释定义了预览的职责:"接收来自 CameraEngine 的输入流,并把它送入 View 层级中的一个输出 surface"。它的两个类型参数正对应这一点:

  • T extends View:承载内容的视图(SurfaceView/TextureView/GLSurfaceView);
  • Output:暴露给引擎的输出对象,只有两种——SurfaceHolder或SurfaceTexture,通过抽象方法getOutput()返回(CameraPreview.java#L143)。

引擎侧根据getOutput()的运行时类型选择挂载方式,例如 Camera1Engine 中:

mCamera.setPreviewDisplay((SurfaceHolder) mPreview.getOutput()); // 或 mCamera.setPreviewTexture((SurfaceTexture) mPreview.getOutput());

预览与引擎的绑定经由 CameraBaseEngine.setPreview() 完成:引擎把自己注册为预览的SurfaceCallback,从而在 surface 可用、尺寸变化、销毁时通知引擎重算预览流尺寸并触发新一轮布局。

2.4 三种预览的实现差异

SURFACE —— SurfaceCameraPreview

基于SurfaceView,输出为SurfaceHolder(getOutput())。类注释直言不讳:它是"硬件加速关闭时的 fallback,是最后的保底方案(last resort)……Do not use"。它还有两条结构性限制:

  • 不支持裁剪(supportsCropping()返回基类默认值false,见 CameraPreview.java#L314-L316)。当裁剪不受支持时,CameraView 的测量逻辑 会把EXACTLY测量模式降级为AT_MOST,意味着CameraView被强制按wrap_content处理——这正是文档中"will not work well with dynamic layout changes"的源码级原因;
  • 使用了SURFACE_TYPE_PUSH_BUFFERS(SurfaceCameraPreview.java#L42)这类老旧的 surface 模式。

SurfaceView是挖洞式透明视图,层级与父 ViewGroup 的合成行为特殊,这是它"可能更省电但布局行为不友好"的来源。

TEXTURE —— TextureCameraPreview

基于TextureView,输出为SurfaceTexture,位于普通 View 合成层之上,可参与普通布局变换,且 supportsCropping() 返回 true。但正如类注释所说,它"比 SurfaceCameraPreview 好,却远不如 GlCameraPreview 强大":缺少 GL 渲染管线,因此无法承载实时滤镜与 Overlay,也不支持视频快照。另外它依赖硬件加速——未开启时如 2.2 节所述会静默回退为 GL 预览。

GL_SURFACE —— GlCameraPreview

功能完整度最高的实现,类注释完整描述了数据流:

  1. 相机把图像流写入一个SurfaceTexture,构造该SurfaceTexture时传入一个预创建的 GL texture handle;
  2. 相机按引擎选定的预览尺寸向该 buffer 写数据;
  3. SurfaceTexture.updateTexImage()抓取最新帧并绑定到该 GL 纹理,同时给出变换矩阵;
  4. GLSurfaceView管理 EGL 上下文,并运行独立的渲染线程执行绘制;
  5. 使用RENDERMODE_WHEN_DIRTY,每当OnFrameAvailableListener收到新帧就调用线程安全的requestRender()触发一次渲染。

在 onDrawFrame() 中可以看到完整的绘制步骤:updateTexImage()锁帧 → 读取变换矩阵 → 按需叠加 Camera2 的绘制旋转 → 计算并应用居中裁剪(mCropScaleX/Y)→ 经GlTextureDrawer绘制(这一步会套用当前Filter,即实时滤镜的挂接点)。EGL 上下文版本固定为 2.0(GlCameraPreview.java#L88),预览布局由 cameraview_gl_view.xml 提供。

正因为拥有独立的 GL 渲染线程与纹理句柄,GlCameraPreview 还实现了FilterCameraPreview与RendererCameraPreview两个扩展接口,向后者开放RendererFrameCallback机制——外部组件可以注册回调,在渲染线程上拿到textureId、EGL 上下文与裁剪信息。

GL 快照为何更高效:文档所说"避免 OOM、运行时旋转、读 EXIF"的具体实现是 SnapshotGlPictureRecorder。其类注释交代了完整流程:注册一次性RendererFrameCallback→ 在渲染线程取得纹理与 EGL 上下文 → 切到工作线程为同一上下文创建新的 EGL surface 并重绘该纹理(顺带绘制 Overlay)→glReadPixels直接读出像素写文件。因为是在 GPU 纹理层面"再画一次"而不是解码相机 JPEG,旋转由变换矩阵处理,无需 CPU 端 Bitmap 操作,也无需解析/写入 EXIF 方向信息;且由于与相机输出流解耦,录像过程中同样可以截出图片快照(视频快照)。

三、XML Attributes

原文档给出的最小配置示例:

<com.otaliastudios.cameraview.CameraView app:cameraEngine="camera1|camera2" app:cameraPreview="surface|texture|glSurface"/>

结合 attrs.xml 与 demo 布局 activity_camera.xml,一个启用 CAMERA2 引擎的完整写法是:

<com.otaliastudios.cameraview.CameraView android:id="@+id/camera" android:layout_width="wrap_content" android:layout_height="wrap_content" app:cameraExperimental="true" app:cameraEngine="camera2" app:cameraPreview="glSurface" ... />

参数要点:

属性可选值默认值备注
app:cameraEnginecamera1(0) /camera2(1)camera1camera2还要求cameraExperimental="true"且 API ≥ 21,否则回退 camera1
app:cameraPreviewsurface(0) /texture(1) /glSurface(2)glSurfacetexture在硬件加速关闭时静默回退为glSurface
app:cameraExperimentalbooleanfalse声明于 attrs.xml#L170,是启用 CAMERA2 的必要条件

四、Related APIs:setPreview / setEngine 及其生效条件

MethodDescription
setPreview(Preview)Sets the preview implementation.
getPreview()Gets the current preview implementation.
setEngine(Engine)Sets the engine implementation.
getEngine()Gets the current engine implementation.

文档特别强调了两个方法各自的调用时机约束,源码与之一一对应:

setPreview 只在"从未附加到窗口"时有效。CameraView.setPreview() 的实现:

public void setPreview(@NonNull Preview preview) { boolean isNew = preview != mPreview; if (isNew) { mPreview = preview; boolean isAttachedToWindow = getWindowToken() != null; if (!isAttachedToWindow && mCameraPreview != null) { // Null the preview: will create another when re-attaching. mCameraPreview.onDestroy(); mCameraPreview = null; } } }

即:如果CameraView尚未加入窗口(例如你刚以纯代码方式创建它),setPreview会记录新值;若此时已有预览对象,则销毁并置空,等下次onAttachedToWindow()时按新值重新实例化。若 View 已经在窗口中,调用没有任何效果——预览视图一旦建立就无法热切换,这也解释了为什么官方推荐直接在 XML 里声明cameraPreview。

setEngine 只在"相机处于关闭状态"时有效。CameraView.setEngine() 的第一行就是if (!isClosed()) return;。当条件满足时,它做了三件事:

  1. 调用doInstantiateEngine()重新构造Camera1Engine/Camera2Engine实例,并把现有预览重新绑定给新引擎(mCameraEngine.setPreview(mCameraPreview));
  2. 将旧引擎上的全部控制项逐一搬移到新引擎——facing、flash、mode、whiteBalance、hdr、audio 及码率、picture/video 尺寸选择器、视频参数、预览帧率、快照与帧处理尺寸等(源码注释:"Adding new ones? See setEngine()."),保证切换引擎后配置不丢失;
  3. 注意setFrameProcessingFormat被特意传 0,注释说明"this is very engine specific, so do not pass",即帧处理格式因引擎而异,不做迁移。

另外,Engine与Preview都实现了Control接口,因此也可以使用通用短路 API 读写(CameraView.set()/get()):

cameraView.set(Preview.GL_SURFACE); cameraView.set(Engine.CAMERA2); Preview current = cameraView.get(Preview.class); Engine engine = cameraView.get(Engine.class);

五、选型建议小结

综合文档结论与源码行为:

  • 绝大多数场景:保持默认GL_SURFACE预览,不显式设置引擎(即 CAMERA1),获得全部功能(实时滤镜、水印/Overlay、视频快照、GL 图片快照);
  • 需要新控制项或面向 API 21+ 的设备:加cameraExperimental="true"并选择cameraEngine="camera2",接受其"实验性"定位;
  • 选择 SURFACE / TEXTURE 预览:仅在明确了解其代价(SURFACE 失去裁剪与 EXACTLY 布局能力、TEXTURE 无视频快照与滤镜、TEXTURE 依赖硬件加速且可能静默回退)时才有意义;
  • 代码切换时机:setPreview需在 View 入窗之前调用,setEngine需在相机关闭(close()之后、open()之前)时调用,否则静默无效。
  • 移动开发
  • 音视频

【免费下载链接】CameraView

📸 A well documented, high-level Android interface that makes capturing pictures and videos easy, addressing all of the common issues and needs. Real-time filters, gestures, watermarks, frame processing, RAW, output of any size.

项目地址:https://gitcode.com/gh_mirrors/ca/CameraView
点击查看免费下载
上一篇:Go Web 应用安全与加密实战总结:build-web-application-with-golang 第 9 章要点全览
下一篇:Klavis QuickBooks MCP Server 实战指南:让 AI Agent 通过 OAuth 安全操作 QuickBooks 会计数据

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 3:06:50

免TPM免微软账号:Win11本地账户安装盘3步做好

免TPM免微软账号&#xff1a;Win11本地账户安装盘3步做好 【免费下载链接】rufus The Reliable USB Formatting Utility 项目地址: https://gitcode.com/GitHub_Trending/ru/rufus 给一台2019年的笔记本装Windows 11&#xff0c;安装程序第一句就是"此PC不满足最低…

作者头像 李华
网站建设 2026/10/13 3:06:15

用vscode写python代码,vscode代替pycharm

默认下过python解释器了 vscode插件搜python&#xff0c;第一个带有microsoft的就是&#xff0c;这个是方便写代码的 新建一个文件夹用vscode打开&#xff0c;在文件夹下新建一个python文件&#xff0c;比如test.py 随便编辑一段代码print("hello,岁岁年年&#xff0c;朝朝…

作者头像 李华
网站建设 2026/10/13 3:05:58

从源码到上线:H5棋牌游戏前后端部署与二次开发指南

简介&#xff1a;面向棋牌游戏开发者和创业者的H5棋牌全开源运营版资源包&#xff0c;包含完整源码、数据库与架设教程&#xff0c;覆盖牛牛金花等玩法&#xff0c;支持观战、积分模式及前后台控制&#xff0c;可帮助用户从零搭建并定制棋牌平台。整套资源共2000个文件&#xf…

作者头像 李华
网站建设 2026/10/13 3:05:40

openclaw 命令速查:把 settings 改到 TaoToken 的完整配置清单

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/13 3:05:22

第48篇-微服务系统分析与设计

【软考高级系统分析师全链路通关实战】第 48 篇&#xff1a;微服务系统分析与设计 本系列定位&#xff1a;面向有开发经验、从零备考软考高级「系统分析师」的工程师&#xff0c;以《系统分析师教程&#xff08;第 2 版&#xff09;》为主线&#xff0c;按「综合知识 → 案例分…

作者头像 李华
网站建设 2026/10/13 3:04:32

蜂鸟E200 RISC-V核实战:从配置仿真到上板调试的完整避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华