- 移动开发
- 音视频
【免费下载链接】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.
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。
| Engine | API Level | 说明 |
|---|---|---|
Engine.CAMERA1 | 全部 | 经过充分测试,可靠性高。目前支持完整的特性集 |
Engine.CAMERA2 | API 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:
mExperimental为 true——它来自 XML 属性cameraExperimental(CameraView.java#L195 读取,属性声明见 attrs.xml#L170,默认false),也可以运行时用setExperimental(boolean)修改(CameraView.java#L899-L901);- 用户选择的引擎是
Engine.CAMERA2; - 设备 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.SURFACE | SurfaceView | 可能更省电,但不适合动态布局变化等场景;不支持视频快照 |
Preview.TEXTURE | TextureView | 更好一些。要求硬件加速开启;不支持视频快照 |
Preview.GL_SURFACE | GLSurfaceView | 推荐。支持视频快照、水印/叠加层、实时滤镜 |
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
功能完整度最高的实现,类注释完整描述了数据流:
- 相机把图像流写入一个
SurfaceTexture,构造该SurfaceTexture时传入一个预创建的 GL texture handle; - 相机按引擎选定的预览尺寸向该 buffer 写数据;
SurfaceTexture.updateTexImage()抓取最新帧并绑定到该 GL 纹理,同时给出变换矩阵;GLSurfaceView管理 EGL 上下文,并运行独立的渲染线程执行绘制;- 使用
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:cameraEngine | camera1(0) /camera2(1) | camera1 | camera2还要求cameraExperimental="true"且 API ≥ 21,否则回退 camera1 |
app:cameraPreview | surface(0) /texture(1) /glSurface(2) | glSurface | texture在硬件加速关闭时静默回退为glSurface |
app:cameraExperimental | boolean | false | 声明于 attrs.xml#L170,是启用 CAMERA2 的必要条件 |
四、Related APIs:setPreview / setEngine 及其生效条件
| Method | Description |
|---|---|
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;。当条件满足时,它做了三件事:
- 调用
doInstantiateEngine()重新构造Camera1Engine/Camera2Engine实例,并把现有预览重新绑定给新引擎(mCameraEngine.setPreview(mCameraPreview)); - 将旧引擎上的全部控制项逐一搬移到新引擎——facing、flash、mode、whiteBalance、hdr、audio 及码率、picture/video 尺寸选择器、视频参数、预览帧率、快照与帧处理尺寸等(源码注释:"Adding new ones? See setEngine()."),保证切换引擎后配置不丢失;
- 注意
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.
相关推荐
CameraView预览实现原理:SurfaceView与TextureView的选择策略
CameraView预览实现原理:SurfaceView与TextureView的选择策略 在Android相机开发中,CameraView项目通过Surfac
移动开发CameraView项目深度解析:相机引擎与预览实现方案
CameraView项目深度解析:相机引擎与预览实现方案 前言 在移动应用开发中,相机功能的实现一直是技术难点之一。CameraView作为一个强大的相机视图组
移动开发音视频lowcode-engine 预置设置器详解:SelectSetter 枚举下拉选择器的配置与实现原理
lowcode engine 预置设置器详解:SelectSetter 枚举下拉选择器的配置与实现原理 本篇基于 lowcode engine(面向扩展设计的企
前端低代码
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考