Android USB Camera : 调试记录 Posted 2023-01-04 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
MTK平台还需要额外打开otg配置
CONFIG_USB_MTK_OTG =y
CONFIG_USB_MTK_HDRC =y
CONFIG_USB_MTK_HDRC_HCD =y
插入摄像头,如果生成了/dev/video0设备节点,则证明uvc摄像头已经加载成功了。成功生成驱动节点后还需要为它添加权限
2.2 添加权限
在uevent.rc中加入
/dev/video0 0666 root root
在system_app.te中加入
allow system_app video_device:chr_file read write open getattr ;
2.3 Debug
如果没有出现/dev/video0节点,需要先判断是否枚举成功。在shell终端cat相关的节点查询
cat /sys/kernel/debug /usb/devices
如果该摄像头枚举成功,则能找到对应的设备信息
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
如果枚举成功则需要判断当前的usb摄像头是不是遵循uvc协议的摄像头。将usb摄像头插到PC上(ubuntu操作系统),通过”lsusb”命令查找是否有视频类接口信息
lsusb -d 18 ec:3399 -v | grep "14 Video"
如果该摄像头遵循UVC协议,则会输出以下类似信息
bFunctionClass 14 Video
bInterfaceClass 14 Video
bInterfaceClass 14 Video
bInterfaceClass 14 Video
其中18ec:3399是摄像头的vid和pid,而14 video代表uvc规范
2.4 几个比较有用的调试命令
打开/关闭linux uvc driver log
echo 0xffff > /sys/m odule/uvcvideo/parameters/trace
echo 0 > /sys/m odule/uvcvideo/parameters/trace
获取详细的usb设备描述符
lsusb -d 18 ec:3399 –v
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;
__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;