Hi3559AV100外接UVC/MJPEG相機實時採圖設計(一):Linux USB攝像頭驅動分析

  下面將給出Hi3559AV100外接UVC/MJPEG相機實時採圖設計的總體流程,主要實現是經過V4L2接口將UVC/MJPEG相機採集的數據送入至MPP平臺,通過VDEC、VPSS、VO最後經過HDMI的輸出,首先給出(一)Linux USB攝像頭驅動加載與分析。node

板載平臺:BOXER-8410AIlinux

芯片型號:Hi3559AV100git

相機型號:Logitch c270ubuntu

開發環境:VM15.5+ubuntu16.04+Hilinuxapp

一、肯定USB攝像頭支持UVC

  首先,能夠把USB攝像頭插在PC端,而後經過設備管理器找到相機,右鍵選擇屬性,選擇詳細信息,更改屬性一欄,選擇硬件ID,從中能夠看到USB攝像頭的VID和PID,好比Logitech c270的ID號爲:046d:0825,以後經過這個網頁 http://www.ideasonboard.org/uvc/ 來查看是否支持 UVC,這個網站是 USB Video Class Linux device driver 的主頁,裏面有 UVC 的詳細的介紹。根據前面的打印信息,根據本身的 ID 號, 這裏是搜索 USB 攝像頭的 VID 號:046d 和 PID 號:0825,主頁以下所示:ide

  經過攝像頭的 ID,能夠看到該攝像頭是否支持 UVC 和其餘信息。綠勾表明支持。函數

二、配置與相機型號匹配的USB host驅動

  目前Hilinux系統自帶了部分型號的usb攝像頭驅動,但並非支持全部市面上usb攝像頭,像Logitch c270這一款usb攝像頭就不支持,若是說linux kernel驅動中不支持,須要咱們從新配置該驅動,或者須要進行裁剪等操做,而這個過程須要咱們進行手動配置,配置過程以下:在內核目錄下,輸入以下命令(以emmc啓動爲例):網站

 1         待進入內核源代碼目錄後,執行如下操做
 2 
 3         cp arch/arm64/configs/hi3559av100_arm64_big_little_emmc_defconfig .config
 4 
 5 
 6         make ARCH=arm64 CROSS_COMPILE=aarch64-himix100-linux- menuconfig
 7 
 8 
 9         cp .config arch/arm64/configs/hi3559av100_arm64_big_little_emmc_defconfig
10 
11 
12         osdrv頂層目錄下執行:make BOOT_MEDIA=emmc AMP_TYPE=linux atf

 

執行 make ARCH=arm64 CROSS_COMPILE=aarch64-himix100-linux- menuconfig 後,彈出以下窗口:ui

以後進行驅動配置,打開UVC驅動等等。atom

  在配置好以後,彈出menuconfig窗口後,記得保存,保存完以後在手動修改usb驅動代碼:修改位置以下:

linux-xxx\drivers\media\usb\uvc\uvc_driver.c

  設備插入時調用probe將會按默認的id_table來加載驅動,也就是這個uvc_ids末尾說的Generic USB Video Class,具體以下所示:

1     /* Generic USB Video Class */
2     { USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, UVC_PC_PROTOCOL_UNDEFINED) },
3     { USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, UVC_PC_PROTOCOL_15) },
4     {}

  struct usb_device_id uvc_ids[]中模仿以前的加上本身的USB設備信息:

1     { .match_flags        = USB_DEVICE_ID_MATCH_DEVICE
2                 | USB_DEVICE_ID_MATCH_INT_INFO,
3       .idVendor        = 0x046d,
4       .idProduct        = 0x0825,
5       .bInterfaceClass    = USB_CLASS_VIDEO,
6       .bInterfaceSubClass    = 1,
7       .bInterfaceProtocol    = 0,
8       .driver_info        = UVC_QUIRK_RESTORE_CTRLS_ON_INIT },

  注意一下這個driver_info的賦值,能夠用來限制幀率,UVC_QUIRK_RESTORE_CTRLS_ON_INIT的值是0x400,這個設置好像是跟帶寬有關係,沒有深刻了解,若是設的太小,將致使沒法出圖。並且USB2.0的帶寬上限也只有480Mbit/s,連一個攝像頭都夠嗆了。修改完以後,還須要從新編譯內核。

  以後將攝像頭插在板載上,終端出現以下:

   也能夠經過命令ls /dev/video*查看video設備,以下所示, 到此驅動部分添加完成。

 1 /dev/video0                                                      

 三、UVC driver的研究

  上述終端顯示的信息是由uvc_probe()函數輸出,對應函數位置爲linux-xxx\drivers\media\usb\uvc\uvc_driver.c,函數具體內容以下:

  1 static int uvc_probe(struct usb_interface *intf,
  2              const struct usb_device_id *id)
  3 {
  4     struct usb_device *udev = interface_to_usbdev(intf);
  5     struct uvc_device *dev;
  6     int ret;
  7 
  8     if (id->idVendor && id->idProduct)
  9         uvc_trace(UVC_TRACE_PROBE, "Probing known UVC device %s "
 10                 "(%04x:%04x)\n", udev->devpath, id->idVendor,
 11                 id->idProduct);
 12     else
 13         uvc_trace(UVC_TRACE_PROBE, "Probing generic UVC device %s\n",
 14                 udev->devpath);
 15 
 16     /* Allocate memory for the device and initialize it. */
 17     if ((dev = kzalloc(sizeof *dev, GFP_KERNEL)) == NULL)
 18         return -ENOMEM;
 19 
 20     INIT_LIST_HEAD(&dev->entities);
 21     INIT_LIST_HEAD(&dev->chains);
 22     INIT_LIST_HEAD(&dev->streams);
 23     atomic_set(&dev->nstreams, 0);
 24     atomic_set(&dev->nmappings, 0);
 25     mutex_init(&dev->lock);
 26 
 27     dev->udev = usb_get_dev(udev);
 28     dev->intf = usb_get_intf(intf);
 29     dev->intfnum = intf->cur_altsetting->desc.bInterfaceNumber;
 30     dev->quirks = (uvc_quirks_param == -1)
 31             ? id->driver_info : uvc_quirks_param;
 32 
 33     if (udev->product != NULL)
 34         strlcpy(dev->name, udev->product, sizeof dev->name);
 35     else
 36         snprintf(dev->name, sizeof dev->name,
 37             "UVC Camera (%04x:%04x)",
 38             le16_to_cpu(udev->descriptor.idVendor),
 39             le16_to_cpu(udev->descriptor.idProduct));
 40 
 41     /* Parse the Video Class control descriptor. */
 42     if (uvc_parse_control(dev) < 0) {
 43         uvc_trace(UVC_TRACE_PROBE, "Unable to parse UVC "
 44             "descriptors.\n");
 45         goto error;
 46     }
 47 
 48     uvc_printk(KERN_INFO, "Found UVC %u.%02x device %s (%04x:%04x)\n",
 49         dev->uvc_version >> 8, dev->uvc_version & 0xff,
 50         udev->product ? udev->product : "<unnamed>",
 51         le16_to_cpu(udev->descriptor.idVendor),
 52         le16_to_cpu(udev->descriptor.idProduct));
 53 
 54     if (dev->quirks != id->driver_info) {
 55         uvc_printk(KERN_INFO, "Forcing device quirks to 0x%x by module "
 56             "parameter for testing purpose.\n", dev->quirks);
 57         uvc_printk(KERN_INFO, "Please report required quirks to the "
 58             "linux-uvc-devel mailing list.\n");
 59     }
 60 
 61     /* Initialize the media device and register the V4L2 device. */
 62 #ifdef CONFIG_MEDIA_CONTROLLER
 63     dev->mdev.dev = &intf->dev;
 64     strlcpy(dev->mdev.model, dev->name, sizeof(dev->mdev.model));
 65     if (udev->serial)
 66         strlcpy(dev->mdev.serial, udev->serial,
 67             sizeof(dev->mdev.serial));
 68     strcpy(dev->mdev.bus_info, udev->devpath);
 69     dev->mdev.hw_revision = le16_to_cpu(udev->descriptor.bcdDevice);
 70     dev->mdev.driver_version = LINUX_VERSION_CODE;
 71     media_device_init(&dev->mdev);
 72 
 73     dev->vdev.mdev = &dev->mdev;
 74 #endif
 75     if (v4l2_device_register(&intf->dev, &dev->vdev) < 0)
 76         goto error;
 77 
 78     /* Initialize controls. */
 79     if (uvc_ctrl_init_device(dev) < 0)
 80         goto error;
 81 
 82     /* Scan the device for video chains. */
 83     if (uvc_scan_device(dev) < 0)
 84         goto error;
 85 
 86     /* Register video device nodes. */
 87     if (uvc_register_chains(dev) < 0)
 88         goto error;
 89 
 90 #ifdef CONFIG_MEDIA_CONTROLLER
 91     /* Register the media device node */
 92     if (media_device_register(&dev->mdev) < 0)
 93         goto error;
 94 #endif
 95     /* Save our data pointer in the interface data. */
 96     usb_set_intfdata(intf, dev);
 97 
 98     /* Initialize the interrupt URB. */
 99     if ((ret = uvc_status_init(dev)) < 0) {
100         uvc_printk(KERN_INFO, "Unable to initialize the status "
101             "endpoint (%d), status interrupt will not be "
102             "supported.\n", ret);
103     }
104 
105     uvc_trace(UVC_TRACE_PROBE, "UVC device initialized.\n");
106     usb_enable_autosuspend(udev);
107     return 0;
108 
109 error:
110     uvc_unregister_video(dev);
111     return -ENODEV;
112 }
  對於初始kernel內核USB驅動應該是加載了一部分支持 UVC 攝像頭的驅動,在默認的uvc_driver 下,是沒有咱們攝像頭 046d 0825 ID 號的,須要咱們手動添加。當內核發現與 uvc_ids 匹配的 USB 攝像頭就會調用 uvc_probe 函數,一旦內核發現插入的 USB 攝像頭被匹配後,最終就會調用 uvc_probe 函數。
  USB攝像頭驅動其實就是一個字符設備驅動,重點關注v4l2_fops結構體,以下所示:
 1 static const struct file_operations v4l2_fops = {
 2     .owner = THIS_MODULE,
 3     .read = v4l2_read,
 4     .write = v4l2_write,
 5     .open = v4l2_open,
 6     .get_unmapped_area = v4l2_get_unmapped_area,
 7     .mmap = v4l2_mmap,
 8     .unlocked_ioctl = v4l2_ioctl,
 9 #ifdef CONFIG_COMPAT
10     .compat_ioctl = v4l2_compat_ioctl32,
11 #endif
12     .release = v4l2_release,
13     .poll = v4l2_poll,
14     .llseek = no_llseek,
15 };
  而USB攝像頭結合V4L2接口時,當應用程序調用 open 函數,例如: open("/dev/video0",....),首先就會調用到 v4l2 核內心的 open 函數,也就是 v4l2_open 函數(v4l2-dev.c)。而在v4l2-open 函數中調用了 vdev->fops->open(filp),至關於調用 uvc_v4l2_open()函數。這個函數的實如今\drivers\media\usb\uvc\uvc_v4l2.c 裏。一樣地,vdev->fops->unlocked_ioctl(filp,cmd,arg)(v4l2-dev.c);最後至關於調用 uvc_v4l2_ioctl()函數,它又調用 video_usercopy(file,cmd,arg,uvc_v4l2_do_ioctl);函數,video_usercopy()函數的做用從名字上能夠猜想,它是根據用戶空間傳遞過來的 cmd 命令,調用 uvc_v4l2_do_ioctl()函數來解析 arg 參數。
  ctrl 屬性的函數調用流程
  uvc_probe
    uvc_register_chains
      uvc_register_terms
        uvc_register_video
          video_register_device__video_register_device
            determine_valid_ioctls
  這些 ctrl 屬性就是 USB 攝像頭的各類屬性,好比亮度的調節,打開、關閉STREAM 等等操做,這些是 v4l2 核心最最複雜的工做了,沒有之一。
參考: \drivers\media\v4l2-core\v4l2-dev.c
相關文章
相關標籤/搜索