UVC调用过程部分细节分析

news2024/11/19 0:36:20

UVC调用过程部分细节分析


文章目录

  • UVC调用过程部分细节分析
  • 概括
  • 分析UVC驱动调用过程
    • 1.open:
      • ioctl
    • 2.VIDIOC_QUERYCAP
    • 3.VIDIOC_ENUM_FMT
    • 4.VIDIOC_G_FMT
    • 5.VIDIOC_TRY_FMT
    • 6.VIDIOC_S_FMT /
    • 7.VIDIOC_REQBUFS
    • 8.VIDIOC_QUERYBUF
    • 9.mmap
    • 10.VIDIOC_QBUF
    • 11.VIDIOC_STREAMON
    • 12.poll
    • 13.VIDIOC_DQBUF
    • 14.VIDIOC_STREAMOFF
    • 分析设置亮度过程:


概括

通过VideoControl Interface来控制,
通过VideoStreaming Interface来读视频数据,
VC里含有多个Unit/Terminal等功能模块,可以通过访问这些模块进行控制,比如调亮度

在这里插入图片描述

总结:
UVC设备有2个interface: VideoControl Interface, VideoStreaming Interface

VideoControl Interface用于控制,比如设置亮度。它内部有多个Unit/Terminal(在程序里Unit/Terminal都称为entity)
在这里插入图片描述

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);

可以通过类似的函数来访问:

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用来表示分辨率等信息)

可以通过类似的函数来访问:

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时只是简单的使用format = &stream->format[fmt->index]; // 获取视频流格式,等数据,

这些数据哪来的?
应是设备被枚举时设置的,也就是分析它的描述符时设置的。
UVC驱动的重点在于:
描述符的分析
属性的控制: 通过VideoControl Interface来设置
格式的选择:通过VideoStreaming Interface来设置
数据的获得:通过VideoStreaming Interface的URB来获得

分析UVC驱动调用过程

const struct v4l2_file_operations uvc_fops = {
    .owner        = THIS_MODULE,
    .open        = uvc_v4l2_open,
    .release    = uvc_v4l2_release,
    .unlocked_ioctl    = video_ioctl2,
#ifdef CONFIG_COMPAT
    .compat_ioctl32    = uvc_v4l2_compat_ioctl32,
#endif
    .read        = uvc_v4l2_read,
    .mmap        = uvc_v4l2_mmap,
    .poll        = uvc_v4l2_poll,
#ifndef CONFIG_MMU
    .get_unmapped_area = uvc_v4l2_get_unmapped_area,
#endif
};

1.open:

uvc_v4l2_open

static int uvc_v4l2_open(struct file *file)
{
    struct uvc_streaming *stream;
    struct uvc_fh *handle;
    int ret = 0;

    // 打印函数调用信息
    uvc_trace(UVC_TRACE_CALLS, "uvc_v4l2_open\n");
    // 获取视频设备的私有数据
    stream = video_drvdata(file);

    // 如果设备已经断开连接,返回错误码
    if (stream->dev->state & UVC_DEV_DISCONNECTED)
        return -ENODEV;

    // 获取接口的使用权
    ret = usb_autopm_get_interface(stream->dev->intf);
    if (ret < 0)
        return ret;

    // 创建设备句柄
    handle = kzalloc(sizeof *handle, GFP_KERNEL);
    if (handle == NULL) {
        usb_autopm_put_interface(stream->dev->intf);
        return -ENOMEM;
    }

    // 加锁,防止多个进程同时访问
    mutex_lock(&stream->dev->lock);
    // 如果当前没有用户使用设备,启动设备状态
    if (stream->dev->users == 0) {
        ret = uvc_status_start(stream->dev, GFP_KERNEL);
        if (ret < 0) {
            mutex_unlock(&stream->dev->lock);
            usb_autopm_put_interface(stream->dev->intf);
            kfree(handle);
            return ret;
        }
    }

    // 增加用户数
    stream->dev->users++;
    mutex_unlock(&stream->dev->lock);

    // 初始化文件句柄
    v4l2_fh_init(&handle->vfh, &stream->vdev);
    // 添加文件句柄
    v4l2_fh_add(&handle->vfh);
    handle->chain = stream->chain;
    handle->stream = stream;
    handle->state = UVC_HANDLE_PASSIVE;
    file->private_data = handle;

    return 0;

}

ioctl

video_ioctl2将应用中提供的参数拷贝到内核,调用__video_do_ioctl

long video_ioctl2(struct file *file,
           unsigned int cmd, unsigned long arg)
{
    return video_usercopy(file, cmd, arg, __video_do_ioctl);
}

video_ioctl2->__video_do_ioctl

static long __video_do_ioctl(struct file *file,
        unsigned int cmd, void *arg)
{
    // 获取video_device结构体
    struct video_device *vfd = video_devdata(file);
    // 获取v4l2_ioctl_ops结构体
    const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;
    // 判断是否为只写操作
    bool write_only = false;
    // 定义默认的ioctl信息
    struct v4l2_ioctl_info default_info;
    // 定义ioctl信息
    const struct v4l2_ioctl_info *info;
    // 获取file结构体的私有数据
    void *fh = file->private_data;
    // 获取v4l2_fh结构体
    struct v4l2_fh *vfh = NULL;
    // 获取dev_debug
    int dev_debug = vfd->dev_debug;
    // 定义返回值
    long ret = -ENOTTY;

    // 判断是否有ioctl_ops
    if (ops == NULL) {
        pr_warn("%s: has no ioctl_ops.\n",
                video_device_node_name(vfd));
        return ret;
    }

    // 判断是否使用v4l2_fh
    if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags))
        vfh = file->private_data;

    // 判断是否为已知的ioctl
    if (v4l2_is_known_ioctl(cmd)) {
        // 获取ioctl信息
        info = &v4l2_ioctls[_IOC_NR(cmd)];

            // 判断是否为有效的ioctl
        if (!test_bit(_IOC_NR(cmd), vfd->valid_ioctls) &&
            !((info->flags & INFO_FL_CTRL) && vfh && vfh->ctrl_handler))
            goto done;

        // 判断是否需要检查优先级
        if (vfh && (info->flags & INFO_FL_PRIO)) {
            ret = v4l2_prio_check(vfd->prio, vfh->prio);
            if (ret)
                goto done;
        }
    } else {
        // 设置默认的ioctl信息
        default_info.ioctl = cmd;
        default_info.flags = 0;
        default_info.debug = v4l_print_default;
        info = &default_info;
    }

write_only = _IOC_DIR(cmd) == _IOC_WRITE;
    // 判断是否为标准的ioctl
    if (info->flags & INFO_FL_STD) {
        // 定义vidioc_op函数指针类型
        typedef int (*vidioc_op)(struct file *file, void *fh, void *p);
        // 获取ioctl_ops
        const void *p = vfd->ioctl_ops;
        // 获取vidioc函数指针
        const vidioc_op *vidioc = p + info->u.offset;

        // 调用vidioc函数
        ret = (*vidioc)(file, fh, arg);
    } 
    // 判断是否为函数ioctl
    else if (info->flags & INFO_FL_FUNC) {
        // 调用函数ioctl
        ret = info->u.func(ops, file, fh, arg);
    } 
    // 判断是否有默认的ioctl
    else if (!ops->vidioc_default) {
        ret = -ENOTTY;
    } 
    // 调用默认的ioctl
    else {
        ret = ops->vidioc_default(file, fh,
            vfh ? v4l2_prio_check(vfd->prio, vfh->prio) >= 0 : 0,
            cmd, arg);
    }

done:
    // 判断是否需要打印调试信息
    if (dev_debug & (V4L2_DEV_DEBUG_IOCTL | V4L2_DEV_DEBUG_IOCTL_ARG)) {
        // 判断是否需要打印流信息
        if (!(dev_debug & V4L2_DEV_DEBUG_STREAMING) &&
            (cmd == VIDIOC_QBUF || cmd == VIDIOC_DQBUF))
            return ret;

        // 打印ioctl信息
        v4l_printk_ioctl(video_device_node_name(vfd), cmd);
        if (ret < 0)
            pr_cont(": error %ld", ret);
        if (!(dev_debug & V4L2_DEV_DEBUG_IOCTL_ARG))
            pr_cont("\n");
        else if (_IOC_DIR(cmd) == _IOC_NONE)
            info->debug(arg, write_only);
        else {
            pr_cont(": ");
            info->debug(arg, write_only);
        }
    }

    return ret;
}
根据传入的参数调用对应的ioctl
    write_only = _IOC_DIR(cmd) == _IOC_WRITE;
    // 判断是否为标准的ioctl
    if (info->flags & INFO_FL_STD) {
        // 定义vidioc_op函数指针类型
        typedef int (*vidioc_op)(struct file *file, void *fh, void *p);
        // 获取ioctl_ops
        const void *p = vfd->ioctl_ops;
        // 获取vidioc函数指针
        const vidioc_op *vidioc = p + info->u.offset;

        // 调用vidioc函数
        ret = (*vidioc)(file, fh, arg);
    } 
    // 判断是否为函数ioctl
    else if (info->flags & INFO_FL_FUNC) {
        // 调用函数ioctl
        ret = info->u.func(ops, file, fh, arg);
    } 
    // 判断是否有默认的ioctl
    else if (!ops->vidioc_default) {
        ret = -ENOTTY;
    } 
    // 调用默认的ioctl
    else {
        ret = ops->vidioc_default(file, fh,
            vfh ? v4l2_prio_check(vfd->prio, vfh->prio) >= 0 : 0,
            cmd, arg);
    }

/driver/media/usb/unc/uvc_v4l2.c

const struct v4l2_ioctl_ops uvc_ioctl_ops = {
    .vidioc_querycap = uvc_ioctl_querycap,
    .vidioc_enum_fmt_vid_cap = uvc_ioctl_enum_fmt_vid_cap,
    .vidioc_enum_fmt_vid_out = uvc_ioctl_enum_fmt_vid_out,
    .vidioc_g_fmt_vid_cap = uvc_ioctl_g_fmt_vid_cap,
    .vidioc_g_fmt_vid_out = uvc_ioctl_g_fmt_vid_out,
    .vidioc_s_fmt_vid_cap = uvc_ioctl_s_fmt_vid_cap,
    .vidioc_s_fmt_vid_out = uvc_ioctl_s_fmt_vid_out,
    .vidioc_try_fmt_vid_cap = uvc_ioctl_try_fmt_vid_cap,
    .vidioc_try_fmt_vid_out = uvc_ioctl_try_fmt_vid_out,
    .vidioc_reqbufs = uvc_ioctl_reqbufs,
    .vidioc_querybuf = uvc_ioctl_querybuf,
    .vidioc_qbuf = uvc_ioctl_qbuf,
    .vidioc_dqbuf = uvc_ioctl_dqbuf,
    .vidioc_create_bufs = uvc_ioctl_create_bufs,
    .vidioc_streamon = uvc_ioctl_streamon,
    .vidioc_streamoff = uvc_ioctl_streamoff,
    .vidioc_enum_input = uvc_ioctl_enum_input,
    .vidioc_g_input = uvc_ioctl_g_input,
    .vidioc_s_input = uvc_ioctl_s_input,
    .vidioc_queryctrl = uvc_ioctl_queryctrl,
    .vidioc_query_ext_ctrl = uvc_ioctl_query_ext_ctrl,
    .vidioc_g_ctrl = uvc_ioctl_g_ctrl,
    .vidioc_s_ctrl = uvc_ioctl_s_ctrl,
    .vidioc_g_ext_ctrls = uvc_ioctl_g_ext_ctrls,
    .vidioc_s_ext_ctrls = uvc_ioctl_s_ext_ctrls,
    .vidioc_try_ext_ctrls = uvc_ioctl_try_ext_ctrls,
    .vidioc_querymenu = uvc_ioctl_querymenu,
    .vidioc_g_selection = uvc_ioctl_g_selection,
    .vidioc_g_parm = uvc_ioctl_g_parm,
    .vidioc_s_parm = uvc_ioctl_s_parm,
    .vidioc_enum_framesizes = uvc_ioctl_enum_framesizes,
    .vidioc_enum_frameintervals = uvc_ioctl_enum_frameintervals,
    .vidioc_subscribe_event = uvc_ioctl_subscribe_event,
    .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
    .vidioc_default = uvc_ioctl_default,
};

2.VIDIOC_QUERYCAP

video->streaming->type 应该是在设备被枚举时分析描述符时设置的

.vidioc_querycap = uvc_ioctl_querycap,

uvc_ioctl_querycap

static int uvc_ioctl_querycap(struct file *file, void *fh,
                  struct v4l2_capability *cap)
{
    // 获取video_device结构体
    struct video_device *vdev = video_devdata(file);
    // 获取uvc_fh结构体
    struct uvc_fh *handle = file->private_data;
    // 获取uvc_video_chain结构体
    struct uvc_video_chain *chain = handle->chain;
    // 获取uvc_streaming结构体
    struct uvc_streaming *stream = handle->stream;

    // 设置驱动名称
    strlcpy(cap->driver, "uvcvideo", sizeof(cap->driver));
    // 设置设备名称
    strlcpy(cap->card, vdev->name, sizeof(cap->card));
    // 设置总线信息
    usb_make_path(stream->dev->udev, cap->bus_info, sizeof(cap->bus_info));
    // 设置设备能力
    cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING | chain->caps;
    // 设置设备类型
    if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
        cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
    else
        cap->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;

    return 0;
}
   // 设置设备类型
    if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
        cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
    else
        cap->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;

3.VIDIOC_ENUM_FMT

format数组应是在设备被枚举时设置的

在这里插入图片描述

static int uvc_ioctl_enum_fmt(struct uvc_streaming *stream,
                  struct v4l2_fmtdesc *fmt)
{
struct uvc_format *format;
    enum v4l2_buf_type type = fmt->type; // 获取视频流类型
    __u32 index = fmt->index; // 获取视频流格式索引

    if (fmt->type != stream->type || fmt->index >= stream->nformats) // 如果视频流类型不匹配或者视频流格式索引超出范围
        return -EINVAL; // 返回无效参数错误

    memset(fmt, 0, sizeof(*fmt)); // 清空fmt结构体
    fmt->index = index; // 设置视频流格式索引
    fmt->type = type; // 设置视频流类型

    format = &stream->format[fmt->index]; // 获取视频流格式
    fmt->flags = 0; // 清空标志位
    if (format->flags & UVC_FMT_FLAG_COMPRESSED) // 如果视频流格式是压缩格式
        fmt->flags |= V4L2_FMT_FLAG_COMPRESSED; // 设置标志位
    strlcpy(fmt->description, format->name, sizeof(fmt->description)); // 设置视频流格式描述
    fmt->description[sizeof(fmt->description) - 1] = 0; // 设置视频流格式描述
    fmt->pixelformat = format->fcc; // 设置视频流像素格式
    return 0; // 返回成功

}
format = &stream->format[fmt->index]; // 获取视频流格式

4.VIDIOC_G_FMT

uvc_v4l2_get_format // USB摄像头支持多种格式fromat, 每种格式下有多种frame(比如分辨率)

static int uvc_v4l2_get_format(struct uvc_streaming *stream,
    struct v4l2_format *fmt)
{
    // 获取当前流的格式和帧
    struct uvc_format *format;
    struct uvc_frame *frame;
    int ret = 0;

    // 如果请求的格式不是当前流的格式,返回错误
    if (fmt->type != stream->type)
        return -EINVAL;

    // 获取当前流的格式和帧
    mutex_lock(&stream->mutex);
    format = stream->cur_format;
    frame = stream->cur_frame;

    // 如果当前流的格式或帧为空,返回错误
    if (format == NULL || frame == NULL) {
        ret = -EINVAL;
        goto done;
    }

    // 设置请求的格式
    fmt->fmt.pix.pixelformat = format->fcc;
    fmt->fmt.pix.width = frame->wWidth;
    fmt->fmt.pix.height = frame->wHeight;
    fmt->fmt.pix.field = V4L2_FIELD_NONE;
    fmt->fmt.pix.bytesperline = format->bpp * frame->wWidth / 8;
    fmt->fmt.pix.sizeimage = stream->ctrl.dwMaxVideoFrameSize;
    fmt->fmt.pix.colorspace = format->colorspace;
    fmt->fmt.pix.priv = 0;

done:
    mutex_unlock(&stream->mutex);
    return ret;
}
// 获取当前流的格式和帧
struct uvc_format *format;
struct uvc_frame *frame;

// 获取当前流的格式和帧
mutex_lock(&stream->mutex);
format = stream->cur_format;
frame = stream->cur_frame;

5.VIDIOC_TRY_FMT

uvc_v4l2_try_format

    /* Check if the hardware supports the requested format. */

     /* Find the closest image size. The distance between image sizes is
      * the size in pixels of the non-overlapping regions between the
      * requested size and the frame-specified size.
      */

uvc_v4l2_try_format
static int uvc_v4l2_try_format(struct uvc_streaming *stream,
    struct v4l2_format *fmt, struct uvc_streaming_control *probe,
    struct uvc_format **uvc_format, struct uvc_frame **uvc_frame)
{
struct uvc_format *format = NULL; // 初始化uvc_format结构体指针
    struct uvc_frame *frame = NULL; // 初始化uvc_frame结构体指针
    __u16 rw, rh; // 定义宽度和高度
    unsigned int d, maxd; // 定义距离和最大距离
    unsigned int i; // 定义无符号整型变量i
    __u32 interval; // 定义无符号32位整型变量interval
    int ret = 0; // 定义整型变量ret并初始化为0
    __u8 *fcc; // 定义指向无符号8位整型变量的指针fcc

    if (fmt->type != stream->type) // 如果格式类型不匹配
        return -EINVAL; // 返回无效参数错误

    fcc = (__u8 *)&fmt->fmt.pix.pixelformat; // 将fcc指针指向像素格式
    uvc_trace(UVC_TRACE_FORMAT, "Trying format 0x%08x (%c%c%c%c): %ux%u.\n",
            fmt->fmt.pix.pixelformat,
            fcc[0], fcc[1], fcc[2], fcc[3],
            fmt->fmt.pix.width, fmt->fmt.pix.height); // 打印正在尝试的格式

    /* Check if the hardware supports the requested format, use the default
     * format otherwise.
     */
    for (i = 0; i < stream->nformats; ++i) { // 遍历所有格式
        format = &stream->format[i]; // 将format指针指向当前格式
        if (format->fcc == fmt->fmt.pix.pixelformat) // 如果当前格式与请求格式匹配
            break; // 跳出循环
    }

    if (i == stream->nformats) { // 如果没有匹配的格式
        format = stream->def_format; // 使用默认格式
        fmt->fmt.pix.pixelformat = format->fcc; // 将请求格式设置为默认格式
    }

/* 寻找最接近的图像大小。图像大小之间的距离是请求大小和帧指定大小之间的非重叠区域的大小。*/
    rw = fmt->fmt.pix.width; // 获取请求的宽度
    rh = fmt->fmt.pix.height; // 获取请求的高度
    maxd = (unsigned int)-1; // 初始化最大距离

    for (i = 0; i < format->nframes; ++i) { // 遍历所有帧
        __u16 w = format->frame[i].wWidth; // 获取当前帧的宽度
        __u16 h = format->frame[i].wHeight; // 获取当前帧的高度

        d = min(w, rw) * min(h, rh); // 计算非重叠区域的大小
        d = w*h + rw*rh - 2*d; // 计算距离
        if (d < maxd) { // 如果距离小于最大距离
            maxd = d; // 更新最大距离
            frame = &format->frame[i]; // 更新帧
        }

        if (maxd == 0) // 如果最大距离为0
            break; // 跳出循环
    }

    if (frame == NULL) { // 如果没有找到匹配的帧
        uvc_trace(UVC_TRACE_FORMAT, "Unsupported size %ux%u.\n",
                fmt->fmt.pix.width, fmt->fmt.pix.height); // 打印不支持的大小
        return -EINVAL; // 返回无效参数错误
    }

/* Use the default frame interval. */
    // 使用默认帧间隔
    interval = frame->dwDefaultFrameInterval;
    // 打印使用的帧间隔
    uvc_trace(UVC_TRACE_FORMAT, "Using default frame interval %u.%u us "
        "(%u.%u fps).\n", interval/10, interval%10, 10000000/interval,
        (100000000/interval)%10);

    /* Set the format index, frame index and frame interval. */
    // 设置格式索引、帧索引和帧间隔
    memset(probe, 0, sizeof *probe);
    probe->bmHint = 1;  /* dwFrameInterval */
    probe->bFormatIndex = format->index;
    probe->bFrameIndex = frame->bFrameIndex;
    probe->dwFrameInterval = uvc_try_frame_interval(frame, interval);

    /* Some webcams stall the probe control set request when the
     * dwMaxVideoFrameSize field is set to zero. The UVC specification
     * clearly states that the field is read-only from the host, so this
     * is a webcam bug. Set dwMaxVideoFrameSize to the value reported by
     * the webcam to work around the problem.
     *
     * The workaround could probably be enabled for all webcams, so the
     * quirk can be removed if needed. It's currently useful to detect
     * webcam bugs and fix them before they hit the market (providing
     * developers test their webcams with the Linux driver as well as with
     * the Windows driver).
     */
// 加锁
    mutex_lock(&stream->mutex);
    // 如果设备有 UVC_QUIRK_PROBE_EXTRAFIELDS 标志,设置探测结构体的最大视频帧大小
    if (stream->dev->quirks & UVC_QUIRK_PROBE_EXTRAFIELDS)
        probe->dwMaxVideoFrameSize =
            stream->ctrl.dwMaxVideoFrameSize;

    /* Probe the device. */
    // 探测设备
    ret = uvc_probe_video(stream, probe);
    // 解锁
    mutex_unlock(&stream->mutex);
    // 如果探测失败,跳转到 done 标签
    if (ret < 0)
        goto done;

    // 设置请求的格式
    fmt->fmt.pix.width = frame->wWidth;
    fmt->fmt.pix.height = frame->wHeight;
    fmt->fmt.pix.field = V4L2_FIELD_NONE;
    fmt->fmt.pix.bytesperline = format->bpp * frame->wWidth / 8;
    fmt->fmt.pix.sizeimage = probe->dwMaxVideoFrameSize;
    fmt->fmt.pix.colorspace = format->colorspace;
    fmt->fmt.pix.priv = 0;

    // 如果 uvc_format 不为空,将当前流的格式赋值给它
    if (uvc_format != NULL)
        *uvc_format = format;
    // 如果 uvc_frame 不为空,将当前流的帧赋值给它
    if (uvc_frame != NULL)
        *uvc_frame = frame;

done:
    // 返回探测结果
    return ret;
}

6.VIDIOC_S_FMT /

只是把参数保存起来,还没有发给USB摄像头

 uvc_v4l2_set_format
     uvc_v4l2_try_format
     video->streaming->cur_format = format;
     video->streaming->cur_frame = frame;
static int uvc_v4l2_set_format(struct uvc_streaming *stream,
    struct v4l2_format *fmt)
{
    // 定义探测结构体、格式、帧和返回值
    struct uvc_streaming_control probe;
    struct uvc_format *format;
    struct uvc_frame *frame;
    int ret;

    // 如果请求的格式不是当前流的格式,返回错误
    if (fmt->type != stream->type)
        return -EINVAL;

    // 尝试设置格式
    ret = uvc_v4l2_try_format(stream, fmt, &probe, &format, &frame);
    if (ret < 0)
        return ret;

    // 上锁
    mutex_lock(&stream->mutex);

    // 如果队列已经分配,返回错误
    if (uvc_queue_allocated(&stream->queue)) {
        ret = -EBUSY;
        goto done;
    }

    // 设置控制结构体、当前格式和当前帧
    stream->ctrl = probe;
    stream->cur_format = format;
    stream->cur_frame = frame;

done:
    // 解锁
    mutex_unlock(&stream->mutex);
    return ret;

}

7.VIDIOC_REQBUFS

uvc_ioctl_reqbufs

static int uvc_ioctl_reqbufs(struct file *file, void *fh,
                 struct v4l2_requestbuffers *rb)
{
    // 获取文件句柄
    struct uvc_fh *handle = fh;
    // 获取流
    struct uvc_streaming *stream = handle->stream;
    int ret;

    // 获取权限
    ret = uvc_acquire_privileges(handle);
    if (ret < 0)
        return ret;

    // 上锁
    mutex_lock(&stream->mutex);
    // 请求缓冲区
    ret = uvc_request_buffers(&stream->queue, rb);
    // 解锁
    mutex_unlock(&stream->mutex);
    if (ret < 0)
        return ret;

    // 如果请求成功,解除权限
    if (ret == 0)
        uvc_dismiss_privileges(handle);

    return 0;
}

uvc_ioctl_reqbufs->uvc_request_buffers

int uvc_request_buffers(struct uvc_video_queue *queue,
            struct v4l2_requestbuffers *rb)
{
    int ret;

    mutex_lock(&queue->mutex);
    ret = vb2_reqbufs(&queue->queue, rb);
    mutex_unlock(&queue->mutex);

    return ret ? ret : rb->count;
}

uvc_ioctl_reqbufs->uvc_request_buffers->vb2_reqbuf

int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
{
    int ret = __verify_memory_type(q, req->memory, req->type);

    return ret ? ret : __reqbufs(q, req);
}

uvc_ioctl_reqbufs->uvc_request_buffers->vb2_reqbuf->__reqbufs分配缓冲区

static int __reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
{
    unsigned int num_buffers, allocated_buffers, num_planes = 0;
    int ret;

    // 检查是否正在流传输
    if (q->streaming) {
        dprintk(1, "streaming active\n");
        return -EBUSY;
    }

    // 检查是否需要重新分配内存
    if (req->count == 0 || q->num_buffers != 0 || q->memory != req->memory) {
        /*
         * We already have buffers allocated, so first check if they
         * are not in use and can be freed.
         */
        mutex_lock(&q->mmap_lock);

        // 检查内存是否正在使用
        if (q->memory == V4L2_MEMORY_MMAP && __buffers_in_use(q)) {
            mutex_unlock(&q->mmap_lock);
            dprintk(1, "memory in use, cannot free\n");
            return -EBUSY;
        }

        /*
         * Call queue_cancel to clean up any buffers in the PREPARED or
         * QUEUED state which is possible if buffers were prepared or
         * queued without ever calling STREAMON.
         */

        // 取消队列中所有处于 PREPARED 或 QUEUED 状态的缓冲区
        __vb2_queue_cancel(q);

        // 释放所有缓冲区
        ret = __vb2_queue_free(q, q->num_buffers);
        mutex_unlock(&q->mmap_lock);
        if (ret)
            return ret;

        /*
         * In case of REQBUFS(0) return immediately without calling
         * driver's queue_setup() callback and allocating resources.
         */

        // 如果请求的缓冲区数量为 0,则直接返回
        if (req->count == 0)
            return 0;
    }

/*
     * Make sure the requested values and current defaults are sane.
     */
    // 确保请求的值和当前默认值是合理的
    num_buffers = min_t(unsigned int, req->count, VIDEO_MAX_FRAME);
    num_buffers = max_t(unsigned int, num_buffers, q->min_buffers_needed);
    memset(q->plane_sizes, 0, sizeof(q->plane_sizes));
    memset(q->alloc_ctx, 0, sizeof(q->alloc_ctx));
    q->memory = req->memory;

    /*
     * Ask the driver how many buffers and planes per buffer it requires.
     * Driver also sets the size and allocator context for each plane.
     */
    // 询问驱动程序需要多少缓冲区和每个缓冲区的平面数
    // 驱动程序还为每个平面设置大小和分配器上下文
    ret = call_qop(q, queue_setup, q, NULL, &num_buffers, &num_planes,
               q->plane_sizes, q->alloc_ctx);
    if (ret)
        return ret;

    /* Finally, allocate buffers and video memory */
    // 最后,分配缓冲区和视频内存
    allocated_buffers = __vb2_queue_alloc(q, req->memory, num_buffers, num_planes);
    if (allocated_buffers == 0) {
        dprintk(1, "memory allocation failed\n");
        return -ENOMEM;
    }

    /*
     * There is no point in continuing if we can't allocate the minimum
     * number of buffers needed by this vb2_queue.
     */
    // 如果我们无法为此 vb2_queue 分配所需的最小缓冲区数量,则继续没有意义
    if (allocated_buffers < q->min_buffers_needed)
        ret = -ENOMEM;

/*
     * Check if driver can handle the allocated number of buffers.
     */
    // 检查驱动程序是否可以处理分配的缓冲区数量
    if (!ret && allocated_buffers < num_buffers) {
        num_buffers = allocated_buffers;

        // 询问驱动程序需要多少缓冲区和每个缓冲区的平面数
        // 驱动程序还为每个平面设置大小和分配器上下文
        ret = call_qop(q, queue_setup, q, NULL, &num_buffers,
                   &num_planes, q->plane_sizes, q->alloc_ctx);

        // 如果分配的缓冲区数量小于请求的数量,则返回错误
        if (!ret && allocated_buffers < num_buffers)
            ret = -ENOMEM;

        /*
         * Either the driver has accepted a smaller number of buffers,
         * or .queue_setup() returned an error
         */
    }

    // 获取互斥锁
    mutex_lock(&q->mmap_lock);
    q->num_buffers = allocated_buffers;

    if (ret < 0) {
        /*
         * Note: __vb2_queue_free() will subtract 'allocated_buffers'
         * from q->num_buffers.
         */
        // 如果分配失败,则释放所有缓冲区
        __vb2_queue_free(q, allocated_buffers);
        mutex_unlock(&q->mmap_lock);
        return ret;
    }
    // 释放互斥锁
    mutex_unlock(&q->mmap_lock);

    /*
     * Return the number of successfully allocated buffers
     * to the userspace.
     */
    // 返回成功分配的缓冲区数量
    req->count = allocated_buffers;
    q->waiting_for_buffers = !V4L2_TYPE_IS_OUTPUT(q->type);

    return 0;


}

8.VIDIOC_QUERYBUF

uvc_ioctl_querybuf

static int uvc_ioctl_querybuf(struct file *file, void *fh,
                  struct v4l2_buffer *buf)
{
    // 获取文件句柄
    struct uvc_fh *handle = fh;
    // 获取流
    struct uvc_streaming *stream = handle->stream;

    // 如果没有权限,返回错误
    if (!uvc_has_privileges(handle))
        return -EBUSY;

    // 查询缓冲区
    return uvc_query_buffer(&stream->queue, buf);
}

uvc_ioctl_querybuf
uvc_query_buffer

int uvc_query_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf)
{


    int ret;

    mutex_lock(&queue->mutex);
    ret = vb2_querybuf(&queue->queue, buf);
    mutex_unlock(&queue->mutex);

    return ret;
}
 uvc_ioctl_querybuf
     uvc_query_buffer
         vb2_querybuf
int vb2_querybuf(struct vb2_queue *q, struct v4l2_buffer *b)
{
    // 获取对应的vb2_buffer
    struct vb2_buffer *vb;
    int ret;

    // 检查buffer类型是否正确
    if (b->type != q->type) {
        dprintk(1, "wrong buffer type\n");
        return -EINVAL;
    }

    // 检查buffer index是否越界
    if (b->index >= q->num_buffers) {
        dprintk(1, "buffer index out of range\n");
        return -EINVAL;
    }
    vb = q->bufs[b->index];

    // 验证planes数组是否正确
    ret = __verify_planes_array(vb, b);

    // 填充v4l2_buffer结构体
    if (!ret)
        __fill_v4l2_buffer(vb, b);
    return ret;
}
 uvc_ioctl_querybuf
     uvc_query_buffer
         vb2_querybuf
            __fill_v4l2_buffer

复制参数

memcpy(b, &vb->v4l2_buf, offsetof(struct v4l2_buffer, m));

__fill_v4l2_buffer

static void __fill_v4l2_buffer(struct vb2_buffer *vb, struct v4l2_buffer *b)
{
    struct vb2_queue *q = vb->vb2_queue;

    /* 复制回时间戳、标志等数据 */
    memcpy(b, &vb->v4l2_buf, offsetof(struct v4l2_buffer, m));
    b->reserved2 = vb->v4l2_buf.reserved2;
    b->reserved = vb->v4l2_buf.reserved;

    if (V4L2_TYPE_IS_MULTIPLANAR(q->type)) {
        /*
         * 如果用户空间提供了一个数组,填充与平面相关的数据。
         * 调用者已经验证了内存和大小。
         */
        b->length = vb->num_planes;
        memcpy(b->m.planes, vb->v4l2_planes,
            b->length * sizeof(struct v4l2_plane));
    } else {
        /*
         * 即使对于单平面缓冲区,我们也使用v4l2_planes数组中的长度和偏移量,
         * 但用户空间不使用。
         */
        b->length = vb->v4l2_planes[0].length;
        b->bytesused = vb->v4l2_planes[0].bytesused;
        if (q->memory == V4L2_MEMORY_MMAP)
            b->m.offset = vb->v4l2_planes[0].m.mem_offset;
        else if (q->memory == V4L2_MEMORY_USERPTR)
            b->m.userptr = vb->v4l2_planes[0].m.userptr;
        else if (q->memory == V4L2_MEMORY_DMABUF)
            b->m.fd = vb->v4l2_planes[0].m.fd;
    }

/*
     * Clear any buffer state related flags.
     */
    // 清除任何与缓冲区状态相关的标志。
    b->flags &= ~V4L2_BUFFER_MASK_FLAGS;
    // 将时间戳标志设置为队列的时间戳标志
    b->flags |= q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK;
    // 如果时间戳标志不是V4L2_BUF_FLAG_TIMESTAMP_COPY,则删除时间戳源位并从队列中获取时间戳源。
    if ((q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) !=
        V4L2_BUF_FLAG_TIMESTAMP_COPY) {
        b->flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
        b->flags |= q->timestamp_flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
    }

    switch (vb->state) {
    case VB2_BUF_STATE_QUEUED:
    case VB2_BUF_STATE_ACTIVE:
        // 如果缓冲区状态为VB2_BUF_STATE_QUEUED或VB2_BUF_STATE_ACTIVE,则将V4L2_BUF_FLAG_QUEUED标志设置为1。
        b->flags |= V4L2_BUF_FLAG_QUEUED;
        break;
    case VB2_BUF_STATE_ERROR:
        // 如果缓冲区状态为VB2_BUF_STATE_ERROR,则将V4L2_BUF_FLAG_ERROR标志设置为1。
        b->flags |= V4L2_BUF_FLAG_ERROR;
        /* fall through */
    case VB2_BUF_STATE_DONE:
        // 如果缓冲区状态为VB2_BUF_STATE_DONE,则将V4L2_BUF_FLAG_DONE标志设置为1。
        b->flags |= V4L2_BUF_FLAG_DONE;
        break;
    case VB2_BUF_STATE_PREPARED:
        // 如果缓冲区状态为VB2_BUF_STATE_PREPARED,则将V4L2_BUF_FLAG_PREPARED标志设置为1。
        b->flags |= V4L2_BUF_FLAG_PREPARED;
        break;
    case VB2_BUF_STATE_PREPARING:
    case VB2_BUF_STATE_DEQUEUED:
        // 如果缓冲区状态为VB2_BUF_STATE_PREPARING或VB2_BUF_STATE_DEQUEUED,则不做任何操作。
        break;
    }

    // 如果缓冲区正在使用,则将V4L2_BUF_FLAG_MAPPED标志设置为1。
    if (__buffer_in_use(q, vb))
        b->flags |= V4L2_BUF_FLAG_MAPPED;
}

9.mmap

uvc_v4l2_mmap

static int
uvc_v4l2_mmap(struct file *file, struct vm_area_struct *vma)
{
    struct video_device *vdev = video_devdata(file);
    struct uvc_device *uvc = video_get_drvdata(vdev);

    return uvcg_queue_mmap(&uvc->video.queue, vma);
}

uvc_v4l2_mmap
uvcg_queue_mmap
vb2_mmap

int vb2_mmap(struct vb2_queue *q, struct vm_area_struct *vma)
{
    // 将虚拟内存地址转换为物理内存地址
    unsigned long off = vma->vm_pgoff << PAGE_SHIFT;
    // 定义一个vb2_buffer结构体指针
    struct vb2_buffer *vb;
    // 定义一个unsigned int类型的变量buffer和plane
    unsigned int buffer = 0, plane = 0;
    // 定义一个int类型的变量ret
    int ret;
    // 定义一个unsigned long类型的变量length

    // 判断内存类型是否为V4L2_MEMORY_MMAP
    if (q->memory != V4L2_MEMORY_MMAP) {
        dprintk(1, "queue is not currently set up for mmap\n");
        return -EINVAL;
    }

    /*
     * 检查内存区域访问模式。
     */
    if (!(vma->vm_flags & VM_SHARED)) {
        dprintk(1, "invalid vma flags, VM_SHARED needed\n");
        return -EINVAL;
    }
    if (V4L2_TYPE_IS_OUTPUT(q->type)) {
        if (!(vma->vm_flags & VM_WRITE)) {
            dprintk(1, "invalid vma flags, VM_WRITE needed\n");
            return -EINVAL;
        }
    } else {
        if (!(vma->vm_flags & VM_READ)) {
            dprintk(1, "invalid vma flags, VM_READ needed\n");
            return -EINVAL;
        }
    }
    if (vb2_fileio_is_active(q)) {
        dprintk(1, "mmap: file io in progress\n");
        return -EBUSY;
    }



    /*
     * Find the plane corresponding to the offset passed by userspace.
     */
    // 找到与用户空间传递的偏移量对应的平面
    ret = __find_plane_by_offset(q, off, &buffer, &plane);
    if (ret)
        return ret;

    vb = q->bufs[buffer];

    /*
     * MMAP requires page_aligned buffers.
     * The buffer length was page_aligned at __vb2_buf_mem_alloc(),
     * so, we need to do the same here.
     */
    // MMAP需要页面对齐的缓冲区。
    // 缓冲区长度在__vb2_buf_mem_alloc()中进行了页面对齐,
    // 因此我们需要在此处执行相同的操作。
    length = PAGE_ALIGN(vb->v4l2_planes[plane].length);
    if (length < (vma->vm_end - vma->vm_start)) {
        dprintk(1,
            "MMAP invalid, as it would overflow buffer length\n");
        return -EINVAL;
    }

    mutex_lock(&q->mmap_lock);
    // 调用内存操作函数mmap
    ret = call_memop(vb, mmap, vb->planes[plane].mem_priv, vma);
    mutex_unlock(&q->mmap_lock);
    if (ret)
        return ret;

    dprintk(3, "buffer %d, plane %d successfully mapped\n", buffer, plane);
    return 0;


}

10.VIDIOC_QBUF

uvc_ioctl_qbuf

static int uvc_ioctl_qbuf(struct file *file, void *fh, struct v4l2_buffer *buf)
{
    // 获取文件句柄
    struct uvc_fh *handle = fh;
    // 获取流
    struct uvc_streaming *stream = handle->stream;

    // 如果没有权限,返回错误
    if (!uvc_has_privileges(handle))
        return -EBUSY;

    // 将缓冲区加入队列
    return uvc_queue_buffer(&stream->queue, buf);
}

uvc_queue_buffer
uvc_queue_buffer
vb2_qbuf
vb2_internal_qbuf

将缓冲区加入队列

static int vb2_internal_qbuf(struct vb2_queue *q, struct v4l2_buffer *b)
{
    /* 调用驱动程序提供的函数做些预处理 */
    int ret = vb2_queue_or_prepare_buf(q, b, "qbuf");
    struct vb2_buffer *vb;

    if (ret)
        return ret;

    vb = q->bufs[b->index];

    switch (vb->state) {
    case VB2_BUF_STATE_DEQUEUED:
        ret = __buf_prepare(vb, b);
        if (ret)
            return ret;
        break;
    case VB2_BUF_STATE_PREPARED:
        break;
    case VB2_BUF_STATE_PREPARING:
        dprintk(1, "buffer still being prepared\n");
        return -EINVAL;
    default:
        dprintk(1, "invalid buffer state %d\n", vb->state);
        return -EINVAL;
    }

    /*
     * Add to the queued buffers list, a buffer will stay on it until
     * dequeued in dqbuf.
     */
     /* 把缓冲区放入队列的尾部 */
    list_add_tail(&vb->queued_entry, &q->queued_list);
    q->queued_count++;
    q->waiting_for_buffers = false;
    vb->state = VB2_BUF_STATE_QUEUED;
    if (V4L2_TYPE_IS_OUTPUT(q->type)) {
        /*
         * For output buffers copy the timestamp if needed,
         * and the timecode field and flag if needed.
         */
        if ((q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) ==
            V4L2_BUF_FLAG_TIMESTAMP_COPY)
            vb->v4l2_buf.timestamp = b->timestamp;
        vb->v4l2_buf.flags |= b->flags & V4L2_BUF_FLAG_TIMECODE;
        if (b->flags & V4L2_BUF_FLAG_TIMECODE)
            vb->v4l2_buf.timecode = b->timecode;
    }

    /*
     * If already streaming, give the buffer to driver for processing.
     * If not, the buffer will be given to driver on next streamon.
     */
    if (q->start_streaming_called)
        __enqueue_in_driver(vb);

    /* Fill buffer information for the userspace */
    __fill_v4l2_buffer(vb, b);

    /*
     * If streamon has been called, and we haven't yet called
     * start_streaming() since not enough buffers were queued, and
     * we now have reached the minimum number of queued buffers,
     * then we can finally call start_streaming().
     */
    if (q->streaming && !q->start_streaming_called &&
        q->queued_count >= q->min_buffers_needed) {
        ret = vb2_start_streaming(q);
        if (ret)
            return ret;
    }

    dprintk(1, "qbuf of buffer %d succeeded\n", vb->v4l2_buf.index);
    return 0;

}

11.VIDIOC_STREAMON

uvc_ioctl_streamon

uvc_video_enable(video, 1)  // 把所设置的参数发给硬件,然后启动摄像头
    /* Commit the streaming parameters. */
    uvc_commit_video
        uvc_set_video_ctrl  /* 设置格式fromat, frame */
                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);
            
    /* 启动:Initialize isochronous/bulk URBs and allocate transfer buffers. */
    uvc_init_video(video, GFP_KERNEL);
            uvc_init_video_isoc / uvc_init_video_bulk
                urb->complete = uvc_video_complete; (收到数据后此函数被调用,它又调用video->decode(urb, video, buf); ==> uvc_video_decode_isoc/uvc_video_encode_bulk => uvc_queue_next_buffer => wake_up(&buf->wait);)
                
            usb_submit_urb                        

uvc_ioctl_streamon

static int uvc_ioctl_streamon(struct file *file, void *fh,
                  enum v4l2_buf_type type)
{
    // 获取文件句柄
    struct uvc_fh *handle = fh;
    // 获取流
    struct uvc_streaming *stream = handle->stream;
    int ret;

    // 如果没有权限,返回错误
    if (!uvc_has_privileges(handle))
        return -EBUSY;

    // 上锁
    mutex_lock(&stream->mutex);
    // 开始流
    ret = uvc_queue_streamon(&stream->queue, type);
    // 解锁
    mutex_unlock(&stream->mutex);

    return ret;
}
uvc_queue_streamon
    vb2_streamon
        vb2_internal_streamon
static int vb2_internal_streamon(struct vb2_queue *q, enum v4l2_buf_type type)
{
    // 检查type是否合法
    if (type != q->type) {
        dprintk(1, "invalid stream type\n");
        return -EINVAL;
    }

    // 如果已经在streaming了,直接返回0
    if (q->streaming) {
        dprintk(3, "already streaming\n");
        return 0;
    }

    // 如果没有分配buffer,返回错误
    if (!q->num_buffers) {
        dprintk(1, "no buffers have been allocated\n");
        return -EINVAL;
    }

    // 如果分配的buffer数量小于最小需要的数量,返回错误
    if (q->num_buffers < q->min_buffers_needed) {
        dprintk(1, "need at least %u allocated buffers\n",
                q->min_buffers_needed);
        return -EINVAL;
    }

    /*
     * 如果已经有足够的buffer在队列中,就通知驱动开始streaming
     */
    if (q->queued_count >= q->min_buffers_needed) {
        int ret = vb2_start_streaming(q);
        if (ret) {
            __vb2_queue_cancel(q);
            return ret;
        }
    }

    // 设置streaming标志位
    q->streaming = 1;

    dprintk(3, "successful\n");
    return 0;
}
uvc_queue_streamon
    vb2_streamon
        vb2_internal_streamon
            vb2_start_streaming
static int vb2_start_streaming(struct vb2_queue *q)
{
    struct vb2_buffer *vb;
    int ret;

    /* 如果有任何缓冲区在流开启之前被排队,我们现在可以将它们传递给驱动程序进行处理。 */
    list_for_each_entry(vb, &q->queued_list, queued_entry)
        __enqueue_in_driver(vb);

    /* 告诉驱动程序开始流 */
    q->start_streaming_called = 1;
    ret = call_qop(q, start_streaming, q,
               atomic_read(&q->owned_by_drv_count));
    if (!ret)
        return 0;

    q->start_streaming_called = 0;

    dprintk(1, "driver refused to start streaming\n");
    /*
     * 如果您看到此警告,则表示驱动程序在失败的start_streaming()之后没有正确清理。
     * 有关如何在start_streaming()中将缓冲区返回给vb2的更多信息,请参见videobuf2-core.h中的start_streaming()文档。
     */
    if (WARN_ON(atomic_read(&q->owned_by_drv_count))) {
        unsigned i;

        /*
         * 如果驱动程序没有正确将它们返回给vb2,则强制回收缓冲区。
         */
        for (i = 0; i < q->num_buffers; ++i) {
            vb = q->bufs[i];
            if (vb->state == VB2_BUF_STATE_ACTIVE)
                vb2_buffer_done(vb, VB2_BUF_STATE_QUEUED);
        }
        /* 必须现在为零 */
        WARN_ON(atomic_read(&q->owned_by_drv_count));
    }
    /*
     * 如果done_list不为空,则start_streaming()没有调用vb2_buffer_done(vb, VB2_BUF_STATE_QUEUED),而是STATE_ERROR或STATE_DONE。
     */
    WARN_ON(!list_empty(&q->done_list));
    return ret;
}
uvc_queue_streamon
    vb2_streamon
        vb2_internal_streamon
            __vb2_queue_cancel
static void __vb2_queue_cancel(struct vb2_queue *q)
{
    unsigned int i;

    /*
     * 停止所有事务并释放所有排队的缓冲区。
     */
    if (q->start_streaming_called)
        call_void_qop(q, stop_streaming, q);

    /*
     * 如果您看到此警告,则表示驱动程序在stop_streaming()中没有正确清理。
     * 有关如何在stop_streaming()中将缓冲区返回给vb2的更多信息,请参见videobuf2-core.h中的stop_streaming()文档。
     */
    if (WARN_ON(atomic_read(&q->owned_by_drv_count))) {
        for (i = 0; i < q->num_buffers; ++i)
            if (q->bufs[i]->state == VB2_BUF_STATE_ACTIVE)
                vb2_buffer_done(q->bufs[i], VB2_BUF_STATE_ERROR);
        /* 现在必须为零 */
        WARN_ON(atomic_read(&q->owned_by_drv_count));
    }

    q->streaming = 0;
    q->start_streaming_called = 0;
    q->queued_count = 0;
    q->error = 0;

    /*
     * 从videobuf的列表中删除所有缓冲区...
     */
    INIT_LIST_HEAD(&q->queued_list);
    /*
     * ...和完成列表;在启动取消之前,用户空间将不会收到任何它尚未出队的缓冲区。
     */
    INIT_LIST_HEAD(&q->done_list);
    atomic_set(&q->owned_by_drv_count, 0);
    wake_up_all(&q->done_wq);

/*
 * Reinitialize all buffers for next use.
 * Make sure to call buf_finish for any queued buffers. Normally
 * that's done in dqbuf, but that's not going to happen when we
 * cancel the whole queue. Note: this code belongs here, not in
 * __vb2_dqbuf() since in vb2_internal_dqbuf() there is a critical
 * call to __fill_v4l2_buffer() after buf_finish(). That order can't
 * be changed, so we can't move the buf_finish() to __vb2_dqbuf().
 */
    for (i = 0; i < q->num_buffers; ++i) {
    // 获取队列中的缓冲区
    struct vb2_buffer *vb = q->bufs[i];

    // 如果缓冲区状态不是VB2_BUF_STATE_DEQUEUED,将其状态设置为VB2_BUF_STATE_PREPARED
    if (vb->state != VB2_BUF_STATE_DEQUEUED) {
        vb->state = VB2_BUF_STATE_PREPARED;
        // 调用buf_finish回调函数,以便在取消队列时完成任何排队的缓冲区
        call_void_vb_qop(vb, buf_finish, vb);
    }
    // 调用__vb2_dqbuf函数,将缓冲区状态设置为VB2_BUF_STATE_ERROR
    __vb2_dqbuf(vb);
    }
}

12.poll

uvc_v4l2_poll

static unsigned int uvc_v4l2_poll(struct file *file, poll_table *wait)
{
    // 获取文件句柄
    struct uvc_fh *handle = file->private_data;
    // 获取流
    struct uvc_streaming *stream = handle->stream;

    // 打印调试信息
    uvc_trace(UVC_TRACE_CALLS, "uvc_v4l2_poll\n");

    // 调用队列的poll函数
    return uvc_queue_poll(&stream->queue, file, wait);
}

uvc_v4l2_poll
uvc_queue_poll

unsigned int uvc_queue_poll(struct uvc_video_queue *queue, struct file *file,
                poll_table *wait)
{
    // 定义返回值
    unsigned int ret;

    // 获取互斥锁
    mutex_lock(&queue->mutex);
    // 调用vb2_poll函数
    ret = vb2_poll(&queue->queue, file, wait);
    // 释放互斥锁
    mutex_unlock(&queue->mutex);

    // 返回结果
    return ret;
}

uvc_v4l2_poll
uvc_queue_poll
vb2_poll

unsigned int vb2_poll(struct vb2_queue *q, struct file *file, poll_table *wait)
{
    // 获取文件描述符对应的video_device结构体
    struct video_device *vfd = video_devdata(file);
    // 获取用户请求的事件
    unsigned long req_events = poll_requested_events(wait);
    // 初始化vb指针
    struct vb2_buffer *vb = NULL;
    // 初始化返回值
    unsigned int res = 0;
    // 初始化标志位
    unsigned long flags;

    // 如果驱动程序使用struct v4l2_fh结构体,则检查是否有挂起的事件
    if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags)) {
        struct v4l2_fh *fh = file->private_data;

        // 如果有挂起的事件,则返回POLLPRI
        if (v4l2_event_pending(fh))
            res = POLLPRI;
        // 如果用户请求的事件中包含POLLPRI,则等待事件
        else if (req_events & POLLPRI)
            poll_wait(file, &fh->wait, wait);
    }


    if (!V4L2_TYPE_IS_OUTPUT(q->type) && !(req_events & (POLLIN | POLLRDNORM)))
        return res;
    if (V4L2_TYPE_IS_OUTPUT(q->type) && !(req_events & (POLLOUT | POLLWRNORM)))
        return res;
    /*
     * 如果队列中没有缓冲区,且文件I/O模拟器未启动,则启动文件I/O模拟器。
     */
    if (q->num_buffers == 0 && !vb2_fileio_is_active(q)) {
        /*
         * 如果队列类型不是输出类型,且I/O模式为读,且用户请求的事件中包含POLLIN或POLLRDNORM,则启动文件I/O模拟器。
         */
        if (!V4L2_TYPE_IS_OUTPUT(q->type) && (q->io_modes & VB2_READ) &&
                (req_events & (POLLIN | POLLRDNORM))) {
            /*
             * 如果文件I/O模拟器初始化失败,则返回POLLERR。
             */
            if (__vb2_init_fileio(q, 1))
                return res | POLLERR;
        }
        /*
         * 如果队列类型是输出类型,且I/O模式为写,且用户请求的事件中包含POLLOUT或POLLWRNORM,则启动文件I/O模拟器。
         */
        if (V4L2_TYPE_IS_OUTPUT(q->type) && (q->io_modes & VB2_WRITE) &&
                (req_events & (POLLOUT | POLLWRNORM))) {
            /*
             * 如果文件I/O模拟器初始化失败,则返回POLLERR。
             */
            if (__vb2_init_fileio(q, 0))
                return res | POLLERR;
            /*
             * 对于输出队列,可以立即进行写操作。
             */
            return res | POLLOUT | POLLWRNORM;
        }
    }


    /*
     * 如果队列不在流式传输状态,或者错误标志被设置,则没有等待的内容。
     */
    if (!vb2_is_streaming(q) || q->error)
        return res | POLLERR;
    /*
     * 为了与vb1兼容:如果尚未调用QBUF,则返回POLLERR。这仅影响捕获队列,输出队列将始终将waiting_for_buffers初始化为false。
     */
    if (q->waiting_for_buffers)
        return res | POLLERR;


    /*
     * 对于输出流,只要排队的缓冲区少于可用的缓冲区,就可以写入。
     */
    if (V4L2_TYPE_IS_OUTPUT(q->type) && q->queued_count < q->num_buffers)
        return res | POLLOUT | POLLWRNORM;

    // 如果done_list为空,则等待
    if (list_empty(&q->done_list))
        poll_wait(file, &q->done_wq, wait);

    /*
     * 取出第一个可用于出队的缓冲区。
     */
    spin_lock_irqsave(&q->done_lock, flags);
    if (!list_empty(&q->done_list))
        vb = list_first_entry(&q->done_list, struct vb2_buffer,
                    done_entry);
    spin_unlock_irqrestore(&q->done_lock, flags);

    /*
     * 如果缓冲区存在且状态为VB2_BUF_STATE_DONE或VB2_BUF_STATE_ERROR,则返回POLLOUT | POLLWRNORM或POLLIN | POLLRDNORM。
     */
    if (vb && (vb->state == VB2_BUF_STATE_DONE
            || vb->state == VB2_BUF_STATE_ERROR)) {
        return (V4L2_TYPE_IS_OUTPUT(q->type)) ?
                res | POLLOUT | POLLWRNORM :
                res | POLLIN | POLLRDNORM;
    }
    return res;


}

13.VIDIOC_DQBUF

uvc_ioctl_dqbuf

// 从队列中取出缓冲区
static int uvc_ioctl_dqbuf(struct file *file, void *fh, struct v4l2_buffer *buf)
{
    // 获取文件句柄
    struct uvc_fh *handle = fh;
    // 获取流
    struct uvc_streaming *stream = handle->stream;

    // 如果没有权限,返回错误
    if (!uvc_has_privileges(handle))
        return -EBUSY;

    // 从队列中取出缓冲区
    return uvc_dequeue_buffer(&stream->queue, buf,
                  file->f_flags & O_NONBLOCK);
}

uvc_ioctl_dqbuf
uvc_dequeue_buffer

// 从视频队列中取出一个缓冲区
int uvc_dequeue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf,
               int nonblocking)
{
    // 定义返回值
    int ret;

    // 获取互斥锁
    mutex_lock(&queue->mutex);
    // 调用vb2_dqbuf函数从视频队列中取出一个缓冲区
    ret = vb2_dqbuf(&queue->queue, buf, nonblocking);
    // 释放互斥锁
    mutex_unlock(&queue->mutex);

    // 返回结果
    return ret;
}

uvc_ioctl_dqbuf
uvc_dequeue_buffer
vb2_dqbuf

int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking)
{
    // 如果文件IO正在进行,则返回忙
    if (vb2_fileio_is_active(q)) {
        dprintk(1, "file io in progress\n"); // 打印信息
        return -EBUSY; // 返回忙
    }
    // 调用内部dqbuf函数
    return vb2_internal_dqbuf(q, b, nonblocking);
}

uvc_ioctl_dqbuf
uvc_dequeue_buffer
vb2_dqbuf
vb2_internal_dqbuf

static int vb2_internal_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking)
{
    struct vb2_buffer *vb = NULL;
    int ret;

    if (b->type != q->type) { // 如果缓冲区类型与队列类型不匹配
        dprintk(1, "invalid buffer type\n"); // 打印错误信息
        return -EINVAL; // 返回无效参数错误
    }
    ret = __vb2_get_done_vb(q, &vb, b, nonblocking); // 获取已完成的缓冲区
    if (ret < 0) // 如果获取失败
        return ret; // 返回获取失败的错误码

    switch (vb->state) { // 根据缓冲区状态进行不同的操作
    case VB2_BUF_STATE_DONE: // 如果缓冲区状态为已完成
        dprintk(3, "returning done buffer\n"); // 打印信息
        break;
    case VB2_BUF_STATE_ERROR: // 如果缓冲区状态为错误
        dprintk(3, "returning done buffer with errors\n"); // 打印信息
        break;
    default: // 如果缓冲区状态无效
        dprintk(1, "invalid buffer state\n"); // 打印错误信息
        return -EINVAL; // 返回无效参数错误
    }

    call_void_vb_qop(vb, buf_finish, vb); // 调用buf_finish回调

    /* 填充缓冲区信息以供用户空间使用 */
    __fill_v4l2_buffer(vb, b);
    /* 从videobuf队列中删除 */
    list_del(&vb->queued_entry);
    q->queued_count--;
    /* 回到已出队列状态 */
    __vb2_dqbuf(vb);

    dprintk(1, "dqbuf of buffer %d, with state %d\n",
            vb->v4l2_buf.index, vb->state); // 打印信息

    return 0; // 返回成功
}

14.VIDIOC_STREAMOFF

uvc_ioctl_streamoff

// 停止流
static int uvc_ioctl_streamoff(struct file *file, void *fh,
                   enum v4l2_buf_type type)
{
    // 获取文件句柄
    struct uvc_fh *handle = fh;
    // 获取流
    struct uvc_streaming *stream = handle->stream;

    // 如果没有权限,返回错误
    if (!uvc_has_privileges(handle))
        return -EBUSY;

    // 上锁
    mutex_lock(&stream->mutex);
    // 停止流
    uvc_queue_streamoff(&stream->queue, type);
    // 解锁
    mutex_unlock(&stream->mutex);

    return 0;
}

uvc_ioctl_streamoff
uvc_queue_streamoff

// uvc_queue_streamoff函数,用于停止视频流
int uvc_queue_streamoff(struct uvc_video_queue *queue, enum v4l2_buf_type type)
{
    int ret;

    // 获取互斥锁
    mutex_lock(&queue->mutex);
    // 调用vb2_streamoff函数停止视频流
    ret = vb2_streamoff(&queue->queue, type);
    // 释放互斥锁
    mutex_unlock(&queue->mutex);

    // 返回结果
    return ret;
}

uvc_ioctl_streamoff
uvc_queue_streamoff
vb2_streamoff

int vb2_streamoff(struct vb2_queue *q, enum v4l2_buf_type type)
{
    // 如果文件IO正在进行中,则返回EBUSY
    if (vb2_fileio_is_active(q)) {
        dprintk(1, "file io in progress\n");
        return -EBUSY;
    }
    // 调用vb2_internal_streamoff函数停止streaming
    return vb2_internal_streamoff(q, type);
}

uvc_ioctl_streamoff
uvc_queue_streamoff
vb2_streamoff
vb2_internal_streamoff

static int vb2_internal_streamoff(struct vb2_queue *q, enum v4l2_buf_type type)
{
    // 如果type不等于当前队列的type,返回错误
    if (type != q->type) {
        dprintk(1, "invalid stream type\n");
        return -EINVAL;
    }

    /*
     * Cancel will pause streaming and remove all buffers from the driver
     * and videobuf, effectively returning control over them to userspace.
     *
     * Note that we do this even if q->streaming == 0: if you prepare or
     * queue buffers, and then call streamoff without ever having called
     * streamon, you would still expect those buffers to be returned to
     * their normal dequeued state.
     */
    // 取消streaming并将所有缓冲区从驱动程序和videobuf中删除,将控制权返回给用户空间
    __vb2_queue_cancel(q);
    // 如果队列类型不是输出类型,则等待缓冲区
    q->waiting_for_buffers = !V4L2_TYPE_IS_OUTPUT(q->type);

    dprintk(3, "successful\n");
    return 0;
}

uvc_ioctl_streamoff
uvc_queue_streamoff
vb2_streamoff
vb2_internal_streamoff
__vb2_queue_cancel

static void __vb2_queue_cancel(struct vb2_queue *q)
{
    unsigned int i;

    /*
     * 停止所有事务并释放所有排队的缓冲区。
     */
    if (q->start_streaming_called)
        call_void_qop(q, stop_streaming, q);

    /*
     * 如果您看到此警告,则表示驱动程序在stop_streaming()中没有正确清理。
     * 有关如何在stop_streaming()中将缓冲区返回给vb2的更多信息,请参见videobuf2-core.h中的stop_streaming()文档。
     */
    if (WARN_ON(atomic_read(&q->owned_by_drv_count))) {
        for (i = 0; i < q->num_buffers; ++i)
            if (q->bufs[i]->state == VB2_BUF_STATE_ACTIVE)
                vb2_buffer_done(q->bufs[i], VB2_BUF_STATE_ERROR);
        /* 现在必须为零 */
        WARN_ON(atomic_read(&q->owned_by_drv_count));
    }

    q->streaming = 0;
    q->start_streaming_called = 0;
    q->queued_count = 0;
    q->error = 0;

    /*
     * 从videobuf的列表中删除所有缓冲区...
     */
    INIT_LIST_HEAD(&q->queued_list);
    /*
     * ...和完成列表;在启动取消之前,用户空间将不会收到任何它尚未出队的缓冲区。
     */
    INIT_LIST_HEAD(&q->done_list);
    atomic_set(&q->owned_by_drv_count, 0);
    wake_up_all(&q->done_wq);

/*
 * Reinitialize all buffers for next use.
 * Make sure to call buf_finish for any queued buffers. Normally
 * that's done in dqbuf, but that's not going to happen when we
 * cancel the whole queue. Note: this code belongs here, not in
 * __vb2_dqbuf() since in vb2_internal_dqbuf() there is a critical
 * call to __fill_v4l2_buffer() after buf_finish(). That order can't
 * be changed, so we can't move the buf_finish() to __vb2_dqbuf().
 */
    for (i = 0; i < q->num_buffers; ++i) {
    // 获取队列中的缓冲区
    struct vb2_buffer *vb = q->bufs[i];

    // 如果缓冲区状态不是VB2_BUF_STATE_DEQUEUED,将其状态设置为VB2_BUF_STATE_PREPARED
    if (vb->state != VB2_BUF_STATE_DEQUEUED) {
        vb->state = VB2_BUF_STATE_PREPARED;
        // 调用buf_finish回调函数,以便在取消队列时完成任何排队的缓冲区
        call_void_vb_qop(vb, buf_finish, vb);
    }
    // 调用__vb2_dqbuf函数,将缓冲区状态设置为VB2_BUF_STATE_ERROR
    __vb2_dqbuf(vb);
    }
}

分析设置亮度过程:

ioctl: VIDIOC_S_CTRL

uvc_ctrl_set
uvc_ctrl_commit
__uvc_ctrl_commit(video, 0);
uvc_ctrl_commit_entity(video->dev, entity, rollback);
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);

uvc_ioctl_s_ctrl

static int uvc_ioctl_s_ctrl(struct file *file, void *fh,
                struct v4l2_control *ctrl)
{
    // 获取文件句柄
    struct uvc_fh *handle = fh;
    // 获取视频链
    struct uvc_video_chain *chain = handle->chain;
    // 定义一个v4l2_ext_control结构体
    struct v4l2_ext_control xctrl;
    // 定义返回值
    int ret;

    // 初始化xctrl结构体
    memset(&xctrl, 0, sizeof(xctrl));
    // 将ctrl结构体中的id和value赋值给xctrl结构体
    xctrl.id = ctrl->id;
    xctrl.value = ctrl->value;

    // 开始控制
    ret = uvc_ctrl_begin(chain);
    if (ret < 0)
        return ret;

    // 设置控制
    ret = uvc_ctrl_set(chain, &xctrl);
    if (ret < 0) {
        // 回滚控制
        uvc_ctrl_rollback(handle);
        return ret;
    }

    // 提交控制
    ret = uvc_ctrl_commit(handle, &xctrl, 1);
    if (ret < 0)
        return ret;

    // 将xctrl结构体中的value赋值给ctrl结构体
    ctrl->value = xctrl.value;
    return 0;
}

uvc_ioctl_s_ctrl
uvc_ctrl_commit
__uvc_ctrl_commit
uvc_ctrl_commit_entity

static int uvc_ctrl_commit_entity(struct uvc_device *dev,
    struct uvc_entity *entity, int rollback)
{
    // 如果entity为空,直接返回0
    if (entity == NULL)
        return 0;

    // 遍历entity的所有控制项
    for (unsigned int i = 0; i < entity->ncontrols; ++i) {
        // 获取控制项
        struct uvc_control *ctrl = &entity->controls[i];
        // 如果控制项未初始化,则跳过
        if (!ctrl->initialized)
            continue;

        /* Reset the loaded flag for auto-update controls that were
         * marked as loaded in uvc_ctrl_get/uvc_ctrl_set to prevent
         * uvc_ctrl_get from using the cached value, and for write-only
         * controls to prevent uvc_ctrl_set from setting bits not
         * explicitly set by the user.
         */
        // 如果控制项是自动更新或者不支持获取当前值,则将loaded标志位重置为0
        if (ctrl->info.flags & UVC_CTRL_FLAG_AUTO_UPDATE ||
            !(ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR))
            ctrl->loaded = 0;

        // 如果控制项没有被修改,则跳过
        if (!ctrl->dirty)
            continue;

        // 如果不是回滚操作,则将控制项的当前值写入硬件
        if (!rollback)
            ret = uvc_query_ctrl(dev, UVC_SET_CUR, ctrl->entity->id,
                dev->intfnum, ctrl->info.selector,
                uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
                ctrl->info.size);
        else
            ret = 0;

        // 如果是回滚操作或者写入硬件失败,则将控制项的当前值重置为备份值
        if (rollback || ret < 0)
            memcpy(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
                   uvc_ctrl_data(ctrl, UVC_CTRL_DATA_BACKUP),
                   ctrl->info.size);

        // 将控制项的dirty标志位重置为0
        ctrl->dirty = 0;

        // 如果写入硬件失败,则返回错误码
        if (ret < 0)
            return ret;
    }

    return 0;
}

在这里插入图片描述

if (!rollback)
ret = uvc_query_ctrl(dev, UVC_SET_CUR, ctrl->entity->id,
dev->intfnum, ctrl->info.selector,
uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
ctrl->info.size);
else
ret = 0;

// 如果是回滚操作或者写入硬件失败,则将控制项的当前值重置为备份值
if (rollback || ret < 0)
    memcpy(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
           uvc_ctrl_data(ctrl, UVC_CTRL_DATA_BACKUP),
           ctrl->info.size);

dev:哪一个USB设备
ctrl->entity->id:哪一个,unit/terminal
dev->intfnum:哪一个接口, VC interface

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);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/554354.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Java+SpringBoot+vue+element实现校园疫情防控系统详细设计和实现

基于JavaSpringBootvueelement实现校园疫情防控系统详细设计和实现 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源…

好用又便宜的平替苹果笔有哪些?平价的平板触控笔推荐

苹果的Pencil在最近一直都受到市场的追捧&#xff0c;而苹果原装的那款电容笔&#xff0c;除了性能好&#xff0c;还有就是价格贵了点。当然&#xff0c;你也可以使用这款Apple Pencil&#xff0c;但是&#xff0c;如果你不愿意花费太多的钱&#xff0c;可以选择一个平替的电容…

低功耗红外测距感应模块 引领皂液机的革新应用方案WTU201F2 B004

作为现代社会卫生意识的提升&#xff0c;智能洗手设备在公共场所的普及变得越来越重要。为了满足市场需求&#xff0c;唯创知音推出了全新的WTU201F2 B004红外测距模块&#xff0c;作为皂液机红外感应模块&#xff0c;凭借其低功耗和小体积的特点&#xff0c;这款模组将成为开发…

本地电脑部署微力同步私人网盘,端口映射实现远程访问

✨个人主页&#xff1a;bit me&#x1f447; 目 录 &#x1f43e;1.前言&#x1f490;2. 微力同步网站搭建&#x1f338;2.1 微力同步下载和安装&#x1f337;2.2 微力同步网页测试&#x1f340;2.3 cpolar的安装和注册 &#x1f339;3.本地网页发布&#x1f33b;3.1 Cpolar云端…

新增Video-Worker组件,支持会话录像自动转MP4格式,JumpServer堡垒机v3.3.0发布

2023年5月22日&#xff0c;JumpServer开源堡垒机正式发布v3.3.0版本。在这一版本中&#xff0c;资产连接令牌支持在有效期内不限次数地复用&#xff1b;用户登录方式&#xff08;包含钉钉、飞书、企业微信扫码登录&#xff09;支持当不存在的用户扫码登录后&#xff0c;自动创建…

【C++】函数提高

欢迎来到博主 Apeiron 的博客&#xff0c;祝您旅程愉快 &#xff01;时止则止&#xff0c;时行则行。动静不失其时&#xff0c;其道光明。 目录 1、缘起 2、函数默认参数 3、函数占位参数 4、总结 1、缘起 以前学习过了函数的基本用法和功能&#xff0c;现在是时候学习函数…

Android应用程序架构分析和基本语法

文章目录 一、控制层与表现层二、Android程序的组成结构三、Android语法基础数据类型与转换转义字符类与对象接口 一、控制层与表现层 在Android应用程序中&#xff0c;逻辑控制层与表现层是分开的设计的。逻辑控制层由Java应用程序实现&#xff0c;表现层由XML文档描述&#…

深入探索 Cilium 的工作机制

这篇之前写 Kubernetes 网络学习之 Cilium 与 eBPF 记录的内容&#xff0c;隔了几个月终于想起把笔记完成&#xff0c;作为探索 Cilium 工作原理的入门&#xff0c;也还是 Cilium 冰山一角&#xff0c;像是高级的网络策略、网络加密、BGP 网络、服务网格等方面并没有深入。如果…

Python实现ACO蚁群优化算法优化BP神经网络回归模型(BP神经网络回归算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 蚁群优化算法(Ant Colony Optimization, ACO)是一种源于大自然生物世界的新的仿生进化算法&#xff0c…

tuple基本用法

元组简介 C11 标准新引入了一种类模板&#xff0c;命名为 tuple&#xff08;中文可直译为元组&#xff09;。tuple 最大的特点是&#xff1a;实例化的对象可以存储任意数量、任意类型的数据。 tuple 的应用场景很广泛&#xff0c;例如当需要存储多个不同类型的元素时&#xf…

Spring Boot 是什么?与传统的 Spring 框架有何不同?

Spring Boot是一个基于Spring框架的快速开发框架&#xff0c;它使用了约定大于配置的方式&#xff0c;可以帮助开发人员快速搭建基于Spring的Web应用程序。相较于传统的Spring框架&#xff0c;Spring Boot的优势在于自动化配置、嵌入式Web容器、依赖管理等方面。本文将详细介绍…

【活动预告】数据集成引擎BitSail遇上CDC

BitSail是字节跳动开源数据集成引擎&#xff0c;于2022年10月26日宣布开源&#xff0c;可支持多种异构数据源间的数据同步&#xff0c;并提供离线、实时、全量、增量场景下全域数据集成解决方案。BitSail支撑了字节内部众多的业务线&#xff0c;支持多种数据源之间的批式/流式/…

码上行动:零基础学会Python编程

前言&#xff1a; Hello大家好&#xff0c;我是Dream。 欢迎来到 Crossin的编程教室 &#xff01;Crossin的新书《码上行动&#xff1a;零基础学会Python编程》终于和大家见面啦&#xff01; 本书力求做到浅显易懂&#xff0c;让完全没有编程经验的零基础“小白”也能学会Pytho…

【网络】IP地址和静态路由

目录 &#x1f341;IP地址的格式 子网掩码 &#x1f341;路由器基本原理与配置 配置IP地址通信 &#x1f341;ARP协议和ICMP协议 &#x1f9e7;广播和广播域的概念 &#x1f9e7;ARP协议 &#x1f9e7;ICMP协议 &#x1f341;静态路由和默认路由 &#x1f9e7;路由原理 &#x…

【Python】判断语句 ① ( if 语句 | if 语句语法 | 代码示例 )

文章目录 一、if 语句语法二、代码示例1、代码示例 - 触发 if 语句2、代码示例 - 不触发 if 语句 一、if 语句语法 在 Python 中 , 使用 if 语句进行判断 , 语法格式如下 : if 判断条件,布尔类型变量或表达式:条件成立,布尔类型变量或表达式为 True 执行的代码判断条件没有括号…

Ansys Speos 2023 R1新功能 | Texture可视化纹理提升视觉感知

Ansys Speos 2023 R1 新功能介绍 Ansys Speos 持续推动创新&#xff0c;为光学设计人员提供精确、高性能的仿真功能。2023 R1 新版本提供强大的功能&#xff0c;可加快结果生成速度、提高仿真精度并扩展与其他 Ansys 产品的互操作性。 更新Texture映射预览&#xff0c;为textur…

如何区别BI、大数据、信息化和数字化转型

商业智能BI可以实现业务流程和业务数据的规范化、流程化、标准化&#xff0c;打通ERP、OA、CRM等不同业务信息系统&#xff0c;整合归纳企业数据&#xff0c;利用数据可视化满足企业不同人群对数据查询、分析和探索的需求&#xff0c;从而为管理和业务提供数据依据和决策支持。…

日用行业外贸ERP软件系统,提高工作效率降低成本

日用行业是一个广泛的行业&#xff0c;包括了许多不同的产品&#xff0c;如家居用品、化妆品、个人护理用品、厨房用具等等。日用行业产品出口&#xff0c;也是我国传统外贸产业之一&#xff0c;在外贸市场来说相对有竞争力优势&#xff0c;在国际贸易中具有很大的需求和市场潜…

抓取鼠标动画

今天给大家分享一个抓取鼠标的动画&#xff0c;嗯&#xff0c;真的是抓取鼠标&#xff01; 代码如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><link href…

C++ 位图及其应用

前言 现实生活中&#xff0c;有很多场景是需要处理数据量很大的数据的&#xff0c;比如&#xff1a; 给40亿个不重复的无符号整数&#xff0c;没排过序。给一个无符号整数&#xff0c;如何快速判断一个数是否在这40亿个数中。 一看到这样的题&#xff0c;我们可能想到的就是 …