在Linux内核的Media Controller框架中,V4L2设备作为实体(entity)的注册过程涉及以下步骤:
media_device
实例,并与V4L2设备(如v4l2_device
)关联。例如:struct media_device *mdev = devm_kzalloc(dev, sizeof(*mdev), GFP_KERNEL);
media_device_init(mdev);
mdev->dev = dev; // 关联到父设备
v4l2_dev->mdev = mdev; // 将media_device绑定到V4L2设备
v4l2_subdev
表示,并初始化其media_entity
:struct v4l2_subdev *sd = &sensor->sd;
v4l2_subdev_init(sd, &sensor_ops); // 初始化子设备
sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; // 设置实体类型
media_entity_pads_init(&sd->entity, num_pads, pads); // 初始化pads
media_device
中:int ret = media_device_register_entity(mdev, &sd->entity);
media_create_pad_link()
在源和目标的pad之间创建链接:media_create_pad_link(&sensor_sd->entity, SENSOR_PAD_SRC,
&isp_sd->entity, ISP_PAD_SINK, 0);
media_device
,用户空间可通过/dev/mediaX
访问:int ret = media_device_register(mdev);
media_entity
表示,其类型由function
字段标识(如MEDIA_ENT_F_CAM_SENSOR
)。media_entity
会自动加入media_device
,无需手动添加。media-ctl
工具查看拓扑结构,例如:media-ctl -p -d /dev/media0
// 初始化media_device
mdev = devm_kzalloc(dev, sizeof(*mdev), GFP_KERNEL);
media_device_init(mdev);
mdev->dev = dev;
strscpy(mdev->model, "My Camera", sizeof(mdev->model));
// 初始化传感器子设备
v4l2_subdev_init(&sensor_sd, &sensor_ops);
sensor_sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
sensor_pads[0].flags = MEDIA_PAD_FL_SOURCE;
media_entity_pads_init(&sensor_sd.entity, 1, sensor_pads);
media_device_register_entity(mdev, &sensor_sd.entity);
// 注册media_device
media_device_register(mdev);
通过上述步骤,V4L2子设备被注册为Media Controller框架中的实体,用户空间可管理复杂的数据流拓扑。
在 Linux 的 Media Controller 框架中,构建一个 **Pipeline(数据流管道)**需要明确设备中各个硬件组件(Entity)的拓扑关系,并通过 Pads 和 Links 将它们连接起来。以下是完整的构建流程和关键步骤:
在构建 Pipeline 前,需明确以下核心概念:
每个硬件组件在驱动中注册为一个 media_entity
,例如:
MEDIA_ENT_F_CAM_SENSOR
MEDIA_ENT_F_PROC_VIDEO_ISP
MEDIA_ENT_F_IO_V4L
通过 media-ctl
工具查看设备拓扑:
media-ctl -p -d /dev/media0
输出示例:
Entity 1: Camera Sensor (type 0x0000, function CAM_SENSOR)
Pad 0: Source [fmt:SRGGB10/1920x1080]
Entity 2: ISP (type 0x0000, function PROC_VIDEO_ISP)
Pad 0: Sink [fmt:SRGGB10/1920x1080]
Pad 1: Source [fmt:YUYV/1920x1080]
Entity 3: DMA Engine (type 0x0000, function IO_V4L)
Pad 0: Sink [fmt:YUYV/1920x1080]
通过 media-ctl
或驱动代码创建连接,确保数据流方向正确:
# 连接传感器(Entity 1)的 Source Pad 0 → ISP(Entity 2)的 Sink Pad 0
media-ctl -d /dev/media0 -l "'Camera Sensor':0 -> 'ISP':0 [1]"
# 连接 ISP(Entity 2)的 Source Pad 1 → DMA Engine(Entity 3)的 Sink Pad 0
media-ctl -d /dev/media0 -l "'ISP':1 -> 'DMA Engine':0 [1]"
[1]
表示启用 Link,[0]
表示禁用。每个 Pad 需要设置一致的数据格式(如分辨率、像素格式)。例如,在用户空间通过 media-ctl
设置:
# 设置传感器(Entity 1)的 Source Pad 0 格式为 SRGGB10_1920x1080
media-ctl -d /dev/media0 -V "'Camera Sensor':0 [fmt:SRGGB10/1920x1080]"
# 设置 ISP(Entity 2)的 Sink Pad 0 格式为 SRGGB10_1920x1080(与传感器匹配)
media-ctl -d /dev/media0 -V "'ISP':0 [fmt:SRGGB10/1920x1080]"
# 设置 ISP(Entity 2)的 Source Pad 1 格式为 YUYV_1920x1080
media-ctl -d /dev/media0 -V "'ISP':1 [fmt:YUYV/1920x1080]"
# 设置 DMA Engine(Entity 3)的 Sink Pad 0 格式为 YUYV_1920x1080(与 ISP 匹配)
media-ctl -d /dev/media0 -V "'DMA Engine':0 [fmt:YUYV/1920x1080]"
通过 V4L2 API 启动数据流:
# 使用 v4l2-ctl 捕获数据(假设 DMA Engine 对应 /dev/video0)
v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat=YUYV
v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=10 --stream-to=output.raw
驱动需要实现以下功能以支持 Pipeline 构建:
在驱动中初始化 media_entity
和 media_pad
:
// 示例:摄像头传感器驱动
struct v4l2_subdev *sensor_sd = &sensor->sd;
// 初始化子设备
v4l2_subdev_init(sensor_sd, &sensor_ops);
// 设置 Entity 类型
sensor_sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
// 初始化 Pads(假设传感器只有一个 Source Pad)
struct media_pad *sensor_pads = &sensor->pads[0];
sensor_pads[0].flags = MEDIA_PAD_FL_SOURCE; // Source Pad
media_entity_pads_init(&sensor_sd->entity, 1, sensor_pads);
// 注册到 Media Controller
media_device_register_entity(mdev, &sensor_sd->entity);
在驱动中动态创建 Links:
// 连接传感器(Source Pad 0)→ ISP(Sink Pad 0)
media_create_pad_link(
&sensor_sd->entity, 0, // Source Pad 0
&isp_sd->entity, 0, // Sink Pad 0
MEDIA_LNK_FL_ENABLED // 启用 Link
);
通过以下方法验证 Pipeline 是否正常工作:
media-ctl -p -d /dev/media0
media-ctl -V -d /dev/media0
v4l2-ctl --stream-mmap --stream-to=frame.raw
```## 标题
media-ctl
命令中的 [1]
标志。/dev/mediaX
和 /dev/videoX
。构建 Media Controller Pipeline 的核心步骤为:
media-ctl
)。通过这一流程,复杂的硬件拓扑(如摄像头 → ISP → 编码器 → 显示)可以在用户空间和内核中高效管理。
在 Linux 的 Media Controller 框架中,启动一个 Pipeline 的数据传输需要结合用户空间的 V4L2 API 和内核驱动的协作。以下是完整的流程和步骤:
在开始传输前,需确保以下条件已满足:
Pipeline 已正确构建:
用户空间与内核的通信准备:
/dev/video0
)已打开。通过 V4L2 API 启动数据传输的核心步骤如下:
int fd = open("/dev/video0", O_RDWR);
if (fd < 0) {
perror("Failed to open device");
exit(EXIT_FAILURE);
}
设置视频流的格式(需与 Pipeline 中 DMA Sink Pad 的格式一致):
struct v4l2_format fmt = {0};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 1920;
fmt.fmt.pix.height = 1080;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // 与 DMA Sink Pad 的格式一致
fmt.fmt.pix.field = V4L2_FIELD_NONE;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
perror("Failed to set format");
exit(EXIT_FAILURE);
}
请求内核分配一定数量的 DMA 缓冲区:
struct v4l2_requestbuffers req = {0};
req.count = 4; // 缓冲区数量
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP; // 使用内存映射模式
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
perror("Failed to request buffers");
exit(EXIT_FAILURE);
}
将内核分配的缓冲区映射到用户空间:
struct buffer *buffers = calloc(req.count, sizeof(*buffers));
for (int i = 0; i < req.count; i++) {
struct v4l2_buffer buf = {0};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
perror("Failed to query buffer");
exit(EXIT_FAILURE);
}
// 映射到用户空间
buffers[i].length = buf.length;
buffers[i].start = mmap(NULL, buf.length,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, buf.m.offset);
}
将缓冲区放入内核的输入队列,等待填充数据:
for (int i = 0; i < req.count; i++) {
struct v4l2_buffer buf = {0};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
perror("Failed to queue buffer");
exit(EXIT_FAILURE);
}
}
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
perror("Failed to start streaming");
exit(EXIT_FAILURE);
}
循环从内核队列中取出已填充数据的缓冲区:
while (1) {
struct v4l2_buffer buf = {0};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
perror("Failed to dequeue buffer");
break;
}
// 处理数据(例如保存到文件)
process_image(buffers[buf.index].start, buf.bytesused);
// 重新将缓冲区入队
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
perror("Failed to re-queue buffer");
break;
}
}
内核驱动需要实现以下关键回调函数以支持数据传输:
当用户空间调用 VIDIOC_STREAMON
时,驱动需启动硬件的数据流:
static int my_driver_start_streaming(struct vb2_queue *vq, unsigned int count) {
struct my_device *dev = vb2_get_drv_priv(vq);
// 1. 配置硬件寄存器,启动传感器、ISP、DMA 等
start_hardware(dev);
// 2. 通知下游 Entities(如 ISP)开始工作
v4l2_subdev_call(dev->isp_sd, video, s_stream, 1);
return 0;
}
当用户空间调用 VIDIOC_STREAMOFF
时,驱动需停止硬件:
static void my_driver_stop_streaming(struct vb2_queue *vq) {
struct my_device *dev = vb2_get_drv_priv(vq);
// 1. 停止传感器、ISP、DMA 等
stop_hardware(dev);
// 2. 通知下游 Entities 停止工作
v4l2_subdev_call(dev->isp_sd, video, s_stream, 0);
}
驱动需要将硬件填充的 DMA 缓冲区返回给用户空间:
static void my_driver_dma_callback(struct my_device *dev) {
struct vb2_buffer *vb = dev->current_vb;
// 标记缓冲区已填充数据
vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
dev->current_vb = NULL;
}
# 查看所有 Entities 和 Links
media-ctl -p -d /dev/media0
# 查看格式协商结果
media-ctl -V -d /dev/media0
使用 v4l2-ctl
捕获数据:
v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=10 --stream-to=output.raw
media-ctl -l
)。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 系统中高效运行。