Android USB Camera : 调试记录

Posted bobuddy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android USB Camera : 调试记录相关的知识,希望对你有一定的参考价值。

1. 前言

前段时间调试了一个uvc摄像头,这里做下记录。硬件平台为mt6735,软件平台为android 5.0

2. 底层配置

UVC全称是usb video class,一种usb视频规范。所有遵循uvc协议的摄像头都不需要安装额外的驱动,只需要一个通用驱动即可。Linux内核已经集成了uvc驱动,代码路径是kernel-3.10/drivers/media/usb/uvc/

2.1 打开配置

Linux内核需要打开以下配置来支持uvc设备

CONFIG_MEDIA_SUPPORT=y
CONFIG_MEDIA_CAMERA_SUPPORT=y
CONFIG_VIDEO_DEV=y
CONFIG_VIDEO_V4L2=y
CONFIG_VIDEOBUF2_CORE=y
CONFIG_VIDEOBUF2_MEMOPS=y
CONFIG_VIDEOBUF2_VMALLOC=y
CONFIG_MEDIA_USB_SUPPORT=y
CONFIG_USB_VIDEO_CLASS=y
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

MTK平台还需要额外打开otg配置

CONFIG_USB_MTK_OTG=y 
CONFIG_USB_MTK_HDRC=y 
CONFIG_USB_MTK_HDRC_HCD=y
 
  • 1
  • 2
  • 3

插入摄像头,如果生成了/dev/video0设备节点,则证明uvc摄像头已经加载成功了。成功生成驱动节点后还需要为它添加权限

2.2 添加权限

在uevent.rc中加入

/dev/video0               0666   root       root
 
  • 1

在system_app.te中加入

allow system_app video_device:chr_file  read write open getattr ;
 
  • 1

2.3 Debug

如果没有出现/dev/video0节点,需要先判断是否枚举成功。在shell终端cat相关的节点查询

cat /sys/kernel/debug/usb/devices
 
  • 1

如果该摄像头枚举成功,则能找到对应的设备信息

T:  Bus=01 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#=1 Spd=480 MxCh=1
D:  Ver=2.00 Cls=00(>ifc) Sub=00 Prot=00 MxPS=64 #Cfgs=1
P:  Vendor=18EC ProdID=3399 Rev=0.00
S:  Manufacturer=ARKMICRO
S:  Product=USB PC CAMERA
 
  • 1
  • 2
  • 3
  • 4
  • 5

如果枚举成功则需要判断当前的usb摄像头是不是遵循uvc协议的摄像头。将usb摄像头插到PC上(ubuntu操作系统),通过”lsusb”命令查找是否有视频类接口信息

lsusb -d 18ec:3399 -v | grep "14 Video"
 
  • 1

如果该摄像头遵循UVC协议,则会输出以下类似信息

bFunctionClass 14 Video
bInterfaceClass 14 Video
bInterfaceClass 14 Video
bInterfaceClass 14 Video
 
  • 1
  • 2
  • 3
  • 4

其中18ec:3399是摄像头的vid和pid,而14 video代表uvc规范

2.4 几个比较有用的调试命令

打开/关闭linux uvc driver log

echo 0xffff > /sys/module/uvcvideo/parameters/trace //打开
echo 0 > /sys/module/uvcvideo/parameters/trace //关闭
 
  • 1
  • 2

获取详细的usb设备描述符

lsusb -d 18ec:3399 –v
 
  • 1

3. 上层应用

v4l2 - Video for Linux 2,是Linux内核中关于视频设备的内核驱动框架,为上层的访问底层的视频设备提供了统一的接口。同时是针对uvc免驱usb设备的编程框架,主要用于采集usb摄像头等。

MTK标准的Camera并没有采用v4l2框架,所以需要在jni层实现基本的v4l2视频采集流程。

3.1 操作流程

在v4l2编程中,一般使用ioctl函数来对设备进行操作:

extern int ioctl (int __fd, unsigned long int __request, …) __THROW;
 
  • 1

__fd:设备的ID,例如用open函数打开/dev/video0后返回的cameraFd; 
__request:具体的命令标志符。 
在进行V4L2开发中,一般会用到以下的命令标志符: 
VIDIOC_REQBUFS:分配内存 
VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址 
VIDIOC_QUERYCAP:查询驱动功能 
VIDIOC_ENUM_FMT:获取当前驱动支持的视频格式 
VIDIOC_S_FMT:设置当前驱动的视频格式 
VIDIOC_G_FMT:读取当前驱动的视频格式 
VIDIOC_TRY_FMT:验证当前驱动的视频格式 
VIDIOC_CROPCAP:查询驱动的修剪能力 
VIDIOC_S_CROP:设置视频信号的边框 
VIDIOC_G_CROP:读取视频信号的边框 
VIDIOC_QBUF:把数据放回缓存队列 
VIDIOC_DQBUF:把数据从缓存中读取出来 
VIDIOC_STREAMON:开始视频采集 
VIDIOC_STREAMOFF:结束视频采集 
VIDIOC_QUERYSTD:检查当前视频设备支持的标准,例如PAL或NTSC。 
这些IO调用,有些是必须的,有些是可选择的。

在网上有开源的应用simplewebcam,它已经实现了基本的v4l2视频采集流程。大概看下它是怎么做的

操作流程

3.2 具体代码实现

(1) 打开设备驱动节点

int opendevice(int i)

    struct stat st;

    sprintf(dev_name,"/dev/video%d",i);

    if (-1 == stat (dev_name, &st)) 
        LOGE("Cannot identify '%s': %d, %s", dev_name, errno, strerror (errno));
        return ERROR_LOCAL;
    

    if (!S_ISCHR (st.st_mode)) 
        LOGE("%s is no device", dev_name);
        return ERROR_LOCAL;
    

    fd = open (dev_name, O_RDWR);

    if (-1 == fd) 
        LOGE("Cannot open '%s': %d, %s", dev_name, errno, strerror (errno));
        return ERROR_LOCAL;
    
    return SUCCESS_LOCAL;

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

(2) 查询驱动功能

int initdevice(void) 

    struct v4l2_capability cap;
    struct v4l2_format fmt;
    unsigned int min;

    if (-1 == xioctl (fd, VIDIOC_QUERYCAP, &cap)) 
        if (EINVAL == errno) 
            LOGE("%s is no V4L2 device", dev_name);
            return ERROR_LOCAL;
         else 
            return errnoexit ("VIDIOC_QUERYCAP");
        
    

    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) 
        LOGE("%s is no video capture device", dev_name);
        return ERROR_LOCAL;
    

    if (!(cap.capabilities & V4L2_CAP_STREAMING)) 
        LOGE("%s does not support streaming i/o", dev_name);
        return ERROR_LOCAL;
    

    ......


 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

(3) 设置视频格式

int initdevice(void) 

    struct v4l2_capability cap;
    struct v4l2_format fmt;

    ......

    CLEAR (fmt);
    fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width       = IMG_WIDTH; 
    fmt.fmt.pix.height      = IMG_HEIGHT;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;

    if (-1 == xioctl (fd, VIDIOC_S_FMT, &fmt))
        return errnoexit ("VIDIOC_S_FMT");

    ......

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

(4) 申请帧缓存并映射到用户空间

int initmmap(void)

    struct v4l2_requestbuffers req;

    CLEAR (req);
    req.count               = 4;
    req.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory              = V4L2_MEMORY_MMAP;

    if (-1 == xioctl (fd, VIDIOC_REQBUFS, &req)) 
        if (EINVAL == errno) 
            LOGE("%s does not support memory mapping", dev_name);
            return ERROR_LOCAL;
         else 
            return errnoexit ("VIDIOC_REQBUFS");
        
    

    if (req.count < 2) 
        LOGE("Insufficient buffer memory on %s", dev_name);
        return ERROR_LOCAL;
    

    buffers = calloc (req.count, sizeof (*buffers));

    if (!buffers) 
        LOGE("Out of memory");
        return ERROR_LOCAL;
    

    for (n_buffers = 0; n_buffers < req.count; ++n_buffers) 
        struct v4l2_buffer buf;

        CLEAR (buf);
        buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory      = V4L2_MEMORY_MMAP;
        buf.index       = n_buffers;

        if (-1 == xioctl (fd, VIDIOC_QUERYBUF, &buf))
            return errnoexit ("VIDIOC_QUERYBUF");

        buffers[n_buffers].length = buf.length;
        buffers[n_buffers].start =
        mmap (NULL ,
            buf.length,
            PROT_READ | PROT_WRITE,
            MAP_SHARED,
            fd, buf.m.offset);

        if (MAP_FAILED == buffers[n_buffers].start)
            return errnoexit ("mmap");
    

    return SUCCESS_LOCAL;

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

(5) 将帧缓存加入缓存队列并启动视频采集

int startcapturing(void)

    unsigned int i;
    struct v4l2_buffer buf;
    enum v4l2_buf_type type;

    for (i = 0; i < n_buffers; ++i) 
        CLEAR (buf);
        buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory      = V4L2_MEMORY_MMAP;
        buf.index       = i;

        if (-1 == xioctl (fd, VIDIOC_QBUF, &buf))
            return errnoexit ("VIDIOC_QBUF");
    

    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (-1 == xioctl (fd, VIDIOC_STREAMON, &type))
        return errnoexit ("VIDIOC_STREAMON");

    return SUCCESS_LOCAL;

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

(6) 从缓存队列中取出一帧

int readframeonce(void)

    for (;;) 
        fd_set fds;
        struct timeval tv;
        int r;

        FD_ZERO (&fds);
        FD_SET (fd, &fds);

        tv.tv_sec = 2;
        tv.tv_usec = 0;

        r = select (fd + 1, &fds, NULL, NULL, &tv);

        if (-1 == r) 
            if (EINTR == errno)
                continue;

            return errnoexit ("select");
        

        if (0 == r) 
            LOGE("select timeout");
            return ERROR_LOCAL;

        

        if (readframe ()==1)
            break;

    

    return realImageSize;