Skip to content

V4L2_Frame

SweerItTer edited this page Feb 1, 2026 · 3 revisions

Frame API 文档

概述

Frame 是 utilsCore V4L2 模块的核心类,提供统一的帧数据接口,支持 MMAP 和 DMA-BUF 两种内存类型,自动管理缓冲区生命周期。

职责

  • 统一的帧数据接口
  • 支持 MMAP 和 DMA-BUF 两种内存类型
  • 自动管理缓冲区生命周期
  • 提供元数据信息(时间戳、尺寸、索引等)
  • 使用内存池优化分配性能

适用场景

  • V4L2 相机采集的帧数据封装
  • 多线程间传递帧数据
  • 零拷贝传输(配合 DMA-BUF)
  • 帧数据的序列化和反序列化

依赖关系

  • 依赖: sharedBufferState(共享缓冲区状态管理)
  • 被依赖: VisionPipeline, RecordPipeline, RgaProcessor 等上层模块

类分析

Frame 类

职责与用途

Frame 是统一帧数据接口,提供:

  • 单平面和多平面格式支持
  • MMAP 和 DMA-BUF 两种内存类型
  • RAII 自动生命周期管理
  • 元数据信息封装
  • 内存池优化分配性能

设计模式

  • RAII: 析构时自动归还缓冲区
  • 内存池: 使用 FixedSizePool 减少 new/delete 开销
  • 共享所有权: 使用 shared_ptr 管理缓冲区状态

FrameMeta 结构体

职责与用途

FrameMeta 存储帧的元数据信息。

结构体定义

struct FrameMeta {
    uint64_t frame_id = -1;      // 单调递增帧ID
    uint64_t timestamp_ns = -1;  // 时间戳(CLOCK_MONOTONIC)
    int index = -1;              // V4L2缓冲区索引
    uint32_t w = 0;              // 原始宽度
    uint32_t h = 0;              // 原始高度
};

字段说明

字段 类型 说明
frame_id uint64_t 单调递增的帧ID,用于标识帧序
timestamp_ns uint64_t 时间戳(纳秒),使用 CLOCK_MONOTONIC
index int V4L2 缓冲区索引,用于归还缓冲区
w uint32_t 图像宽度(像素)
h uint32_t 图像高度(像素)

MemoryType 枚举

枚举定义

enum class MemoryType {
    Unknown,  // 未知类型
    MMAP,     // MMAP 内存
    DMABUF    // DMA-BUF 内存
};

说明

  • Unknown: 未知类型,未初始化的 Frame
  • MMAP: MMAP 内存,通过 mmap 映射到用户空间
  • DMABUF: DMA-BUF 内存,直接使用 fd

公共 API 方法

构造函数

Frame() noexcept;
Frame(SharedBufferPtr s);                          // 单平面
Frame(std::vector<SharedBufferPtr> states);        // 多平面
~Frame();

参数说明:

  • s (输入): 单平面缓冲区状态
  • states (输入): 多平面缓冲区状态列表

返回值: 无

所有权归属:

  • Frame 持有 SharedBufferState 的所有权
  • 通过 shared_ptr 管理引用计数

注意事项:

  1. 默认构造函数创建空 Frame
  2. Frame 支持移动语义,但不支持拷贝(因为包含 std::function 成员)
  3. 使用内存池分配,提高性能(仅 Frame 类本身,派生类不会使用内存池)

operator new/delete - 内存池分配

static void* operator new(std::size_t size);
static void operator delete(void* p) noexcept;

参数说明:

  • size (输入): 要分配的大小
  • p (输入): 要释放的指针

返回值:

  • operator new: 返回分配的内存指针
  • operator delete: 无返回值

所有权归属:

  • 内存由 FixedSizePool 管理

注意事项:

  1. 重载了 operator new/delete 使用内存池
  2. 内存池大小: sizeof(Frame), 2048 个 block, 64 TLS cache, 32 page size
  3. 派生类不会使用内存池(size != sizeof(Frame) 时使用默认 new/delete)
  4. 不要手动调用 operator delete,由析构函数自动调用

type() - 获取内存类型

MemoryType type() const noexcept;

参数说明: 无

返回值:

  • MemoryType::MMAP: MMAP 内存
  • MemoryType::DMABUF: DMA-BUF 内存
  • MemoryType::Unknown: 未知类型

所有权归属: 只读访问


data() - 获取数据指针(MMAP 模式)

void* data(int planeIndex = -1) const;

参数说明:

  • planeIndex (输入): 平面索引,-1 表示第一个平面

返回值:

  • 成功: 返回数据指针
  • 失败: 返回 nullptr

所有权归属:

  • 返回的指针由 SharedBufferState 管理
  • 不要手动释放

注意事项:

  1. 只在 MemoryType::MMAP 模式下有效
  2. 多平面格式需要指定 planeIndex
  3. DMA-BUF 模式下应使用 dmabuf_fd()
  4. 使用前会二次检查 valid 标志
  5. 默认构造的 Frame(类型为 Unknown)调用此方法会返回错误并返回 nullptr

dmabuf_fd() - 获取 DMA-BUF fd

int dmabuf_fd(int planeIndex = -1) const;

参数说明:

  • planeIndex (输入): 平面索引,-1 表示第一个平面(默认值)

返回值:

  • 成功: 返回 DMA-BUF 文件描述符
  • 失败: 返回 -1

所有权归属:

  • 返回的 fd 由 SharedBufferState 管理
  • Frame 析构时不会关闭 fd

注意事项:

  1. 只在 MemoryType::DMABUF 模式下有效
  2. 多平面格式在 RK3568 上通常物理内存连续,只需 plane 0 的 fd
  3. fd 可以跨设备/进程共享
  4. 使用前会二次检查 valid 标志
  5. 如果类型不是 DMABUF,会输出错误消息并返回 -1

使用例程:

// 获取 DMA-BUF fd
int fd = frame->dmabuf_fd();
if (fd >= 0) {
    // 传递给其他硬件模块
    rga_process(fd, width, height);
    drm_display(fd, ...);
}

size() - 获取总大小

size_t size() const;

参数说明: 无

返回值: 缓冲区的总字节数

所有权归属: 只读访问

注意事项:

  • 多平面 Frame 返回所有平面的总大小
  • 单平面 Frame 返回单个平面的大小

sharedState() - 获取共享状态

SharedBufferPtr sharedState(int planeIndex = -1) const noexcept;

参数说明:

  • planeIndex (输入): 平面索引,-1 表示第一个平面

返回值: SharedBufferPtr 智能指针

所有权归属: Frame 持有引用计数,返回引用

注意事项:

  1. 返回的 shared_ptr 可以跨线程共享
  2. 使用前检查 valid 标志
  3. 不要手动管理 SharedBufferState 的生命周期
  4. 多平面格式需要指定 planeIndex
  5. planeIndex 越界时返回 nullptr

实现细节:

SharedBufferPtr sharedState(int planeIndex = -1) const noexcept {
    if (!mutiPlane_) {
        return state_;
    }
    if (planeIndex < 0 || planeIndex >= states_.size()) {
        fprintf(stderr, "Frame is mutiplane.\n");
        return nullptr;
    }
    auto s = states_[planeIndex];
    if (nullptr == s || false == s->valid) {
        fprintf(stderr, "Current Plane is invalid.\n");
        return nullptr;
    }
    return s;
}

index() - 获取缓冲区索引

int index() const { return meta.index; }

参数说明: 无

返回值: V4L2 缓冲区索引

所有权归属: 只读访问


timestamp() - 获取时间戳

uint64_t timestamp() const { return meta.timestamp_ns; }

参数说明: 无

返回值: 时间戳(纳秒)

所有权归属: 只读访问


setTimestamp() - 设置时间戳

void setTimestamp(uint64_t ts);

参数说明:

  • ts (输入): 时间戳(纳秒)

返回值: 无

所有权归属: 无


setReleaseCallback() - 设置释放回调

void setReleaseCallback(std::function<void(int)> bufReleasCallback);

参数说明:

  • bufReleasCallback (输入): 释放回调函数

返回值: 无

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

注意事项:

  1. 通常由 CameraController 自动设置
  2. Frame 析构时会自动调用此回调
  3. 用于归还缓冲区到 V4L2 队列

所有权管理

析构函数实现

Frame::~Frame() {
    if (bufReleasCallback_ && state_ != nullptr) {
        if (meta.index >= 0)
            bufReleasCallback_(meta.index);  // 自动归还缓冲区
    }
}

所有权规则

创建流:
CameraController → FramePtr(std::move(sharedState)) → 回调接收者持有

使用流:
回调接收者持有 FramePtr → 访问数据 → 使用完毕后 Frame 析构

归还流:
Frame 析构 → bufReleasCallback_(meta.index) → CameraController::returnBuffer(index)

线程安全

  • 引用计数: shared_ptr 本身是线程安全的
  • valid 标志: atomic<bool> 保证跨线程可见性
  • 析构回调: 在析构线程中执行,确保缓冲区归还
  • 二次检查: data() 和 dmabuf_fd() 会二次检查 valid 标志

内存池优化

FixedSizePool 配置

static FixedSizePool s_pool_(sizeof(Frame), 2048, 64, 32);
  • Block 大小: sizeof(Frame)
  • 总 block 数: 2048
  • TLS 缓存: 64
  • Page 大小: 32

性能优势

  • 减少 new/delete 系统调用
  • 线程本地缓存(TLS)减少锁竞争
  • 预分配内存避免动态分配开销

典型使用场景

场景 1: MMAP 模式使用

camera.setFrameCallback([](FramePtr frame) {
    if (frame->type() == Frame::MemoryType::MMAP) {
        void* data = frame->data();
        size_t size = frame->size();
        
        // 访问数据
        memcpy(process_buffer, data, size);
    }
});

场景 2: DMA-BUF 模式使用

camera.setFrameCallback([](FramePtr frame) {
    if (frame->type() == Frame::MemoryType::DMABUF) {
        int fd = frame->dmabuf_fd();
        uint32_t w = frame->meta.w;
        uint32_t h = frame->meta.h;
        
        // 零拷贝传递给 RGA
        rga_process(fd, w, h);
        
        // Frame 析构时自动归还缓冲区
    }
});

场景 3: 多线程传递

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);  // FramePtr 拷贝到队列
});

// 消费线程
FramePtr frame = frame_queue.front();
frame_queue.pop();

// 使用 frame
auto state = frame->sharedState();
if (state && state->valid) {
    // 处理数据
}

// Frame 析构时自动归还缓冲区

场景 4: 多平面格式

camera.setFrameCallback([](FramePtr frame) {
    if (frame->type() == Frame::MemoryType::DMABUF) {
        // NV12 格式通常只需要 plane 0 的 fd
        int fd = frame->dmabuf_fd(0);
        
        // Y 平面
        uint8_t* y_data = ...;  // 从 fd 映射或直接使用
        
        // UV 平面(通过偏移量计算)
        uint8_t* uv_data = y_data + (frame->meta.w * frame->meta.h);
    }
});

注意事项

  1. 内存池: Frame 使用 FixedSizePool 分配,不要手动 delete
  2. 多平面访问: 访问多平面格式时,planeIndex 必须有效
  3. valid 检查: 访问数据前检查 sharedState()->valid 标志
  4. 拷贝和移动: Frame 未实现拷贝/移动构造和对应运算符重载, 但 std::function<void(int)> 为不可拷贝对象, 因此建议使用shared_ptr管理
  5. 自动归还: Frame 析构时会自动调用释放回调
  6. 线程安全: 多个线程不能同时访问同一个 Frame 的数据
  7. 二次检查: data() 和 dmabuf_fd() 会二次检查 valid 标志
  8. DMA-BUF 连续性: 多平面格式在 RK3568 上通常物理内存连续
  9. 派生类: 派生类不会使用内存池(size != sizeof(Frame))

相关文档


参考资料

主页

API 文档

DMA 模块

DRM 模块

NET 模块

V4L2 模块

V4L2Param 模块

RGA 模块

MPP 模块

Sys 模块

Mouse 模块

Utils 模块

Clone this wiki locally