Chinaunix首页 | 论坛 | 博客
  • 博客访问: 53068
  • 博文数量: 17
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 70
  • 用 户 组: 普通用户
  • 注册时间: 2014-11-09 22:49
文章分类
文章存档

2015年(9)

2014年(8)

我的朋友

分类: LINUX

2015-02-05 22:20:22

原文地址:V4L2之usb camera 作者:steven_miao

usb camera

谨以此文纪念过往的岁月。

一.前言
usb camera会涉及两个子系统,一个usb系统,另一个video系统。本文主要是记录gspca的源码的学习,同时学习usb的isoc传输机制和video的系统。下面就是根据gspca的注册以及运行的流程来看。从linux2.6.26后,gspca的源码被添加进linux源码中,其将设备和gspca分开为两个文件,一个是设备单独的初始化文件,一个则为gspca的公共文件。本文主要是根据SN9C110和OV7648来学习usb camera的,其主要函数在sonixj.c和gspca.c中。学习这些程序并不是我们以后再usb camera的驱动中就必须按照其格式来写,其主要是借鉴其编程的方法来实现自己独自的usb camera程序设计。在学习usb camera的驱动中同时会学习usb bulk以及usb isoc驱动的编程方法以及视频驱动的编写方法。

二.v4l2子系统
usb camera主要由usb子系统和v4l2子系统构成,本文主要是讲述v4l2的应用,同时在其中掺插usb的内容。
2.1video_device结构体
通常一个video设备会通过video_device_alloc来分配内存空间,然后对其赋值。
struct video_device *vdev = video_device_alloc();
以gspca_template为例
static struct video_device gspca_template = {
    .name = "gspca main driver",       --video的name出现在sysfs和kernel log中
    .fops = &dev_fops,                 --用于cdev,这个和普通的cdev的fops没有区别
    .ioctl_ops = &dev_ioctl_ops,       --用于ioctl
    .release = video_device_release,   --释放video_device
    .minor = -1,                       --表明自动分配minor
};
可以采用memcpy将gspca_template的值赋给vdev。
2.2video_device注册
注册v4l2设备
int video_register_device(struct video_device *vfd, int type, int nr)
{
    return video_register_device_index(vfd, type, nr, -1);   --注册成功返回0
}
vfd  : 想注册的video_device结构体
type : video_device类型
            VFL_TYPE_GRABBER - A frame grabber
            VFL_TYPE_VTX - A teletext device
            VFL_TYPE_VBI - Vertical blank data (undecoded)
            VFL_TYPE_RADIO - A radio card
nr   :设备号(0 == /dev/video0, 1 == /dev/video1, ... -1 == first free)

int video_register_device_index(struct video_device *vfd, int type, int nr,int index)
index : 基于父设备的流号,-1代表自动分配
2.3 cdev的fops
video_device注册后,那就来看其video_device结构中的几个成员
其中主要一个是:static struct file_operations *fops;还是以gspca为例
static struct file_operations dev_fops = {
    .owner = THIS_MODULE,
    .open = dev_open,
    .release = dev_close,
    .read = dev_read,
    .mmap = dev_mmap,
    .ioctl = video_ioctl2,
    .llseek = no_llseek,
    .poll    = dev_poll,
};
在probe时调用了video_set_drvdata(gspca_dev->vdev, gspca_dev);
设备打开
static int dev_open(struct inode *inode, struct file *file)
{
    struct gspca_dev *gspca_dev;
    int ret;
    gspca_dev = video_drvdata(file);
    if (mutex_lock_interruptible(&gspca_dev->queue_lock))
        return -ERESTARTSYS;
    if (!gspca_dev->present) {
        ret = -ENODEV;
        goto out;
    }
    if (gspca_dev->users > 4) {              --用户最多为4
        ret = -EBUSY;
        goto out;
    }
    if (!try_module_get(gspca_dev->module)) {
        ret = -ENODEV;
        goto out;
    }
    gspca_dev->users++;
    kref_get(&gspca_dev->kref);                --多用户
    file->private_data = gspca_dev;
    ret = 0;
out:
    mutex_unlock(&gspca_dev->queue_lock);
    if (ret != 0)
        PDEBUG(D_ERR|D_STREAM, "open failed err %d", ret);
    else
        PDEBUG(D_STREAM, "open done");
    return ret;
}
open设备并没有真正打开什么,仅仅是对一些计数器进行初始值,由于v4l2支持多用户打开,所以对于用户的信息保存很重要。
static int dev_close(struct inode *inode, struct file *file)
{
    struct gspca_dev *gspca_dev = file->private_data;

    if (mutex_lock_interruptible(&gspca_dev->queue_lock))
        return -ERESTARTSYS;
    gspca_dev->users--;

    if (gspca_dev->capt_file == file) {   --如果文件是捕获,释放流资源
        if (gspca_dev->streaming) {
            mutex_lock(&gspca_dev->usb_lock);
            gspca_stream_off(gspca_dev);
            mutex_unlock(&gspca_dev->usb_lock);
        }
        frame_free(gspca_dev);              --释放当前设备所开辟的内存空间
        gspca_dev->capt_file = NULL;
        gspca_dev->memory = GSPCA_MEMORY_NO;
    }
    file->private_data = NULL;
    module_put(gspca_dev->module);
    mutex_unlock(&gspca_dev->queue_lock);
    kref_put(&gspca_dev->kref, gspca_delete);
    return 0;
}
设备读
static ssize_t dev_read(struct file *file, char __user *data,size_t count, loff_t *ppos)
{
    struct gspca_dev *gspca_dev = file->private_data;
    struct gspca_frame *frame;
    struct v4l2_buffer v4l2_buf;
    struct timeval timestamp;
    int n, ret, ret2;

    switch (gspca_dev->memory) {
    case GSPCA_MEMORY_NO:                         --第一次读,需要开辟空间,其主要原因在于设备open的时候并没有去申请资源。
        ret = read_alloc(gspca_dev, file);   --申请开辟资源
        if (ret != 0)
            return ret;
        break;
    case GSPCA_MEMORY_READ:                --内存读,保存当前file 信息。这是最慢的一种,通过copy_to_usr来实现。
        if (gspca_dev->capt_file == file)
            break;
        /* fall thru */
    default:
        return -EINVAL;
    }

  --获取一帧数据
    jiffies_to_timeval(get_jiffies_64(), ×tamp);
    timestamp.tv_sec--;
    n = 2;
    for (;;) {
        memset(&v4l2_buf, 0, sizeof v4l2_buf);
        v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        v4l2_buf.memory = GSPCA_MEMORY_READ;
        ret = vidioc_dqbuf(file, gspca_dev, &v4l2_buf);    --把缓冲区放回缓存队列
        if (ret != 0) {
            return ret;
        }

        frame = &gspca_dev->frame[v4l2_buf.index];
        if (--n < 0)                                     --避免无限循环
            break;            
        if (frame->v4l2_buf.timestamp.tv_sec >= timestamp.tv_sec)  --
            break;
        ret = vidioc_qbuf(file, gspca_dev, &v4l2_buf);     --从缓冲队列中取出缓冲区。
        if (ret != 0){
            return ret;
        }
    }

    if (count > frame->v4l2_buf.bytesused)             --复制一帧数据
        count = frame->v4l2_buf.bytesused;
    ret = copy_to_user(data, frame->data, count);
    if (ret != 0) {
        ret = -EFAULT;
        goto out;
    }
    ret = count;
out:
    ret2 = vidioc_qbuf(file, gspca_dev, &v4l2_buf);    --这段不知道为什么?  
    if (ret2 != 0)
        return ret2;
    return ret;
}

非阻塞I/O,其主要是该进程不会阻塞,而在上层被阻塞。关于如何使用的以后再看,在这里就不细究了
static unsigned int dev_poll(struct file *file, poll_table *wait);
内存映射,关于如何映射的以前学过,这里就不管了。
static int dev_mmap(struct file *file, struct vm_area_struct *vma);

2.4 ioctl
对于v4l2设备而言,ioctl很重要,几乎所有的操作都可以通过ioctl来完成,那来看几个比较重要的命令。
video_ioctl2-> __video_do_ioctl
还是以gspca为例
2.4.1 VIDIOC_QUERYCAP
VIDIOC_QUERYCAP 查询驱动功能,其最终调用函数为vidioc_querycap:
struct v4l2_capability {
    __u8    driver[16];          --驱动信息
    __u8    card[32];               --设备信息
    __u8    bus_info[32];        --所依附的总线,如usb camera 则为usb
    __u32 version;             --驱动的版本号
    __u32    capabilities;           --属性
    __u32    reserved[4];         --保留
};
static int vidioc_querycap(struct file *file, void  *priv,struct v4l2_capability *cap)
{
    struct gspca_dev *gspca_dev = priv;

    memset(cap, 0, sizeof *cap);
    strncpy(cap->driver, gspca_dev->sd_desc->name, sizeof cap->driver);
    if (gspca_dev->dev->product != NULL) {
        strncpy(cap->card, gspca_dev->dev->product,sizeof cap->card);
    } else {
        snprintf(cap->card, sizeof cap->card,"USB Camera (%04x:%04x)",le16_to_cpu(gspca_dev->dev->descriptor.idVendor),le16_to_cpu(gspca_dev->dev->descriptor.idProduct));
    }
    strncpy(cap->bus_info, gspca_dev->dev->bus->bus_name,sizeof cap->bus_info);
    cap->version = DRIVER_VERSION_NUMBER;
    cap->capabilities = V4L2_CAP_VIDEO_CAPTURE| V4L2_CAP_STREAMING | V4L2_CAP_READWRITE; --关于这几个宏定义,可以参考源码中的解释。主要数目设备的类型以及读写的属性
    return 0;
}
上面的程序很好理解,主要讲驱动的信息读到应用层。

2.4.2 VIDIOC_ENUM_FMT
获取当前驱动支持的视频格式,其视频格式描述结构体如下:
struct v4l2_fmtdesc {
    __u32            index;                   --格式索引号
    enum v4l2_buf_type  type;            --缓冲区类型
    __u32               flags;           --缓冲区标示符
    __u8            description[32];         --描述符字符串
    __u32            pixelformat;             --格式描述
    __u32            reserved[4];             --保留
};
enum v4l2_buf_type {
    V4L2_BUF_TYPE_VIDEO_CAPTURE        = 1,
    V4L2_BUF_TYPE_VIDEO_OUTPUT         = 2,
    V4L2_BUF_TYPE_VIDEO_OVERLAY        = 3,
    V4L2_BUF_TYPE_VBI_CAPTURE          = 4,
    V4L2_BUF_TYPE_VBI_OUTPUT           = 5,
    V4L2_BUF_TYPE_SLICED_VBI_CAPTURE   = 6,
    V4L2_BUF_TYPE_SLICED_VBI_OUTPUT    = 7,
    V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8,
    V4L2_BUF_TYPE_PRIVATE              = 0x80,
};
以V4L2_BUF_TYPE_VIDEO_CAPTURE为例,在下面没有特殊声明的情况下都是V4L2_BUF_TYPE_VIDEO_CAPTURE类型:
static int vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,struct v4l2_fmtdesc *fmtdesc)
{
    struct gspca_dev *gspca_dev = priv;
    int i, j, index;
    __u32 fmt_tb[8];

    index = 0;
    j = 0;
    for (i = gspca_dev->cam.nmodes; --i >= 0; ) {                --这个循环怎么感觉怪怪的,我以为这段可以完全不要的,直接采用循环检测就可以了,
        fmt_tb[index] = gspca_dev->cam.cam_mode[i].pixelformat;
        j = 0;
        for (;;) {
            if (fmt_tb[j] == fmt_tb[index])
                break;
            j++;
        }
        if (j == index) {
            if (fmtdesc->index == index)
                break;        /* new format */
            index++;
            if (index >= sizeof fmt_tb / sizeof fmt_tb[0])
                return -EINVAL;
        }
    }
    if (i < 0)
        return -EINVAL;        /* no more format */

    fmtdesc->pixelformat = fmt_tb[index];
    if (gspca_is_compressed(fmt_tb[index]))       --判断fmt是否被压缩
        fmtdesc->flags = V4L2_FMT_FLAG_COMPRESSED;
    fmtdesc->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmtdesc->description[0] = fmtdesc->pixelformat & 0xff;   
    fmtdesc->description[1] = (fmtdesc->pixelformat >> 8) & 0xff;
    fmtdesc->description[2] = (fmtdesc->pixelformat >> 16) & 0xff;
    fmtdesc->description[3] = fmtdesc->pixelformat >> 24;
    fmtdesc->description[4] = '\0';
    return 0;
}

2.4.3 VIDIOC_G_FMT
获取当前驱动的帧捕获格式
struct v4l2_format {
    enum v4l2_buf_type type;     --同样是类型
    union {
        struct v4l2_pix_format        pix;         /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
        struct v4l2_window        win;             /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
        struct v4l2_vbi_format        vbi;         /* V4L2_BUF_TYPE_VBI_CAPTURE */
        struct v4l2_sliced_vbi_format    sliced;  /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
        __u8    raw_data[200];                   /* user-defined */
    } fmt;
};
对于V4L2_BUF_TYPE_VIDEO_CAPTURE类型而言,其fmt为v4l2_pix_format类型
struct v4l2_pix_format {
    __u32                 width;       --捕获图片的宽度
    __u32                            height;      --高度
    __u32                            pixelformat; --像素点类型
    enum v4l2_field      field;       --与域
    __u32                bytesperline;  --每一行的bytes
    __u32                  sizeimage;   --图像大小
    enum v4l2_colorspace    colorspace; --色彩空间
    __u32            priv;                      --私有数据,依赖于像素点类型
};
也许上面的结构体很空洞,不知道各个成员的含义是什么,以OV7648为为例:
static struct v4l2_pix_format vga_mode[] = {
    {160, 120, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
        .bytesperline = 160,
        .sizeimage = 160 * 120 * 4 / 8 + 590,
        .colorspace = V4L2_COLORSPACE_JPEG,
        .priv = 2},
    {320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
        .bytesperline = 320,
        .sizeimage = 320 * 240 * 3 / 8 + 590,
        .colorspace = V4L2_COLORSPACE_JPEG,
        .priv = 1},
    {640, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
        .bytesperline = 640,
        .sizeimage = 640 * 480 * 3 / 8 + 590,
        .colorspace = V4L2_COLORSPACE_JPEG,
        .priv = 0},
};
static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,struct v4l2_format *fmt)
{
    struct gspca_dev *gspca_dev = priv;
    int mode;

    if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
        return -EINVAL;
    mode = gspca_dev->curr_mode;
    memcpy(&fmt->fmt.pix, &gspca_dev->cam.cam_mode[mode],sizeof fmt->fmt.pix);
    return 0;
}

2.4.4 VIDIOC_S_FMT
设置当前驱动的频捕获格式
static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,struct v4l2_format *fmt)
{
    struct gspca_dev *gspca_dev = priv;
    int ret;

    if (mutex_lock_interruptible(&gspca_dev->queue_lock))
        return -ERESTARTSYS;

    ret = try_fmt_vid_cap(gspca_dev, fmt);     --查找对应的mode
    if (ret < 0)
        goto out;

    if (gspca_dev->nframes != 0
        && fmt->fmt.pix.sizeimage > gspca_dev->frsz) {
        ret = -EINVAL;
        goto out;
    }

    if (ret == gspca_dev->curr_mode) {    --如果设置的mode与当前mode一样,则不需要重新设置,直接返回。
        ret = 0;
        goto out;            /* same mode */
    }

    if (gspca_dev->streaming) {
        ret = -EBUSY;
        goto out;
    }
    gspca_dev->width = fmt->fmt.pix.width;
    gspca_dev->height = fmt->fmt.pix.height;
    gspca_dev->pixfmt = fmt->fmt.pix.pixelformat;
    gspca_dev->curr_mode = ret;

    ret = 0;
out:
    mutex_unlock(&gspca_dev->queue_lock);
    return ret;
}
2.4.5 VIDIOC_TRY_FMT
验证当前驱动的显示格式
static int vidioc_try_fmt_vid_cap(struct file *file,void *priv,struct v4l2_format *fmt)
{
    struct gspca_dev *gspca_dev = priv;
    int ret;

    ret = try_fmt_vid_cap(gspca_dev, fmt);
    if (ret < 0)
        return ret;
    return 0;
}

static int try_fmt_vid_cap(struct gspca_dev *gspca_dev,struct v4l2_format *fmt)
{
    int w, h, mode, mode2;

    if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
        return -EINVAL;
    w = fmt->fmt.pix.width;
    h = fmt->fmt.pix.height;

    /* search the closest mode for width and height */
    mode = wxh_to_mode(gspca_dev, w, h);     --根据设定的图像宽度和高度来查询mode
    if (gspca_dev->cam.cam_mode[mode].pixelformat!= fmt->fmt.pix.pixelformat) {  --如果像素格式不匹配,则根据格式查找对应的mode
        mode2 = gspca_get_mode(gspca_dev, mode,fmt->fmt.pix.pixelformat);
        if (mode2 >= 0)
            mode = mode2;
    }
    memcpy(&fmt->fmt.pix, &gspca_dev->cam.cam_mode[mode],sizeof fmt->fmt.pix);
    return mode;            
}

static int wxh_to_mode(struct gspca_dev *gspca_dev,int width, int height)
{
    int i;
    for (i = gspca_dev->cam.nmodes; --i > 0; ) { --高度和宽度匹配
        if (width >= gspca_dev->cam.cam_mode[i].width && height >= gspca_dev->cam.cam_mode[i].height)
            break;
    }
    return i;
}
static int gspca_get_mode(struct gspca_dev *gspca_dev,int mode,int pixfmt)
{
    int modeU, modeD;

    modeU = modeD = mode;
    while ((modeU < gspca_dev->cam.nmodes) || modeD >= 0){
        if (--modeD >= 0) {
            if (gspca_dev->cam.cam_mode[modeD].pixelformat== pixfmt)
                return modeD;
        }
        if (++modeU < gspca_dev->cam.nmodes) {
            if (gspca_dev->cam.cam_mode[modeU].pixelformat== pixfmt)
                return modeU;
        }
    }
    return -EINVAL;
}

2.4.6 VIDIOC_REQBUFS
分配内存 ,以下面的调用为例:
struct v4l2_requestbuffers req;
memset(&req, 0, sizeof (req));
req.count = 10;                --缓冲区块数
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;   --采用内存映射
ioctl(fd, VIDIOC_REQBUFS, &req);
static int vidioc_reqbufs(struct file *file, void *priv,struct v4l2_requestbuffers *rb)
{
    struct gspca_dev *gspca_dev = priv;
    int i, ret = 0;

    if (rb->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
        return -EINVAL;
    switch (rb->memory) {
    case GSPCA_MEMORY_READ:            /* (internal call) */
    case V4L2_MEMORY_MMAP:
    case V4L2_MEMORY_USERPTR:
        break;
    default:
        return -EINVAL;
    }
    if (mutex_lock_interruptible(&gspca_dev->queue_lock))
        return -ERESTARTSYS;

    if (gspca_dev->memory != GSPCA_MEMORY_NO && gspca_dev->memory != rb->memory) { --分配内存时应该满足memory标志为未分配内存。
        ret = -EBUSY;
        goto out;
    }

    if (gspca_dev->capt_file != NULL && gspca_dev->capt_file != file) {
        ret = -EBUSY;
        goto out;
    }

    for (i = 0; i < gspca_dev->nframes; i++) {  --如果内存被分配了,要保证内存没有被映射
        if (gspca_dev->frame[i].vma_use_count) {   
            ret = -EBUSY;
            goto out;
        }
    }
    
    if (gspca_dev->streaming) {   --如果视频流开始显示,则要关闭
        mutex_lock(&gspca_dev->usb_lock);
        gspca_stream_off(gspca_dev);
        mutex_unlock(&gspca_dev->usb_lock);
    }
    if (gspca_dev->nframes != 0) {  --释放以前分配的内存
        frame_free(gspca_dev);
        gspca_dev->capt_file = NULL;
    }
    if (rb->count == 0)              --如果申请的内存块为0,错误
        goto out;
    gspca_dev->memory = rb->memory;        
    ret = frame_alloc(gspca_dev, rb->count); --真正的帧分配
    if (ret == 0) {
        rb->count = gspca_dev->nframes;
        gspca_dev->capt_file = file;
    }
out:
    mutex_unlock(&gspca_dev->queue_lock);
    PDEBUG(D_STREAM, "reqbufs st:%d c:%d", ret, rb->count);
    return ret;
}

static int frame_alloc(struct gspca_dev *gspca_dev,unsigned int count)
{
    struct gspca_frame *frame;
    unsigned int frsz;
    int i;

    i = gspca_dev->curr_mode;
    frsz = gspca_dev->cam.cam_mode[i].sizeimage;
    frsz = PAGE_ALIGN(frsz);        --页对齐一帧内存的大小
    gspca_dev->frsz = frsz;
    if (count > GSPCA_MAX_FRAMES)
        count = GSPCA_MAX_FRAMES;
    gspca_dev->frbuf = rvmalloc(frsz * count);  --开辟count帧内存大小
    if (!gspca_dev->frbuf) {
        return -ENOMEM;
    }
    gspca_dev->nframes = count;       --针对于上面的应用count=10;
    for (i = 0; i < count; i++) {
        frame = &gspca_dev->frame[i];
        frame->v4l2_buf.index = i;
        frame->v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        frame->v4l2_buf.flags = 0;
        frame->v4l2_buf.field = V4L2_FIELD_NONE;
        frame->v4l2_buf.length = frsz;
        frame->v4l2_buf.memory = gspca_dev->memory;
        frame->v4l2_buf.sequence = 0;
        frame->data = frame->data_end = gspca_dev->frbuf + i * frsz; --数据地址开始和结尾
        frame->v4l2_buf.m.offset = i * frsz;
    }
    gspca_dev->fr_i = gspca_dev->fr_o = gspca_dev->fr_q = 0;  --初始化input和output的值为0,没有进入和输出的framebuffer
    gspca_dev->last_packet_type = DISCARD_PACKET;    --设置包类型
    gspca_dev->sequence = 0;
    atomic_set(&gspca_dev->nevent, 0);
    return 0;
}
这里开辟的内存在物理空间上是不连续的。只是在虚拟地址上是连续的。
static void *rvmalloc(unsigned long size)
{
    void *mem;
    unsigned long adr;

    mem = vmalloc_32(size);   --开辟32位对齐的数据内存,函数的效率不是很高。
    if (mem != NULL) {
        adr = (unsigned long) mem;
        while ((long) size > 0) {
            SetPageReserved(vmalloc_to_page((void *) adr));  --设置页保留
            adr += PAGE_SIZE;
            size -= PAGE_SIZE;
        }
    }
    return mem;
}
以上就是开辟framebuffer,不过这种内存开辟方式对于实时性要求很高的系统是不合适的。一种比较快速的办法是在系统启动的时候,采用alloc_bootmem来开辟一段大的物理内存,这样采集过来的数据可以通过DMA的办法传输到缓冲区,同时可以采用内存映射的手段直接映射到用户空间。这个将在以后自己实现usb camera驱动的时候来实现具体的实现。

2.4.7 VIDIOC_QUERYBUF
查询framebuffer信息
static int vidioc_querybuf(struct file *file, void *priv,struct v4l2_buffer *v4l2_buf)
{
    struct gspca_dev *gspca_dev = priv;
    struct gspca_frame *frame;

    frame = &gspca_dev->frame[v4l2_buf->index];
    memcpy(v4l2_buf, &frame->v4l2_buf, sizeof *v4l2_buf);
    return 0;
}

2.4.8 VIDIOC_QBUF
将一个framebuffer添加入队列,用于等待数据填充,那来看一下具体的实现。
static int vidioc_qbuf(struct file *file, void *priv,struct v4l2_buffer *v4l2_buf)
{
    struct gspca_dev *gspca_dev = priv;
    struct gspca_frame *frame;
    int i, index, ret;
    frame = &gspca_dev->frame[index];
    frame->v4l2_buf.flags |= V4L2_BUF_FLAG_QUEUED;

    if (frame->v4l2_buf.memory == V4L2_MEMORY_USERPTR) {
        frame->v4l2_buf.m.userptr = v4l2_buf->m.userptr;
        frame->v4l2_buf.length = v4l2_buf->length;
    }
    index = v4l2_buf->index;
    i = gspca_dev->fr_q;        --fr_q在申请内存空间时初始化为0
    gspca_dev->fr_queue[i] = index; --保存用户空间所用的index
    gspca_dev->fr_q = (i + 1) % gspca_dev->nframes; --保存下一个需要入队的值。
    v4l2_buf->flags |= V4L2_BUF_FLAG_QUEUED;    --设置
    v4l2_buf->flags &= ~V4L2_BUF_FLAG_DONE;
    ret = 0;
out:
    mutex_unlock(&gspca_dev->queue_lock);
    return ret;
}

2.4.9 VIDIOC_DQBUF
从缓冲队列中出列一个buffer,前提是这个buffer里面有一帧数据,否则将阻塞。
static int vidioc_dqbuf(struct file *file, void *priv,struct v4l2_buffer *v4l2_buf)
{
    struct gspca_dev *gspca_dev = priv;
    struct gspca_frame *frame;
    int i, ret;

    if (!(file->f_flags & O_NONBLOCK)&& !gspca_dev->streaming && gspca_dev->users == 1)
        return -EINVAL;
 
    if (mutex_lock_interruptible(&gspca_dev->read_lock))
        return -ERESTARTSYS;

    ret = frame_wait(gspca_dev, file->f_flags & O_NONBLOCK);  --等待数据填充。
    if (ret < 0)
        goto out;
    i = ret;            
    frame = &gspca_dev->frame[i];
    if (gspca_dev->memory == V4L2_MEMORY_USERPTR) {
        if (copy_to_user((__u8 __user *) frame->v4l2_buf.m.userptr,frame->data,frame->v4l2_buf.bytesused)) {
            ret = -EFAULT;
            goto out;
        }
    }
    frame->v4l2_buf.flags &= ~V4L2_BUF_FLAG_DONE;
    memcpy(v4l2_buf, &frame->v4l2_buf, sizeof *v4l2_buf);
    ret = 0;
out:
    mutex_unlock(&gspca_dev->read_lock);
    return ret;
}

static int frame_wait(struct gspca_dev *gspca_dev,int nonblock_ing)
{
    struct gspca_frame *frame;
    int i, j, ret;

    i = gspca_dev->fr_o;    --检测是否完成一帧
    j = gspca_dev->fr_queue[i];
    frame = &gspca_dev->frame[j];
    if (frame->v4l2_buf.flags & V4L2_BUF_FLAG_DONE) {
        atomic_dec(&gspca_dev->nevent);
        goto ok;
    }
    if (nonblock_ing)            
        return -EAGAIN;

    for (;;) {
        ret = wait_event_interruptible_timeout(gspca_dev->wq,atomic_read(&gspca_dev->nevent) > 0,msecs_to_jiffies(3000)); --等待一帧完成
        if (ret <= 0) {
            if (ret < 0)
                return ret;    /* interrupt */
            return -EIO;        /* timeout */
        }
        atomic_dec(&gspca_dev->nevent);
        if (!gspca_dev->streaming || !gspca_dev->present)
            return -EIO;
        i = gspca_dev->fr_o;
        j = gspca_dev->fr_queue[i];
        frame = &gspca_dev->frame[j];
        if (frame->v4l2_buf.flags & V4L2_BUF_FLAG_DONE)
            break;
    }
ok:
    gspca_dev->fr_o = (i + 1) % gspca_dev->nframes;
    if (gspca_dev->sd_desc->dq_callback) {
        mutex_lock(&gspca_dev->usb_lock);
        gspca_dev->sd_desc->dq_callback(gspca_dev);
        mutex_unlock(&gspca_dev->usb_lock);
    }
    return j;
}
那我们现在再来回顾一下gspca_dev中三个参数含义
fr_o下一个出队的index
fr_i下一个入队的index
fr_queue用于保存index所代表的帧号。

上面的入队和出队比较好理解,将这两个函数联合起来看就可以很容易的去理解了。

2.4.10 VIDIOC_STREAMON
视频流打开,这个从字面意思上去理解很难理解,那来看源码会好很多。
static int vidioc_streamon(struct file *file, void *priv,enum v4l2_buf_type buf_type)
{
    struct gspca_dev *gspca_dev = priv;
    int ret;
    
    if (mutex_lock_interruptible(&gspca_dev->queue_lock))
        return -ERESTARTSYS;
    if (!gspca_dev->present) {
        ret = -ENODEV;
        goto out;
    }
    if (gspca_dev->nframes == 0) {
        ret = -EINVAL;
        goto out;
    }
    if (!gspca_dev->streaming) {
        ret = gspca_init_transfer(gspca_dev);
        if (ret < 0)
            goto out;
    }
    ret = 0;
out:
    mutex_unlock(&gspca_dev->queue_lock);
    return ret;
}
这个函数的核心是gspca_init_transfer。
static int gspca_init_transfer(struct gspca_dev *gspca_dev)
{
    struct usb_host_endpoint *ep;
    int n, ret;

    if (mutex_lock_interruptible(&gspca_dev->usb_lock))
        return -ERESTARTSYS;

    gspca_dev->alt = gspca_dev->nbalt;
    for (;;) {
        ep = get_ep(gspca_dev);     --获取一个可用的endpoint,这里面支持两种一种isoc一种bulk
        if (ep == NULL) {
            ret = -EIO;
            goto out;
        }
        ret = create_urbs(gspca_dev, ep); --创建urb
        if (ret < 0)
            goto out;
        ret = gspca_dev->sd_desc->start(gspca_dev);  --打开camera
        if (ret < 0) {
            destroy_urbs(gspca_dev);
            goto out;
        }
        gspca_dev->streaming = 1;    --设置流 已被打开
        atomic_set(&gspca_dev->nevent, 0);

        if (gspca_dev->alt == 0)    --如果是bulk传输则在子系统中就开始
            break;

        for (n = 0; n < gspca_dev->nurbs; n++) {    --以下是提交isoc urb
            ret = usb_submit_urb(gspca_dev->urb[n], GFP_KERNEL);
            if (ret < 0) {
                gspca_dev->streaming = 0;
                destroy_urbs(gspca_dev);
                if (ret == -ENOSPC)
                    break;    /* try the previous alt */
                goto out;
            }
        }
        if (ret >= 0)
            break;
    }
out:
    mutex_unlock(&gspca_dev->usb_lock);
    return ret;
}
创建urb,下面就是usb中创建urb的一个很好的例程。
static int create_urbs(struct gspca_dev *gspca_dev,struct usb_host_endpoint *ep)
{
    struct urb *urb;
    int n, nurbs, i, psize, npkt, bsize;

    psize = le16_to_cpu(ep->desc.wMaxPacketSize);   --计算最大包数

    if (gspca_dev->alt != 0) {            --这个值在probe的时候就被设置,这个是根据interface->num_altsetting决定,在SN9C110中这个值为0,即采用bulk传输
        psize = (psize & 0x07ff) * (1 + ((psize >> 11) & 3));
        npkt = ISO_MAX_SIZE / psize;
        if (npkt > ISO_MAX_PKT)
            npkt = ISO_MAX_PKT;
        bsize = psize * npkt;
        nurbs = DEF_NURBS;
    } else {                /* bulk */
        npkt = 0;
        bsize = gspca_dev->cam.bulk_size;
        if (bsize == 0)
            bsize = psize;
        nurbs = 1;
    }
    gspca_dev->nurbs = nurbs;
    for (n = 0; n < nurbs; n++) {
        urb = usb_alloc_urb(npkt, GFP_KERNEL);
        if (!urb) {
            destroy_urbs(gspca_dev);
            return -ENOMEM;
        }
        urb->transfer_buffer = usb_buffer_alloc(gspca_dev->dev,bsize,GFP_KERNEL,&urb->transfer_dma);  --开辟DMA空间
        if (urb->transfer_buffer == NULL) {
            usb_free_urb(urb);
            destroy_urbs(gspca_dev);
            return -ENOMEM;
        }
        gspca_dev->urb[n] = urb;
        urb->dev = gspca_dev->dev;
        urb->context = gspca_dev;
        urb->transfer_buffer_length = bsize;
        if (npkt != 0) {        /* ISOC */
            urb->pipe = usb_rcvisocpipe(gspca_dev->dev,ep->desc.bEndpointAddress);
            urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
            urb->interval = ep->desc.bInterval;
            urb->complete = isoc_irq;
            urb->number_of_packets = npkt;
            for (i = 0; i < npkt; i++) {
                urb->iso_frame_desc[i].length = psize;
                urb->iso_frame_desc[i].offset = psize * i;
            }
        } else {        /* bulk */
            urb->pipe = usb_rcvbulkpipe(gspca_dev->dev,ep->desc.bEndpointAddress),
            urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
            urb->complete = bulk_irq;
        }
    }
    return 0;
}
上面就是几个重要的cmd,剩下的一部分是很重要的数据填充部分。这个部分需要好好的理解,不过这个前提是理解JPEG格式。每次数据的填充主要是在urb完成的处理函数中。如isoc为isoc_irq,bulk为bulk_irq。我们以bulk_irq为例。
static void bulk_irq(struct urb *urb)
{
    struct gspca_dev *gspca_dev = (struct gspca_dev *) urb->context;
    struct gspca_frame *frame;
    if (!gspca_dev->streaming)
        return;
    if (urb->status != 0 && urb->status != -ECONNRESET) {
        return;        /* disconnection ? */
    }

    frame = gspca_get_i_frame(gspca_dev);  --检查入队的buffer是否可用
    if (!frame) {
        gspca_dev->last_packet_type = DISCARD_PACKET;
    } else {
        gspca_dev->sd_desc->pkt_scan(gspca_dev,frame,urb->transfer_buffer,urb->actual_length);
    }
}
最后一个函数指针指向该函数
static void sd_pkt_scan(struct gspca_dev *gspca_dev,struct gspca_frame *frame,__u8 *data,int len)    
{
    struct sd *sd = (struct sd *) gspca_dev;
    int sof, avg_lum;

    sof = len - 64;
    if (sof >= 0 && data[sof] == 0xff && data[sof + 1] == 0xd9) {  --查询是否为帧尾
        gspca_frame_add(gspca_dev, LAST_PACKET,frame, data, sof + 2);
        if (sd->ag_cnt < 0)
            return;
/* w1 w2 w3 */
/* w4 w5 w6 */
/* w7 w8 */
/* w4 */
        avg_lum = ((data[sof + 29] << 8) | data[sof + 30]) >> 6;
/* w6 */
        avg_lum += ((data[sof + 33] << 8) | data[sof + 34]) >> 6;
/* w2 */
        avg_lum += ((data[sof + 25] << 8) | data[sof + 26]) >> 6;
/* w8 */
        avg_lum += ((data[sof + 37] << 8) | data[sof + 38]) >> 6;
/* w5 */
        avg_lum += ((data[sof + 31] << 8) | data[sof + 32]) >> 4;
        avg_lum >>= 4;
        atomic_set(&sd->avg_lum, avg_lum);
        return;
    }
    if (gspca_dev->last_packet_type == LAST_PACKET) {  --如果上一次的包类型为最后一包,则重新填充一帧

        /* put the JPEG 422 header */
        jpeg_put_header(gspca_dev, frame, sd->qindex, 0x21);
    }
    gspca_frame_add(gspca_dev, INTER_PACKET, frame, data, len);  --如果不是上面两种则直接填充。
}

对于填充一帧数据对于包类型而言,FIRST_PACKET和LAST_PACKET是必须的,而INTER_PACKET可以没有也可以有多个。
struct gspca_frame *gspca_frame_add(struct gspca_dev *gspca_dev,
                    enum gspca_packet_type packet_type,
                    struct gspca_frame *frame,
                    const __u8 *data,
                    int len)
{
    int i, j;
    if (packet_type == FIRST_PACKET) {
        if ((frame->v4l2_buf.flags & BUF_ALL_FLAGS)!= V4L2_BUF_FLAG_QUEUED) {
            gspca_dev->last_packet_type = DISCARD_PACKET;
            return frame;
        }
        frame->data_end = frame->data;
        jiffies_to_timeval(get_jiffies_64()&frame->v4l2_buf.timestamp);
        frame->v4l2_buf.sequence = ++gspca_dev->sequence;
    } else if (gspca_dev->last_packet_type == DISCARD_PACKET) {  --这个对于第一帧图像而言有极其重要的作用。
        if (packet_type == LAST_PACKET)
            gspca_dev->last_packet_type = packet_type;
        return frame;
    }

    if (len > 0) {
        if (frame->data_end - frame->data + len> frame->v4l2_buf.length) {
            packet_type = DISCARD_PACKET;    --错误将整个帧无效
        }else{
            memcpy(frame->data_end, data, len);
            frame->data_end += len;
        }
    }
    gspca_dev->last_packet_type = packet_type;

    if (packet_type == LAST_PACKET) {
        frame->v4l2_buf.bytesused = frame->data_end - frame->data;  --记录一帧的长度
        frame->v4l2_buf.flags &= ~V4L2_BUF_FLAG_QUEUED;
        frame->v4l2_buf.flags |= V4L2_BUF_FLAG_DONE;
        atomic_inc(&gspca_dev->nevent);
        wake_up_interruptible(&gspca_dev->wq);       --这儿唤醒wq的等待,这个在frame_wait中阻塞。
        i = (gspca_dev->fr_i + 1) % gspca_dev->nframes; --下个等待存储数据的frame
        gspca_dev->fr_i = i;
        j = gspca_dev->fr_queue[i];
        frame = &gspca_dev->frame[j];
    }
    return frame;    --返回帧信息。
}

三.总结
到此usb camera可以说看完了,但是距离自己编写自己单独的usb camera驱动还有一定的距离。但是还是有收获的,对于V4L2有了比较深的理解。对其中的几个重要的cmd有了深入的了解。


阅读(1262) | 评论(0) | 转发(0) |
0

上一篇:linux input 子系统分析 三

下一篇:没有了

给主人留下些什么吧!~~