J2440 摄像头驱动之实现数据传输2_简单函数_学习笔记



/* 参考 drivers/media/video/uvc下的一系列文件 */

1、12个ioctl

(1)查询属性,是否为摄像头设备

static int myuvc_vidioc_querycap(struct file *file, void  *priv,
struct v4l2_capability *cap)
{    
    memset(cap, 0, sizeof *cap);///v4l2_capability是属性结构体,清零
    strcpy(cap->driver, "myuvc");//结构体填充, driver 域需要和 struct video_device 中的 name 匹配
    strcpy(cap->card, "myuvc");
    cap->version = 1; //驱动版本号
    
    cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;//设置属性(视频捕捉设备,streaming表示用ioctl来读写视频而不是read/write函数)
 

read和write应该是写入和读出数据的,应该是作为单纯的数据交换的方式来处理。而ioctl则是控制read和write一些选项的。比如:你做了一个通用的读写IO端口的驱动模块。read和write是从端口读写数据的,但是更改读写的端口,这个操作应该如何处理呢?显然用ioctl来实现比较合理。比如你的read和write是可以阻塞的,或者不能阻塞的,或者对设备文件的读写是可以并发的,或者是不可以并发的,这些都可以写成可以用ioctl来配置的情况。后面为了可以用ioctl来实现模块不同的IO特点。

return 0;
}

  1. struct v4l2_capability  
  2. {  
  3.     u8 driver[16];      // 驱动名字  
  4.     u8 card[32];        // 设备名字  
  5.     u8 bus_info[32];    // 设备在系统中的位置  
  6.     u32 version;        // 驱动版本号  
  7.     u32 capabilities;   // 设备支持的操作  
  8.     u32 reserved[4];    // 保留字段  
  9. };  

如:driver:uvcvideo,card:UVC Camera (046d:0825),bus_info:usb-0000:02:03.0-1,version:0x100,capabilities:0x4000001

#define V4L2_CAP_VIDEO_CAPTURE0x00000001  /* Is a video capture device */

#define V4L2_CAP_VIDEO_OUTPUT 0x00000002  /* Is a video output device */

#define V4L2_CAP_VIDEO_OVERLAY 0x00000004  /* Can do video overlay */

#define V4L2_CAP_VBI_CAPTURE 0x00000010  /* Is a raw VBI capture device */

#define V4L2_CAP_STREAMING              0x04000000  /* streaming I/O ioctls */


(2)列举摄像头数据的格式(参考Uvc_v4l2.c)

static int myuvc_vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
struct v4l2_fmtdesc *f)
{
    /* 人工查看描述符可知我们用的摄像头只支持1种格式 */
if (f->index >= 1)//v4l2_fmtdesc结构体的index成员
return -EINVAL;


    /* 支持什么格式呢?
     * 查看VideoStreaming Interface的描述符,
     * 得到GUID为"59 55 59 32 00 00 10 00 80 00 00 aa 00 38 9b 71"
     */

strcpy(f->description, "4:2:2, packed, YUYV");//格式描述
f->pixelformat = V4L2_PIX_FMT_YUYV;    //像素格式为YUYV
    
return 0;
}


  1. struct v4l2_fmtdesc  
  2. {  
  3.     u32 index;          // 要查询的格式序号,应用程序设置  
  4.     enum v4l2_buf_type type;    // 帧类型,应用程序设置  
  5.     u32 flags;          // 是否为压缩格式  
  6.     u8 description[32];         // 格式名称  
  7.     u32 pixelformat;        // 格式  
  8.     u32 reserved[4];        // 保留  
  9. };  

,uvc驱动会分析描述符,把格式存在数组format里面。数组的初始化:根据guid来确定格式

需要看数组uvc_fmts(有格式的名字,guid,宏(表示哪种格式))




  

这里YUY2的十六进制是59 55 59 32,跟之前的guid一样

(3)返回当前所使用的格式

//定义1个全局变量,把设置的格式存储返回

static int myuvc_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
    memcpy(f, &myuvc_format, sizeof(myuvc_format));
return (0);
}

  1. struct v4l2_format {  
  2.     enum v4l2_buf_type type;  
  3.     union {  
  4.         struct v4l2_pix_format         pix;     /* V4L2_BUF_TYPE_VIDEO_CAPTURE */  
  5.         struct v4l2_window             win;     /* V4L2_BUF_TYPE_VIDEO_OVERLAY */  
  6.         struct v4l2_vbi_format         vbi;     /* V4L2_BUF_TYPE_VBI_CAPTURE */  
  7.         struct v4l2_sliced_vbi_format  sliced;  /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */  
  8.         __u8   raw_data[200];                   /* user-defined */  
  9.     } fmt;  
  10. };  
  11. 其中  
  12. enum v4l2_buf_type {  
  13.     V4L2_BUF_TYPE_VIDEO_CAPTURE        = 1,  
  14.     V4L2_BUF_TYPE_VIDEO_OUTPUT         = 2,  
  15.     V4L2_BUF_TYPE_VIDEO_OVERLAY        = 3,  
  16.     ...  
  17.     V4L2_BUF_TYPE_PRIVATE              = 0x80,  
  18. };  
  19.   
  20. struct v4l2_pix_format {  
  21.     __u32                   width;  
  22.     __u32                   height;  
  23.     __u32                   pixelformat;  
  24.     enum v4l2_field         field;  
  25.     __u32                   bytesperline;   /* for padding, zero if unused */  
  26.     __u32                   sizeimage;  
  27.     enum v4l2_colorspace    colorspace;  
  28.     __u32                   priv;           /* private data, depends on pixelformat */  
  29. };  

(4)测试驱动程序是否支持某种格式(类型、像素格式)

static int myuvc_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
    if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)//如果不是视频捕捉类设备,就返回错误
    {
        return -EINVAL;
    }


    if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV)//如果像素格式不是YUYV,就输出错误
        return -EINVAL;
    
    /* 调整format的width, height, 格式的分辨率
     * 计算bytesperline, sizeimage
     */



    /* 人工查看描述符, 确定支持哪几种分辨率 *//可以把多种分辨率设置成结构体数组/
    f->fmt.pix.width  = frames[frame_idx].width;//某种分辨率的宽度
    f->fmt.pix.height = frames[frame_idx].height;//某种分辨率的高度
    
f->fmt.pix.bytesperline =
(f->fmt.pix.width * bBitsPerPixel) >> 3;//每行的字节数,每个像素用多少位表示,可以查看描述符(这个参数主要是考虑到字节对齐)

-v:显示USB设备的详细信息,d<厂商:产品>:仅显示指定厂商和产品编号的设备
f->fmt.pix.sizeimage =
f->fmt.pix.height * f->fmt.pix.bytesperline;//图片大小
    
    return 0;
}

(5)设置该格式

static int myuvc_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
int ret = myuvc_vidioc_try_fmt_vid_cap(file, NULL, f);//测试一下是否支持此格式,格式强制设置为f
if (ret < 0)
return ret;
//void *memcpy(void *dest, const void *src, size_t n);
从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中

    memcpy(&myuvc_format, f, sizeof(myuvc_format));//把格式赋给myuvc_format
    
    return 0;
}

(6)申请缓存

static int myuvc_vidioc_reqbufs(struct file *file, void *priv,
 struct v4l2_requestbuffers *p)
{
    int nbuffers = p->count;//申请缓存数
    int bufsize  = PAGE_ALIGN(myuvc_format.fmt.pix.sizeimage);//每一块缓存的大小,以整页分配(页对齐)
    unsigned int i;
    void *mem = NULL;
    int ret;

  1. struct v4l2_requestbuffers {  
  2.     __u32                   count;  
  3.     enum v4l2_buf_type      type;  
  4.     enum v4l2_memory        memory;  
  5.     __u32                   reserved[2];  
  6. };  
  7. 其中  
  8. enum v4l2_memory {  
  9.     V4L2_MEMORY_MMAP             = 1,  
  10.     V4L2_MEMORY_USERPTR          = 2,  
  11.     V4L2_MEMORY_OVERLAY          = 3,  
  12. };  


    if ((ret = myuvc_free_buffers()) < 0) //如果已经有缓存就释放掉
        goto done;


    /* Bail out if no buffers should be allocated. */ 如果不分配缓冲区,则退出
    if (nbuffers == 0)
        goto done;


    /* Decrement the number of buffers until allocation succeeds. */
    for (; nbuffers > 0; --nbuffers) {
        mem = vmalloc_32(nbuffers * bufsize); //分配缓存的总大小,缓存数*缓存的大小,mem是整块内存起始地址,通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间
        if (mem != NULL)//如果
            break;
    }


    if (mem == NULL) {
        ret = -ENOMEM;
        goto done;
    }


    /* 这些缓存是一次性作为一个整体来分配的 */
    memset(&myuvc_queue, 0, sizeof(myuvc_queue)); //对存储缓存的队列进行操作


//放入2个队列
INIT_LIST_HEAD(&myuvc_queue.mainqueue);
INIT_LIST_HEAD(&myuvc_queue.irqqueue);struct list_head mainqueue;   /* 供APP消费用 */struct list_head irqqueue;    /* 供底层驱动生产用 */

//针对每一个缓存进行操作
    for (i = 0; i < nbuffers; ++i) {
        myuvc_queue.buffer[i].buf.index = i;//第几个buffer
        myuvc_queue.buffer[i].buf.m.offset = i * bufsize;//buffer的偏移值
        myuvc_queue.buffer[i].buf.length = myuvc_format.fmt.pix.sizeimage;//buffer的长度(图像的大小)
        myuvc_queue.buffer[i].buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//buffer的类型(视频捕捉类)
        myuvc_queue.buffer[i].buf.sequence = 0;
        myuvc_queue.buffer[i].buf.field = V4L2_FIELD_NONE;
        myuvc_queue.buffer[i].buf.memory = V4L2_MEMORY_MMAP;
        myuvc_queue.buffer[i].buf.flags = 0;
        myuvc_queue.buffer[i].state     = VIDEOBUF_IDLE;//buffer的状态
        init_waitqueue_head(&myuvc_queue.buffer[i].wait);//初始化等待队列,最后的视频数据是放到一个一个缓冲区,应用程序在读某个缓冲区的时候。有可能因为缓冲区还没有数据就会休眠。缓冲区里面应该有一个队列(用来存储要读这个缓冲区的进程)

参考http://blog.csdn.net/lanmanck/article/details/4770103

就是要求写资料到buffer 的 process放到 wq 这个 wait_queue 里
    }


    myuvc_queue.mem = mem;//总buffer的地址(队列里面地址)
    myuvc_queue.count = nbuffers;//队列里缓冲区的个数
    myuvc_queue.buf_size = bufsize;//每个缓冲区的大小,页对齐后的大小
    ret = nbuffers;


done:
    return ret;
}

  1. truct v4l2_buffer  
  2. {  
  3.     u32 index;          //buffer 序号  
  4.     enum v4l2_buf_type type;    //buffer 类型  
  5.     u32 byteused;           //buffer 中已使用的字节数  
  6.     u32 flags;          // 区分是MMAP 还是USERPTR  
  7.     enum v4l2_field field;  
  8.     struct timeval timestamp;   // 获取第一个字节时的系统时间  
  9.     struct v4l2_timecode timecode;  
  10.     u32 sequence; // 队列中的序号  
  11.     enum v4l2_memory memory;    //IO 方式,被应用程序设置  
  12.     union m{  
  13.         u32 offset;     // 缓冲帧地址,只对MMAP 有效  
  14.         unsigned long userptr;  
  15.     };  
  16.     u32 length;         // 缓冲帧长度  
  17.     u32 input;  
  18.     u32 reserved;  
  19. };  

(7)查询缓存

static int myuvc_vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *v4l2_buf)
{
    int ret = 0;
    
if (v4l2_buf->index >= myuvc_queue.count) {//如果索引值大于缓冲区的个数时,返回错误
ret = -EINVAL;
goto done;
}


    memcpy(v4l2_buf, &myuvc_queue.buffer[v4l2_buf->index].buf, sizeof(*v4l2_buf));//拷贝buffer


    /* 更新flags */
if (myuvc_queue.buffer[v4l2_buf->index].vma_use_count)//vma_use_count如果被mmap到用户空间就加1,就设置标志
v4l2_buf->flags |= V4L2_BUF_FLAG_MAPPED; //mmap内存映射函数把内核空间内存映射到用户空间


flags 为缓存当前状态(常见值有 V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE,分别代表当前缓存已经映射、缓存可以采集数据、缓存可以提取数据)

switch (myuvc_queue.buffer[v4l2_buf->index].state) {//根据不同的状态做不同的标志
    case VIDEOBUF_ERROR:
    case VIDEOBUF_DONE:
    v4l2_buf->flags |= V4L2_BUF_FLAG_DONE;//动作完成
    break;

//还在队列中
    case VIDEOBUF_QUEUED:
    case VIDEOBUF_ACTIVE:
    v4l2_buf->flags |= V4L2_BUF_FLAG_QUEUED;//在进行中
    break;
    case VIDEOBUF_IDLE://空闲状态
    default:
    break;
}


done:    
return ret;
}

(8)把缓存放入队列

static int myuvc_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *v4l2_buf)
{
    struct myuvc_buffer *buf;


    /* 0. APP传入的v4l2_buf可能有问题, 要做判断 *,类型判断/


if (v4l2_buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
   v4l2_buf->memory != V4L2_MEMORY_MMAP) {
return -EINVAL;
}

//索引值大于我们设定的索引值
if (v4l2_buf->index >= myuvc_queue.count) {
return -EINVAL;
}


    buf = &myuvc_queue.buffer[v4l2_buf->index];


if (buf->state != VIDEOBUF_IDLE) {
return -EINVAL;
}




    /* 1. 修改状态 */(入列)
buf->state = VIDEOBUF_QUEUED;
buf->buf.bytesused = 0;

把buf放入两个队列(注释参考后面)
    /* 2. 放入2个队列 */
    /* 队列1: 供APP使用 
     * 当缓冲区没有数据时,放入mainqueue队列
     * 当缓冲区有数据时, APP从mainqueue队列中取出
     */

list_add_tail(&buf->stream, &myuvc_queue.mainqueue);  struct list_head stream;
struct list_head irq;   

关于list_add_tail函数参考http://blog.csdn.net/qingkongyeyue/article/details/76089257

    /* 队列2: 供产生数据的函数使用
     * 当采集到数据时,从irqqueue队列中取出第1个缓冲区,存入数据
     */

list_add_tail(&buf->irq, &myuvc_queue.irqqueue);
    
return 0;
}


(9)把缓冲区取出队列

static int myuvc_vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *v4l2_buf)
{
    /* APP发现数据就绪后, 从mainqueue里取出这个buffer */


    struct myuvc_buffer *buf;
    int ret = 0;


if (list_empty(&myuvc_queue.mainqueue)) {//队列非空
ret = -EINVAL;
goto done;
}
    
buf = list_first_entry(&myuvc_queue.mainqueue, struct myuvc_buffer, stream);//取出第1个buffer


switch (buf->state) {
case VIDEOBUF_ERROR:
ret = -EIO;
case VIDEOBUF_DONE:
buf->state = VIDEOBUF_IDLE;
break;


case VIDEOBUF_IDLE:
case VIDEOBUF_QUEUED:
case VIDEOBUF_ACTIVE:
default:
ret = -EINVAL;
goto done;
}


list_del(&buf->stream);//删除第1个buf


done:
return ret;

}

#################################################

假设我们有两个队列,两个buf

产生了一个数据后,驱动程序会从irqqueqe队列里取出第一个buf1,把数据放到里面去。


app取一个数据,从队列mainqueue里取出一个buf1


APP处理完后,会把被处理的buf放入队列尾部






猜你喜欢

转载自blog.csdn.net/weixin_38807927/article/details/87871578