# ndk_camera_demo **Repository Path**: lizzwnn/ndk_camera_demo ## Basic Information - **Project Name**: ndk_camera_demo - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-15 - **Last Updated**: 2026-04-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # NDK Camera Demo 使用 Android Camera2 NDK API 的纯 C++ 独立进程,捕获摄像头帧并将原始 YUV 数据转储到文件中。无需 Java、无需 JNI — 仅一个原生 ELF 二进制文件。 ## 概述 ``` Camera HAL → CameraService → Camera2 NDK → AImageReader → AImage → YUV dump ``` ## 构建 ### 前置条件 - Android NDK r29,位于 `/opt/android-ndk-r29`(或修改 `build.sh`) - CMake 3.22+ - aarch64 目标设备(Android 8.0+) ### 编译 ```bash ./build.sh ``` 输出:`build/ndk_camera_demo`(aarch64 ELF 可执行文件) ### 部署与运行 默认采集所有可用摄像头(并发),10 秒: ```bash adb connect 192.168.1.25:5555 adb push build/ndk_camera_demo /data/local/tmp/ adb push libs/libbinder_ndk.so /data/local/tmp/ adb shell "export LD_LIBRARY_PATH=/data/local/tmp && /data/local/tmp/ndk_camera_demo -t 10 -d /data/local/tmp/ndk_camera_dump" ``` ### 命令行选项 | 参数 | 说明 | 默认值 | |------|------|--------| | `-c ` | 指定单个摄像头 ID | 所有摄像头(并发) | | `-t ` | 捕获时长(秒) | 10 | | `-d ` | 输出目录 | `/data/local/tmp/ndk_camera_dump` | | `-h` | 显示帮助 | — | ## 输出格式 每个摄像头输出为一个 `camera_.yuv` 文件 — **紧密排列的 NV21 格式**(已去除 stride 填充)。每帧固定大小 = `width × height × 1.5` 字节。1920×1080 每帧 3,110,400 字节。10 秒采集(~30fps)单文件约 933MB: ``` camera_4.yuv (连续追加 10 秒内所有帧,~290 帧) camera_5.yuv camera_6.yuv camera_7.yuv ``` YUView 打开时选择 **NV21** 格式,分辨率 1920×1080。 ## 架构 ``` main.cpp ├── ABinderProcess_setThreadPoolMaxThreadCount(8) # 多摄像头并发 IPC ├── ABinderProcess_startThreadPool() # Camera2 NDK IPC 必需 ├── enumerateCameras() # 获取所有可用摄像头 ID ├── 为每个摄像头创建独立 CameraCapture 实例(并发启动) └── 等待时长到期 → _exit(0) camera_capture.h ├── enumerateCameras() # 枚举所有摄像头(自由函数) └── CameraCapture 类 # 每个实例管理一个摄像头 camera_capture.cpp ├── ACameraManager → ACameraDevice # Camera2 NDK 管线 ├── AImageReader # 帧接收(YUV_420_888) ├── ACaptureSession # 捕获会话管理 ├── dumpFramesForDuration() # 按时长采集(定时器线程控制) └── writeFrameToFile() # 去除 stride 填充,写入紧密 NV21 数据 ``` ## 已知平台约束 ### libcamera2ndk 悬垂指针问题 设备的 `libcamera2ndk.so` 存在竞态条件:调用 `ACameraDevice_close()` 时,回调线程可能仍在访问已释放的对象 → 在 `RefBase::decStrong` 中崩溃。进程使用 `_exit(0)` 跳过析构函数,让内核进行清理。与 `avm_service` 的关闭模式一致。 ### Binder 线程池必需 Camera2 NDK 通过 Binder 与 `cameraserver` 通信。未调用 `ABinderProcess_startThreadPool()` 时,`ACameraManager_openCamera` 会返回 `ACAMERA_ERROR_INVALID_PARAMETER`。NDK r29 头文件未暴露这些函数 — 通过 `extern "C"` 手动声明。 ### 摄像头 ID 与设备节点 Camera2 NDK ID("4"、"5"、"6"、"7")通过 Camera HAL 映射到 V4L2 节点 `/dev/video51~66`。传给 demo 的是 HAL 层 ID,而非 video 设备编号。 ### Chroma 平面重叠检测 设备的 `YUV_420_888` chroma plane[1] 和 plane[2] 具有相同的 rowStride/length/pixelStride,实际指向同一块 VU 交错缓冲区(NV21)。代码通过比对 stride 参数检测此情况,只写入一次 UV 数据,避免重复。 ### AImageReader maxImages 设置为 3 以平衡帧缓冲与内存。更高的值增加延迟但减少帧丢失。 ## 目录结构 ``` ndk_camera_demo/ ├── CMakeLists.txt # 构建配置 ├── build.sh # 便捷构建脚本 ├── libs/ │ └── libbinder_ndk.so # 从设备提取(不在 NDK sysroot 中) ├── data/ # 本地存储的采集数据(不纳入版本控制) │ └── camera_.yuv # 从设备拉回的原始 YUV 文件 └── src/ ├── main.cpp # 入口点、多摄像头枚举、并发启动、关闭处理 ├── camera_capture.h # CameraCapture 类 + enumerateCameras() 声明 └── camera_capture.cpp # Camera2 NDK 实现 + 单文件追加写入 ```