第2课第1.1.7节 从零写USB摄像头驱动之实现数据传输2_简单函数

原创 2018年04月17日 12:38:46
/* A2 参考 uvc_v4l2_do_ioctl */
static int myuvc_vidioc_querycap(struct file *file, void  *priv,
struct v4l2_capability *cap)
{    
    memset(cap, 0, sizeof *cap);
    strcpy(cap->driver, "myuvc");
    strcpy(cap->card, "myuvc");
    cap->version = 1;
    
    cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;  //视频捕获设备,并且不是用read/write函数来读取视频,而是利用那些ioctl来获取
    break;


return 0;

}



/* A3 列举支持哪种格式,
 * 参考:uvc_driver.c中 uvc_fmts 数组
 */
static int myuvc_vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
struct v4l2_fmtdesc *f)                              //其实就是设置该函数所传入的结构体f
{
    /* 人工查看描述符可知我们用的摄像头只支持1种格式 */
if (f->index >= 1)                                              //index0表示第一种格式
return -EINVAL;


    /* 支持什么格式呢?
     * 查看VideoStreaming Interface的描述符,
     * 得到GUID为"59 55 59 32 00 00 10 00 80 00 00 aa 00 38 9b 71"  ,根据GUID来确定格式
     */
strcpy(f->description, "4:2:2, packed, YUYV");
f->pixelformat = V4L2_PIX_FMT_YUYV;    
    
return 0;

}



/* A5 测试驱动程序是否支持某种格式, 强制设置该格式 
 * 参考: uvc_v4l2_try_format
 *       myvivi_vidioc_try_fmt_vid_cap
 */
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)
        return -EINVAL;
    
    /* 调整format的width, height, 
     * 计算bytesperline, sizeimage
     */


    /* 人工查看描述符, 确定支持哪几种分辨率 */
    f->fmt.pix.width  = frames[frame_idx].width;                  //frames和frame_idx都是前面自己定义的,因为在测试时发现第一种分辨率有问题,所以用第二种
    f->fmt.pix.height = frames[frame_idx].height;
    
f->fmt.pix.bytesperline =
(f->fmt.pix.width * bBitsPerPixel) >> 3;                          //一行多少字节数,bBitsPerPixel(在format中,而不是frame中)可以在描述符中查到是16.
f->fmt.pix.sizeimage =
f->fmt.pix.height * f->fmt.pix.bytesperline;
    
    return 0;

}



/* A6 参考 myvivi_vidioc_s_fmt_vid_cap */
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);    //try之后,f就已经被强制设置好了
if (ret < 0)
return ret;


    memcpy(&myuvc_format, f, sizeof(myuvc_format));     //然后将设置好的f赋给全局变量myuvc_format
    
    return 0;

}









/* A7 APP调用该ioctl让驱动程序分配若干个缓存, APP将从这些缓存中读到视频数据 

 * 参考: uvc_alloc_buffers

 */




/* 参考uvc_video_queue定义一些结构体 */
struct myuvc_buffer {
    struct v4l2_buffer buf;                                                //为什么要有这个v4l2_buffer结构体,因为后面的ioctl要传入这个参数
    int state;
    int vma_use_count; /* 表示是否已经被mmap ,每mmap一次,这个值就加1*/
    wait_queue_head_t wait;  /* APP要读某个缓冲区,如果无数据,在此休眠 */
struct list_head stream;
struct list_head irq;    
};


struct myuvc_queue {
    void *mem;                                                           //这一整块内存的起始地址
    int count;
    int buf_size;    
    struct myuvc_buffer buffer[32];
struct list_head mainqueue;   /* 供APP消费用 */
struct list_head irqqueue;    /* 供底层驱动生产用 */
};

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;


    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);
        if (mem != NULL)
            break;
    }


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


    /* 这些缓存是一次性作为一个整体来分配的,要对每一个缓存设置其属性 */
    memset(&myuvc_queue, 0, sizeof(myuvc_queue));


INIT_LIST_HEAD(&myuvc_queue.mainqueue);                          //初始化队列,供A10使用
INIT_LIST_HEAD(&myuvc_queue.irqqueue);


    for (i = 0; i < nbuffers; ++i) {
        myuvc_queue.buffer[i].buf.index = i;
        myuvc_queue.buffer[i].buf.m.offset = i * bufsize;
        myuvc_queue.buffer[i].buf.length = myuvc_format.fmt.pix.sizeimage;  //原始大小,并不是页对齐后的大小
        myuvc_queue.buffer[i].buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        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;
        init_waitqueue_head(&myuvc_queue.buffer[i].wait);              //初始化一个等待队列
    }


    myuvc_queue.mem = mem;
    myuvc_queue.count = nbuffers;
    myuvc_queue.buf_size = bufsize;
    ret = nbuffers;


done:
    return ret;

}



/* A10 把缓冲区放入队列, 底层的硬件操作函数将会把数据放入这个队列的缓存 
 * 参考: uvc_queue_buffer
 */
static int myuvc_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *v4l2_buf)
{
    struct myuvc_buffer *buf = &myuvc_queue.buffer[v4l2_buf->index];
    /* 1. 修改状态 */
buf->state = VIDEOBUF_QUEUED;
v4l2_buf->bytesused = 0;                              //表示没有数据


    /* 2. 放入2个队列 */
    /* 队列1: 供APP使用 
     * 当缓冲区没有数据时,放入mainqueue队列
     * 当缓冲区有数据时, APP从mainqueue队列中取出
     */
list_add_tail(&buf->stream, &myuvc_queue.mainqueue);    //将stream所代表的list_head插入mainqueue所索引的队列的尾部,stream并不需要做什么初始化,stream只是个节点


    /* 队列2: 供产生数据的函数使用
     * 当采集到数据时,从irqqueue队列中取出第1个缓冲区,存入数据
     */
list_add_tail(&buf->queue, &myuvc_queue.irqqueue);   //理解同上一个函数
    
return 0;

}




static int myuvc_get_streaming_params(struct myuvc_streaming_control *ctrl)
{
__u8 *data;
__u16 size;
int ret;
__u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
unsigned int pipe;


size = uvc_version >= 0x0110 ? 34 : 26;
data = kmalloc(size, GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
   
pipe = (GET_CUR & 0x80) ? usb_rcvctrlpipe(myuvc_udev, 0)                                  //通道,0表示的端点0
     : usb_sndctrlpipe(myuvc_udev, 0);
type |= (GET_CUR & 0x80) ? USB_DIR_IN : USB_DIR_OUT;                              //表示数据传输方向


ret = usb_control_msg(myuvc_udev, pipe, GET_CUR, type, VS_PROBE_CONTROL << 8,    //是去枚举参数,而不是提交参数,所以是VS_PROBE_CONTROL 
0 << 8 | myuvc_streaming_intf, data, size, 5000);


    if (ret < 0)
        goto done;


ctrl->bmHint = le16_to_cpup((__le16 *)&data[0]);
ctrl->bFormatIndex = data[2];
ctrl->bFrameIndex = data[3];
ctrl->dwFrameInterval = le32_to_cpup((__le32 *)&data[4]);
ctrl->wKeyFrameRate = le16_to_cpup((__le16 *)&data[8]);
ctrl->wPFrameRate = le16_to_cpup((__le16 *)&data[10]);
ctrl->wCompQuality = le16_to_cpup((__le16 *)&data[12]);
ctrl->wCompWindowSize = le16_to_cpup((__le16 *)&data[14]);
ctrl->wDelay = le16_to_cpup((__le16 *)&data[16]);
ctrl->dwMaxVideoFrameSize = get_unaligned_le32(&data[18]);
ctrl->dwMaxPayloadTransferSize = get_unaligned_le32(&data[22]);


if (size == 34) {
ctrl->dwClockFrequency = get_unaligned_le32(&data[26]);
ctrl->bmFramingInfo = data[30];
ctrl->bPreferedVersion = data[31];
ctrl->bMinVersion = data[32];
ctrl->bMaxVersion = data[33];
} else {
//ctrl->dwClockFrequency = video->dev->clock_frequency;
ctrl->bmFramingInfo = 0;
ctrl->bPreferedVersion = 0;
ctrl->bMinVersion = 0;
ctrl->bMaxVersion = 0;
}


done:
    kfree(data);
    
    return (ret < 0) ? ret : 0;

}




/* 参考: uvc_v4l2_try_format ∕uvc_probe_video 
 *       uvc_set_video_ctrl(video, probe, 1)
 */
static int myuvc_try_streaming_params(struct myuvc_streaming_control *ctrl)             //试试设置的参数能不能用
{
    __u8 *data;
    __u16 size;
    int ret;
__u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
unsigned int pipe;
    
memset(ctrl, 0, sizeof *ctrl);
    
ctrl->bmHint = 1; /* dwFrameInterval */   //为1表示在协商过程中保持FrameInterval不变
ctrl->bFormatIndex = 1;                               //本程序中只支持一种数据格式
ctrl->bFrameIndex  = frame_idx + 1;            //使用第二种分辨率
ctrl->dwFrameInterval = 333333;                  //查找USB规范可知,这是帧间隙,在描述符中搜索可知,值为333333




    size = uvc_version >= 0x0110 ? 34 : 26;
    data = kzalloc(size, GFP_KERNEL);
    if (data == NULL)
        return -ENOMEM;


    *(__le16 *)&data[0] = cpu_to_le16(ctrl->bmHint);
    data[2] = ctrl->bFormatIndex;
    data[3] = ctrl->bFrameIndex;
    *(__le32 *)&data[4] = cpu_to_le32(ctrl->dwFrameInterval);
    *(__le16 *)&data[8] = cpu_to_le16(ctrl->wKeyFrameRate);
    *(__le16 *)&data[10] = cpu_to_le16(ctrl->wPFrameRate);
    *(__le16 *)&data[12] = cpu_to_le16(ctrl->wCompQuality);
    *(__le16 *)&data[14] = cpu_to_le16(ctrl->wCompWindowSize);
    *(__le16 *)&data[16] = cpu_to_le16(ctrl->wDelay);
    put_unaligned_le32(ctrl->dwMaxVideoFrameSize, &data[18]);
    put_unaligned_le32(ctrl->dwMaxPayloadTransferSize, &data[22]);


    if (size == 34) {
        put_unaligned_le32(ctrl->dwClockFrequency, &data[26]);
        data[30] = ctrl->bmFramingInfo;
        data[31] = ctrl->bPreferedVersion;
        data[32] = ctrl->bMinVersion;
        data[33] = ctrl->bMaxVersion;
    }


    pipe = (SET_CUR & 0x80) ? usb_rcvctrlpipe(myuvc_udev, 0)
                  : usb_sndctrlpipe(myuvc_udev, 0);
    type |= (SET_CUR & 0x80) ? USB_DIR_IN : USB_DIR_OUT;


    ret = usb_control_msg(myuvc_udev, pipe, SET_CUR, type, VS_PROBE_CONTROL << 8,   //只是去尝试这些参数,并不是真正设置,因此是VS_PROBE_CONTROL 。
            0 << 8 | myuvc_streaming_intf, data, size, 5000);


    kfree(data);
    
    return (ret < 0) ? ret : 0;
    

}



/* A11 启动传输 
 * 参考: uvc_video_enable(video, 1):
 *           uvc_commit_video
 *           uvc_init_video
 */
static int myuvc_vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{
    int ret;
    
    /* 1. 向USB摄像头设置参数: 比如使用哪个format, 使用这个format下的哪个frame(分辨率) 
     * 参考: uvc_set_video_ctrl / uvc_get_video_ctrl
     * 1.1 根据一个结构体uvc_streaming_control设置数据包: 可以手工设置,也可以读出后再修改
     * 1.2 调用usb_control_msg发出数据包
     */


    /* a. 测试参数 */
    ret = myuvc_try_streaming_params(&myuvc_params);      //try后如果发现参数能用,系统则会将其他参数补齐
    printk("myuvc_try_streaming_params ret = %d\n", ret);


    /* b. 取出参数 */
    ret = myuvc_get_streaming_params(&myuvc_params);           //在set之前必须有get这一步
    printk("myuvc_get_streaming_params ret = %d\n", ret);


    /* c. 设置参数 */
    ret = myuvc_set_streaming_params(&myuvc_params);
    printk("myuvc_set_streaming_params ret = %d\n", ret);
    
    myuvc_print_streaming_params(&myuvc_params);


    /* d. 设置VideoStreaming Interface所使用的setting
     * d.1 从myuvc_params确定带宽
     * d.2 根据setting的endpoint能传输的wMaxPacketSize
     *     找到能满足该带宽的setting
     */
    /* 手工确定:
     * bandwidth = myuvc_params.dwMaxPayloadTransferSize = 1024    //在内核打印信息里能打印出这个值
     * 观察lsusb -v -d 0x1e4e:的结果:
     *                wMaxPacketSize     0x0400  1x 1024 bytes           //查看描述符,找到能满足>=1024的设置,可知第8个设置可以满足
     * bAlternateSetting       8
     */
    usb_set_interface(myuvc_udev, myuvc_streaming_intf, myuvc_streaming_bAlternateSetting);  //设置当前接口下起作用的设置
    
    /* 2. 分配设置URB */
    ret = myuvc_alloc_init_urbs();
    if (ret)
        printk("myuvc_alloc_init_urbs err : ret = %d\n", ret);


    /* 3. 提交URB以接收数据 */
for (i = 0; i < MYUVC_URBS; ++i) {
if ((ret = usb_submit_urb(myuvc_queue.urb[i], GFP_KERNEL)) < 0) {
printk("Failed to submit URB %u (%d).\n", i, ret);
myuvc_uninit_urbs();
return ret;
}
}
    
return 0;

}




/* 参考: uvc_init_video_isoc */
static int myuvc_alloc_init_urbs(void)
{
u16 psize;
u32 size;
    int npackets;                          //要传完一帧数据要传多少次
    int i;
    int j;


    struct urb *urb;


psize = wMaxPacketSize; /* 实时传输端点一次能传输的最大字节数 ,就是之前所确定的那个1024(仅是在我们这个例子中是1024,其他要重新查看)*/
size  = myuvc_params.dwMaxVideoFrameSize;  /* 一帧数据的最大长度 */
    npackets = DIV_ROUND_UP(size, psize);                //向上取整
    if (npackets > 32)
        npackets = 32;


    size = myuvc_queue.urb_size = psize * npackets;
    
    for (i = 0; i < MYUVC_URBS; ++i) {
        /* 1. 分配usb_buffers */
        
        myuvc_queue.urb_buffer[i] = usb_buffer_alloc(                          
            myuvc_udev, size,
            GFP_KERNEL | __GFP_NOWARN, &myuvc_queue.urb_dma[i]);      //urb_dma[i]是所分配缓存的物理地址


        /* 2. 分配urb */
myuvc_queue.urb[i] = usb_alloc_urb(npackets, GFP_KERNEL);     //urb是一个结构体,它一定会有一个成员指向用来存储数据的缓冲区usb_buffer,后面会把urb提交给总线驱动程序


        if (!myuvc_queue.urb_buffer[i] || !myuvc_queue.urb[i])
        {
            myuvc_uninit_urbs();
            return -ENOMEM;
        }


    }




    /* 3. 设置urb */
    for (i = 0; i < MYUVC_URBS; ++i) {
        urb = myuvc_queue.urb[i];
        
        urb->dev = myuvc_udev;
        urb->context = NULL;
        urb->pipe = usb_rcvisocpipe(myuvc_udev,myuvc_bEndpointAddress);    //查看第八个设置的描述符可知是0x81
        urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
        urb->interval = 1;                                                                                   //查看端点描述符
        urb->transfer_buffer = myuvc_queue.urb_buffer[i];
        urb->transfer_dma = myuvc_queue.urb_dma[i];
        urb->complete = myuvc_video_complete;             //驱动程序收完一帧数据之后,会产生一个中断,这个就是中断处理函数
        urb->number_of_packets = npackets;                 //这个urb要传输npackets次数据
        urb->transfer_buffer_length = size;                    //总共是多长的数据
         
        for (j = 0; j < npackets; ++j) {                                          //设置每次传输的数据存放在哪里
            urb->iso_frame_desc[j].offset = j * psize;
            urb->iso_frame_desc[j].length = psize;
        }
    
    }
    
    return 0;

}



/* 参考: uvc_video_complete / uvc_video_decode_isoc */
static void myuvc_video_complete(struct urb *urb)
{
u8 *src;
    u8 *dest;
int ret, i;
    int len;
    int maxlen;
    int nbytes;
    struct myuvc_buffer *buf;
    
switch (urb->status) {
case 0:
break;


default:
printk("Non-zero status (%d) in video "
"completion handler.\n", urb->status);
return;
}


    /* 从irqqueue队列中取出第1个缓冲区 */
if (!list_empty(&myuvc_queue.irqqueue))
{
buf = list_first_entry(&myuvc_queue.irqqueue, struct myuvc_buffer, irq); //根据irq节点在irqqueue队列中取出第一个myuvc_buffer
    


    for (i = 0; i < urb->number_of_packets; ++i) {                     //对于每次urb传输包含多少子包,检查每个子包的状态,状态正常的话就要对每个子包进行操作了
    if (urb->iso_frame_desc[i].status < 0) {
    printk("USB isochronous frame "
    "lost (%d).\n", urb->iso_frame_desc[i].status);
    continue;
    }


            src  = urb->transfer_buffer + urb->iso_frame_desc[i].offset;   //数据源,每个子包的地址,前者是指urb的起始地址


            dest = myuvc_queue.mem + buf->buf.m.offset + buf->buf.bytesused;  //目的地址


            len = urb->iso_frame_desc[i].actual_length;                    
            /* 判断数据是否有效 */
            /* URB数据含义:
             * src[0] : 头部长度
             * src[1] : 错误状态
             */
            if (len < 2 || src[0] < 2 || src[0] > len)
                continue;
            
            /* Skip payloads marked with the error bit ("error frames"). */
            if (src[1] & UVC_STREAM_ERR) {
                printk("Dropping payload (error bit set).\n");
                continue;
            }


            /* 除去头部后的数据长度 */
            len -= src[0];


            /* 缓冲区最多还能存多少数据 */
            maxlen = buf->buf.length - buf->buf.bytesused;
            nbytes = min(len, maxlen);


            /* 复制数据 */
            memcpy(dest, src + src[0], nbytes);
            buf->buf.bytesused += nbytes;


            /* 判断一帧数据是否已经全部接收到 ,意思是一帧数据是由多个子包传输的?*/
            if (len > maxlen) {
                buf->state = VIDEOBUF_DONE;
            }
            
            /* Mark the buffer as done if the EOF marker is set. */
            if (src[1] & UVC_STREAM_EOF && buf->buf.bytesused != 0) {
                printk("Frame complete (EOF found).\n");
                if (len == 0)
                    printk("EOF in empty payload.\n");
                buf->state = VIDEOBUF_DONE;
            }


    }


        /* 当接收完一帧数据, 
         * 从irqqueue中删除这个缓冲区
         * 唤醒等待数据的进程 
         */
        if (buf->state == VIDEOBUF_DONE ||
            buf->state == VIDEOBUF_ERROR)
        {
            list_del(&buf->irq);
            wake_up(&buf->wait);
        }
}


    /* 再次提交URB */
if ((ret = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
printk("Failed to resubmit video URB (%d).\n", ret);
}

}





/* A17 停止 
 * 参考 : uvc_video_enable(video, 0)
 */
static int myuvc_vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type t)
{
struct urb *urb;
unsigned int i;


    /* 1. kill URB */
for (i = 0; i < MYUVC_URBS; ++i) {
if ((urb = myuvc_queue.urb[i]) == NULL)
continue;
usb_kill_urb(urb);
}


    /* 2. free URB */
    myuvc_uninit_urbs();


    /* 3. 设置VideoStreaming Interface为setting 0 */
    usb_set_interface(myuvc_udev, myuvc_streaming_intf, 0);              //处于不工作状态
    
    return 0;
}

Python全栈工程师-第2周

-
  • 1970年01月01日 08:00

从零写USB摄像头驱动之实现数据传输2_简单函数

参考: uvc_driver.c //linux-2.6.31.14/drives/media/video/uvc/uvc_driver.c vdev->fops = &uvc_f...
  • hello__xs
  • hello__xs
  • 2018-01-26 23:30:14
  • 69

摄像头驱动之实现数据传输3_设置参数_学习笔记

1、启动传输 static int myuvc_vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i) {     i...
  • qingkongyeyue
  • qingkongyeyue
  • 2017-01-18 15:12:36
  • 1534

从0写USB摄像头驱动程序

从0写USB摄像头驱动程序 1.构造一个usb_driver结构体 .id_table .probe 1.分配video_device结构体 2.设置 3.注册 2.下面具体分析probe函数中的内容...
  • u013985662
  • u013985662
  • 2015-10-01 14:58:41
  • 1954

韦东山Linux视频驱动第2期》学习总结之第17课(USB设备驱动程序)

2012-02-29 20:02:44 分类: LINUX 驱动源码: usbmouse_as_key.zip    diff后结果: usbmouse.zip    平台:友善之臂Tin...
  • RopenYuan
  • RopenYuan
  • 2014-12-31 18:52:36
  • 1736

USB驱动----USB总线驱动程序

usb总线驱动程序的作用 1. 识别设备 1.1 分配地址 1.2 并告诉USB设备(set address) 1.3 发出命令获取描述符 设备描述符的信息在 linux-2...
  • hello__xs
  • hello__xs
  • 2017-08-23 22:20:39
  • 210

摄像头驱动框架

对应应用程序(xawtv),摄像头的操作过程: ①open打开摄像头设备(v4l2_open) ②获取或者设置摄像头的属性参数(v4l2_read_attr/v4l2_write_attr) ③...
  • o0Avalon0o
  • o0Avalon0o
  • 2016-08-25 16:00:52
  • 244

摄像头驱动之实现数据传输5_调试_学习笔记

1、实验 (1)编译好驱动程序,卸载原来的驱动,安装新驱动 (2)查看设备节点 (3)桌面环境下调用xawtv应用程序进行测试(出现段错误) (4)查看 之前存储内核信息的文本文件(显...
  • qingkongyeyue
  • qingkongyeyue
  • 2017-01-19 13:45:35
  • 424

摄像头驱动之实现数据传输1_框架_学习笔记

1、往期回顾 (1)根据描述符可知道摄像头支持哪几种格式的视频数据,某一种格式下有哪几种分辨率,摄像头是否支持某些属性。 (2)我们在PC上做实验,为了看到内核打印(printk)的信息,我们用的是d...
  • qingkongyeyue
  • qingkongyeyue
  • 2017-01-17 15:50:43
  • 690

第2课第1.1.8节 摄像头驱动_从零写USB摄像头驱动之设置属性

 硬件上怎么设置属性?1.  UVC规范里定义了哪些属性 : uvc_ctrl.c里数组: static struct uvc_control_info uvc_ctrls[],这个数组里每一项都对应...
  • hahaha_2017
  • hahaha_2017
  • 2018-04-22 11:26:36
  • 5
收藏助手
不良信息举报
您举报文章:第2课第1.1.7节 从零写USB摄像头驱动之实现数据传输2_简单函数
举报原因:
原因补充:

(最多只允许输入30个字)