最近有个需求,要在ARM Linux上实现USB Camera 拍照功能。
0. 背景知识:
首先要确认的是,Kernel 是否支持 USB Camera。因为 Linux 下,USB 协议除了电气协议和标准,还有很多 Class。 这些 Class 就是为了支持和定义某一类设备接口和交互数据格式。只要符合这类标准,则不同厂商的 USB 设备,不需要特定的 driver 就能在Linux下使用。
例如:USB Input class, 则使所有输入设备都可以直接使用。还有类似 Audio Class,Pring Class,Mass Storage Class,Video class 等。
其中 Video Class 就是我们常说的 UVC(USB Video Class). 只要 USB Camera 符合 UVC 标准。理论上在 2.6 Kernel Linux 就可以正常使用。
网络上有人谈到怎样判断是否 UVC Camera 设备:
#lsusb
Bus 001 Device 010: ID 046d:0825 Logitech, Inc.
#lsusb -d 046d:0825 -v | grep "14 Video"
如果出现:
bInterfaceClass 14 Video bInterfaceClass 14 Video bInterfaceClass 14 Video bInterfaceClass 14 Video bInterfaceClass 14 Video bInterfaceClass 14 Video bInterfaceClass 14 Video bInterfaceClass 14 Video bInterfaceClass 14 Video bInterfaceClass 14 Video bInterfaceClass 14 Video bInterfaceClass 14 Video bInterfaceClass 14 Video
则说明是支持UVC.
1. Kernel配置:
Device Drivers ---> <*> Multimedia support ---> <M> Video For Linux
Device Drivers ---> <*> Multimedia support ---> [*] Video capture adapters ---> [*] V4L USB devices ---> <M> USB Video Class (UVC)
--- V4L USB devices: 这里还有很多特定厂商的 driver. 可供选择。
分析:
"USB Video Class (UVC)":对应的 driver 是:uvcvideo.ko
"Video For Linux": 对应 driver 是:videodev.ko
安装 driver 顺序如下:
insmod v4l1_compat.ko insmod videodev.ko insmod uvcvideo.ko
driver 会创建一个或多个主设备号为81,次设备号:0-255的设备。
除了camer a会创建为:/dev/videoX 之外,还有 VBI 设备 -/dev/vbiX. Radio 设备 --/dev/radioX.
2. V4L2一些概念:
2.1:Video Input and Output:
video input and output是指 device 物理连接。
只有 video 和 VBI capture 拥有 input.
Radio 设备则没有 video input 和 output.
2.2: Video Standards:
Video Device支持一个或多个Video 标准。
3. 使用V4L2编程:
使用V4L2(Video for Linux 2) API的过程大致如下:
Opening the device Changing device properties, selecting a video and audio input, video standard, picture brightness a. o. Negotiating a data format Negotiating an input/output method The actual input/output loop Closing the device
3.1:打开设备:
fd = open ("/dev/video0", O_RDWR, 0); //以阻塞模式打开设想头
3.2: 查询设备能力:Querying Capabilities:
因为 V4L2 可以对多种设备编程,所以并不是所有 API 可以对所有设备编程,哪怕是同类型的设备,使用ioctl--VIDIOC_QUERYCAP 去询问支持什么功能。
struct v4l2_capability cap; rel = ioctl(fdUsbCam, VIDIOC_QUERYCAP, &cap); if(rel != 0) { perror("ioctl VIDIOC_QUERYCAP"); return -1; }
结构体如下:
struct v4l2_capability { __u8 driver[16]; __u8 card[32]; __u8 bus_info[32]; __u32 version; __u32 capabilities; __u32 reserved[4]; };
这里面最重要的是:capabilities:
头文件 linux/videodev2.h 和 kernel 头文件 linux/videodev2.h 中都有描述:
#define V4L2_CAP_VIDEO_CAPTURE 0x00000001 #define V4L2_CAP_VIDEO_OUTPUT 0x00000002 #define V4L2_CAP_VIDEO_OVERLAY 0x00000004 #define V4L2_CAP_VBI_CAPTURE 0x00000010 #define V4L2_CAP_VBI_OUTPUT 0x00000020 #define V4L2_CAP_SLICED_VBI_CAPTURE 0x00000040 #define V4L2_CAP_SLICED_VBI_OUTPUT 0x00000080 #define V4L2_CAP_RDS_CAPTURE 0x00000100 #define V4L2_CAP_VIDEO_OUTPUT_OVERLAY 0x00000200 #define V4L2_CAP_HW_FREQ_SEEK 0x00000400 #define V4L2_CAP_RDS_OUTPUT 0x00000800 #define V4L2_CAP_TUNER 0x00010000 #define V4L2_CAP_AUDIO 0x00020000 #define V4L2_CAP_RADIO 0x00040000 #define V4L2_CAP_MODULATOR 0x00080000 #define V4L2_CAP_READWRITE 0x01000000 #define V4L2_CAP_ASYNCIO 0x02000000 #define V4L2_CAP_STREAMING 0x04000000
这里要说到 VBI,Vertical Blanking Interval 的缩写。 电视信号包括一部分非可视信号,它不传送可视信息,因此被称为ⅦI(垂直消隐期间)。VBI 可以用于传送其他信息,通常是一种专用字幕信号, 这和 Blog 重显率中所说暗合。
在这里,V4L2_CAP_VIDEO_CAPTURE 说明设备是个图像采集设备,V4L2_CAP_STREAMING 说明是个 Streaming 设备。 通常,摄像头都支持以上两个能力。
3.3:查询当前捕获格式:
memset(&fmt, 0, sizeof(struct v4l2_format)); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(fdUsbCam, VIDIOC_G_FMT, &fmt) < 0) { printf("get format failed\n"); return -1; }
注意,此处,fmt 是个 in/out 参数。
参见 Kernel 代码 v4l2_ioctl.c 中。此 ioctl,它会首先判断 fmt.type.
type 类型和含义如下:
V4L2_BUF_TYPE_VIDEO_CAPTURE :vid-cap V4L2_BUF_TYPE_VIDEO_OVERLAY :vid-overlay V4L2_BUF_TYPE_VIDEO_OUTPUT : vid-out V4L2_BUF_TYPE_VBI_CAPTURE : vbi-cap V4L2_BUF_TYPE_VBI_OUTPUT : vbi-out V4L2_BUF_TYPE_SLICED_VBI_CAPTURE : sliced-vbi-cap V4L2_BUF_TYPE_SLICED_VBI_OUTPUT : sliced-vbi-out V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY : vid-out-overlay
咱们是使用 Video Cam 的。所以用 V4L2_BUF_TYPE_VIDEO_CAPTURE
struct v4l2_format { enum v4l2_buf_type type; union { struct v4l2_pix_format pix; struct v4l2_window win; struct v4l2_vbi_format vbi; struct v4l2_sliced_vbi_format sliced; __u8 raw_data[200]; } fmt; };
我们得到的信息在 v4l2_pix_format 中。
你可以看到,宽,高,像素格式。
其中像素格式包括:
#define V4L2_PIX_FMT_RGB332 v4l2_fourcc('R','G','B','1') #define V4L2_PIX_FMT_RGB555 v4l2_fourcc('R','G','B','O') #define V4L2_PIX_FMT_RGB565 v4l2_fourcc('R','G','B','P') #define V4L2_PIX_FMT_RGB555X v4l2_fourcc('R','G','B','Q') #define V4L2_PIX_FMT_RGB565X v4l2_fourcc('R','G','B','R') #define V4L2_PIX_FMT_BGR24 v4l2_fourcc('B','G','R','3') #define V4L2_PIX_FMT_RGB24 v4l2_fourcc('R','G','B','3') #define V4L2_PIX_FMT_BGR32 v4l2_fourcc('B','G','R','4') #define V4L2_PIX_FMT_RGB32 v4l2_fourcc('R','G','B','4') #define V4L2_PIX_FMT_GREY v4l2_fourcc('G','R','E','Y') #define V4L2_PIX_FMT_YVU410 v4l2_fourcc('Y','V','U','9') #define V4L2_PIX_FMT_YVU420 v4l2_fourcc('Y','V','1','2') #define V4L2_PIX_FMT_YUYV v4l2_fourcc('Y','U','Y','V') #define V4L2_PIX_FMT_UYVY v4l2_fourcc('U','Y','V','Y') #define V4L2_PIX_FMT_YUV422P v4l2_fourcc('4','2','2','P') #define V4L2_PIX_FMT_YUV411P v4l2_fourcc('4','1','1','P') #define V4L2_PIX_FMT_Y41P v4l2_fourcc('Y','4','1','P') #define V4L2_PIX_FMT_NV12 v4l2_fourcc('N','V','1','2') #define V4L2_PIX_FMT_NV21 v4l2_fourcc('N','V','2','1') #define V4L2_PIX_FMT_YUV410 v4l2_fourcc('Y','U','V','9') #define V4L2_PIX_FMT_YUV420 v4l2_fourcc('Y','U','1','2') #define V4L2_PIX_FMT_YYUV v4l2_fourcc('Y','Y','U','V') #define V4L2_PIX_FMT_HI240 v4l2_fourcc('H','I','2','4') #define V4L2_PIX_FMT_HM12 v4l2_fourcc('H','M','1','2') #define V4L2_PIX_FMT_SBGGR8 v4l2_fourcc('B','A','8','1') #define V4L2_PIX_FMT_MJPEG v4l2_fourcc('M','J','P','G') #define V4L2_PIX_FMT_JPEG v4l2_fourcc('J','P','E','G') #define V4L2_PIX_FMT_DV v4l2_fourcc('d','v','s','d') #define V4L2_PIX_FMT_MPEG v4l2_fourcc('M','P','E','G') #define V4L2_PIX_FMT_WNVA v4l2_fourcc('W','N','V','A') #define V4L2_PIX_FMT_SN9C10X v4l2_fourcc('S','9','1','0') #define V4L2_PIX_FMT_PWC1 v4l2_fourcc('P','W','C','1') #define V4L2_PIX_FMT_PWC2 v4l2_fourcc('P','W','C','2') #define V4L2_PIX_FMT_ET61X251 v4l2_fourcc('E','6','2','5')
请注意,此时取到的宽,高,像素格式均正确。但不知为何,bytesperline 却为 0。
3.4:设置当前捕获格式
fmt.fmt.pix.width = 640; fmt.fmt.pix.height = 480; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; rel = ioctl(fdUsbCam, VIDIOC_S_FMT, &fmt); if (rel < 0) { printf("\nSet format failed\n"); return -1; }
此时,再取当前捕获格式,则一切正常。包括 bytesperline
3.5:读取 Stream 设置。
struct v4l2_streamparm *setfps; setfps = (struct v4l2_streamparm *) calloc(1, sizeof(struct v4l2_streamparm)); memset(setfps, 0, sizeof(struct v4l2_streamparm)); setfps->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; rel = ioctl(fdUsbCam, VIDIOC_G_PARM, setfps); if(rel == 0) { printf("\n Frame rate: %u/%u\n", setfps->parm.capture.timeperframe.denominator, setfps->parm.capture.timeperframe.numerator ); } else { perror("Unable to read out current frame rate"); return -1; }
注意: ioctl(fdUsbCam, VIDIOC_G_PARM, setfps); 参数 3 也是 i/o 参数。必须要首先其 type.
struct v4l2_streamparm { enum v4l2_buf_type type; union { struct v4l2_captureparm capture; struct v4l2_outputparm output; __u8 raw_data[200]; } parm; };
type 字段描述的是在涉及的操作的类型。对于视频捕获设备,应该为 V4L2_BUF_TYPE_VIDEO_CAPTURE。对于输出设备应该为V4L2_BUF_TYPE_VIDEO_OUTPUT。它的值也可以是 V4L2_BUF_TYPE_PRIVATE,在这种情况下,raw_data字段用来传递一些私有的,不可移植的,甚至是不鼓励的数据给内核 。
enum v4l2_buf_type { V4L2_BUF_TYPE_VIDEO_CAPTURE = 1, V4L2_BUF_TYPE_VIDEO_OUTPUT = 2, V4L2_BUF_TYPE_VIDEO_OVERLAY = 3, V4L2_BUF_TYPE_VBI_CAPTURE = 4, V4L2_BUF_TYPE_VBI_OUTPUT = 5, V4L2_BUF_TYPE_SLICED_VBI_CAPTURE = 6, V4L2_BUF_TYPE_SLICED_VBI_OUTPUT = 7, V4L2_BUF_TYPE_PRIVATE = 0x80, };
咱们当然选用V4L2_BUF_TYPE_VIDEO_CAPTURE
对于捕获设备而言,parm.capture 字段是要关注的内容,这个结构体如下:
struct v4l2_captureparm { __u32 capability; __u32 capturemode; struct v4l2_fract timeperframe; __u32 extendedmode; __u32 readbuffers; __u32 reserved[4]; };
timeperframe 字段用于指定想要使用的帧频率,它又是一个结构体:
struct v4l2_fract { __u32 numerator; __u32 denominator; };
numerator 和 denominator 所描述的系数给出的是成功的帧之间的时间间隔。
numerator 分子, denominator 分母。主要表达每次帧之间时间间隔。 numerator/denominator 秒一帧。
3.6:设置 Stream 参数。(主要是采集帧数)
setfps->parm.capture.timeperframe.numerator = 1; setfps->parm.capture.timeperframe.denominator = 60; rel = ioctl(fdUsbCam, VIDIOC_S_PARM, setfps); if (rel != 0) { printf("\nUnable to Set FPS"); return -1; }
当然,setfps 的其它项目,都是之前使用 VIDIOC_G_PARM 取得的。
3.7:创建一组缓冲区(buf)
struct v4l2_requestbuffers rb; memset(&rb, 0, sizeof(struct v4l2_requestbuffers)); rb.count = 3; rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; rb.memory = V4L2_MEMORY_MMAP; rel = ioctl(fdUsbCam, VIDIOC_REQBUFS, &rb); if (rel < 0) { printf("Unable to allocate buffers: %d.\n", errno); return -1; }
其中参数 rb 为:struct v4l2_requestbuffers:
struct v4l2_requestbuffers { __u32 count; enum v4l2_buf_type type; enum v4l2_memory memory; __u32 reserved[2]; };
type 字段描述的是完成的 I/O 操作的类型。通常它的值要么是视频获得设备的 V4L2_BUF_TYPE_VIDEO_CAPTURE,要么是输出设备的 V4L2_BUF_TYPE_VIDEO_OUTPUT
struct v4l2_memory: enum v4l2_memory { V4L2_MEMORY_MMAP = 1, V4L2_MEMORY_USERPTR = 2, V4L2_MEMORY_OVERLAY = 3, };
想要使用内存映谢的缓冲区,它将会把 memory 字段置为 V4L2_MEMORY_MMAP,count 置为它想要使用的缓冲区的数目。
顺便看看 USB TO Serail:
Device Drivers --->[*] USB support ---> USB Serial Converter support ---> USB Prolific 2303 Single Port Serial Driver
USB Prolific 2303 Single Port Serial Driver 是指出支持 pl2303 芯片的 USB 2 serial.
pl2303.ko
USB Serial Converter support 是基础 driver. 对应 usbserial.ko
注1:ioctl 中常用的 cmd.
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。 VIDIOC_G_PARM:得到 Stream 信息。如帧数等。 VIDIOC_S_PARM: 设置 Stream 信息。如帧数等。
注2:
如何判断某 ioctl cmd 所用参数类型:
例如:
ioctl-cmd: VIDIOC_QUERYCAP.
它的返回参数类型 ioctl(fd, cmd, 参数)。
首先想到的是从 kernel Source v4l2_ioctl.c 中看。但这比较麻烦,又个简单办法:可以在 video2dev.h 中看到:
#define VIDIOC_QUERYCAP _IOR ('V', 0, struct v4l2_capability)
即使用 cmd 为 VIDIOC_QUERYCAP 时,参数为 struct v4l2_capability