网站已经开发怎样用微信实现手机网站开发,沈阳做网站怎样收费,设计师接单的网站,上海企业网站建设服务Video4Linux2#xff08;v4l2#xff09;是用于Linux系统的视频设备驱动框架#xff0c;它允许用户空间应用程序直接与视频设备#xff08;如摄像头、视频采集卡等#xff09;进行交互。 linux系统下一切皆文件#xff0c;对视频设备的操作就像对文件的操作一样#xff… Video4Linux2v4l2是用于Linux系统的视频设备驱动框架它允许用户空间应用程序直接与视频设备如摄像头、视频采集卡等进行交互。 linux系统下一切皆文件对视频设备的操作就像对文件的操作一样使用类似读取、写入文件的方式来进行v4l2也都是通过open()、ioctl()、read()、close()来实现对视频设备的操作。 以下是使用v4l2获取视频流的一般流程 1、打开设备 首先应用程序需要打开要使用的视频设备。通常这可以通过调用open()系统调用来完成传递设备文件的路径作为参数。例如摄像头通常会以/dev/videoX的形式出现其中X是数字。 // O_NONBLOCK以非阻塞方式打开
fd open(/dev/video0, O_RDWR | O_NONBLOCK, 0); 2、查询设备能力、设置视频格式 一旦设备打开应用程序通常会查询设备的能力例如支持的视频格式、分辨率、帧率等信息。这可以通过调用ioctl()系统调用来执行VIDIOC_QUERYCAP操作来完成。根据设备的能力和应用程序的需求可以设置所需的视频格式。这包括像素格式、分辨率、帧率等。通常可以使用VIDIOC_S_FMT操作来设置视频格式。 ioctl第二个参数表示命令类型第三个参数是变参不同请求命令对应不同的参数
int ioctl(int fd, unsigned long request, ...); v4l2请求命令和请求参数在/usr/include/linux/videodev2.h里面定义 查询设备信息
/** 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) {perror(VIDIOC_QUERYCAP);return -1;
} 查询摄像头支持的格式
/** 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;
} 设置采集的视频格式
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;
} struct v4l2_format是v4l2中一个很重要的结构体用于视频格式设置定义如下
struct v4l2_format {__u32 type;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 */struct v4l2_sdr_format sdr; /* V4L2_BUF_TYPE_SDR_CAPTURE */struct v4l2_meta_format meta; /* V4L2_BUF_TYPE_META_CAPTURE */__u8 raw_data[200]; /* user-defined */} fmt;
};type是枚举类型enum v4l2_buf_type用于表示数据流格式定义如下
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, // 私有类型用于自定义和扩展目的
}; v4l2_format中的fmt是联合体不同的type使用不用类型的结构体V4L2_BUF_TYPE_VIDEO_CAPTURE使用v4l2_pix_format。定义如下
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
}; 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, YCbCr 编码__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) {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;
}; 当是多平面是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) {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);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); 9、完整代码 代码流程图如下 保存的视频用yuvplayer播放。
#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);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;
}