【北京迅为】《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://xiahunao.cn/news/2980428.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

企商在线亮相2024中国生成式AI大会,展出多元异构算力服务

4月18—19日&#xff0c;由知名媒体机构智东西与智猩猩共同主办的2024中国生成式AI大会在北京举行&#xff0c;55位重量级产学研投界代表同台分享。企商在线作为算力行业代表企业&#xff0c;参展生成式AI展区&#xff0c;现场展出企商在线AI算力平台及异构算力服务。 大会以“…

三分钟快速理解Flink 作业提交流程(包工头的工程之路)

核心组件 我们先来简单了解一下 flink 作业提交涉及到的组件 同时&#xff0c;如果不了解 Yarn 的同学欢迎跳转到这篇文章&#xff0c;了解一下健鑫集团的工程承包流程(doge): 三分钟快速理解Yarn的工作流程 JobManager JobManager 是整个flink作业的管理者 包含 Dispatch…

“PowerInfer:消费级GPU上的高效大型语言模型推理引擎“

PowerInfer是由上海交通大学IPADS实验室开发的一个高效大型语言模型&#xff08;LLM&#xff09;推理引擎&#xff0c;专为个人电脑&#xff08;PC&#xff09;上的消费者级GPU设计。它通过利用LLM推理中的高局部性&#xff0c;实现了快速且资源消耗低的模型推理&#xff0c;这…

深入探究图像增强(C语言实现)

我们将从基础出发使用C语言进行图像处理与分析&#xff0c;重点讨论图像增强和平滑技术。图像增强技术旨在通过增加对比度、亮度和整体清晰度来改善图像的视觉质量。另一方面&#xff0c;图像平滑方法则用于减少噪声并减少图像中的突变&#xff0c;使图像更加均匀和视觉上吸引人…

Github Copilot正版的激活成功,终于可以chat了

Github Copilot 代码补全等功能&#xff0c;提高写代码的效率 https://web.52shizhan.cn/activity/copilot 登录授权后&#xff0c;已经可以使用&#xff0c;完美。如图

OpenFE:开启数据特征工程新时代

OpenFE&#xff1a;开启数据特征工程新时代 数据特征工程是机器学习和数据分析领域中至关重要的一环&#xff0c;它涉及对原始数据进行处理和转换&#xff0c;以提取出有用的特征&#xff0c;为模型构建和预测提供更好的输入。在这个领域中&#xff0c;Python库OpenFE为数据科学…

查找两个字符串的最长公共子串

暴力解法 #include <iostream> #include <vector> #include <cstring> using namespace std; string a, b, minn ""; // a和b是我们输入的 // minn存储的是我们最小的那个字符串string cut(int l, int r) {string tmp "";for (int i …

大小端解释以及如何使用程序判断IDE的存储模式

今天让我们来了解一下大小端的概念吧 什么是大小端&#xff1f; 大端&#xff08;存储&#xff09;模式&#xff1a;指的是数据的低位保存在内存的高地址处&#xff0c;而数据的高位则保存在内存的低地址处。 小端&#xff08;存储&#xff09;模式&#xff1a;指的是数据的低位…

Discuz! X系列版本安装包

源码下载地址&#xff1a;Discuz! X系列版本安装包 很多新老站长跟我说要找Discuz! X以前的版本安装包&#xff0c;我们做Discuz! X开发已经十几年了&#xff0c;这些都是官方原版安装包&#xff0c;方便大家使用&#xff08;在官网已经找不到这些版本的安装包了&#xff09; …

新网站上线需要注意什么?

质量保证&#xff1a;确保网站的所有功能和页面都经过了充分的测试&#xff0c;并且在各种不同的浏览器和设备上都能够正常运行。检查所有链接、表单和交互式元素&#xff0c;确保它们都能够按照预期工作。优化性能&#xff1a;确保网站加载速度快&#xff0c;响应迅速。优化图…

详细UI色彩搭配方案分享

UI 配色是设计一个成功的用户界面的关键之一。UI 配色需要考虑品牌标志、用户感受、应用程序的使用场景&#xff0c;这样可以帮助你创建一个有吸引力、易于使用的应用程序。本文将分享 UI 配色的相关知识&#xff0c;帮助设计师快速构建 UI 配色方案&#xff0c;以满足企业的需…

环回光模块

&#x1f44f;&#x1f4cd;环回光模块&#xff08;Lookback&#xff09;&#xff0c;也称为光模块自环测试回路器&#xff0c;用于测试系统或网络中的信号回传。通过回传信号&#xff08;主要是成对连接发射端到接收端的一侧&#xff09;&#xff0c;可以检测网络链路中各种潜…

文件上传的复习(upload-labs1-5关)

什么是文件上传漏洞&#xff1f; 文件上传本身是一个正常的业务需求&#xff0c;对于网站来说&#xff0c;很多时候也确实需要用户将文件上传到服务器&#xff0c;比如&#xff1a;上传图片&#xff0c;资料。 文件上传漏洞不仅涉及上传漏洞这个行为&#xff0c;还涉及文件上…

安卓手机投屏到电脑:实现屏幕共享的实用指南

“吃饭的时候觉得手机看剧实在是太费眼睛了&#xff0c;终于经过一番摸索、试验&#xff0c;我探索出了新大陆&#xff01;只要将安卓手机投屏到电脑&#xff0c;就可以放大画面&#xff0c;还能同步操作&#xff0c;远离屏幕的同时还能够看清视频&#xff01;这些方法太实用啦…

JS -正则表达式

正则表达式 关于正则表达式&#xff0c;其实我写过几篇了&#xff0c;但是真正的正则表达式其实主要用于定义一些字符串的规则&#xff0c;计算机根据给出的正则表达式&#xff0c;来检查一个字符串是否符合规则。 我们来看一下&#xff0c;在JS中如何创建正则表达式对象。 语…

公链系统开发全指南: 从规划到实施

在区块链技术的迅速发展和应用推广下&#xff0c;公链系统的开发成为了当前数字资产领域的热门话题。从规划到实施&#xff0c;公链系统的开发过程需要经历多个步骤&#xff0c;下文将详细介绍每个步骤。 第一步: 规划和设计 市场调研: 分析市场需求和竞争情况&#xff0c;确定…

Power BI 如何创建页面导航器?(添加目录按钮/切换页面按钮)

Power BI 中页导航是什么&#xff1f; 在Power BI中&#xff0c;页导航&#xff08;Page Navigation&#xff09;是指在报告中创建多个页面&#xff08;页&#xff09;&#xff0c;然后允许用户在这些页面之间进行导航的功能。 如下图所示&#xff0c;页导航的选项和报告中的…

多模态模型

转换器成功作为构建语言模型的一种方法&#xff0c;促使 AI 研究人员考虑同样的方法是否对图像数据也有效。 研究结果是开发多模态模型&#xff0c;其中模型使用大量带有描述文字的图像进行训练&#xff0c;没有固定的标签。 图像编码器基于像素值从图像中提取特征&#xff0c;…

调度问题变形的贪心算法分析与实现

调度问题变形的贪心算法分析与实现 一、问题背景与算法描述二、算法正确性证明三、算法实现与分析四、结论 一、问题背景与算法描述 带截止时间和惩罚的单位时间任务调度问题是一个典型的贪心算法应用场景。该问题的目标是最小化超过截止时间导致的惩罚总和。给定一组单位时间…

基于51单片机的数码管显示的proteus仿真

文章目录 一、数码管二、单个数码管显示0~F仿真图仿真程序 三、数码管静态显示74HC138译码器74HC245缓冲器仿真图仿真程序 四、数码管动态显示仿真图仿真程序 三、总结 一、数码管 数码管&#xff0c;也称作辉光管&#xff0c;是一种可以显示数字和其他信息的电子设备。它的基…