总结

数据采集流程

  • 设置好长宽之后,摄像头可能不支持,需要检查长宽参数是否被修改。
  • app 根据自身处理能力的快慢,指定申请的 buffer 数量。
  • 一般使用链表的形式来组织 buffer,app 从 out 链表中拿数据,拿完之后,放入 in 链表;驱动从 in 链表中拿到 buffer,然后填充数据,再放到 out 链表中去。

app 开发

关键结构体:

  • v4l2_fmtdesc format enumeration
  • v4l2_format stream data format

ioctl 中常用的 id 是:

  • VIDIOC_QUERYCTRL 查找参数
  • VIDIOC_G_CTRL 获取当前参数
  • VIDIOC_S_CTRL 设置当前参数

对于 VideoControl interface 内部,有多个 unit。 unit 自身只能多个输入,单个输出。 unit之间,只能单个 unit 输出给多个 unit.

VideoControl interface 内部有 CT(camera terminal), IT(input terminal), OT(out terminal), SU(selector unit), PU(processing unit), EU(encoding unit), XU(extension unit)

VideoStreaming interface 是 USB 相关

如果自己做摄像头,想要增加额外功能,那就需要用到 XU,并且还需要添加 mapping,指定具体哪些位是哪些功能。

app 帧细节

格式枚举(yuv 还是其他),用 VIDIOC_ENUM_FMT; 帧枚举(分辨率之类)用 VIDIOC_ENUM_FRAMESIZES, 都需要让相应的 index 增加来通过 ioctl 枚举。

app 获取数据

  • 检查设备能力
    struct v4l2_capability cap;
    memset(&cap, 0, sizeof(struct v4l2_capability));
    if (0 == ioctl(fd, VIDIOC_QUERYCAP, &cap))
    {        
        if((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) {
  • 枚举格式和帧VIDIOC_ENUM_FMT VIDIOC_ENUM_FRAMESIZES
  • 设置格式
    struct v4l2_format fmt;
    memset(&fmt, 0, sizeof(struct v4l2_format));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = 1024;
    fmt.fmt.pix.height = 768;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
    fmt.fmt.pix.field = V4L2_FIELD_ANY;
    if (0 == ioctl(fd, VIDIOC_S_FMT, &fmt))
  • 申请buffer 并 mmap
    struct v4l2_requestbuffers rb;
    memset(&rb, 0, sizeof(struct v4l2_requestbuffers));
    rb.count = 32;
    rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    rb.memory = V4L2_MEMORY_MMAP;
    if (0 == ioctl(fd, VIDIOC_REQBUFS, &rb))
    {
        /* 申请成功后, mmap这些buffer */
        buf_cnt = rb.count;
        for(i = 0; i < rb.count; i++) {
            struct v4l2_buffer buf;
            memset(&buf, 0, sizeof(struct v4l2_buffer));
            buf.index = i;
            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            buf.memory = V4L2_MEMORY_MMAP;
            if (0 == ioctl(fd, VIDIOC_QUERYBUF, &buf))
            {
                /* mmap */
                bufs[i] = mmap(0 /* start anywhere */ ,
                                  buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd,
                                  buf.m.offset);
                if(bufs[i] == MAP_FAILED) {
                    perror("Unable to map buffer");
                    return -1;
                }
            }
  • 所有buffer放入"空闲链表"
    for(i = 0; i < buf_cnt; ++i) {
        struct v4l2_buffer buf;
        memset(&buf, 0, sizeof(struct v4l2_buffer));
        buf.index = i;
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        if (0 != ioctl(fd, VIDIOC_QBUF, &buf))
  • 启动摄像头 if (0 != ioctl(fd, VIDIOC_STREAMON, &type))
  • 使用 poll 从 buffer 获取数据,并写入文件,然后再放回队列
    while (1)
    {
        /* poll */
        memset(fds, 0, sizeof(fds));
        fds[0].fd = fd;
        fds[0].events = POLLIN;
        if (1 == poll(fds, 1, -1))
        {
            /* 把buffer取出队列 */
            struct v4l2_buffer buf;
            memset(&buf, 0, sizeof(struct v4l2_buffer));
            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            buf.memory = V4L2_MEMORY_MMAP;
            if (0 != ioctl(fd, VIDIOC_DQBUF, &buf))
            {
                perror("Unable to dequeue buffer");
                return -1;
            }
            /* 把buffer的数据存为文件 */
            sprintf(filename, "video_raw_data_%04d.jpg", file_cnt++);
            int fd_file = open(filename, O_RDWR | O_CREAT, 0666);
            if (fd_file < 0)
            {
                printf("can not create file : %s\n", filename);
            }
            printf("capture to %s\n", filename);
            write(fd_file, bufs[buf.index], buf.bytesused);
            close(fd_file);
            /* 把buffer放入队列 */
            if (0 != ioctl(fd, VIDIOC_QBUF, &buf))
            {
                perror("Unable to queue buffer");
                return -1;
            }
        }
    }
  • 关闭摄像头 if (0 != ioctl(fd, VIDIOC_STREAMOFF, &type))

app brightness

主线程获取图像数据,调整亮度另外开一个线程。

先用 v4l2_queryctrl V4L2_CID_BRIGHTNESS 获取 亮度范围,然后再用 VIDIOC_G_CTRL 获取当前亮度。 再用 getchar 获取按键,然后调整当前亮度数值,并用 VIDIOC_S_CTRL 来设置亮度。

v4l2 驱动框架

v4l2 接口,已经在 v4l2-dev.h 中定义好了,在 v4l2-dev.c 实现好了。

重要的结构体 struct video_device 以及其中的 const struct v4l2_file_operations *fops;

具体可以参考 drivers\media\usb\airspy\airspy.c 中的实现。

  • s->vdev = airspy_template; 赋值 video_device
  • video_set_drvdata(&s->vdev, s); 把 s 赋值给 video_device 中的 device 里面的 driver_data
  • 注册 v4l2_device
    s->v4l2_dev.release = airspy_video_release;
    ret = v4l2_device_register(&intf->dev, &s->v4l2_dev);
  • s->vdev.v4l2_dev = &s->v4l2_dev; 把 v4l2_device 赋值给 video_device
  • ret = video_register_device(&s->vdev, VFL_TYPE_SDR, -1); 注册 video_device
    • vdev->cdev->ops = &v4l2_fops; 把 file_operations 赋值给 video_device

v4l2 ioctl 调用流程分析

app 调用 ioctl, 就是 drivers\media\v4l2-core\v4l2-dev.c 里面的 v4l2_fops->v4l2_ioctl 调用 vdev->fops->unlocked_ioctl

这个来自 drivers\media\usb\airspy\airspy.cairspy_fops->unlocked_ioctl 值是 video_ioctl2 这个函数。

video_ioctl2
    video_usercopy(file, cmd, arg, __video_do_ioctl);

__video_do_ioctl 调用了 info = &v4l2_ioctls[_IOC_NR(cmd)];

static struct v4l2_ioctl_info v4l2_ioctls[] = {
    IOCTL_INFO_FNC(VIDIOC_QUERYCAP, v4l_querycap, v4l_print_querycap, 0),
    IOCTL_INFO_FNC(VIDIOC_ENUM_FMT, v4l_enum_fmt, v4l_print_fmtdesc, INFO_FL_CLEAR(v4l2_fmtdesc, type)),
    IOCTL_INFO_FNC(VIDIOC_G_FMT, v4l_g_fmt, v4l_print_format, 0),
    IOCTL_INFO_FNC(VIDIOC_S_FMT, v4l_s_fmt, v4l_print_format, INFO_FL_PRIO),
    IOCTL_INFO_FNC(VIDIOC_REQBUFS, v4l_reqbufs, v4l_print_requestbuffers, INFO_FL_PRIO | INFO_FL_QUEUE),
    IOCTL_INFO_FNC(VIDIOC_QUERYBUF, v4l_querybuf, v4l_print_buffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_buffer, length)),
    IOCTL_INFO_STD(VIDIOC_G_FBUF, vidioc_g_fbuf, v4l_print_framebuffer, 0),
    IOCTL_INFO_STD(VIDIOC_S_FBUF, vidioc_s_fbuf, v4l_print_framebuffer, INFO_FL_PRIO),

上面的 v4l2_ioctls 中含有 IOCTL_INFO_FNC 和 IOCTL_INFO_STD 两种。

struct v4l2_ioctl_info {
    unsigned int ioctl;
    u32 flags;
    const char * const name;
    union {
        u32 offset;
        int (*func)(const struct v4l2_ioctl_ops *ops,
                struct file *file, void *fh, void *p);
    } u;
    void (*debug)(const void *arg, bool write_only);
};
#define IOCTL_INFO_STD(_ioctl, _vidioc, _debug, _flags)         \
    [_IOC_NR(_ioctl)] = {                       \
        .ioctl = _ioctl,                    \
        .flags = _flags | INFO_FL_STD,              \
        .name = #_ioctl,                    \
        .u.offset = offsetof(struct v4l2_ioctl_ops, _vidioc),   \
        .debug = _debug,                    \
    }

#define IOCTL_INFO_FNC(_ioctl, _func, _debug, _flags)           \
    [_IOC_NR(_ioctl)] = {                       \
        .ioctl = _ioctl,                    \
        .flags = _flags | INFO_FL_FUNC,             \
        .name = #_ioctl,                    \
        .u.func = _func,                    \
        .debug = _debug,                    \
    }

可以看出来,std 这种是在 struct v4l2_ioctl_ops 中的 offset 便宜,而 func 是直接用的赋值的函数。这个 std 对应的是:

// drivers\media\usb\airspy\airspy.c
static const struct v4l2_ioctl_ops airspy_ioctl_ops = {
    .vidioc_querycap          = airspy_querycap,
    .vidioc_enum_fmt_sdr_cap  = airspy_enum_fmt_sdr_cap,
    .vidioc_g_fmt_sdr_cap     = airspy_g_fmt_sdr_cap,
    .vidioc_s_fmt_sdr_cap     = airspy_s_fmt_sdr_cap,
    .vidioc_try_fmt_sdr_cap   = airspy_try_fmt_sdr_cap,
    .vidioc_reqbufs           = vb2_ioctl_reqbufs,
    .vidioc_create_bufs       = vb2_ioctl_create_bufs,
    .vidioc_prepare_buf       = vb2_ioctl_prepare_buf,
    .vidioc_querybuf          = vb2_ioctl_querybuf,
    .vidioc_qbuf              = vb2_ioctl_qbuf,
    .vidioc_dqbuf             = vb2_ioctl_dqbuf,
    ...

videobuffer2 结构体

airspy_ioctl_ops 中的 .vidioc_reqbufs = vb2_ioctl_reqbufs,

int vb2_ioctl_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *p)
    res = vb2_core_reqbufs(vdev->queue, p->memory, &p->count);
        allocated_buffers = __vb2_queue_alloc(q, memory, num_buffers, num_planes, plane_sizes);

__vb2_queue_alloc 中用到了 vb2_queue vb2_buffer 等结构体。实际的 buffer 是 vb2_queue.bufs -> vb2_buffer.planes -> vb2_plane.mem_priv -> vb2_vmalloc_buf.vddr 指向真正的 buffer.

一般申请 buffer 之后,会用 ioctl(fd, VIDIOC_QBUF, &buf) 先把 buffer 挂在 vb2_queue.queued_list, 等到摄像头保存数据之后,就把 buffer 挂到 vb2_queue.done_list , 等到 app 读取完数据后,就可以用 ioctl(fd, VIDIOC_DQBUF, &buf) 从 done_list 拿数据之后,就可以用 ioctl(fd, VIDIOC_QBUF, &buf) 再把 buffer 挂到 queued_list 中。

videobuffer2 的 ops

video_device 对应了两个 ops:

static struct video_device airspy_template = {
    .name                     = "AirSpy SDR",
    .release                  = video_device_release_empty,
    .fops                     = &airspy_fops,
    .ioctl_ops                = &airspy_ioctl_ops,
};

vb2_queue 对应了三个 ops:

    s->vb_queue.ops = &airspy_vb2_ops;
    s->vb_queue.mem_ops = &vb2_vmalloc_memops;
    ret = vb2_queue_init(&s->vb_queue);
        q->buf_ops = &v4l2_buf_ops;

其中: app 和 驱动之间的 open,read 等操作,靠的是 video_device 中的:

  • static const struct v4l2_file_operations airspy_fops
    static const struct v4l2_file_operations airspy_fops = {
    .owner                    = THIS_MODULE,
    .open                     = v4l2_fh_open,
    .release                  = vb2_fop_release,
    .read                     = vb2_fop_read,
    .poll                     = vb2_fop_poll,
    .mmap                     = vb2_fop_mmap,
    .unlocked_ioctl           = video_ioctl2,
    };
  • static const struct v4l2_ioctl_ops airspy_ioctl_ops
    static const struct v4l2_ioctl_ops airspy_ioctl_ops = {
    .vidioc_querycap          = airspy_querycap,
    .vidioc_enum_fmt_sdr_cap  = airspy_enum_fmt_sdr_cap,
    .vidioc_g_fmt_sdr_cap     = airspy_g_fmt_sdr_cap,
    .vidioc_s_fmt_sdr_cap     = airspy_s_fmt_sdr_cap,
    .vidioc_try_fmt_sdr_cap   = airspy_try_fmt_sdr_cap,
    .vidioc_reqbufs           = vb2_ioctl_reqbufs,
    .vidioc_create_bufs       = vb2_ioctl_create_bufs,
    .vidioc_prepare_buf       = vb2_ioctl_prepare_buf,
    .vidioc_querybuf          = vb2_ioctl_querybuf,
    .vidioc_qbuf              = vb2_ioctl_qbuf,
    .vidioc_dqbuf             = vb2_ioctl_dqbuf,
    ... 

app 和 驱动之间 buf 也就是 v4l2_buffer 和 vb2_buffer 之间,靠的是 vb2_ops, vb2_mem_ops, vb2_buf_ops。

  • struct vb2_buf_ops 中重要的是 fill_user_buffer 向用户 buf提供数据,fill_vb2_buffer 向驱动 buf 提供数据
    // drivers\media\v4l2-core\videobuf2-v4l2.c
    static const struct vb2_buf_ops v4l2_buf_ops = {
    .verify_planes_array    = __verify_planes_array_core,
    .fill_user_buffer   = __fill_v4l2_buffer,
    .fill_vb2_buffer    = __fill_vb2_buffer,
    .copy_timestamp     = __copy_timestamp,
    };
  • struct vb2_mem_ops 主要用于内存辅助函数
    // drivers\media\v4l2-core\videobuf2-vmalloc.c
    const struct vb2_mem_ops vb2_vmalloc_memops = {
    .alloc      = vb2_vmalloc_alloc,
    .put        = vb2_vmalloc_put,
    .get_userptr    = vb2_vmalloc_get_userptr,
    .put_userptr    = vb2_vmalloc_put_userptr,
    #ifdef CONFIG_HAS_DMA
    .get_dmabuf = vb2_vmalloc_get_dmabuf,
    #endif
    .map_dmabuf = vb2_vmalloc_map_dmabuf,
    .unmap_dmabuf   = vb2_vmalloc_unmap_dmabuf,
    .attach_dmabuf  = vb2_vmalloc_attach_dmabuf,
    .detach_dmabuf  = vb2_vmalloc_detach_dmabuf,
    .vaddr      = vb2_vmalloc_vaddr,
    .mmap       = vb2_vmalloc_mmap,
    .num_users  = vb2_vmalloc_num_users,
    };
  • struct vb2_ops 用于硬件操作相关的处理
    // drivers\media\usb\airspy\airspy.c
    static const struct vb2_ops airspy_vb2_ops = {
    .queue_setup            = airspy_queue_setup,
    .buf_queue              = airspy_buf_queue,
    .start_streaming        = airspy_start_streaming,
    .stop_streaming         = airspy_stop_streaming,
    .wait_prepare           = vb2_ops_wait_prepare,
    .wait_finish            = vb2_ops_wait_finish,
    };

videobuffer2 情景分析

分配内存

app 通过 ioctl(fd, VIDIOC_REQBUFS, &rb) 调用了驱动中的 vb2_ioctl_reqbufs,

.vidioc_reqbufs           = vb2_ioctl_reqbufs,
    int vb2_ioctl_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *p)
        res = vb2_core_reqbufs(vdev->queue, p->memory, &p->count);
            ret = call_qop(q, queue_setup, q, &num_buffers, &num_planes, plane_sizes, q->alloc_devs);
            ...
            __vb2_queue_alloc(q, memory, num_buffers, num_planes, plane_sizes);
                ret = __vb2_buf_mem_alloc(vb);
                    mem_priv = call_ptr_memop(vb, alloc, q->alloc_devs[plane] ? : q->dev, q->dma_attrs, size, dma_dir, q->gfp_flags);
#define call_qop(q, op, args...)                    \
    ((q)->ops->op ? (q)->ops->op(args) : 0)

#define call_ptr_memop(vb, op, args...)                 \
    ((vb)->vb2_queue->mem_ops->op ?                 \
        (vb)->vb2_queue->mem_ops->op(args) : NULL)
  • call_qop 宏展开: vdev->queue->ops->queue_setup 调用了 struct vb2_ops 里面的 queue_setup
  • call_ptr_memop 宏展开: vb->vb2_queue->mem_ops->alloc 调用了 struct vb2_mem_ops 里面的 alloc

buffer 放入队列

app 通过 ioctl(fd, VIDIOC_QBUF, &buf) 调用了驱动中的 vb2_ioctl_qbuf,

.vidioc_qbuf              = vb2_ioctl_qbuf,
    int vb2_ioctl_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
        return vb2_qbuf(vdev->queue, p);
            vb2_core_qbuf(q, b->index, b);
                ret = __buf_prepare(vb, pb);
                    ret = __qbuf_mmap(vb, pb);
                        if (pb) ret = call_bufop(vb->vb2_queue, fill_vb2_buffer, vb, pb, vb->planes);
                        return ret ? ret : call_vb_qop(vb, buf_prepare, vb);

                list_add_tail(&vb->queued_entry, &q->queued_list);

                if (pb) call_void_bufop(q, copy_timestamp, vb, pb);

                if (q->start_streaming_called)  __enqueue_in_driver(vb);
                    for (plane = 0; plane < vb->num_planes; ++plane) call_void_memop(vb, prepare, vb->planes[plane].mem_priv);
                    call_void_vb_qop(vb, buf_queue, vb);

                if (pb) call_void_bufop(q, fill_user_buffer, vb, pb);
                if (q->streaming && !q->start_streaming_called && q->queued_count >= q->min_buffers_needed) { ret = vb2_start_streaming(q); ... }
                    list_for_each_entry(vb, &q->queued_list, queued_entry) __enqueue_in_driver(vb);
                        for (plane = 0; plane < vb->num_planes; ++plane) call_void_memop(vb, prepare, vb->planes[plane].mem_priv);
                        call_void_vb_qop(vb, buf_queue, vb);
                    ret = call_qop(q, start_streaming, q, atomic_read(&q->owned_by_drv_count));
#define call_bufop(q, op, args...)                  \
({                                  \
    int ret = 0;                            \
    if (q && q->buf_ops && q->buf_ops->op)              \
        ret = q->buf_ops->op(args);             \
    ret;                                \
})

#define call_vb_qop(vb, op, args...)                    \
    ((vb)->vb2_queue->ops->op ? (vb)->vb2_queue->ops->op(args) : 0)

#define call_void_memop(vb, op, args...)                \
    do {                                \
        if ((vb)->vb2_queue->mem_ops->op)           \
            (vb)->vb2_queue->mem_ops->op(args);     \
    } while (0)

#define call_void_bufop(q, op, args...)                 \
({                                  \
    if (q && q->buf_ops && q->buf_ops->op)              \
        q->buf_ops->op(args);                   \
})

#define call_qop(q, op, args...)                    \
    ((q)->ops->op ? (q)->ops->op(args) : 0)

注: struct vb2_ops 是硬件相关的函数。

  • call_bufop 宏展开: vb->vb2_queue->buf_ops->fill_vb2_buffer 调用了 struct vb2_buf_ops 里面的 fill_vb2_buffer
  • call_vb_qop 宏展开: vb->vb2_queue->ops->buf_prepare 调用了 struct vb2_ops 里面的 buf_prepare
  • list_add_tail(&vb->queued_entry, &q->queued_list); vb2_buffer 加入到 vb2_queue
  • call_bufop 宏展开: vb2_queue->buf_ops->copy_timestamp 调用了 struct vb2_buf_ops 里面的 copy_timestamp
  • call_void_memop(vb, prepare, vb->planes[plane].mem_priv); 宏展开: vb->vb2_queue->mem_ops->prepare 调用了 struct vb2_mem_ops 里面的 prepare
  • call_void_vb_qop(vb, buf_queue, vb); 宏展开: vb->vb2_queue->ops->buf_queue 调用了 struct vb2_ops 里面的 buf_queue
  • call_void_bufop(q, fill_user_buffer, vb, pb); 宏展开: vb2_queue->buf_ops->fill_user_buffer 调用了 struct vb2_buf_ops 里面的 fill_user_buffer
  • call_qop(q, start_streaming, q, atomic_read(&q->owned_by_drv_count)); 宏展开: vb2_queue->ops->start_streaming 调用了 struct vb2_ops 里面的 start_streaming

回顾与编写驱动框架

drivers\media\v4l2-core\v4l2-dev.c 中的 fops 是 file_operations 经过一路调用,最后到硬件相关的 v4l2_file_operations

// drivers\media\v4l2-core\v4l2-dev.c
static const struct file_operations v4l2_fops = {
    .owner = THIS_MODULE,
    .read = v4l2_read,
    .write = v4l2_write,
    .open = v4l2_open,
    .get_unmapped_area = v4l2_get_unmapped_area,
    .mmap = v4l2_mmap,
    .unlocked_ioctl = v4l2_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl = v4l2_compat_ioctl32,
#endif
    .release = v4l2_release,
    .poll = v4l2_poll,
    .llseek = no_llseek,
};

// drivers\media\usb\airspy\airspy.c
static const struct v4l2_file_operations airspy_fops = {
    .owner                    = THIS_MODULE,
    .open                     = v4l2_fh_open,
    .release                  = vb2_fop_release,
    .read                     = vb2_fop_read,
    .poll                     = vb2_fop_poll,
    .mmap                     = vb2_fop_mmap,
    .unlocked_ioctl           = video_ioctl2,
};

有两个 device,通过 g_vdev.v4l2_dev = &g_v4l2_dev; 这样的方法,把 v4l2_device 赋值给 video_device 中的一个成员变量。注册的时候,先注册 v4l2_device 后注册 video_device ; 卸载 v4l2_device 需要先 s->v4l2_dev.release = airspy_video_release; 设置好 release,然后让内核自动调用。

  • 一个是 struct video_device, 用 ret = video_register_device(&g_vdev, VFL_TYPE_SDR, -1); 注册
  • 一个是 struct v4l2_device , 用 ret = v4l2_device_register(NULL, &g_v4l2_dev); 注册。

虚拟摄像头 ioctl

根据 app 前面使用到的 ioctl 来实现驱动中相关的函数。

ioctl(fd, VIDIOC_QUERYCAP, &cap)

static const struct v4l2_ioctl_ops virtual_ioctl_ops 增加 .vidioc_querycap = virtual_querycap, 并在函数中配置 struct v4l2_capability

ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc)

static const struct v4l2_ioctl_ops virtual_ioctl_ops 增加 .vidioc_enum_fmt_vid_cap = virtual_enum_fmt_cap, 并在函数中配置 f->pixelformat = V4L2_PIX_FMT_MJPEG;,用于向 app 返回可用格式。

ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsenum)

static const struct v4l2_ioctl_ops virtual_ioctl_ops 增加 .vidioc_enum_framesizes = virtual_enum_framesizes, 并在函数中配置 分辨率, 用于向 app 返回可用分辨率

ioctl(fd, VIDIOC_S_FMT, &fmt)

static const struct v4l2_ioctl_ops virtual_ioctl_ops 增加 .vidioc_s_fmt_vid_cap = virtual_s_fmt_cap, 并在函数中配置从 app 传过来的参数,如果传过来的参数不可用,就返回可用参数。

其他 ioctl

其他 ioctl 中可以在 static const struct v4l2_ioctl_ops virtual_ioctl_ops 中配置为内核提供的 vb2_ioctl_ 开头的函数。

驱动 buff 代码

buffer 相关的三个结构体中,vb2_buf_opsvb2_mem_ops 使用内核自带的函数,vb2_ops 使用的是硬件相关,需要开发人员提供函数。

static const struct vb2_ops virtul_vb2_ops = {
    .queue_setup            = virtual_queue_setup,
    .buf_queue              = virtual_buf_queue,
    .start_streaming        = virtual_start_streaming,
    .stop_streaming         = virtual_stop_streaming,
    .wait_prepare           = vb2_ops_wait_prepare,
    .wait_finish            = vb2_ops_wait_finish,
};

queue_setup

用于检查用户需求的 buf 数量是否够用,驱动程序可以调整数据。并且把有些地址进行页面对其。

buf_queue

  • 通过 container_of 从 vb2_buffer 找到 vb2_v4l2_buffer 再找到含有 list 的 airspy_frame_buf
  • 通过 list_add_tail 把 airspy_frame_buf 挂载到 设备的 queued_bufs 下面。

驱动 传输相关

在 start_streaming 之后,会启动数据传输,然后在相关的中断函数中,去读取摄像头数据,并保存到 buffer 中,然后放到 done 的列表中,供 app 使用。

start_streaming

简单的设置并开始一个定时器,然后在定时器中进行具体处理。

virtual_timer_expire

  • fbuf = airspy_get_next_fill_buf(s); 先实现这个函数,找到下一个可用的 buffer
  • ptr = vb2_plane_vaddr(&fbuf->vb.vb2_buf, 0); 从 下一个可用的 buffer 获得 写入地址。
  • len = airspy_convert_stream(s, ptr, urb->transfer_buffer, urb->actual_length); 把具体数据写入到 buffer 中。
  • vb2_set_plane_payload(&fbuf->vb.vb2_buf, 0, len); 设置前面写入 buffer 的长度。
  • vb2_buffer_done(&fbuf->vb.vb2_buf, VB2_BUF_STATE_DONE); 把 buffer 放到 done list 中。

驱动 上级调试

可以参考 drivers\media\usb\airspy\airspy.c drivers\media\usb\uvc\uvc_driver.c

  • 相关的 lock 还是需要提供,在 prepare 之类的时候,会被内核中其他函数使用。
  • ret = video_register_device(&g_vdev, VFL_TYPE_GRABBER, -1); 参数建议用 VFL_TYPE_GRABBER,创建设备的时候,是 /dev/video*, 如果是 VFL_TYPE_SDR,设备名称就是一个 sw 开头的。
  • v4l2_ioctl_ops 中的 vidioc_g_fmt_vid_cap 函数也需要实现,用于获取当前摄像头的 v4l2_pix_format
  • stop_streaming 的实现函数中,需要把 queue 链表中的所有 buffer,全部通过 vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); 这个操作一边,才能正常退出。

注: 可以在 app 中使用 perror 来打印出更多的信息。

发表评论