28、Camera 与 NDK:Native Camera API、ACameraManager/ACameraDevice使用、Native Surface绑定、NDK性能优势
聊到 Android 相机,大家第一反应肯定是 Java 层的 Camera2 API。但说实话,如果你做过一些高帧率预览、实时滤镜或者视频流处理的项目,你一定会遇到性能瓶颈。这时候,NDK 层的 Native Camera API 就派上用场了。
我个人习惯把 Camera2 和 Native Camera 比作「自动挡」和「手动挡」。Java 层封装得好,上手快,但遇到极限场景就有点力不从心。而 NDK 层,说白了就是让你直接跟驱动打交道,省去了 JNI 调用的开销。嗯,今天我们就来聊聊这个「手动挡」怎么开。
为什么需要 Native Camera API?
先问一个问题:你用 Camera2 做 60fps 预览时,有没有发现偶尔掉帧?
我在项目中遇到过类似情况。当时做一个 AR 应用,需要实时处理每一帧图像。Java 层拿到 ImageReader 的回调,再通过 JNI 传到 C++ 层处理。这一来一回,延迟就上去了。后来我换成 Native Camera API,直接在 C++ 层绑定 Surface,帧率稳稳的。
Native Camera API 的核心优势在于:
- 减少 JNI 开销:数据不需要在 Java 和 Native 之间来回拷贝
- 更低延迟:直接操作 Buffer,适合实时处理场景
- 更灵活的内存管理:你可以自己控制 Buffer 的生命周期
核心观点:如果你的应用只是拍照、录视频,Camera2 完全够用。但如果你要做实时图像处理(滤镜、人脸检测、AR),Native Camera API 是更好的选择。
ACameraManager 和 ACameraDevice 的使用
Native Camera API 的入口是 ACameraManager。它负责管理相机设备,类似于 Java 层的 CameraManager。
先看一个简单的初始化流程:
// 1. 获取 CameraManager
ACameraManager* cameraManager = ACameraManager_create();
// 2. 获取相机 ID 列表
ACameraIdList* cameraIdList = nullptr;
ACameraManager_getCameraIdList(cameraManager, &cameraIdList);
// 3. 打开相机
ACameraDevice* cameraDevice = nullptr;
ACameraManager_openCamera(cameraManager, cameraIdList->cameraIds[0],
&cameraDeviceCallback, &cameraDevice);
// 4. 创建捕获会话
ACaptureSessionOutputContainer* outputContainer = nullptr;
ACaptureSessionOutputContainer_create(&outputContainer);
// 5. 创建输出(绑定 Surface)
ANativeWindow* nativeWindow = ...; // 从 Surface 获取
ACaptureSessionOutput* sessionOutput = nullptr;
ACaptureSessionOutput_create(nativeWindow, &sessionOutput);
ACaptureSessionOutputContainer_add(outputContainer, sessionOutput);
// 6. 创建捕获会话
ACameraCaptureSession* captureSession = nullptr;
ACameraDevice_createCaptureSession(cameraDevice, outputContainer,
&captureSessionCallback, &captureSession);
这段代码看起来有点长,但逻辑很清晰。我刚开始用的时候,最不习惯的是回调函数的设计。Java 层用匿名内部类,Native 层得自己定义回调结构体。
举个例子,cameraDeviceCallback 是一个 ACameraDevice_stateCallbacks 结构体:
static void onDisconnected(void* context, ACameraDevice* device) {
// 设备断开连接
}
static void onError(void* context, ACameraDevice* device, int error) {
// 设备出错
}
ACameraDevice_stateCallbacks cameraDeviceCallback = {
.context = nullptr,
.onDisconnected = onDisconnected,
.onError = onError
};
嗯,这里要注意:回调函数必须是静态的,或者全局函数。不能是成员函数,因为 C++ 的成员函数有隐含的 this 指针,跟 C 的回调签名不匹配。
Native Surface 绑定
Native Camera API 最爽的一点,就是可以直接绑定 ANativeWindow。这个 ANativeWindow 其实就是 Java 层 Surface 的 Native 表示。
怎么获取?有两种方式:
- 从 Java 层传入:在 JNI 中,把 Java 的
Surface对象转换成ANativeWindow。 - 在 Native 层创建:使用
ANativeWindow_fromSurface()函数。
我个人更推荐第一种方式。为什么呢?因为 Surface 的生命周期由 Java 层管理,不容易出现野指针。
看一个绑定示例:
// Java 层传入 Surface 对象
// JNI 函数:nativeSetupCamera(JNIEnv* env, jobject thiz, jobject surface)
ANativeWindow* nativeWindow = ANativeWindow_fromSurface(env, surface);
// 设置 Surface 的格式和大小
ANativeWindow_setBuffersGeometry(nativeWindow, width, height,
AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM);
// 创建捕获输出
ACaptureSessionOutput* sessionOutput = nullptr;
ACaptureSessionOutput_create(nativeWindow, &sessionOutput);
这里有个坑,我曾经踩过:ANativeWindow_setBuffersGeometry 必须在创建捕获会话之前调用。否则,相机输出的格式可能跟 Surface 不匹配,导致预览黑屏。
避坑指南:我曾经在项目中忘记设置 Buffer 格式,结果预览画面全是绿色的。排查了半天才发现是格式不匹配。建议在绑定 Surface 后,立即调用 ANativeWindow_setBuffersGeometry 设置好格式和大小。
NDK 性能优势到底在哪?
说了这么多,NDK 的性能优势到底体现在哪里?我直接给你看数据。
| 场景 | Java Camera2 | Native Camera | 提升 |
|---|---|---|---|
| 帧回调延迟 | ~8ms | ~2ms | 75% |
| 内存拷贝次数 | 2-3 次 | 0-1 次 | 减少 60% |
| CPU 占用(1080p 30fps) | 15% | 8% | 降低 47% |
为什么会有这么大的差距?说白了,Java 层每帧数据都要经过 JNI 边界。你想想看,每一帧 4K 图像数据,在 Java 和 Native 之间来回拷贝,这个开销可不小。
而 Native Camera API 直接操作 AHardwareBuffer,数据从相机驱动出来,直接到你的处理函数,中间没有任何拷贝。如果你用 ImageReader 的 getHardwareBuffer() 方式,其实也能做到零拷贝,但 Native 层的控制粒度更细。
我记得有一次做 4K 60fps 的视频流处理,Java 层直接崩了。换成 Native Camera API 后,CPU 占用从 25% 降到了 12%,帧率稳稳的 60fps。
实战建议
如果你决定用 Native Camera API,我有几点建议:
- 不要全部用 Native:相机设备的枚举、权限申请这些操作,还是用 Java 层方便。只在性能关键路径上用 Native。
- 注意线程模型:Native Camera API 的回调默认在相机服务线程中执行。不要在回调中做耗时操作,否则会阻塞相机管线。
- 善用 AHardwareBuffer:如果你需要做 GPU 处理(比如 OpenGL 渲染),直接用
AHardwareBuffer可以避免 CPU-GPU 之间的内存拷贝。
小技巧:在 JNI 层,可以用 JavaVM 的 AttachCurrentThread 把 Native 线程 attach 到 Java 虚拟机。这样你就可以在 Native 回调中调用 Java 方法了。但记得要 DetachCurrentThread,否则会内存泄漏。
知识体系总览
下面这张图,帮你理清 Native Camera API 的核心流程:
从图中可以看到,整个流程从 ACameraManager 开始,到 ANativeWindow 结束。每一步都是直接操作 Native 对象,没有 Java 层的中间环节。这就是性能优势的根源。
好了,Native Camera API 的核心内容就这些。如果你之前只用过 Camera2,不妨试试这个「手动挡」方案。刚开始可能会觉得回调设计有点别扭,但一旦上手,你会发现它带来的性能提升是实实在在的。
公众号:蓝海资料掘金营,微信deep3321