Linux驱动之V4L2

cpp 复制代码
#include <linux/version.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-event.h>
#include <media/videobuf2-v4l2.h>
#include <media/videobuf2-vmalloc.h>

extern unsigned char red[8230];
extern unsigned char blue[8267];
extern unsigned char green[8265];

static int g_copy_cnt = 0;

static struct list_head g_queued_bufs;
static struct timer_list g_virtual_timer;
static struct mutex g_vb_queue_lock;  /* Protects vb_queue and capt_file */
static struct mutex g_v4l2_lock;      /* Protects everything else */

/* intermediate buffers with raw data from the USB device */
struct virtual_frame_buf {
	/* common v4l buffer stuff -- must be first */
	struct vb2_v4l2_buffer vb;
	struct list_head list;
};

/* Private functions */
static struct virtual_frame_buf *virtual_get_next_buf(void)
{
	//unsigned long flags;
	struct virtual_frame_buf *buf = NULL;

	//spin_lock_irqsave(&s->queued_bufs_lock, flags);
	if (list_empty(&g_queued_bufs))
		goto leave;

	buf = list_entry(g_queued_bufs.next,
			struct virtual_frame_buf, list);
	list_del(&buf->list);
leave:
	//spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
	return buf;
}


#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0)
static void virtual_timer_expire(unsigned long data)
#else
static void virtual_timer_expire(struct timer_list *t)
#endif
{
	/* 从硬件上读到数据(使用red/green/blue数组来模拟) */
	
	/* 获得第1个空闲buffer */
	struct virtual_frame_buf *buf = virtual_get_next_buf();
	void *ptr;

	if (buf)
	{
		/* 写入buffer */
		ptr = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);

		if (g_copy_cnt <= 60)
		{
			memcpy(ptr, red, sizeof(red));
			vb2_set_plane_payload(&buf->vb.vb2_buf, 0, sizeof(red));
		}
		else if(g_copy_cnt <= 120)
		{
			memcpy(ptr, green, sizeof(green));
			vb2_set_plane_payload(&buf->vb.vb2_buf, 0, sizeof(green));
		}
		else 
		{
			memcpy(ptr, blue, sizeof(blue));
			vb2_set_plane_payload(&buf->vb.vb2_buf, 0, sizeof(blue));
		}
		
		/* vb2_buffer_done */
		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
	}

	g_copy_cnt++;
	if (g_copy_cnt > 180)
		g_copy_cnt = 0;

	/* 再次设置timer的超时时间 */
	mod_timer(&g_virtual_timer, jiffies + HZ/30);
}

static int virtual_querycap(struct file *file, void *fh,
		struct v4l2_capability *cap)
{
	strlcpy(cap->driver, "virtual_video_drv", sizeof(cap->driver));
	strlcpy(cap->card, "no-card", sizeof(cap->card));
	cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_READWRITE ;
	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;

	return 0;
}

static int virtual_enum_fmt_cap(struct file *file, void *priv,
        struct v4l2_fmtdesc *f)
{
    if (f->index > 0)
        return -EINVAL;

    strlcpy(f->description, "motion_jpeg", sizeof(f->description));
    f->pixelformat = V4L2_PIX_FMT_MJPEG;

    return 0;
}

        
static int virtual_s_fmt_cap(struct file *file, void *priv,
        struct v4l2_format *f)
{
    /* 分辨用户传入的参数是否可用
     * 如果不可用, 给APP提供最接近的、硬件支持的参数
     */

    if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
        return -EINVAL;
    if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_MJPEG)
        return -EINVAL;
    
    f->fmt.pix.width = 800;
    f->fmt.pix.height = 600;

    return 0;
}

static int virtual_enum_framesizes(struct file *file, void *fh,
                     struct v4l2_frmsizeenum *fsize)
{
    if (fsize->index > 0)
        return -EINVAL;

    fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
    fsize->discrete.width = 800;
    fsize->discrete.height = 600;
    return 0;
}


 static int virtual_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
{
	struct v4l2_pix_format *pix = &f->fmt.pix;

	pix->width = 800;
	pix->height = 600;
	pix->field = V4L2_FIELD_NONE;
	pix->pixelformat = V4L2_PIX_FMT_MJPEG;
	pix->bytesperline = 0;
    return 0;
}

static const struct v4l2_file_operations virtual_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 virtual_ioctl_ops = {
	.vidioc_querycap          = virtual_querycap,

	.vidioc_enum_fmt_vid_cap  = virtual_enum_fmt_cap,
	.vidioc_s_fmt_vid_cap     = virtual_s_fmt_cap,
	.vidioc_enum_framesizes   = virtual_enum_framesizes,
	.vidioc_g_fmt_vid_cap     = virtual_g_fmt,

	.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,

	.vidioc_streamon          = vb2_ioctl_streamon,
	.vidioc_streamoff         = vb2_ioctl_streamoff,
};

static struct video_device g_vdev = {
	.name                     = "xupt_vir_drv",
	.release                  = video_device_release_empty,
	.fops                     = &virtual_fops,
	.ioctl_ops                = &virtual_ioctl_ops,
};
static struct v4l2_device g_v4l2_dev;

static struct vb2_queue g_vb_queue;

/* Videobuf2 operations */
static int virtual_queue_setup(struct vb2_queue *vq,
		unsigned int *nbuffers,
		unsigned int *nplanes, unsigned int sizes[], struct device *alloc_devs[])
{
    /* 假装:至少需要8个buffer, 每个buffer只有1个plane */

	/* Need at least 8 buffers */
	if (vq->num_buffers + *nbuffers < 8)
		*nbuffers = 8 - vq->num_buffers;
	*nplanes = 1;
	sizes[0] = PAGE_ALIGN(800*600*2);

	return 0;
}

        
static void virtual_buf_queue(struct vb2_buffer *vb)
{

    /* 把这个buffer告诉硬件相关的驱动程序 */

    struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
    struct virtual_frame_buf *buf =
            container_of(vbuf, struct virtual_frame_buf, vb);
    //unsigned long flags;

    //spin_lock_irqsave(&s->queued_bufs_lock, flags);
    list_add_tail(&buf->list, &g_queued_bufs);
    //spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
}

static int virtual_start_streaming(struct vb2_queue *vq, unsigned int count)
{
    /* 启动硬件传输 */

	/* 使用timer来模拟硬件中断
	 * 创建timer
	 * 启动timer
	 */
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0)
	setup_timer(&g_virtual_timer, virtual_timer_expire, 0);
#else
	timer_setup(&g_virtual_timer, virtual_timer_expire, 0);
#endif

	g_virtual_timer.expires = jiffies + HZ/30;
	add_timer(&g_virtual_timer);

    return 0;
}

static void virtual_stop_streaming(struct vb2_queue *vq)
{
    /* 停止硬件传输 */
	del_timer(&g_virtual_timer);

	while (!list_empty(&g_queued_bufs)) {
		struct virtual_frame_buf *buf;

		buf = list_entry(g_queued_bufs.next,
				struct virtual_frame_buf, list);
		list_del(&buf->list);
		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
	}

}


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,
};

static void virtual_video_release(struct v4l2_device *v)
{
}

static int virtual_video_drv_init(void)
{
    int ret;
    
    /* 分配/设置/注册video_device */
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    /* 设置
     * 1. 函数调用(比如ioctl)
     * 2. 队列/buffer的管理
     */

    g_vb_queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    g_vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
    g_vb_queue.drv_priv = NULL;
    g_vb_queue.buf_struct_size = sizeof(struct virtual_frame_buf);  /* 分配vb时, 分配的空间大小为buf_struct_size */
    g_vb_queue.ops = &virtul_vb2_ops;
    g_vb_queue.mem_ops = &vb2_vmalloc_memops;
    g_vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
	ret = vb2_queue_init(&g_vb_queue);
	if (ret) {
		printk("Could not initialize vb2 queue\n");
		return -1;;
	}
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	mutex_init(&g_vb_queue_lock);

    g_vdev.queue = &g_vb_queue;     
    g_vdev.queue->lock = &g_vb_queue_lock;

	/* Register the v4l2_device structure(辅助作用) */
	g_v4l2_dev.release = virtual_video_release;
    strcpy(g_v4l2_dev.name, "virtual_v4l2");
	ret = v4l2_device_register(NULL, &g_v4l2_dev);
	if (ret) {
		printk("Failed to register v4l2-device (%d)\n", ret);
		return -1;
	}
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	g_vdev.v4l2_dev = &g_v4l2_dev;
	g_vdev.lock = &g_v4l2_lock;
	mutex_init(&g_v4l2_lock);

	ret = video_register_device(&g_vdev, VFL_TYPE_GRABBER, -1);
	if (ret) {
		printk("Failed to register as video device (%d)\n", ret);
		return -1;
	}
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	INIT_LIST_HEAD(&g_queued_bufs);
    
    return 0;
}

static void virtual_video_drv_exit(void)
{
    /* 反注册/释放video_device */
    v4l2_device_unregister(&g_v4l2_dev);
    video_unregister_device(&g_vdev);
}

module_init(virtual_video_drv_init);
module_exit(virtual_video_drv_exit);

MODULE_LICENSE("GPL");

一、V4L2应用程序开发

Video for linux two(Video4Linux2)简称V4L2,是V4L的改进版。V4L2支持三种方式来采集图像:内存映射方式(mmap)、直接读取方式(read)和用户指针。内存映射的方式采集速度较快,一般用于连续视频数据的采集,实际工作中的应用概率更高;直接读取的方式相对速度慢一些,所以常用于静态图片数据的采集;用户指针使用较少,如有兴趣可自行研究。

1.1 处理流程框架

一般来说,摄像头采集到数据之后,会产生中断,驱动程序将数据保存到一个空闲链表的buf中,并将其挂在完成链表的链表中。这时候应用程序会从完成链表中获取buf进行处理,再把buf放入空闲链表的尾部。

1.2 控制流程

二、UVC 设备(USB 摄像头)

UVC 设备(USB 摄像头)有两个主要接口:

  1. VideoControl Interface (VC)

    • 用来描述摄像头的功能结构(拓扑),相当于摄像头的"控制平面"。

    • 包含 Terminal (输入/输出终端)和 Unit(功能单元)。

    • Terminal 代表视频源/输出端口(如 Camera Terminal、Output Terminal)。

    • Unit 则是中间的处理模块(如 Processing Unit、Extension Unit)。

  2. VideoStreaming Interface (VS)

    • 用来传输视频流数据,比如 MJPEG、H.264、YUYV。

    • 包含视频格式描述(format descriptor)、分辨率、帧率等信息。

如果公司需要设置自定义的操作,可以使用下列代码:

2.1 编写APP(参考mjpg-streamer)

2.1.1 列出帧格式/帧大小(分辨率)

这里其实就是两个循环,一个循环获取帧的格式,然后另外一个循环获取该格式下支持的分辨率。

cpp 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <linux/types.h>
#include <linux/videodev2.h>

/* ./video_test /dev/video* */
int main(int argc , char * argv[])
{
    int fd,j;
    int fmt_index = 0;
    int fream_index = 0;
    struct v4l2_fmtdesc fmtdesc;
    struct v4l2_frmsizeenum fsenum;
    if(argc != 2){
        printf("Usage: %s [/dev/video*]\n" , argv[0]);
        return -1;
    }
    /* open file */
    fd = open(argv[1] , O_RDWR);
    if(fd < 0){
        printf("can not open %s\n" , argv[1]);
        return -1;   
    }
    
    while(1){
        /* 枚举格式 */
        fmtdesc.index = fmt_index; //从0开始
        fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //指定type为捕获
        if(0 != ioctl(fd , VIDIOC_ENUM_FMT, &fmtdesc))
            break;
        fream_index = 0;
        while(1){
            /* 枚举这种格式所支持的帧大小 */
            /* 先清空 */
            memset(&fsenum, 0, sizeof(struct v4l2_frmsizeenum));
            fsenum.pixel_format = fmtdesc.pixelformat;
            fsenum.index = fream_index;
            if(ioctl(fd , VIDIOC_ENUM_FRAMESIZES, &fsenum) == 0)
            {
                printf("format %s,%d, framesize %d: %d * %d\n",
                    fmtdesc.description , fmtdesc.pixelformat , fream_index , fsenum.discrete.width , fsenum.discrete.height);
                
            }
            else{
                break;
            }
            fream_index++;
        }
        fmt_index++;
    }
    return 0;
}

2.1.2 获取数据

流程如下:打开设备-->查询能力(ioctl VIDIOC_QUERYCAP)-->申请buffer(ioctl VIDIOC_REQBUF)-->查询buffer信息(ioctl VIDIOC_QUERYBUF)、映射-->把buffer放入空闲链表(ioctl VIDIOC_QBUF)-->启动摄像头(ioctl STREAMON)-->使用poll/select监测buffer,然后从完成链表中取出buffer(ioctl DQBUF)-->处理后再放入空闲链表

2.1.2.1 查询能力(VIDIOC_QUERYCAP)
cpp 复制代码
struct v4l2_capability capability;

memset(&capability , 0 , sizeof(struct v4l2_capability));

for(ioctl(fd , VIDIOC_QUERYCAP , &capability) == 0){
    if((capability.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0){
        /* 如果不相等,说明其不具有捕获能力,直接退出 */
        printf("this device has not capabilities\n");
        return -1;
    }
    if((capability.capabilities & V4L2_CAP_STREAMING) == 0){
        /* 设备是否支持流式传输,如果不支持就只能使用read,而不能用mmap */
        /* 本文主要是想用mmap来读取图片,如果设备不支持直接返回 */
        printf("this device can not use mmaping\n");
        return -1;
    }
}
2.1.2.2 设置格式(VIDIOC_S_FMT)
cpp 复制代码
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(ioctl(fd , VIDIOC_S_FMT , &fmt) == 0){
    printf("set format ok : %d * %d\n" , fmt.fmt.pix.width , fmt.fmt.pix.height);
}else{
    printf("can not set format\n");
    return -1;
} 
2.1.2.3 申请buffer(VIDIOC_REQBUFS申请buffer、VIDIOC_QUERYBUF映射)
cpp 复制代码
struct v4l2_requestbuffers buffer;
struct v4l2_buffer vbuf;
void * bufs[32];
int buf_count = 0;

memset(&buffer , 0 , sizeof(struct v4l2_requestbuffers));
buffer.count = 32;
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer.memory = V4L2_MEMORY_MMAP; //用作MMAP映射

if(ioctl(fd, VIDIOC_REQBUFS , &buffer) == 0){
    buf_count = buffer.count;
    /* 申请内存,对申请到的内存进行mmap,使得应用程序可以直接使用这些映射的内存 */
    for(i = 0 ; i < buf_count ; i++){
        memset(&vbuf , 0 , sizeof(struct v4l2_buffer));
        vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        vbuf.index = i;
        vbuf.memory = V4L2_MEMORY_MMAP; //buf是用于mmap的
        if(ioctl(fd , VIDIOC_QUERYBUF , &vbuf) == 0){
            bufs[i] = mmap(0 , vbuf.length , PROT_READ | PROT_WRITE , MAP_SHARED , 
                    fd , vbuf.m.offset);
            if(bufs[i] == MAP_FAILED){
                printf("unable to map buffer\n");
                return -1;
            }
        }else{
            printf("can not query buf\n");
            return -1;
        } 
    }
    printf("map %d buffers ok\n" , buf_count);
}
2.1.2.4 buffer放到空闲链表(VIDIOC_QBUF)

用户把一个空 buffer(还没有数据)交还给内核, 内核把它挂到空闲链表, 驱动随后会在采集过程中,把数据写进这个 buffer。

cpp 复制代码
for(i = 0 ; i < buf_count ; ++i){
    struct v4l2_buffer buf;
    memset(&buf, buf_count, sizeof(struct v4l2_buffer));
    buf.index = i;
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    if(ioctl(fd , VIDIOC_QBUF , buf) != 0){
        printf("can not queue buf\n");
        return -1;
    }
}    
printf("queue buf ok\n");
2.1.2.5 启动摄像头(VIDIOC_STREAMON)
cpp 复制代码
if(ioctl(fd, VIDIOC_STREAMON , &type) != 0){
   printf("can not start capture\n");
   return -1;
}
printf("start capture ok\n");
2.1.2.6 poll查询队列,查询到把buf取出,保存内容之后再放入空闲链表
cpp 复制代码
struct pollfd fds[1];
char file_name[32];
int file_cnt = 0;
int file_fd;

while(1){
    memset(&fds , 0 , sizeof(fds));
    fds[0].fd = fd;
    fds[0].events = POLLIN;
    if(poll(fds , 1 , -1) == 1){
        /* 有事件发生,需要从buf中取出,将buf数据存为文件,在把buf放入队列 */
        struct v4l2_buffer buf;
        memset(&buf , 0 , sizeof(struct v4l2_buffer));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        if(ioctl(fd , VIDIOC_DQBUF , &buf) != 0){
            printf("can not dequeue buffer\n");
            return -1;
        }
        sprintf(file_name , "video_raw_data_%04d.jpeg" , file_cnt++);
        file_fd = open(file_name , O_RDWR | O_CREAT , 0777);
        if(file_fd < 0){
            printf("create file error\n");
            return -1;
        }
        write(file_fd , bufs[buf.index] , buf.bytesused);
        close(file_fd);
        if(ioctl(fd , VIDIOC_QBUF , &buf) != 0){
            printf("can not queue buf\n");
            return -1;
        }
    }
}
2.1.2.6 关闭摄像头
cpp 复制代码
if(ioctl(fd, VIDIOC_STREAMOFF , &type) != 0){
   printf("can not stop capture\n");
   return -1;
}

2.1.3 控制亮度

这个需要在中间起一个线程进行控制亮度。

cpp 复制代码
static void* brightness_ctl (void *args){
    int fd = *(int *)args;
    unsigned char c;
    int delta;
    /* 查询亮度信息 */
    struct v4l2_queryctrl query_c;
    memset(&query_c, 0, sizeof(struct v4l2_queryctrl));
    query_c.id = V4L2_CID_BRIGHTNESS;
    
    if(ioctl(fd , VIDIOC_QUERYCTRL , &query_c) != 0){
        printf("can not get brightness\n");
        return NULL;
    }
    printf("brightness min = %d , max = %d\n" , query_c.minimum , query_c.maximum);
    delta = (query_c.maximum - query_c.minimum)/10;
    /* 获取当前亮度值 */
    struct v4l2_control ctl;
    memset(&ctl, 0, sizeof(struct v4l2_control));
    ctl.id = V4L2_CID_BRIGHTNESS;
    ioctl(fd , VIDIOC_G_CTRL , &ctl);
    while(1){
        c = getchar();

        if(c == 'u' || c == 'U'){
            ctl.value += delta; 
        }else if (c == 'd' || c == 'D') {
            ctl.value -= delta;
        }else {
            continue;
        }
        if(ctl.value > query_c.maximum){
            ctl.value = query_c.maximum;
        }else if (ctl.value < query_c.minimum) {
            ctl.value = query_c.minimum;
        }
        ioctl(fd , VIDIOC_S_CTRL , &ctl);
    }
    return NULL;
}

编译程序的时候必须要加上-lpthread库。

整体代码如下:

cpp 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <linux/types.h>
#include <linux/videodev2.h>
#include <poll.h>
#include <sys/mman.h>
#include <pthread.h>

static void* brightness_ctl (void *args)
{
    int fd = *(int*)args;
    unsigned char c;
    int delta;
    /* 获取亮度最大最小值 */
    struct v4l2_queryctrl query_c;
    memset(&query_c, 0, sizeof(struct v4l2_queryctrl));
    query_c.id = V4L2_CID_BRIGHTNESS;
    if(ioctl(fd , VIDIOC_QUERYCTRL , &query_c) != 0){
        printf("can not get brightness\n");
        return NULL;
    }
    printf("brightness min = %d , max = %d\n" , query_c.minimum , query_c.maximum);
    delta = (query_c.maximum - query_c.minimum)/10;
    /* 获取当前亮度值 */
    struct v4l2_control ctl;
    memset(&ctl, 0, sizeof(struct v4l2_control));
    ctl.id = V4L2_CID_BRIGHTNESS;
    ioctl(fd , VIDIOC_G_CTRL , &ctl);

    while(1){
        c = getchar();

        if(c == 'u' || c == 'U'){
            ctl.value += delta; 
        }else if (c == 'd' || c == 'D') {
            ctl.value -= delta;
        }else {
            continue;
        }
        if(ctl.value > query_c.maximum){
            ctl.value = query_c.maximum;
        }else if (ctl.value < query_c.minimum) {
            ctl.value = query_c.minimum;
        }
        ioctl(fd , VIDIOC_S_CTRL , &ctl);
    }
    return NULL;
}
/* ./video_test /dev/video* */
int main(int argc , char * argv[])
{
    int fd , j , i ;
    int fmt_index = 0;
    int fream_index = 0;
    struct v4l2_fmtdesc fmtdesc;
    struct v4l2_frmsizeenum fsenum;
    struct v4l2_format fmt;
    struct v4l2_capability capability;
    struct v4l2_requestbuffers buffer;
    struct v4l2_buffer vbuf;
    void * bufs[32];
    char filename[32];
    int file_cnt = 0;
    int buf_count;
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    struct pollfd fds[1];
    pthread_t thread;
    if(argc != 2){
        printf("Usage: %s [/dev/video*]\n" , argv[0]);
        return -1;
    }
    /* open file */
    fd = open(argv[1] , O_RDWR);
    if(fd < 0){
        printf("can not open %s\n" , argv[1]);
        return -1;   
    }
    /* 查询能力 */
    memset(&capability, 0, sizeof(struct v4l2_capability));
    if(ioctl(fd , VIDIOC_QUERYCAP , &capability) == 0){
        if((capability.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0){
            printf("this device is not capabilities\n");
            return -1;
        }
        if((capability.capabilities & V4L2_CAP_STREAMING) == 0){
            printf("this device can not use mmaping\n");
            return -1;
        }
    }else {
        printf("can not get capabilities\n");
        return -1;
    }
    while(1){
        /* 枚举格式 */
        fmtdesc.index = fmt_index; //从0开始
        fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //指定type为捕获
        if(0 != ioctl(fd , VIDIOC_ENUM_FMT, &fmtdesc))
            break;
        fream_index = 0;
        while(1){
            /* 枚举这种格式所支持的帧大小 */
            /* 先清空 */
            memset(&fsenum, 0, sizeof(struct v4l2_frmsizeenum));
            fsenum.pixel_format = fmtdesc.pixelformat;
            fsenum.index = fream_index;
            if(ioctl(fd , VIDIOC_ENUM_FRAMESIZES, &fsenum) == 0)
            {
                printf("format %s,%d, framesize %d: %d * %d\n",
                    fmtdesc.description , fmtdesc.pixelformat , fream_index , fsenum.discrete.width , fsenum.discrete.height);
            }
            else{
                break;
            }
            fream_index++;
        }
        fmt_index++;
    }
    /* 设置格式 */
    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(ioctl(fd , VIDIOC_S_FMT , &fmt) == 0){
        printf("set format ok : %d * %d\n" , fmt.fmt.pix.width , fmt.fmt.pix.height);
    }else{
        printf("can not set format\n");
        return -1;
    } 
    /* 申请buf */
    memset(&buffer, 0, sizeof(struct v4l2_requestbuffers));
    buffer.count = 32; /* 申请buffer个数,具体多少由驱动程序决定 */
    buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buffer.memory = V4L2_MEMORY_MMAP;
    if(ioctl(fd, VIDIOC_REQBUFS , &buffer) == 0){
        buf_count = buffer.count;
        /* 申请成功之后到底有多少buf由内核决定,申请成功之后,mmap这些buffer,应用程序可以直接使用这些buffer */
        for(i = 0; i < buffer.count ; i++){
            /* 获取buf的信息 */
            memset(&vbuf, 0, sizeof(struct v4l2_buffer));
            vbuf.index = i;
            vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            vbuf.memory = V4L2_MEMORY_MMAP; //buf是用于mmap的
            if(ioctl(fd , VIDIOC_QUERYBUF , &vbuf) == 0){
                /* map the buffer */
                bufs[i] = mmap(0 , vbuf.length , PROT_READ | PROT_WRITE , MAP_SHARED , 
                    fd , vbuf.m.offset);
                if(bufs[i] == MAP_FAILED){
                    printf("unable to map buffer\n");
                    return -1;
                }
            }else{
                printf("can not query buf\n");
                return -1;
            } 
        }
        printf("map %d buffers ok\n" , buf_count);
    }
    else{
        printf("can not request buffer\n");
        return -1;
    }
    /* 把所有buf放入空闲链表 */
    for(i = 0 ; i < buf_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;
        /* 用户把一个空 buffer(还没有数据)交还给内核, 内核把它挂到空闲链表, 驱动随后会在采集过程中,把数据写进这个 buffer */
        if(ioctl(fd , VIDIOC_QBUF , &buf) != 0)
        {
            printf("can not queue buf\n");
            return -1;
        }
    }
    printf("queue buf ok\n");
    /* 启动摄像头 */
    if(ioctl(fd, VIDIOC_STREAMON , &type) != 0){
        printf("can not start capture\n");
        return -1;
    }
    printf("start capture ok\n");

    /* 开启一个线程调整亮度 */
    pthread_create(&thread , NULL , brightness_ctl , &fd);
    /* poll 查询数据,并把buf取出队列,把buf的数据存为文件,把buf放入队列 */
    while(1){
        memset(fds, 0, sizeof(fds));
        fds[0].fd = fd;
        fds[0].events = POLLIN;
        if(poll(fds , 1 , -1) == 1){
            /* 把buf取出队列 */
            struct v4l2_buffer buf;
            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            buf.memory = V4L2_MEMORY_MMAP;
            if(ioctl(fd , VIDIOC_DQBUF , &buf) != 0){
                printf("can not dequeue buffer\n");
                return -1;
            }
            /* 把buf的数据存为文件 */
            sprintf(filename, "video_raw_data_%04d.jpeg" , file_cnt++);
            int fd_file = open(filename , O_RDWR | O_CREAT , 0777);
            if(fd_file < 0){
                printf("can not create or open file\n");
                return -1;
            }
            printf("capture to %s\n" , filename);
            write(fd_file , bufs[buf.index] , buf.bytesused);
            close(fd_file);
            /* 把buf放入队列 */
            if(ioctl(fd , VIDIOC_QBUF , &buf) != 0)
            {
                printf("can not queue buf\n");
                return -1;
            }
        }
    }
    /* 关闭摄像头 */
    if(ioctl(fd, VIDIOC_STREAMOFF , &type) != 0){
        printf("can not stop capture\n");
        return -1;
    }
    printf("stop capture ok\n");
    close(fd);
    return 0;
}

三、V4L2驱动框架之videobuf2

APP想使用固定的接口来使用摄像头,那么如何让驱动工程师也按照这种方式提供呢?写一份文档,人家也不愿意看,这里就要采用Linux中的分层思想,系统提供好了接口层的代码(v4l2-dev.c,该程序无法支持硬件操作),驱动工程师就只需要提供硬件相关层即可。

3.1 怎么写一个摄像头驱动?

3.1.1 从上往下看

APP调用open/write/read/ioctl函数,进入到接口层函数。找到v4l2_fops结构体,通过传进来的filp结构体,找到次设备号,根据次设备号打开video_device。使用video_device中的fops去操作硬件相关的信息。

3.1.2 从下往上看

从典型的驱动程序中来看,例如airspy.c中。

在注册函数video_register_device中会用到vdev中的次设备号,注册一个字符设备,将这个字符设备的fops指向v4l2_dev.c中的v4l2_fops。

接下来我们来总结一下总体的流程:a、分配设置一个video_device,b、注册一个(其实应该是初始化)v4l2_dev起到辅助作用,里面有各种的锁,c、将vdev中的v4l2_dev->注册的v4l2_dev,d、最后去注册这个video_device,里面那个宏应该使用VFL_TYPE_GRABBER。

3.2 ioctl的调用分析

在驱动函数中,这些ioctI被分为2类:

INFO_FL_STD:标准的,无需特殊的代码来处理,APP的调用可以直达这些处理函数。

INFO_FL_FUNC:这类ioctl需要特殊处理,比如对于VIDIOC_ENUM_FMT,它需要根据设备的类型分别枚举。

3.3 buffer的内核实现和管理

3.3.1 回顾调用流程

自从2019年之后基本使用的都是videobuffer2。

3.3.2 videobuffer2缓冲区结构体

每一个摄像头驱动中都会有一个vb2_queue结构体,在这个队列中会有一个或者多个vb2_buffer,这个vb2_buf会根据硬件的不同,可能会有多个planes,一个plane就代表一个平面的意思(多个plane就相当于多平面相机),每一个planes中会记录信息(plane当前的信息),那么buffer如何表示呢,在vb2_plane中有一个mem_priv指针,该指针会指向vb2_vmalloc_buf结构体,这个结构体中含有vaddr,就把数据放到这个vaddr中。

分配流程:

驱动程序初始化时,就构造了vb2_queue,这是"buffer的队列",一开始里面没有"buffer。APP调用ioctI VIDIOC REQBUFS向驱动申请N个buffer,驱动程序分配n(n<=N)个vb2 buffer结构体,然后对于普通摄像头,还分配一个vb2_plane结构体、vb2_vmalloc buf结构体,最后分配存数据buffer。对于多平面摄像头,给每个vb2 buffer分配多个"vb2 plane结构体、vb2_vmalloc buf结构体、存数据的buffer。

入队列流程: APP调用ioctl VIDIOC_QBUF,驱动程序根据其index找到vb2_buffer把这个vb2_buffer放入链表vb2_queue.queued_list。硬件驱动接收到数据后,比如URB传输完成后从链表vb2_queue.queued_list取出vb2_buffer,把硬件数据存入vb2 buffer,把vb2_buffer放入链表vb2_queue.done_list。

出队列流程:APP调用ioctl VIDIOC DQBUF,驱动程序从链表vb2_queue.done_list取出并移除第1个vb2_buffer,驱动程序也把这个vb2_buffer从链表vb2_queue.queued_list移除。

3.3.3 videobuffer2中的3个ops

3.3.3.1 v4l2_buf_ops

videobuffer 和 应用程序的v4l2_buf 如何交互信息(videobuf2_v4l2.c):

在这个结构体中fill_v4l2_buffer是用来使用vb_buf构建v4l2_buf的,将信息返回给上层应用,然后fill_vb2_buf是使用v4l2_buf来构建vb_buf。这里的vb2_buf_ops中的buf可以理解成buf的描述符

用来描述buf的结构体,这样比较好理解。

3.3.3.2 vb2_mem_ops(videobuf2-vmalloc.c)

用户申请buf,内核态如何做呢?如何将buf映射到用户空间呢?均是由vb2_mem_ops结构体决定的。如果没有特殊的需求,直接使用内核提供的即可。

用法如下:

DMABUF可以由其他驱动分配,直接将数据传给v4l2,无需通过CPU来进行分配。

3.3.3.3 vb2_ops(具体硬件操作所需要的信息,如airspy驱动函数所示,就在该驱动程序下)

硬件相关的代码:

用法如下:

APP到驱动程序,从上到下,含有两个辅助结构体。第一个结构体是用来在用户空间和用户空间传递数据的,第二个结构体提供内存映射,vb2_ops提供硬件操作相关的函数。

3.4 完整的调用流程

(以airspy.c为例)probe函数中所做的事情:前提是先进入了v4l2_dev.c中提供的file_operations结构体。

这里宏call_qop会被调用两次,第一次是分配内存之前,询问底层硬件相关的函数,需要分配几个buffer,每个buffer中含有多少个plane,第二次调用时分配内存结束,驱动想得到M个buffer,但是实际分配了N个buffer,这时候再次调用queue_setup,确认目前分配的buffer个数和信息是否符合硬件需求。

四、从0编写虚拟摄像头驱动

现在我们来回顾一下流程,就拿应用程序调用ioctl函数来说,首先会进入v4l2_dev.c函数中的file_operations结构体,调用v4l2_ioctl,在该ioctl函数中从file结构体中获取video_device,并且调用这个device的unlocked_iosct。在这个函数中会调用video_usercopy,在该函数中使用到v4l2_is_known_ioctl,去分辨最上层的ioctl属于v4l2_ioctl_info数组中的哪一项,再去调用到硬件相关的v4l2_ioctl_ops结构体。

bash 复制代码
userspace ioctl(fd, cmd, arg)
        ↓
v4l2_dev.c → file_operations->unlocked_ioctl
        ↓
v4l2_ioctl
        ↓
video_usercopy
        ↓
__video_do_ioctl
        ↓
v4l2_is_known_ioctl → v4l2_ioctl_info[]
        ↓
v4l2_ioctl_ops (driver实现)
        ↓
硬件操作 (sensor, ISP, codec ...)

有关内存交互部分的结构体有vb2_buf_ops、vb2_mem_ops、vb2_ops,但是我们写摄像头驱动只需要提供vb2_ops这个有关硬件的部分即可,其余有关的均可以使用内核自带的函数。(vb2_ops 是驱动与硬件的 唯一绑定点,驱动开发者只需要实现它,就能让整个 videobuf2 框架跑起来。)

4.1 框架

框架部分只是搭出了一个最简框架,当然里面有关硬件相关的函数都还未填写,之后慢慢填充里面有关硬件操作的相关函数。

cpp 复制代码
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-event.h>
#include <media/videobuf2-v4l2.h>
#include <media/videobuf2-vmalloc.h>

static const struct v4l2_ioctl_ops vir_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,

	.vidioc_streamon          = vb2_ioctl_streamon,
	.vidioc_streamoff         = vb2_ioctl_streamoff,

	.vidioc_g_tuner           = airspy_g_tuner,
	.vidioc_s_tuner           = airspy_s_tuner,

	.vidioc_g_frequency       = airspy_g_frequency,
	.vidioc_s_frequency       = airspy_s_frequency,
	.vidioc_enum_freq_bands   = airspy_enum_freq_bands,

	.vidioc_subscribe_event   = v4l2_ctrl_subscribe_event,
	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
	.vidioc_log_status        = v4l2_ctrl_log_status,
};

static const struct v4l2_file_operations vir_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 struct video_device g_vir_dev = {
    .name                     = "virtual_video_drv",
	.release                  = video_device_release_empty,
	.fops                     = &vir_fops,
	.ioctl_ops                = &vir_ioctl_ops,
};

struct vb2_queue g_vb_queue;
struct v4l2_device g_v4l2_dev;
static int virtual_video_drv_init(void){
    int ret;
    /* 重点:分配/设置/注册一个video_device */
    /* 1、设置
     * 2、queue/buffer的管理
     */
    g_vb_queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	g_vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
	g_vb_queue.drv_priv = s;
	g_vb_queue.buf_struct_size = sizeof(struct airspy_frame_buf);
	g_vb_queue.ops = &airspy_vb2_ops;								//vb2_ops,硬件相关的操作函数
	g_vb_queue.mem_ops = &vb2_vmalloc_memops;						//vb2_mem_ops 辅助结构体,用于mmap等等,映射函数	
	g_vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
	ret = vb2_queue_init(&g_vb_queue);								//vb2_buf_ops用于APP和驱动传递参数
	if (ret) {														//init函数中q->buf_ops = &v4l2_buf_ops;
		printk("Could not initialize vb2 queue\n");
		return -1;
	}

    g_vir_dev.queue = &g_vb_queue;				//指向前面构造的vb2_queue结构体
	g_vir_dev.queue->lock = &g_vb_queue;
	/* Register the v4l2_device structure */
	g_v4l2_dev.release = airspy_video_release;
	ret = v4l2_device_register(NULL, &g_v4l2_dev);
	if (ret) {
		printk("Failed to register v4l2-device (%d)\n", ret);
		return -1;
	}
	g_vir_dev.v4l2_dev = &g_v4l2_dev;
	g_vir_dev.lock = &g_v4l2_dev;

	ret = video_register_device(&g_vir_dev, VFL_TYPE_SDR, -1);
	if (ret) {
		printk("Failed to register as video device (%d)\n",
				ret);
		return -1;
	}
    
    return 0;
}

static void virtual_video_drv_exit(void){
    v4l2_device_unregister(&g_v4l2_dev);
    video_unregister_device(&g_vir_dev);
    return ;
}

module_init(virtual_video_drv_init);
module_exit(virtual_video_drv_exit);
MODULE_LICENSE("GPL");

4.2 编写硬件相关的代码

编写这部分需要提前了解该虚拟的摄像头驱动支持什么功能:

1、MJPG格式

2、在驱动程序中预先构造3幅图片,纯红绿蓝,供应用程序读取

4.2.1 ioctl部分代码

这里主要是将APP的代码程序实现的功能,我们要在驱动程序中提供这些功能:

4.2.1.1 查询能力

由上面的调用过程,可以知道,当用户态调用ioctl的时候,会使用到驱动函数中硬件部分代码提供的v4l2_ioctl_ops结构体,那我们要提供该结构体中的函数。

cpp 复制代码
static int vir_querycap(struct file *file, void *fh,
		struct v4l2_capability *cap)
{
	strlcpy(cap->driver, "XUPT_VIRTUAL", sizeof(cap->driver));
	strlcpy(cap->card, "no-cart", sizeof(cap->card));
	cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_READWRITE;
	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
    
    return 0;
}
4.2.1.2 枚举格式

这里我们的虚拟摄像头驱动只支持一种MJPEG格式,所以index只会为0,不为0直接返回。

cpp 复制代码
static int vir_enum_fmt_cap(struct file *file, void *priv,
		struct v4l2_fmtdesc *f)
{
	/* 枚举支持的格式 */
	if (f->index > 0)
		return -EINVAL;

	strlcpy(f->description, "Motion_JPEG", sizeof(f->description));
	f->pixelformat = V4L2_PIX_FMT_MJPEG;

	return 0;
}
4.2.1.3 获取帧格式大小

该驱动只支持大小为800*600的格式,所以不论传进来多少的值,都会变成800*600。(但是一般的会支持多种帧格式大小,一般会根据传进来的值做一个判断,如果传进来的值没有刚好匹配上,会自动将值调整为支持的最近的那个帧大小)

cpp 复制代码
static int vir_enum_framesizes(struct file *file, void *fh,
				struct v4l2_frmsizeenum *fsize)
{
	/* 获取帧格式大小 */
	if(fsize->index > 0){
		return -EINVAL;
	}
	fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;	
	fsize->discrete.width = 800;
	fsize->discrete.height = 600;

	return 0;
}
4.2.1.4 设置格式
cpp 复制代码
static int vir_s_fmt_cap(struct file *file, void *priv,
		struct v4l2_format *f)
{
	/* 设置格式,只能支持分辨率为800*600,并且只支持MJPEG格式*/

	if(f->fmt.pix.pixelformat != V4L2_PIX_FMT_MJPEG){
		return -EINVAL;
	}
	if(f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE){
		return -EINVAL;
	}
    f->fmt.pix.width = 800;
    f->fmt.pix.height = 600;

	return 0;
}

4.2.2 与buffer相关的代码

4.2.2.1 申请buffer

同上述流程,会进入到v4l2_ioctl_ops结构体中的vb2_ioctl_reqbufs,在该函数中使用vb2_core_reqbufs,这个函数会调用到call_qop函数,调用硬件相关的vb2_ops.queue_setup函数,确认需要多少的buffer、每个buffer中有多少个plane。

cpp 复制代码
static int vir_queue_setup(struct vb2_queue *vq,
		unsigned int *nbuffers,
		unsigned int *nplanes, unsigned int sizes[], struct device *alloc_devs[])
{
	/* 至少需要8个buffer */
	/* Need at least 8 buffers */
	if (vq->num_buffers + *nbuffers < 8)
		*nbuffers = 8 - vq->num_buffers;
	*nplanes = 1;
	sizes[0] = PAGE_ALIGN(800*600);

	return 0;
}
4.2.2.2 将空闲buffer放入队列

调用流程:vb2_ioctl_qbuf-->vb2_qbuf-->vb2_core_qbuf-->准备好buffer之后-->__enqueue_in_driver-->call_void_vb_qop-->buf_queue(硬件提供)

cpp 复制代码
用户态 ioctl(QBUF)
   ↓
v4l2_ioctl
   ↓
vb2_ioctl_qbuf
   ↓
vb2_qbuf
   ↓
vb2_core_qbuf
   ↓ (准备 buffer: buf_prepare)
__enqueue_in_driver
   ↓
call_void_vb_qop
   ↓
buf_queue (驱动实现,提交给硬件)

buf_queue()vb2 框架与具体硬件驱动的交汇点 ,它的作用可以一句话概括:👉 把用户提交的 buffer 放到驱动的等待队列里,等"虚拟硬件"准备好数据后,再出队完成它。

cpp 复制代码
static void vir_buf_queue(struct vb2_buffer *vb)
{
	/* 把这个buffer告诉硬件相关的驱动程序,应用程序传入多个buffer,驱动程序使用链表来管理 */
	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
	struct vir_frame_buf *buf =
			container_of(vbuf, struct vir_frame_buf, vb);
	// unsigned long flags;

	// spin_lock_irqsave(&s->queued_bufs_lock, flags);
	/* 这个链表是硬件驱动相关的链表
	 * 这个空闲的buffer先被放入上层驱动vb2_queue的queued_list链表
	 * 然后才会被放入这个底层硬件驱动的链表
	 */
	list_add_tail(&buf->list, &g_vir_queued_bufs);
	// spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
}

4.2.3 数据传输

正常的摄像头驱动在启动传输的时候应该会去写相机的寄存器等等,并且当camera有数据了会产生中断,在驱动函数的中断处理函数去读取数据,记录在buffer中。流程如下图所示:

在我们的虚拟摄像头驱动程序中,没有相关的相机硬件设备,那么我们需要使用Timer定时器来模仿camera产生中断。在定时器超时时候调用超时处理函数,在该函数调用vir_get_next_buf,会去链表中取出第一个buffer,并且将这个buffer在链表中删除,同时返回这个buffer给内核进行数据传递。然后循环将三种颜色的图片输出给ptr,这里是去buffer中取出第一个plane的地址进行拷贝数据的。拷贝数据完之后会调用vb2_buffer_done将buffer放入完成链表。

cpp 复制代码
static struct vir_frame_buf *vir_get_next_buf(void)
{
	unsigned long flags;
	struct vir_frame_buf *buf = NULL;
	/* 从链表中取出第一项,然后删除并且返回这一项 */
	// spin_lock_irqsave(&s->queued_bufs_lock, flags);
	if (list_empty(&g_vir_queued_bufs))
		goto leave;

	buf = list_entry(g_vir_queued_bufs.next,
			struct vir_frame_buf, list);
	list_del(&buf->list);
leave:
	// spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
	return buf;
}


static void vir_timer_expire(struct timer_list * t)
{
	void *ptr;
	/* 从硬件上读到数据(使用red/green/blue数组)来模仿 */

	/* 获取第一个空闲的buffer */
	struct vir_frame_buf *buf = vir_get_next_buf();
	if(buf){
		/* 写入buffer */
		/* 从buf里面得到第0个plane的地址,随后就可以填入参数了 */
		ptr = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
		if(g_count <= 60){
			memcpy(ptr, red, sizeof(red));
			vb2_set_plane_payload(&buf->vb.vb2_buf, 0, sizeof(red));
		}
		else if (g_count < 120) {
			memcpy(ptr, green, sizeof(green));
			vb2_set_plane_payload(&buf->vb.vb2_buf, 0, sizeof(green));
		}else{
			memcpy(ptr, blue, sizeof(blue));
			vb2_set_plane_payload(&buf->vb.vb2_buf, 0, sizeof(blue));
		}
		/* vb2_buffer_done放入完成链表 */
		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);		
	}
	g_count++;
	if(g_count > 180)
		g_count = 0;
	/* 再次设置timer的超时时间 */
	mod_timer(&g_vir_timer, jiffies + HZ/30);
}

/* 这里的count值表示vb2_queue中有多少个buffer */
static int vir_start_streaming(struct vb2_queue *vq, unsigned int count)
{
	/* 启动硬件传输 */
	/* 使用timer来模拟硬件中断
	 * 创建timer 
	 * 启动timer
	 */
	timer_setup(&g_vir_timer, vir_timer_expire, 0);
	/* 每秒传输30帧,那么超时时间就是1/30 = 30ms */
	g_vir_timer.expires = jiffies + HZ/30;
	add_timer(&g_vir_timer);

	return 0;
}

static void vir_stop_streaming(struct vb2_queue *vq)
{
	/* 停止硬件传输 */
	del_timer(&g_vir_timer);
}

一套驱动程序对应多个设备节点时才需要这些锁,比如某个驱动生成了设备节点/dev/video0,/dev/video1,APP1访问/dev/video0,APP2访间/dev/video1, APP1、APP2有可能同时调用驱动里同一套代码这时,驱动里就要进行互斥操作。但是我们这个虚拟驱动程序只会产生一个设备节点,所以无需实现互斥操作。

五、上传板子实验

5.1 代码

cpp 复制代码
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/types.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-event.h>
#include <media/videobuf2-v4l2.h>
#include <media/videobuf2-vmalloc.h>

/* intermediate buffers with raw data from the USB device */
struct vir_frame_buf {
	/* common v4l buffer stuff -- must be first */
	struct vb2_v4l2_buffer vb;
	struct list_head list;
}; 
struct vb2_queue g_vb_queue;
struct v4l2_device g_v4l2_dev;

static struct timer_list g_vir_timer;
static struct list_head g_vir_queued_bufs;
static struct mutex g_vb_queue_lock;  /* Protects vb_queue and capt_file */
static struct mutex g_v4l2_lock;      /* Protects everything else */

/* 构建图片数据 */
extern unsigned char red[8341];
extern unsigned char green[8229];
extern unsigned char blue[8267];

static int g_count = 0;

static int vir_querycap(struct file *file, void *fh,
		struct v4l2_capability *cap)
{
	/* 查询能力 ok*/
	strlcpy(cap->driver, "XUPT_VIRTUAL", sizeof(cap->driver));
	strlcpy(cap->card, "no-card", sizeof(cap->card));
	cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_READWRITE;
	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;

	return 0;
}

static int vir_enum_fmt_cap(struct file *file, void *priv,
		struct v4l2_fmtdesc *f)
{
	/* 枚举支持的格式 ok*/
	if (f->index > 0)
		return -EINVAL;

	strlcpy(f->description, "Motion_JPEG", sizeof(f->description));
	f->pixelformat = V4L2_PIX_FMT_MJPEG;

	return 0;
}

static int vir_s_fmt_cap(struct file *file, void *priv,
		struct v4l2_format *f)
{
	/* 设置格式,只能支持分辨率为800*600,并且只支持MJPEG格式 ok*/
	if(f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE){
		return -EINVAL;
	}

	if(f->fmt.pix.pixelformat != V4L2_PIX_FMT_MJPEG){
		return -EINVAL;
	}
	
    f->fmt.pix.width = 800;
    f->fmt.pix.height = 600;

	return 0;
}

static int vir_enum_framesizes(struct file *file, void *fh,
				struct v4l2_frmsizeenum *fsize)
{
	/* 获取帧格式大小 ok*/
	if(fsize->index > 0){
		return -EINVAL;
	}
	fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;	
	fsize->discrete.width = 800;
	fsize->discrete.height = 600;

	return 0;
}

static int vir_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
{
	struct v4l2_pix_format *pix = &f->fmt.pix;

	pix->width = 800;
	pix->height = 600;
	pix->field = V4L2_FIELD_NONE;
	pix->pixelformat = V4L2_PIX_FMT_MJPEG;
	pix->bytesperline = 0;
    return 0;
}

static void vir_buf_queue(struct vb2_buffer *vb)
{
	/* 把这个buffer告诉硬件相关的驱动程序,应用程序传入多个buffer,驱动程序使用链表来管理 */
	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
	struct vir_frame_buf *buf =
			container_of(vbuf, struct vir_frame_buf, vb);
	// unsigned long flags;

	// spin_lock_irqsave(&s->queued_bufs_lock, flags);
	/* 这个链表是硬件驱动相关的链表
	 * 这个空闲的buffer先被放入上层驱动vb2_queue的queued_list链表
	 * 然后才会被放入这个底层硬件驱动的链表
	 */
	list_add_tail(&buf->list, &g_vir_queued_bufs);
	// spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
}

static int vir_queue_setup(struct vb2_queue *vq,
		unsigned int *nbuffers,
		unsigned int *nplanes, unsigned int sizes[], struct device *alloc_devs[])
{
	/* 至少需要8个buffer */
	/* Need at least 8 buffers */
	if (vq->num_buffers + *nbuffers < 8)
		*nbuffers = 8 - vq->num_buffers;
	*nplanes = 1;
	sizes[0] = PAGE_ALIGN(800*600);

	return 0;
}

static struct vir_frame_buf *vir_get_next_buf(void)
{
	// unsigned long flags;
	struct vir_frame_buf *buf = NULL;
	/* 从链表中取出第一项,然后删除并且返回这一项 */
	// spin_lock_irqsave(&s->queued_bufs_lock, flags);
	if (list_empty(&g_vir_queued_bufs))
		goto leave;

	buf = list_entry(g_vir_queued_bufs.next,
			struct vir_frame_buf, list);
	list_del(&buf->list);
leave:
	// spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
	return buf;
}


static void vir_timer_expire(struct timer_list * t)
{
	void *ptr;
	/* 从硬件上读到数据(使用red/green/blue数组)来模仿 */

	/* 获取第一个空闲的buffer */
	struct vir_frame_buf *buf = vir_get_next_buf();
	if(buf){
		/* 写入buffer */
		/* 从buf里面得到第0个plane的地址,随后就可以填入参数了 */
		ptr = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
		if(g_count <= 60){
			memcpy(ptr, red, sizeof(red));
			vb2_set_plane_payload(&buf->vb.vb2_buf, 0, sizeof(red));
		}
		else if (g_count < 120) {
			memcpy(ptr, green, sizeof(green));
			vb2_set_plane_payload(&buf->vb.vb2_buf, 0, sizeof(green));
		}else{
			memcpy(ptr, blue, sizeof(blue));
			vb2_set_plane_payload(&buf->vb.vb2_buf, 0, sizeof(blue));
		}
		/* vb2_buffer_done放入完成链表 */
		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);		
	}
	g_count++;
	if(g_count > 180)
		g_count = 0;
	/* 再次设置timer的超时时间 */
	mod_timer(&g_vir_timer, jiffies + HZ/30);
}

/* 这里的count值表示vb2_queue中有多少个buffer */
static int vir_start_streaming(struct vb2_queue *vq, unsigned int count)
{
	/* 启动硬件传输 */
	/* 使用timer来模拟硬件中断
	 * 创建timer 
	 * 启动timer
	 */
	timer_setup(&g_vir_timer, vir_timer_expire, 0);
	/* 每秒传输30帧,那么超时时间就是1/30 = 30ms */
	g_vir_timer.expires = jiffies + HZ/30;
	add_timer(&g_vir_timer);

	return 0;
}

static void vir_stop_streaming(struct vb2_queue *vq)
{
	/* 停止硬件传输 */
	del_timer(&g_vir_timer);
}

static const struct v4l2_ioctl_ops vir_ioctl_ops = {
	.vidioc_querycap          = vir_querycap,

	.vidioc_enum_fmt_vid_cap  = vir_enum_fmt_cap,
	.vidioc_s_fmt_vid_cap	  = vir_s_fmt_cap,
	.vidioc_enum_framesizes	  = vir_enum_framesizes,
	.vidioc_g_fmt_vid_cap     = vir_g_fmt,
	
	.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,

	.vidioc_streamon          = vb2_ioctl_streamon,
	.vidioc_streamoff         = vb2_ioctl_streamoff,
};

static const struct v4l2_file_operations vir_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 struct video_device g_vir_dev = {
    .name                     = "virtual_video_drv",
	.release                  = video_device_release_empty,
	.fops                     = &vir_fops,
	.ioctl_ops                = &vir_ioctl_ops,
};

/* buf_queue 的作用是:把buffer传给驱动,驱动获取数据填充好buffer后会调用
 * vb2_buffer_done函数返还buffer。
 */
static const struct vb2_ops vir_vb2_ops = {
	.queue_setup            = vir_queue_setup,
	.buf_queue              = vir_buf_queue,
	.start_streaming        = vir_start_streaming,
	.stop_streaming         = vir_stop_streaming,
	.wait_prepare           = vb2_ops_wait_prepare,
	.wait_finish            = vb2_ops_wait_finish,
};

static void vir_video_release(struct v4l2_device *v)
{
}

static int virtual_video_drv_init(void){
    int ret;
    /* 重点:分配/设置/注册一个video_device */
    /* 1、设置
     * 2、queue/buffer的管理
     */
	printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);
    g_vb_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE;
	g_vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
	g_vb_queue.drv_priv = NULL;
	g_vb_queue.buf_struct_size = sizeof(struct vir_frame_buf); /* 分配vb时,分配的空间大小为buf_struct_size */
	g_vb_queue.ops = &vir_vb2_ops;								//vb2_ops,硬件相关的操作函数
	g_vb_queue.mem_ops = &vb2_vmalloc_memops;						//vb2_mem_ops 辅助结构体,用于mmap等等,映射函数	
	g_vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
	ret = vb2_queue_init(&g_vb_queue);								//vb2_buf_ops用于APP和驱动传递参数
	if (ret) {														//init函数中q->buf_ops = &v4l2_buf_ops;
		printk("Could not initialize vb2 queue\n");
		return -1;
	}
	printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);

	mutex_init(&g_vb_queue_lock);
	mutex_init(&g_v4l2_lock);
    g_vir_dev.queue = &g_vb_queue;				//指向前面构造的vb2_queue结构体
	g_vir_dev.queue->lock = &g_vb_queue_lock;
	/* Register the v4l2_device structure */
	g_v4l2_dev.release = vir_video_release;
	strcpy(g_v4l2_dev.name, "vir_v4l2");
	ret = v4l2_device_register(NULL, &g_v4l2_dev);
	if (ret) {
		printk("Failed to register v4l2-device (%d)\n", ret);
		return -1;
	}
	printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);
	g_vir_dev.v4l2_dev = &g_v4l2_dev;
	g_vir_dev.lock = &g_v4l2_lock;

	ret = video_register_device(&g_vir_dev, VFL_TYPE_GRABBER, -1);
	if (ret) {
		printk("Failed to register as video device (%d)\n",
				ret);
		return -1;
	}
	printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);
	INIT_LIST_HEAD(&g_vir_queued_bufs);
	printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);

    return 0;
}

static void virtual_video_drv_exit(void)
{
	printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);
    v4l2_device_unregister(&g_v4l2_dev);
    video_unregister_device(&g_vir_dev);
    return ;
}

module_init(virtual_video_drv_init);
module_exit(virtual_video_drv_exit);
MODULE_LICENSE("GPL");

5.2 结果

通过dmseg我们发现,上述程序在register v4l2-device的时候出现了错误。这里其实是由于没有给v4l2_dev设置名字,我们只要给 strcpy(g_v4l2_dev.name, "vir_v4l2");设置名字即可。

insmod 我们的驱动程序后会发现,ls /dev/video* -l 下面没有生成设备节点,但是在 ls /dev/swradio0 这个设备生成了节点,这是因为代码在 ret = video_register_device(&g_vir_dev, VFL_TYPE_SDR, -1);的时候传的type不对,生成错了设备节点,所以我们要将这个type改成VFL_TYPE_GRABBER 或者直接传0即可。结果如下图所示,video11是我们生成的节点。

六、完整代码

cpp 复制代码
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/types.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-event.h>
#include <media/videobuf2-v4l2.h>
#include <media/videobuf2-vmalloc.h>

/* intermediate buffers with raw data from the USB device */
struct vir_frame_buf {
	/* common v4l buffer stuff -- must be first */
	struct vb2_v4l2_buffer vb;
	struct list_head list;
}; 
struct vb2_queue g_vb_queue;
struct v4l2_device g_v4l2_dev;

static struct timer_list g_vir_timer;
static struct list_head g_vir_queued_bufs;
static struct mutex g_vb_queue_lock;  /* Protects vb_queue and capt_file */
static struct mutex g_v4l2_lock;      /* Protects everything else */

/* 构建图片数据 */
extern unsigned char red[8341];
extern unsigned char green[8229];
extern unsigned char blue[8267];

static int g_count = 0;

static int vir_querycap(struct file *file, void *fh,
		struct v4l2_capability *cap)
{
	/* 查询能力 ok*/
	strlcpy(cap->driver, "XUPT_VIRTUAL", sizeof(cap->driver));
	strlcpy(cap->card, "no-card", sizeof(cap->card));
	cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_READWRITE;
	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;

	return 0;
}

static int vir_enum_fmt_cap(struct file *file, void *priv,
		struct v4l2_fmtdesc *f)
{
	/* 枚举支持的格式 ok*/
	if (f->index > 0)
		return -EINVAL;

	strlcpy(f->description, "Motion_JPEG", sizeof(f->description));
	f->pixelformat = V4L2_PIX_FMT_MJPEG;

	return 0;
}

static int vir_s_fmt_cap(struct file *file, void *priv,
		struct v4l2_format *f)
{
	/* 设置格式,只能支持分辨率为800*600,并且只支持MJPEG格式 ok*/
	if(f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE){
		return -EINVAL;
	}

	if(f->fmt.pix.pixelformat != V4L2_PIX_FMT_MJPEG){
		return -EINVAL;
	}
	
    f->fmt.pix.width = 800;
    f->fmt.pix.height = 600;

	return 0;
}

static int vir_enum_framesizes(struct file *file, void *fh,
				struct v4l2_frmsizeenum *fsize)
{
	/* 获取帧格式大小 ok*/
	if(fsize->index > 0){
		return -EINVAL;
	}
	fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;	
	fsize->discrete.width = 800;
	fsize->discrete.height = 600;

	return 0;
}

static int vir_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
{
	/* 该函数用于得到摄像头当前正在使用得格式,通过设置v4l2_format结构体把格式返回给用户态 */

	struct v4l2_pix_format *pix = &f->fmt.pix;

	pix->width = 800;
	pix->height = 600;
	pix->field = V4L2_FIELD_NONE;
	pix->pixelformat = V4L2_PIX_FMT_MJPEG;
	pix->bytesperline = 0;
    return 0;
}

static void vir_buf_queue(struct vb2_buffer *vb)
{
	/* 把这个buffer告诉硬件相关的驱动程序,应用程序传入多个buffer,驱动程序使用链表来管理 */
	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
	struct vir_frame_buf *buf =
			container_of(vbuf, struct vir_frame_buf, vb);
	// unsigned long flags;

	// spin_lock_irqsave(&s->queued_bufs_lock, flags);
	/* 这个链表是硬件驱动相关的链表
	 * 这个空闲的buffer先被放入上层驱动vb2_queue的queued_list链表
	 * 然后才会被放入这个底层硬件驱动的链表
	 */
	list_add_tail(&buf->list, &g_vir_queued_bufs);
	// spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
}

static int vir_queue_setup(struct vb2_queue *vq,
		unsigned int *nbuffers,
		unsigned int *nplanes, unsigned int sizes[], struct device *alloc_devs[])
{
	/* 至少需要8个buffer */
	/* Need at least 8 buffers */
	if (vq->num_buffers + *nbuffers < 8)
		*nbuffers = 8 - vq->num_buffers;
	*nplanes = 1;
	sizes[0] = PAGE_ALIGN(800*600);

	return 0;
}

static struct vir_frame_buf *vir_get_next_buf(void)
{
	// unsigned long flags;
	struct vir_frame_buf *buf = NULL;
	/* 从链表中取出第一项,然后删除并且返回这一项 */
	// spin_lock_irqsave(&s->queued_bufs_lock, flags);
	if (list_empty(&g_vir_queued_bufs))
		goto leave;

	buf = list_entry(g_vir_queued_bufs.next,
			struct vir_frame_buf, list);
	list_del(&buf->list);
leave:
	// spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
	return buf;
}


static void vir_timer_expire(struct timer_list * t)
{
	void *ptr;
	/* 从硬件上读到数据(使用red/green/blue数组)来模仿 */

	/* 获取第一个空闲的buffer */
	struct vir_frame_buf *buf = vir_get_next_buf();
	if(buf){
		/* 写入buffer */
		/* 从buf里面得到第0个plane的地址,随后就可以填入参数了 */
		ptr = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
		if(g_count <= 60){
			memcpy(ptr, red, sizeof(red));
			vb2_set_plane_payload(&buf->vb.vb2_buf, 0, sizeof(red));
		}
		else if (g_count < 120) {
			memcpy(ptr, green, sizeof(green));
			vb2_set_plane_payload(&buf->vb.vb2_buf, 0, sizeof(green));
		}else{
			memcpy(ptr, blue, sizeof(blue));
			vb2_set_plane_payload(&buf->vb.vb2_buf, 0, sizeof(blue));
		}
		/* vb2_buffer_done放入完成链表 */
		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);		
	}
	g_count++;
	if(g_count > 180)
		g_count = 0;
	/* 再次设置timer的超时时间 */
	mod_timer(&g_vir_timer, jiffies + HZ/30);
}

/* 这里的count值表示vb2_queue中有多少个buffer */
static int vir_start_streaming(struct vb2_queue *vq, unsigned int count)
{
	/* 启动硬件传输 */
	/* 使用timer来模拟硬件中断
	 * 创建timer 
	 * 启动timer
	 */
	timer_setup(&g_vir_timer, vir_timer_expire, 0);
	/* 每秒传输30帧,那么超时时间就是1/30 = 30ms */
	g_vir_timer.expires = jiffies + HZ/30;
	add_timer(&g_vir_timer);

	return 0;
}

static void vir_stop_streaming(struct vb2_queue *vq)
{
	/* 停止硬件传输 */
	del_timer(&g_vir_timer);

	while (!list_empty(&g_vir_queued_bufs)) {
		struct vir_frame_buf *buf;

		buf = list_entry(g_vir_queued_bufs.next,
				struct vir_frame_buf, list);
		list_del(&buf->list);
		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
	}

}

static const struct v4l2_ioctl_ops vir_ioctl_ops = {
	.vidioc_querycap          = vir_querycap,

	.vidioc_enum_fmt_vid_cap  = vir_enum_fmt_cap,
	.vidioc_s_fmt_vid_cap	  = vir_s_fmt_cap,
	.vidioc_enum_framesizes	  = vir_enum_framesizes,
	.vidioc_g_fmt_vid_cap     = vir_g_fmt,
	
	.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,

	.vidioc_streamon          = vb2_ioctl_streamon,
	.vidioc_streamoff         = vb2_ioctl_streamoff,
};

static const struct v4l2_file_operations vir_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 struct video_device g_vir_dev = {
    .name                     = "virtual_video_drv",
	.release                  = video_device_release_empty,
	.fops                     = &vir_fops,
	.ioctl_ops                = &vir_ioctl_ops,
};

/* buf_queue 的作用是:把buffer传给驱动,驱动获取数据填充好buffer后会调用
 * vb2_buffer_done函数返还buffer。
 */
static const struct vb2_ops vir_vb2_ops = {
	.queue_setup            = vir_queue_setup,
	.buf_queue              = vir_buf_queue,
	.start_streaming        = vir_start_streaming,
	.stop_streaming         = vir_stop_streaming,
	.wait_prepare           = vb2_ops_wait_prepare,
	.wait_finish            = vb2_ops_wait_finish,
};

static void vir_video_release(struct v4l2_device *v)
{
}

static int virtual_video_drv_init(void){
    int ret;
    /* 重点:分配/设置/注册一个video_device */
    /* 1、设置
     * 2、queue/buffer的管理
     */
	printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);
    g_vb_queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	g_vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
	g_vb_queue.drv_priv = NULL;
	g_vb_queue.buf_struct_size = sizeof(struct vir_frame_buf); /* 分配vb时,分配的空间大小为buf_struct_size */
	g_vb_queue.ops = &vir_vb2_ops;								//vb2_ops,硬件相关的操作函数
	g_vb_queue.mem_ops = &vb2_vmalloc_memops;						//vb2_mem_ops 辅助结构体,用于mmap等等,映射函数	
	g_vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
	ret = vb2_queue_init(&g_vb_queue);								//vb2_buf_ops用于APP和驱动传递参数
	if (ret) {														//init函数中q->buf_ops = &v4l2_buf_ops;
		printk("Could not initialize vb2 queue\n");
		return -1;
	}
	printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);

	mutex_init(&g_vb_queue_lock);
	mutex_init(&g_v4l2_lock);
    g_vir_dev.queue = &g_vb_queue;				//指向前面构造的vb2_queue结构体
	g_vir_dev.queue->lock = &g_vb_queue_lock;
	/* Register the v4l2_device structure */
	g_v4l2_dev.release = vir_video_release;
	strcpy(g_v4l2_dev.name, "vir_v4l2");
	ret = v4l2_device_register(NULL, &g_v4l2_dev);
	if (ret) {
		printk("Failed to register v4l2-device (%d)\n", ret);
		return -1;
	}
	printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);
	g_vir_dev.v4l2_dev = &g_v4l2_dev;
	g_vir_dev.lock = &g_v4l2_lock;

	ret = video_register_device(&g_vir_dev, VFL_TYPE_GRABBER, -1);
	if (ret) {
		printk("Failed to register as video device (%d)\n",
				ret);
		return -1;
	}
	printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);
	INIT_LIST_HEAD(&g_vir_queued_bufs);
	printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);

    return 0;
}

static void virtual_video_drv_exit(void)
{
	printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);
    v4l2_device_unregister(&g_v4l2_dev);
    video_unregister_device(&g_vir_dev);
    return ;
}

module_init(virtual_video_drv_init);
module_exit(virtual_video_drv_exit);
MODULE_LICENSE("GPL");
相关推荐
Lowjin_7 小时前
计算机网络-RIP协议
网络·计算机网络·智能路由器
问道飞鱼9 小时前
【数据库知识】TxSQL 主从数据库同步底层原理深度解析
网络·数据库·半同步复制·txsql
骥龙11 小时前
粤港澳全运会网络安全防御体系深度解析:威胁态势与实战防护
网络·安全·web安全
漫谈网络11 小时前
InfiniBand 深度解析
网络·rdma·infiniband·roce v2
海域云赵从友11 小时前
从直播卡顿到流畅带货:SD-WAN网络专线如何优化阿联酋TikTok体验?
网络
Saniffer_SH11 小时前
【高清视频】CXL 2.0 over Fibre演示和答疑 - 将内存拉到服务器10米之外
运维·服务器·网络·人工智能·驱动开发·计算机外设·硬件工程
jyan_敬言12 小时前
【Docker】docker网络配置
网络·docker·容器
apple_ttt13 小时前
范式革命:RDMA 如何让网络成为 “分布式内存总线”
网络·计算机网络·数据中心·rdma·数据中心网络
豆沙沙包?13 小时前
http/1.1,http/2和http/3、三次握手和四次挥手
网络·网络协议·http