一、 程序编写流程

本文借鉴麦兜(chenchengwudi@sina.com)的笔记从用户空间使用V4L2子系统
下文以泰山派RK3566开发板为例,系统为Buildroot,虚拟机使用Ubuntu18.04,摄像头使用OV5695摄像头。
USB免驱摄像头可查看 嵌入式Linux应用开发——摄像头V4L2应用程序开发(USB免驱摄像头)

  • 嵌入式Linux学习交流群:1005210698
  • 本文相关源码以及更多免费资料可加群获取

1.1 打开设备节点

open:打开设备节点 /dev/videoX

fd = open("/dev/video0", O_RDWR | O_NONBLOCK);

说明1:

  • 对于 V4L2 设备,也可以在 open 函数中指定 O_NONBLOCK 标志,即以非阻塞模式打开。在非阻塞模式下,如果在进行 ioctI+VIDIOC_DQBUF 操作时没有已经就绪的缓冲区,则应用程序不会阻塞,而是立即返回-1,并且将 errno 置为 EAGAIN

说明2:

  • 在泰山派RK3566中,OV5695摄像头对应的设备节点是 /dev/video0

1.2 查询设备能力

ioctl VIDIOC_QUERYCAP:Query Capbility,查询设备能力,比如

  • 确认是否是“捕获设备”,因为有些节点是输出设备
  • 确认是否支持 mmap 操作,还是仅支持 read/write 操作
struct v4l2_capability cap;
memset(&cap, 0, sizeof(cap));
if(0 <= ioctl(fd, VIDIOC_QUERYCAP, &cap)){
	printf("driver = %s\n", cap.driver);
	printf("card = %s\n", cap.card);
	printf("bus_info = %s\n",cap.bus_info);
	printf("version = %u.%u.%u\n",(cap.version >>16) & 0xFF,
		   (cap.version >> 8) & 0xFF, cap.version & 0xFF);
	printf("capability = 0x%x\n", cap.capabilities);
	printf("device_caps = 0x%x\n",cap.device_caps);
}

打印信息
在这里插入图片描述
v4l2_capability结构体

struct v4l2_capability {
    __u8    driver[16];         // 驱动模块的名称(例如:“bttv”)
    __u8    card[32];           // 卡片的名称(例如:“运营商天盛Hauppauge”)
    __u8    bus_info[32];       // 总线的名称(例如:pci_name(pci_dev))
    __u32   version;            // 内核版本
    __u32   capabilities;       // 物理设备的整体功能
    __u32   device_caps;        // 通过这个特定设备(节点)访问的功能
    __u32   reserved[3];        // 为将来的扩展保留字段
};

说明1: capabilitiesdevice_cap字段的构成

capabilitiesdevice_cap 字段由一系列标志位构成,此处简要说明如下概念的含义:

  1. single-planarmulti-planar 用于描述图像类型的存储方式
  2. captureoutput 用于描述设备数据流向,capture 的数据是从设备到内存(e.g.视频采集设备),output 的数据是从内存到设备 (e.g.视频输出设备)
  3. memory-to-memory 也是用于描述设备的数据流向,即数据从内存到设备再到内存,典型的就是图像codec的数据流向(e.g.待编码的图像来自内存,编码后的码流也输出到内存)

在这里插入图片描述

说明2:实验硬件设备能力分析

  • 本次设备的 capabilities0x84201000,表示该设备支持 V4L2_CAP_DEVICE_CAPSV4L2_CAP_STREAMINGV4L2_CAP_EXT_PIX_FORMATV4L2_CAP_VIDEO_CAPTURE_MPLANE
  • 其中 V4L2_CAP_VIDEO_CAPTURE_MPLANE 尤为重要,与 USB摄像头 不同,该摄像头支持的是 多平面格式图像

1.3 枚举设备格式

ioctl VIDIOC_ENUM_FMT:枚举设备支持的格式

struct v4l2_fmtdesc fmtdesc;
memset(&fmtdesc, 0, sizeof(fmtdesc));
fmtdesc.index = 0;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; 
while(1){
	if (0 > ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc)) {
		if(error != EINVAL){
			perror("VIDIOC_ENUM_FMT error");
			goto err;
		}
		else {
			printf("enumerate end\n");
			break;
		}
	}
	printf("index = %d\n",fmtdesc.index);
	printf("type = %d\n",fmtdesc.type);
	printf("flags = 0x%x\n", fmtdesc.flags);
	printf("description = %s\n", fmtdesc.description);
	printf("pixelformat = %c.%c.%c.%c\n\n",fmtdesc.pixelformat & 0xFF,
		(fmtdesc.pixelformat >> 8)& 0xFF,(fmtdesc.pixelformat >>16) & 0xFF,
		(fmtdesc.pixelformat >>24) & 0xFF);
	fmtdesc.index++;
}

打印信息

  • 只截取了一部分,后续将图像格式设置为 NV12 格式
    在这里插入图片描述

v4l2_fmtdesc结构体

struct v4l2_fmtdesc {
	__u32		    index;             /* 格式枚举序号 */
	__u32		    type;              /* 缓冲区类型 */
	__u32           flags;
	__u8		    description[32];   /* 描述  */
	__u32		    pixelformat;       /* 像素格式 */
	__u32		    reserved[4];
};

说明1: type 缓冲区类型

  • 从实现逻辑上来说,是驱动程序先指定 vb2_queue 结构体的缓冲区类型,之后应用程序需要指定相应的缓冲区类型才能索引到正确的 vb2_queue 结构体

在这里插入图片描述

说明2:格式标志 flags

  1. 格式标志flags由一系列标志位构成
    在这里插入图片描述

  2. 实验硬件设备支持格式的标志flags字段值如下,

    • YUYV 格式的 flags 字段值为 0x0,即 YUYV 格式既不是压缩格式,也不是软件模拟格式

1.4 枚举指定格式分辨率

ioctl VIDIOC_ENUM_FRAMESIZES:枚举指定格式的分辨率

struct v4l2_frmsizeenum frmsize;
memset(&frmsize, 0, sizeof(frmsize));
frmsize.index = 0;
frmsize.pixel_format = V4L2_PIX_FMT_NV12;

while(1){
	ret = ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize);
        if(ret < 0){
            printf("enumerate end\n");
            break;
        }
        printf("type = %d\n", frmsize.type);
        switch (frmsize.type) {
            case V4L2_FRMSIZE_TYPE_DISCRETE:
                printf("V4L2_FRMSIZE_TYPE_DISCRETE\n");
                break;
            case V4L2_FRMSIZE_TYPE_CONTINUOUS:
                printf("V4L2_FRMSIZE_TYPE_CONTINUOUS\n");
                break;
            case V4L2_FRMSIZE_TYPE_STEPWISE:
                printf("V4L2_FRMSIZE_TYPE_STEPWISE\n");
                break;
        }
        printf("index = %d\n",frmsize.index);
        if(frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE)
            printf("width = %d, height = %d\n", 
            frmsize.discrete.width, frmsize.discrete.height);
        if(frmsize.type == V4L2_FRMSIZE_TYPE_STEPWISE 
        || frmsize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS){
            printf("min_width = %d, min_height = %d\n", 
		            frmsize.stepwise.min_width, frmsize.stepwise.min_height);
            printf("max_width = %d, max_height = %d\n", 
		            frmsize.stepwise.max_width, frmsize.stepwise.max_height);
            printf("step_width = %d, step_height = %d\n", 
		            frmsize.stepwise.step_width, frmsize.stepwise.step_height);
        }
        frmsize.index++;
    }
}

打印信息
在这里插入图片描述

v4l2_frmsizeenum结构体

struct v4l2_frmsizeenum {
	__u32			index;		    // 分辨率遍历序列
	__u32			pixel_format;	// fourcc格式编码
	__u32			type;		    // 设备支持的帧大小类型
	union {					/* Frame size */
		struct v4l2_frmsize_discrete	discrete;
		struct v4l2_frmsize_stepwise	stepwise;
	};
	__u32   reserved[2];			/* Reserved space for future use */
};

struct v4l2_frmsize_discrete {
    __u32           width;          // 帧宽[像素]
    __u32           height;         // 帧高[像素]
};

struct v4l2_frmsize_stepwise {
    __u32           min_width;      // 最小帧宽[像素]
    __u32           max_width;      // 最大帧宽[像素]
    __u32           step_width;     // 帧宽步长[像素]
    __u32           min_height;     // 最小帧高[像素]
    __u32           max_height;     // 最大帧高[像素]
    __u32           step_height;    // 帧高步长[像素]
};

说明1:关于分辨率

  1. 分辨率类型由 v4l2_frmsizetypes 枚举值指定,具体枚举值如下
    在这里插入图片描述
  • 离散型分辨率:设备支持的分辨率由一系列离散值构成
  • 阶梯型分辨率:设备支持的分辨率在指定的范围内以步长为单位递增
  • 连续型分辨率:连续型分辨率可以理解为阶梯型分辨率的特例,即步长为1的阶梯型分辨率

1.5 设置图像格式

ioctl VIDIOC_S_FMT:在上面枚举出的格式里,选择一个来设置格式

struct v4l2_format fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
fmt.fmt.pix_mp.width = 1280;
fmt.fmt.pix_mp.height = 720;
fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12;
fmt.fmt.pix.field = V4L2_FIELD_ANY;

int ret = ioctl(vd->fd, VIDIOC_S_FMT, &fmt);

printf("type = %d\n", fmt.type);
printf("width = %d\n", fmt.fmt.pix_mp.width);
printf("height = %d\n",fmt.fmt.pix_mp.height);
printf("pixelformat = %c.%c.%c.%c\n",fmt.fmt.pix_mp.pixelformat & 0xFF,
		(fmt.fmt.pix_mp.pixelformat >>8) & 0xFF,
		(fmt.fmt.pix_mp.pixelformat >>16) & 0xFF,
		(fmt.fmt.pix_mp.pixelformat >>24) & 0xFF);
printf("field = 0x%x\n", fmt.fmt.pix_mp.field);
printf("num_plans = %d\n", fmt.fmt.pix_mp.num_planes);
printf("colorspace = 0x%x\n", fmt.fmt.pix_mp.colorspace);
printf("plane: sizeimage  = %d, bytesperline = %d\n",
						fmt.fmt.pix_mp.plane_fmt[0].sizeimage, 
						fmt.fmt.pix_mp.plane_fmt[0].bytesperline);
}

打印信息
在这里插入图片描述

说明1:图像存储方式

在这里插入图片描述

  • 泰山派底层驱动强制设置为了1个平面,将 NV12 的 ​两个平面合并为单一连续内存块,形成逻辑上的单平面
  • 以图像格式位 YUYV 为例,分辨率在1280 * 720的情况下,驱动程序填充了1个平面的 bytesperlinesizeimage 字段,其中:
    • bytesperline = 1280 (= 1280 * 1, 存储一个 Y 像素需要 1B
    • sizeimage = 1382400 (= 1280 * 720 * 3/2,UV 分量水平采样率和垂直采样率都是是 Y 分量的一半,存储一组 UV分量需要 2B,这里只有一个平面,则将 YUV 相加)

v4l2_format结构体

struct v4l2_format {
    __u32    type;                                 // 数据流类型
    union {
        struct v4l2_pix_format      pix;           // 图像格式的定义
        struct v4l2_pix_format_mplane   pix_mp;    // 多平面图像格式的定义
        struct v4l2_window      win;               // 覆盖图像的定义
        struct v4l2_vbi_format      vbi;           // 原始VBI捕获或输出参数
        struct v4l2_sliced_vbi_format   sliced;    // 切片VBI捕获或输出参数
        struct v4l2_sdr_format      sdr;     /* V4L2_BUF_TYPE_SDR_CAPTURE */
        struct v4l2_meta_format     meta;    /* V4L2_BUF_TYPE_META_CAPTURE */
        __u8    raw_data[200];                     // 未来扩展和自定义格式的占位符
    } fmt;
};

v4l2_pix_format_mplane结构体

struct v4l2_pix_format_mplane {
    __u32 width;                // 图像宽度(单位:像素)
    __u32 height;               // 图像高度(单位:像素)
    __u32 pixelformat;          // 像素格式(如 V4L2_PIX_FMT_NV12)
    __u32 field;                // 视频场类型(如顶场、底场、隔行扫描等)
    __u32 colorspace;           // 颜色空间(如 BT.601、BT.709 等)

    struct v4l2_plane_pix_format plane_fmt[VIDEO_MAX_PLANES]; // 各平面的像素格式参数
    __u8 num_planes;            // 实际使用的平面数量(<= VIDEO_MAX_PLANES)
    __u8 flags;                 // 标志位(如是否连续存储、预乘Alpha等)
    union {
        __u8 ycbcr_enc;         // Y'CbCr 编码范围(如 BT.601 或 BT.709)
        __u8 hsv_enc;           // HSV 编码方式(若适用)
    };
    __u8 quantization;          // 量化范围(全范围或有限范围)
    __u8 xfer_func;             // 传输函数(如 BT.709、sRGB 等)
    __u8 reserved[7];           // 保留字段(对齐或扩展用)
} __attribute__ ((packed));     // 紧凑内存对齐(无填充字节)

** v4l2_plane_pix_format 结构体:**

struct v4l2_plane_pix_format {
    __u32 sizeimage;        // 当前平面的图像数据总大小(字节),包含所有行和可能的填充
    __u32 bytesperline;     // 每行的字节数
    __u16 reserved[6];      // 保留字段
} __attribute__ ((packed)); // 紧凑内存对齐(无填充字节)

说明2:VIDIOC_TRY_FMT命令

  • VIDIOC_TRY_FMT 命令和 VIDIOC_S_FMT 命令的唯一区别就是他不会修改驱动程序的状态,也就是说==VIDIOC_TRY_FMT 命令只是尝试设置而不会真正设置图像格式==

1.6 申请缓冲区

ioctl VIDIOC_REQBUFS:申请 bufferAPP 可以申请多个 buffer,但是驱动程序不一定申请到

struct v4l2_requestbuffers rb;
memset(&rb, 0, sizeof(rb));
rb.count = 3;
rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
rb.memory = V4L2_MEMORY_MMAP;

ret = ioctl(fd, VIDIOC_REQBUFS, &rb);

printf("type = %d\n",rb.type);
printf("count = %d\n",rb.count);
printf("memory = %d\n", rb.memory);

打印信息
在这里插入图片描述

v4l2_requestbuffer结构体

struct v4l2_requestbuffers {
    __u32           count;         // 驱动程序会根据实际分配的缓冲区个数对其修改
    __u32           type;       /* enum v4l2_buf_type */
    __u32           memory;     /* enum v4l2_memory */
    __u32           reserved[2];
};

说明1:

  • 如果执行 VIDIOC_REQBUFS 命令时将 count 字段设置为 0,表示释放已分配的所有缓冲区

1.7 映射缓冲区

ioctl VIDIOC_QUERYBUFmmap:申请 buffer 信息、映射

  • 如果申请到了 Nbuffer,这个 ioctl 就应该执行 N
  • 执行 mmap 后,APP 就可以直接读写这些 buffer
struct v4l2_buffer buf;
struct v4l2_plane planes[VIDEO_MAX_PLANES];
for(int i = 0; i < rb.count; i++){
	memset(&buf, 0, sizeof(buf));
	memset(&planes, 0, sizeof(planes));

	buf.index = i;
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
	buf.memory = V4L2_MEMORY_MMAP;
	buf.m.planes = planes;
	buf.length = 1;

	ret = ioctl(fd, VIDIOC_QUERYBUF, &buf);
	if(ret < 0){
		printf("VIDIOC_QUERYBUF error\n");
		goto err;
	}

	user_bufs[i].planes[0].len = planes[0].length;
	user_bufs[i].planes[0].m.offset = planes[0].m.mem_offset;

	user_bufs[i].planes[0].start = mmap(NULL, planes[0].length, PROT_READ | PROT_WRITE, 
		MAP_SHARED, fd, planes[0].m.mem_offset);

	if(user_bufs[i].planes[0].start == MAP_FAILED){
		printf("Unable to map buffer\n");
		goto err;
	}

	printf("index = %d\n", buf.index);
	printf("type = %d\n", buf.type);
	printf("memory = %d\n", buf.memory);
	printf("length = %d\n", buf.length);
	printf("sequence = %d\n", buf.sequence);
	printf("plane: legnth = %d, bytesused = %d, mem_offset = 0x%x, start = %p\n", 
		planes[0].length, planes[0].bytesused, 
		planes[0].m.mem_offset, user_bufs[i].planes[0].start);
	printf("\n");
}

打印信息
在这里插入图片描述

说明1:

  • multi-planar 图像格式在 VIDIOC_QUERYBUF 控制命令中 length 字段的设置

  • 对于 multi-planar 图像格式,length 字段设置的是 v4l2_planes 数组的成员个数

  • 因为内核驱动是单平面,这里的 buf.length 手动设置为1

  • 内核会检查作为入参设置的 length 字段,该值必须满足如下范围。可见此时传递的 v4l2_planes 数组成员个数可以超过图像格式实际的 plane 个数。后续在作为出参时,内核会对其进行修正

  • [buf.m.planes = 8]
    在这里插入图片描述

  • VIDIOC_QUERYBUF 控制命令返回时,内核会根据图像格式实际的 plane 个数修正作为出参的 length 字段
    在这里插入图片描述

v4l2_buffer结构体

struct v4l2_buffer {
	__u32			index;             // 缓冲区序号(实际分配个数 - 1)
	__u32			type;              // 缓冲区类型(type == *_MPLANE 是多平面缓冲区)
	// 缓冲区(有效载荷)中数据占用的字节数,多平面缓冲区未使用(设置为0)
	__u32			bytesused;         
	__u32			flags;             // 缓冲区信息标志
	__u32			field;             // 缓冲区中图像的字段顺序(enum v4l2_field)
	struct timeval		timestamp;     // 缓冲区时间戳
	struct v4l2_timecode	timecode;  // 缓冲区时间编码
	__u32			sequence;          // 缓冲区的序列计数

	/* memory location */
	__u32			memory;            // 缓冲区内存类型(enum v4l2_memory)
	union {
		__u32           offset;        // single-planar mmap类型缓冲区内存信息
		unsigned long   userptr;       // single-planar userptr类型缓冲区内存信息
		struct v4l2_plane *planes;     // multi-planar 类型信息
		__s32		fd;                // single-planar dmabuf类型缓冲区内存信息
	} m;
	__u32			length;
	__u32			reserved2;
	__u32			reserved;
};

struct v4l2_plane {
	__u32			bytesused;     // 平面中数据占用的字节数(有效载荷)
	__u32			length;        // 这个平面的大小(不是有效载荷),以字节为单位
	union {
		__u32		mem_offset;    // mmap 类型缓冲区内存信息
		unsigned long	userptr;   // userptr 类型缓冲区内存信息
		__s32		fd;
	} m;
	__u32			data_offset;  // 数据在平面上的偏移量;通常为0,除非在数据前面有一个标题
	__u32			reserved[11];
};

1.8 将缓冲区加入队列

ioctl VIDIOC_QBUF:把 buffer 放入“空间链表
- 如果申请到了 Nbuffer,这个 ioctl 就应该执行 N

for(int i = 0; i < rb.count; i++){
	memset(&buf, 0, sizeof(buf));
	memset(&planes, 0, sizeof(planes));

	buf.index = i;
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
	buf.memory = V4L2_MEMORY_MMAP;
	buf.m.planes = planes;
	buf.length = 1;

	ret = ioctl(fd, VIDIOC_QBUF, &buf);
	if(ret < 0){
		printf("VIDIOC_QBUF error\n");
		goto err;
	}
}

说明1:V4L2缓冲区管理概述

  1. 对于一个内核态的 vb2_queue 结构体(也就是一个缓冲区队列),V4L2框架会维护2个缓冲区链表
    在这里插入图片描述

    • queued_list当前从用户空间排队的缓冲区列表。(对于图像采集设备,应用程序将缓冲区通过 VIDIOC_QBUF 操作加入 queued_list ,以便设备填充数据)
    • done_list准备退出用户空间队列的缓冲区列表。(对于图像采集设备,驱动程序会将填充好数据的缓冲区加入 done_list ,等待应用程序通过 VIDIOC_DQBUF 操作取出)
  2. 缓冲区在 queued_listdone_list 链表之间的流转状态如下:

    • 分配的缓冲区既不在 queued_list,也不在 done_list
    • 应用程序通过 VIDIOC_QBUF 操作,将缓冲区加入 queued_list,以便驱动程序使用
    • 驱动程序从 queued_list 获取缓冲区使用,在操作完成后(e.g.填充完图像后),将缓冲区再加入 done_list
    • 应用程序通过 VIDIOC_DQBUF 操作,将缓冲区同时从 done_listqueued_list 链表中取出使用
  3. 需要注意的是,VIDIOC_QBUF 操作和 VIDIOC_DQBUF 操作中传递 v4I2_buffer 结构体的目的,是让内核态 V4L2 框架可以通过用户态传递的 v4l2_buffer 结构体信息索引到相应的 vb2_buffer 结构体。因此用户态一般只需要定义一个 v4l2_buffer 结构体类型变量,在传递时设置相应的字段即可

  4. 缓冲区加入队列会将缓冲区的内存页锁定在物理内存中,这样这些页面就不会被交换到磁盘中,以便于硬件设备访问 (通常是DMA操作)


1.9 开启流传输

ioctl VIDIOC_STRTAMON:启动摄像头

enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_STREAMON, &type);

1.10 采集图像

这里是个循环:使用 poll/select 监测 buffer,然后从“完成链表”中取出 buffer,处理再放入“空闲链表

  1. poll/select
memset(fds, 0, sizeof(struct fds));
fds[0].fd = fd;
fds[0].events = POLLIN;
poll(fds, 1, -1)
  1. ioctl VIDIOC_DQBUF:从“完成链表”中取出 buffer
for(int i = 0; i < rb.count; i++){
	ret = poll(p, 1, -1);
	
	memset(&buf, 0, sizeof(buf));
	memset(&planes, 0, sizeof(planes));
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
	buf.memory = V4L2_MEMORY_MMAP;
	buf.m.planes = planes;
	buf.length = 1;

	ret = ioctl(fd, VIDIOC_DQBUF, &buf);

说明1:

  • 用户态在向 VIDIOC_DQBUF 命令传递 v4I2_buffer 结构体地址时,不需要设置其中的 memory 字段,只需要设置 type 字段即可。
  • 内核态在处理 VIDIOC_DQBUF 命令时不会检查 memory 字段,并且会根据缓冲区队列的 memory 字段设置 v4l2_buffer 作为出参时的 memory
  1. 处理:前面使用 mmap 映射了每个 buffer 的地址,处理时就可以直接使用地址来访问 buffer
sprintf(file_name, "image%d.nv12", file_index++);
fp = fopen(file_name, "w+");

fwrite(user_bufs[buf.index].planes[0].start, planes[0].bytesused, 1, fp);
fclose(fp);

printf("index = %d\n", buf.index);
printf("type = %d\n", buf.type);
printf("memory = %d\n", buf.memory);
printf("length = %d\n", buf.length);
printf("sequence = %d\n", buf.sequence);

printf("plane: legnth = %d, bytesused = %d, mem_offset = 0x%x\n", 
		planes[0].length, planes[0].bytesused, planes[0].m.mem_offset);
printf("       data_offset = 0x%x, start = %p\n\n", 
		planes[0].data_offset,user_bufs[buf.index].planes[0].start);

打印信息:
在这里插入图片描述

说明1:

  • 在将缓冲区中的图像写入文件时,应该使用 planes[0].bytesused 字段,该字段标识缓冲区中有效数据的长度
  • 对于 YUV 类型图像格式,由于是非压缩类型,缓冲区中有效数据长度和缓冲区长度是一致的。如果将格式改为 MJPEG 这类压缩类型,则每帧图像有效数据的长度是不同的(但是都不会超过缓冲区长度)

说明2:

  • 捕获到的图像需使用支持YUV的工具打开
  • 可使用 YuvPlayer 软件查看,需设置对应的像素和分辨率
  1. ioctl VIDIOC_QBUF:把 buffer 放入“空闲链表
ioctl(fd, VIDIOC_QBUF, &buf);

说明2:

  • 当映射了多个缓冲区时,将 buff 放回空闲链表时,需指定 buffer 所对应的索引
  • 此处是封装在一个函数里,可以不用设置

1.11 关闭流传输

ioctl VIDIOC_STREAMOFF:停止摄像头

type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_STREAMOFF, &type);

二、程序源码

程序源码

Logo

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

更多推荐