v4l2
简述
linux kernel v4l2驱动的编写,可以分为四步:
注册struct v4l2_device, struct vb2_queue, struct video_device
填充struct vb2_ops, struct v4l2_file_operations, v4l2_ioctl_ops
注销
linux kernel v4l2驱动的测试,可以分为二步:
通过v4l2-ctl捕捉RAW图
通过gst-launch-1.0转换成.jpeg格式
linux kernel v4l2驱动的编写
1. 注册struct v4l2_device, struct vb2_queue, struct video_device
struct device *dev;
struct v4l2_device v4l2_dev;
struct vb2_queue *queue;
struct video_device *vdev;
struct list_head list;
struct mutex mutex;
// 注册struct v4l2_device
v4l2_device_register(dev, &v4l2_dev);
// 初始化链表
INIT_LIST_HEAD(&list);
// 初始化互斥表
mutex_init(&mutex);
queue->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
// 应用层采用mmap方法进行读取数据
queue->io_modes = VB2_MMAP;
// buffers队列管理
queue->ops = &xxx_queue_ops;
// buffers内存管理
queue->mem_ops = &vb2_vmalloc_memops;
queue->buf_struct_size = sizeof(struct xxx_buffer);
queue->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
queue->lock = &mutex;
queue->drv_priv = xxx;
// 初始化struct vb2_queue
vb2_queue_init(queue);
vdev->fops = &xxx_fops;
vdev->ioctl_ops = &xxx_ioctl_ops;
// V4L2_CAP_VIDEO_CAPTURE, V4L2_CAP_STREAMING代表一个摄像头
vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
vdev->release = xxx_release;
vdev->v4l2_dev = &v4l2_dev;
vdev->queue = queue;
video_set_drvdata(vdev, xxx);
// 注册struct video_device
video_register_device(vdev, VFL_TYPE_GRABBER, -1);
2. 填充struct vb2_ops, struct v4l2_file_operations, v4l2_ioctl_ops
struct task_struct *kthread;
struct xxx_buffer {
/* common v4l buffer stuff -- must be first */
struct vb2_v4l2_buffer vb;
struct list_head list;
};
struct xxx_fmt {
u32 pixelformat;
u32 bit_depth;
};
struct xxx_fmt xxx_formats[] = {
{
.pixelformat = V4L2_PIX_FMT_RGB565,
.bit_depth = 16,
},
};
int xxx_queue_setup(struct vb2_queue *q,
unsigned int *num_buffers, unsigned int *num_planes,
unsigned int sizes[], struct device *alloc_devs[])
{
// 一帧数据大小sizeimage(bytes)
sizes[0] = sizeimage;
return 0;
}
int xxx_buf_prepare(struct vb2_buffer *vb)
{
// 一帧数据大小sizeimage(bytes)
vb2_set_plane_payload(vb, 0, sizeimage);
return 0;
}
void xxx_buf_queue(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct xxx_buffer *buf = container_of(vbuf, struct xxx_buffer, vb);
// 将buffer存放到链表中
list_add_tail(&buf->list, &list);
}
static int xxx_kthread(void *data)
{
struct xxx_buffer *buf = NULL;
void *vbuf;
for (;;) {
if (kthread_should_stop())
break;
// 获得链表第一个buffer
buf = list_entry(list.next, struct xxx_buffer, list);
// 从链表中删除buffer
list_del(&buf->list);
// 获得buffer地址
vbuf = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
// 这里为了简单,直接填充数据0x88,一帧数据大小sizeimage(bytes)
// 实际中通过此处填充摄像头数据
memset(vbuf, 0x88, sizeimage);
// 通知应用层buffer已经准备完毕,可以读取
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
// 这里为了简单,直接调用msleep(),实际应用中不能这样做
// 实际中通过此处实现摄像头帧率
msleep(100);
}
return 0;
}
int xxx_start_streaming(struct vb2_queue *q, unsigned int count)
{
/* 创建并启动内核线程
* xxx_kthread : 内核线程函数
* xxx : 传递给内核线程的参数,
* xxx-kthread : 内核线程的名字
*/
kthread = kthread_run(xxx_kthread, xxx, "xxx-kthread");
return 0;
}
void xxx_stop_streaming(struct vb2_queue *q)
{
// 删除链表中所有buffers
while (!list_empty(&list)) {
struct xxx_buffer *buf;
buf = list_entry(list.next, struct xxx_buffer, list);
list_del(&buf->list);
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
}
kthread_stop(kthread);
}
const struct vb2_ops xxx_queue_ops = {
.queue_setup = xxx_queue_setup,
.buf_prepare = xxx_buf_prepare,
.buf_queue = xxx_buf_queue,
.start_streaming = xxx_start_streaming,
.stop_streaming = xxx_stop_streaming,
};
/*
* 获得摄像头信息
*/
int xxx_querycap(struct file *file, void *fh, struct v4l2_capability *cap)
{
strcpy(cap->driver, "xxx_driver");
strcpy(cap->card, "xxx_card");
strcpy(cap->bus_info, "xxx_bus_info");
return 0;
}
/*
* 枚举摄像头支持的像素格式
*/
int xxx_enum_fmt_vid_cap(struct file *file, void *fh, struct v4l2_fmtdesc *f)
{
struct xxx_fmt *fmt;
if (f->index >= ARRAY_SIZE(xxx_formats))
return -EINVAL;
fmt = &xxx_formats[f->index];
f->pixelformat = fmt->pixelformat;
return 0;
}
/*
* 获得目前摄像头的分辨率width*height, 像素格式pixelformat,一帧数据大小sizeimage(bytes)
*/
int xxx_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f)
{
struct v4l2_pix_format pix = f->fmt.pix;
pix.width = width;
pix.height = height;
pix.pixelformat = pixelformat;
pix.sizeimage = sizeimage;
return 0;
}
/*
* 设置摄像头的分辨率width*height, 像素格式pixelformat,一帧数据大小sizeimage(bytes)
*/
int xxx_s_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f)
{
struct v4l2_pix_format pix = f->fmt.pix;
int i;
width = pix.width;
height = pix.height;
pixelformat = pix.pixelformat;
for(i=0; i<ARRAY_SIZE(xxx_formats); i++) {
if(pix.pixelformat == xxx_formats[i].pixelformat)
break;
}
sizeimage = width * (xxx_formats[i].bit_depth >> 3) * height;
return 0;
}
const struct v4l2_file_operations xxx_fops = {
.owner = THIS_MODULE,
.open = v4l2_fh_open,
.release = v4l2_fh_release,
.poll = vb2_fop_poll,
.unlocked_ioctl = video_ioctl2,
.mmap = vb2_fop_mmap,
};
const struct v4l2_ioctl_ops xxx_ioctl_ops = {
.vidioc_querycap = xxx_querycap,
.vidioc_enum_fmt_vid_cap = xxx_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = xxx_g_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = xxx_s_fmt_vid_cap,
.vidioc_reqbufs = vb2_ioctl_reqbufs,
.vidioc_querybuf = vb2_ioctl_querybuf,
.vidioc_qbuf = vb2_ioctl_qbuf,
.vidioc_dqbuf = vb2_ioctl_dqbuf,
.vidioc_streamon = vb2_ioctl_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,
};
void xxx_release(struct video_device *vdev)
{
}
3. 注销
video_unregister_device(&vdev);
v4l2_device_put(&v4l2_dev);
linux应用层
1. 通过v4l2-ctl捕捉RAW图
分辨率为640x480,像素格式为RGBP(rgb565),使用mmap方法捕捉一张RAW图,保存为grab-640x360-rgb565.raw
$ v4l2-ctl --set-fmt-video=width=640,height=360,pixelformat=RGBP --stream-mmap --stream-count=1 --stream-to=grab-640x360-rgb565.raw
2. 通过gst-launch-1.0转换成.jpeg格式
blocksize=460800,代表RAW图的大小,即width * height * bytes_per_pix
bytes_per_pix是多少?因为rgb565 = 16bits,即16/8=2bytes
即 640 360 2 = 460800
$ gst-launch-1.0 filesrc location=grab-640x360-rgb565.raw blocksize=460800 ! "video/x-raw, format=(string)RGB16, width=(int)640, height=(int)360, framerate=(fraction)30/1" ! videoconvert ! jpegenc ! filesink location=grab-640x360-rgb565.jpeg
参考网址
Last updated
Was this helpful?