linux音视频采集技术: v4l2

打印 上一主题 下一主题

主题 861|帖子 861|积分 2583

简介

在 Linux 系统中,视频设备的支持和管理离不开 V4L2(Video for Linux 2)。作为 Linux 内核的一部分,V4L2 提供了一套统一的接口,允许开发者与视频设备(如摄像头、视频采集卡等)进行交互。无论是视频采集、处理,还是编码和显示,V4L2 都提供了强盛的支持。本文将简朴介绍一下 V4L2 的工作流程以及怎样利用它进行视频采集。
参数介绍

v4l2并没有提供单独封装的API接口,而是通过 ioctl 系统调用以及v4l2所提供的特定参数来对设备进行控制和采集。
下面是主要的 ioctl 控制参数:
1.VIDIOC_QUERYCAP:查询设备能力。
可用于查询罗列视频设备,获取设备名、总线名、支持的能力等。并非所有设备都支持,有大概会查询失败。
相干结构定义:
  1. struct v4l2_capability {
  2.     __u8 driver[16];      // 驱动名称
  3.     __u8 card[32];        // 设备名称
  4.     __u8 bus_info[32];    // 设备的总线信息
  5.     __u32 version;        // 驱动版本号
  6.     __u32 capabilities;   // 设备支持的功能
  7.     __u32 device_caps;    // 设备的具体能力
  8.     __u32 reserved[3];    // 保留字段
  9. };
复制代码
2.VIDIOC_S_FMT:设置视频格式。
不同设备所支持的 pixelformat 不尽雷同,所以设置的某些格式大概不会见效,好比我利用的海康摄像头只支持mjpg和yuyv。因此最好先利用 VIDIOC_ENUM_FMT 查询设备设备支持的格式,以确保设置见效。
相干结构定义:
  1. struct v4l2_format {
  2.     enum v4l2_buf_type type; // 缓冲区类型(如视频采集)
  3.     union {
  4.         struct v4l2_pix_format pix; // 视频帧格式
  5.         // 其他格式(如 overlay、视频输出等)
  6.     } fmt;
  7. };
  8. struct v4l2_pix_format {
  9.     __u32 width;          // 视频宽度
  10.     __u32 height;         // 视频高度
  11.     __u32 pixelformat;    // 像素格式(如 V4L2_PIX_FMT_YUYV)
  12.     __u32 field;          // 场序(如逐行扫描、隔行扫描)
  13.     __u32 bytesperline;   // 每行的字节数
  14.     __u32 sizeimage;      // 每帧的总字节数
  15.     // 其他字段
  16. };
复制代码
3.VIDIOC_REQBUFS:请求分配缓冲区。
memory类型利用MMAP,后续用于mmap内核缓冲区到用户态,制止内存拷贝
相干结构定义:
  1. struct v4l2_requestbuffers {
  2.     __u32 count;          // 请求的缓冲区数量
  3.     enum v4l2_buf_type type; // 缓冲区类型
  4.     enum v4l2_memory memory; // 内存类型(如 MMAP、USERPTR)
  5.     // 其他字段
  6. };
复制代码
4.VIDIOC_QUERYBUF:查询缓冲区信息。
相干结构定义:
  1. struct v4l2_buffer {
  2.     __u32 index;          // 缓冲区索引
  3.     enum v4l2_buf_type type; // 缓冲区类型
  4.     __u32 bytesused;      // 缓冲区中实际使用的字节数
  5.     __u32 flags;          // 缓冲区标志
  6.     __u32 field;          // 场序
  7.     struct timeval timestamp; // 时间戳
  8.     // 其他字段
  9. };
复制代码
5.VIDIOC_QBUF:将缓冲区加入队列。
将申请的 v4l2_buffer 实例入队
6.VIDIOC_DQBUF:从队列中取出缓冲区。
弹出 v4l2_buffer 实例,并通过mmap映射的地址读取采集数据
7.VIDIOC_STREAMON:开始视频采集。
8.VIDIOC_STREAMOFF:制止视频采集。
9.VIDIOC_ENUM_FMT:罗列设备支持的像素格式。
用于提前罗列支持的图像采集格式,以便选择最合适的采集方式。
相干结构定义:
  1. struct v4l2_fmtdesc {
  2.     __u32 index;             // 格式索引(从 0 开始)
  3.     enum v4l2_buf_type type; // 缓冲区类型(如视频采集)
  4.     __u32 flags;             // 格式标志
  5.     __u8 description[32];    // 格式描述
  6.     __u32 pixelformat;       // 像素格式(如 V4L2_PIX_FMT_YUYV)
  7.     __u32 reserved[4];       // 保留字段
  8. };
复制代码
流程

通常利用的采集流程如下:
1.查询设备能力:利用 VIDIOC_QUERYCAP 查询罗列设备是否支持采集。
2.打开设备:利用 open 打开设备节点。
3.查询设备图像能力:利用 VIDIOC_ENUM_FMT 查询设备支持的像素格式是否匹配。
4.设置视频格式:利用 VIDIOC_S_FMT 设置分辨率、像素格式等。
5.请求缓冲区:利用 VIDIOC_REQBUFS 请求分配缓冲区。
6.映射缓冲区:利用 mmap 将缓冲区映射到用户空间。
7.开始采集:利用 VIDIOC_STREAMON 开始视频采集。
8.采集数据:利用 VIDIOC_DQBUF 从队列中取出缓冲区,处理数据后利用 VIDIOC_QBUF 将缓冲区重新加入队列。
9.制止采集:利用 VIDIOC_STREAMOFF 制止视频采集。
10.释放资源:利用 munmap 释放缓冲区,并关闭设备。
代码示例

  1. #include <iostream>
  2. #include <fcntl.h>
  3. #include <unistd.h>
  4. #include <sys/ioctl.h>
  5. #include <sys/mman.h>
  6. #include <linux/videodev2.h>
  7. #include <fstream>
  8. #include <vector>
  9. #include <cstring>
  10. #define VIDEO_DEVICE "/dev/video0"
  11. #define WIDTH 640
  12. #define HEIGHT 480
  13. #define FPS 30
  14. #define OUTPUT_FILE "output.yuv"
  15. #define BUFFER_COUNT 4 // 缓冲区数量
  16. // 检查 V4L2 调用的返回值
  17. #define CHECK(x) \
  18.     if ((x) < 0) { \
  19.         std::cerr << "ioctl error at " << __FILE__ << ":" << __LINE__ << " - " << strerror(errno) << std::endl; \
  20.         exit(EXIT_FAILURE); \
  21.     }
  22. // 检查设备是否支持指定格式
  23. bool is_format_supported(int fd, unsigned int pixel_format) {
  24.     struct v4l2_fmtdesc fmt_desc = {};
  25.     fmt_desc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  26.     fmt_desc.index = 0;
  27.     while (ioctl(fd, VIDIOC_ENUM_FMT, &fmt_desc) == 0) {
  28.         if (fmt_desc.pixelformat == pixel_format) {
  29.             std::cout << "Device supports format: " << fmt_desc.description << std::endl;
  30.             return true;
  31.         }
  32.         fmt_desc.index++;
  33.     }
  34.     std::cerr << "Device does not support the required format (YUV420)" << std::endl;
  35.     return false;
  36. }
  37. int main() {
  38.     // 打开视频设备
  39.     int fd = open(VIDEO_DEVICE, O_RDWR);
  40.     CHECK(fd);
  41.     // 检查设备是否支持 YUV420P 格式
  42.     if (!is_format_supported(fd, V4L2_PIX_FMT_YUV420)) {
  43.         close(fd);
  44.         return -1;
  45.     }
  46.     // 设置视频格式
  47.     struct v4l2_format fmt = {};
  48.     fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  49.     fmt.fmt.pix.width = WIDTH;
  50.     fmt.fmt.pix.height = HEIGHT;
  51.     fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; // YUV420P 格式
  52.     fmt.fmt.pix.field = V4L2_FIELD_NONE;
  53.     CHECK(ioctl(fd, VIDIOC_S_FMT, &fmt));
  54.     // 检查设备是否实际设置了 YUV420P 格式
  55.     if (fmt.fmt.pix.pixelformat != V4L2_PIX_FMT_YUV420) {
  56.         std::cerr << "Device does not support YUV420P format" << std::endl;
  57.         close(fd);
  58.         return -1;
  59.     }
  60.     // 设置帧率
  61.     struct v4l2_streamparm streamparm = {};
  62.     streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  63.     streamparm.parm.capture.timeperframe.numerator = 1;
  64.     streamparm.parm.capture.timeperframe.denominator = FPS;
  65.     CHECK(ioctl(fd, VIDIOC_S_PARM, &streamparm));
  66.     // 请求缓冲区
  67.     struct v4l2_requestbuffers req = {};
  68.     req.count = BUFFER_COUNT; // 4 个缓冲区
  69.     req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  70.     req.memory = V4L2_MEMORY_MMAP;
  71.     CHECK(ioctl(fd, VIDIOC_REQBUFS, &req));
  72.     // 映射所有缓冲区
  73.     std::vector<unsigned char *> buffers(BUFFER_COUNT);
  74.     std::vector<size_t> buffer_sizes(BUFFER_COUNT);
  75.     for (unsigned int i = 0; i < BUFFER_COUNT; i++) {
  76.         struct v4l2_buffer buf = {};
  77.         buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  78.         buf.memory = V4L2_MEMORY_MMAP;
  79.         buf.index = i;
  80.         CHECK(ioctl(fd, VIDIOC_QUERYBUF, &buf));
  81.         buffers[i] = (unsigned char *)mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
  82.         if (buffers[i] == MAP_FAILED) {
  83.             std::cerr << "Failed to mmap buffer " << i << std::endl;
  84.             close(fd);
  85.             return -1;
  86.         }
  87.         buffer_sizes[i] = buf.length;
  88.         // 将缓冲区加入队列
  89.         CHECK(ioctl(fd, VIDIOC_QBUF, &buf));
  90.     }
  91.     // 开始采集
  92.     enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  93.     CHECK(ioctl(fd, VIDIOC_STREAMON, &type));
  94.     // 打开输出文件
  95.     std::ofstream outfile(OUTPUT_FILE, std::ios::binary);
  96.     if (!outfile) {
  97.         std::cerr << "Failed to open output file" << std::endl;
  98.         close(fd);
  99.         return -1;
  100.     }
  101.     // 采集 100 帧数据并保存到文件
  102.     for (int i = 0; i < 100; i++) {
  103.         struct v4l2_buffer buf = {};
  104.         buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  105.         buf.memory = V4L2_MEMORY_MMAP;
  106.         // 从队列中取出缓冲区
  107.         CHECK(ioctl(fd, VIDIOC_DQBUF, &buf));
  108.         // 将 YUV420P 数据写入文件
  109.         outfile.write((char *)buffers[buf.index], buf.bytesused);
  110.         // 将缓冲区重新加入队列
  111.         CHECK(ioctl(fd, VIDIOC_QBUF, &buf));
  112.     }
  113.     // 停止采集
  114.     CHECK(ioctl(fd, VIDIOC_STREAMOFF, &type));
  115.     // 释放资源
  116.     for (unsigned int i = 0; i < BUFFER_COUNT; i++) {
  117.         munmap(buffers[i], buffer_sizes[i]);
  118.     }
  119.     close(fd);
  120.     outfile.close();
  121.     std::cout << "YUV420P data saved to " << OUTPUT_FILE << std::endl;
  122.     return 0;
  123. }
复制代码
编译前需要先安装v4l2的开发包:
  1. sudo apt install libv4l-dev
复制代码
也可以同时安装v4l2的工具包,用于信息查询:
  1. sudo apt install v4l-utils
复制代码

注:webrtc在linux下提供了两种采集方式,一种是v4l2,另一种是pipewire,感兴趣的可以看一下它们的实现




免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

我爱普洱茶

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表