Linux驱动,之camera的驱动v4L2(video for linux two)框架,应用篇(一)
https://blog.csdn.net/mabin2005/category_9258316.htmlhttps://blog.csdn.net/MENGHUANBEIKE/article/details/102795635https://blog.csdn.net/m0_72838865/article/details/127012187
一、前言
在前一篇,我会总结,记录工作过程中,调试过的各种接口(rgb,hdmi,cdi,mipi,usb)的摄像头,各种接口的时序图,接口总线。
本篇将下应用层v4L2(video for linux two),采集图像的框架,很早就像写了,以前多次研究过,很久不用,老是忘记。
其实v4L2和海思的音频芯片35xx,sdk音视频采集非常像,海思对他进行了封装,流程差不多。
应用层v4L2(video for linux two)其实非常简单,不像底层的v4L2驱动框架,那么复杂,应用层,就是api,设置摄像头设备
的参数(码率,分辨率,有的会设置编码格式,有的摄像头是不支持的,输出的是raw数据,帧率等等。),然后mmap映射
dma分配一块内存地址映射(这块和Lcd,codex,网络有点像,通过dma来搬运数据,而不是cpu),然后采集数据,送到lcd显示
或者送到编码模块,发送网络。
Video4Linux2(v4l2)是用于Linux系统的视频设备驱动框架,它允许用户空间应用程序直接与视频设备(如摄像头、视频采集卡等)进行交互。
linux系统下一切皆文件,对视频设备的操作就像对文件的操作一样,使用类似读取、写入文件的方式来进行,v4l2也都是通过open()、ioctl()、read()、close(),字符设备那一套规则,来实现对视频设备的操作。
感谢原文博主热心分享,他写的非常好了。
原文链接:https://blog.csdn.net/weixin_43147845/article/details/136899272
一、v4L2采集摄像头流程
梳理大概得流程和设置
摄像头框架编程步骤
(1)打开摄像头设备(/dev/video0 、/dev/video1 )。
(2)设置图像格式:VIDIOC_S_FMT(视频捕获格式、图像颜色数据格式、图像宽和高)。
(3)申请缓冲区:VIDIOC_REQBUFS(缓冲区数量、缓冲映射方式、视频捕获格式)。
(4)将缓冲区映射到进程空间:VIDIOC_QUERYBUF(要映射的缓冲区下标、缓冲映射方式、视频捕获格式)。
(5)将缓冲区添加到队列中:VIDIOC_QBUF(映射的缓冲区下标、缓冲映射方式、视频捕获格式)。
(6)开启摄像头采集:VIDIOC_STREAMON (视频捕获格式)。
(7)从采集队列中取出图像数据VIDIOC_DQBUF,进行图像渲染。
1、打开设备:
首先,应用程序需要打开要使用的视频设备。通常,这可以通过调用open()系统调用来完成,传递设备文件的路径作为参数。例如,摄像头通常会以/dev/videoX的形式出现,其中X是数字。
O_NONBLOCK以非阻塞方式打开
fd = open("/dev/video0", O_RDWR | O_NONBLOCK, 0);
2、查询设备能力、设置视频格式:
一旦设备打开,应用程序通常会查询设备的能力,例如支持的视频格式、分辨率、帧率等信息。这可以通过调用ioctl()系统调用来执行VIDIOC_QUERYCAP操作来完成。根据设备的能力和应用程序的需求,可以设置所需的视频格式。这包括像素格式、分辨率、帧率等。通常,可以使用VIDIOC_S_FMT操作来设置视频格式。
int ioctl(int fd, unsigned long request, ...);
v4l2请求命令和请求参数在/usr/include/linux/videodev2.h里面定义
2.1、查询设备信息:
linux定义了如下一个结构体
/*
* struct v4l2_capability {
* __u8 driver[16]; // 驱动模块的名称(例如 "bttv")
* __u8 card[32]; // 设备的名称(例如 "Hauppauge WinTV")
* __u8 bus_info[32]; // 总线的名称(例如 "PCI:" + pci_name(pci_dev))
* __u32 version; // KERNEL_VERSION
* __u32 capabilities; // 整个物理设备的功能
* __u32 device_caps; // 通过此特定设备(节点)访问的功能
* __u32 reserved[3]; // 保留字段,用于未来扩展
* };
*/
struct v4l2_capability cap;
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) { //调用ioctl函数,VIDIOC_QUERYCAP查询信息,返回到cap的结构体里
perror("VIDIOC_QUERYCAP");
return -1;
}
2.2、 查询摄像头支持的格式:
/*
* struct v4l2_fmtdesc {
* __u32 index; // 格式编号,摄像头可能支持多种格式,查看的时候需要指定格式编号,循环遍历获取所有支持的格式
* __u32 type; // 枚举 v4l2_buf_type
* __u32 flags;
* __u8 description[32]; // 描述字符串
* __u32 pixelformat; // 格式 FourCC,通常由四个ASCII字符组成,用于唯一地标识特定的数据格式或编码方式
* __u32 reserved[4];
* };
*/
struct v4l2_fmtdesc fmtdesc;
fmtdesc.index = 0;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
while (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) != -1) {
printf("\t%d.%s\n", fmtdesc.index + 1, fmtdesc.description);
fmtdesc.index++;
}
2.3、 设置采集的视频格式:
struct v4l2_pix_format
struct v4l2_pix_format {
__u32 width; //图像宽度
__u32 height; //图像高度
__u32 pixelformat; //图像数据格式
__u32 field; /*enum v4l2_field */
__u32 bytesperline; /*for padding, zero if unused */
__u32 sizeimage;
__u32 colorspace; /*enum v4l2_colorspace*/
__u32 priv; /*private data, depends on pixelformat */
};
struct v4l2_format
struct v4l2_format {
__u32 type; /* 类型V4L2_BUF_TYPE_VIDEO_CAPTURE*/
union {
struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE视频捕获格式*/
struct v4l2_pix_format_mplane pix_mp; /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE /
struct v4l2_window win; / V4L2_BUF_TYPE_VIDEO_OVERLAY /
struct v4l2_vbi_format vbi; / V4L2_BUF_TYPE_VBI_CAPTURE /
struct v4l2_sliced_vbi_format sliced; / V4L2_BUF_TYPE_SLICED_VBI_CAPTURE /
__u8 raw_data[200]; / user-defined */
} fmt;
};
struct v4l2_format中的, type是枚举类型,enum v4l2_buf_type用于表示数据流格式,
定义如下:fmt是枚举体,根据类型,选择设置对应的结构体。
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, // 垂直空白间隔(VBI)捕获类型,用于捕获VBI数据
V4L2_BUF_TYPE_VBI_OUTPUT = 5, // VBI输出类型,用于输出VBI数据
V4L2_BUF_TYPE_SLICED_VBI_CAPTURE = 6, // 切片VBI捕获类型,用于捕获切片VBI数据
V4L2_BUF_TYPE_SLICED_VBI_OUTPUT = 7, // 切片VBI输出类型,用于输出切片VBI数据
V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8, // 视频输出叠加类型,用于输出叠加图像或视频
V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE = 9, // 多平面视频捕获类型,用于从多平面视频设备捕获图像数据(视频编解码)
V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE = 10, // 多平面视频输出类型,用于将图像数据输出到多平面视频设备(视频编解码)
V4L2_BUF_TYPE_SDR_CAPTURE = 11, // SDR捕获类型,用于从SDR设备捕获数据
V4L2_BUF_TYPE_SDR_OUTPUT = 12, // SDR输出类型,用于将数据输出到SDR设备
V4L2_BUF_TYPE_META_CAPTURE = 13, // 元数据捕获类型,用于从设备捕获元数据
/* 已废弃,请勿使用 */
V4L2_BUF_TYPE_PRIVATE = 0x80, // 私有类型,用于自定义和扩展目的
};
看下这个像素格式结构体
struct v4l2_pix_format {
__u32 width; // 图像宽度
__u32 height; // 图像高度
__u32 pixelformat; // 像素格式,使用 FOURCC 表示
__u32 field; // 视频场类型,枚举 v4l2_field
__u32 bytesperline; // 每行字节数,用于填充,如果未使用则为零
__u32 sizeimage; // 图像数据大小
__u32 colorspace; // 颜色空间,枚举 v4l2_colorspace
__u32 priv; // 私有数据,依赖于像素格式
__u32 flags; // 格式标志(V4L2_PIX_FMT_FLAG_*)
union {
// YCbCr 编码
__u32 ycbcr_enc;
// HSV 编码
__u32 hsv_enc;
};
__u32 quantization; // 量化方式,枚举 v4l2_quantization
__u32 xfer_func; // 传输函数,枚举 v4l2_xfer_func
};
例如、填充结构体,设置采集参数
struct v4l2_format fmt;
CLEAN(fmt);
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = WIDTH;
fmt.fmt.pix.height = HEIGHT;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
printf("VIDIOC_S_FMT IS ERROR! LINE:%d\n", __LINE__);
//return -1;
}
如果设置类型type,选择的是,V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,。则使用struct v4l2_pix_format_mplane,一般用于v4l2视频编解码中,定义如下:
struct v4l2_pix_format_mplane {
__u32 width; // 图像宽度
__u32 height; // 图像高度
__u32 pixelformat; // 图像像素格式 小端四字符代码(FourCC)
__u32 field; // 图像字段顺序 enum v4l2_field; 字段顺序(用于隔行扫描视频)
__u32 colorspace; // 图像色彩空间 enum v4l2_colorspace; 与 pixelformat 相关的补充信息
struct v4l2_plane_pix_format plane_fmt[VIDEO_MAX_PLANES]; // 平面格式数组 每个平面的信息
__u8 num_planes; // 平面数量 此格式的平面数量
__u8 flags; // 格式标志(V4L2_PIX_FMT_FLAG_*)
union {
__u8 ycbcr_enc; // enum v4l2_ycbcr_encoding, Y'CbCr 编码
__u8 hsv_enc; // enum v4l2_quantization, 色彩空间量化
};
__u8 quantization; // 色彩空间量化
__u8 xfer_func; // enum v4l2_xfer_func, 色彩空间传输函数
__u8 reserved[7]; // 保留字段
} __attribute__ ((packed));
struct v4l2_plane_pix_format {
__u32 sizeimage; // 用于此平面的数据所需的最大字节数
__u32 bytesperline; // 相邻两行中最左侧像素之间的字节距离
__u16 reserved[6]; // 保留字段
} __attribute__ ((packed));
3、请求和分配缓冲区:
应用程序需要请求并分配用于存储视频数据的缓冲区。这可以通过调用VIDIOC_REQBUFS操作来请求缓冲区,并使用VIDIOC_QUERYBUF操作来获取每个缓冲区的详细信息。然后,应用程序将缓冲区映射到当前进程空间。
向内核申请多个缓冲区:
/*
*struct v4l2_requestbuffers {
* __u32 count; // 请求的缓冲区数量
* __u32 type; // 数据流类型,枚举 v4l2_buf_type
* __u32 memory; // 缓冲区的内存类型,枚举 v4l2_memory
* __u32 reserved[2]; /* 保留字段,用于未来扩展
*};
*/
struct v4l2_requestbuffers req;
CLEAN(req);
req.count = 4;
req.memory = V4L2_MEMORY_MMAP; // 使用内存映射缓冲区
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
// 申请4个帧缓冲区,在内核空间中
if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
printf("VIDIOC_REQBUFS IS ERROR! LINE:%d\n", __LINE__);
return -1;
}
申请后,就可以把内核缓冲区映射到当前进程空间,这样进程就可以直接读写这个地址的数据,之后缓冲区入内核队列,准备采集视频:
/*
* typedef struct BufferSt {
* void *start;
* unsigned int length;
* } BufferSt;
*/
buffer = (BufferSt *)calloc(req.count, sizeof(BufferSt));
if (buffer == NULL) {
printf("calloc is error! LINE:%d\n", __LINE__);
return -1;
}
struct v4l2_buffer buf;
int buf_index = 0;
for (buf_index = 0; buf_index < req.count; buf_index++) //前面申请了4个缓冲区
{
CLEAN(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.index = buf_index;
buf.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) // 获取每个帧缓冲区的信息 如length和offset
{
printf("VIDIOC_QUERYBUF IS ERROR! LINE:%d\n", __LINE__);
return -1;
}
// 将内核空间中的帧缓冲区映射到用户空间
buffer[buf_index].length = buf.length;
buffer[buf_index].start = mmap(NULL, // 由内核分配映射的起始地址
buf.length, // 长度
PROT_READ | PROT_WRITE, // 可读写
MAP_SHARED, // 可共享
fd,
buf.m.offset);
if (buffer[buf_index].start == MAP_FAILED) {
printf("MAP_FAILED LINE:%d\n", __LINE__);
return -1;
}
// 将帧缓冲区放入视频输入队列
if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
printf("VIDIOC_QBUF IS ERROR! LINE:%d\n", __LINE__);
return -1;
}
printf("Frame buffer :%d address :0x%x length:%d\n", buf_index, (__u32)buffer[buf_index].start, buffer[buf_index].length);
}
struct v4l2_buffer 是v4l2中另一个重要的结构体,用来定义缓冲区:
struct v4l2_buffer {
__u32 index; // 缓冲区的ID号
__u32 type; // 枚举 v4l2_buf_type; 缓冲区类型(type == *_MPLANE 表示多平面缓冲区)
__u32 bytesused; // 枚举 v4l2_field; 缓冲区中图像的场序
__u32 field; // 缓冲区中图像的场序
struct timeval timestamp; // 帧时间戳
struct v4l2_timecode timecode; // 帧时间码
__u32 sequence; // 本帧的序列计数
/* 内存位置 */
__u32 memory; // 枚举 v4l2_memory; 传递实际视频数据的方法
union {
__u32 offset; // 对于 memory == V4L2_MEMORY_MMAP 的非多平面缓冲区;从设备内存开始的偏移量
unsigned long userptr; // 对于 memory == V4L2_MEMORY_USERPTR 的非多平面缓冲区;指向该缓冲区的用户空间指针
struct v4l2_plane *planes; // 对于多平面缓冲区;指向该缓冲区的平面信息结构数组的用户空间指针
__s32 fd; // 对于 memory == V4L2_MEMORY_DMABUF 的非多平面缓冲区;与该缓冲区相关联的用户空间文件描述符
} m;
__u32 length; // 单平面缓冲区的缓冲区大小(而不是有效载荷)的字节数(当 type != *_MPLANE 时)
//对于多平面缓冲区,表示平面数组中的元素数(当 type == *_MPLANE 时)
__u32 reserved2;
__u32 reserved;
};
做过视频的都知道,yuv有分多种格式存放,有plane
当是多平面是,v4l2_buffer的m使用struct v4l2_plane,这个一般在v4l2视频编解码中使用,用于存储原始视频的Y U V分量,定义如下:
struct v4l2_plane {
__u32 bytesused; // 平面中数据所占用的字节数(有效载荷)
__u32 length; // 该平面的大小(而不是有效载荷)的字节数
union {
__u32 mem_offset; // 当 memory 为 V4L2_MEMORY_MMAP 时,从设备内存开始的偏移量
unsigned long userptr; // 当 memory 为 V4L2_MEMORY_USERPTR 时,指向该平面的用户空间指针
__s32 fd; // 当 memory 为 V4L2_MEMORY_DMABUF 时,与该平面相关联的用户空间文件描述符
} m;
__u32 data_offset; // 平面中数据开始的偏移量
__u32 reserved[11];
};
4、开始捕获视频:
一旦缓冲区准备就绪,应用程序可以调用 VIDIOC_STREAMON 操作来开始捕获视频流。此时,设备将开始向分配的缓冲区写入视频数据。
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMON, &type) == -1) {
printf("VIDIOC_STREAMON IS ERROR! LINE:%d\n", __LINE__);
exit(1);
}
5、获取和处理视频帧:
应用程序可以轮询或使用异步IO等机制从缓冲区中获取视频帧数据。获取数据后,应用程序可以对视频帧进行处理,例如显示、存储或传输等操作。
static int read_frame()
{
struct v4l2_buffer buf;
int ret = 0;
CLEAN(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) { //查询一下是否有数据,有数据,就来读,和hisi35xx一样的,做法,我怀疑,hisi就是封装了v4l2
printf("VIDIOC_DQBUF! LINEL:%d\n", __LINE__);
return -1;
}
ret = write(out_fd, buffer[buf.index].start, buf.bytesused);
if (ret == -1) {
printf("write is error !\n");
return -1;
}
if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
printf("VIDIOC_QBUF! LINE:%d\n", __LINE__);
return -1;
}
return 0;
}
static int capture_frame()
{
struct timeval tvptr;
int ret;
tvptr.tv_usec = 0;
tvptr.tv_sec = 2;
fd_set fdread;
FD_ZERO(&fdread);
FD_SET(fd, &fdread);
ret = select(fd + 1, &fdread, NULL, NULL, &tvptr);//采用异步的方式,来读写,有数据,就过来读下队列,和alsa一样的,做过音视频也这样玩
if (ret == -1) {
perror("select");
exit(1);
}
if (ret == 0) {
printf("timeout! \n");
return -1;
}
read_frame();
}
过程为:从内核队列中取出准备好buf、从buf映射到进程空间的内存中读取视频数据、buf重新送入内核缓冲队列中,因为open的时候使用的非阻塞IO,所以这里使用select监听。
6、停止捕获视频:
当视频采集完成时,应用程序可以调用VIDIOC_STREAMOFF操作来停止视频捕获。
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMOFF, &type) == -1) {
printf("VIDIOC_STREAMOFF IS ERROR! LINE:%d\n", __LINE__);
exit(1);
}
7、解除缓冲区内存映射:
结束内核缓冲区到当前进程空间的内存映射
int i = 0;
for (i = 0; i < 4; i++) {
munmap(buffer[i].start, buffer[i].length);
}
free(buffer);
8、关闭设备:
最后,应用程序应该关闭视频设备,释放所有相关的资源。这可以通过调用close()系统调用来完成。
close(fd);
下面我们先看下一段v4l2的代码,网络上有非常多关于这个解释。这个博主,是把摄像头数据流,保存为yuv文件。
我做hisi平台的音视频时,是需要发送到网络的,将采集写成一个线程,写到应用层,自己定义的一个循环队列里,写另一个线程,负责网络发送,不可能合在一起的,发送可能比较费时,如果读一帧,发一帧,就会有延时。
#include <asm/types.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <linux/fb.h>
#include <linux/videodev2.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#define CLEAN(x) (memset(&(x), 0, sizeof(x)))
#define WIDTH 640
#define HEIGHT 480
typedef struct BufferSt {
void *start;
unsigned int length;
} BufferSt;
int fd;
int out_fd;
static BufferSt *buffer = NULL;
static int query_set_format()
{
// 查询设备信息
struct v4l2_capability cap;
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) {
perror("VIDIOC_QUERYCAP");
return -1;
}
printf("DriverName:%s\nCard Name:%s\nBus info:%s\nDriverVersion:%u.%u.%u\n",
cap.driver, cap.card, cap.bus_info, (cap.version >> 16) & 0xFF, (cap.version >> 8) & 0xFF, (cap.version) & 0xFF);
// 查询帧格式
struct v4l2_fmtdesc fmtdesc;
fmtdesc.index = 0;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
while (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) != -1) {
printf("\t%d.%s\n", fmtdesc.index + 1, fmtdesc.description);
fmtdesc.index++;
}
// 设置帧格式
struct v4l2_format fmt;
CLEAN(fmt);
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = WIDTH;
fmt.fmt.pix.height = HEIGHT;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
printf("VIDIOC_S_FMT IS ERROR! LINE:%d\n", __LINE__);
//return -1;
}
// 上面设置帧格式可能失败,这里需要查看一下实际帧格式
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_G_FMT, &fmt) == -1) {
printf("VIDIOC_G_FMT IS ERROR! LINE:%d\n", __LINE__);
return -1;
}
printf("width:%d\nheight:%d\npixelformat:%c%c%c%c\n",
fmt.fmt.pix.width, fmt.fmt.pix.height,
fmt.fmt.pix.pixelformat & 0xFF,
(fmt.fmt.pix.pixelformat >> 8) & 0xFF,
(fmt.fmt.pix.pixelformat >> 16) & 0xFF,
(fmt.fmt.pix.pixelformat >> 24) & 0xFF);
return 0;
}
static int request_allocate_buffers()
{
// 申请帧缓冲区
struct v4l2_requestbuffers req;
CLEAN(req);
req.count = 4;
req.memory = V4L2_MEMORY_MMAP; // 使用内存映射缓冲区
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
// 申请4个帧缓冲区,在内核空间中
if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
printf("VIDIOC_REQBUFS IS ERROR! LINE:%d\n", __LINE__);
return -1;
}
// 获取每个帧信息,并映射到用户空间
buffer = (BufferSt *)calloc(req.count, sizeof(BufferSt));
if (buffer == NULL) {
printf("calloc is error! LINE:%d\n", __LINE__);
return -1;
}
struct v4l2_buffer buf;
int buf_index = 0;
for (buf_index = 0; buf_index < req.count; buf_index++) {
CLEAN(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.index = buf_index;
buf.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) // 获取每个帧缓冲区的信息 如length和offset
{
printf("VIDIOC_QUERYBUF IS ERROR! LINE:%d\n", __LINE__);
return -1;
}
// 将内核空间中的帧缓冲区映射到用户空间
buffer[buf_index].length = buf.length;
buffer[buf_index].start = mmap(NULL, // 由内核分配映射的起始地址
buf.length, // 长度
PROT_READ | PROT_WRITE, // 可读写
MAP_SHARED, // 可共享
fd,
buf.m.offset);
if (buffer[buf_index].start == MAP_FAILED) {
printf("MAP_FAILED LINE:%d\n", __LINE__);
return -1;
}
// 将帧缓冲区放入视频输入队列
if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
printf("VIDIOC_QBUF IS ERROR! LINE:%d\n", __LINE__);
return -1;
}
printf("Frame buffer :%d address :0x%x length:%d\n", buf_index, (__u32)buffer[buf_index].start
, buffer[buf_index].length);
}
}
static void start_capture()
{
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMON, &type) == -1) {
printf("VIDIOC_STREAMON IS ERROR! LINE:%d\n", __LINE__);
exit(1);
}
}
static void end_capture()
{
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMOFF, &type) == -1) {
printf("VIDIOC_STREAMOFF IS ERROR! LINE:%d\n", __LINE__);
exit(1);
}
}
static int read_frame()
{
struct v4l2_buffer buf;
int ret = 0;
CLEAN(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) {
printf("VIDIOC_DQBUF! LINEL:%d\n", __LINE__);
return -1;
}
ret = write(out_fd, buffer[buf.index].start, buf.bytesused);
if (ret == -1) {
printf("write is error !\n");
return -1;
}
if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
printf("VIDIOC_QBUF! LINE:%d\n", __LINE__);
return -1;
}
return 0;
}
static void unmap_buffer()
{
int i = 0;
for (i = 0; i < 4; i++) {
munmap(buffer[i].start, buffer[i].length);
}
free(buffer);
}
static int capture_frame()
{
struct timeval tvptr;
int ret;
tvptr.tv_usec = 0;
tvptr.tv_sec = 2;
fd_set fdread;
FD_ZERO(&fdread);
FD_SET(fd, &fdread);
ret = select(fd + 1, &fdread, NULL, NULL, &tvptr);
if (ret == -1) {
perror("select");
exit(1);
}
if (ret == 0) {
printf("timeout! \n");
return -1;
}
read_frame();
}
int main(int argc, char *argv[])
{
// 1、打开摄像头
fd = open("/dev/video0", O_RDWR | O_NONBLOCK, 0);
if (fd == -1) {
printf("can not open '%s'\n", "/dev/video0");
return -1;
}
out_fd = open("./out.yuv", O_RDWR | O_CREAT, 0777);//保存采集的数据为yuv文件。
if (out_fd == -1) {
printf("open out file is error!\n");
return -1;
}
// 2、查询设备、设置视频格式
query_set_format();
// 3、请求和分配缓冲区
request_allocate_buffers();
// 4 、开始捕获视频
start_capture();
// 5、获取和处理视频帧
for (int i = 0; i < 20; i++) {
capture_frame();
printf("frame:%d\n", i);
}
// 6、停止捕获视频
end_capture();
// 7、解除缓冲区内存映射
unmap_buffer();
// 8、关闭摄像头
close(fd);
close(out_fd);
return 0;
}
也可以通过FFmpeg来直接采集摄像头数据。我在做qt实验的时候,也写过V4L2的测试,非常好用。

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐
所有评论(0)