基于 Jetson Orin + Argus API 的高精度双目同步采集。
Compiled and Testd with L4T 36.4.7 on Jetson Orin Nano
项目使用公共构建文件 CMakeLists.txt,同时构建多个目标,其中基本环境路径、公共库依赖移植自 jetson_multimedia_api/samples/Rules.mk。
cd dual_camera_record && mkdir build
# 构建
cmake -S . -B build
# 编译
cmake --build build./bin 下是二进制备份:
bin\
* 老版本,源代码已被迭代
dual_camera_record
dual_camera_mp4
* 20260202 版
DualRecordMp4-2k
DualRecordMp4-4k
* Newest Version
DualRecordMp4-260505
./src All Source files with headers Here.
其他都是测试生成文件,gitignore。
Start Up with build
# cd dual_camera_record/
mkdir <your_build_dir>
cmake -B <your_build_dir> -S .
cmake --build <your_build_dir>Start program with script
mkdir outputs/
./script/2kdual.sh 120 5000
# or
./script/4kdual.sh 120 5000包含一些公共常用头文件引用、宏定义。声明并定义 Application 的全局参数,包含分辨率、帧率、缓冲区大小等,使用 namespace 区分。
一个双目同步采集视频流,软件编码为 MP4 的工具。
源文件:./src/dual_record_mp4.cpp
牢记:Jetson Orin Nano 系列没有硬件编码器,诸如
NVENC、NvVideoEncoder::createVideoEncoder、nvv4l2h264enc等都无法使用!
$ ./build/DualRecordMp4 -h
Usage: DualRecordMP4 [OPTIONS]
Options:
-r Set output resolution WxH [Default 1640x1232]
-o Set output prefix [Default output]
-d Set capture duration [Default 5 seconds]
-f Set capture framerate [Default 21 fps]
-e Encode MJPEG (AVI) output on the fly (CPU, libavcodec)
-p Enable profiling
-v Enable verbose message
-h Print this help
Outputs:
<prefix>_left.avi, <prefix>_right.avi (with -e)
<prefix>_left.nv12, <prefix>_right.nv12 (without -e)
<prefix>_left_meta.csv, <prefix>_right_meta.csv-
ffmpeg软件编码库实现 MJPEG(AVI)输出到磁盘,由 Codex 实现,重构于SoftEncoder.cpp/h中。编码线程与采集线程解耦,队列满则丢帧避免阻塞采集。 -
传感器模式控制:
使用nvargus_nvraw提前查看传感器支持的模式和详细信息:# 显示分辨率和 Mode Index nvargus_nvraw --lps # 指定 Mode Index 查看详细参数 nvargus_nvraw --c 0 --mode 0 --sensorinfo
目前使用 IMX219 1640x1232 的原生分辨率,
nvargus_nvraw显示该最大 FR 为 29。
因此需要将DEFAULT_FPS对应的frameDurationNsclamp 到范围内:const uint64_t targetFrameNs = 1000000000ULL / static_cast<uint64_t>(DEFAULT_FPS); frameDurationNs = clampUint64(targetFrameNs, frameRange.min(), frameRange.max());
曝光时间
ExposureTime和帧生成时间FrameDuration不同,前者往往小于后者。
配置曝光锁定、关闭自动增益:execute() { ... SensorMode* mode_select = mode_list[3]; ... i_source_settings->setSensorMode(mode_select) i_source_settings->setFrameDurationRange(Range<uint64_t>(frameDurationNs, frameDurationNs)) i_source_settings->setExposureTimeRange(Range<uint64_t>(exposureNs, exposureNs)) ... i_auto_settings->setAeMode(AE_MODE_OFF) }
-
软件编码需要真实的
EFFECTIVE_FPS,来自于实际的帧生成时间frameDurationNs,否则播放时的 FPS 会漂。 -
缓冲区与 NVMM/EGL 的关系:
NvBuffer是 MM API 的通用缓冲区封装(包含 plane/FD/stride 等)。
NvNativeBuffer是 Argus samples 的 native buffer,继承NativeBuffer(IEGLImageSource),它在 NVMM 上分配并能创建EGLImage。
当前DmaBuffer通过多继承同时提供NvNativeBuffer的 EGLImage 能力和NvBuffer的 plane/FD 结构,便于 Argus + CPU/NVMM 共享同一块内存。 -
EGLStream/NV/ImageNativeBuffer.h是 EGLStream 场景下的扩展接口,允许把 EGLStream 的 Image 拷贝到 NvBuffer(可缩放/格式转换)。
当前项目使用的是BufferOutputStream + EGLImage,不走 EGLStream;如果后续改为 EGLStream(FrameConsumer/VPI)可考虑使用该接口。 -
NVBuffer和EGLImage不是同一类型,但可以指向同一块 NVMM:EGLImage是 GPU/EGL 视角的“句柄”,NvBuffer/NvBufSurface是 DMABUF + plane 元数据视角。两者通过 DMABUF 互相导入。 -
STREAM_SIZE在 BufferOutputStream 中通过 缓冲区尺寸生效,没有setResolution()。若设置了更小的DmaBuffer尺寸,Argus 会把传感器输出缩放到该尺寸;要拿到原始分辨率就要分配同尺寸的缓冲区。 -
试着开启了 Sync,不知道有什么用。
execute() { ... i_bufstream_settings->setSyncType(SYNC_TYPE_EGL_SYNC); ... } // 需要配套的同步处理 ConsumerThread::dumpNv12Frame() { if (iBuffer->getSyncType() == SYNC_TYPE_EGL_SYNC) { IEGLSync *iSync = interface_cast<IEGLSync>(buffer); EGLSyncKHR acquireSync = EGL_NO_SYNC_KHR; if (iSync->getAcquireSync(eglDisplay, &acquireSync) != STATUS_OK) ... }
若开启 EGL Sync,就必须 获取并等待/销毁 acquire sync,否则会出现:
(Argus) Error InvalidState: Buffer was released with an acquire sync that was never retreived。
其中有两个关键函数eglClientWaitSyncKHR和eglDestroySyncKHR的调用,可以在jetson_multimedia_api/include/NvEglRenderer.h中看到,\但是构建时链接错误 undefined reference to
ArgusSamples::eglClientWaitSyncKHR之类,根据 Codex 的说法,eglClientWaitSyncKHR/eglDestroySyncKHR属于 EGL 扩展符号,Jetson 的 libEGL 可能并不导出这些符号,必须通过eglGetProcAddress动态获取。解决方案是通过initEglSyncFunctions()使用函数指针调取函数。\但是
jetson_multimedia_api/argus/samples/eglImage/main.cpp中就是原来这么写的。很奇怪,这个例子还没有编译过,不清楚能否正常执行。 -
CaptureMetadata::getSourceIndex()在 BufferOutputStream 场景里可能一直是 0,不能当作左右相机 ID; -
VPI 管线建议:当前 BufferOutputStream + EGLImage 已经落在 NVMM,可用
NvBufSurfaceFromFd获取NvBufSurface,再用 VPI 的 NVMM wrapper 创建VPIImage让 GPU/CPU/VIC 调度。
若走 EGLStream 路线,则用 EGLOutputStream + VPI 的 EGLStream consumer;EGLStream 更适合 GPU-only 路径,但对同步和 consumer 管理要求更高。
- 适配 Orin NX,采用 NVENC 编码
- 统一采集时间戳,为接入 ROS 做准备
- Publish data e.g. image raw on NVMM, camera raw metadata to ROS topic
- Wrap the program as ROS Node