转自:http://www.cnblogs.com/tureno/articles/6694463.html

转载于: http://blog.csdn.net/lizuobin2/article/details/53006927

本文基于:linux3.5

前面一篇文章中,简单分析了 V4L2 大框架,本文借助内核中的虚拟摄像头驱动 vivi 来分析一个完整的摄像头驱动程序。vivi 相对于后面要分析的 usb 摄像头驱动程序,它没有真正的硬件相关层的操作,也就是说抛开了复杂的 usb 层的相关知识,便于理解 V4L2 驱动框架,侧重于驱动和应用的交互。

前面我们提到,V4L2 的核心是 v4l2-dev.c 它向上提供统一的文件操作接口 v4l2_fops ,向下提供 video_device 注册接口 register_video_device ,作为一个具体的驱动,需要做的工作就是分配、设置、注册一个 video_device.框架很简单,复杂的是视频设备相关众多的 ioctl。

一、vivi 框架分析

  1. static int __init vivi_init(void)
  2. {
  3. ret = vivi_create_instance(i);
  4. ...
  5. return ret;
  6. }
  7. module_init(vivi_init);

vivi 分配了一个 video_device 指针,没有去设置而是直接让它指向了一个现成的 video_device 结构 vivi_template ,那么全部的工作都将围绕 vivi_template 展开。

  1. static int __init vivi_create_instance(int inst)
  2. {
  3. struct vivi_dev *dev;
  4. struct video_device *vfd;
  5. struct v4l2_ctrl_handler *hdl;
  6. struct vb2_queue *q;
  7. // 分配一个 vivi_dev 结构体
  8. dev = kzalloc(sizeof(*dev), GFP_KERNEL);
  9. // v4l2_dev 初始化,并没有什么作用
  10. ret = v4l2_device_register(NULL, &dev->v4l2_dev);
  11. // 设置 dev 的一些参数,比如图像格式、大小
  12. dev->fmt = &formats[0];
  13. dev->width = 640;
  14. dev->height = 480;
  15. dev->pixelsize = dev->fmt->depth / 8;
  16. ...
  17. // vivi_dev->vb_vidq(vb2_queue) 初始化
  18. q = &dev->vb_vidq;
  19. memset(q, 0, sizeof(dev->vb_vidq));
  20. q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  21. q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ;
  22. q->drv_priv = dev;
  23. q->buf_struct_size = sizeof(struct vivi_buffer);
  24. // vivi_dev->vb_vidq(vb2_queue)->ops
  25. q->ops     = &vivi_video_qops;
  26. // vivi_dev->vb_vidq(vb2_queue)->mem_ops
  27. q->mem_ops = &vb2_vmalloc_memops;
  28. // 初始化一些锁之类的东西
  29. vb2_queue_init(q);
  30. /* init video dma queues */
  31. INIT_LIST_HEAD(&dev->vidq.active);
  32. init_waitqueue_head(&dev->vidq.wq);
  33. // 分配一个 video_device ,这才是重点
  34. vfd = video_device_alloc();
  35. *vfd = vivi_template;
  36. vfd->debug = debug;
  37. vfd->v4l2_dev = &dev->v4l2_dev;
  38. set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);
  39. vfd->lock = &dev->mutex;
  40. // 注册 video_device !!!
  41. ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);
  42. // 把 vivi_dev 放入 video_device->dev->p->driver_data ,这个后边经常用到
  43. video_set_drvdata(vfd, dev);
  44. /* Now that everything is fine, let's add it to device list */
  45. list_add_tail(&dev->vivi_devlist, &vivi_devlist);
  46. if (video_nr != -1)
  47. video_nr++;
  48. // vivi_dev->vfd(video_device) =  vfd
  49. dev->vfd = vfd;
  50. v4l2_info(&dev->v4l2_dev, "V4L2 device registered as %s\n",
  51. video_device_node_name(vfd));
  52. return 0;
  53. }

用户空间调用的是 v4l2_fops ,但是最终会调用到 vivi_fops ,vivi_fops 中的 ioctl 调用 video_ioctl2

  1. static struct video_device vivi_template = {
  2. .name       = "vivi",
  3. .fops           = &vivi_fops,
  4. .ioctl_ops  = &vivi_ioctl_ops,
  5. .minor      = -1,
  6. .release    = video_device_release,
  7. .tvnorms              = V4L2_STD_525_60,
  8. .current_norm         = V4L2_STD_NTSC_M,
  9. };

video_register_device 过程就不详细分析了,前面的文章中分析过,大概就是向核心层注册 video_device 结构体,核心层注册字符设备并提供一个统一的 fops ,当用户空间 read write ioctl 等,最终还是会跳转到 video_device->fops ,还有一点就是核心层会把我们注册进来的 video_device 结构放入一个全局的 video_device数组。

  1. static const struct v4l2_file_operations vivi_fops = {
  2. .owner      = THIS_MODULE,
  3. .open           = v4l2_fh_open,
  4. .release        = vivi_close,
  5. .read           = vivi_read,
  6. .poll       = vivi_poll,
  7. .unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
  8. .mmap           = vivi_mmap,
  9. };

这里,先看一下 v4l2_fh_open 函数

  1. int v4l2_fh_open(struct file *filp)
  2. {
  3. // 前面注册时,我们将 video_device 结构体放入了全局数组 video_device ,现在通过     video_devdata 函数取出来,后面经常用到这种做法
  4. struct video_device *vdev = video_devdata(filp);
  5. // 分配一个 v4l2_fh 结构,放入file->private_data 中
  6. struct v4l2_fh *fh = kzalloc(sizeof(*fh), GFP_KERNEL);
  7. filp->private_data = fh;
  8. if (fh == NULL)
  9. return -ENOMEM;
  10. v4l2_fh_init(fh, vdev);
  11. v4l2_fh_add(fh);
  12. return 0;
  13. }

1、我们随时可以通过 video_devdata 取出我们注册的 video_device 结构进行操作

2、我们随时可以通过 file->private_data 取出 v4l2_fh 结构,虽然现在还不知道它有啥用

下面来分析 ioctl ...首先来看一下调用过程

  1. long video_ioctl2(struct file *file,
  2. unsigned int cmd, unsigned long arg)
  3. {
  4. return video_usercopy(file, cmd, arg, __video_do_ioctl);
  5. }
  1. static long __video_do_ioctl(struct file *file,
  2. unsigned int cmd, void *arg)
  3. {
  4. struct video_device *vfd = video_devdata(file);
  5. const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;
  6. void *fh = file->private_data;
  7. struct v4l2_fh *vfh = NULL;
  8. int use_fh_prio = 0;
  9. long ret = -ENOTTY;
  10. if (ops == NULL) {
  11. printk(KERN_WARNING "videodev: \"%s\" has no ioctl_ops.\n",
  12. vfd->name);
  13. return ret;
  14. }
  15. if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags)) {
  16. vfh = file->private_data;
  17. use_fh_prio = test_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);
  18. }
  19. if (v4l2_is_known_ioctl(cmd)) {
  20. struct v4l2_ioctl_info *info = &v4l2_ioctls[_IOC_NR(cmd)];
  21. if (!test_bit(_IOC_NR(cmd), vfd->valid_ioctls) &&
  22. !((info->flags & INFO_FL_CTRL) && vfh && vfh->ctrl_handler))
  23. return -ENOTTY;
  24. if (use_fh_prio && (info->flags & INFO_FL_PRIO)) {
  25. ret = v4l2_prio_check(vfd->prio, vfh->prio);
  26. if (ret)
  27. return ret;
  28. }
  29. }
  30. if ((vfd->debug & V4L2_DEBUG_IOCTL) &&
  31. !(vfd->debug & V4L2_DEBUG_IOCTL_ARG)) {
  32. v4l_print_ioctl(vfd->name, cmd);
  33. printk(KERN_CONT "\n");
  34. }
  35. switch (cmd) {
  36. /* --- capabilities ------------------------------------------ */
  37. case VIDIOC_QUERYCAP:
  38. {
  39. struct v4l2_capability *cap = (struct v4l2_capability *)arg;
  40. cap->version = LINUX_VERSION_CODE;
  41. ret = ops->vidioc_querycap(file, fh, cap);
  42. if (!ret)
  43. dbgarg(cmd, "driver=%s, card=%s, bus=%s, "
  44. "version=0x%08x, "
  45. "capabilities=0x%08x, "
  46. "device_caps=0x%08x\n",
  47. cap->driver, cap->card, cap->bus_info,
  48. cap->version,
  49. cap->capabilities,
  50. cap->device_caps);
  51. break;
  52. }

vivi 驱动就复杂在这些 ioctl 上,下面按照应用层与驱动的交互顺序来具体的分析这些 ioctl 。

二、ioctl 深入分析

应用空间的一个视频 app 与驱动的交互流程大致如下图所示:

  下面就根据流程,分析每一个 ioctl 在 vivi 中的具体实现。把以上的过程吃透,自己写一个虚拟摄像头程序应该就不成问题了。

2.1 VIDIOC_QUERYCAP 查询设备能力

应用层:

  1. struct v4l2_capability {
  2. __u8    driver[16]; /* i.e. "bttv" */
  3. __u8    card[32];   /* i.e. "Hauppauge WinTV" */
  4. __u8    bus_info[32];   /* "PCI:" + pci_name(pci_dev) */
  5. __u32   version;        <span style="white-space:pre">    </span>/* should use KERNEL_VERSION() */
  6. __u32   capabilities;   /* Device capabilities */
  7. __u32   reserved[4];
  8. };
  9. struct v4l2_capability cap;
  10. ret = ioctl(fd,VIDIOC_QUERYCAP,&cap);
  11. if (ret < 0) {
  12. LOG("VIDIOC_QUERYCAP failed (%d)\n", ret);
  13. return ret;
  14. }

驱动层:

  1. void *fh = file->private_data;
  2. ops->vidioc_querycap(file, fh, cap);
  3. static int vidioc_querycap(struct file *file, void  *priv, struct v4l2_capability *cap)
  4. {
  5. struct vivi_fh  *fh  = priv;
  6. struct vivi_dev *dev = fh->dev;
  7. // 这里只是将一些信息写回用户空间而已,非常简单
  8. strcpy(cap->driver, "vivi");
  9. strcpy(cap->card, "vivi");
  10. strlcpy(cap->bus_info, dev->v4l2_dev.name, sizeof(cap->bus_info));
  11. cap->version =     VIVI_VERSION; cap->capabilities =V4L2_CAP_VIDEO_CAPTURE |V4L2_CAP_STREAMING     | V4L2_CAP_READWRITE;return 0;}
  12. }

一般我们只关心 capabilities 成员,比如V4L2_CAP_VIDEO_CAPTURE 具有视频捕获能力,其它定义如下:

  1. /* Values for 'capabilities' field */
  2. #define V4L2_CAP_VIDEO_CAPTURE      0x00000001  /* Is a video capture device */
  3. #define V4L2_CAP_VIDEO_OUTPUT       0x00000002  /* Is a video output device */
  4. #define V4L2_CAP_VIDEO_OVERLAY      0x00000004  /* Can do video overlay */
  5. #define V4L2_CAP_VBI_CAPTURE        0x00000010  /* Is a raw VBI capture device */
  6. #define V4L2_CAP_VBI_OUTPUT     0x00000020  /* Is a raw VBI output device */
  7. #define V4L2_CAP_SLICED_VBI_CAPTURE 0x00000040  /* Is a sliced VBI capture device */
  8. #define V4L2_CAP_SLICED_VBI_OUTPUT  0x00000080  /* Is a sliced VBI output device */
  9. #define V4L2_CAP_RDS_CAPTURE        0x00000100  /* RDS data capture */
  10. #define V4L2_CAP_VIDEO_OUTPUT_OVERLAY   0x00000200  /* Can do video output overlay */
  11. #define V4L2_CAP_HW_FREQ_SEEK       0x00000400  /* Can do hardware frequency seek  */
  12. #define V4L2_CAP_RDS_OUTPUT     0x00000800  /* Is an RDS encoder */

2.2 VIDIOC_ENUM_FMT 枚举(查询)设备支持的视频格式

应用层:

  1. struct v4l2_fmtdesc {
  2. __u32           index;             /* Format number      */
  3. enum v4l2_buf_type  type;              /* buffer type        */
  4. __u32               flags;
  5. __u8            description[32];   /* Description string */
  6. __u32           pixelformat;       /* Format fourcc      */
  7. __u32           reserved[4];
  8. };
  9. struct v4l2_fmtdesc fmtdesc;
  10. fmtdesc.index=0;
  11. fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
  12. while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)
  13. {
  14. printf("SUPPORT\t%d.%s\n",fmtdesc.index+1,fmtdesc.description);
  15. fmtdesc.index++;
  16. }

驱动层:

  1. static struct vivi_fmt formats[] = {
  2. {
  3. .name     = "4:2:2, packed, YUYV",
  4. .fourcc   = V4L2_PIX_FMT_YUYV,
  5. .depth    = 16,
  6. },
  7. ...
  8. }
  9. static int vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
  10. struct v4l2_fmtdesc *f)
  11. {
  12. struct vivi_fmt *fmt;
  13. if (f->index >= ARRAY_SIZE(formats))
  14. return -EINVAL;
  15. fmt = &formats[f->index];
  16. strlcpy(f->description, fmt->name, sizeof(f->description));
  17. f->pixelformat = fmt->fourcc;
  18. return 0;
  19. }

一般一个设备支持多种视频格式,比如 vivi 它所支持的格式存放在 formats 数组中,由于应用层并不知道设备支持多少种格式,也不知道某种格式具体存放在哪个数组项中,因此通过index从0开始尝试,对于驱动层来说就是遍历所有的数组项,返回每一个index对应的视频格式,比如 V4L2_PIX_FMT_YUYV .

2.3 VIDIOC_S_FMT 设置视频格式

应用层:

  1. struct v4l2_format {
  2. enum v4l2_buf_type type;
  3. union {
  4. struct v4l2_pix_format      pix;     /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
  5. struct v4l2_window      win;     /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
  6. struct v4l2_vbi_format      vbi;     /* V4L2_BUF_TYPE_VBI_CAPTURE */
  7. struct v4l2_sliced_vbi_format   sliced;  /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
  8. __u8    raw_data[200];                   /* user-defined */
  9. } fmt;
  10. };
  11. struct v4l2_pix_format {
  12. __u32               width;
  13. __u32           height;
  14. __u32           pixelformat;
  15. enum v4l2_field     field;
  16. __u32               bytesperline;   /* for padding, zero if unused */
  17. __u32               sizeimage;
  18. enum v4l2_colorspace    colorspace;
  19. __u32           priv;       /* private data, depends on pixelformat */
  20. };
  21. struct v4l2_format fmt;
  22. memset(&fmt, 0, sizeof(fmt));
  23. fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//格式类型
  24. fmt.fmt.pix.width //宽度
  25. fmt.fmt.pix.height //高度
  26. fmt.fmt.pix.pixelformat = VIDEO_FORMAT;//这一项必须是前面查询出来的某种格式,对应 vivi formats数组
  27. fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;//好像是隔行扫描的意思
  28. ret = ioctl(fd, VIDIOC_S_FMT, &fmt);
  29. if (ret < 0) {
  30. LOG("VIDIOC_S_FMT failed (%d)\n", ret);
  31. return ret;
  32. }

驱动层:

  1. static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
  2. struct v4l2_format *f)
  3. {
  4. struct vivi_dev *dev = video_drvdata(file);
  5. struct vb2_queue *q = &dev->vb_vidq;
  6. int ret = vidioc_try_fmt_vid_cap(file, priv, f);
  7. //if (fmt->fourcc == f->fmt.pix.pixelformat)返回formats[k]
  8. dev->fmt = get_format(f);
  9. dev->pixelsize   = dev->fmt->depth / 8;
  10. dev->width       = f->fmt.pix.width;
  11. dev->height  = f->fmt.pix.height;
  12. dev->field       = f->fmt.pix.field;
  13. return 0;
  14. }
  15. static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
  16. struct v4l2_format *f)
  17. {
  18. struct vivi_dev *dev = video_drvdata(file);
  19. struct vivi_fmt *fmt;
  20. enum v4l2_field field;
  21. fmt = get_format(f);
  22. field = f->fmt.pix.field;
  23. if (field == V4L2_FIELD_ANY) {
  24. field = V4L2_FIELD_INTERLACED;
  25. }
  26. f->fmt.pix.field = field;
  27. v4l_bound_align_image(&f->fmt.pix.width, 48, MAX_WIDTH, 2,
  28. &f->fmt.pix.height, 32, MAX_HEIGHT, 0, 0);
  29. f->fmt.pix.bytesperline =
  30. (f->fmt.pix.width * fmt->depth) >> 3;
  31. f->fmt.pix.sizeimage =
  32. f->fmt.pix.height * f->fmt.pix.bytesperline;
  33. if (fmt->fourcc == V4L2_PIX_FMT_YUYV ||
  34. fmt->fourcc == V4L2_PIX_FMT_UYVY)
  35. f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
  36. else
  37. f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
  38. return 0;
  39. }

这里将应用层传进来的视频格式简单处理后存放进了一个 vivi_dev 结构,vivi_dev 哪里来的呢?,在一开始的时候 vivi_create_instance ,我们创建了一个 video_device 结构代表我们的设备,并设置了一个 vivi_dev 作为 video_device->dev->privatedata ,之后 register_video_device ,内核会自动将我们的 video_device 放入全局数组 video_device[] 中。

2.4 VIDIOC_G_FMT 获得设置好的视频格式

应用层:

  1. ret = ioctl(fd, VIDIOC_G_FMT, &fmt);
  2. if (ret < 0) {
  3. LOG("VIDIOC_G_FMT failed (%d)\n", ret);
  4. return ret;
  5. }
  6. // Print Stream Format
  7. LOG("Stream Format Informations:\n");
  8. LOG(" type: %d\n", fmt.type);
  9. LOG(" width: %d\n", fmt.fmt.pix.width);
  10. LOG(" height: %d\n", fmt.fmt.pix.height);
  11. char fmtstr[8];
  12. memset(fmtstr, 0, 8);
  13. memcpy(fmtstr, &fmt.fmt.pix.pixelformat, 4);
  14. LOG(" pixelformat: %s\n", fmtstr);
  15. LOG(" field: %d\n", fmt.fmt.pix.field);
  16. LOG(" bytesperline: %d\n", fmt.fmt.pix.bytesperline);
  17. LOG(" sizeimage: %d\n", fmt.fmt.pix.sizeimage);
  18. LOG(" colorspace: %d\n", fmt.fmt.pix.colorspace);
  19. LOG(" priv: %d\n", fmt.fmt.pix.priv);
  20. LOG(" raw_date: %s\n", fmt.fmt.raw_data);

驱动层:

  1. static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
  2. struct v4l2_format *f)
  3. {
  4. struct vivi_dev *dev = video_drvdata(file);
  5. <span style="white-space:pre">    </span>// 把记录在 vivi_dev 中的参数写回用户空间
  6. f->fmt.pix.width        = dev->width;
  7. f->fmt.pix.height       = dev->height;
  8. f->fmt.pix.field        = dev->field;
  9. f->fmt.pix.pixelformat  = dev->fmt->fourcc;
  10. f->fmt.pix.bytesperline =
  11. (f->fmt.pix.width * dev->fmt->depth) >> 3;
  12. f->fmt.pix.sizeimage =
  13. f->fmt.pix.height * f->fmt.pix.bytesperline;
  14. if (dev->fmt->fourcc == V4L2_PIX_FMT_YUYV ||
  15. dev->fmt->fourcc == V4L2_PIX_FMT_UYVY)
  16. f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
  17. else
  18. f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
  19. return 0;
  20. }

将我们之前设置的格式返回而已。
  2.5 VIDIOC_REQBUFS 请求在内核空间分配视频缓冲区
    分配的内存位于内核空间,应用程序无法直接访问,需要通过调用mmap内存映射函数,把内核空间的内存映射到用户空间,应用才可以用用户空间地址来访问内核空间。
应用层:

  1. struct v4l2_requestbuffers {
  2. __u32           count;
  3. __u32           type;       /* enum v4l2_buf_type */
  4. __u32           memory;     /* enum v4l2_memory */
  5. __u32           reserved[2];
  6. };
  7. struct v4l2_requestbuffers reqbuf;
  8. reqbuf.type     = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  9. reqbuf.memory   = V4L2_MEMORY_MMAP;
  10. reqbuf.count    = BUFFER_COUNT;
  11. ret = ioctl(fd , VIDIOC_REQBUFS, &reqbuf);
  12. if(ret < 0) {
  13. LOG("VIDIOC_REQBUFS failed (%d)\n", ret);
  14. return ret;
  15. }

驱动层:

  1. static int vidioc_reqbufs(struct file *file, void *priv,
  2. struct v4l2_requestbuffers *p)
  3. {
  4. struct vivi_dev *dev = video_drvdata(file);
  5. return vb2_reqbufs(&dev->vb_vidq, p);    //核心层提供的标准函数
  6. }

vb_vidq 是 vivi_dev 的一个成员,前面我们提到它有两个 ops ,一个是 ops 另一个是 mem_ops

  1. static struct vb2_ops vivi_video_qops = {
  2. .queue_setup    = queue_setup,
  3. .buf_init       = buffer_init,
  4. .buf_prepare    = buffer_prepare,
  5. .buf_finish         = buffer_finish,
  6. .buf_cleanup        = buffer_cleanup,
  7. .buf_queue      = buffer_queue,
  8. .start_streaming= start_streaming,
  9. .stop_streaming     = stop_streaming,
  10. .wait_prepare       = vivi_unlock,
  11. .wait_finish        = vivi_lock,
  12. };
  1. static int vidioc_reqbufs(struct file *file, void *priv,
  2. struct v4l2_requestbuffers *p)
  3. {
  4. struct vivi_dev *dev = video_drvdata(file);
  5. return vb2_reqbufs(&dev->vb_vidq, p);    //核心层提供的标准函数
  6. }
  1. int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
  2. {
  3. unsigned int num_buffers, allocated_buffers, num_planes = 0;
  4. int ret = 0;
  5. // 判断 re->count 是否小于 VIDEO_MAX_FRAME
  6. num_buffers = min_t(unsigned int, req->count, VIDEO_MAX_FRAME);
  7. memset(q->plane_sizes, 0, sizeof(q->plane_sizes));
  8. memset(q->alloc_ctx, 0, sizeof(q->alloc_ctx));
  9. q->memory = req->memory;
  10. //(q)->ops->queue_setup(q,NULL,...)
  11. ret = call_qop(q, queue_setup, q, NULL, &num_buffers, &num_planes,
  12. q->plane_sizes, q->alloc_ctx);
  13. /* Finally, allocate buffers and video memory */
  14. ret = __vb2_queue_alloc(q, req->memory, num_buffers, num_planes);
  15. allocated_buffers = ret;
  16. q->num_buffers = allocated_buffers;
  17. req->count = allocated_buffers;
  18. return 0;
  19. }
  1. static int queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt,
  2. unsigned int *nbuffers, unsigned int *nplanes,
  3. unsigned int sizes[], void *alloc_ctxs[])
  4. {
  5. struct vivi_dev *dev = vb2_get_drv_priv(vq);
  6. unsigned long size;
  7. // 每一个buffer 的大小
  8. size = dev->width * dev->height * dev->pixelsize;
  9. if (0 == *nbuffers)
  10. *nbuffers = 32;
  11. // 如果申请的buffer过多,导致空间不够减少buffer
  12. while (size * *nbuffers > vid_limit * 1024 * 1024)
  13. (*nbuffers)--;
  14. *nplanes = 1;
  15. // 把总大小放入 vivi_dev->vb_vidq->plane_size[0]
  16. sizes[0] = size;
  17. return 0;
  18. }
  1. static int __vb2_queue_alloc(struct vb2_queue *q, enum v4l2_memory memory,
  2. unsigned int num_buffers, unsigned int num_planes)
  3. {
  4. unsigned int buffer;
  5. struct vb2_buffer *vb;
  6. int ret;
  7. // 分配多个 vb2_buffer 填充并放入 vivi_dev->vb_vidq->bufs[]
  8. for (buffer = 0; buffer < num_buffers; ++buffer) {
  9. /* Allocate videobuf buffer structures */
  10. vb = kzalloc(q->buf_struct_size, GFP_KERNEL);
  11. /* Length stores number of planes for multiplanar buffers */
  12. if (V4L2_TYPE_IS_MULTIPLANAR(q->type))
  13. vb->v4l2_buf.length = num_planes;
  14. vb->state = VB2_BUF_STATE_DEQUEUED;
  15. vb->vb2_queue = q;
  16. vb->num_planes = num_planes;
  17. vb->v4l2_buf.index = q->num_buffers + buffer;
  18. vb->v4l2_buf.type = q->type;
  19. vb->v4l2_buf.memory = memory;
  20. /* Allocate video buffer memory for the MMAP type */
  21. if (memory == V4L2_MEMORY_MMAP) {
  22. ret = __vb2_buf_mem_alloc(vb);//核心提供的标准函数
  23. ret = call_qop(q, buf_init, vb);//q->ops->buf_init
  24. }
  25. q->bufs[q->num_buffers + buffer] = vb;
  26. }
  27. __setup_offsets(q, buffer);
  28. return buffer;
  29. }
  1. static int __vb2_buf_mem_alloc(struct vb2_buffer *vb)
  2. {
  3. struct vb2_queue *q = vb->vb2_queue;
  4. void *mem_priv;
  5. int plane;
  6. /* num_planes == 1 */
  7. for (plane = 0; plane < vb->num_planes; ++plane) {
  8. mem_priv = call_memop(q, alloc, q->alloc_ctx[plane],
  9. q->plane_sizes[plane]);
  10. /* Associate allocator private data with this plane */
  11. vb->planes[plane].mem_priv = mem_priv;
  12. vb->v4l2_planes[plane].length = q->[plane];
  13. }
  14. return 0;
  15. }
  1. static void *vb2_vmalloc_alloc(void *alloc_ctx, unsigned long size)
  2. {
  3. struct vb2_vmalloc_buf *buf;
  4. buf = kzalloc(sizeof(*buf), GFP_KERNEL);
  5. buf->size = size;
  6. // 分配空间
  7. buf->vaddr = vmalloc_user(buf->size);
  8. buf->handler.refcount = &buf->refcount;
  9. buf->handler.put = vb2_vmalloc_put;
  10. buf->handler.arg = buf;
  11. atomic_inc(&buf->refcount);
  12. return buf;
  13. }

2.6 VIDIOC_QUERYBUF 查询分配好的 buffer 信息
    查询已经分配好的V4L2视频缓冲区的相关信息,包括缓冲区的使用状态、在内核空间的偏移地址、缓冲区长度等,然后应用程序根据这些信息使用mmap把内核空间地址映射到用户空间。
应用层:

  1. struct v4l2_buffer {
  2. __u32                   index;
  3. enum v4l2_buf_type      type;
  4. __u32                   bytesused;
  5. __u32                   flags;
  6. enum v4l2_field         field;
  7. struct timeval          timestamp;
  8. struct v4l2_timecode    timecode;
  9. __u32                   sequence;
  10. /* memory location */
  11. enum v4l2_memory        memory;
  12. union {
  13. __u32               offset;
  14. unsigned long       userptr;
  15. } m;
  16. __u32                   length;
  17. __u32                   input;
  18. __u32                   reserved;
  19. };
  20. v4l2_buffer buf;
  21. buf.index = i;
  22. buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  23. buf.memory = V4L2_MEMORY_MMAP;
  24. ret = ioctl(fd , VIDIOC_QUERYBUF, &buf);
  25. if(ret < 0) {
  26. LOG("VIDIOC_QUERYBUF (%d) failed (%d)\n", i, ret);
  27. return ret;
  28. }

驱动层:

  1. ops->vidioc_querybuf(file, fh, p);
  2. static int vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
  3. {
  4. struct vivi_dev *dev = video_drvdata(file);
  5. return vb2_querybuf(&dev->vb_vidq, p);
  6. }
  1. int vb2_querybuf(struct vb2_queue *q, struct v4l2_buffer *b)
  2. {
  3. struct vb2_buffer *vb;
  4. // 取出 buf
  5. vb = q->bufs[b->index];
  6. // 将 buf 信息写回用户空间传递的 b
  7. return __fill_v4l2_buffer(vb, b);
  8. }
  1. static int __fill_v4l2_buffer(struct vb2_buffer *vb, struct v4l2_buffer *b)
  2. {
  3. struct vb2_queue *q = vb->vb2_queue;
  4. int ret;
  5. /* Copy back data such as timestamp, flags, input, etc. */
  6. memcpy(b, &vb->v4l2_buf, offsetof(struct v4l2_buffer, m));
  7. b->input = vb->v4l2_buf.input;
  8. b->reserved = vb->v4l2_buf.reserved;
  9. if (V4L2_TYPE_IS_MULTIPLANAR(q->type)) {
  10. ret = __verify_planes_array(vb, b);
  11. if (ret)
  12. return ret;
  13. /*
  14. * Fill in plane-related data if userspace provided an array
  15. * for it. The memory and size is verified above.
  16. */
  17. memcpy(b->m.planes, vb->v4l2_planes,
  18. b->length * sizeof(struct v4l2_plane));
  19. if (q->memory == V4L2_MEMORY_DMABUF) {
  20. unsigned int plane;
  21. for (plane = 0; plane < vb->num_planes; ++plane)
  22. b->m.planes[plane].m.fd = 0;
  23. }
  24. } else {
  25. /*
  26. * We use length and offset in v4l2_planes array even for
  27. * single-planar buffers, but userspace does not.
  28. */
  29. b->length = vb->v4l2_planes[0].length;
  30. b->bytesused = vb->v4l2_planes[0].bytesused;
  31. if (q->memory == V4L2_MEMORY_MMAP)
  32. b->m.offset = vb->v4l2_planes[0].m.mem_offset;
  33. else if (q->memory == V4L2_MEMORY_USERPTR)
  34. b->m.userptr = vb->v4l2_planes[0].m.userptr;
  35. else if (q->memory == V4L2_MEMORY_DMABUF)
  36. b->m.fd = 0;
  37. }
  38. /*
  39. * Clear any buffer state related flags.
  40. */
  41. b->flags &= ~V4L2_BUFFER_STATE_FLAGS;
  42. switch (vb->state) {
  43. case VB2_BUF_STATE_QUEUED:
  44. case VB2_BUF_STATE_ACTIVE:
  45. b->flags |= V4L2_BUF_FLAG_QUEUED;
  46. break;
  47. case VB2_BUF_STATE_ERROR:
  48. b->flags |= V4L2_BUF_FLAG_ERROR;
  49. /* fall through */
  50. case VB2_BUF_STATE_DONE:
  51. b->flags |= V4L2_BUF_FLAG_DONE;
  52. break;
  53. case VB2_BUF_STATE_PREPARED:
  54. b->flags |= V4L2_BUF_FLAG_PREPARED;
  55. break;
  56. case VB2_BUF_STATE_DEQUEUED:
  57. /* nothing */
  58. break;
  59. }
  60. if (__buffer_in_use(q, vb))
  61. b->flags |= V4L2_BUF_FLAG_MAPPED;
  62. return 0;
  63. }

2.7 mmap
应用层:

  1. v4l2_buffer framebuf[]
  2. framebuf[i].length = buf.length;
  3. framebuf[i].start = (char *) mmap(
  4. NULL,       // 欲指向内存的起始地址,一般为NULL,表示系统自动分配
  5. buf.length, //映射长度
  6. PROT_READ|PROT_WRITE,   //可读可写
  7. MAP_SHARED,     //对映射区的读写会写回内核空间,而且允许其它映射该内核空间地址的进程共享
  8. fd,
  9. buf.m.offset
  10. );
  11. if (framebuf[i].start == MAP_FAILED) {
  12. LOG("mmap (%d) failed: %s\n", i, strerror(errno));
  13. return -1;
  14. }

驱动层:

  1. static int vivi_mmap(struct file *file, struct vm_area_struct *vma)
  2. {
  3. struct vivi_dev *dev = video_drvdata(file);
  4. int ret;
  5. ret = vb2_mmap(&dev->vb_vidq, vma);//核心层提供的函数
  6. return ret;
  7. }

2.8 VIDIOC_QBUF 
  投放一个空的视频缓冲区到视频缓冲区输入队列,执行成功后,在启动视频设备拍摄图像时,相应的视频数据被保存到视频输入队列相应的视频缓冲区中。
应用层:

  1. ret = ioctl(fd , VIDIOC_QBUF, &buf);
  2. if (ret < 0) {
  3. LOG("VIDIOC_QBUF (%d) failed (%d)\n", i, ret);
  4. return -1;

驱动层:

  1. static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
  2. {
  3. struct vivi_dev *dev = video_drvdata(file);
  4. return vb2_qbuf(&dev->vb_vidq, p);
  5. }
  1. int vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b)
  2. {
  3. struct rw_semaphore *mmap_sem = NULL;
  4. struct vb2_buffer *vb;
  5. int ret = 0;
  6. vb = q->bufs[b->index];
  7. switch (vb->state) {
  8. case VB2_BUF_STATE_DEQUEUED:
  9. ret = __buf_prepare(vb, b);
  10. }
  11. // 将这个 buffer 挂入 q->queued_list
  12. list_add_tail(&vb->queued_entry, &q->queued_list);
  13. vb->state = VB2_BUF_STATE_QUEUED;
  14. if (q->streaming)
  15. __enqueue_in_driver(vb);
  16. /* Fill buffer information for the userspace */
  17. __fill_v4l2_buffer(vb, b);
  18. unlock:
  19. if (mmap_sem)
  20. up_read(mmap_sem);
  21. return ret;
  22. }

实质上就是取出一个 vb2_buffer 挂入 vivi_dev->vb_vidq->queued_list
2.9 VIDIOC_STREAMON
应用层:

  1. enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  2. ret = ioctl(fd, VIDIOC_STREAMON, &type);
  3. if (ret < 0) {
  4. LOG("VIDIOC_STREAMON failed (%d)\n", ret);
  5. return ret;
  6. }

驱动层:

  1. static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
  2. {
  3. struct vivi_dev *dev = video_drvdata(file);
  4. return vb2_streamon(&dev->vb_vidq, i);
  5. }
  1. int vb2_streamon(struct vb2_queue *q, enum v4l2_buf_type type)
  2. {
  3. struct vb2_buffer *vb;
  4. int ret;
  5. vb->state = VB2_BUF_STATE_ACTIVE;
  6. // 在 queued_list 链表中取出每一个 buffer 调用buffer queue,对于vivi来说就是放入 vidq->active 链表
  7. list_for_each_entry(vb, &q->queued_list, queued_entry)
  8. __enqueue_in_driver(vb);
  9. ret = call_qop(q, start_streaming, q, atomic_read(&q->queued_count));
  10. q->streaming = 1;
  11. return 0;
  12. }
  1. static void __enqueue_in_driver(struct vb2_buffer *vb)
  2. {
  3. struct vb2_queue *q = vb->vb2_queue;
  4. vb->state = VB2_BUF_STATE_ACTIVE;
  5. /* sync buffers */
  6. for (plane = 0; plane < vb->num_planes; ++plane)
  7. call_memop(q, prepare, vb->planes[plane].mem_priv);
  8. q->ops->buf_queue(vb);//    list_add_tail(&buf->list, &vidq->active);
  9. }
  1. static int start_streaming(struct vb2_queue *vq, unsigned int count)
  2. {
  3. struct vivi_dev *dev = vb2_get_drv_priv(vq);
  4. dprintk(dev, 1, "%s\n", __func__);
  5. return vivi_start_generating(dev);
  6. }
  1. static int vivi_start_generating(struct vivi_dev *dev)
  2. {
  3. struct vivi_dmaqueue *dma_q = &dev->vidq;
  4. /* Resets frame counters */
  5. dev->ms = 0;
  6. dev->mv_count = 0;
  7. dev->jiffies = jiffies;
  8. dma_q->frame = 0;
  9. dma_q->ini_jiffies = jiffies;
  10. // 创建一个内核线程,入口函数 vivi_thread
  11. dma_q->kthread = kthread_run(vivi_thread, dev, dev->v4l2_dev.name);
  12. /* Wakes thread */
  13. wake_up_interruptible(&dma_q->wq);
  14. return 0;
  15. }
  1. static int vivi_thread(void *data)
  2. {
  3. struct vivi_dev *dev = data;
  4. dprintk(dev, 1, "thread started\n");
  5. set_freezable();
  6. for (;;) {
  7. vivi_sleep(dev);
  8. if (kthread_should_stop())
  9. break;
  10. }
  11. dprintk(dev, 1, "thread: exit\n");
  12. return 0;
  13. }
  1. static void vivi_sleep(struct vivi_dev *dev)
  2. {
  3. struct vivi_dmaqueue *dma_q = &dev->vidq;
  4. int timeout;
  5. DECLARE_WAITQUEUE(wait, current);
  6. add_wait_queue(&dma_q->wq, &wait);
  7. if (kthread_should_stop())
  8. goto stop_task;
  9. /* Calculate time to wake up */
  10. timeout = msecs_to_jiffies(frames_to_ms(1));
  11. vivi_thread_tick(dev);
  12. schedule_timeout_interruptible(timeout);
  13. stop_task:
  14. remove_wait_queue(&dma_q->wq, &wait);
  15. try_to_freeze();
  16. }

每次调用 vivi_sleep 这个线程都被挂入等待队列,调用 vivi_thread_tick 填充数据,然后休眠指定的时间自动唤醒,一直循环下去。这样就生成了一帧一帧的视频数据。

  1. static void vivi_thread_tick(struct vivi_dev *dev)
  2. {
  3. struct vivi_dmaqueue *dma_q = &dev->vidq;
  4. struct vivi_buffer *buf;
  5. unsigned long flags = 0;
  6. spin_lock_irqsave(&dev->slock, flags);
  7. buf = list_entry(dma_q->active.next, struct vivi_buffer, list);
  8. list_del(&buf->list);
  9. spin_unlock_irqrestore(&dev->slock, flags);
  10. do_gettimeofday(&buf->vb.v4l2_buf.timestamp);
  11. /* 填充Buffer */
  12. vivi_fillbuff(dev, buf);
  13. vb2_buffer_done(&buf->vb, VB2_BUF_STATE_DONE);
  14. }
  1. void vb2_buffer_done(struct vb2_buffer *vb, enum vb2_buffer_state state)
  2. {
  3. struct vb2_queue *q = vb->vb2_queue;
  4. unsigned long flags;
  5. unsigned int plane;
  6. /* sync buffers */
  7. for (plane = 0; plane < vb->num_planes; ++plane)
  8. call_memop(q, finish, vb->planes[plane].mem_priv);
  9. /* Add the buffer to the done buffers list */
  10. spin_lock_irqsave(&q->done_lock, flags);
  11. vb->state = state;
  12. list_add_tail(&vb->done_entry, &q->done_list);
  13. atomic_dec(&q->queued_count);
  14. #ifdef CONFIG_SYNC
  15. sw_sync_timeline_inc(q->timeline, 1);
  16. #endif
  17. spin_unlock_irqrestore(&q->done_lock, flags);
  18. /* 应用程序select 时 poll_wait 里休眠,现在有数据了唤醒 */
  19. wake_up(&q->done_wq);
  20. }

开始的时候我们将以一个 vb_buffer 挂入 vb_vidq->queued_list ,当启动视频传输之后,它被取出挂入
vb_vidq->vidq->active 队列,然后在内核线程中每一个 tick ,又将它取出填充视频数据之后,再挂入
vb_vidq->done_list ,唤醒正在休眠等待视频数据的应用程序。
2.10 select
驱动层:

  1. vivi_poll(struct file *file, struct poll_table_struct *wait)
  2. {
  3. struct vivi_dev *dev = video_drvdata(file);
  4. struct vb2_queue *q = &dev->vb_vidq;
  5. return vb2_poll(q, file, wait);
  6. }
  1. unsigned int vb2_poll(struct vb2_queue *q, struct file *file, poll_table *wait)
  2. {
  3. // 挂入休眠队列,是否休眠还要看返回值,大概没有数据就休眠,有数据就不休眠
  4. poll_wait(file, &q->done_wq, wait);
  5. if (!list_empty(&q->done_list))
  6. vb = list_first_entry(&q->done_list, struct vb2_buffer,
  7. done_entry);
  8. spin_unlock_irqrestore(&q->done_lock, flags);
  9. if (vb && (vb->state == VB2_BUF_STATE_DONE
  10. || vb->state == VB2_BUF_STATE_ERROR)) {
  11. return (V4L2_TYPE_IS_OUTPUT(q->type)) ?
  12. res | POLLOUT | POLLWRNORM :
  13. res | POLLIN | POLLRDNORM;
  14. }
  15. return res;
  16. }

唤醒之后,我们就可以去从视频输出队列中取出buffer,然后根据映射关系,在应用空间取出视频数据了
2.11 VIDIOC_DQBUF
应用层:

  1. ret = ioctl(fd, VIDIOC_DQBUF, &buf);
  2. if (ret < 0) {
  3. LOG("VIDIOC_DQBUF failed (%d)\n", ret);
  4. return ret;
  5. }
  1. static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
  2. {
  3. struct vivi_dev *dev = video_drvdata(file);
  4. return vb2_dqbuf(&dev->vb_vidq, p, file->f_flags & O_NONBLOCK);
  5. }
  1. int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking)
  2. {
  3. struct vb2_buffer *vb = NULL;
  4. int ret;
  5. // 等待在 q->done_list 取出第一个可用的 buffer
  6. ret = __vb2_get_done_vb(q, &vb, nonblocking);
  7. ret = call_qop(q, buf_finish, vb);
  8. /* 写回buffer的信息到用户空间,应用程序找个这个buffer的mmap之后的地址读数据 */
  9. __fill_v4l2_buffer(vb, b);
  10. /* Remove from videobuf queue */
  11. list_del(&vb->queued_entry);
  12. vb->state = VB2_BUF_STATE_DEQUEUED;
  13. return 0;
  14. }
  1. static int __vb2_get_done_vb(struct vb2_queue *q, struct vb2_buffer **vb,int nonblocking)
  2. {
  3. unsigned long flags;
  4. int ret;
  5. /*
  6. * Wait for at least one buffer to become available on the done_list.
  7. */
  8. ret = __vb2_wait_for_done_vb(q, nonblocking);
  9. spin_lock_irqsave(&q->done_lock, flags);
  10. *vb = list_first_entry(&q->done_list, struct vb2_buffer, done_entry);
  11. list_del(&(*vb)->done_entry);
  12. spin_unlock_irqrestore(&q->done_lock, flags);
  13. return 0;
  14. }
    1. static int buffer_finish(struct vb2_buffer *vb)
    2. {
    3. struct vivi_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
    4. dprintk(dev, 1, "%s\n", __func__);
    5. return 0;
    6. }

V4L2(二)虚拟摄像头驱动vivi深入分析【转】的更多相关文章

  1. 二十四、V4L2框架主要结构体分析和虚拟摄像头驱动编写

    一.V4L2框架主要结构体分析 V4L2(video for linux version 2),是内核中视频设备的驱动框架,为上层访问视频设备提供统一接口. V4L2整体框架如下图: 图中主要包括两层 ...

  2. V4L2学习(五)VIVI虚拟摄像头驱动

    概述 前面简单分析了内核中虚拟摄像头驱动 vivi 的框架与实现,本文参考 vivi 来写一个虚拟摄像头驱动,查询.设置视频格式相对简单,难点在于 vb2_buf 的处理过程. 数据采集流程分析 在我 ...

  3. 彻底分析虚拟视频驱动vivi(三)

    在Ubuntu系统中接上usb摄像头设备时,系统会自动安装对应的usb设备驱动程序.我们现在要使用自己编译的vivi驱动,该怎么办呢? 1.先安装系统自带的vivi驱动和它所依赖的所有驱动:sudo ...

  4. vivi虚拟摄像头驱动程序

    一.vivi虚拟摄像头驱动 基于V4L2(video for linux 2)摄像头驱动程序,我们减去不需要的ioctl_fops的函数,只增加ioctl函数增加的必要的摄像头流查询等函数: #inc ...

  5. V4l2初识(七)-----------浅析app获取虚拟摄像头数据的过程

    继续分析数据的获取过程: 1.请求分配的缓冲区: ioctl(4,VIDIOC_REQBUFS) vidioc_reqbufs 2.查询和映射缓冲区   ioctl(4,VIDIOC_QUERYBUF ...

  6. Linux摄像头驱动学习之:(一)V4L2_框架分析

    这段时间开始搞安卓camera底层驱动了,把以前的的Linux视频驱动回顾一下,本篇主要概述一下vfl2(video for linux 2). 一. V4L2框架: video for linux ...

  7. linux虚拟摄像头vivid配置

    总述    最近在看摄像头驱动,需要配置虚拟摄像头的驱动,但是教程里面是linux2.6内核的,实际电脑的是Ubuntu16,内核是linux4.15版本,从2.6到4.15内核好多文件发生了变化,所 ...

  8. 初始v4l2(六)-------根据虚拟驱动vivi的使用彻底分析摄像头驱动

    前面的几篇文章已经分析了v4l2的框架,对框架的分析是比较粗浅的,能基本清楚函数之间的调用过程.但是很多内容并没有分析,比如说里面有很多ioctl,并没有分析哪些ioctl是必须的,也没有分析如何从应 ...

  9. Linux摄像头驱动学习之:(二)通过虚拟驱动vivi分析摄像头驱动

    一.通过指令 "strace -o xawtv.log xawtv" 得到以下调用信息:// 1~7都是在v4l2_open里调用1. open2. ioctl(4, VIDIOC ...

随机推荐

  1. bzoj1143-祭祀

    题目 给出一个有向无环图,要在上面安放祭祀点.两个祭祀点必须不可达,求最多能安放多少个祭祀点. 分析 由于一条无法再延伸链上只能安放一个祭祀点,而我们要求的是最多能安放祭祀点的个数,所以要求的就是最长 ...

  2. bzoj2676 Contra

    题意: 给定N,R,Q,S 有N个关卡,初始有Q条命,且任意时刻最多只能有Q条命 每通过一个关卡,会得到u分和1条命,其中u=min(最近一次连续通过的关数,R) 若没有通过这个关卡,将失去一条命,并 ...

  3. CF#508 1038E Maximum Matching

    ---题面--- 题解: 感觉还是比较妙的,复杂度看上去很高(其实也很高),但是因为n只有100,所以还是可以过的. 考虑一个很暴力的状态f[i][j][x][y]表示考虑取区间i ~ j的方格,左右 ...

  4. 洛谷 P1640 [SCOI2010]连续攻击游戏 解题报告

    P1640 [SCOI2010]连续攻击游戏 题目描述 lxhgww最近迷上了一款游戏,在游戏里,他拥有很多的装备,每种装备都有2个属性,这些属性的值用[1,10000]之间的数表示.当他使用某种装备 ...

  5. NOIP系列

    NOIP2015运输计划 唉 真是 这题 卡死我了 tarjan离线lca复杂度O(n) 最后各种卡常,多交几遍才A(洛谷104ms) %%%zk学长609ms 注意二分的时候左边界要定成0 根据题意 ...

  6. 用ByteArrayOutputStream解决IO流乱码问题

    IO中用ByteArrayOutputStream解决乱码问题 --另一种解决乱码的方法 IO中另外一种防止乱码的方法:使用ByteArrayOutputStream在创建ByteArrayOutpu ...

  7. 【树形DP】【UVA10859】 Placing Lampposts

    传送门 Description 给定一个\(n\)个点\(m\)条边的无向无环图,选择尽量少的节点,使得所有边都至少有一个顶点被选择.在这个基础上,要求有两个顶点被选择的边数尽可能大 Input 多组 ...

  8. 创建JavaScript的哈希表Hashtable

    Hashtable是最常用的数据结构之一,但在JavaScript里没有各种数据结构对象.但是我们可以利用动态语言的一些特性来实现一些常用的数据结构和操作,这样可以使一些复杂的代码逻辑更清晰,也更符合 ...

  9. Codeforces Round #342 (Div. 2) A

    A. Guest From the Past time limit per test 1 second memory limit per test 256 megabytes input standa ...

  10. 使用feign调用服务的时候注意的问题

    服务端 rest api @RequestMapping(value = "/phone") public ResponsePhone getPhone(@RequestParam ...