树莓派--V4L2编程入门1

   V4L2是linux操作系统下用于采集图片、视频和音频数据的API接口,配合适当的视频采集设备和相应的驱动程序,可以实现图片、视频、音频等的采集。在远程会议、可视电话、视频监控系统和嵌入式多媒体终端中都有广泛的应用。    

 

要函数介绍:ioctl函数 

ioctl函数主要用于控制设备

1 #include <sys/ioctl.h>
2 int ioctl(int fd, unsigned long request, ...);
3 参数: fd: 打开设备的文件描述符
4 unsigned long request: 给驱动的命令 
5 第三个参数是变参:根据第二个参数来定 比如:request是获取摄像头格式命令, 第三个
参数就是存储格式的结构体
6 返回值:成功返回0, 失败‐1

 V4L2采集视频显示流程

 1. 打开设备,关闭设备

 

注: video0 查询到当前系统摄像头设备为video0 如果是开发板(video0video6, 自己新添

加的一般是 video7
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <linux/videodev2.h>
int main(void)
{
    //1.打开设备
    int fd = open("/dev/video0", O_RDWR);
    if(fd < 0)
    {
        perror("open fail");
        return ‐1;
    }
    //2.关闭设备
    close(fd);
    return 0;
 }
2.获取摄像头支持的格式(MJPEG, YUYV)
用到的命令: VIDIOC_ENUM_FMT
命名对应结构体 :struct v4l2_fmtdesc
摄像头采集数据 :后面的v4l2_buf_type必须全部设置为
V4L2_BUF_TYPE_VIDEO_CAPTURE (在调到用ioctl时候都要自己设置)
相关结构体:
struct v4l2_fmtdesc
{	
__u32 index;   // 要查询的格式序号,应用程序设置
enum v4l2_buf_type type;     // 帧类型,应用程序设置
__u32 flags;    // 是否为压缩格式
__u8 description[32];      // 格式名称
__u32 pixelformat; // 像素格式
__u32 reserved[4]; // 保留
}; 
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,     
    V4L2_BUF_TYPE_VBI_OUTPUT           = 5,     
    V4L2_BUF_TYPE_SLICED_VBI_CAPTURE   = 6,     
    V4L2_BUF_TYPE_SLICED_VBI_OUTPUT    = 7,     
    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,     
    V4L2_BUF_TYPE_SDR_OUTPUT           = 12,     
    V4L2_BUF_TYPE_META_CAPTURE         = 13,     
    V4L2_BUF_TYPE_META_OUTPUT      = 14,     
    /* Deprecated, do not use */      
    V4L2_BUF_TYPE_PRIVATE              = 0x80,     
};

应用会设置index和type字段。index是用来区别格式的一个整形数;与V4l2所使用的其他索引一样,这个也是从0开始递增至最大允许值为止,应用可以通过一直递增索引值index直到返回EINVAL的方式枚举所有支持的格式。
type字段描述的是数据流类型;对于视频捕捉设备来说(摄像头)就是V4L2_BUF_TYPE_VIDEO_CAPTURE

 //获取摄像头支持的格式
    struct v4l2_fmtdesc fmt;
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    int i=0;
    for(;;i++)
    {
        fmt.index = i;
        int ret = ioctl(fd, VIDIOC_ENUM_FMT, &fmt);
        if(ret < 0)
        {
            break;
        }
        printf("%s--%s\n", fmt.description, (char*)(&fmt.pixelformat));
    }


3、设置摄像头采集格式,获取当前采集格式

  //设置采集格式
    struct v4l2_format vfmat;
    vfmat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    vfmat.fmt.pix.width = 300;//宽
    vfmat.fmt.pix.height = 300;//高
    vfmat.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;  //采集格式
    int ret  = ioctl(fd, VIDIOC_S_FMT, &vfmat);
    if(ret < 0)
    {
        perror("set fail");
    }

    //获取当前采集的格式
    memset(&vfmat, 0, sizeof(vfmat));//清空
    vfmat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret  = ioctl(fd, VIDIOC_G_FMT, &vfmat);
    if(ret < 0)
    {
        perror("get fail");
    }
    printf("当前采集格式:%d:%d--%s\n",vfmat.fmt.pix.width,
                                    vfmat.fmt.pix.height,
                                    (char *)(&vfmat.fmt.pix.pixelformat));

其中

设置采集格式:

  • struct v4l2_format vfmat;:定义一个 v4l2_format 结构体变量 vfmat,用于设置采集格式。
    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_MPL
    ANE */
    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_CAPTUR
    E */
    struct v4l2_sdr_format sdr; /* V4L2_BUF_TYPE_SDR_CAPTURE */
     __u8 raw_data[200]; /* user‐defined */
     } fmt;
     };
    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 */
    __u32 flags; /* format flags (V4L2_PIX_FMT_FLAG_*) */
    __u32 ycbcr_enc; /* enum v4l2_ycbcr_encoding */
    __u32 quantization; /* enum v4l2_quantization */
    __u32 xfer_func; /* enum v4l2_xfer_func */
     };
    

  • vfmat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;:设置数据流类型为视频采集。
  • vfmat.fmt.pix.width = 300;:设置视频采集的宽度为300像素。
  • vfmat.fmt.pix.height = 300;:设置视频采集的高度为300像素。
  • vfmat.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;:设置视频采集的像素格式为 YUYV,这是一种常见的格式,每4个字节表示两个像素点,其中 Y 表示亮度信息,U 和 V 表示色度信息。
  • int ret = ioctl(fd, VIDIOC_S_FMT, &vfmat);:通过 ioctl 系统调用将设置好的采集格式应用到视频设备。   
  •  memset(&vfmat, 0, sizeof(vfmat));:清空 vfmat 结构体

清空 vfmat 结构体是为了确保结构体中的字段都被正确初始化。在这段代码中,vfmat 结构体在设置采集格式之前被使用,然后在获取当前采集格式之前被清空。

结构体在声明后,它的字段中可能包含一些随机的数据。为了确保在设置采集格式之前的字段值都是有效的、预期的值,需要将结构体清空,使其字段都初始化为零或空。

在这里,使用 memset(&vfmat, 0, sizeof(vfmat))vfmat 结构体的所有字节都设置为零,从而清空了结构体中的所有字段。这样做可以确保之前的设置不会影响到当前的设置,同时也可以避免在获取当前采集格式时读取到未初始化的值。

清空结构体是一种良好的编程实践,可以确保结构体中的字段始终处于可控状态。

4. 申请内核缓冲区队列,把缓冲区队列映射到用户空间

命令: VIDIOC_REQBUFS --申请内核缓冲区队列长度(图像缓冲区个数)一般不超过五个
结构体:struct v4l2_requestbuffers //结构体在/usr/include/linux/videodev2.h
1 struct v4l2_requestbuffers {
2 __u32 count; //申请缓冲区个数
3 __u32 type; /* enum v4l2_buf_type */
4 __u32 memory; /* enum v4l2_memory */ ‐‐设置为映射
5 __u32 reserved[2];
6 };
VIDIOC_QUERYBUF -- 查询到队列中某个缓冲区,来映射
结构体:struct v4l2_buffer
VIDIOC_QBUF --对应的缓冲区已经映射完毕
结构体:struct v4l2_buffer
1 struct v4l2_buffer {
2 __u32 index;
3 __u32 type;
4 __u32 bytesused;
5 __u32 flags;
6 __u32 field;
7 struct timeval timestamp;
8 struct v4l2_timecode timecode;
9 __u32 sequence;
10
11 /* memory location */
12 __u32 memory;
13 union {
14 __u32 offset;
15 unsigned long userptr;
16 struct v4l2_plane *planes;
17 __s32 fd;
18 } m;
19 __u32 length;
20 __u32 reserved2;
21 __u32 reserved;
22 };
//重点关注index和length

 将申请到的帧缓冲映射到用户空间,这样就可以直接操作采集到的帧了,而不必去复制。

1 //申请缓冲区队列4
2 struct v4l2_requestbuffers requestbuf;
3 requestbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
4 requestbuf.count = 4;//申请4个缓冲区
5 requestbuf.memory = V4L2_MEMORY_MMAP;//映射空间
6 ret = ioctl(fd, VIDIOC_REQBUFS, &requestbuf);
7 if(ret < 0)
8 {
9 perror("request fail");
10 }
11
12 unsigned char *mmp[4]; int size[4];//用户空间首地址
13 for(int i=0 ; i<requestbuf.count; i++)
14 {
15 //1.获取某一个缓冲区
16 struct v4l2_buffer vbuf;
17 vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
18 vbuf.index = i;
19 ret = ioctl(fd, VIDIOC_QUERYBUF, &vbuf);//从内核中查询一个空间做映射
20 if(ret < 0)
21 {
22 perror("QUERYBUF fail");
23 }
24 //2.映射
25 mmp[i] = mmap(NULL, vbuf.length, PROT_READ|PROT_WRITE, MAP_SHARED, fd,
vbuf.m.offset);
26 size[i] = vbuf.length;
27 //3.告诉驱动映射完毕
28 ret = ioctl(fd, VIDIOC_QBUF, &vbuf);
29 if(ret < 0)
30 {
31 perror("QBUF fail");
32 }
33 }
34
5. 开始采集, 停止采集
开始采集 VIDIOC_STREAMON(开始采集写数据到队列中)
读取数据(读取数据显示或者保存) VIDIOC_DQBUF (告诉内核我要某一个数据,内核不可修改) VIDIOC_QBUF (告诉内核我已经使用完毕)
停止采集,不再向队列中写数据:  VIDIOC_STREAMOFF

 

命令: VIDIOC_STREAMON,VIDIOC_STREAMOFF
1 #define VIDIOC_STREAMON _IOW('V', 18, int)
2 #define VIDIOC_STREAMOFF _IOW('V', 19, int)
1 //开始采集
2 int val = V4L2_BUF_TYPE_VIDEO_CAPTURE;
3 ret = ioctl(fd,VIDIOC_STREAMON, &val);
4 if(ret < 0)
5 {
6 perror("start fail");
7 }
8 {
9 //采集代码
10 }
11
12 //停止采集
13 ret = ioctl(fd,VIDIOC_STREAMOFF, &val);
14 if(ret < 0)
15 {
16 perror("stop fail");
17 }
采集数据
1 //采集一帧数据
2 struct v4l2_buffer readbuffer;
3 readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//先初始化
4 ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer);
5 if(ret < 0)
6 {
7 perror("read‐start fail");
8 }
9 //根据readbuffer结构体中的index获取用户空间映射空间, length获取数据长度
10 FILE *file = fopen("my.jpg", "w+");
11 fwrite(mmp[readbuffer.index], readbuffer.length, 1, file);
12 fclose(file);
13
14 ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);//通知驱动内核使用完毕
15 if(ret < 0)
16 {
17 perror("read‐end fail");
18 }
释放映射, 关闭设备
1 //释放映射
2 for(int i=0; i<4; i++)
3 munmap(mmp[i], size[i]);
4 close(fd)

注意:

操作系统一般把系统使用的内存划分成用户空间和内核空间,分别由应用程序管理和操作系统管理。应用程序可以直接访问内存的地址,而内核空间存放的是供内核访问的代码和数据,用户不能直接访问。

v4l2捕获的数据,最初是存放在内核空间的,这意味着用户不能直接访问该段内存,必须通过某些手段来转换地址。

一共有三种视频采集方式:使用read、write方式;内存映射方式和用户指针模式

read、write方式:在用户空间和内核空间不断拷贝数据,占用了大量用户内存空间,效率不高。

内存映射方式:把设备里的内存映射到应用程序中的内存控件,直接处理设备内存,这是一种有效的方式。上面的mmap函数就是使用这种方式。

用户指针模式:内存片段由应用程序自己分配。这点需要在v4l2_requestbuffers里将memory字段设置成V4L2_MEMORY_USERPTR。

处理采集数据

V4L2有一个数据缓存,存放req.count数量的缓存数据。数据缓存采用FIFO的方式,当应用程序调用缓存数据时,缓存队列将最先采集到的视频数据缓存送出,并重新采集一张视频数据。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值