Linux Media 子体系 V4l2

守听  论坛元老 | 2025-2-13 08:00:35 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1026|帖子 1026|积分 3078

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
一 创建 V4l2 的 entity

在Linux内核的Media Controller框架中,V4L2装备作为实体(entity)的注册过程涉及以下步骤:
1. 初始化Media Controller结构



  • 驱动首先创建一个media_device实例,并与V4L2装备(如v4l2_device)关联。比方:
    1. struct media_device *mdev = devm_kzalloc(dev, sizeof(*mdev), GFP_KERNEL);
    2. media_device_init(mdev);
    3. mdev->dev = dev; // 关联到父设备
    4. v4l2_dev->mdev = mdev; // 将media_device绑定到V4L2设备
    复制代码
2. 创建V4L2子装备(Subdev)并注册为Entity



  • 每个硬件组件(如传感器、ISP)通过v4l2_subdev体现,并初始化其media_entity:
    1. struct v4l2_subdev *sd = &sensor->sd;
    2. v4l2_subdev_init(sd, &sensor_ops); // 初始化子设备
    3. sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; // 设置实体类型
    4. media_entity_pads_init(&sd->entity, num_pads, pads); // 初始化pads
    复制代码
3. 注册Entities到Media Controller



  • 将子装备的实体添加到media_device中:
    1. int ret = media_device_register_entity(mdev, &sd->entity);
    复制代码
4. 创建实体间的连接(Links)



  • 使用media_create_pad_link()在源和目标的pad之间创建链接:
    1. media_create_pad_link(&sensor_sd->entity, SENSOR_PAD_SRC,
    2.                      &isp_sd->entity, ISP_PAD_SINK, 0);
    复制代码
5. 注册Media Controller到内核



  • 最后,注册整个media_device,用户空间可通过/dev/mediaX访问:
    1. int ret = media_device_register(mdev);
    复制代码
关键点



  • Entity范例:V4L2子装备(如传感器、ISP)通过media_entity体现,其范例由function字段标识(如MEDIA_ENT_F_CAM_SENSOR)。
  • 自动关联:当V4L2子装备注册时,其media_entity会自动参加media_device,无需手动添加。
  • 用户空间交互:用户可通过media-ctl工具检察拓扑结构,比方:
    1. media-ctl -p -d /dev/media0
    复制代码
示例驱动代码片段

  1. // 初始化media_device
  2. mdev = devm_kzalloc(dev, sizeof(*mdev), GFP_KERNEL);
  3. media_device_init(mdev);
  4. mdev->dev = dev;
  5. strscpy(mdev->model, "My Camera", sizeof(mdev->model));
  6. // 初始化传感器子设备
  7. v4l2_subdev_init(&sensor_sd, &sensor_ops);
  8. sensor_sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
  9. sensor_pads[0].flags = MEDIA_PAD_FL_SOURCE;
  10. media_entity_pads_init(&sensor_sd.entity, 1, sensor_pads);
  11. media_device_register_entity(mdev, &sensor_sd.entity);
  12. // 注册media_device
  13. media_device_register(mdev);
复制代码
通过上述步骤,V4L2子装备被注册为Media Controller框架中的实体,用户空间可管理复杂的数据流拓扑。
二 构建 pipeline

在 Linux 的 Media Controller 框架中,构建一个 **Pipeline(数据流管道)**需要明白装备中各个硬件组件(Entity)的拓扑关系,并通过 PadsLinks 将它们连接起来。以下是完整的构建流程和关键步骤:
1. 理解基本概念

在构建 Pipeline 前,需明白以下核心概念:


  • Entity(实体):体现硬件组件(如摄像头传感器、ISP、DMA引擎等)。
  • Pad(端点):Entity 的输入/输出端点,分为 Source Pad(源)Sink Pad(汇)
  • Link(链路):连接两个 Entity 的 Pad,定义数据流方向(如传感器 → ISP → DMA)。
  • Format(格式):数据格式(如分辨率、像素格式)需要在连接的 Pad 之间协商。
2. 构建 Pipeline 的步骤

(1) 识别装备中的 Entities

每个硬件组件在驱动中注册为一个 media_entity,比方:


  • 传感器:MEDIA_ENT_F_CAM_SENSOR
  • ISP(图像处理器):MEDIA_ENT_F_PROC_VIDEO_ISP
  • DMA 引擎:MEDIA_ENT_F_IO_V4L
通过 media-ctl 工具检察装备拓扑:
  1. media-ctl -p -d /dev/media0
复制代码
输出示例:
  1. Entity 1: Camera Sensor (type 0x0000, function CAM_SENSOR)
  2.   Pad 0: Source [fmt:SRGGB10/1920x1080]
  3. Entity 2: ISP (type 0x0000, function PROC_VIDEO_ISP)
  4.   Pad 0: Sink [fmt:SRGGB10/1920x1080]
  5.   Pad 1: Source [fmt:YUYV/1920x1080]
  6. Entity 3: DMA Engine (type 0x0000, function IO_V4L)
  7.   Pad 0: Sink [fmt:YUYV/1920x1080]
复制代码
(2) 创建 Links(连接 Pads)

通过 media-ctl 或驱动代码创建连接,确保数据流方向准确:
  1. # 连接传感器(Entity 1)的 Source Pad 0 → ISP(Entity 2)的 Sink Pad 0
  2. media-ctl -d /dev/media0 -l "'Camera Sensor':0 -> 'ISP':0 [1]"
  3. # 连接 ISP(Entity 2)的 Source Pad 1 → DMA Engine(Entity 3)的 Sink Pad 0
  4. media-ctl -d /dev/media0 -l "'ISP':1 -> 'DMA Engine':0 [1]"
复制代码


  • [1] 体现启用 Link,[0] 体现禁用。
(3) 协商格式(Format Negotiation)

每个 Pad 需要设置一致的数据格式(如分辨率、像素格式)。比方,在用户空间通过 media-ctl 设置:
  1. # 设置传感器(Entity 1)的 Source Pad 0 格式为 SRGGB10_1920x1080
  2. media-ctl -d /dev/media0 -V "'Camera Sensor':0 [fmt:SRGGB10/1920x1080]"
  3. # 设置 ISP(Entity 2)的 Sink Pad 0 格式为 SRGGB10_1920x1080(与传感器匹配)
  4. media-ctl -d /dev/media0 -V "'ISP':0 [fmt:SRGGB10/1920x1080]"
  5. # 设置 ISP(Entity 2)的 Source Pad 1 格式为 YUYV_1920x1080
  6. media-ctl -d /dev/media0 -V "'ISP':1 [fmt:YUYV/1920x1080]"
  7. # 设置 DMA Engine(Entity 3)的 Sink Pad 0 格式为 YUYV_1920x1080(与 ISP 匹配)
  8. media-ctl -d /dev/media0 -V "'DMA Engine':0 [fmt:YUYV/1920x1080]"
复制代码
(4) 激活 Pipeline

通过 V4L2 API 启动数据流:
  1. # 使用 v4l2-ctl 捕获数据(假设 DMA Engine 对应 /dev/video0)
  2. v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat=YUYV
  3. v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=10 --stream-to=output.raw
复制代码
3. 内核驱动中的关键操作

驱动需要实现以下功能以支持 Pipeline 构建:
(1) 注册 Entities 和 Pads

在驱动中初始化 media_entity 和 media_pad:
  1. // 示例:摄像头传感器驱动
  2. struct v4l2_subdev *sensor_sd = &sensor->sd;
  3. // 初始化子设备
  4. v4l2_subdev_init(sensor_sd, &sensor_ops);
  5. // 设置 Entity 类型
  6. sensor_sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
  7. // 初始化 Pads(假设传感器只有一个 Source Pad)
  8. struct media_pad *sensor_pads = &sensor->pads[0];
  9. sensor_pads[0].flags = MEDIA_PAD_FL_SOURCE; // Source Pad
  10. media_entity_pads_init(&sensor_sd->entity, 1, sensor_pads);
  11. // 注册到 Media Controller
  12. media_device_register_entity(mdev, &sensor_sd->entity);
复制代码
(2) 创建 Links

在驱动中动态创建 Links:
  1. // 连接传感器(Source Pad 0)→ ISP(Sink Pad 0)
  2. media_create_pad_link(
  3.     &sensor_sd->entity, 0, // Source Pad 0
  4.     &isp_sd->entity, 0,    // Sink Pad 0
  5.     MEDIA_LNK_FL_ENABLED   // 启用 Link
  6. );
复制代码
4. 验证 Pipeline

通过以下方法验证 Pipeline 是否正常工作:

  • 检察拓扑
    1. media-ctl -p -d /dev/media0
    复制代码
  • 查抄格式协商
    1. media-ctl -V -d /dev/media0
    复制代码
  • 捕获数据
    1. v4l2-ctl --stream-mmap --stream-to=frame.raw
    2. ```## 标题
    复制代码
5. 常见标题



  • Link 未启用:查抄 media-ctl 命令中的 [1] 标志。
  • 格式不匹配:确保相邻 Pads 的格式(如像素格式、分辨率)一致。
  • 权限标题:确保用户有权限访问 /dev/mediaX 和 /dev/videoX。
总结

构建 Media Controller Pipeline 的核心步骤为:

  • 识别 Entities 和 Pads(通过驱动或 media-ctl)。
  • 创建 Links 定义数据流方向。
  • 协商 Formats 确保相邻 Pads 格式一致。
  • 激活数据流(通过 V4L2 API)。
通过这一流程,复杂的硬件拓扑(如摄像头 → ISP → 编码器 → 显示)可以在用户空间和内核中高效管理。
三 pipeline 传输数据

在 Linux 的 Media Controller 框架中,启动一个 Pipeline 的数据传输需要结合用户空间的 V4L2 API 和内核驱动的协作。以下是完整的流程和步骤:
1. Pipeline 数据传输的启动条件

在开始传输前,需确保以下条件已满意:

  • Pipeline 已准确构建

    • Entities(实体,如传感器、ISP、DMA)已注册到 Media Controller。
    • Pads(端点)之间的 Links(链路)已创建并启用。
    • 所有相邻 Pads 的格式(分辨率、像素格式)已协商一致。

  • 用户空间与内核的通信准备

    • V4L2 装备节点(如 /dev/video0)已打开。
    • 内存缓冲区(DMA Buffer)已分配并映射到用户空间。

2. 用户空间启动 Pipeline 的步骤

通过 V4L2 API 启动数据传输的核心步骤如下:
(1) 打开 V4L2 装备节点

  1. int fd = open("/dev/video0", O_RDWR);
  2. if (fd < 0) {
  3.     perror("Failed to open device");
  4.     exit(EXIT_FAILURE);
  5. }
复制代码
(2) 设置数据格式

设置视频流的格式(需与 Pipeline 中 DMA Sink Pad 的格式一致):
  1. struct v4l2_format fmt = {0};
  2. fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  3. fmt.fmt.pix.width = 1920;
  4. fmt.fmt.pix.height = 1080;
  5. fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // 与 DMA Sink Pad 的格式一致
  6. fmt.fmt.pix.field = V4L2_FIELD_NONE;
  7. if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
  8.     perror("Failed to set format");
  9.     exit(EXIT_FAILURE);
  10. }
复制代码
(3) 申请缓冲区(Request Buffers)

哀求内核分配一定命量的 DMA 缓冲区:
  1. struct v4l2_requestbuffers req = {0};
  2. req.count = 4;                // 缓冲区数量
  3. req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  4. req.memory = V4L2_MEMORY_MMAP; // 使用内存映射模式
  5. if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
  6.     perror("Failed to request buffers");
  7.     exit(EXIT_FAILURE);
  8. }
复制代码
(4) 内存映射(MMAP)缓冲区

将内核分配的缓冲区映射到用户空间:
  1. struct buffer *buffers = calloc(req.count, sizeof(*buffers));
  2. for (int i = 0; i < req.count; i++) {
  3.     struct v4l2_buffer buf = {0};
  4.     buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  5.     buf.memory = V4L2_MEMORY_MMAP;
  6.     buf.index = i;
  7.     if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
  8.         perror("Failed to query buffer");
  9.         exit(EXIT_FAILURE);
  10.     }
  11.     // 映射到用户空间
  12.     buffers[i].length = buf.length;
  13.     buffers[i].start = mmap(NULL, buf.length,
  14.                            PROT_READ | PROT_WRITE,
  15.                            MAP_SHARED, fd, buf.m.offset);
  16. }
复制代码
(5) 将缓冲区入队(Queue Buffers)

将缓冲区放入内核的输入队列,等候填凑数据:
  1. for (int i = 0; i < req.count; i++) {
  2.     struct v4l2_buffer buf = {0};
  3.     buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  4.     buf.memory = V4L2_MEMORY_MMAP;
  5.     buf.index = i;
  6.     if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
  7.         perror("Failed to queue buffer");
  8.         exit(EXIT_FAILURE);
  9.     }
  10. }
复制代码
(6) 启动传播输(Start Streaming)

  1. enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  2. if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
  3.     perror("Failed to start streaming");
  4.     exit(EXIT_FAILURE);
  5. }
复制代码
(7) 捕获数据(Dequeue Buffers)

循环从内核队列中取出已填凑数据的缓冲区:
  1. while (1) {
  2.     struct v4l2_buffer buf = {0};
  3.     buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  4.     buf.memory = V4L2_MEMORY_MMAP;
  5.     if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
  6.         perror("Failed to dequeue buffer");
  7.         break;
  8.     }
  9.     // 处理数据(例如保存到文件)
  10.     process_image(buffers[buf.index].start, buf.bytesused);
  11.     // 重新将缓冲区入队
  12.     if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
  13.         perror("Failed to re-queue buffer");
  14.         break;
  15.     }
  16. }
复制代码
3. 内核驱动的职责

内核驱动需要实现以下关键回调函数以支持数据传输:
(1) 启动流(Start Streaming)

当用户空间调用 VIDIOC_STREAMON 时,驱动需启动硬件的数据流:
  1. static int my_driver_start_streaming(struct vb2_queue *vq, unsigned int count) {
  2.     struct my_device *dev = vb2_get_drv_priv(vq);
  3.     // 1. 配置硬件寄存器,启动传感器、ISP、DMA 等
  4.     start_hardware(dev);
  5.     // 2. 通知下游 Entities(如 ISP)开始工作
  6.     v4l2_subdev_call(dev->isp_sd, video, s_stream, 1);
  7.     return 0;
  8. }
复制代码
(2) 制止流(Stop Streaming)

当用户空间调用 VIDIOC_STREAMOFF 时,驱动需制止硬件:
  1. static void my_driver_stop_streaming(struct vb2_queue *vq) {
  2.     struct my_device *dev = vb2_get_drv_priv(vq);
  3.     // 1. 停止传感器、ISP、DMA 等
  4.     stop_hardware(dev);
  5.     // 2. 通知下游 Entities 停止工作
  6.     v4l2_subdev_call(dev->isp_sd, video, s_stream, 0);
  7. }
复制代码
(3) 缓冲区处理

驱动需要将硬件填充的 DMA 缓冲区返回给用户空间:
  1. static void my_driver_dma_callback(struct my_device *dev) {
  2.     struct vb2_buffer *vb = dev->current_vb;
  3.     // 标记缓冲区已填充数据
  4.     vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
  5.     dev->current_vb = NULL;
  6. }
复制代码

4. 调试与验证

(1) 查抄 Pipeline 状态

  1. # 检察所有 Entities 和 Linksmedia-ctl -p -d /dev/media0
  2. # 检察格式协商结果media-ctl -V -d /dev/media0
复制代码
(2) 验证数据传输

使用 v4l2-ctl 捕获数据:
  1. v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=10 --stream-to=output.raw
复制代码
(3) 常见标题



  • 无数据流

    • 查抄 Links 是否启用(media-ctl -l)。
    • 确保所有 Pads 的格式一致。

  • 权限标题
    1. sudo chmod 666 /dev/media0 /dev/video0
    复制代码
  • 驱动未实现回调

    • 确认 vb2_ops 中的 start_streaming 和 stop_streaming 已注册。

总结

启动 Pipeline 数据传输的完整流程:

  • 用户空间

    • 打开装备 → 设置格式 → 申请缓冲区 → 启动流 → 循环捕获数据。

  • 内核驱动

    • 实现 start_streaming 和 stop_streaming 回调,协调硬件和卑鄙 Entities。

  • 调试工具

    • 使用 media-ctl 和 v4l2-ctl 验证 Pipeline 状态和数据流。

通过这一流程,复杂硬件(如摄像头 → ISP → 编码器)的及时数据流可以在 Linux 体系中高效运行。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

守听

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表