总结
数据采集流程
- 设置好长宽之后,摄像头可能不支持,需要检查长宽参数是否被修改。
- app 根据自身处理能力的快慢,指定申请的 buffer 数量。
- 一般使用链表的形式来组织 buffer,app 从 out 链表中拿数据,拿完之后,放入 in 链表;驱动从 in 链表中拿到 buffer,然后填充数据,再放到 out 链表中去。
app 开发
关键结构体:
v4l2_fmtdesc
format enumerationv4l2_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_devicevideo_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_deviceret = video_register_device(&s->vdev, VFL_TYPE_SDR, -1);
注册 video_devicevdev->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.c
的 airspy_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_ops
和 vb2_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);
先实现这个函数,找到下一个可用的 bufferptr = 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
来打印出更多的信息。