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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

标题:深入了解 JavaScript 中的字符串分割:遇到 / 和 | 都分割

在 JavaScript 中&#xff0c;处理字符串是一项常见的任务。有时候&#xff0c;我们需要将字符串按照特定的字符进行分割&#xff0c;以提取或操作其中的各个部分。在这篇博客中&#xff0c;我们将深入探讨如何使用 JavaScript 的字符串分割功能&#xff0c;特别是当遇到斜杠 /…

汽车后视镜反射率检测光纤光谱仪:安全驾驶的守护神

在汽车的日常使用中&#xff0c;后视镜扮演着至关重要的角色。它不仅帮助驾驶员观察车辆后方的情况&#xff0c;还确保了行车的安全性。然而&#xff0c;由于各种原因&#xff0c;后视镜的反射率可能会降低&#xff0c;从而影响到驾驶员的视线范围和判断能力。为了解决这一问题…

Reactor 模式全解:实现非阻塞 I/O 多路复用

Reactor网络模式是什么&#xff1f; Reactor网络模式时目前网络最常用的网络模式。如果你使用Netty&#xff0c;那么你在使用Reactor;如果你使用Twisted,那么你子啊使用Reactor;如果你使用netpoll&#xff0c;那么你在使用Reactor。 这里先给出答案&#xff1a;Reactor I/O多…

Python与供应链-2预测误差及指数平滑需求预测模型

主要介绍预测误差和指数平滑模型的相关理论,然后再通过Python的statsmodels封装的指数平滑函数预测需求。 1预测误差 预测误差是指预测结果与预测对象发展变化的真实结果之间的差距。这种误差分为绝对误差和相对误差。绝对误差是预测值与实际观测值的绝对差距,而相对误差则…

Spring学习——什么是循环依赖及其解决方式

文章目录 前言一、什么是循环依赖二、解决思路1、循环依赖分类2、对象初始化步骤及对象分类3、spring是如何解决的4、图解5、三级缓存1、区别2、ObjectFactory是什么 三、源码debug1、spring创建对象过程1、dubug第一步——找到getBean2、dubug第二步——getBean与doGetBean3、…

35.基于SpringBoot + Vue实现的前后端分离-在线考试系统(项目 + 论文)

项目介绍 本站是一个B/S模式系统&#xff0c;采用SpringBoot Vue框架&#xff0c;MYSQL数据库设计开发&#xff0c;充分保证系统的稳定性。系统具有界面清晰、操作简单&#xff0c;功能齐全的特点&#xff0c;使得基于SpringBoot Vue技术的在线考试系统设计与实现管理工作系统…

Uniapp三种常用提示框

具体参数方法可参考: uniapp交互反馈 uni.showToast(OBJECT) //显示消息提示框。 uni.hideToast() //隐藏消息提示框。 //具体使用 uni.showToast({title: 新增成功,duration: 2000 }); uni.showLoading(OBJECT) //显示 loading 提示框, 需主动调用 uni.hideLoading 才能关闭提…

区块链安全之DDoS防护的重要性及其实施策略

随着区块链技术的不断发展和广泛应用&#xff0c;其安全问题也日益凸显。其中&#xff0c;分布式拒绝服务(DDoS)攻击是对区块链网络稳定性和效率构成潜在威胁的重要因素之一。本文旨在深入探讨区块链为何需要采取DDoS高防措施&#xff0c;并提出相应的防护策略。 一、区块链面…

毕马威:量子计算成未来3-5年重大挑战

毕马威&#xff08;KPMG&#xff09;是一家全球性的专业服务网络&#xff0c;其历史可追溯到19世纪末。作为“四大”会计师事务所之一&#xff0c;毕马威在审计、税务和咨询服务领域享有盛誉。公司在全球范围内拥有多个办事处&#xff0c;服务遍及各个行业&#xff0c;包括金融…

【大数据】Flink学习笔记

认识Flink Docker安装Flink version: "2.1" services:jobmanager:image: flinkexpose:- "6123"ports:- "20010:8081"command: jobmanagerenvironment:- JOB_MANAGER_RPC_ADDRESSjobmanagertaskmanager:image: flinkexpose:- "6121"- …

Visio Viewer for Mac(Visio文件查看工具)

Visio Viewer for Mac是一款专为Mac用户设计的Microsoft Visio文件查看器。它拥有直观易用的用户界面&#xff0c;使得用户可以快速加载和显示Visio文件&#xff0c;无需安装完整的Microsoft Visio软件。 软件下载&#xff1a;Visio Viewer for Mac3.1.0激活版 Visio Viewer fo…

java Web线上网游商品交易平台用eclipse定制开发mysql数据库BS模式java编程jdbc

一、源码特点 jsp线上网游商品交易平台是一套完善的web设计系统&#xff0c;对理解JSP java SERLVET mvc编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,eclipse开发&#xff0c;数据库为Mysql5.0…

spring使用内置jetty创建提供http接口服务

1、添加pom文件依赖 <dependency><groupId>org.eclipse.jetty</groupId><artifactId>jetty-server</artifactId><version>9.4.22.v20191022</version> </dependency> <dependency><groupId>org.eclipse.jetty<…

azure服务器通过手机客户端远程连接

下载客户端 输入ip地址 输入用户名和密码 连接成功 人工智能学习网站&#xff1a; https://chat.xutongbao.top

大鱼来客实景无人直播系统源码开发部署---支持OEM贴牌-无限开户

实景无人直播系统需要包含以下几个主要功能&#xff1a; 视频采集与编码&#xff1a;使用摄像头或其他视频设备进行视频采集&#xff0c;并对视频进行编码压缩&#xff0c;以便实现实时传输。 视频传输与接收&#xff1a;将编码后的视频通过网络传输至客户端&#xff0c;客户端…

探究 HTTPS 的工作过程

目录 1. HTTPS 协议原理 1.1. 为什么要有HTTPS协议 1.2. 如何理解安全 1.3. HTTPS 协议是什么 2. HTTPS 的前置概念 2.1. 什么是加密 && 解密 2.2. 为什么要加密 2.3. 常见的加密方式 2.3.1. 对称加密 2.3.2. 非对称加密 2.4. 数据摘要 && 数据指纹…

Avalonia11.0.2+.Net6.0支持多语言,国际化

Avalonia11.0.2+.Net6.0支持多语言,国际化 操作系统项目结构最终效果具体实现安装Prism.Avalonia准备多语言文件语言资源加载类界面标记扩展类界面中使用国际化VM具体实现VM里面使用多语言方法操作系统 项目结构 最

【jenkins+cmake+svn管理c++项目】windows修改jenkins的工作目录

jenkins默认的存放源码的workspace是&#xff1a; C:\Users\用户\AppData\Local\Jenkins\.jenkins\workspace。由于jenkins会拉取大量的源代码以及编译生成一些文件&#xff0c;我希望我能自己指定目录作为它的工作空间&#xff0c;放在这里显然不太合适。 那么修改目录的方式有…

【剑指offer】顺时针打印矩阵

题目链接 acwing leetcode 题目描述 输入一个矩阵&#xff0c;按照从外向里以顺时针的顺序依次打印出每一个数字。 数据范围矩阵中元素数量 [0,400]。 输入&#xff1a; [ [1, 2, 3, 4], [5, 6, 7, 8], [9,10,11,12] ] 输出&#xff1a;[1,2,3,4,8,12,11,10,9,5,6,7] 解题 …

移动端Web笔记day03

移动 Web 第三题 01-移动 Web 基础 谷歌模拟器 模拟移动设备&#xff0c;方便查看页面效果&#xff0c;移动端的效果是当手机屏幕发生了变化&#xff0c;页面和页面中的元素也要跟着等比例变化。 屏幕分辨率 分类&#xff1a; 硬件分辨路 -> 物理分辨率&#xff1a;硬件…