由于是第一次接触UVC,所以内容会比较杂。文章内容多为参考整合。
参考链接:
【1】添加内核支持部分:https://blog.csdn.net/u010034969/article/details/115210890
【2】100ask-摄像头V4L2编程应用开发:http://100ask.org/pages/cfba84/#_7-1-v4l2%E7%AE%80%E4%BB%8B
【3】完整代码来源:https://download.csdn.net/download/luckywang1103/6707987
之前在淘宝上买了个USB摄像头,最近想着在我的imx6ull开发板上应用试试,在这里做个记录。
下面是从淘宝商品详情找到的产品参数:
附上目录:
1.相关概念
1.1 UVC
UVC全称为USB Video Class,即:USB视频类,是一种为USB视频捕获设备定义的协议标准。UVC是Microsoft与另外几家设备厂商联合推出的为USB视频捕获设备定义的协议标准,已成为USB org标准之一。支持 USB Video Class (UVC) standard 1.1可以让相机在所有的作业系统以及平台中使用(Windows, Linux, Mac etc.)。用户只需连接相机便可进行图像传输,而无需安装任何驱动程序 。
简单点说,就是只要USB摄像头是UVC摄像头,那这个摄像头的驱动就遵循一个通用的格式,可以实现免驱的操作。在Linux系统中,UVC驱动的支持在Linux Kernel 2.4之后被增加到内核中。但是为了让内核识别到这款摄像头,还要告诉内核这个USB的ID是UVC设备才行。
UVC设备的PID和VID简介
每个USB设备都有VID(Vender ID,供应商识别码)和PID(Product ID,产品识别码),两者的长度均为2Byte。PID和VID是主机识别USB设备时使用。
主机检测到USB设备后,首先会通过USB Class查询插入的是什么设备。检测到插入设备的类型后,通过读取和检索VID和PID,主机就能知道当前连接的设备的类型,并能了解应该给这个USB设备加载什么样的驱动程序进行通信。
例如,我们插入了U盘
- 系统首先会检测插入USB设备的Class。
- U盘的Class是0x08 —— Mass Storage。那么就会按照USB大容量存储设备的方式对USB设备进行操作。
- 同时,系统还会查看设备的VID和PID,识别该U盘的制造商和型号,并查看是否需要加载特殊的驱动。
1.2 Linux下V4L2简介
V4L2是Video for linux2的简称,是linux中关于视频设备的内核驱动。在Linux中,视频设备是设备文件,可以像访问普通文件一样对其进行读写或是MMAP。
摄像头的设备节点在/dev/video下,如果只有一个视频设备,通过为/dev/video0,也有可能是/dev/uvcvideo。
1.3 V4L2视频采集原理
使用V4L2采集图像,需要对收集到的图像进行如下步骤:
- 分配帧缓冲区
- 将分配到的帧缓冲区从内核空间映射到用户空间
- 将申请到的帧缓冲区在视频采集输入队列中排队
- 等待数据的到来
数据传输流程:
- 首先驱动程序采集一帧图像数据,并放入第一个帧缓冲区
- 第一个帧缓冲区存满一帧图像数据后,将该缓冲区放入视频采集输出队列,等待应用程序取出
- 应用程序取出图像数据后可以对图像数据进行处理或存储操作,然后将该帧缓冲区放入视频采集输入队列的尾部
- 驱动程序采集下一帧数据,放入第二个缓冲区,存满一帧数据后放入输出队列
循环上面几步,就可以实现循环采集。流程图如下:
1.4 V4L2应用程序实现流程
V4L2视频采集一般分为以下5步:
- 打开设备,进行初始化参数设置,通过V4L2接口设置视频图像的采集窗口、采集的点阵大小和格式;
- 申请图像帧缓冲,并进行内存映射,将这些帧缓冲从内核空间映射到用户空间,便于应用程序读取、处理图像数据;
- 将帧缓冲进行入队操作,启动视频采集;
- 驱动开始视频数据的采集,应用程序从视频采集输出队列取出帧缓冲区,处理完后,将帧缓冲区重新放入视频采集输入队列,循环往复采集连续的视频数据;
- 释放资源,停止采集工作。
1.4.* 一些常用的命令表示符
(1)VIDIOC_REQBUFS:分配内存;
(2)VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址;
(3)VIDIOC_QUERYCAP:查询驱动功能;
(4)VIDIOC_ENUM_FMT:获取当前驱动支持的视频格式;
(5)VIDIOC_S_FMT:设置当前驱动的视频捕获格式;
(6)VIDIOC_G_FMT:读取当前驱动的视频捕获格式;
(7)VIDIOC_TRY_FMT:验证当前驱动的显示格式;
(8)VIDIOC_CROPCAP:查询驱动的修剪功能;
(9)VIDIOC_S_CROP:设置视频信号的边框;
(10)VIDIOC_G_CROP:读取视频信号的边框;
(11)VIDIOC_QBUF:把数据从缓存中读取出来;
(12)VIDIOC_DQBUF:把数据放回缓存队列;
(13)VIDIOC_STREAMOP:开始视频显示函数;
(14)VIDIOC_STREAMOFF:结束视频显示函数;
(15)VIDIOC_QUERYSTD:检查当前视频设备支持的标准,例如PAL或NTSC;
2.环境搭建
本部分参考https://blog.csdn.net/u010034969/article/details/115210890
首先需要在内核中添加UVC支持,为了让这个板子正常识别到这个摄像头,需要在内核中打开UVC摄像头的编译,并将摄像头的PID和VID加入到代码中。
2.1 内核添加UVC支持
2.1.1 查看PID与VID
将摄像头插入电脑USB接口,借助windows的设备管理器,查看对应摄像头的属性。并在详细信息中找到硬件ID。
得到其vid和pid:
- VID:0x0C45
- PID:0x64AB
保存PID和VID备用。
2.1.2 内核图形配置界面添加UVC设备支持
进入内核根目录,make menuconfig
进入内核编译配置。打开如下几项
Device Drives
---> Multimedia support
---> <*>Media USB Adapters
---> V4L platform devices
---> <*>SoC camera support
---> <*>platform camera support
下面直接引用参考链接【1】的图。
- 进入Device Drivers → Multimedia support → Media USB Adapters,找到并开启USB Video Class(UVC)如下图:
如果找不到这项,说明是相关的依赖项未打开。
可以搜索关键词USB_VIDEO_CLASS,能看到这项的依赖项:
将所有依赖项设置为Y(编译进内核)即可。- 同样,Device Drivers → Multimedia support → V4L platform devices下,开启如下两项:
2.1.3 内核中增加摄像头的PID和VID
打开内核代码driver/media/usb/uvc/uvc_driver.c,找到usb_device_id结构体uvc_ids。
仿照其他摄像头的代码,添加摄像头的PID和VID:
static struct usb_device_id uvc_ids[] = {
//下面是新增的摄像头的id信息,VID是0x05A3,PID是0x9601
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x0C45, //摄像头的VID
.idProduct = 0x64AB,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_RESTRICT_FRAME_RATE
},
//下面是原有的信息
/* LogiLink Wireless Webcam */
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE
| USB_DEVICE_ID_MATCH_INT_INFO,
.idVendor = 0x0416,
.idProduct = 0xa91a,
.bInterfaceClass = USB_CLASS_VIDEO,
.bInterfaceSubClass = 1,
.bInterfaceProtocol = 0,
.driver_info = UVC_QUIRK_PROBE_MINMAX
},
//省略...
}
可以通过如下指令查看新增的设备节点。
/lib/modules # ls /dev/
video0 video1
在这里新增的是/dev/video1。
2.2 环境安装
2.2.1 JPEGLIB
摄像头输出数据为MJPEG格式(怎么查看支持的输出格式在后面有写到(VIDIOC_G_FMT指令)),需要使用jpeglib库将数据转化为rbg的格式。
官网下载安装包,解压。
./configure CC=arm-linux-gnueabihf-gcc LD=arm-linux-gnueabihf-ld --host=arm-linux-gnueabihf --prefix=/home/book/linux_study/IMX6ULL/Project/Downloads/jpeg-9e/temp --exec-prefix=/home/book/linux_study/IMX6ULL/Project/Downloads/jpeg-9e/temp --enable-shared --enable-static
make
make install
这里的CC、LD用于指定编译器与链接器,host指定运行主机,–prefix指定安装目录,安装完成后文件都在–prefix指定的目录中,–enable-shared 选项开启动态库支持。
安装完成之后,需要把对应的头文件和库文件移植到运行环境中,通过下面的语句查看交叉编译链的库文件和头文件所在目录。
echo 'main(){}' | arm-linux-gnueabihf-gcc -E -v -
进入上面指定的目录/home/book/linux_study/IMX6ULL/Project/Downloads/jpeg-9e/temp
,复制lib与include下的文件到对应的目录。
cp lib/* -rfd /home/book/linux_study/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin/../arm-linux-gnueabihf/libc/usr/lib/
cp include/* /home/book/linux_study/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin/../arm-linux-gnueabihf/libc/usr/include
以及同样需要复制到开发板的文件系统中,对应/usr/lib
与/usr/include
至此jpeglib移植完成,在对程序进行交叉编译时,需要加上链接选项--ljpeg
。
头文件包含:#include <jpeglib.h>
,可在测试程序中包含头文件并编译检查是否移植成功。
3. 各步骤详细编程实现
V4L2的代码主要在video2lcd/video/v4l2.c
文件中。
本章内容参考搬运自100ask-摄像头V4L2编程应用开发
3.1. 打开设备
devname = "/dev/video1";
if(argc==2) devname = argv[1];
int video_fd = open(devname, O_RDWR, 0); //打开摄像头设备,使用阻塞方式打开
if (video_fd <0)
{
printf(“open device error\n”);
return -1;
}
3.2. 查询设备属性
通过VIDIOC_QUERYCAP
命令来查询driver是否合乎规范。因为V4L2要求所有driver和device都支持这个ioctl。所以,通过VIDIOC_QUERYCAP
命令是否成功来判断当前device和driver是否符合V4L2规范。当然,这个命令执行成功的同时还能够得到设备足够的信息,如struct v4l2_capability
结构体所示内容。19~22行代码检查当前设备是否为capture设备,并检查使用内存映射还是直接读的方式获取图像数据。
1 struct v4l2_capability tV4l2Cap;
2 iError = ioctl(video_fd , VIDIOC_QUERYCAP, &tV4l2Cap);
3 memset(&tV4l2Cap, 0, sizeof(struct v4l2_capability));
4 iError = ioctl(video_fd , VIDIOC_QUERYCAP, &tV4l2Cap);
5 if (iError) {
6 DBG_PRINTF("Error opening device %s: unable to query device.\n", strDevName);
7 goto err_exit;
8 }
9
10 if (!(tV4l2Cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))//检测是否为capture设备
11 {
12 DBG_PRINTF("%s is not a video capture device\n", strDevName);
13 goto err_exit;
14 }
15
16 if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING) {
17 DBG_PRINTF("%s supports streaming i/o\n", strDevName);
18 }
19
20 if (tV4l2Cap.capabilities & V4L2_CAP_READWRITE) {
21 DBG_PRINTF("%s supports read i/o\n", strDevName);
22 }
3.3. 获取视频格式
结构体
v4l2_fmtdesc
的内容如下,该结构体描述当前camera支持的格式信息。01 struct v4l2_fmtdesc 02 { 03 __u32 index; // 要查询的格式序号,应用程序设置 04 enum v4l2_buf_type type; // 帧类型,应用程序设置 05 __u32 flags; // 是否为压缩格式 06 __u8 description[32]; // 格式名称 07 __u32 pixelformat; //所支持的格式 08 __u32 reserved[4]; // 保留 09 };
使用VIDIOC_ENUM_FMT
命令查询当前camera支持的所有格式。
struct v4l2_fmtdes
c结构体中index
要设置,从0开始;
enum v4l2_buf_type type
也要设置,如果使用的是camera设备,则enum v4l2_buf_type type
要设置为V4L2_BUF_TYPE_VIDEO_CAPTURE
,因为camera是CAPTURE设备。
结构体中的其他内容driver会填充。其中__u32 pixelformat
参数在设置图像帧格式时需要使用。
struct v4l2_fmtdesc tFmtDesc;
memset(&tFmtDesc, 0, sizeof(tFmtDesc));
tFmtDesc.index = 0;
tFmtDesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
while ((iError = ioctl(iFd, VIDIOC_ENUM_FMT, &tFmtDesc)) == 0) {//查询当前camera支持的所有格式。
if (isSupportThisFormat(tFmtDesc.pixelformat))
{
ptVideoDevice->iPixelFormat = tFmtDesc.pixelformat;
break;
}
printf("enum fmt desc\n");
printf("%d.%s\n",tFmtDesc.index,tFmtDesc.description);
tFmtDesc.index++;
}//经查询,买的摄像头只支持MJPEG
3.4. 设置图像帧格式
设置图像帧格式需要用到
struct v4l2_format
结构体,该结构体描述每帧图像的具体格式,包括帧类型以及图像的长、宽等信息。01 struct v4l2_format 02 { 03 enum v4l2_buf_type type; // 帧类型,应用程序设置 04 union fmt 05 { 06 struct v4l2_pix_format pix; // 视频设备使用 07 structv 4l2_window win; 08 struct v4l2_vbi_format vbi; 09 struct v4l2_sliced_vbi_format sliced; 10 __u8 raw_data[200]; 11 }; 12 };
struct v4l2_format
结构体需要设置enum v4l2_buf_type type
==和union fmt
中的struct v4l2_pix_format pix
。
enum v4l2_buf_type type
因为使用的是camera设备,camera是CAPTURE设备,所以设置成V4L2_BUF_TYPE_VIDEO_CAPTURE。
struct v4l2_pix_format pix
设置一帧图像的长、宽和格式等,由于要适配LCD输出,所以长、宽设置为LCD支持的长、宽,如124~125行所示。
struct v4l2_format tV4l2Fmt; //设置获取视频的格式
memset( &tV4l2Fmt, 0, sizeof(tV4l2Fmt));
tV4l2Fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //视频数据流类型,永远都是V4L2_BUF_TYPE_VIDEO_CAPTURE
tV4l2Fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;//视频源的格式为JPEG或YUN4:2:2或RGB
tV4l2Fmt.fmt.pix.width = iLcdWidth; //设置视频宽度
tV4l2Fmt.fmt.pix.height = iLcdHeight; //设置视频高度
tV4l2Fmt.fmt.pix.field = V4L2_FIELD_ANY;
if (ioctl(fd, VIDIOC_S_FMT, &tV4l2Fmt) < 0) //使配置生效
{
printf("set format failed\n");
return -1;
}
设置后可通过VIDIOC_G_FMT
读取格式查看是否设置成功。
3.5. 申请缓冲区
相关结构体如下,该结构体描述申请的缓冲区的基本信息。
01 struct v4l2_requestbuffers 02 { 03 __u32 count; // 缓冲区内缓冲帧的数目 04 enum v4l2_buf_type type; // 缓冲帧数据格式 05 enum v4l2_memorymemory; // 区别是内存映射还是用户指针方式 06 __u32 reserved[2]; 07 };
- 申请一个拥有四个缓冲帧的缓冲区,
__u32 count
为缓冲帧的数目; enum v4l2_buf_type type
和前文一样,同样设置成V4L2_BUF_TYPE_VIDEO_CAPTURE
;enum v4l2_memorymemory
用来区分是内存映射还是用户指针,我们使用内存映射的方式,所以设置成V4L2_MEMORY_MMAP
。
struct v4l2_requestbuffers tV4l2ReqBuffs; //申请帧缓冲
/* request buffers */
memset(&tV4l2ReqBuffs, 0, sizeof(struct v4l2_requestbuffers));
tV4l2ReqBuffs.count = NB_BUFFER; //4,缓存数量,即可保存的图片数量
tV4l2ReqBuffs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //数据流类型,永远都是V4L2_BUF_TYPE_VIDEO_CAPTURE
tV4l2ReqBuffs.memory = V4L2_MEMORY_MMAP; //存储类型:V4L2_MEMORY_MMAP或V4L2_MEMORY_USERPTR
iError = ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);//使配置生效
if (iError)
{
DBG_PRINTF(“Unable to allocate buffers.\n”);
goto err_exit;
}
3.6. 对申请的帧缓冲内存映射
相关结构体如下,该结构体表示一帧图像数据的基本信息,包含序号、缓冲帧长度和缓冲帧地址等信息。
01 struct v4l2_buffer 02 { 03 __u32 index; //buffer 序号 04 enum v4l2_buf_type type; //buffer 类型 05 __u32 byteused; //buffer 中已使用的字节数 06 __u32 flags; // 区分是MMAP 还是USERPTR 07 enum v4l2_field field; 08 struct timeval timestamp; // 获取第一个字节时的系统时间 09 struct v4l2_timecode timecode; 10 __u32 sequence; // 队列中的序号 11 enum v4l2_memory memory; //IO 方式,被应用程序设置 12 union m 13 { 14 __u32 offset; // 缓冲帧地址,只对MMAP 有效 15 unsigned long userptr; 16 }; 17 __u32 length; // 缓冲帧长度 18 __u32 input; 19 __u32 reserved; 20 };
将内核空间的帧缓冲映射到用户空间,需要两个数据接收帧缓冲的长度和地址,
这里我们需要自己定义一个结构体,其中iVideoBufMaxLen接收帧缓冲的长度,pucVideBuf接收帧缓冲地址。
16 struct VideoDevice {
17 int iFd; //设备文件句柄
18 int iPixelFormat; //支持的像素格式
19 int iWidth; //视频宽度与高度
20 int iHeight;
21
22 int iVideoBufCnt; //表示缓冲区个数
23 int iVideoBufMaxLen; //表示接收帧缓冲区长度
24 int iVideoBufCurIndex;//表示当前缓冲区索引
25 unsigned char *pucVideBuf[NB_BUFFER]; //用于存储各个接收缓冲区的地址。
26
27 /* 函数 */
28 PT_VideoOpr ptOPr;
29 };
mmap原型:
void *mmap(void*addr, size_t length, int prot, int flags, int fd, off_t offset);
- 1
参数具体的含义:
addr:映射起始地址,一般为NULL,让内核自动选择;
length:被映射内存块的长度;
prot:标志映射后能否被读写,其值为PROT_EXEC,PROT_READ,PROT_WRITE,PROT_NONE;
flags:确定此内存映射能否被其他进程共享,可设置为MAP_SHARED或MAP_PRIVATE;
fd:设备文件句柄;
offset:确定映射后的内存地址
以下代码使用VIDIOC_QUERYBUF
命令和mmap
函数将内核空间的缓冲区映射到用户空间。
VIDIOC_QUERYBUF
命令的使用需要参数struct v4l2_buffer
结构体- 结构体中的
type
、memory
和index
参数需要设置type
和memory
和前文中的设置一样,分别设置成V4L2_BUF_TYPE_VIDEO_CAPTURE
和V4L2_MEMORY_MMAP
index
参数表示申请的缓冲帧的标号,从0开始,包含申请的所有缓冲帧。
- 结构体中的
struct v4l2_buffer tV4l2Buf;
/* map the buffers */
for (i = 0; i < ptVideoDevice->iVideoBufCnt; i++)
{
memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
tV4l2Buf.index = i; //申请的缓冲帧的标号
tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
tV4l2Buf.memory = V4L2_MEMORY_MMAP; //MMAP方式访问
iError = ioctl(iFd, VIDIOC_QUERYBUF, &tV4l2Buf); //把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址;
if (iError)
{
DBG_PRINTF("Unable to query buffer.\n");
goto err_exit;
}
ptVideoDevice->iVideoBufMaxLen = tV4l2Buf.length;
ptVideoDevice->pucVideBuf[i] = mmap(0 /* start anywhere */ ,
tV4l2Buf.length, PROT_READ, MAP_SHARED, iFd,
tV4l2Buf.m.offset);//将内核空间的缓冲区映射到用户空间。
if (ptVideoDevice->pucVideBuf[i] == MAP_FAILED)
{
DBG_PRINTF("Unable to map buffer\n");
goto err_exit;
}
}
3.8. 启动捕捉图像数据
启动捕捉图像数据使用VIDIOC_STREAMON命令,当该命令执行成功后,便可以等待图像数据的到来。
/********************************************************************** * 函数名称:V4l2StartDevice * 功能描述:开始捕捉图像数据 * 输入参数:ptVideoDevice * 输出参数:无 * 返 回 值:无 * 修改日期 版本号 修改人 修改内容 * ----------------------------------------------- * 2020/02/16 V1.0 zhenhua 创建 ***********************************************************************/ /********************************************************************** * 函数名称:V4l2StartDevice * 功能描述:开始捕捉图像数据 * 输入参数:ptVideoDevice * 输出参数:无 * 返 回 值:无 * 修改日期 版本号 修改人 修改内容 * ----------------------------------------------- * 2020/02/16 V1.0 zhenhua 创建 ***********************************************************************/ static int V4l2StartDevice(PT_VideoDevice ptVideoDevice) { int iType = V4L2_BUF_TYPE_VIDEO_CAPTURE;//数据流类型,永远都是V4L2_BUF_TYPE_VIDEO_CAPTURE int iError;
iError = ioctl(ptVideoDevice->iFd, VIDIOC_STREAMON, &iType);//启动捕捉图像数据 if (iError) { DBG_PRINTF("Unable to start capture.\n"); return -1; } return 0;
}
3.9. 出列采集的帧缓冲,并处理图像数据,然后再将数据帧入列
我们可以使用VIDIOC_DQBUF
命令,等待缓冲帧的到来,当有缓冲帧被放入视频输出缓冲队列,我们便可以采到一帧图像。接收到图像我们可以对图像进行操作,例如保存、压缩或者LCD输出等。
/********************************************************************** * 函数名称:V4l2GetFrameForStreaming * 功能描述:从图像数据流中获取一帧图像数据 * 输入参数:ptVideoDevice ptVideoBuf * 输出参数:无 * 返 回 值:无 * 修改日期 版本号 修改人 修改内容 * ----------------------------------------------- * 2020/02/16 V1.0 zhenhua 创建 ***********************************************************************/ static int V4l2GetFrameForStreaming(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf) { struct pollfd tFds[1]; int iRet; struct v4l2_buffer tV4l2Buf; /* poll */ tFds[0].fd = ptVideoDevice->iFd; tFds[0].events = POLLIN; iRet = poll(tFds, 1, -1); if (iRet <= 0) { DBG_PRINTF("poll error!\n"); return -1; } /* VIDIOC_DQBUF */ memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer)); tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; tV4l2Buf.memory = V4L2_MEMORY_MMAP; iRet = ioctl(ptVideoDevice->iFd, VIDIOC_DQBUF, &tV4l2Buf);//VIDIOC_DQBUF把数据放回缓存队列; if (iRet < 0) { DBG_PRINTF("Unable to dequeue buffer.\n"); return -1; } ptVideoDevice->iVideoBufCurIndex = tV4l2Buf.index; ptVideoBuf->iPixelFormat = ptVideoDevice->iPixelFormat; ptVideoBuf->tPixelDatas.iWidth = ptVideoDevice->iWidth; ptVideoBuf->tPixelDatas.iHeight = ptVideoDevice->iHeight; ptVideoBuf->tPixelDatas.iBpp = (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_YUYV) ? 16 : \ (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_MJPEG) ? 0 : \ (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_RGB565) ? 16 : \ 0; ptVideoBuf->tPixelDatas.iLineBytes = ptVideoDevice->iWidth * ptVideoBuf->tPixelDatas.iBpp / 8; ptVideoBuf->tPixelDatas.iTotalBytes = tV4l2Buf.bytesused; ptVideoBuf->tPixelDatas.aucPixelDatas = ptVideoDevice->pucVideBuf[tV4l2Buf.index]; return 0; }
当我们从缓冲帧输出队列取出一个缓冲帧,取出图像数据后我们需要将缓冲帧重新放回到视频输入缓冲队列,该操作还是使用VIDIOC_QBUF命令,放回缓冲帧输入队列后继续等待被填满。
296 /********************************************************************** 297 * 函数名称:V4l2PutFrameForStreaming 298 * 功能描述:将取出的帧缓冲重新放回图像输入队列 299 * 输入参数:ptVideoDevice 300 ptVideoBuf 301 * 输出参数:无 302 * 返 回 值:无 303 * 修改日期 版本号 修改人 修改内容 304 * ----------------------------------------------- 305 * 2020/02/16 V1.0 zhenhua 创建 306 ***********************************************************************/ 307 static int V4l2PutFrameForStreaming(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf) 308 { 309 /* VIDIOC_QBUF */ 310 struct v4l2_buffer tV4l2Buf; 311 int iError; 312 313 memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer)); 314 tV4l2Buf.index = ptVideoDevice->iVideoBufCurIndex; 315 tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 316 tV4l2Buf.memory = V4L2_MEMORY_MMAP; 317 iError = ioctl(ptVideoDevice->iFd, VIDIOC_QBUF, &tV4l2Buf); 318 if (iError) 319 { 320 DBG_PRINTF("Unable to queue buffer.\n"); 321 return -1; 322 } 323 return 0; 324 }