Skip to content

V4L2_CameraController

SweerItTer edited this page Feb 1, 2026 · 3 revisions

CameraController API 文档

概述

CameraController 是 utilsCore V4L2 模块的核心类,提供 V4L2 摄像头设备的高级封装,负责相机设备的初始化、配置、采集控制和缓冲区管理。

职责

  • V4L2 设备打开和初始化
  • 摄像头参数配置(分辨率、格式、帧率等)
  • 缓冲区管理(MMAP 和 DMA-BUF 两种模式)
  • 异步帧采集和回调机制
  • 线程控制和 CPU 亲和性设置

适用场景

  • V4L2 摄像头的实时采集
  • 视频流的获取和处理
  • 零拷贝数据传输(配合 DMA-BUF)
  • 多线程视频处理流水线

依赖关系

  • 依赖: v4l2 内核子系统
  • 被依赖: VisionPipeline, RecordPipeline 等上层模块

类分析

CameraController 类

职责与用途

CameraController 是 V4L2 摄像头的完整封装,提供:

  • PIMPL 模式隐藏实现细节
  • MMAP 和 DMA-BUF 两种内存模式
  • 异步帧采集回调机制
  • 双缓冲区管理
  • 线程控制功能

设计模式

  • PIMPL 模式: 使用 Impl 类隐藏实现细节
  • 回调机制: 通过 FrameCallback 异步通知帧数据
  • RAII: 自动管理设备资源和缓冲区

Config 配置结构体

职责与用途

Config 结构体用于配置 CameraController 的运行参数。

结构体定义

struct Config {
    int buffer_count = 4;                    // 缓冲区数量
    __u32 plane_count = 2;                   // 平面数量(多平面格式)
    bool use_dmabuf = false;                 // 内存类型:true=DMA-BUF, false=MMAP
    std::string device = "/dev/video0";      // 设备路径
    uint32_t width = 1280;                   // 图像宽度
    uint32_t height = 720;                   // 图像高度
    uint32_t format = V4L2_PIX_FMT_NV12;     // 像素格式
};

字段说明

字段 类型 默认值 说明
buffer_count int 4 V4L2 缓冲区数量,建议 4-6
plane_count __u32 2 多平面格式的平面数(NV12=2, RGB=1)
use_dmabuf bool false true=使用 DMA-BUF, false=使用 MMAP
device string "/dev/video0" V4L2 设备路径
width uint32_t 1280 图像宽度(像素)
height uint32_t 720 图像高度(像素)
format uint32_t NV12 像素格式(V4L2_PIX_FMT_*)

公共 API 方法

构造函数

explicit CameraController(const Config& config);
~CameraController();

参数说明:

  • config (输入): 配置结构体

返回值: 无

所有权归属: CameraController 拥有设备 fd 和所有缓冲区的所有权

注意事项:

  1. 构造时会自动打开设备和初始化
  2. 失败时会抛出异常
  3. 必须先设置回调才能调用 start()

start() - 启动采集

void start();

参数说明: 无

返回值: 无

所有权归属: 无

注意事项:

  1. 必须先调用 setFrameCallback() 设置回调
  2. 启动采集线程,开始获取帧数据
  3. 失败时会抛出异常

使用例程:

CameraController::Config cfg;
cfg.device = "/dev/video0";
cfg.width = 1920;
cfg.height = 1080;
cfg.format = V4L2_PIX_FMT_NV12;
cfg.use_dmabuf = true;

CameraController camera(cfg);
camera.setFrameCallback([](FramePtr frame) {
    printf("Got frame: %dx%d\n", frame->meta.w, frame->meta.h);
});

camera.start();  // 开始采集

pause() - 暂停采集

void pause();

参数说明: 无

返回值: 无

所有权归属: 无

注意事项:

  • 暂停采集,但保持设备打开状态
  • 不会释放缓冲区
  • 可以通过 stop() 停止采集
  • 暂停期间不会触发帧回调

stop() - 停止采集

void stop();

参数说明: 无

返回值: 无

所有权归属: 无

注意事项:

  • 停止采集线程
  • 释放所有缓冲区
  • 关闭设备 fd
  • 调用后需要重新创建 CameraController 才能再次使用

setThreadAffinity() - 设置线程亲和性

void setThreadAffinity(int cpu_core);

参数说明:

  • cpu_core (输入): CPU 核心编号

返回值: 无

所有权归属: 无

注意事项:

  1. 绑定采集线程到指定 CPU 核心
  2. 提高缓存命中率,减少上下文切换
  3. 必须在 start() 之前调用

returnBuffer() - 归还缓冲区

void returnBuffer(int index);

参数说明:

  • index (输入): 缓冲区索引

返回值: 无

所有权归属: 无

注意事项:

  1. 通常由 Frame 析构函数自动调用
  2. 手动调用可以提前归还缓冲区
  3. 索引必须在有效范围内
  4. 线程安全,可以并发调用

setFrameCallback() - 设置帧回调

void setFrameCallback(FrameCallback&& callback);

参数说明:

  • callback (输入): 帧回调函数,类型为 std::function<void(FramePtr)>

返回值: 无

所有权归属: CameraController 持有回调函数的所有权

注意事项:

  1. 必须先调用此方法再调用 start()
  2. 回调函数会在采集线程中调用
  3. 不要在回调中执行耗时操作
  4. 建议使用队列传递帧数据到其他线程

使用例程:

// 简单回调
camera.setFrameCallback([](FramePtr frame) {
    // 快速处理
    printf("Frame: %dx%d\n", frame->meta.w, frame->meta.h);
});

// 使用队列传递
std::queue<FramePtr> frame_queue;
std::mutex queue_mutex;

camera.setFrameCallback([&](FramePtr frame) {
    std::lock_guard<std::mutex> lock(queue_mutex);
    frame_queue.push(frame);
});

getDeviceFd() - 获取设备文件描述符

int getDeviceFd() const;

参数说明: 无

返回值: V4L2 设备文件描述符

所有权归属: 只读访问,不要手动关闭

注意事项:

  • fd 由 CameraController 管理
  • 可以用于直接调用 V4L2 ioctl
  • 返回的 fd 由 CameraController 负责关闭

FrameCallback 回调机制

职责

FrameCallback 是采集线程通知用户有新帧数据的回调机制。

回调时机

  • 每次成功采集到一帧数据时
  • 在采集线程中调用
  • 异步通知,不阻塞采集流程

数据所有权

  • FramePtr 的所有权通过 std::move 转移给回调接收者
  • 回调接收者负责持有 FramePtr
  • Frame 析构时会自动归还缓冲区到 V4L2 队列

线程安全

  • 回调在采集线程中调用
  • 多个 FramePtr 不能共享,每个 Frame 有独立的所有权
  • 需要额外的同步机制来管理回调中的数据流

DMA-BUF 分配机制

概述

DMA-BUF 模式下,CameraController 分配的缓冲区可以直接被其他硬件模块(RGA、DRM、MPP)使用,实现零拷贝传输。

分配流程

1. 打开设备 (/dev/video0)
2. 查询设备能力(是否支持 DMA-BUF)
3. 分配 DMA-BUF (DmaBuffer::create)
4. 将 DMA-BUF fd 关联到 V4L2 缓冲区
5. 启动流

所有权管理

V4L2 采集 → DMA-BUF fd → FramePtr (回调中) → 用户持有
  • V4L2 不直接拥有 DMA-BUF
  • CameraController 拥有 DMA-BUF 的 DRM handle
  • FramePtr 通过 SharedBufferState 持有 DMA-BUF fd
  • Frame 析构时自动归还缓冲区

所有权规则总结

资源 创建者 拥有者 释放方式 线程保护
V4L2 fd CameraController CameraController ~CameraController()close() 内部同步
V4L2 缓冲区 CameraController CameraController stop()VIDIOC_STREAMOFF 内部同步
DMA-BUF handle CameraController CameraController ~DmaBuffer() fd_mutex
DMA-BUF fd CameraController SharedBufferState ~SharedBufferState() 原子标志
FramePtr CameraController 回调接收者 ~Frame()returnBuffer() 自动归还

所有权传递规则

创建流:
CameraController 构造 → 拥有 V4L2 fd 和缓冲区

采集流:
V4L2 采集 → 创建 FramePtr → 回调中 std::move → 回调接收者持有

归还流:
Frame 构构 → bufReleasCallback_() → returnBuffer(index) → 归还到 V4L2 队列

线程安全说明

同步机制

  1. 缓冲区访问: 使用 std::mutex _mutex_ 保护缓冲区队列操作
  2. 线程状态: 使用 std::atomic<bool> 控制运行/停止/暂停状态
  3. Frame 所有权: 通过 std::move 转移所有权,避免并发访问

线程安全建议

  • 回调函数: 不要在回调中阻塞,建议使用队列传递
  • Frame 使用: 每个 FramePtr 只能由一个线程持有
  • 停止等待: stop() 会等待采集线程退出
  • 暂停恢复: pause() 是线程安全的

典型使用场景

场景 1: 基本采集

// 1. 配置参数
CameraController::Config cfg;
cfg.device = "/dev/video0";
cfg.width = 1280;
cfg.height = 720;
cfg.format = V4L2_PIX_FMT_NV12;
cfg.use_dmabuf = true;

// 2. 创建控制器
CameraController camera(cfg);

// 3. 设置回调
camera.setFrameCallback([](FramePtr frame) {
    printf("Frame: %dx%d, timestamp=%lu\n", 
           frame->meta.w, frame->meta.h, frame->meta.timestamp_ns);
});

// 4. 启动采集
camera.start();

// 5. 运行一段时间
sleep(10);

// 6. 停止采集
camera.stop();

场景 2: 队列传递

std::queue<FramePtr> frame_queue;
std::mutex queue_mutex;
std::condition_variable queue_cv;

camera.setFrameCallback([&](FramePtr frame) {
    {
        std::lock_guard<std::mutex> lock(queue_mutex);
        frame_queue.push(frame);
    }
    queue_cv.notify_one();
});

// 消费线程
std::thread consumer([&]() {
    while (running) {
        FramePtr frame;
        {
            std::unique_lock<std::mutex> lock(queue_mutex);
            queue_cv.wait(lock, [&] { return !frame_queue.empty() || !running; });
            if (!running) break;
            frame = frame_queue.front();
            frame_queue.pop();
        }
        
        // 处理帧
        if (frame) {
            process_frame(frame);
        }
    }
});

场景 3: CPU 亲和性

CameraController::Config cfg;
// ... 配置 ...

CameraController camera(cfg);

// 绑定到 CPU 核心 1
camera.setThreadAffinity(1);

camera.setFrameCallback(...);
camera.start();

注意事项

  1. 必须先设置回调: setFrameCallback() 必须在 start() 之前调用
  2. 回调中不要阻塞: 建议使用队列传递帧数据到其他线程
  3. 不要手动归还缓冲区: Frame 析构时会自动调用 returnBuffer()
  4. DMA-BUF 连续性: 多平面格式通常物理内存连续,只需 plane 0 的 fd
  5. 停止前等待: 调用 stop() 前确保没有异步操作在执行
  6. 线程安全: 多线程使用时,使用队列和互斥锁保护共享数据
  7. 设备独占: CameraController 独占设备,不能同时打开同一个设备
  8. 格式支持: 不同摄像头支持的格式不同,需要查询设备能力
  9. 暂停功能: 使用 pause() 暂停采集,使用 stop() 停止采集
  10. 不可拷贝: CameraController 禁止拷贝和移动

相关文档


参考资料

主页

API 文档

DMA 模块

DRM 模块

NET 模块

V4L2 模块

V4L2Param 模块

RGA 模块

MPP 模块

Sys 模块

Mouse 模块

Utils 模块

Clone this wiki locally