【北京迅为】《iTOP-3588开发板系统编程手册》-第19章 V4L2摄像头应用编程

RK3588是一款低功耗、高性能的处理器,适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用,RK3588支持8K视频编解码,内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP,内置NPU,支持INT4/INT8/INT16/FP16混合运算能力,支持安卓12和、Debian11、Build root、Ubuntu20和22版本登系统。了解更多信息可点击迅为官网   

【粉丝群】824412014

【实验平台】:迅为RK3588开发板

【内容来源】《iTOP-3588开发板系统编程手册》

【全套资料及网盘获取方式】联系淘宝客服加入售后技术支持群内下载

【视频介绍】:【强者之芯】 新一代AIOT高端应用芯片 iTOP -3588人工智能工业AI主板


第19章 V4L2摄像头应用编程 

在本章内容开始之前需要注意的是,本章节用到的摄像头为USB接口的UVC摄像头,开发板配套的OV5695摄像头不支持本章节实验(之后会进行适配)。

19.1 V4L2介绍

V4L2 (Video4Linux2) 是 Linux 内核中的一个框架,提供了一套用于视频设备驱动程序开发的 API。它是一个开放的、通用的、模块化的视频设备驱动程序框架,允许 Linux 操作系统和应用程序与各种视频设备(如摄像头、视频采集卡等)进行交互。

V4L2 提供了一个通用的 API,使应用程序能够访问和控制视频设备,包括获取设备信息、设置设备参数、采集视频数据、控制设备状态等。V4L2 还提供了一个统一的视频数据格式,允许应用程序在处理视频数据时无需考虑设备的具体格式。

下面我们来详细介绍一下 V4L2 的主要特性:

(1)模块化的架构

V4L2 是一个模块化的架构,允许多个设备驱动程序同时存在并共享同一个 API。每个设备驱动程序都是一个独立的内核模块,可以在运行时加载和卸载。这种架构可以使开发人员更容易地开发新的视频设备驱动程序,并允许多个驱动程序同时使用相同的 API。

(2)统一的设备节点

V4L2 提供了一种统一的设备节点,使应用程序可以使用相同的方式访问不同类型的视频设备。这种节点通常是 /dev/videoX,其中 X 是一个数字,表示设备的编号。应用程序可以通过打开这个节点来访问设备,并使用 V4L2 API 进行数据采集和控制。Buildroot系统启动之后使用以下命令对videoX节点进行查看,如下图所示:

ls /dev/video

(3)统一的视频数据格式

V4L2 提供了一个统一的视频数据格式,称为 V4L2_PIX_FMT,允许应用程序在处理视频数据时无需考虑设备的具体格式。V4L2_PIX_FMT 包括了许多常见的视频格式,如 RGB、YUV 等。应用程序可以使用 V4L2 API 来查询设备支持的数据格式,并选择适当的格式进行数据采集和处理。

(4)支持多种视频设备

V4L2 支持许多不同类型的视频设备,包括摄像头、视频采集卡、TV 卡等。每个设备都有自己的驱动程序,提供了相应的 V4L2 API。这些驱动程序可以根据设备的不同特性,提供不同的采集模式、数据格式、控制参数等。

(5)支持流式 I/O

V4L2 支持流式 I/O,即通过内存映射的方式将视频数据从设备直接传输到应用程序中。这种方式可以减少数据复制的次数,提高数据传输的效率。

(6)支持控制参数

  V4L2 允许应用程序通过 API 来控制视频设备的参数,包括亮度、对比度、色彩饱和度、曝光时间等。应用程序可以使用 V4L2 API 来查询设备支持的参数,并设置适当的值。

(7)支持事件通知

 V4L2 支持事件通知,当视频设备状态发生变化时,如视频信号丢失、帧率变化等,V4L2 驱动程序可以向应用程序发送通知,以便应用程序做出相应的处理。

从上面的特征可以看出,V4L2 提供了一套通用、灵活、可扩展的视频设备驱动程序框架,使得 Linux 操作系统和应用程序可以方便地与各种视频设备进行交互,并且不需要关心设备的具体实现细节。从而让开发人员能够更加专注于应用程序的开发。

19.2 V4L2视频采集步骤

V4L2视频采集的常用步骤如下所示:

步骤

步骤描述

1

打开视频设备

使用 open() 系统调用打开相应的视频设备文件,获取文件描述符以便后续的操作。

2

查询设备能力

使用 ioctl() 系统调用发送 VIDIOC_QUERYCAP 命令查询视频设备的基本信息,如设备名称、版本号、驱动程序信息等。

3

设置采集参数

使用 ioctl() 系统调用发送 VIDIOC_S_FMT 命令来设置采集参数,如视频格式、分辨率、帧率等。需要检查参数是否被设备支持。

4

请求帧缓冲

使用 ioctl() 系统调用发送 VIDIOC_REQBUFS 命令来请求帧缓冲,指定帧缓冲的数量和类型等参数。

5

映射帧缓冲

使用 ioctl() 系统调用发送 VIDIOC_QUERYBUF 命令来查询帧缓冲的信息,如帧缓冲的地址和大小等。然后使用 mmap() 系统调用将帧缓冲映射到用户空间。

6

启动视频采集

使用 ioctl() 系统调用发送 VIDIOC_STREAMON 命令来启动视频采集,视频设备开始采集视频帧并将其存储到帧缓冲中。

7

读取视频帧数据

使用 read() 系统调用读取视频帧数据,也可以使用 select() 系统调用等待视频帧的到来并读取视频帧数据。

8

停止视频采集和释放资源

使用 ioctl() 系统调用发送 VIDIOC_STREAMOFF 命令来停止视频采集。然后使用 munmap() 系统调用将帧缓冲从用户空间解除映射,最后使用 close() 系统调用关闭视频设备,释放资源。

对于打开和关闭设备想必大家已经非常熟悉了,本小节将对上述用到的ioctl参数和宏进行讲解。

19.2.1查询设备能力

在使用V4L2进行视频采集前,需要先通过查询设备能力来获取设备可以提供的视频格式、分辨率等信息。

(1)查询设备的基本信息

在程序中使用VIDIOC_QUERYCAP命令通过ioctl()函数查询设备的基本信息,例如设备名称、版本号以及已支持的标准等等,使用代码如下所示:

// 定义一个v4l2_capability结构体的变量cap
struct v4l2_capability cap; 
// 使用ioctl函数发送VIDIOC_QUERYCAP命令来获取视频设备的基本信息,并将结果保存到cap变量中 
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) 
{ 
perror("VIDIOC_QUERYCAP"); 
return -1; 
} 

在查询设备信息之后,cap结构体的各个字段将被填充,可以通过这些字段来获取设备的基本信息。

struct v4l2_capability结构体定义在linux/videodev2.h头文件中,用于获取设备的能力和驱动程序的一些信息,包括设备名称、驱动名称、是否支持视频捕获和视频输出等。结构体定义如下:

struct v4l2_capability //描述 video 设备功能和设备信息的结构体{ __u8 driver[16]; //设备所属的 driver 名称__u8 card[32]; //设备名称__u8 bus_info[32];//设备所连接的总线信息,如 USB 控制器__u32 version;//设备 driver 版本__u32 capabilities; //设备支持的能力,如视频捕获、输出、调整、元数据等__u32 device_caps; //设备特有的能力__u32 reserved[3]; //保留字段 };

(2)查询设备支持的视频格式

查询设备的能力后,应用程序还应该查询设备支持的视频格式。这可以通过向VIDIOC_ENUM_FMT命令传递一个v4l2_fmtdesc结构体完成,驱动程序将返回支持的视频格式和Resolutions,使用VIDIOC_ENUM_FMT命令通过ioctl()函数来查询设备支持的视频格式程序示例如下所示:

struct v4l2_fmtdesc fmt; // 定义v4l2_fmtdesc结构体变量fmt 
memset(&fmt, 0, sizeof(fmt)); // 将fmt结构体的所有成员变量初始化为0 
fmt.index = 0; // 设置fmt结构体的index成员变量为0 
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 设置fmt结构体的type成员变量为V4L2_BUF_TYPE_VIDEO_CAPTURE,表示视频捕捉类型 
while (ioctl(fd, VIDIOC_ENUM_FMT, &fmt) == 0) // 使用while循环,每次调用ioctl函数执行VIDIOC_ENUM_FMT命令,返回值为0表示命令执行成功 
{ fmt.index++; // 每次循环将fmt结构体的index成员变量加1 printf("Format: %s\n", fmt.description); // 在控制台输出fmt结构体中的description成员变量值,即所枚举到的格式名称 
}

该代码段列出了设备支持的所有视频格式,包括每种格式的名称、描述和FourCC编码。FourCC编码是四个字符的代码,用于唯一识别每种视频格式。

v4l2_fmtdesc结构体内容如下所示:

struct v4l2_fmtdesc {__u32            index;          // 格式编号,由应用程序提供enum v4l2_buf_type type;          // 缓冲类型,比如 V4L2_BUF_TYPE_VIDEO_CAPTURE__u32            flags;          // 支持的格式的标志__u8             description[32];// 格式的描述信息,以空字符结束__u32            pixelformat;    // 格式的四字符编码__u32            reserved[4];    // 保留字段,必须设置为0
};

(3)查询支持分辨率

在 V4L2 驱动程序中,摄像头通常支持多种不同的像素格式,每种像素格式都可以支持不同的帧大小。为了查询摄像头支持的所有帧大小,可以使用 v4l2_frmsizeenum 结构体和 VIDIOC_ENUM_FRAMESIZES 命令。程序示例如下所示:

struct v4l2_frmsizeenum frmsize;// 定义一个名为 frmsize 的 v4l2_frmsizeenum 结构体变量
memset(&frmsize, 0, sizeof(frmsize));// 将 frmsize 变量的内存清零,使其所有位都变为 0
frmsize.index = 0;// 设置 frmsize 变量的 index 成员变量为 0// 设置 frmsize 变量的 pixel_format 成员变量为 V4L2_PIX_FMT_YUYV
frmsize.pixel_format = V4L2_PIX_FMT_YUYV;// while 循环,当 VIDIOC_ENUM_FRAMESIZES 命令执行成功时继续循环
// VIDIOC_ENUM_FRAMESIZES 命令用于获取指定像素格式的所有帧大小
while (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) == 0) 
{
frmsize.index++;    // 增加 frmsize 变量的 index 成员变量值
printf("Width: %d, Height: %d\n", (int)frmsize.discrete.width,
(int)frmsize.discrete.height);    // 输出 frmsize 变量的 discrete 结构体的 width 和 height 成员变量
}

 上面的代码逐一查询当前设备支持的分辨率,并输出分辨率的宽度和高度。 v4l2_frmsizeenum结构体具体内容如下所示:

struct v4l2_frmsizeenum {__u32           index;          /* 帧大小编号 */__u32           pixel_format;   /* 像素格式 */__u32           type;           /* 设备支持的帧大小类型 */union {                        /* 帧大小 */struct v4l2_frmsize_discrete    discrete;   /* 离散的帧大小 */struct v4l2_frmsize_stepwise    stepwise;   /* 非离散的帧大小 */};__u32   reserved[2];              /* 保留空间以备未来使用 */
};

(4)查询支持的帧率范围

在使用视频设备时,通常需要查询设备支持的帧率范围以便进行设置。在 V4L2 中,可以通过以下步骤查询视频设备支持的帧率范围:可以使用v4l2_frmivalenum 结构体和VIDIOC_ENUM_FRAMEINTERVALS命令通过ioctl()函数来查询当前设备支持的帧率范围等参数,例如:

// 定义一个 v4l2_frmivalenum 结构体用于查询设备支持的帧率范围
struct v4l2_frmivalenum frmival;
// 使用 0 值填充结构体内存,相当于初始化结构体
memset(&frmival, 0, sizeof(frmival));
// 设置查询的帧率范围的序号为 0
frmival.index = 0;
// 设置查询的像素格式为 V4L2_PIX_FMT_YUYV
frmival.pixel_format = V4L2_PIX_FMT_YUYV;
// 设置查询的帧率范围的分辨率为 640x480
frmival.width = 640;
frmival.height = 480;
// 通过循环遍历查询设备支持的所有帧率
while (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival) == 0) 
{// 将帧率范围序号加 1,继续查询下一个帧率范围frmival.index++;// 打印支持的帧率的分子和分母printf("Interval: %d/%d\n", (int)frmival.discrete.numerator,(int)frmival.discrete.denominator);
}

上面的代码逐一查询当前设备支持的帧率范围,并输出帧率的分子和分母。

v4l2_frmsizeenum结构体具体内容如下所示:

struct v4l2_frmivalenum {__u32 index;                    // 查询的帧率范围序号__u32 pixel_format;             // 像素格式__u32 width, height;            // 分辨率__u32 type;                     // 帧率类型union {struct v4l2_frmival_discrete discrete;    // 离散帧率struct v4l2_frmival_stepwise stepwise;    // 范围帧率};__u32 reserved[2];              // 保留空间
};

至此,关于查询设备能力的API就讲解完成了,根据上述API可以很容易地获取到V4L2设备的详细信息,从格式、分辨率到帧率等信息,这对于开发多媒体应用程序非常有帮助。

19.2.2设置采集参数

在使用V4L2进行视频采集时,设置采集参数是非常重要的一步。采集参数会影响视频数据的质量和传输速度,合适的采集参数可以使得视频数据的质量更好,传输速度更快。

设置采集参数可以使用v4l2_format和VIDIOC_S_FM命令通过调用ioctl函数来实现。使用v4l2_format结构体来描述要设置的格式和参数,该结构体定义如下:

struct v4l2_format {enum v4l2_buf_type type;    // 缓冲类型,必须设置union {struct v4l2_pix_format  pix;    // 像素格式struct v4l2_window  win;    // 窗口格式struct v4l2_vbi_format  vbi;    // VBI格式struct v4l2_sliced_vbi_format sliced;  // 切片VBI格式__u8  raw_data[200];    // 原始格式} fmt;  // 格式类型,必须设置
};

在v4l2_format结构体中,必须设置type和fmt字段,其中type指定了要设置的缓冲类型,例如视频捕获或视频输出。 fmt字段是一个联合体,可以根据type的值选择其中的一种格式类型,例如像素格式、窗口格式、VBI格式、切片VBI格式或原始格式。而对于像素格式,可以使用v4l2_pix_format结构体来描述采集参数。该结构体定义如下:

struct v4l2_pix_format {__u32  width;          // 宽度__u32  height;         // 高度__u32  pixelformat;    // 像素格式enum v4l2_field field; // 图像扫描方式__u32  bytesperline;   // 一行所占字节数__u32  sizeimage;      // 图像数据大小enum v4l2_colorspace  colorspace;  // 颜色空间__u32  priv;           // 私有数据
};

例如,如果要设置像素格式为YUYV(YUV422)格式,图像的宽度和高度分别为640和480像素,则可以使用以下代码:

struct v4l2_format fmt; // 定义V4L2的格式结构体
memset(&fmt, 0, sizeof(fmt)); // 将结构体清零
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 指定格式类型为视频采集
fmt.fmt.pix.width = 640; // 视频采集分辨率的宽度
fmt.fmt.pix.height = 480; // 视频采集分辨率的高度
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // 视频数据的像素格式,例如YUYV、MJPEG等
fmt.fmt.pix.field = V4L2_FIELD_ANY; // 视频数据的扫描方式,例如隔行扫描或逐行扫描等
if (ioctl(fd, VIDIOC_S_FMT < 0, &fmt)) 
{printf("ioctl error: VIDIOC_S_FMT\n");return -1;
}

19.2.3请求帧缓冲

在使用 V4L2 进行视频采集时,需要申请一个或多个帧缓冲,用于存储采集到的视频数据。在请求帧缓冲之前,需要先设置好视频采集的参数(例如分辨率、帧率等)。可以使用v4l2_v4l2_requestbuffers结构体和VIDIOC_REQBUFS命令通过ioctl()函数来请求帧缓存,例如:

struct v4l2_requestbuffers req;//创建 V4L2 请求结构体并清零
memset(&req, 0, sizeof(req));
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//设置请求的帧缓冲类型
req.count = BUFFER_COUNT;//设置请求的帧缓冲个数
req.memory = V4L2_MEMORY_MMAP;//设置请求的帧缓冲内存的映射方式
if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) //向内核发送请求,申请帧缓冲
{perror("request buffers failed");exit(EXIT_FAILURE);
}

其中,fd 为打开的视频设备文件描述符,BUFFER_COUNT 为请求的帧缓冲个数,V4L2_MEMORY_MMAP 表示请求内存的映射方式为 mmap,还可以选择其他的内存映射方式。

如果请求成功,内核会在内存中分配一块连续的内存区域,用于存储采集到的视频数据。并将分配的帧缓冲的信息保存在 V4L2 的缓冲结构体中。我们需要遍历缓冲结构体,将每个帧缓冲都映射到用户空间,以便后续使用。

v4l2_requestbuffers结构体内容如下所示:

struct v4l2_requestbuffers {__u32       count;              /* 请求数量 */enum v4l2_buf_type type;        /* 缓冲区类型 */enum v4l2_memory memory;        /* 分配内存方式 */__u32       reserved[2];
};

19.2.4映射帧缓冲

在上一节请求分配一些帧缓冲来存储视频帧数据之后,这些帧缓冲并不能直接使用,还需要将它们映射到进程的虚拟地址空间中,才能对其进行访问和处理。

在 V4L2 中,使用 ioctl 系统调用的 VIDIOC_QUERYBUF 命令可以查询一个帧缓冲的信息,并将查询到的信息可以填充 v4l2_buffer 结构体中,包括该缓冲的物理地址、大小等信息,查询完成后,需要将该帧缓冲映射到进程的虚拟地址空间中。在 V4L2 中,可以使用 mmap 系统调用进行映射。需要注意的是,mmap 映射的是物理地址,因此需要将 VIDIOC_QUERYBUF 返回的帧缓冲的物理地址转换为虚拟地址。在 mmap 映射成功后,即可在进程中使用指针来访问和处理该帧缓冲的数据了。映射帧缓冲的代码示例如下所示:

struct v4l2_buffer buf;
for (int i = 0; i < req.count; ++i) 
{ // 分配并映射帧缓冲memset(&buf, 0, sizeof(buf)); // 将结构体清零buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 帧缓冲类型为视频采集buf.memory = V4L2_MEMORY_MMAP; // 内存映射方式获取帧缓冲buf.index = i; // 选择第i个帧缓冲
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) 
{ // 获取帧缓冲的信息perror("VIDIOC_QUERYBUF");exit(EXIT_FAILURE);}void *addr = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, // 映射帧缓冲到用户空间MAP_SHARED, fd, buf.m.offset);
if (addr == MAP_FAILED) 
{ // 判断映射是否成功perror("mmap");exit(EXIT_FAILURE);}
if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) 
{ // 将帧缓冲插入队列中等待采集perror("VIDIOC_QBUF");exit(EXIT_FAILURE);}
}

v4l2_requestbuffers 结构体是用于请求分配帧缓冲的结构体,它在 v4l2 中扮演着非常重要的角色。它的定义如下:

struct v4l2_requestbuffers {__u32                count;      // 请求的帧缓冲数量enum v4l2_buf_type   type;       // 帧缓冲类型enum v4l2_memory     memory;     // 帧缓冲的内存类型__u32                reserved[2];
};

19.2.5启动视频采集

在上一小节将预备好的帧缓冲放入队列后,使用 ioctl 系统调用的 VIDIOC_STREAMON 命令启动视频采集,代码示例如下所示:

if (ioctl(fd, VIDIOC_STREAMON, &type) == -1)// 启动视频流
{perror("VIDIOC_STREAMON"); // 如果VIDIOC_STREAMON操作失败,输出错误信息exit(EXIT_FAILURE); // 退出程序
}

视频采集启动后,设备会开始采集视频数据并将其存储到预备好的帧。

19.2.6停止视频采集

闭视频流可以通过 ioctl 调用 VIDIOC_STREAMOFF 来完成,该 ioctl 调用需要传递一个枚举类型参数,表示关闭的是视频流的哪个方向(输入流还是输出流),示例代码如下所示:

if (ioctl(fd, VIDIOC_STREAMOFF, &type) == -1) //关闭视频流
{perror("VIDIOC_STREAMOFF");exit(EXIT_FAILURE);
}

最后还要通过 munmap() 函数取消内存映射。整理好的V4L2使用流程如下所示:

19.3 V4L2摄像头应用编程实验

本小节代码在配套资料“iTOP-3588开发板\03_【iTOP-RK3588开发板】指南教程\03_系统编程配套程序\69”目录下,如下图所示:

实验要求:

通过V4L2摄像头采集应用采集USB摄像头的摄像信息,并显示在LCD液晶显示器上。

实验步骤:

首先进入到ubuntu的终端界面输入以下命令来创建 demo69_v4l2.c文件,如下图所示:

vim  demo69_v4l2.c

然后向该文件中添加以下内容:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include <linux/fb.h>typedef struct camera_format {unsigned char description[32]; //字符串描述信息unsigned int pixelformat; //像素格式
} cam_fmt;static cam_fmt cam_fmts[2]; int lcdfd = 0; //LCD设备文件描述符
int *lcdptr = NULL; //LCD映射到内存的指针
int lcd_w=800, lcd_h=1280 ; //LCD屏幕的分辨率
int video_width = 640, video_height= 360; //摄像头采集数据的分辨率static int fb_dev_init(void) 
{//打开LCD设备文件lcdfd = open("/dev/fb0", O_RDWR);if (lcdfd < 0) {perror("LCD open failed:");}/*获取LCD信息*/struct fb_var_screeninfo info;int lret = ioctl(lcdfd, FBIOGET_VSCREENINFO, &info);if (lret < 0) {perror("get info failed:");}//获取LCD的分辨率lcd_w = info.xres;lcd_h = info.yres;//映射LCD到内存lcdptr = (int *)mmap(NULL, lcd_w*lcd_h*4,PROT_READ | PROT_WRITE, MAP_SHARED, lcdfd, 0);if (lcdptr == NULL) {perror("lcd mmap failed:");}//清空LCD屏幕并填充白色背景memset(lcdptr, 0xFF, lcd_w*lcd_h*4);return 0;
}//将YUYV格式的数据转换为RGB格式
void yuyv_to_rgb(unsigned char *yuyvdata, unsigned char * rgbdata, int w, int h)
{int r1, g1, b1;int r2, g2, b2;for (int i = 0; i < w*h/2; i++) {char data[4];memcpy(data, yuyvdata + i*4, 4);unsigned char Y0 = data[0];unsigned char U0 = data[1];unsigned char Y1 = data[2];unsigned char V1 = data[3];r1 = Y0 + 1.4075*(V1 - 128);if (r1 > 255)r1 = 255;if (r1 < 0)r1 = 0;g1 = Y0 - 0.3455*(U0 - 128) - 0.7169*(V1 - 128);if (g1 > 255)g1 = 255;if (g1 < 0)g1 = 0;b1 = Y0 + 1.779*(U0 - 128);if (b1 > 255)b1 = 255;if (b1 < 0)b1 = 0;r2 = Y1 + 1.4075*(V1 - 128);if (r2 > 255)r2 = 255;if (r2 < 0)r2 = 0;g2 = Y1 - 0.3455*(U0 - 128) - 0.7169*(V1 - 128);if (g2 > 255)g2 = 255;if (g2 < 0)g2 = 0;b2 = Y1 + 1.779*(U0 - 128);if (b2 > 255)b2 = 255;if (b2 < 0)b2 = 0;rgbdata[i*6 + 0] = r1;rgbdata[i*6 + 1] = g1;rgbdata[i*6 + 2] = b1;rgbdata[i*6 + 3] = r2;rgbdata[i*6 + 4] = g2;rgbdata[i*6 + 5] = b2;}
}void lcd_show_rgb(unsigned char *rgbdata, int w, int h)
{unsigned int *ptr = lcdptr;for (int i = 0; i < h; i++) {for (int j = 0; j < w; j++) {memcpy(ptr + j, rgbdata + j*3, 3);}ptr += lcd_w;//偏移一行rgbdata += w*3;//偏移一行}
}int main(int argc,char *argv[])
{int ret,i;int fd;unsigned short *base;unsigned short *start;int min_w, min_h;int j;fb_dev_init();/* 步骤一,打开视频设备 */fd = open(argv[1], O_RDWR);if (fd < 0) {printf("file open error\n");return -1;}/* 步骤二,查询设备能力 *///查询设备的基本信息struct v4l2_capability cap; // 定义一个v4l2_capability结构体的变量cap// 使用ioctl函数发送VIDIOC_QUERYCAP命令来获取视频设备的基本信息,并将结果保存到cap变量中 if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) { perror("VIDIOC_QUERYCAP"); return -1; } //查看支持的图像格式、分辨率、帧率struct v4l2_fmtdesc fmtdesc = {0};//定义支持的像素格式结构体struct v4l2_frmsizeenum frmsize = {0};//定义支持的分辨率结构体struct v4l2_frmivalenum frmival = {0};//定义支持的帧率结构体fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//设置视频采集类型为 V4L2_BUF_TYPE_VIDEO_CAPTUREfmtdesc.index = 0;while (!ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))//获取支持的像素格式{strcpy(cam_fmts[fmtdesc.index].description, fmtdesc.description);cam_fmts[fmtdesc.index].pixelformat = fmtdesc.pixelformat;fmtdesc.index++;}frmsize.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;frmival.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;for(i=0;i<fmtdesc.index;i++)//枚举每一种像素格式{printf("description:%s\npixelformat:0x%x\n", cam_fmts[i].description,cam_fmts[i].pixelformat );frmsize.index = 0;frmsize.pixel_format = cam_fmts[i].pixelformat;frmival.pixel_format = cam_fmts[i].pixelformat;// 2.枚举出摄像头所支持的所有视频采集分辨率while (0 == ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize)) {printf("size<%d*%d> ",frmsize.discrete.width,frmsize.discrete.height);frmsize.index++;frmival.index = 0;frmival.width = frmsize.discrete.width;frmival.height = frmsize.discrete.height;// 3. 获取摄像头视频采集帧率while (0 == ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival)) {printf("<%dfps>", frmival.discrete.denominator/frmival.discrete.numerator);frmival.index++;}printf("\n");}printf("\n");}/*步骤三,设置采集参数,视频帧宽度、高度、格式、视频帧率等信息*/struct v4l2_format fmt = {0};fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//type 类型fmt.fmt.pix.width = video_width; //设置视频帧宽度fmt.fmt.pix.height = video_height;//设置视频帧高度fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //设置像素格式 if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {printf("ioctl error: VIDIOC_S_FMT\n");return -1;}struct v4l2_streamparm streamparm = {0};streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ioctl(fd, VIDIOC_G_PARM, &streamparm);if (V4L2_CAP_TIMEPERFRAME & streamparm.parm.capture.capability) {streamparm.parm.capture.timeperframe.numerator = 1;streamparm.parm.capture.timeperframe.denominator = 30;//30fpsif (0 > ioctl(fd, VIDIOC_S_PARM, &streamparm)) {printf("ioctl error: VIDIOC_S_PARM");return -1;}}/*步骤四,请求帧缓冲*/struct v4l2_requestbuffers reqbuffer;reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;reqbuffer.count = 4;//缓存数量reqbuffer.memory = V4L2_MEMORY_MMAP;//映射方式ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuffer);if (ret < 0) {printf("Request Queue space failed \n");return -1;}/*步骤五,映射帧缓冲*/struct v4l2_buffer mapbuffer;unsigned char *mptr[4];unsigned int size[4];//存储大小,方便释放mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;for (i = 0; i < 4; i++) {mapbuffer.index = i;ret = ioctl(fd, VIDIOC_QUERYBUF, &mapbuffer);if (ret < 0) {printf("Kernel space queue failed\n");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) {printf("ioctl error: VIDIOC_QBUF \n");return -1;}}/*步骤六,开启视频采集*/int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ret = ioctl(fd, VIDIOC_STREAMON, &type);if (ret < 0){printf("ioctl error: VIDIOC_STREAMON \n");return -1;}//步骤七,读取数据、对数据进行处理unsigned char rgbdata[video_width*video_height*3];while (1) {struct v4l2_buffer readbuffer;//出队列readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer);if (ret < 0) {printf("ioctl error:VIDIOC_DQBUF \n");}//显示在LCD上yuyv_to_rgb(mptr[readbuffer.index], rgbdata, video_width, video_height);lcd_show_rgb(rgbdata, video_width, video_height);//通知内核已经使用完毕,入队列ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);if (ret < 0) {printf("ioctl error:VIDIOC_QBUF \n");}}//步骤八,停止视频采集和释放资源ret = ioctl(fd, VIDIOC_STREAMOFF, &type);if (ret < 0) {printf("ioctl error:VIDIOC_STREAMOFF \n");return -1;}//释放映射空间for (i = 0; i < 4; i++) {munmap(mptr[i], size[i]);}close(fd);return 0;
}

上述代码中已经添加了相应的注释,本小节使用的测试屏幕为800*1280的MIPI屏幕所以第21行的LCD屏幕分辨率定义的为800*1280,如果使用的是屏幕设置成相应的分辨率即可,第22行的摄像头采集数据的分辨率也可以根据摄像头的格式来进行修改。最后由于我们采集到的数据为yuyv格式,需要转换为rgb类型的数据才可正常显示,转换函数为第56行的yuyv_to_rgb函数,具体的转换原理大家可以自行查找。

保存退出之后,使用以下命令设置交叉编译器环境,并对demo69_v4l2.c进行交叉编译,编译完成如下图所示:

export PATH=/usr/local/arm64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin:$PATH

aarch64-none-linux-gnu-gcc -o demo69_v4l2 demo69_v4l2.c

然后将交叉编译生成的demo71_v4l2文件拷贝到/home/nfs共享目录下,如下图所示: 

Buildroot系统启动之后,由于QT桌面会对显示信息造成干扰,所以需要使用以下命令将QT程序关闭:

 killall weston

然后使用以下命令进行nfs共享目录的挂载(其中192.168.1.7为作者ubuntu的ip地址,需要根据自身ubuntu的ip来设置),如下图所示:

mount -t nfs -o nfsvers=3,nolock 192.168.1.7:/home/nfs /mnt

nfs共享目录挂载到了开发板的/mnt目录下,进入到/mnt目录下,如下图所示: 

可以看到/mnt目录下demo69_v4l2文件已经存在了,然后使用以下命令运行该程序如下图所示:

./demo69_v4l2  /dev/video21

首先会打印USB摄像头的支持的图像格式、分辨率和帧率,最后会将摄像头采集到的图像显示到LCD液晶显示屏上。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/2429.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vben admin Table 实现表格列宽自由拖拽

更改BasicTable.vue文件 Table添加 resize-column“resizeColumn” 添加并 return resizeColumn const resizeColumn (w, col) > { setCacheColumnsByField(col.dataIndex, { width: w }); }; 在column中添加 resizable: true,

linux下编译c++程序报错“undefined reference to `std::allocator<char>::allocator()‘”

问题 linux下编译c程序报错“undefined reference to std::allocator::allocator()”。 原因 找不到c标准库文件。 解决办法 开始尝试给gcc指令添加-L和-l选项指定库路径和库文件名&#xff0c;但是一直不成功&#xff0c;后来把gcc改为g就可以了。

Rust - 引用和借用

上一篇章末尾提到&#xff0c;如果仅仅支持通过转移所有权的方式获取一个值&#xff0c;那会让程序变得复杂。 Rust 能否像其它编程语言一样&#xff0c;使用某个变量的指针或者引用呢&#xff1f;答案是可以。 Rust 通过 借用(Borrowing) 这个行为来达成上述的目的&#xff0…

李沐60_机器翻译数据集——自学笔记

!pip install d2limport os import torch from d2l import torch as d2l下载和预处理数据集 在这个将英语翻译成法语的机器翻译问题中&#xff0c; 英语是源语言&#xff08;source language&#xff09;&#xff0c; 法语是目标语言&#xff08;target language&#xff09;。…

【活动邀请·成都】成都 UG 生成式 AI 工作坊:AI 原生应用的探索与创新!

文章目录 前言一、活动介绍二、报名预约方式三、活动安排四、活动福利五、讲师介绍5.1、陈琪——《如何安全高效地构建生成式 AI 应用》5.2、刘文溢——《AIGC 的产业变革》5.3、胡荣亮——《生成式 AI 在企业应用与实践》5.4、陈明栋——《激发您的灵感&#xff0c;基于生成式…

Swing用法的简单展示

1.简单的登陆界面示例 import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener;public class Main extends JFrame {private JTextField usernameField;private JPasswordField passwordField;public Main() {setTitle("登陆界…

第26天:安全开发-PHP应用模版引用Smarty渲染MVC模型数据联动RCE安全

第二十六天 一、PHP新闻显示-数据库操作读取显示 1.新闻列表 数据库创建新闻存储代码连接数据库读取页面进行自定义显示 二、PHP模版引用-自写模版&Smarty渲染 1.自写模版引用 页面显示样式编排显示数据插入页面引用模版调用触发 2.Smarty模版引用 1.下载&#xff1a…

信创传输软件,如何进行国产化替代?

信创产业&#xff0c;即信息技术应用创新产业&#xff0c;它与“863 计划”“973 计划”“核高基” 一脉相承&#xff0c;是我国 IT 产业发展升级采取的长期计划。网络安全事件频发后&#xff0c;中国要确保 IT 相关设施的全部环节国产化&#xff0c;任何不能保证自主可控的环节…

服务器(AIX、Linux、UNIX)性能监视器工具【nmon】使用介绍

目录 ■nmon简介 1.安装 2.使用简介 3.使用&#xff08;具体使用的例子【CPU】【内存】&#xff09; 4.采集数据 5.查看log&#xff08;根据结果&#xff0c;生成报表&#xff09; 6.分析结果 ■nmon简介 nmon&#xff08;"Nigels performance Monitor"&…

终于有人说明白了session、cookie和token的区别

一、首先介绍一下名词&#xff1a;Session、cookie、token&#xff0c;如下&#xff1a; 1.Session会话&#xff1a;客户端A访问服务器&#xff0c;服务器存储A的数据value&#xff0c;把key返回给客户端A&#xff0c;客户端A下次带着key&#xff08;session ID&#xff09;来…

一文浅谈FRTC8563时钟芯片

FRTC8563是NYFEA徕飞公司推出的一款实时时钟芯片&#xff0c;采用SOP-8封装形式。这种封装形式具有体积小、引脚间距小、便于集成等特点&#xff0c;使得FRTC8563能够方便地应用于各种电子设备中。 FRTC8563芯片基于32.768kHz的晶体振荡器工作&#xff0c;这种频率的晶体振荡器…

JavaSE——程序逻辑控制

1. 顺序结构 顺序结构 比较简单&#xff0c;按照代码书写的顺序一行一行执行。 例如&#xff1a; public static void main(String[] args) {System.out.println(111);System.out.println(222);System.out.println(333);} 运行结果如下&#xff1a; 如果调整代码的书写顺序 , …

(ICML-2021)从自然语言监督中学习可迁移的视觉模型

从自然语言监督中学习可迁移的视觉模型 Title&#xff1a;Learning Transferable Visual Models From Natural Language Supervision paper是OpenAI发表在ICML 21的工作 paper链接 Abstract SOTA计算机视觉系统经过训练可以预测一组固定的预定目标类别。这种受限的监督形式限制…

服务器基本故障和排查方法

前言 服务器运维工作中遇到的问题形形色色&#xff0c;无论何种故障&#xff0c;都需要结合具体情况&#xff0c;预防为主的思想&#xff0c;熟悉各种工具和技术手段&#xff0c;养成良好的日志分析习惯&#xff0c;同时建立完善的应急预案和备份恢复策略&#xff0c;才能有效…

工业设备管理平台

在这个数字化、智能化的新时代&#xff0c;工业设备管理平台正成为推动工业转型升级的重要力量。在众多平台中&#xff0c;HiWoo Cloud以其卓越的性能、稳定的服务和创新的理念&#xff0c;赢得了广大用户的青睐。今天&#xff0c;就让我们一起走进HiWoo Cloud的世界&#xff0…

WebSocket的原理、作用、常见注解和生命周期的简单介绍,附带SpringBoot示例

文章目录 WebSocket是什么WebSocket的原理WebSocket的作用全双工和半双工客户端【浏览器】API服务端 【Java】APIWebSocket的生命周期WebSocket的常见注解SpringBoot简单代码示例 WebSocket是什么 WebSocket是一种 通信协议 &#xff0c;它在 客户端和服务器之间建立了一个双向…

123.Mit6.S081-实验1-Xv6 and Unix utilities

今天我们来进行Mit6.S081实验一的内容。 实验任务 一、启动xv6(难度&#xff1a;Easy) 获取实验室的xv6源代码并切换到util分支。 $ git clone git://g.csail.mit.edu/xv6-labs-2020 Cloning into xv6-labs-2020... ... $ cd xv6-labs-2020 $ git checkout util Branch util …

Go 堆内存分配源码解读

简要介绍 在Go的内存分配中存在几个关键结构&#xff0c;分别是page、mspan、mcache、mcentral、mheap&#xff0c;其中mheap中又包括heapArena&#xff0c;具体这些结构在内存分配中担任什么角色呢&#xff1f; 如下图&#xff0c;可以先看一下整体的结构&#xff1a; mcach…

Linux进程详解二:创建、状态、进程排队

文章目录 进程创建进程状态进程排队 进程创建 pid_t fork(void) 创建一个子进程成功将子进程的pid返回给父进程&#xff0c;0返回给新创建的子进程 fork之后有两个执行分支&#xff08;父和子&#xff09;&#xff0c;fork之后代码共享 bash -> 父 -> 子 创建一个进…

比特币成长的代价

作者&#xff1a;Jeffrey Tucker&#xff0c;作家和总裁。曾就经济、技术、社会哲学和文化等话题广泛发表演讲。编译&#xff1a;秦晋 2017 年之后参与比特币市场的人遇到了与之前的人不同的操作和理想。如今&#xff0c;没有人会太在意之前的事情&#xff0c;说的是 2010-2016…