网站推广做哪个比较好,建设网站人员,做网站团队的人员安排,合肥网站定制作者简介#xff1a; 一个平凡而乐于分享的小比特#xff0c;中南民族大学通信工程专业研究生在读#xff0c;研究方向无线联邦学习 擅长领域#xff1a;驱动开发#xff0c;嵌入式软件开发#xff0c;BSP开发 作者主页#xff1a;一个平凡而乐于分享的小比特的个人主页…作者简介 一个平凡而乐于分享的小比特中南民族大学通信工程专业研究生在读研究方向无线联邦学习 擅长领域驱动开发嵌入式软件开发BSP开发 作者主页一个平凡而乐于分享的小比特的个人主页 文章收录专栏IMX8MP,本专栏记录imx8mp开发板学习开发过程中的问题及解决方法记录 欢迎大家点赞 收藏 ⭐ 加关注哦
usb摄像头应用编程
最近在京东上买了一个usb摄像头。想研究研究应用到项目开发中。此文档记录我学习的过程。
1.V4L2简介
1.1 什么是v4l2
V4L2即 Video for linux two 是 Linux 内核中视频类设备的一套驱动框架为视频类设备驱动开发和应用层提供了一套统一的接口规范使用 V4L2 设备驱动框架注册的设备会在 Linux 系统/dev/目录下生成对应的设备节点文件设备节点的名称通常为 videoX(X为0、1、2…
V4L2是Linux视频处理模块的最新标准代码包括对视频输入设备的处理如高频(即、电视信号输入端子)或摄像头还包括视频处理输出装置。一般来说最常见的是使用V4L2来处理相机数据采集的问题。我们通常使用的相机实际上是一个图像传感器将捕捉到的光线通过视频芯片处理后编码成JPG/MJPG或YUV格式输出。我们可以很容易地通过V4L2与第一台摄像机设备“通信”如设置或获取它们的工作参数。
1.2 Video设备的V4L2框架
基于Video设备的V4L2框架 Linux系统中视频设备主要包括以下四个部分
1.字符设备驱动程序核心V4L2本身就是一个字符设备具有字符设备所有的特性暴露接口给用户空间用户空间可以通过Ioctl系统调用控制设备在应用层我们可以在 /dev 目录发现 videoxx 类似的设备节点。
2.V4L2驱动核心主要是构建一个内核中标准视频设备驱动的框架为视频操作提供统一的接口函数
3.平台V4L2设备驱动在V4L2框架下根据平台自身的特性实现与平台相关V4L2驱动部分包括注册video_device和v4l2_dev。
4.具体的sensor驱动主要上电、提供工作时钟、视频图像裁剪、流IO开启等实现各种设备控制方法供上层调用并注册v4l2_subdev。
1.3 V4L2支持的设备类型
4L2支持的设备十分广泛但是其中只有很少一部分在本质上是真正的视频设备
video capture interface视频采集接口从camera上获取视频数据视频捕获是V4L2的基本应用
video output interface视频输出接口允许应用程序驱动外设提供视频图像
video overlay interface视频覆盖接口是捕获接口的一个变体其工作是便于捕获设备直接显示到显示器无需经过CPUAndroid拍照应用在进行预览时可能就是这种模式。
VBI interfaces基于电视场消隐实现远程传送文字的技术与设备
radio interface无线电接口从AM和FM调谐器设备访问音频流。
Codec Interface编解码接口对视频数据流执行转换。
通常情况下V4L2的主设备号是81次设备号为0255这些次设备号里又分为多类设备视频设备、Radio收音机设备、Teletext on VBI等。因此V4L2设备对应的文件节点有/dev/videoX、/dev/vbiX、/dev/radioX。对于Radio设备即用于收发声音。但要提醒注意的是对于声音的采集与处理在我们的Android手持设备中常会有个Mic设备它则是属于ALSA子系统的。我们主要讨论的是Video设备。
2.V4L2-utils、ffplay工具测试usb摄像头
详细操作讲解请参考摄像头应用测试
输入密令录制视频
v4l2-ctl --device/dev/video0 --set-fmt-videowidth640,height480,pixelformatYUYV --stream-mmap --stream-tovideo100.yuv --stream-count100将录制得到的video100.yuv拷贝到虚拟机上利用ffplay工具播放
ffplay -video_size 640x480 -pixel_format yuyv422 -framerate 10 -i video100.yuv usb摄像头视频采集 说明USB摄像头工作是正常的
3. V4L2接口编程
3.1 v4l2接口编程流程
首先是打开摄像头设备查询设备的属性或功能设置设备的参数譬如像素格式、 帧大小、 帧率申请帧缓冲、 内存映射帧缓冲入队开启视频采集帧缓冲出队、对采集的数据进行处理处理完后再次将帧缓冲入队往复结束采集。 可以看到 V4L2 的应用编程的内容并不复杂其实就是 openioctlmmapmunmapclose 这几个调用其中只有 ioctl 的使用稍微复杂。
VIDIOC_QUERYCAP // 查询设备属性和功能
VIDIOC_ENUM_FMT // 列举数据帧格式
VIDIOC_G_FMT // 获取数据帧格式
VIDIOC_S_FMT // 设置数据帧格式
VIDIOC_REQBUFS // 申请帧缓冲区
VIDIOC_QUERYBUF // 查询帧缓冲区
VIDIOC_STREAMON // 开始视频采集
VIDIOC_DQBUF // 帧缓冲出队
VIDIOC_QBUF // 帧缓冲入队
VIDIOC_STREAMOFF // 停止视频采集3.2 v4l2视频采集原理
通过 V4L2 采集图像之前我们需要做的很多但是很重要的一步是分配帧缓冲区并将分配的帧缓冲区从内核空间映射到用户空间然后将申请到的帧缓冲区在视频采集输入队列排队剩下的就是等待视频数据的到来。
其具体过程为当启动视频采集后驱动程序开始采集一帧图像数据会把采集的图像数据放入视频采集输入队列的第一个帧缓冲区一阵图像数据就算采集完成了。第一个帧缓冲区存满一帧图像数据后驱动程序将该帧缓冲区移至视频采集输出队列等待应用程序从输出队列取出应用程序取出图像数据可以对图像数据进行处理或存储操作然后将帧该缓冲区放入视频采集输入队列的尾部。驱动程序接下来采集下一帧数据放入第二个缓冲区同样的帧缓冲区存满一帧数据后驱动程序将该缓冲区移至视频采集输出队列应用程序将该帧缓冲区的图像数据取出后又将该帧缓冲区放入视频输入队列尾部这样循环往复就实现了循环采集。 不同的 USB 摄像头所支持的种类不同所支持的种类数量也不相同以本章的 USB 摄像头为例可以查看摄像头支持的视频帧尺寸。
tjfubuntu16:~$ lsusb -vVideoStreaming Interface Descriptor:bLength 15bDescriptorType 36bDescriptorSubtype 1 (INPUT_HEADER)bNumFormats 2wTotalLength 365bEndPointAddress 130bmInfo 0bTerminalLink 3bStillCaptureMethod 1bTriggerSupport 0bTriggerUsage 0bControlSize 1bmaControls( 0) 11bmaControls( 1) 11VideoStreaming Interface Descriptor:bLength 11bDescriptorType 36bDescriptorSubtype 6 (FORMAT_MJPEG)bFormatIndex 1bNumFrameDescriptors 5bFlags 1Fixed-size samples: YesbDefaultFrameIndex 1bAspectRatioX 0bAspectRatioY 0bmInterlaceFlags 0x00Interlaced stream or variable: NoFields per frame: 1 fieldsField 1 first: NoField pattern: Field 1 onlybCopyProtect 0VideoStreaming Interface Descriptor:bLength 30bDescriptorType 36bDescriptorSubtype 7 (FRAME_MJPEG)bFrameIndex 1bmCapabilities 0x01Still image supportedwWidth 1920wHeight 1080dwMinBitRate 995328000dwMaxBitRate 995328000dwMaxVideoFrameBufferSize 4147200dwDefaultFrameInterval 333333bFrameIntervalType 1dwFrameInterval( 0) 333333VideoStreaming Interface Descriptor:bLength 30bDescriptorType 36bDescriptorSubtype 7 (FRAME_MJPEG)bFrameIndex 2bmCapabilities 0x01Still image supportedwWidth 1280wHeight 720dwMinBitRate 442368000dwMaxBitRate 442368000dwMaxVideoFrameBufferSize 1843200dwDefaultFrameInterval 333333bFrameIntervalType 1dwFrameInterval( 0) 333333VideoStreaming Interface Descriptor:bLength 30bDescriptorType 36bDescriptorSubtype 7 (FRAME_MJPEG)bFrameIndex 3bmCapabilities 0x01Still image supportedwWidth 800wHeight 600dwMinBitRate 230400000dwMaxBitRate 230400000dwMaxVideoFrameBufferSize 960000dwDefaultFrameInterval 333333bFrameIntervalType 1dwFrameInterval( 0) 333333VideoStreaming Interface Descriptor:bLength 30bDescriptorType 36bDescriptorSubtype 7 (FRAME_MJPEG)bFrameIndex 4bmCapabilities 0x01Still image supportedwWidth 640wHeight 480dwMinBitRate 147456000dwMaxBitRate 147456000dwMaxVideoFrameBufferSize 614400dwDefaultFrameInterval 333333bFrameIntervalType 1dwFrameInterval( 0) 333333VideoStreaming Interface Descriptor:bLength 30bDescriptorType 36bDescriptorSubtype 7 (FRAME_MJPEG)bFrameIndex 5bmCapabilities 0x01Still image supportedwWidth 640wHeight 360dwMinBitRate 110592000dwMaxBitRate 110592000dwMaxVideoFrameBufferSize 460800dwDefaultFrameInterval 333333bFrameIntervalType 1dwFrameInterval( 0) 333333VideoStreaming Interface Descriptor:bLength 6bDescriptorType 36bDescriptorSubtype 13 (COLORFORMAT)bColorPrimaries 1 (BT.709,sRGB)bTransferCharacteristics 1 (BT.709)bMatrixCoefficients 4 (SMPTE 170M (BT.601))VideoStreaming Interface Descriptor:bLength 27bDescriptorType 36bDescriptorSubtype 4 (FORMAT_UNCOMPRESSED)bFormatIndex 2bNumFrameDescriptors 5guidFormat {59555932-0000-1000-8000-00aa00389b71}bBitsPerPixel 16bDefaultFrameIndex 1bAspectRatioX 0bAspectRatioY 0bmInterlaceFlags 0x00Interlaced stream or variable: NoFields per frame: 2 fieldsField 1 first: NoField pattern: Field 1 onlybCopyProtect 0VideoStreaming Interface Descriptor:bLength 30bDescriptorType 36bDescriptorSubtype 5 (FRAME_UNCOMPRESSED)bFrameIndex 1bmCapabilities 0x01Still image supportedwWidth 1920wHeight 1080dwMinBitRate 165888000dwMaxBitRate 165888000dwMaxVideoFrameBufferSize 4147200dwDefaultFrameInterval 2000000bFrameIntervalType 1dwFrameInterval( 0) 2000000VideoStreaming Interface Descriptor:bLength 30bDescriptorType 36bDescriptorSubtype 5 (FRAME_UNCOMPRESSED)bFrameIndex 2bmCapabilities 0x01Still image supportedwWidth 1280wHeight 720dwMinBitRate 147456000dwMaxBitRate 147456000dwMaxVideoFrameBufferSize 1843200dwDefaultFrameInterval 1000000bFrameIntervalType 1dwFrameInterval( 0) 1000000VideoStreaming Interface Descriptor:bLength 30bDescriptorType 36bDescriptorSubtype 5 (FRAME_UNCOMPRESSED)bFrameIndex 3bmCapabilities 0x01Still image supportedwWidth 800wHeight 600dwMinBitRate 153600000dwMaxBitRate 153600000dwMaxVideoFrameBufferSize 960000dwDefaultFrameInterval 500000bFrameIntervalType 1dwFrameInterval( 0) 500000VideoStreaming Interface Descriptor:bLength 30bDescriptorType 36bDescriptorSubtype 5 (FRAME_UNCOMPRESSED)bFrameIndex 4bmCapabilities 0x01Still image supportedwWidth 640wHeight 480dwMinBitRate 147456000dwMaxBitRate 147456000dwMaxVideoFrameBufferSize 614400dwDefaultFrameInterval 333333bFrameIntervalType 1dwFrameInterval( 0) 333333VideoStreaming Interface Descriptor:bLength 30bDescriptorType 36bDescriptorSubtype 5 (FRAME_UNCOMPRESSED)bFrameIndex 5bmCapabilities 0x01Still image supportedwWidth 640wHeight 360dwMinBitRate 110592000dwMaxBitRate 110592000dwMaxVideoFrameBufferSize 460800dwDefaultFrameInterval 333333bFrameIntervalType 1dwFrameInterval( 0) 333333VideoStreaming Interface Descriptor:bLength 6bDescriptorType 36bDescriptorSubtype 13 (COLORFORMAT)bColorPrimaries 1 (BT.709,sRGB)bTransferCharacteristics 1 (BT.709)bMatrixCoefficients 4 (SMPTE 170M (BT.601))
可以看到打印的信息很多我们需要找到 VideoStreaming Interface Descriptor 就是用于 USB 数据传输的接口然后查看 wWidth 和 wHeight 这两个属性就可以知道摄像头所支持的视频帧的尺寸本章选用的是 640 * 480 的视频帧大小。
3.3 v4l2设备操作流程
V4L2支持多种接口capture(捕获)、output(输出)、overlay(预览)等等
这里讲解如何使用capture功能下面讲解操作流程
step1打开设备
在Linux中视频设备节点为/dev/videox使用open函数将其打开 //1.打开设备int fd open(/dev/video0,O_RDWR);if(fd 0){perror(打开设备失败);return -1;}step 2获取摄像头支持的格式
有的摄像头支持多种像素格式有的摄像头只支持一种像素格式在设置格式之前要先枚举出所有的格式看一看是否支持要设置的格式然后再进一步设置
struct v4l2_fmtdesc v4fmt;
v4fmt.type V4L2_BUF_TYPE_VIDEO_CAPTURE;//摄像头采集int i 0;
while(1)
{v4fmt.index i;int ret ioctl(fd,VIDIOC_ENUM_FMT,v4fmt);if(ret 0){perror(获取失败);break;} printf(index%d\n,v4fmt.index);printf(flags%d\n,v4fmt.flags);printf(description%s\n,v4fmt.description);unsigned char *p (unsigned char *)v4fmt.pixelformat;printf(pixelformat%c%c%c%c\n,p[0],p[1],p[2],p[3]);printf(reserved%d\n,v4fmt.reserved[0]);
}step 3设置/获取采集格式 struct v4l2_format vfmt;vfmt.type V4L2_BUF_TYPE_VIDEO_CAPTURE;//摄像头采集vfmt.fmt.pix.width 640;//设置宽vfmt.fmt.pix.height 480;//设置高vfmt.fmt.pix.pixelformat V4L2_PIX_FMT_MJPEG;//设置视频采集格式int ret ioctl(fd, VIDIOC_S_FMT, vfmt);if(ret 0){perror(设置格式失败);close(fd);return -1;}memset(vfmt, 0, sizeof(vfmt));vfmt.type V4L2_BUF_TYPE_VIDEO_CAPTURE; // 确保type字段正确设置ret ioctl(fd, VIDIOC_G_FMT, vfmt);if(ret 0){perror(获取格式失败);close(fd);return -1;}if(vfmt.fmt.pix.width 640 vfmt.fmt.pix.height 480 vfmt.fmt.pix.pixelformat V4L2_PIX_FMT_MJPEG){printf(设置成功\n);}else{printf(设置失败\n);close(fd);return -1;}step 4申请内核空间
v4l2设备读取数据的方式有两种一种是read方式一种是streaming方式具体需要看v4l2_capability是支持V4L2_CAP_READWRITE还是V4L2_CAP_STREAMING
看一看v4l2_capability
struct v4l2_capability {__u8 driver[16]; /* i.e. bttv */__u8 card[32]; /* i.e. Hauppauge WinTV */__u8 bus_info[32]; /* PCI: pci_name(pci_dev) */__u32 version; /* should use KERNEL_VERSION() */__u32 capabilities; /* Device capabilities */__u32 reserved[4];
};其中最重要的是capabilities字段这个字段标记着v4l2设备的功能capabilities有以下部分标记位
ID描述符V4L2_CAP_VIDEO_CAPTURE设备支持捕获功能V4L2_CAP_VIDEO_OUTPUT设备支持输出功能V4L2_CAP_VIDEO_OVERLAY设备支持预览功能V4L2_CAP_STREAMING设备支持流读写V4L2_CAP_READWRITE设备支持read、write方式读写
read方式很容易理解就是通过read函数读取那么streaming是什么意思呢
streaming就是在内核空间中维护一个缓存队列然后将内存映射到用户空间应用读取图像数据就是一个不断地出队列和入队列的过程如下图所示 struct v4l2_requestbuffers reqbuffer;reqbuffer.type V4L2_BUF_TYPE_VIDEO_CAPTURE;reqbuffer.count 4;//申请4个缓冲区reqbuffer.memory V4L2_MEMORY_MMAP;//映射方式ret ioctl(fd, VIDIOC_REQBUFS, reqbuffer);if(ret 0){perror(申请队列空间失败);close(fd);return -1;}step 5映射内存
为什么要映射缓存
因为如果使用read方式读取的话图像数据是从内核空间拷贝会应用空间而一副图像的数据一般来讲是比较大的所以效率会比较低。而如果使用映射的方式讲内核空间的内存应用到用户空间那么用户空间读取数据就想在操作内存一样不需要经过内核空间到用户空间的拷贝大大提高效率 unsigned char *mptr[4];//保存映射后用户空间的首地址unsigned int size[4]; struct v4l2_buffer mapbuffer;//初始化typeindexmapbuffer.type V4L2_BUF_TYPE_VIDEO_CAPTURE;for(int i0; i4; i){mapbuffer.index i;ret ioctl(fd, VIDIOC_QUERYBUF, mapbuffer);//从内核空间中查询一个空间做映射if(ret 0){perror(查询内核空间队列失败);close(fd);return -1;}mptr[i] (unsigned char *)mmap(NULL, mapbuffer.length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, mapbuffer.m.offset);size[i] mapbuffer.length;//通知使用完毕--‘放回去’ret ioctl(fd, VIDIOC_QBUF, mapbuffer);if(ret 0){perror(放回失败);close(fd);return -1;} }step 6开始采集 int type V4L2_BUF_TYPE_VIDEO_CAPTURE;ret ioctl(fd, VIDIOC_STREAMON, type);if(ret 0){perror(开启失败);}step 7从队列中提取一帧数据 struct v4l2_buffer readbuffer;readbuffer.type V4L2_BUF_TYPE_VIDEO_CAPTURE;ret ioctl(fd, VIDIOC_DQBUF, readbuffer);if(ret 0){perror(提取数据失败);}FILE *filefopen(my.jpg,w);fwrite(mptr[readbuffer.index],readbuffer.length,1,file);fclose(file);//通知内核已经使用完毕ret ioctl(fd, VIDIOC_QBUF, readbuffer);if(ret 0){perror(放回队列失败);return -1;}step 8停止采集 ret ioctl(fd, VIDIOC_STREAMOFF, type);if(ret 0){ perror(停止采集失败);return -1;} step 9释放映射 for(int i0; i4; i){munmap(mptr[i],size[i]);}step 10关闭设备 close(fd);完整代码
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include stdlib.h
#include unistd.h
#include sys/ioctl.h
#include linux/videodev2.h
#include string.h
#include sys/mman.hint main(void)
{//1.打开设备int fd open(/dev/video0,O_RDWR);if(fd 0){perror(打开设备失败);return -1;}//2.获取摄像头支持的格式struct v4l2_fmtdesc v4fmt;v4fmt.type V4L2_BUF_TYPE_VIDEO_CAPTURE;//摄像头采集int i 0;while(1){v4fmt.index i;int ret ioctl(fd,VIDIOC_ENUM_FMT,v4fmt);if(ret 0){perror(获取失败);break;} printf(index%d\n,v4fmt.index);printf(flags%d\n,v4fmt.flags);printf(description%s\n,v4fmt.description);unsigned char *p (unsigned char *)v4fmt.pixelformat;printf(pixelformat%c%c%c%c\n,p[0],p[1],p[2],p[3]);printf(reserved%d\n,v4fmt.reserved[0]); }//3设置采集格式struct v4l2_format vfmt;vfmt.type V4L2_BUF_TYPE_VIDEO_CAPTURE;//摄像头采集vfmt.fmt.pix.width 640;//设置宽vfmt.fmt.pix.height 480;//设置高vfmt.fmt.pix.pixelformat V4L2_PIX_FMT_MJPEG;//设置视频采集格式int ret ioctl(fd, VIDIOC_S_FMT, vfmt);if(ret 0){perror(设置格式失败);close(fd);return -1;}memset(vfmt, 0, sizeof(vfmt));vfmt.type V4L2_BUF_TYPE_VIDEO_CAPTURE; // 确保type字段正确设置ret ioctl(fd, VIDIOC_G_FMT, vfmt);if(ret 0){perror(获取格式失败);close(fd);return -1;}if(vfmt.fmt.pix.width 640 vfmt.fmt.pix.height 480 vfmt.fmt.pix.pixelformat V4L2_PIX_FMT_MJPEG){printf(设置成功\n);}else{printf(设置失败\n);close(fd);return -1;}//4.申请内核空间struct v4l2_requestbuffers reqbuffer;reqbuffer.type V4L2_BUF_TYPE_VIDEO_CAPTURE;reqbuffer.count 4;//申请4个缓冲区reqbuffer.memory V4L2_MEMORY_MMAP;//映射方式ret ioctl(fd, VIDIOC_REQBUFS, reqbuffer);if(ret 0){perror(申请队列空间失败);close(fd);return -1;}//5.映射unsigned char *mptr[4];//保存映射后用户空间的首地址unsigned int size[4]; struct v4l2_buffer mapbuffer;//初始化typeindexmapbuffer.type V4L2_BUF_TYPE_VIDEO_CAPTURE;for(int i0; i4; i){mapbuffer.index i;ret ioctl(fd, VIDIOC_QUERYBUF, mapbuffer);//从内核空间中查询一个空间做映射if(ret 0){perror(查询内核空间队列失败);close(fd);return -1;}mptr[i] (unsigned char *)mmap(NULL, mapbuffer.length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, mapbuffer.m.offset);size[i] mapbuffer.length;//通知使用完毕--‘放回去’ret ioctl(fd, VIDIOC_QBUF, mapbuffer);if(ret 0){perror(放回失败);close(fd);return -1;} }//6.开始采集int type V4L2_BUF_TYPE_VIDEO_CAPTURE;ret ioctl(fd, VIDIOC_STREAMON, type);if(ret 0){perror(开启失败);}//7.从队列中提取一帧数据struct v4l2_buffer readbuffer;readbuffer.type V4L2_BUF_TYPE_VIDEO_CAPTURE;ret ioctl(fd, VIDIOC_DQBUF, readbuffer);if(ret 0){perror(提取数据失败);}FILE *filefopen(my.jpg,w);fwrite(mptr[readbuffer.index],readbuffer.length,1,file);fclose(file);//通知内核已经使用完毕ret ioctl(fd, VIDIOC_QBUF, readbuffer);if(ret 0){perror(放回队列失败);return -1;}//8.停止采集ret ioctl(fd, VIDIOC_STREAMOFF, type);if(ret 0){ perror(停止采集失败);return -1;} //9.释放映射for(int i0; i4; i){munmap(mptr[i],size[i]);}//10.关闭设备close(fd);return 0;}4.运行测试
在ubuntu上编译程序,并运行
gcc -o video video.c
./video可以看到测usb摄像头支持JPEG和YUYV 4:2:2 两种格式并生成my.jpg格式图片
输入eog my.jpg使用图像查看器来查看它