v4l2

简述

linux kernel v4l2驱动的编写,可以分为四步:

  1. 注册struct v4l2_device, struct vb2_queue, struct video_device

  2. 填充struct vb2_ops, struct v4l2_file_operations, v4l2_ioctl_ops

  3. 注销

linux kernel v4l2驱动的测试,可以分为二步:

  1. 通过v4l2-ctl捕捉RAW图

  2. 通过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

参考网址

V4L2摄像头概述

V4L2框架-videobuf2

Last updated

Was this helpful?