前言:设置打印输出调试信息
A、dmesg命令
A. 设置ubuntu让它从串口0输出printk信息
a. 设置vmware添加serial port, 使用文件作为串口
b. 启动ubuntu,修改/etc/default/grub
GRUB_CMDLINE_LINUX_DEFAULT=""
GRUB_CMDLINE_LINUX="console=tty0 console=ttyS0,115200n8"
sudo update-grub
sudo reboot
当USB插上主机,就会产生两个接口(VC和VS),然后获取到USB描述符并解析,从而设置摄像头(比如分辨率、格式);然后分配缓冲区,启动摄像头,便从USB得到摄像头采集数据,保存到缓冲区供应用程序使用。详细过程看这两篇:UVC 设备框架在 Linux 4.15 内核的演变_v4l2 核心在尝试映射 uvc 控件时找不到相应的文件或目录-CSDN博客Linux摄像头驱动2——UVC(转)_uvc协议分主从关系吗-CSDN博客说的很清楚。
缓冲区的相关操作
首先是申请缓冲区vidioc_reqbufs(),应用层ioctl调用此函数,让其分配若干个buf,应用层后面将从这些buf读取视频数据。
驱动先从传入的v4l2_requestbuffers结构体获得count(buf数量),每个buf的大小是前面my_uvc_format的sizeimage(每帧图像大小),且长度页对齐。
/*A7 APP调用该ioctrl让驱动程序分配若干个缓存,APP将从这些缓存中读到视频数据
* 参考uvcg_alloc_buffers
* */
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;
/*如果已经有BUF则先free掉*/
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);
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;
}
判断my_uvc_queue
结构体里的mem
(内存地址)是否为空,非空的话说明原来已经分配了buf,需要先释放内存、清空my_uvc_queue
。如果传入需要的buf数量为0,则表明不需要分配,直接退出。然后就分配buf,将所有buf作为一个整体一次性分配,大小也就是nbuffers * bufsize
,如果分配失败,减小buf数量,再尝试。
现在就有了一整块buf,对应的起始地址是mem
,再清空my_uvc_queue
进行初始化。
再初始化两个队列(双向链表),mainqueue
用于供应用层读取数据用,irqqueue
用于供驱动产生数据用。
再依次设置每个buf的v4l2_buffer
结构体的index
(索引)、m.offset
(偏移)、length
(大小)、type
(类型)、sequence
(序列计数)、field
(扫描方式)、memory
(内存类型)、flags
(标志),再设置my_uvc_buffer
的state
(状态)和初始化等待队列wait
。最后再设置my_uvc_q
,记录buf首地址、数量和大小。
补充:
VideoControl Interface
用于控制,比如设置亮度。
它内部有多个Unit/Terminal(在程序里Unit/Terminal都称为entity)
可以通过uvc_query_ctrl
类似的函数来访问:
ret = uvc_query_ctrl(dev /*哪一个USB设备*/, SET_CUR, ctrl->entity->id /*哪一个unit/terminal*/, dev->intfnum /*哪一个接口:VC interface*/, ctrl->info->selector, uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT), ctrl->info->size);
VideoStreaming Interface
用于获得视频数据,也可以用来选择fromat/frame(VS可能有多种format,一个format支持多种frame,frame用来表示分辨率等信息)
可以通过__uvc_query_ctrl
类似的函数来访问:
ret = __uvc_query_ctrl(video->dev /*哪一个USB设备*/, SET_CUR, 0, video->streaming->intfnum /*哪一个接口: VS*/, probe ? VS_PROBE_CONTROL : VS_COMMIT_CONTROL, data, size, uvc_timeout_param);
我们在设置FORMAT时只是简单的使用video->streaming->format[fmt->index]等数据,这些数据哪来的?
应是设备被枚举时设置的,也就是分析它的描述符时设置的。
参考链接:https://blog.csdn.net/Parismoor/article/details/97374008