GEC6818 V4L2+USB 摄像头的使用

GEC6818 V4L2摄像头的使用

本文主要涉及GEC6818开发板V4L2的使用,用具体的实例展示了使用一个USB摄像头的使用,其中前面详细讲解了V4L2的相关函数的使用,如果已经知道的可以直接看最后的完整代码。

文章目录

  • GEC6818 V4L2摄像头的使用
    • 一、 V4L2(Video4Linux2)
      • 1.1 结构体:v4l2_capability
      • 1.2 结构体:v4l2_format
      • 1.3 结构体:v4l2_buffer
        • 1.3.1 v4l2_buffer的type字段
      • 1.4 API:ioctl
        • 1.4.1 `ioctl` 的基本使用方式
        • 1.4.2 实例分析
    • 二、 实例使用逐步解析
      • 2.1 打开设备
      • 2.2 获取摄像头功能参数
      • 2.3 获取摄像头支持的格式
      • 2.4 设置视频输入通道
      • 2.5 设置摄像头采集格式
        • 2.5.1 YUYV格式(这个格式需要转化为RGB才可以显示)--转换代码在后面的完整代码中
        • 2.5.2 24位位图(RGB)格式
        • 2.5.3 其他格式的关键字
      • 2.6 与V4L2(Video4Linux2)子系统的交互
      • 2.7 启动摄像头
    • 三、完整代码
      • 3.1 完整代码--YUYV格式
      • 3.2 完整代码--RGB格式
      • 3.3 实现效果

一、 V4L2(Video4Linux2)

V4L2(Video4Linux2)是Linux操作系统中用于处理视频设备的内核框架。V4L2提供了一个统一的API,允许开发者在不同的硬件平台上编写通用的应用程序,从而访问、配置和操作视频设备,如摄像头、USB视频捕获卡等。

以下是有关V4L2摄像头在Linux中的一些关键点和概念:

  1. 设备文件

    • 在Linux中,摄像头通常表示为/dev/videoX,其中X是一个数字,代表摄像头的索引。
    • 例如,第一个检测到的摄像头设备可能是/dev/video0,第二个则可能是/dev/video1,以此类推。
  2. 主要结构和概念

    • v4l2_capability:这是一个结构,用于查询设备的功能。它告诉您设备是否支持视频捕获、视频输出、视频覆盖等功能。
    • v4l2_format:这是一个结构,用于设置和查询视频格式,例如帧大小、格式类型(YUV、RGB等)和帧率。
    • v4l2_buffer:这是一个结构,用于描述单个视频缓冲区的属性和状态。视频帧数据通常存储在这些缓冲区中。
    • ioctl:V4L2 API主要通过ioctl系统调用进行交互。使用不同的ioctl命令和结构体,您可以查询设备能力、设置格式、请求和查询缓冲区等。
  3. 基本操作流程

    • 打开摄像头设备文件(例如/dev/video0)。
    • 查询设备的能力和支持的格式。
    • 设置所需的视频格式。
    • 分配和映射视频缓冲区。
    • 将缓冲区入队以开始视频流捕获。
    • 循环从队列中取出已满的缓冲区,并处理视频帧数据。
    • 将处理后的缓冲区重新入队。

1.1 结构体:v4l2_capability

v4l2_capability 结构体在C语言中的定义如下:

struct v4l2_capability {
    __u8  driver[16];           // 驱动名称或标识符
    __u8  card[32];             // 设备名称或描述
    __u8  bus_info[32];         // 设备连接到的总线信息
    __u32 version;              // V4L2驱动版本
    __u32 capabilities;         // 设备的功能标志位掩码
    __u32 reserved[4];          // 保留字段
};

这个结构体为了节省内存使用了__u8__u32这样的类型,它们通常是无符号整数类型,但确切的大小和类型可能会根据系统架构和编译器而有所不同。

v4l2_capability 结构是用于V4L2(Video4Linux2)API中的一个关键结构,用于查询和描述设备的能力。通过查询这个结构,应用程序可以了解特定摄像头或视频设备支持的功能和特性。

以下是 v4l2_capability 结构中的一些主要字段及其解释:

  1. char driver[16]:

    • 这是一个16字节的字符串,表示设备的驱动名称标识符
  2. char card[32]:

    • 这是一个32字节的字符串,表示设备的名称或描述
  3. __u32 bus_info[32]:

    • 一个32字节的数组,表示设备连接到的总线信息
  4. __u32 version:

    • 表示V4L2驱动的版本号
  5. __u32 capabilities:

    • 这是一个位掩码,用于表示设备支持的各种功能。
    • 一些常见的功能标志包括:
      • V4L2_CAP_VIDEO_CAPTURE: 设备支持视频捕获
      • V4L2_CAP_VIDEO_OUTPUT: 设备支持视频输出
      • V4L2_CAP_VIDEO_OVERLAY: 设备支持视频覆盖
      • V4L2_CAP_STREAMING: 设备支持视频流
      • … 还有其他的功能标志。
  6. __u32 reserved[4]:

    • 一个4元素的保留字段数组,未使用。

当应用程序想要知道摄像头或视频设备的能力时,它会使用ioctl(fd, VIDIOC_QUERYCAP, &cap)系统调用查询该结构。查询结果将填充v4l2_capability结构的字段,应用程序可以基于这些信息进行决策或配置。

例如,如果应用程序需要捕获视频,但摄像头不支持视频捕获功能(即V4L2_CAP_VIDEO_CAPTURE标志未设置),那么应用程序可能会选择一个不同的摄像头或采取其他行动。

1.2 结构体:v4l2_format

v4l2_format 结构体用于描述和设置视频格式,例如视频的帧大小、像素格式等。在V4L2编程中,会经常使用这个结构体来告诉摄像头或视频设备你希望的数据格式

以下是 v4l2_format 结构体的定义和相关参数的详细解释:

struct v4l2_format {
    __u32 type;                 // 数据流类型 (例如:V4L2_BUF_TYPE_VIDEO_CAPTURE 或 V4L2_BUF_TYPE_VIDEO_OUTPUT)
    union {
        struct v4l2_pix_format    pix;      // 像素格式相关的信息
        // 更多的联合字段可以添加到这里,每个字段表示不同类型的数据流格式
    } fmt;
};

其中,union 中的 v4l2_pix_format 结构体用于描述像素格式相关的信息:

struct v4l2_pix_format {
    __u32 width;                 // 帧的宽度
    __u32 height;                // 帧的高度
    __u32 pixelformat;           // 像素格式 (例如:V4L2_PIX_FMT_YUYV 或 V4L2_PIX_FMT_MJPEG)
    __u32 field;                 // 采样格式 (例如:V4L2_FIELD_NONE 或 V4L2_FIELD_INTERLACED)
    __u32 bytesperline;          // 每行的字节数
    __u32 sizeimage;             // 帧大小(以字节为单位)
    __u32 colorspace;            // 颜色空间 (例如:V4L2_COLORSPACE_SRGB)
    __u32 priv;                  // 预留字段(私有数据,可能由特定的驱动程序使用)
    __u32 flags;                 // 标志位,例如 V4L2_PIX_FMT_FLAG_CONTINUOUS_BYTE_STREAM
    __u32 ycbcr_enc;             // YCbCr 编码规范
    __u32 quantization;          // 量化范围
    __u32 xfer_func;             // 传输函数
};

这些字段的具体含义和值会根据所使用的设备和驱动程序而有所不同。当想要设置或查询视频格式时,会使用这些结构体和相关的系统调用,如 ioctl()

1.3 结构体:v4l2_buffer

v4l2_buffer 结构体用于描述视频缓冲区的信息,包括缓冲区的状态、数据的位置等。在V4L2编程中,会用这个结构体来管理视频数据的传输和接收

以下是 v4l2_buffer 结构体的定义及相关参数的详细解释:

struct v4l2_buffer {
    __u32               index;          // 缓冲区的索引号,用于标识特定的缓冲区
    enum v4l2_buf_type  type;           // 缓冲区的类型 (例如:V4L2_BUF_TYPE_VIDEO_CAPTURE)
    __u32               bytesused;      // 缓冲区中实际使用的字节数
    __u32               flags;          // 标志位,例如 V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC
    enum v4l2_field     field;          // 采样的字段 (例如:V4L2_FIELD_NONE 或 V4L2_FIELD_INTERLACED)
    struct timeval      timestamp;      // 时间戳,记录帧的时间信息
    struct v4l2_timecode timecode;       // 时间码,用于视频同步和编辑
    __u32               sequence;       // 用于同步帧的顺序,例如丢帧后恢复顺序
    __u32               memory;         // 缓冲区的内存类型 (例如:V4L2_MEMORY_MMAP 或 V4L2_MEMORY_USERPTR)
    union {
        __u32           offset;         // 内存映射类型 (V4L2_MEMORY_MMAP) 下的偏移量
        unsigned long   userptr;        // 用户指针类型 (V4L2_MEMORY_USERPTR) 下的指针
    } m;
    __u32               length;         // 缓冲区的长度(以字节为单位)
    __u32               input;          // 输入设备的索引号,用于多路复用的设备
    __u32               reserved;       // 保留字段,未使用
};

其中,typememory 字段是两个重要的字段,它们分别指定了缓冲区的类型和内存分配方式:

  • type: 这是一个枚举值,表示缓冲区的类型,例如视频捕获、视频输出等。

  • memory: 这也是一个枚举值,表示缓冲区的内存类型,例如通过内存映射 (V4L2_MEMORY_MMAP) 或用户指针 (V4L2_MEMORY_USERPTR) 进行管理。

1.3.1 v4l2_buffer的type字段

在V4L2(Video4Linux2)中,v4l2_buffer 结构体用于描述视频缓冲区的属性和状态。其中,enum v4l2_buf_type type; 字段用于指定缓冲区的类型。以下是一些常用的缓冲区类型:

  1. V4L2_BUF_TYPE_VIDEO_CAPTURE

    • 表示缓冲区用于视频捕获。
    • 当你想从摄像头或其他视频输入设备捕获视频帧时,你会使用这种类型的缓冲区。
  2. V4L2_BUF_TYPE_VIDEO_OUTPUT

    • 表示缓冲区用于视频输出。
    • 这种类型的缓冲区通常用于播放或发送视频数据。
  3. V4L2_BUF_TYPE_VIDEO_OVERLAY

    • 表示缓冲区用于视频叠加。
    • 用于将视频数据叠加到其他视频流或图像上。
  4. V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY

    • 表示缓冲区同时用于视频输出和叠加。
    • 通常在某些特殊场景中使用。
  5. V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE

    • 表示多平面视频捕获缓冲区。
    • 这是一个多平面缓冲区类型,用于存储多个平面的视频数据。
  6. V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE

    • 表示多平面视频输出缓冲区。
    • 与上述捕获缓冲区类型类似,但是用于视频输出。

这些只是V4L2中可用的一些缓冲区类型。实际上,V4L2提供了更多的缓冲区类型和子类型,以满足不同应用和设备的需求。选择合适的缓冲区类型非常关键,因为它决定了如何配置和使用缓冲区,以及如何处理视频数据。

1.4 API:ioctl

ioctl(Input/Output Control)是Linux系统中的一个系统调用,主要用于设备驱动程序与用户空间应用程序之间进行交互。通过 ioctl,用户空间程序可以发送特定的命令和参数给设备驱动程序,从而控制或查询设备的状态、配置和功能。

1.4.1 ioctl 的基本使用方式

ioctl 的函数原型如下:

int ioctl(int fd, unsigned long request, ...);
  • fd: 是一个打开的文件描述符,通常是对应于某个设备文件的。
  • request: 是一个无符号长整型值,表示要执行的操作或查询。
  • ...: 是可选的参数,用于传递与特定命令相关的数据。
1.4.2 实例分析

假设我们要使用 ioctl 查询一个V4L2设备的能力。

  1. 打开设备:首先,我们需要打开设备文件,并获取其文件描述符。

    int fd_v4l2 = open("/dev/video7", O_RDWR);
    if (fd_v4l2 == -1) {
        perror("Failed to open device");
        exit(EXIT_FAILURE);
    }
    
  2. 查询设备能力:使用 ioctlVIDIOC_QUERYCAP 命令来查询设备的能力。

    struct v4l2_capability cap = {};
    if (ioctl(fd_v4l2, VIDIOC_QUERYCAP, &cap) == -1) {
        perror("ioctl VIDIOC_QUERYCAP failed");
        close(fd_v4l2);
        exit(EXIT_FAILURE);
    }
    

在这个例子中,我们使用了 VIDIOC_QUERYCAP 命令来查询设备的基本信息和能力,并将结果存储在 cap 结构体中。

  1. 处理查询结果:根据 cap 结构体中的信息,我们可以知道设备是否支持视频捕获、视频输出等功能,并据此进行相应的处理。

    if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) {
        printf("Device supports video capture.\n");
    }
    

这只是一个简单的例子,实际的应用场景可能涉及更多的 ioctl 命令和参数,以及复杂的设备配置和操作。

ioctl 提供了一个强大的接口,允许用户空间程序与设备驱动程序进行灵活的交互,从而实现设备的配置、控制和数据传输等功能。

二、 实例使用逐步解析

2.1 打开设备

首先需要确定自己的设备的名称是什么,可以通过SecureCRT进行查看。在目录/dev下面,通过命令ls /dev/video*,通过拔插检测插入自己的摄像头的时候,多出来的设备是哪一个,比如我的就是"videos7".

在Linux中一切皆是文件,所以可以通过open()函数,打开这个设备。

// 打开摄像头
    int fd_v4l2 = open("/dev/video7", O_RDWR); // 根据secureCRT确定设备
    if (fd_v4l2 == -1)
    {
        perror("open error");
        exit(-1);
    }

2.2 获取摄像头功能参数

使用V4L2(Video4Linux2) API查询视频设备(如摄像头)的能力(capabilities)。

 // 获取功能参数
    struct v4l2_capability cap = {};
    int res = ioctl(fd_v4l2, VIDIOC_QUERYCAP, &cap);
    if (res == -1)
    {
        perror("ioctl cap");
        exit(-1);
    }
     // 先确定摄像头功能可以使用
    if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)
    {
        printf("is a capture device!\n");
    }
    else
    {
        printf("is not a capture device!\n");
        exit(-1);
    }
  1. 目的

    • 查询摄像头或其他视频设备的能力,以确定其支持的功能。
  2. 代码解析

    • struct v4l2_capability cap = {};

      • 定义并初始化一个名为capv4l2_capability结构体实例。该结构体用于存储从设备查询到的能力信息。
    • int res = ioctl(fd_v4l2, VIDIOC_QUERYCAP, &cap);

      • 使用ioctl系统调用向fd_v4l2描述的视频设备发送VIDIOC_QUERYCAP命令。
      • &cap参数是一个指向v4l2_capability结构体的指针,表示该结构体将用于接收设备的能力信息。
      • res变量将存储ioctl调用的返回值,通常为0表示成功,-1表示失败。
    • if (res == -1)

      • 检查ioctl调用是否成功。如果res为-1,表示ioctl调用失败。
    • perror("ioctl cap");

      • 打印与ioctl调用相关的错误消息。在这里,如果ioctl调用失败,它将打印一个描述错误的消息。
    • exit(-1);

      • 如果ioctl调用失败,则程序会提前退出。
  3. 参数说明

    • fd_v4l2:这是一个文件描述符,用于打开和访问V4L2设备(例如摄像头)。这里fd_v4l2已经是一个有效的、已经打开的设备描述符。
    • VIDIOC_QUERYCAP:这是一个命令常量,表示要查询设备的能力。
    • &cap:这是一个指向v4l2_capability结构体的指针,用于存储从设备查询到的能力信息。

这段代码的目的是通过ioctl调用查询并获取视频设备(如摄像头)的能力信息,并将这些信息存储在v4l2_capability结构体中。

2.3 获取摄像头支持的格式

// 获取摄像头支持的格式
    struct v4l2_fmtdesc fmt = {};
    fmt.index = 0;
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 获取摄像头格式
    while ((res = ioctl(fd_v4l2, VIDIOC_ENUM_FMT, &fmt)) == 0)
    
    {
        printf("pixformat = %c %c %c %c,description = %s\n",
                fmt.pixelformat & 0xff,lcd_p
                (fmt.pixelformat >> 8) & 0xff,
                (fmt.pixelformat >> 16) & 0xff,
                (fmt.pixelformat >> 24) & 0xff,
                fmt.description);
        fmt.index++;
    }

这段代码用于获取摄像头支持的视频格式。以下是详细的解释:

  1. 目的

    • 查询并列出摄像头支持的所有视频格式。
  2. 代码解析

    • struct v4l2_fmtdesc fmt = {};
      • 声明并初始化一个v4l2_fmtdesc类型的结构体fmt,用于描述摄像头支持的视频格式。
    • fmt.index = 0;
      • 设置要查询的视频格式的索引为0,意味着从第一个视频格式开始查询。
    • fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
      • 指定查询的是视频捕获类型的视频格式。
    • while ((res = ioctl(fd_v4l2, VIDIOC_ENUM_FMT, &fmt)) == 0)
      • 使用ioctl系统调用查询摄像头的视频格式。
      • VIDIOC_ENUM_FMT是一个请求命令,用于获取指定类型的视频格式。
      • 循环会在成功获取一个视频格式后继续执行,直到没有更多的格式可以获取。
    • 在循环内部,使用printf打印每种视频格式的描述信息:
      • fmt.pixelformat & 0xff:提取四个字节中的第一个字节,即低位字节。
      • (fmt.pixelformat >> 8) & 0xff:提取四个字节中的第二个字节。
      • (fmt.pixelformat >> 16) & 0xff:提取四个字节中的第三个字节。
      • (fmt.pixelformat >> 24) & 0xff:提取四个字节中的第四个字节,即高位字节。
      • fmt.description:视频格式的描述信息。
  3. 参数说明

    • fd_v4l2:摄像头设备文件描述符。
    • VIDIOC_ENUM_FMT:用于查询摄像头支持的视频格式的ioctl请求命令。
    • fmt:一个v4l2_fmtdesc类型的结构体,用于存储和描述视频格式的详细信息。
    • fmt.index:视频格式的索引,从0开始逐个增加以获取所有格式。
    • fmt.type:指定要查询的视频类型,此处为视频捕获类型。

总结:这段代码用于查询摄像头支持的所有视频格式,并逐一打印每种格式的描述信息。

2.4 设置视频输入通道

// 设置采集通道
    int index = 0; // 使用通道0
    res = ioctl(fd_v4l2, VIDIOC_S_INPUT, &index);
    if (res == -1)
    {
        perror("ioctl_s_input");
        exit(-1);
    }

具体来说:

  • VIDIOC_S_INPUT 是一个 ioctl 命令,用于设置视频输入通道
  • &index 是传递给 ioctl 的参数,表示要设置的输入通道的索引。在这里,索引为 0 表示设置为第一个视频输入通道。

此外,如果 ioctl 调用失败(返回值为 -1),代码会输出一个错误消息,并退出程序。

简而言之,这段代码的作用是设置摄像头的视频输入通道为第一个通道。

2.5 设置摄像头采集格式

2.5.1 YUYV格式(这个格式需要转化为RGB才可以显示)–转换代码在后面的完整代码中
// 设置摄像头采集格式V4L2_PIX_FMT_YUYV格式
    struct v4l2_format format = {};
    format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//指定了要设置的视频流类型为视频捕获
    format.fmt.pix.width = 640;
    format.fmt.pix.height = 480;
    format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // YUYV
    format.fmt.pix.field = V4L2_FIELD_NONE;//设置视频的字段顺序为无,这意味着视频数据不分为上半部分和下半部分,而是连续的完整帧。
    res = ioctl(fd_v4l2, VIDIOC_S_FMT, &format);//调用来设置摄像头的视频格式。VIDIOC_S_FMT是设置格式的命令。
    if (res == -1)
    {
        perror("ioctl s_fmt");
        exit(-1);
    }

上面的视频格式为YUYV格式的。YUYV是一种常见的YUV 4:2:2格式,其中Y表示亮度,U和V表示色度,而“YUYV”表示这些组件的排列方式。

2.5.2 24位位图(RGB)格式
// 设置摄像头采集格式--24位RGB图
struct v4l2_format format = {};
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
format.fmt.pix.width = 640;
format.fmt.pix.height = 480;
format.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24; // 设置为RGB24
format.fmt.pix.field = V4L2_FIELD_NONE;
res = ioctl(fd_v4l2, VIDIOC_S_FMT, &format);
if (res == -1)
{
    perror("ioctl s_fmt");
    exit(-1);
}

2.5.3 其他格式的关键字

除开上面举例的两种格式,其实还有很多其他的格式:

V4L2(Video4Linux2)框架支持多种像素格式,具体取决于摄像头硬件和驱动的支持。YUYV(也称为YUV 4:2:2)是其中的一种常见格式,但还有其他的格式可以选择。

以下是一些常见的V4L2像素格式:

  1. V4L2_PIX_FMT_RGB24:每像素24位的RGB格式。
  2. V4L2_PIX_FMT_BGR24:每像素24位的BGR格式。
  3. V4L2_PIX_FMT_YUV420:YUV 4:2:0格式,其中Y的采样率最高,而U和V的采样率较低。
  4. V4L2_PIX_FMT_NV12:这是另一种YUV 4:2:0格式,但与YUV 4:2:0格式的主要区别在于UV平面的存储方式。
  5. V4L2_PIX_FMT_MJPEG:MJPEG(Motion JPEG)格式,每帧都是一张JPEG图片,适合于静态场景和需要更好压缩比的应用。

此外,还有许多其他的格式,如YUV422、YUV411、YUV410等,具体可根据需求和硬件支持进行选择。

在使用VIDIOC_ENUM_FMT命令查询摄像头支持的所有格式时,可以看到摄像头支持哪些格式。

2.6 与V4L2(Video4Linux2)子系统的交互

主要涉及与V4L2(Video4Linux2)子系统的交互,用于在Linux上捕获视频流。

  1. 申请缓存空间:

    struct v4l2_requestbuffers req = {};
    req.count = 4;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;
    res = ioctl(fd_v4l2, VIDIOC_REQBUFS, &req);
    if (res == -1)
    {
        perror("ioctl reqbufs");
        exit(-1);
    }
    // 申请临时缓冲区
    current.start = malloc(max_len);
    if (current.start == NULL)
    {
        perror("malloc");
        exit(-1);
    }
    
    • 使用v4l2_requestbuffers结构体来请求缓冲区。
    • count = 4:请求4个缓冲区,通常是为了连续地处理视频帧。
    • type:请求的缓冲区类型为视频捕获。
    • memory:请求使用内存映射I/O (MMAP) 方式管理缓冲区。
  2. 分配映射入队:

    size_t i, max_len = 0;
    for (i = 0; i < 4; i++)
    {
        struct v4l2_buffer buf = {};
        buf.index = i;
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        res = ioctl(fd_v4l2, VIDIOC_QUERYBUF, &buf);
         if (res == -1)
        {
            perror("ioctl querybuf");
            exit(-1);
        }
        // 判读并记录最大长度,以适配各个图帧
        if (buf.length > max_len)
            max_len = buf.length;
        // 映射
        buffer[i].length = buf.length;
        buffer[i].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd_v4l2, buf.m.offset);
        if (buffer[i].start == MAP_FAILED)
        {
            perror("mmap");
            exit(-1);
        }
        // 入队
        res = ioctl(fd_v4l2, VIDIOC_QBUF, &buf);
        if (res == -1)
        {
            perror("ioctl qbuf");
            exit(-1);
        }
    }
    
    • 使用循环为每个请求的缓冲区分配并映射内存。
    • VIDIOC_QUERYBUF命令会为每个缓冲区返回其信息,如长度和偏移。
    • 使用mmap函数映射设备内存到用户空间。这使得用户空间程序可以直接访问设备内存,这在处理视频流时非常有用。
    • 使用VIDIOC_QBUF命令将缓冲区加入到驱动的输入队列中,等待被填充数据。

总的来说,这段代码的目标是为V4L2捕获视频流准备缓冲区,并将这些缓冲区映射到用户空间,以便应用程序可以直接读取或写入这些缓冲区的内容。

2.7 启动摄像头

 // 启动摄像头
    enum v4l2_buf_type buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    res = ioctl(fd_v4l2, VIDIOC_STREAMON, &buf_type);
    if (res == -1)
    {
        perror("ioctl streamon");
        exit(-1);
    }
    // 延时
    sleep(1);

启动摄像头数据流,并在启动后延迟1秒。

  • VIDIOC_STREAMON: 这是一个ioctl操作,用于开始摄像头的数据流。一旦这个操作被调用,摄像头开始从物理硬件捕获图像数据。

  • sleep(1): 这是一个简单的延时操作,使得程序暂停1秒。

在摄像头启动后延迟1秒可能是为了确保摄像头开始稳定工作,或者为了某种初始化操作。

如果应用中有特定的原因需要延迟1秒,那么这是一个合适的做法。如果没有,可能可以考虑移除这个延时或者根据实际需求调整延时时间。

三、完整代码

3.1 完整代码–YUYV格式

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include         //  TCP/IP协议所需头文件
#include 

typedef struct
{
    char *start;
    size_t length;
} buffer_t;

buffer_t buffer[4]; // 映射所需的素材缓存在数组中
buffer_t current;   // 保存当前输出的一帧

int *plcd;  // 用于存储屏幕缓冲区的首地址,以便后续对屏幕进行读写操作
int *lcd_p; // 指向屏幕缓冲区的特定位置
int lcd_fd; // 用于存储屏幕设备文件的文件描述符

void *lcd_init()
{
    lcd_fd = open("/dev/fb0", O_RDWR);
    if (lcd_fd == -1)
    {
        perror("open lcd_file error\n");
        return MAP_FAILED;
    }
    plcd = (int *)mmap(NULL, 800 * 480 * 4, PROT_READ | PROT_WRITE, MAP_SHARED, lcd_fd, 0);
    return plcd;
}

int uninit_lcd()
{
    close(lcd_fd);
    if (munmap(lcd_p, 800 * 480 * 4) == -1)
    {
        return -1;
    }
    return 0;
}

unsigned int sign3 = 0;
//YUYV转rgb
int yuyv2rgb(int y, int u, int v)
{
    unsigned int pixel24 = 0;
    unsigned char *pixel = (unsigned char *)&pixel24;
    int r, g, b;
    static int ruv, guv, buv;
    if (sign3)
    {
        sign3 = 0;
        ruv = 1159 * (v - 128);
        guv = 380 * (u - 128) + 813 * (v - 128);
        buv = 2018 * (u - 128);
    }
    r = (1164 * (y - 16) + ruv) / 1000;
    g = (1164 * (y - 16) - guv) / 1000;
    b = (1164 * (y - 16) + buv) / 1000;
    if (r > 255)
        r = 255;
    if (g > 255)
        g = 255;
    if (b > 255)
        b = 255;
    if (r < 0)
        r = 0;
    if (g < 0)
        g = 0;
    if (b < 0)
        b = 0;
    pixel[0] = r;
    pixel[1] = g;
    pixel[2] = b;
    return pixel24;
}

//YUYV转rgb(调用yuyv2rgb函数)
int yuyv2rgb0(unsigned char *yuv, unsigned char *rgb, unsigned int width, unsigned int height)
{
    unsigned int in, out;
    int y0, u, y1, v;
    unsigned int pixel24;
    unsigned char *pixel = (unsigned char *)&pixel24;
    unsigned int size = width * height * 2;
    for (in = 0, out = 0; in < size; in += 4, out += 6)
    {
        y0 = yuv[in + 0];
        u = yuv[in + 1];
        y1 = yuv[in + 2];
        v = yuv[in + 3];
        sign3 = 1;
        pixel24 = yuyv2rgb(y0, u, v);
        rgb[out + 0] = pixel[0];
        rgb[out + 1] = pixel[1];
        rgb[out + 2] = pixel[2];
        pixel24 = yuyv2rgb(y1, u, v);
        rgb[out + 3] = pixel[0];
        rgb[out + 4] = pixel[1];
        rgb[out + 5] = pixel[2];
    }
    return 0;
}
int main()
{
    lcd_init();
    // 打开摄像头
    int fd_v4l2 = open("/dev/video7", O_RDWR); // 根据secureCRT确定设备
    if (fd_v4l2 == -1)
    {
        perror("open error");
        exit(-1);
    }
    // 获取功能参数
    struct v4l2_capability cap = {};
    int res = ioctl(fd_v4l2, VIDIOC_QUERYCAP, &cap);
    if (res == -1)
    {
        perror("ioctl cap");
        exit(-1);
    }
    // 先确定摄像头功能可以使用
    if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)
    {
        printf("is a capture device!\n");
    }
    else
    {
        printf("is not a capture device!\n");
        exit(-1);
    }
    // 获取摄像头支持的格式
    struct v4l2_fmtdesc fmt = {};
    fmt.index = 0;
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 获取摄像头格式
    while ((res = ioctl(fd_v4l2, VIDIOC_ENUM_FMT, &fmt)) == 0)
    
    {
        printf("pixformat = %c %c %c %c,description = %s\n",
                fmt.pixelformat & 0xff,lcd_p
                (fmt.pixelformat >> 8) & 0xff,
                (fmt.pixelformat >> 16) & 0xff,
                (fmt.pixelformat >> 24) & 0xff,
                fmt.description);
        fmt.index++;
    }
    // 设置采集通道
    int index = 0; // 使用通道0
    res = ioctl(fd_v4l2, VIDIOC_S_INPUT, &index);
    if (res == -1)
    {
        perror("ioctl_s_input");
        exit(-1);
    }
    // 设置摄像头采集格式
    struct v4l2_format format = {};
    format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    format.fmt.pix.width = 640;
    format.fmt.pix.height = 480;
    format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // YUYV
    format.fmt.pix.field = V4L2_FIELD_NONE;
    res = ioctl(fd_v4l2, VIDIOC_S_FMT, &format);
    if (res == -1)
    {
        perror("ioctl s_fmt");
        exit(-1);
    }
    // 申请缓存空间
    struct v4l2_requestbuffers req = {};
    req.count = 4;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;
    res = ioctl(fd_v4l2, VIDIOC_REQBUFS, &req);
    if (res == -1)
    {
        perror("ioctl reqbufs");
        exit(-1);
    }
    // 分配映射入队
    size_t i, max_len = 0;
    for (i = 0; i < 4; i++)
    {
        struct v4l2_buffer buf = {};
        buf.index = i; // 0~3展现4帧图片
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        res = ioctl(fd_v4l2, VIDIOC_QUERYBUF, &buf);
        if (res == -1)
        {
            perror("ioctl querybuf");
            exit(-1);
        }
        // 判读并记录最大长度,以适配各个图帧
        if (buf.length > max_len)
            max_len = buf.length;
        // 映射
        buffer[i].length = buf.length;
        buffer[i].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd_v4l2, buf.m.offset);
        if (buffer[i].start == MAP_FAILED)
        {
            perror("mmap");
            exit(-1);
        }
        // 入队
        res = ioctl(fd_v4l2, VIDIOC_QBUF, &buf);
        if (res == -1)
        {
            perror("ioctl qbuf");
            exit(-1);
        }
    }
    // 申请临时缓冲区
    current.start = malloc(max_len);
    if (current.start == NULL)
    {
        perror("malloc");
        exit(-1);
    }
    // 启动摄像头
    enum v4l2_buf_type buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    res = ioctl(fd_v4l2, VIDIOC_STREAMON, &buf_type);
    if (res == -1)
    {
        perror("ioctl streamon");
        exit(-1);
    }
    // 延时
    sleep(1);
    // RGB缓冲区
    char rgb[640 * 480 * 3];
    // 采集数据
    while (1)
    {
        struct v4l2_buffer buf = {};
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        // 出队
        res = ioctl(fd_v4l2, VIDIOC_DQBUF, &buf);
        if (res == -1)
        {
            perror("ioctl dqbuf");
        }
        // 拷贝数据
        memcpy(current.start, buffer[buf.index].start, buf.bytesused);
        current.length = buf.bytesused;
        // 入队
        res = ioctl(fd_v4l2, VIDIOC_QBUF, &buf);
        if (res == -1)
        {
            perror("ioctl qbuf");
        }
        // YUYV转RGB
        yuyv2rgb0(current.start, rgb, 640, 480);
        // 显示LCD
        int x, y;
        for (y = 0; y < 480; y++)
        {
            for (x = 0; x < 640; x++)
            {
                *(plcd + y*800 + x) = rgb[3 * (y*640 + x)] << 16 | rgb[3 * (y*640 + x) + 1] << 8 | rgb[3 * (y*640 + x) + 2];
            }
        }
    }
    // 关闭摄像头采集
    buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    res = ioctl(fd_v4l2, VIDIOC_STREAMOFF, &buf_type);
    if (res == -1)
    {
        perror("ioctl streamoff");
        exit(-1);
    }
    // 解除映射
    for (i = 0; i < 4; i++)
    {
        munmap(buffer[i].start, buffer[i].length);
    }
    free(current.start);
    sleep(1); // 延时一下
    close(fd_v4l2);
    uninit_lcd();
    return 0;
}

3.2 完整代码–RGB格式

这之前的代码与前面的雷同,只是不需要YUYV转换RGB的两个函数了

// 设置摄像头采集格式
struct v4l2_format format = {};
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
format.fmt.pix.width = 640;
format.fmt.pix.height = 480;
format.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24; // 设置为RGB24
format.fmt.pix.field = V4L2_FIELD_NONE;
res = ioctl(fd_v4l2, VIDIOC_S_FMT, &format);
if (res == -1)
{
    perror("ioctl s_fmt");
    exit(-1);
}
...//与上面YUYV的代码雷同


// 采集数据

while (1)
{
    struct v4l2_buffer buf = {};
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    
    // 出队
    res = ioctl(fd_v4l2, VIDIOC_DQBUF, &buf);
    if (res == -1)
    {
        perror("ioctl dqbuf");
    }
    
    // 拷贝数据
    memcpy(current.start, buffer[buf.index].start, buf.bytesused);
    current.length = buf.bytesused;
    
    // 入队
    res = ioctl(fd_v4l2, VIDIOC_QBUF, &buf);
    if (res == -1)
    {
        perror("ioctl qbuf");
    }
    
    // 直接显示LCD (假设摄像头已经输出24位RGB数据)
    memcpy(plcd, current.start, current.length);  // 直接将数据拷贝到LCD缓冲区
}

3.3 实现效果

你可能感兴趣的:(GEC6818,GEC6818,嵌入式硬件,单片机,学习,粤嵌,V4L2,摄像头)