v4l2采集视频

        Video4Linux2(v4l2)是用于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, 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++) {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;
}

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

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

相关文章

TinyEMU源码分析之启动流程

TinyEMU源码分析之启动流程 1 始于0x10002 确定BBL入口点3 mentry.S执行过程4 启动流程小结 本文属于《 TinyEMU模拟器基础系列教程》之一&#xff0c;欢迎查看其它文章。 本文中使用的代码&#xff0c;均为伪代码&#xff0c;删除了部分源码。 1 始于0x1000 我们沿着TinyEMU…

Java 学习和实践笔记(48):怎样用二维数组来存储表格数据?

怎样用数组的方式&#xff0c;来存储下面这个表格的数据&#xff1f; 示例代码如下&#xff1a; import java.util.Arrays;public class Test001 {public static void main(String[] args) {/*object类对象是类层次结构的根。每个类都有Object作为超类。所有对象&#xff0c;包…

第17篇:七段数码管译码器

Q&#xff1a;之前我们设计实现的逻辑电路最终输出结果都是通过LED显示&#xff0c;本篇我们将实现用七段数码管来显示输出结果。 A&#xff1a;七段数码管显示基本原理&#xff1a;DE2-115开发板有8个共阳极数码管&#xff0c;即低电平逻辑值0点亮数码管段、逻辑值1来使数码管…

Docker Stack(堆栈) 部署多服务集群,多服务编排

1、Docker Stack简介 Docker Stack(堆栈) 是在 Swarm 上管理服务堆栈的工具。而在以前文章docker swarm集群搭建 介绍的 Docker Swarm 只能实现对单个服务的简单部署&#xff0c;于是就引出了Docker Stack。 上面我们介绍到 docker-compose&#xff1a;可以在一台机器上使用…

代码随想录算法训练营 DAY 21 | 230.二叉搜索树的最小绝对差 501.二叉搜索树中的众数 236.二叉树的最近公共祖先

230.二叉搜索树的最小绝对差 二叉搜索树&#xff0c;用中序遍历 用一个全局变量result存储最小绝对差&#xff0c;prev指针存储 在中的逻辑里去更新result&#xff08;保证prev不为空&#xff09;&#xff0c;然后更新prevcur。 牢记谦虚遍历的顺序&#xff01;pre紧跟在cu…

多层陶瓷电容器(MLCC)的基本结构与特点

多层陶瓷电容器&#xff08;MLCC&#xff09;是一种电子元件&#xff0c;用于存储电荷和调节电路中的电容值。它们由多个陶瓷层组成&#xff0c;每个层之间夹有金属电极&#xff0c;然后堆叠在一起&#xff0c;并在两端连接上导体引线&#xff0c;形成一个整体结构。在外部通常…

QML | JavaScript作用域和命名解析2

QML | JavaScript作用域和命名解析3.绑定的作用域对象 属性绑定是QML中最常见的JavaScript应用。属性绑定关联了一个JavaScript表达式的结果和对象的一个属性,该属性所归属的对象被称为绑定的作用域对象。在下面的代码中,Item对象就是一个绑定的作用域对象: ​ 绑定可以…

本地运行环境工具UPUPWANK(win)和Navicat数据库管理工具

UPUPWANK安装地址&#xff1a;https://www.upupw.net 1.进入UPUPWANK后点击一键开启 2.新增项目 这里请千万注意80端口&#xff0c;如果80端口被占用了&#xff0c;请记住去任务管理器关闭占用80端口的进程。不然就不会成功显示。&#xff08;笔者含泪警告&#xff0c;一晚上的…

PostgreSQL技术大讲堂 - 第48讲:PG高可用实现keepalived

PostgreSQL从小白到专家&#xff0c;是从入门逐渐能力提升的一个系列教程&#xff0c;内容包括对PG基础的认知、包括安装使用、包括角色权限、包括维护管理、、等内容&#xff0c;希望对热爱PG、学习PG的同学们有帮助&#xff0c;欢迎持续关注CUUG PG技术大讲堂。 第48讲&#…

javaSSM公司招聘管理系统IDEA开发mysql数据库web结构计算机java编程maven项目

一、源码特点 IDEA开发SSM公司招聘管理系统是一套完善的完整企业内部系统&#xff0c;结合SSM框架和bootstrap完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;MAVEN方式加 载&#xff0c;系统具有完整的源代码和…

设计模式深度解析:深入浅出的揭秘游标尺模式与迭代器模式的神秘面纱 ✨

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 深入浅出的揭秘游标尺模式与迭代器模式的神秘面纱 开篇&#xff1a; 欢迎来到设计模式的神秘…

最小割问题合集,最大权闭合图,最大密度子图,最小权点覆盖,最大权独立子图,OJ练习,代码详解

文章目录 零、回顾1、流网络的割2、最小割问题 一、最小割的应用1.1POJ1966 -- Cable TV Network1.1.1原题链接1.1.2思路分析1.1.3AC代码 1.2ZOJ 2676 Network Wars1.2.1原题链接1.2.2思路分析1.2.3AC代码 1.3OPTM - Optimal Marks1.3.1原题链接1.3.2思路分析1.3.3AC代码 二、最…

ApiPost设置多人协作

有时候一个项目会有多个人一起编写&#xff0c;每个人都有自己的接口&#xff0c;ApiPost提供了一个多人协作功能&#xff0c;可以在一个项目里加入多个成员&#xff0c;每个人新增的接口都可以在项目中看到&#xff0c;从而提高开发效率。 我这边用的是ApiPost7&#xff0c;首…

深入探讨iOS开发:从创建第一个iOS程序到纯代码实现全面解析

iOS开发作为移动应用开发的重要领域之一&#xff0c;对于开发人员具有重要意义。本文将深入探讨iOS开发的各个方面&#xff0c;从创建第一个iOS程序到纯代码实现iOS开发&#xff0c;带领读者全面了解iOS应用程序的开发流程和技术要点。 &#x1f4f1; 第一个iOS程序 在创建第…

【蓝桥杯】tarjan算法

一.概述 Tarjan 算法是基于DFS的算法&#xff0c;用于求解图的连通性问题。 Tarjan 算法可以在线性时间内求出&#xff1a; 无向图&#xff1a; 割点与桥双连通分量 有向图&#xff1a; 强连通分量必经点与必经边 1.割点&#xff1a; 若从图中删除节点 x 以及所有与 x 关联的…

【c++】类和对象(四)深入了解拷贝构造函数

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;c笔记仓 朋友们大家好啊&#xff0c;本篇内容带大家深入了解拷贝构造函数 目录 1.拷贝构造函数1.1传值调用的无限调用1.2浅拷贝1.3深拷贝1.4深拷贝的实现 1.拷贝构造函数 拷贝构造函数是一种特殊的…

Java版企业电子招标采购系统源码——鸿鹄电子招投标系统的技术特点

在数字化时代&#xff0c;采购管理也正经历着前所未有的变革。全过程数字化采购管理成为了企业追求高效、透明和规范的关键。该系统通过Spring Cloud、Spring Boot2、Mybatis等先进技术&#xff0c;打造了从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理。通过…

【Java面试题】计算机网络

文章目录 1.计算机网络基础1.1网络分层模型/OSI七层模型是什么&#xff1f;1.2TCP/IP四层模型是什么&#xff1f;每一层的作用&#xff1f;1.2.1TCP四层模型&#xff1f;1.2.2为什么网络要分层&#xff1f; 1.2常见网络协议1.2.1应用层常见的协议1.2.2网络层常见的协议 2.HTTP2…

解决华为云服务器宝塔面板无法访问显示“此站点的连接不安全”问题

已经配置好安全组以及初始化宝塔面板&#xff0c;还是无法访问镜像管理页面&#xff0c;提示此站点的连接不安全。 解决方案 将地址https改为http即可进入。 成功登录后&#xff0c;开启面板SSL即可。

js实现拖放效果

dataTransfer对象 说明&#xff1a;dataTransfer对象用于从被拖动元素向放置目标传递字符串数据。因为这个对象是 event 的属性&#xff0c;所以在拖放事件的事件处理程序外部无法访问 dataTransfer。在事件处理程序内部&#xff0c;可以使用这个对象的属性和方法实现拖放功能…