第三阶段应用层——2.4 视频监控—从0写USB摄像头驱动(2)-实现数据传输(初步)
视频监控—从0写USB摄像头驱动(2)-实现数据传输(初步)
- 硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)
- 软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
- 参考资料:USB_Video_Example 1.5、UVC 1.5 Class specification
- 开发环境:Linux-4.13.0-41内核(虚拟机)、arm-linux-gcc-4.3.2工具链
- 源码仓库:https://gitee.com/d_1254436976/Embedded-Linux-Phase-3
目录
视频监控—从0写USB摄像头驱动(2)-实现数据传输(初步) - 一、前言
- 二、程序编写
- 1、 初步框架
- 1.1 构造`usb_driver`结构体
- 1.2 设置`usb_driver`结构体
- 1.3 注册`usb_driver`结构体
- 1.4 完整框架
- 2、实现初步数据传输
- 2.1 程序的整体调用顺序
- 2.2 缓冲区的分配与运作问题
- 2.3 USB摄像头控制信息的测试、取出、设置
- 2.4 USB摄像头视频流参数设置
- 3、完整函数
- 三、编译与运行
一、前言
通过【2.1 视频监控—V4L2框架的简单分析】和【2.3 视频监控—uvc驱动框架分析】两篇博文的介绍我们可以知道,对于一个USB摄像头驱动,编写步骤主要如下:
-
构造结构体:
usb_driver
结构体 -
设置结构体:设置其成员变量
.name
= xxx,
.disconnect
= xxx_disconnet,
.id_table
= xxx,
.probe
= xxx_probe,在
xxx_probe()
中进行:
2.1. 分配video_device
结构体 = video_device_alloc
2.2. 设置video_device
结构体.fops
.ioctl_ops (里面需要设置11项)
如果要用内核提供的缓冲区操作函数,还需要构造一个videobuf_queue_ops
2.3. 注册video_device
结构体: video_register_device -
注册结构体:
usb_register()
在实际的工作和应用中,尽量使用内核带有的驱动程序,对于没有适配的驱动程序才自己开发,这个usb驱动程序只是学习的时候使用。
二、程序编写
1、 初步框架
1.1 构造usb_driver
结构体
struct usb_driver myuvc_driver = {.name = "myuvcvideo",.probe = myuvc_probe,.disconnect = myuvc_disconnect,.id_table = myuvc_ids,
};
1.2 设置usb_driver
结构体
/*!* 所支持usb设备类的接口*/
static struct usb_device_id myuvc_ids[] = {/* Generic USB Video Class */{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, 0) }, /**< VideoControl interface */{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 2, 0) }, /**< VideoStreaming interface */{}
};static int myuvc_probe(struct usb_interface *intf,const struct usb_device_id *id)
{ static int s_cnt = 0;printk("myuvc_probe : cnt = %d\n", s_cnt++);/*!* myuvc_probe()调用第二次后执行*/if (s_cnt == 2) {/* 1、分配一个video_device结构体 */s_myuvc_vdev = video_device_alloc();/* 2、设置 *//* 注册过程需要用到release,必须设置 */s_myuvc_vdev->release = myuvc_release;s_myuvc_vdev->fops = &myuvc_fops;s_myuvc_vdev->ioctl_ops = &myuvc_ioctl_ops;/* 3、注册结构体 * -1 - 自动分配次设备号*/video_register_device(s_myuvc_vdev, VFL_TYPE_GRABBER, -1);}return 0;
}static void myuvc_disconnect(struct usb_interface *intf)
{static int s_cnt = 0;printk("myuvc_disconnect : cnt = %d\n", s_cnt++);/*!* myuvc_disconnect()调用第二次后执行*/if (s_cnt == 2) {/* 注销结构体 */video_unregister_device(s_myuvc_vdev);/* 释放结构体 */video_device_release(s_myuvc_vdev); }
}
1.3 注册usb_driver
结构体
static int myuvc_init(void)
{int result;result = usb_register(&myuvc_driver);if (result < 0)printk("USB register error!\n");return result;
}
1.4 完整框架
/******************************************************************************** Copyleft (c) 2021 Kcode** @file myuvc.c* @brief 实现USB摄像头的数据传输(框架)* @author K* @version 0.0.1* @date 2021-07-21* @license MulanPSL-1.0** 文件修改历史:* <时间> | <版本> | <作者> | <描述>* 2021-07-21 | v0.0.1 | Kcode | 实现USB摄像头的数据传输(框架)* -----------------------------------------------------------------------------******************************************************************************/#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/videodev2.h>
#include <linux/vmalloc.h>
#include <linux/wait.h>
#include <asm/atomic.h>
#include <asm/unaligned.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-common.h>static struct video_device *s_myuvc_vdev; /**< video *//*!* 所支持usb设备类的接口*/
static struct usb_device_id myuvc_ids[] = {/* Generic USB Video Class */{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, 0) }, /**< VideoControl interface */{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 2, 0) }, /**< VideoStreaming interface */{}
};/*!* Step1 - 打开myuvc_fops设备文件*/
static int myuvc_open(struct file *file)
{return 0;
}/*!* 关闭myuvc_fops设备文件*/
static int myuvc_close(struct file *file)
{return 0;
}/*!* Step2 - 查询是否为USB摄像头设备*/
static int myuvc_vidioc_querycap(struct file *file, void *priv,struct v4l2_capability *cap)
{return 0;
}/*!* Step3 - 列举USB摄像头设备所支持的格式format*/
static int myuvc_vidioc_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f)
{return 0;
}/*!* Step4 - 返回当前所使用的格式*/
static int myuvc_vidioc_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
{return (0);
}/*!* Step5 - 测试驱动程序是否支持某种格式*/
static int myuvc_vidioc_try_fmt_vid_cap(struct file *file,void *priv, struct v4l2_format *f)
{return 0;
}/*!* Step6 - 设置所支持的格式*/
static int myuvc_vidioc_s_fmt_vid_cap(struct file *file,void *priv, struct v4l2_format *f)
{ return 0;
}/*!* Step7 - 为该设备申请若干个缓冲区,分配头部信息*/
static int myuvc_vidioc_reqbufs(struct file *file,void *priv, struct v4l2_requestbuffers *p)
{return 0;
}/*!* Step8 - 查询缓冲区的信息,如大小、偏移地址等* 得到信息后,APP可mmap进行地址映射,分配真正的存储数据的缓冲区*/
static int myuvc_vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
{return 0;
}/*!* Step9 - APPmmap进行地址映射可直接操作这块内存,分配真正的存储数据的缓冲区*/
static int myuvc_mmap(struct file *file, struct vm_area_struct *vma)
{return 0;
}/*!* Step10 - 把申请的缓冲区放入队列,底层的硬件操作函数将会把数据放入队列*/
static int myuvc_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{return 0;
}/*!* Step11 - 启动数据传输*/
static int myuvc_vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{return 0;
}/*!* Step12 - APP调用poll/select确定缓存是否有数据*/
static unsigned int myuvc_poll(struct file *file, struct poll_table_struct *wait)
{return 0;
}/*!* Step13 - APP通过poll/select确定缓冲区有数据后,从队列中取出并删除缓冲区*/
static int myuvc_vidioc_dqbuf(struct file *file,void *priv, struct v4l2_buffer *p)
{return 0;
}/*!* Step14 - APP已经mmap映射缓存,可直接读数据* Step15 - 再次调用myuvc_vidioc_dqbuf(),把缓存尾插法放入队列* Step16 - 在其调用myuvc_poll()*//*!* Step17 - 不使用时,停止摄像头数据传输*/
static int myuvc_vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
{return 0;
}/*!* 所支持的ioclt函数*/
static const struct v4l2_ioctl_ops myuvc_ioctl_ops = {// 表示它是一个摄像头设备.vidioc_querycap = myuvc_vidioc_querycap,/* 用于列举、获得、测试、设置摄像头的数据的格式 */.vidioc_enum_fmt_vid_cap = myuvc_vidioc_enum_fmt_vid_cap,.vidioc_g_fmt_vid_cap = myuvc_vidioc_g_fmt_vid_cap,.vidioc_try_fmt_vid_cap = myuvc_vidioc_try_fmt_vid_cap,.vidioc_s_fmt_vid_cap = myuvc_vidioc_s_fmt_vid_cap,/* 缓冲区操作: 申请/查询/放入队列/取出队列 */.vidioc_reqbufs = myuvc_vidioc_reqbufs,.vidioc_querybuf = myuvc_vidioc_querybuf,.vidioc_qbuf = myuvc_vidioc_qbuf,.vidioc_dqbuf = myuvc_vidioc_dqbuf,/* 启动/停止 */.vidioc_streamon = myuvc_vidioc_streamon,.vidioc_streamoff = myuvc_vidioc_streamoff,
};static const struct v4l2_file_operations myuvc_fops = {.owner = THIS_MODULE,.open = myuvc_open,.release = myuvc_close,.mmap = myuvc_mmap,.unlocked_ioctl = video_ioctl2,.poll = myuvc_poll,
};static void myuvc_release(struct video_device *vdev)
{}static int myuvc_probe(struct usb_interface *intf,const struct usb_device_id *id)
{ static int s_cnt = 0;printk("myuvc_probe : cnt = %d\n", s_cnt++);/*!* myuvc_probe()调用第二次后执行*/if (s_cnt == 2) {/* 1、分配一个video_device结构体 */s_myuvc_vdev = video_device_alloc();/* 2、设置 *//* 注册过程需要用到release,必须设置 */s_myuvc_vdev->release = myuvc_release;s_myuvc_vdev->fops = &myuvc_fops;s_myuvc_vdev->ioctl_ops = &myuvc_ioctl_ops;/* 3、注册结构体 * -1 - 自动分配次设备号*/video_register_device(s_myuvc_vdev, VFL_TYPE_GRABBER, -1);}return 0;
}static void myuvc_disconnect(struct usb_interface *intf)
{static int s_cnt = 0;printk("myuvc_disconnect : cnt = %d\n", s_cnt++);/*!* myuvc_disconnect()调用第二次后执行*/if (s_cnt == 2) {/* 注销结构体 */video_unregister_device(s_myuvc_vdev);/* 释放结构体 */video_device_release(s_myuvc_vdev); }
}struct usb_driver myuvc_driver = {.name = "myuvcvideo",.probe = myuvc_probe,.disconnect = myuvc_disconnect,.id_table = myuvc_ids,
};static int myuvc_init(void)
{int result;result = usb_register(&myuvc_driver);if (result < 0)printk("USB register error!\n");return result;
}static void myuvc_cleanup(void)
{ usb_deregister(&myuvc_driver);
}module_init(myuvc_init);
module_exit(myuvc_cleanup);MODULE_LICENSE("GPL");
2、实现初步数据传输
2.1 程序的整体调用顺序
- 在这个程序中,注册了
usb_driver
结构体,把驱动加载到内核中; - 插入USB摄像头,内核自动从该
usb_driver
结构体的.id_table
中找到是否支持这个接口; - 支持该设备接口后,调用该
usb_driver
结构体的.probe
函数:
3.1. 分配、设置、注册video_device
结构体
3.2. 调用自定义的.vidioc_streamon
的ioclt函数:测试、设置、打印USB摄像头的参数
通过打印出来的USB摄像头参数来调试程序。
2.2 缓冲区的分配与运作问题
- 存储缓冲区信息的结构体:
/*!* 缓冲区的信息*/
typedef struct myuvc_buffer {int state; /**< 状态位 */int vma_use_count; /**< 是否已经被mmap */struct v4l2_buffer buf; /**< 存储每个缓冲区的查询信息 */wait_queue_head_t wait; /**< APP读取某个缓冲区,如果无数据,在此休眠 */struct list_head stream; /**< mainqueue队列结点,供APP消费用 */struct list_head irq; /**< irqqueue队列结点,供底层驱动生成数据用*/
}MYUVC_BUFFER_S; /*!* 存储分配的整块缓冲区*/
typedef struct mvuvc_video_queue {int count; /**< 分配缓冲区个数 */int buf_size; /**< 每个缓冲区(页对齐)大小 */void *mem; /**< 存储分配的内存 */MYUVC_BUFFER_S buffer[32]; /**< 存储每个缓冲区的信息 */struct list_head mainqueue; /**< mainqueue队列头结点,供APP消费用 */struct list_head irqqueue; /**< irqqueue队列头结点,供底层驱动生成用*/
}MYUVC_VIDEO_QUEUE_S;
-
在
.vidioc_reqbufs
函数中分配的是整个大的缓存区MYUVC_VIDEO_QUEUE_S.mem
:缓冲区的数量MYUVC_VIDEO_QUEUE_S.count
x 每个缓冲区头部MYUVC_VIDEO_QUEUE_S.buffer[ ]
-
每个缓冲区都需要进入两个队列:
mainqueue队列
(供APP消费用)和irqqueue队列
(供底层驱动生成用),其实际操作如下:
- 函数实现:
/*!* @brief Step7 - 为该设备申请若干个缓冲区,分配头部信息* @return 正数:返回成功分配内存的大小,负数:分配失败*/
static int myuvc_vidioc_reqbufs(struct file *file,void *priv, struct v4l2_requestbuffers *p)
{int buf_num = p->count ;int buf_size_unalign = s_myuvc_format.fmt.pix.sizeimage;int buf_size_align = PAGE_ALIGN(s_myuvc_format.fmt.pix.sizeimage);unsigned int i;void *mem = NULL;int ret;if (buf_num > UVC_MAX_VIDEO_BUFFERS)buf_num = UVC_MAX_VIDEO_BUFFERS;/* 释放之前分配的缓存 */if ((ret = myuvc_free_buffers()) < 0)goto done;/* 如果不分配缓冲区,则退出 */if (buf_num == 0)goto done;/* 减少缓冲区的数量,直到分配成功 */for (; buf_num > 0; --buf_num) {mem = vmalloc_32(buf_num * buf_size_align);if (mem != NULL)break;}if (mem == NULL) {ret = -ENOMEM;goto done;}memset(&s_myuvc_queue, 0, sizeof(s_myuvc_queue));/*!* 初始化mainqueue和irqqueue队列*/INIT_LIST_HEAD(&s_myuvc_queue.mainqueue);INIT_LIST_HEAD(&s_myuvc_queue.irqqueue);/*!* 缓存是一次性分配一个大的整体* 需分别设置每个缓存的信息*/for (i = 0; i < buf_num; ++i) {s_myuvc_queue.buffer[i].buf.index = i;s_myuvc_queue.buffer[i].buf.m.offset = i * buf_size_align;s_myuvc_queue.buffer[i].buf.length = buf_size_unalign;s_myuvc_queue.buffer[i].buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;s_myuvc_queue.buffer[i].buf.sequence = 0;s_myuvc_queue.buffer[i].buf.field = V4L2_FIELD_NONE;s_myuvc_queue.buffer[i].buf.memory = V4L2_MEMORY_MMAP;s_myuvc_queue.buffer[i].buf.flags = 0;/* 空闲状态 */s_myuvc_queue.buffer[i].state = VIDEOBUF_IDLE;/* 初始化队列 */init_waitqueue_head(&s_myuvc_queue.buffer[i].wait);}s_myuvc_queue.mem = mem;s_myuvc_queue.count = buf_num;s_myuvc_queue.buf_size = buf_size_align;ret = buf_num;done:return ret;
}/*!* @brief Step10 - 把申请的缓冲区放入队列,底层的硬件操作函数将会把数据放入队列*/
static int myuvc_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *v4l2_buf)
{struct myuvc_buffer *buf = &s_myuvc_queue.buffer[v4l2_buf->index];/*!* 修改状态:处于队列状态,且缓冲区数据为空*/buf->state = VIDEOBUF_QUEUED;v4l2_buf->bytesused = 0;/*!* 队列一:供APP使用* 当缓冲区没有数据时,放入mainqueue队列* 当缓冲区有数据时,APP从mainqueue队列中取出*/list_add_tail(&buf->stream, &s_myuvc_queue.mainqueue);/*!* 队列二:供产生数据的函数使用* 当采集到数据时,从irqqueue队列中取出第一个缓冲区,存入数据*/list_add_tail(&buf->irq, &s_myuvc_queue.irqqueue);return 0;
}
2.3 USB摄像头控制信息的测试、取出、设置
对于一个usb设备,需要调用usb_control_msg()
:允许一个驱动发送和结束USB控制信息。
- 测试该设备是否支持指定的参数:这些参数已经在【2.4 视频监控—从0写USB摄像头驱动(1)-描述符的分析与打印】中已获得
1.1 需要先设置好参数,
1.2 调用usb_control_msg()
发送这个设置好的数据包
/*!* @brief 发送数据包参数,测试设备是否支持* @return 0:成功,负数:错误*/
static int myuvc_try_streaming_params(MYUVC_STREAMING_CONTROL_S *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 */ctrl->bFormatIndex = 1;ctrl->bFrameIndex = s_frame_idx + 1;ctrl->dwFrameInterval = 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(s_myuvc_udev, 0): usb_sndctrlpipe(s_myuvc_udev, 0);type |= (SET_CUR & 0x80) ? USB_DIR_IN : USB_DIR_OUT;/*!* 后发送数据*/ret = usb_control_msg(s_myuvc_udev, pipe, SET_CUR, type, VS_PROBE_CONTROL << 8,0 << 8 | s_myuvc_streaming_intf, data, size, 5000);kfree(data);return (ret < 0) ? ret : 0;
}
- 取出设备支持的参数: 由于已经测试了设备所支持的参数,现在需要获得测试中成功的参数
2.1 调用usb_control_msg()
获得数据包
2.2 根据数据包设置参数
/*!* @brief 根据数据包的数据获得当前设备的参数* @return 0:成功,负数:错误*/
static int myuvc_get_streaming_params(MYUVC_STREAMING_CONTROL_S *ctrl)
{__u8 *data;__u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;__u16 size;int ret = 0;unsigned int pipe; /**< 端点 *//*!* 根据uvc设备版本设置数据宽度*/size = uvc_version >= 0x0110 ? 34 : 26;data = kmalloc(size, GFP_KERNEL);if (data == NULL)return -ENOMEM;/* 确定端点 */pipe = (GET_CUR & 0x80) ? usb_rcvctrlpipe(s_myuvc_udev, 0): usb_sndctrlpipe(s_myuvc_udev, 0);/* 确定类型 */type |= (GET_CUR & 0x80) ? USB_DIR_IN : USB_DIR_OUT;ret = usb_control_msg(s_myuvc_udev, pipe, GET_CUR, type, VS_PROBE_CONTROL << 8,0 << 8 | s_myuvc_streaming_intf, data, size, 500);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;
}
- 设置设备支持的参数:上述步骤已经获得了设备所支持的参数,现在要把参数设置进设备中
3.1 根据上述获得的参数设置数据包
3.2 调用usb_control_msg()
,发送数据包
/*!* @brief 设置数据包参数,测试设备是否支持* @return 0:成功,负数:错误*/
static int myuvc_set_streaming_params(MYUVC_STREAMING_CONTROL_S *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 = 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(s_myuvc_udev, 0): usb_sndctrlpipe(s_myuvc_udev, 0);type |= (SET_CUR & 0x80) ? USB_DIR_IN : USB_DIR_OUT;/*!* 后发送数据*/ret = usb_control_msg(s_myuvc_udev, pipe, SET_CUR, type, VS_COMMIT_CONTROL << 8,0 << 8 | s_myuvc_streaming_intf, data, size, 5000);kfree(data);return (ret < 0) ? ret : 0;
}
2.4 USB摄像头视频流参数设置
由于这些参数已经在【2.4 视频监控—从0写USB摄像头驱动(1)-描述符的分析与打印】中已获得,直接计算好对应参数,调用usb_set_interface()
函数即可
/*!* 手工确定:* bandwidth = s_myuvc_params.dwMaxPayloadTransferSize = 1024* 观察lsusb -v -d 0x1e4e:的结果:* wMaxPacketSize 0x0400 1x 1024 bytes* bAlternateSetting 8*/usb_set_interface(s_myuvc_udev, s_myuvc_streaming_intf, s_myuvc_streaming_setting);
3、完整函数
/******************************************************************************** Copyleft (c) 2021 Kcode** @file myuvc.c* @brief 实现USB摄像头的数据传输(设置参数)* @author K* @version 0.0.1* @date 2021-07-22* @license MulanPSL-1.0** 文件修改历史:* <时间> | <版本> | <作者> | <描述>* 2021-07-22 | v0.0.1 | Kcode | 实现USB摄像头的数据传输(设置参数)* -----------------------------------------------------------------------------******************************************************************************/#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/videodev2.h>
#include <linux/vmalloc.h>
#include <linux/wait.h>
#include <asm/atomic.h>
#include <asm/unaligned.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-common.h>
#include <media/videobuf-core.h>#include "uvcvideo.h"#define MYDRIVER_VERSION_NUMBER 1 /**< myuvc版本 *//*!* 分辨率描述*/
typedef struct frame_desc {int width; /**< x分辨率 */int height; /**< y分辨率 */
} FRAME_DESC_S;/*!* uvc数据流控制结构体*/
typedef struct myuvc_streaming_control {__u16 bmHint;__u8 bFormatIndex;__u8 bFrameIndex;__u32 dwFrameInterval;__u16 wKeyFrameRate;__u16 wPFrameRate;__u16 wCompQuality;__u16 wCompWindowSize;__u16 wDelay;__u32 dwMaxVideoFrameSize;__u32 dwMaxPayloadTransferSize;__u32 dwClockFrequency;__u8 bmFramingInfo;__u8 bPreferedVersion;__u8 bMinVersion;__u8 bMaxVersion;
}MYUVC_STREAMING_CONTROL_S;/*!* 缓冲区的信息*/
typedef struct myuvc_buffer {int state; /**< 状态位 */int vma_use_count; /**< 是否已经被mmap */struct v4l2_buffer buf; /**< 存储每个缓冲区的查询信息 */wait_queue_head_t wait; /**< APP读取某个缓冲区,如果无数据,在此休眠 */struct list_head stream; /**< mainqueue队列结点,供APP消费用 */struct list_head irq; /**< irqqueue队列结点,供底层驱动生成用*/
}MYUVC_BUFFER_S; /*!* 存储分配的整块缓冲区*/
typedef struct mvuvc_video_queue {int count; /**< 分配缓冲区个数 */int buf_size; /**< 每个缓冲区(页对齐)大小 */void *mem; /**< 存储分配的内存 */MYUVC_BUFFER_S buffer[32]; /**< 存储每个缓冲区的信息 */struct list_head mainqueue; /**< mainqueue队列头结点,供APP消费用 */struct list_head irqqueue; /**< irqqueue队列头结点,供底层驱动生成用*/
}MYUVC_VIDEO_QUEUE_S;static int uvc_version = 0x100; /**< 手工查看知道 */
static int s_myuvc_streaming_intf;
static int s_myuvc_streaming_setting = 8;
static int s_myuvc_control_intf;
static struct video_device *s_myuvc_vdev;
static struct v4l2_format s_myuvc_format; /**< USB摄像头的format */
static struct usb_device *s_myuvc_udev;
static MYUVC_VIDEO_QUEUE_S s_myuvc_queue; /**< 存放分配的一整块缓冲区 */
static MYUVC_STREAMING_CONTROL_S s_myuvc_params; /**< uvc数据流解析 */static int s_frame_idx = 1;
static int s_pixel_bits = 16; /**< USB摄像头像素位 */
static int s_frame_index = 1; /**< 指定分辨率数组下标 */static FRAME_DESC_S s_frame_arr[] = {{640, 480},{352, 288},{320, 240},{176, 144},{160, 120},
}; /**< 该USB摄像头所有支持的分辨率 *//*!* 所支持usb设备类的接口*/
static struct usb_device_id myuvc_ids[] = {/* Generic USB Video Class */{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, 0) }, /**< VideoControl interface */{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 2, 0) }, /**< VideoStreaming interface */{}
};/*!* @brief Step1 - 打开myuvc_fops设备文件*/
static int myuvc_open(struct file *file)
{return 0;
}/*!* 关闭myuvc_fops设备文件*/
static int myuvc_close(struct file *file)
{return 0;
}/*!* @brief Step2 - 查询是否为USB摄像头设备*/
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 = MYDRIVER_VERSION_NUMBER;/*!* V4L2_CAP_VIDEO_CAPTURE - 设备为视频捕捉设备* V4L2_CAP_STREAMING - 使用ioctl来读视频数据*/cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;return 0;
}/*!* @brief Step3 - 列举USB摄像头设备所支持的格式format*/
static int myuvc_vidioc_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f)
{/*!* 当前USB摄像头只支持一种格式format(命令查看描述符信息可知)*/if (f->index >= 1)return -EINVAL;/*!* 格式:VS_FORMAT_UNCOMPRESSED(不压缩原始数据)* GUID:59 55 59 32 00 00 10 00 80 00 00 aa 00 38 9b 71 10* 参考:uvc_fmts[]得到 最终格式宏定义V4L2_PIX_FMT_YUYV*/strcpy(f->description, "4:2:2, packed, YUYV");f->pixelformat = V4L2_PIX_FMT_YUYV;return 0;
}/*!* @brief Step4 - 返回当前所使用的格式*/
static int myuvc_vidioc_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
{memcpy(f, &s_myuvc_format, sizeof(s_myuvc_format));return 0;
}/*!* @brief Step5 - 测试驱动程序是否支持某种格式,强制设定格式*/
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) || \(f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV))return -EINVAL;/*!* 手工确定分辨率、像素位、图片大小信息*/f->fmt.pix.width = s_frame_arr[s_frame_index].width;f->fmt.pix.height = s_frame_arr[s_frame_index].height;f->fmt.pix.bytesperline = (f->fmt.pix.width * s_pixel_bits) >> 3;f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.width;return 0;
}/*!* @brief Step6 - 设置所支持的格式*/
static int myuvc_vidioc_s_fmt_vid_cap(struct file *file,void *priv, struct v4l2_format *f)
{int ret;/*!* 测试是否支持该格式(强制设置格式)*/ret = myuvc_vidioc_try_fmt_vid_cap(file, NULL, f); if (ret < 0)return ret;memcpy(&s_myuvc_format, f, sizeof(s_myuvc_format));return 0;
}/*!* @brief 释放分配的缓冲区*/
int myuvc_free_buffers(void)
{kfree(s_myuvc_queue.mem);memset(&s_myuvc_queue, 0, sizeof(s_myuvc_queue));return 0;
}/*!* @brief Step7 - 为该设备申请若干个缓冲区,分配头部信息* @return 正数:返回成功分配内存的大小,负数:分配失败*/
static int myuvc_vidioc_reqbufs(struct file *file,void *priv, struct v4l2_requestbuffers *p)
{int buf_num = p->count ;int buf_size_unalign = s_myuvc_format.fmt.pix.sizeimage;int buf_size_align = PAGE_ALIGN(s_myuvc_format.fmt.pix.sizeimage);unsigned int i;void *mem = NULL;int ret;if (buf_num > UVC_MAX_VIDEO_BUFFERS)buf_num = UVC_MAX_VIDEO_BUFFERS;/* 释放之前分配的缓存 */if ((ret = myuvc_free_buffers()) < 0)goto done;/* 如果不分配缓冲区,则退出 */if (buf_num == 0)goto done;/* 减少缓冲区的数量,直到分配成功 */for (; buf_num > 0; --buf_num) {mem = vmalloc_32(buf_num * buf_size_align);if (mem != NULL)break;}if (mem == NULL) {ret = -ENOMEM;goto done;}memset(&s_myuvc_queue, 0, sizeof(s_myuvc_queue));/*!* 初始化mainqueue和irqqueue队列*/INIT_LIST_HEAD(&s_myuvc_queue.mainqueue);INIT_LIST_HEAD(&s_myuvc_queue.irqqueue);/*!* 缓存是一次性分配一个大的整体* 需分别设置每个缓存的信息*/for (i = 0; i < buf_num; ++i) {s_myuvc_queue.buffer[i].buf.index = i;s_myuvc_queue.buffer[i].buf.m.offset = i * buf_size_align;s_myuvc_queue.buffer[i].buf.length = buf_size_unalign;s_myuvc_queue.buffer[i].buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;s_myuvc_queue.buffer[i].buf.sequence = 0;s_myuvc_queue.buffer[i].buf.field = V4L2_FIELD_NONE;s_myuvc_queue.buffer[i].buf.memory = V4L2_MEMORY_MMAP;s_myuvc_queue.buffer[i].buf.flags = 0;/* 空闲状态 */s_myuvc_queue.buffer[i].state = VIDEOBUF_IDLE;/* 初始化队列 */init_waitqueue_head(&s_myuvc_queue.buffer[i].wait);}s_myuvc_queue.mem = mem;s_myuvc_queue.count = buf_num;s_myuvc_queue.buf_size = buf_size_align;ret = buf_num;done:return ret;
}/*!* @brief Step8 - 查询指定缓冲区的信息,如大小、偏移地址等* 得到信息后,APP可mmap进行地址映射,分配真正的存储数据的缓冲区* @return 0:成功,负数:失败*/
static int myuvc_vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *v4l2_buf)
{int ret = 0;if (v4l2_buf->index >= s_myuvc_queue.count) {ret = -EINVAL;goto done;}/* 拷贝该缓冲区的状态信息 */memcpy(v4l2_buf, &s_myuvc_queue.buffer[v4l2_buf->index].buf,sizeof(*v4l2_buf));/*!* 若已经该缓冲区已被mmap,则更新状态*/if (s_myuvc_queue.buffer[v4l2_buf->index].vma_use_count)v4l2_buf->flags |= V4L2_BUF_FLAG_MAPPED;/*!* 更新状态*/switch (s_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;
}/*!* @brief Step9 - APPmmap进行地址映射可直接操作这块内存,分配真正的存储数据的缓冲区* @return 0:成功*/
static int myuvc_mmap(struct file *file, struct vm_area_struct *vma)
{return 0;
}/*!* @brief Step10 - 把申请的缓冲区放入队列,底层的硬件操作函数将会把数据放入队列*/
static int myuvc_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *v4l2_buf)
{struct myuvc_buffer *buf = &s_myuvc_queue.buffer[v4l2_buf->index];/*!* 修改状态:处于队列状态,且缓冲区数据为空*/buf->state = VIDEOBUF_QUEUED;v4l2_buf->bytesused = 0;/*!* 队列一:供APP使用* 当缓冲区没有数据时,放入mainqueue队列* 当缓冲区有数据时,APP从mainqueue队列中取出*/list_add_tail(&buf->stream, &s_myuvc_queue.mainqueue);/*!* 队列二:供产生数据的函数使用* 当采集到数据时,从irqqueue队列中取出第一个缓冲区,存入数据*/list_add_tail(&buf->irq, &s_myuvc_queue.irqqueue);return 0;
}/*!* 打印设置参数*/
static void myuvc_print_streaming_params(MYUVC_STREAMING_CONTROL_S *ctrl)
{printk("video params:\n");printk("bmHint = %d\n", ctrl->bmHint);printk("bFormatIndex = %d\n", ctrl->bFormatIndex);printk("bFrameIndex = %d\n", ctrl->bFrameIndex);printk("dwFrameInterval = %d\n", ctrl->dwFrameInterval);printk("wKeyFrameRate = %d\n", ctrl->wKeyFrameRate);printk("wPFrameRate = %d\n", ctrl->wPFrameRate);printk("wCompQuality = %d\n", ctrl->wCompQuality);printk("wCompWindowSize = %d\n", ctrl->wCompWindowSize);printk("wDelay = %d\n", ctrl->wDelay);printk("dwMaxVideoFrameSize = %d\n", ctrl->dwMaxVideoFrameSize);printk("dwMaxPayloadTransferSize = %d\n", ctrl->dwMaxPayloadTransferSize);printk("dwClockFrequency = %d\n", ctrl->dwClockFrequency);printk("bmFramingInfo = %d\n", ctrl->bmFramingInfo);printk("bPreferedVersion = %d\n", ctrl->bPreferedVersion);printk("bMinVersion = %d\n", ctrl->bMinVersion);printk("bMinVersion = %d\n", ctrl->bMinVersion);
}/*!* @brief 根据数据包的数据获得当前设备的参数* @return 0:成功,负数:错误*/
static int myuvc_get_streaming_params(MYUVC_STREAMING_CONTROL_S *ctrl)
{__u8 *data;__u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;__u16 size;int ret = 0;unsigned int pipe; /**< 端点 *//*!* 根据uvc设备版本设置数据宽度*/size = uvc_version >= 0x0110 ? 34 : 26;data = kmalloc(size, GFP_KERNEL);if (data == NULL)return -ENOMEM;/* 确定端点 */pipe = (GET_CUR & 0x80) ? usb_rcvctrlpipe(s_myuvc_udev, 0): usb_sndctrlpipe(s_myuvc_udev, 0);/* 确定类型 */type |= (GET_CUR & 0x80) ? USB_DIR_IN : USB_DIR_OUT;ret = usb_control_msg(s_myuvc_udev, pipe, GET_CUR, type, VS_PROBE_CONTROL << 8,0 << 8 | s_myuvc_streaming_intf, data, size, 500);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;
}/*!* @brief 发送数据包参数,测试设备是否支持* @return 0:成功,负数:错误*/
static int myuvc_try_streaming_params(MYUVC_STREAMING_CONTROL_S *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 */ctrl->bFormatIndex = 1;ctrl->bFrameIndex = s_frame_idx + 1;ctrl->dwFrameInterval = 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(s_myuvc_udev, 0): usb_sndctrlpipe(s_myuvc_udev, 0);type |= (SET_CUR & 0x80) ? USB_DIR_IN : USB_DIR_OUT;/*!* 后发送数据*/ret = usb_control_msg(s_myuvc_udev, pipe, SET_CUR, type, VS_PROBE_CONTROL << 8,0 << 8 | s_myuvc_streaming_intf, data, size, 5000);kfree(data);return (ret < 0) ? ret : 0;
}/*!* @brief 设置数据包参数,测试设备是否支持* @return 0:成功,负数:错误*/
static int myuvc_set_streaming_params(MYUVC_STREAMING_CONTROL_S *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 = 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(s_myuvc_udev, 0): usb_sndctrlpipe(s_myuvc_udev, 0);type |= (SET_CUR & 0x80) ? USB_DIR_IN : USB_DIR_OUT;/*!* 后发送数据*/ret = usb_control_msg(s_myuvc_udev, pipe, SET_CUR, type, VS_COMMIT_CONTROL << 8,0 << 8 | s_myuvc_streaming_intf, data, size, 5000);kfree(data);return (ret < 0) ? ret : 0;
}/*!* @brief Step11 - 启动数据传输*/
static int myuvc_vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{int ret = 0;/*!* 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(&s_myuvc_params);if (ret < 0)printk("myuvc_try_streaming_params ret : %d\n", ret);/* b.取出参数 */ret = myuvc_get_streaming_params(&s_myuvc_params);if (ret < 0)printk("myuvc_get_streaming_params ret : %d\n", ret);/* c.设置参数 */ret = myuvc_set_streaming_params(&s_myuvc_params);if (ret < 0)printk("myuvc_set_streaming_params ret : %d\n", ret);myuvc_print_streaming_params(&s_myuvc_params);/*!* d. 设置VideoStreaming Interface所使用的setting* d.1 从myuvc_params确定带宽* d.2 根据setting的endpoint能传输的wMaxPacketSize* 找到能满足该带宽的setting*//*!* 手工确定:* bandwidth = s_myuvc_params.dwMaxPayloadTransferSize = 1024* 观察lsusb -v -d 0x1e4e:的结果:* wMaxPacketSize 0x0400 1x 1024 bytes* bAlternateSetting 8*/usb_set_interface(s_myuvc_udev, s_myuvc_streaming_intf, s_myuvc_streaming_setting);/* 2. 分配设置URB *//* 3. 提交URB以接收数据 */return 0;
}/*!* @brief Step12 - APP调用poll/select确定缓存是否有数据*/
static unsigned int myuvc_poll(struct file *file, struct poll_table_struct *wait)
{return 0;
}/*!* @brief Step13 - APP通过poll/select确定缓冲区有数据后,从队列中取出并删除缓冲区*/
static int myuvc_vidioc_dqbuf(struct file *file,void *priv, struct v4l2_buffer *v4l2_buf)
{struct myuvc_buffer *buf = &s_myuvc_queue.buffer[v4l2_buf->index];/* APP发现数据后,从mianqueue中取出buffer */list_del(&buf->stream);return 0;
}/*!* @brief Step14 - APP已经mmap映射缓存,可直接读数据* Step15 - 再次调用myuvc_vidioc_dqbuf(),把缓存尾插法放入队列* Step16 - 在其调用myuvc_poll()*//*!* @brief Step17 - 不使用时,停止摄像头数据传输*/
static int myuvc_vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
{return 0;
}/*!* 所支持的ioclt函数*/
static const struct v4l2_ioctl_ops myuvc_ioctl_ops = {// 表示它是一个摄像头设备.vidioc_querycap = myuvc_vidioc_querycap,/* 用于列举、获得、测试、设置摄像头的数据的格式 */.vidioc_enum_fmt_vid_cap = myuvc_vidioc_enum_fmt_vid_cap,.vidioc_g_fmt_vid_cap = myuvc_vidioc_g_fmt_vid_cap,.vidioc_try_fmt_vid_cap = myuvc_vidioc_try_fmt_vid_cap,.vidioc_s_fmt_vid_cap = myuvc_vidioc_s_fmt_vid_cap,/* 缓冲区操作: 申请/查询/放入队列/取出队列 */.vidioc_reqbufs = myuvc_vidioc_reqbufs,.vidioc_querybuf = myuvc_vidioc_querybuf,.vidioc_qbuf = myuvc_vidioc_qbuf,.vidioc_dqbuf = myuvc_vidioc_dqbuf,/* 启动/停止 */.vidioc_streamon = myuvc_vidioc_streamon,.vidioc_streamoff = myuvc_vidioc_streamoff,
};static const struct v4l2_file_operations myuvc_fops = {.owner = THIS_MODULE,.open = myuvc_open,.release = myuvc_close,.mmap = myuvc_mmap,.unlocked_ioctl = video_ioctl2,.poll = myuvc_poll,
};static void myuvc_release(struct video_device *vdev)
{}static int myuvc_probe(struct usb_interface *intf,const struct usb_device_id *id)
{ static int s_cnt = 0;struct usb_device *dev = interface_to_usbdev(intf);s_myuvc_udev = dev;printk("myuvc_probe : cnt = %d\n", s_cnt++);if (s_cnt == 1) {s_myuvc_control_intf = intf->cur_altsetting->desc.bInterfaceNumber;} else if (s_cnt == 2) {s_myuvc_streaming_intf = intf->cur_altsetting->desc.bInterfaceNumber;}/*!* myuvc_probe()调用第二次后执行*/if (s_cnt == 2) {/* 1、分配一个video_device结构体 */s_myuvc_vdev = video_device_alloc();/* 2、设置 *//* 注册过程需要用到release,必须设置 */s_myuvc_vdev->release = myuvc_release;s_myuvc_vdev->fops = &myuvc_fops;s_myuvc_vdev->ioctl_ops = &myuvc_ioctl_ops;/* 3、注册结构体 * -1 - 自动分配次设备号*/printk("1\n");video_register_device(s_myuvc_vdev, VFL_TYPE_GRABBER, -1);printk("1\n");myuvc_vidioc_streamon(NULL, NULL, 0);}return 0;
}static void myuvc_disconnect(struct usb_interface *intf)
{static int s_cnt = 0;printk("myuvc_disconnect : cnt = %d\n", s_cnt++);/*!* myuvc_disconnect()调用第二次后执行*/if (s_cnt == 2) {/* 注销结构体 */video_unregister_device(s_myuvc_vdev);/* 释放结构体 */video_device_release(s_myuvc_vdev); }
}struct usb_driver myuvc_driver = {.name = "myuvcvideo",.probe = myuvc_probe,.disconnect = myuvc_disconnect,.id_table = myuvc_ids,
};static int myuvc_init(void)
{int result;result = usb_register(&myuvc_driver);if (result)printk("USB register error!\n");return result;
}static void myuvc_cleanup(void)
{ usb_deregister(&myuvc_driver);
}module_init(myuvc_init);
module_exit(myuvc_cleanup);
MODULE_LICENSE("GPL")
三、编译与运行
- 执行
make
,生成驱动文件并插入USB摄像头之后 - 卸载原先虚拟机的摄像头驱动
sudo rmmod uvcvideo
,装载新驱动sudo insmod myuvc.ko
- 后执行
dmesg
查看输出信息。
可以看到,输出的参数信息与设置参数信息一致。
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
相关文章
- 为什么做软件类项目,会出现人多,事少,工作量大的情况?
人们常说人多力量大,似乎这才符合常理,但是往往在软件项目开展的过程中依旧会出现人多、事少、工作量大的情况,这跟我们以往的认知大相径庭。首先,要解释下标题的意思。人多,指的是同一个项目团队、同一个小组或者同一个部门的范围内;事少, 指的是做出的效果,真正的产出…...
2024/4/23 8:51:22 - 股票配资软件开发-配资软件源码定制化
想做股票配资的平台都在问,如何开发一款高效安全的股票配资系统APP?开发一套完整的股票配资系统需要多少钱?很多客户其实都很在意开发成本,我们知道一套成熟的股票配资系统能够给平台带来不少利润,而搭建一套完整的股票配资系统需要根据一些软件功能来定价。 开发一套股票…...
2024/5/1 6:20:39 - Oracle中表新增数据实现id自增长的方式
1.问题Oracle数据库不论是使用PL/SQL工具或者是sqldeveloper都不能在新建表的时候像MySQL一样设置id自增长,如在开发或者是学习中,id自增长却又是必须要实现的。2.分析在Oracle中,实现 id 自增长的方式是序列+触发器的方式,即:auto_increment = 序列 + 触发器3.实现方式(…...
2024/5/1 3:52:24 - PAT 1034 Head of a Gang (30分)
原题链接:1034 Head of a Gang (30分) 关键词:dfs、并查集 One way that the police finds the head of a gang is to check people’s phone calls. If there is a phone call between A and B, we say that A and B is related. The weight of a relation is defined to be…...
2024/4/23 15:32:05 - 深入浅出HTML-第6章:链接到 Web
链接到 Web 默认情况下,链接被显示为文档中的一串带下划线的文本。当鼠标逗留在链接上方时,指针会从默认形状变为另一种形状, 以表示这是一个链接. 当你选择链接时,浏览器会导航到链接所指向的地方。 1、锚标签 链接的使用基于一个标签:锚标签。这个标签属性不多,但是提供…...
2024/4/23 2:08:55 - div下img css样式图片等比例缩放
img{width:auto;height:auto;max-height:100%;max-width:100%;}...
2024/4/23 17:00:28 - 【经典算法实现 5】部分字符串反转实现(循环,递归)
【经典算法实现 5】部分字符串反转实现 循环,递归一、循环实现部分字符串反转二、递归实现部分字符串反转 本文主要的目的是实现部分字符串的反转, 例如: "helloabchelloabc", 将其中的所有的abc 字串进行反转,其正确结果应该为 "hellocbachellocba"一…...
2024/4/30 0:04:42 - activemq 安装
安装activemq 必须要先配置jdk 不然无法启动 下载 activemq http://activemq.apache.org/components/classic/download/ 下载完之后上传到linux服务器 software文件夹 tar -zxvf xx.tar.gz 解压 进入到activemq文件夹之后cd conf vim jetty.xml<bean id="jettyPort&quo…...
2024/5/2 15:03:59 - Linux下的常用指令(中)
八、man指令 概念:Linux下的命令手册 man+命令:查看相关命令的功能以及选项功能 man+n+命令:在第n节查找命令 man -a+命令:将所有章节对应命令全部显示九、mv指令 **mv+源文件或目录+目标文件或目录 **:移动文件或者改变文件的名字 mv -f+源文件或目录+目标文件或目录:直接…...
2024/4/22 12:25:16 - cookie跨域共享的实现方案
由于工作需要,花费了较多的时间处理跨域的问题,未避免遗忘,特此记录。1.跨域的场景:第一种:协议不同;第二种: 域名不同;第三种:端口不同;2.跨域的关键点:首先,跨域(CORS)需要浏览器和服务器同时支持;其次,跨域的安全性,由浏览器全权负责;最后,跨域通信如果需要带…...
2024/4/23 22:44:28 - 解锁Redis中Bitmap的小技能
Redis中Bitmap的妙用 Bitmap存储结构Bitmap上的统计bitcount count [start end]bitop operation destkey key [key ...]Bitmap的妙用用户在线状态用户签到统计活跃用户 Bitmap存储结构 在Redis中以位为单位存储字符串,这种存储结构称为Bitmap,也叫位图。 每个字节存储结构如下…...
2024/5/1 21:28:12 - vscode快速批量添加引号和逗号
1. vscode -- mac ---command+f 输入图中正则2.选择替换,以下是结果...
2024/5/1 21:46:28 - vue组件的创建和使用
创建vue组件: 大概分为以下几步: 1.先在component里新建文件,XXX.vue 2.在建好的组件里,输入vue会有代码提示 3.在app.vue里写import XXX(组件名)from ‘./components/XXX.vue’ 4.在下面的components:{}里写组件名 5.在app.vue顶部使用组件 第一步 先在component里新建文…...
2024/4/26 18:01:34 - .NET调试器和程序集编辑器dnspy6.1.6
软件功能.NET调试器一、调试器调试.NET Framework,.NET Core和Unity游戏程序集,无需源代码设置断点并进入任何程序集当地人,手表,汽车窗户变量窗口支持将变量(例如,解密的字节数组)保存到磁盘或在十六进制编辑器(内存窗口)中查看它们.NET调试器对象IDq2315702359可以同时调…...
2024/4/25 20:31:48 - java用IO流实现文件夹的拷贝
仅使用File,FileInputStream,FileOutputStream三个类库实现文件夹内所有内容的字节流拷贝 import java.io.*;public class copy03 {public static void main(String[] args) {//拷贝源File srcFile = new File("C:\\Users\\peanut\\Desktop\\实训\\python实训");//…...
2024/4/26 21:35:14 - Comsenz 核心产品 Discuz! X3.3 正式版【2017-07-01】 -论坛搭建
https://www.discuz.net/thread-3796882-1-1.html产品介绍Discuz! X3.3 在继承和完善 Discuz! X3.2 的基础上,针对 PHP7 进行了优化。对于 X3.2 用户来说,X3.3 已继承了 X3.2 的补丁修复工作,是 X3.2 的稳定版本(但更新了版本号)。想升级 PHP 环境到 PHP7 的用户请选择好合…...
2024/4/26 2:12:41 - HTML、JS、CSS 实现果冻按钮效果 代码解读
HTML、JS、CSS实现果冻按钮效果 效果图:参考代码仍然是yuanchuan前辈的codePen项目果冻按钮 下面开始解读 HTML: <div id="boxes"><div style="--color: #f44336"></div><div style="--color: #e91e63"></div>&…...
2024/5/1 23:11:09 - swift笔记
OC和swift混编注意点: 1.新建一个swift文件时,会提示你是否建立桥接文件,系统会建立“工程名-Bridging-Header.h” 2.配置工程Build Settings 设置Defines Module 为Yes,设置Product Module Name 为当前工程名 3.swift引用OC文件,需要在“工程名-Bridging-Header.h”桥接…...
2024/4/26 1:08:42 - PTA 住房空置率(题解)
在不打扰居民的前提下,统计住房空置率的一种方法是根据每户用电量的连续变化规律进行判断。判断方法如下: 在观察期内,若存在超过一半的日子用电量低于某给定的阈值 e,则该住房为“可能空置”; 若观察期超过某给定阈值 D 天,且满足上一个条件,则该住房为“空置”。 现给…...
2024/4/27 0:47:48 - 项目管理中的需求变更分析和解决之道
一、令人烦恼的需求变更作为一个软件项目经理,在项目开发进行中,你是否遇到过这样的问题:客户的一个电话,就推翻了之前你与客户、与你自己的开发团队,经过再三讨论而确认定下来的需求。之后你就重新开始了和客户、和你的开发团队进入新一轮的需求谈论中,甚至是无休止的谈…...
2024/4/25 10:40:44
最新文章
- Linux下安装snaphu
1、官网下载安装包 2、解压,移动文件夹到/usr/local/下 3、在/usr/local/下创建man,在man下创建man1文件夹 4、进入到snaphu的src文件夹里,执行sudo make,如果报错 在这个 Makefile 中,-arch x86_64 是 macOS 特定的…...
2024/5/2 18:08:27 - 梯度消失和梯度爆炸的一些处理方法
在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言,在此感激不尽。 权重和梯度的更新公式如下: w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...
2024/3/20 10:50:27 - BetterZip for Mac2024最新mac解压缩软件
作为一名软件专家,对于市面上各类软件都有较为深入的了解,下面介绍的是一款适用于Mac系统的解压缩软件——BetterZip,将从其功能特点、使用方法、用户体验及适用人群等方面进行详细介绍。 BetterZip5-安装包绿色版下载如下: htt…...
2024/5/1 13:49:16 - 实景三维在数字乡村建设中的重要作用
随着科技的飞速发展,数字乡村建设已成为推动乡村振兴、实现农村现代化的重要途径。实景三维技术作为数字乡村建设的重要支撑,正逐渐在各个领域发挥着不可或缺的作用。本文将从实景三维技术在数字乡村中的应用场景、优势及未来展望等方面进行探讨…...
2024/5/1 13:46:14 - 416. 分割等和子集问题(动态规划)
题目 题解 class Solution:def canPartition(self, nums: List[int]) -> bool:# badcaseif not nums:return True# 不能被2整除if sum(nums) % 2 ! 0:return False# 状态定义:dp[i][j]表示当背包容量为j,用前i个物品是否正好可以将背包填满ÿ…...
2024/5/2 11:19:01 - 【Java】ExcelWriter自适应宽度工具类(支持中文)
工具类 import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet;/*** Excel工具类** author xiaoming* date 2023/11/17 10:40*/ public class ExcelUti…...
2024/5/2 16:04:58 - Spring cloud负载均衡@LoadBalanced LoadBalancerClient
LoadBalance vs Ribbon 由于Spring cloud2020之后移除了Ribbon,直接使用Spring Cloud LoadBalancer作为客户端负载均衡组件,我们讨论Spring负载均衡以Spring Cloud2020之后版本为主,学习Spring Cloud LoadBalance,暂不讨论Ribbon…...
2024/5/1 21:18:12 - TSINGSEE青犀AI智能分析+视频监控工业园区周界安全防范方案
一、背景需求分析 在工业产业园、化工园或生产制造园区中,周界防范意义重大,对园区的安全起到重要的作用。常规的安防方式是采用人员巡查,人力投入成本大而且效率低。周界一旦被破坏或入侵,会影响园区人员和资产安全,…...
2024/5/2 9:47:31 - VB.net WebBrowser网页元素抓取分析方法
在用WebBrowser编程实现网页操作自动化时,常要分析网页Html,例如网页在加载数据时,常会显示“系统处理中,请稍候..”,我们需要在数据加载完成后才能继续下一步操作,如何抓取这个信息的网页html元素变化&…...
2024/5/2 9:47:31 - 【Objective-C】Objective-C汇总
方法定义 参考:https://www.yiibai.com/objective_c/objective_c_functions.html Objective-C编程语言中方法定义的一般形式如下 - (return_type) method_name:( argumentType1 )argumentName1 joiningArgument2:( argumentType2 )argumentName2 ... joiningArgu…...
2024/5/2 6:03:07 - 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】
👨💻博客主页:花无缺 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】🌏题目描述🌏输入格…...
2024/5/2 9:47:30 - 【ES6.0】- 扩展运算符(...)
【ES6.0】- 扩展运算符... 文章目录 【ES6.0】- 扩展运算符...一、概述二、拷贝数组对象三、合并操作四、参数传递五、数组去重六、字符串转字符数组七、NodeList转数组八、解构变量九、打印日志十、总结 一、概述 **扩展运算符(...)**允许一个表达式在期望多个参数࿰…...
2024/5/1 11:24:00 - 摩根看好的前智能硬件头部品牌双11交易数据极度异常!——是模式创新还是饮鸩止渴?
文 | 螳螂观察 作者 | 李燃 双11狂欢已落下帷幕,各大品牌纷纷晒出优异的成绩单,摩根士丹利投资的智能硬件头部品牌凯迪仕也不例外。然而有爆料称,在自媒体平台发布霸榜各大榜单喜讯的凯迪仕智能锁,多个平台数据都表现出极度异常…...
2024/5/2 5:31:39 - Go语言常用命令详解(二)
文章目录 前言常用命令go bug示例参数说明 go doc示例参数说明 go env示例 go fix示例 go fmt示例 go generate示例 总结写在最后 前言 接着上一篇继续介绍Go语言的常用命令 常用命令 以下是一些常用的Go命令,这些命令可以帮助您在Go开发中进行编译、测试、运行和…...
2024/5/1 20:22:59 - 用欧拉路径判断图同构推出reverse合法性:1116T4
http://cplusoj.com/d/senior/p/SS231116D 假设我们要把 a a a 变成 b b b,我们在 a i a_i ai 和 a i 1 a_{i1} ai1 之间连边, b b b 同理,则 a a a 能变成 b b b 的充要条件是两图 A , B A,B A,B 同构。 必要性显然࿰…...
2024/5/2 9:47:28 - 【NGINX--1】基础知识
1、在 Debian/Ubuntu 上安装 NGINX 在 Debian 或 Ubuntu 机器上安装 NGINX 开源版。 更新已配置源的软件包信息,并安装一些有助于配置官方 NGINX 软件包仓库的软件包: apt-get update apt install -y curl gnupg2 ca-certificates lsb-release debian-…...
2024/5/2 9:47:27 - Hive默认分割符、存储格式与数据压缩
目录 1、Hive默认分割符2、Hive存储格式3、Hive数据压缩 1、Hive默认分割符 Hive创建表时指定的行受限(ROW FORMAT)配置标准HQL为: ... ROW FORMAT DELIMITED FIELDS TERMINATED BY \u0001 COLLECTION ITEMS TERMINATED BY , MAP KEYS TERMI…...
2024/5/2 0:07:22 - 【论文阅读】MAG:一种用于航天器遥测数据中有效异常检测的新方法
文章目录 摘要1 引言2 问题描述3 拟议框架4 所提出方法的细节A.数据预处理B.变量相关分析C.MAG模型D.异常分数 5 实验A.数据集和性能指标B.实验设置与平台C.结果和比较 6 结论 摘要 异常检测是保证航天器稳定性的关键。在航天器运行过程中,传感器和控制器产生大量周…...
2024/5/2 8:37:00 - --max-old-space-size=8192报错
vue项目运行时,如果经常运行慢,崩溃停止服务,报如下错误 FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory 因为在 Node 中,通过JavaScript使用内存时只能使用部分内存(64位系统&…...
2024/5/2 9:47:26 - 基于深度学习的恶意软件检测
恶意软件是指恶意软件犯罪者用来感染个人计算机或整个组织的网络的软件。 它利用目标系统漏洞,例如可以被劫持的合法软件(例如浏览器或 Web 应用程序插件)中的错误。 恶意软件渗透可能会造成灾难性的后果,包括数据被盗、勒索或网…...
2024/5/2 9:47:25 - JS原型对象prototype
让我简单的为大家介绍一下原型对象prototype吧! 使用原型实现方法共享 1.构造函数通过原型分配的函数是所有对象所 共享的。 2.JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象…...
2024/5/1 14:33:22 - C++中只能有一个实例的单例类
C中只能有一个实例的单例类 前面讨论的 President 类很不错,但存在一个缺陷:无法禁止通过实例化多个对象来创建多名总统: President One, Two, Three; 由于复制构造函数是私有的,其中每个对象都是不可复制的,但您的目…...
2024/5/1 11:51:23 - python django 小程序图书借阅源码
开发工具: PyCharm,mysql5.7,微信开发者工具 技术说明: python django html 小程序 功能介绍: 用户端: 登录注册(含授权登录) 首页显示搜索图书,轮播图࿰…...
2024/5/2 7:30:11 - 电子学会C/C++编程等级考试2022年03月(一级)真题解析
C/C++等级考试(1~8级)全部真题・点这里 第1题:双精度浮点数的输入输出 输入一个双精度浮点数,保留8位小数,输出这个浮点数。 时间限制:1000 内存限制:65536输入 只有一行,一个双精度浮点数。输出 一行,保留8位小数的浮点数。样例输入 3.1415926535798932样例输出 3.1…...
2024/5/1 20:56:20 - 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...
解析如下:1、长按电脑电源键直至关机,然后再按一次电源健重启电脑,按F8健进入安全模式2、安全模式下进入Windows系统桌面后,按住“winR”打开运行窗口,输入“services.msc”打开服务设置3、在服务界面,选中…...
2022/11/19 21:17:18 - 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。
%读入6幅图像(每一幅图像的大小是564*564) f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...
2022/11/19 21:17:16 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...
win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面,在等待界面中我们需要等待操作结束才能关机,虽然这比较麻烦,但是对系统进行配置和升级…...
2022/11/19 21:17:15 - 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...
有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows,请勿关闭计算机”的提示,要过很久才能进入系统,有的用户甚至几个小时也无法进入,下面就教大家这个问题的解决方法。第一种方法:我们首先在左下角的“开始…...
2022/11/19 21:17:14 - win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...
置信有很多用户都跟小编一样遇到过这样的问题,电脑时发现开机屏幕显现“正在配置Windows Update,请勿关机”(如下图所示),而且还需求等大约5分钟才干进入系统。这是怎样回事呢?一切都是正常操作的,为什么开时机呈现“正…...
2022/11/19 21:17:13 - 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...
Win7系统开机启动时总是出现“配置Windows请勿关机”的提示,没过几秒后电脑自动重启,每次开机都这样无法进入系统,此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一:开机按下F8,在出现的Windows高级启动选…...
2022/11/19 21:17:12 - 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...
有不少windows10系统用户反映说碰到这样一个情况,就是电脑提示正在准备windows请勿关闭计算机,碰到这样的问题该怎么解决呢,现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法:1、2、依次…...
2022/11/19 21:17:11 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...
今天和大家分享一下win7系统重装了Win7旗舰版系统后,每次关机的时候桌面上都会显示一个“配置Windows Update的界面,提示请勿关闭计算机”,每次停留好几分钟才能正常关机,导致什么情况引起的呢?出现配置Windows Update…...
2022/11/19 21:17:10 - 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...
只能是等着,别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚,只能是考虑备份数据后重装系统了。解决来方案一:管理员运行cmd:net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...
2022/11/19 21:17:09 - 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?
原标题:电脑提示“配置Windows Update请勿关闭计算机”怎么办?win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢?一般的方…...
2022/11/19 21:17:08 - 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...
关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!关机提示 windows7 正在配…...
2022/11/19 21:17:05 - 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...
钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...
2022/11/19 21:17:05 - 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...
前几天班里有位学生电脑(windows 7系统)出问题了,具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面,长时间没反应,无法进入系统。这个问题原来帮其他同学也解决过,网上搜了不少资料&#x…...
2022/11/19 21:17:04 - 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...
本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法,并在最后教给你1种保护系统安全的好方法,一起来看看!电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中,添加了1个新功能在“磁…...
2022/11/19 21:17:03 - 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...
许多用户在长期不使用电脑的时候,开启电脑发现电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机。。.这要怎么办呢?下面小编就带着大家一起看看吧!如果能够正常进入系统,建议您暂时移…...
2022/11/19 21:17:02 - 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...
配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!配置windows update失败 还原更改 请勿关闭计算机&#x…...
2022/11/19 21:17:01 - 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...
不知道大家有没有遇到过这样的一个问题,就是我们的win7系统在关机的时候,总是喜欢显示“准备配置windows,请勿关机”这样的一个页面,没有什么大碍,但是如果一直等着的话就要两个小时甚至更久都关不了机,非常…...
2022/11/19 21:17:00 - 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...
当电脑出现正在准备配置windows请勿关闭计算机时,一般是您正对windows进行升级,但是这个要是长时间没有反应,我们不能再傻等下去了。可能是电脑出了别的问题了,来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...
2022/11/19 21:16:59 - 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...
我们使用电脑的过程中有时会遇到这种情况,当我们打开电脑之后,发现一直停留在一个界面:“配置Windows Update失败,还原更改请勿关闭计算机”,等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢࿰…...
2022/11/19 21:16:58 - 如何在iPhone上关闭“请勿打扰”
Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...
2022/11/19 21:16:57