找回密码
 立即注册
首页 业界区 业界 GStreamer开发笔记(三):测试gstreamer/v4l2+sdl2/v4l ...

GStreamer开发笔记(三):测试gstreamer/v4l2+sdl2/v4l2+QtOpengl打摄像头延迟和内存

欧阳雪枫 2025-6-2 23:48:55
前言

  前面测试了多种技术路线,本篇补全剩下的2种主流技术,v4l2+sdl2(偏底层),v4l2+QtOpengl(应用),v4l2+ffmpeg+QtQImage(Image的方式转图低于1ms,但是从yuv格式转到rgb格式需要ffmpeg进行转码耗时)。
 Demo

  
1.png

 注意

  存在色彩空间不准确,不进行细究。
 延迟和内存对比

步骤一:v4l2代码测试延迟和内存

  没有找到命令行,只找到了v4l2-ctl可以查看和控制摄像头的参数。
  看gsteamer的源头就是v4l2src,随手写个代码使用v4l2打开摄像头查看延迟,其中v4l2是个框架负责操作和捕获,无法直接进行渲染显示,本次使用了SDL进行显示。
  注意:这里不对v4l2介绍,会有专门的专栏去讲解v4l2的多媒体开发,但是这里使用v4l2的代码写个简单的程序来打开。
  1. sudo apt-get install libsdl2-dev libsdl2-2.0-0
复制代码
  然后写代码,代码贴在Demo里面
  
2.png

  
3.png

步骤二:v4l2+QtOpenGL+memcpy复制一次

  
4.png

  查看内存:
  
5.png

步骤三:v4l2+QtOpenGL+共享内存

  
6.png

 最终总结

  到这里,我们得出结论,gstreamer基本是最优秀的框架之一了,初步测试不是特别严谨,但是基本能反应情况(比如ffmpeg得fmplay本轮测试是最差,但是ffmpeg写代码可以进行ffmpeg源码和编程代码的优化,达到150ms左右,诸如这类情况不考虑)。
  V4l2+SDL优于gstreamer优于ffmplayer优于v4l2+QtOpenGL优于cheese优于ffmpeg。
  其中v4l2+SDL、gstreamer、fmplayer在内存占用上有点区别,延迟差不多130ms左右。Cheese和v4l2+QtOpenGL延迟差不多
到170ms。Ffmpeg的播放器延迟到500ms左右。
 扩展

  这里要注意,大部分低延迟内窥镜笔者接触的都是buffer叠显存的方式,少数厂家使用v4l2+QtOpenGL的方式,经过测试慢了一帧左右。
 Demo:V4l2+SDL
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <fcntl.h>
  5. #include <unistd.h>
  6. #include <sys/ioctl.h>
  7. #include <sys/mman.h>
  8. #include <linux/videodev2.h>
  9. #include <errno.h>
  10. #include <SDL2/SDL.h>
  11. #include <SDL2/SDL_pixels.h>
  12. #define WIDTH 640
  13. #define HEIGHT 480
  14. int main() {
  15.     setbuf(stdout, NULL);
  16.     int fd;
  17.     struct v4l2_format fmt;
  18.     struct v4l2_requestbuffers req;
  19.     struct v4l2_buffer buf;
  20.     void *buffer_start;
  21.     unsigned int buffer_length;
  22.     // 打开摄像头设备
  23.     fd = open("/dev/video0", O_RDWR);
  24.     if (fd == -1) {
  25.         perror("打开摄像头设备失败");
  26.         return EXIT_FAILURE;
  27.     }
  28.     // 设置视频格式
  29.     memset(&fmt, 0, sizeof(fmt));
  30.     fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  31.     fmt.fmt.pix.width = WIDTH;
  32.     fmt.fmt.pix.height = HEIGHT;
  33.     fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
  34.     fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
  35.     if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
  36.         perror("设置视频格式失败");
  37.         close(fd);
  38.         return EXIT_FAILURE;
  39.     }
  40.     // 请求缓冲区
  41.     memset(&req, 0, sizeof(req));
  42.     req.count = 1;
  43.     req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  44.     req.memory = V4L2_MEMORY_MMAP;
  45.     if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
  46.         perror("请求缓冲区失败");
  47.         close(fd);
  48.         return EXIT_FAILURE;
  49.     }
  50.     // 映射缓冲区
  51.     memset(&buf, 0, sizeof(buf));
  52.     buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  53.     buf.memory = V4L2_MEMORY_MMAP;
  54.     buf.index = 0;
  55.     if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {
  56.         perror("查询缓冲区失败");
  57.         close(fd);
  58.         return EXIT_FAILURE;
  59.     }
  60.     buffer_length = buf.length;
  61.     buffer_start = mmap(NULL, buffer_length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
  62.     if (buffer_start == MAP_FAILED) {
  63.         perror("映射缓冲区失败");
  64.         close(fd);
  65.         return EXIT_FAILURE;
  66.     }
  67.     // 将缓冲区放入队列
  68.     if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
  69.         perror("缓冲区入队失败");
  70.         munmap(buffer_start, buffer_length);
  71.         close(fd);
  72.         return EXIT_FAILURE;
  73.     }
  74.     // 开始视频捕获
  75.     enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  76.     if (ioctl(fd, VIDIOC_STREAMON, &type) == -1) {
  77.         perror("开始视频捕获失败");
  78.         munmap(buffer_start, buffer_length);
  79.         close(fd);
  80.         return EXIT_FAILURE;
  81.     }
  82.     // 初始化 SDL
  83.     if (SDL_Init(SDL_INIT_VIDEO) < 0) {
  84.         fprintf(stderr, "SDL 初始化失败: %s\n", SDL_GetError());
  85.         munmap(buffer_start, buffer_length);
  86.         close(fd);
  87.         return EXIT_FAILURE;
  88.     }
  89.     SDL_Window *window = SDL_CreateWindow("V4L2 Camera", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WIDTH, HEIGHT, 0);
  90.     if (!window) {
  91.         fprintf(stderr, "创建 SDL 窗口失败: %s\n", SDL_GetError());
  92.         SDL_Quit();
  93.         munmap(buffer_start, buffer_length);
  94.         close(fd);
  95.         return EXIT_FAILURE;
  96.     }
  97.     SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, 0);
  98.     // SDL_PIXELFORMAT_YV12 =      /**< Planar mode: Y + V + U  (3 planes) */
  99.     // SDL_PIXELFORMAT_IYUV =      /**< Planar mode: Y + U + V  (3 planes) */
  100.     // SDL_PIXELFORMAT_YUY2 =      /**< Packed mode: Y0+U0+Y1+V0 (1 plane) */
  101.     // SDL_PIXELFORMAT_UYVY =      /**< Packed mode: U0+Y0+V0+Y1 (1 plane) */
  102.     // SDL_PIXELFORMAT_YVYU =      /**< Packed mode: Y0+V0+Y1+U0 (1 plane) */
  103. //    SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);
  104. //    SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);
  105.     SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YUY2, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);
  106. //    SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_UYVY, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);
  107. //    SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YVYU, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);
  108.     int running = 1;
  109.     SDL_Event event;
  110.     while (running) {
  111.         // 处理事件
  112.         while (SDL_PollEvent(&event)) {
  113.             if (event.type == SDL_QUIT) {
  114.                 running = 0;
  115.             }
  116.         }
  117.         // 捕获帧
  118.         memset(&buf, 0, sizeof(buf));
  119.         buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  120.         buf.memory = V4L2_MEMORY_MMAP;
  121.         if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) {
  122.             perror("出队缓冲区失败");
  123.             break;
  124.         }
  125.         // 更新 SDL 纹理
  126.         SDL_UpdateTexture(texture, NULL, buffer_start, WIDTH);
  127.         // 渲染纹理
  128.         SDL_RenderClear(renderer);
  129.         SDL_RenderCopy(renderer, texture, NULL, NULL);
  130.         SDL_RenderPresent(renderer);
  131.         // 将缓冲区重新入队
  132.         if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
  133.             perror("缓冲区入队失败");
  134.             break;
  135.         }
  136.     }
  137.     // 清理资源
  138.     SDL_DestroyTexture(texture);
  139.     SDL_DestroyRenderer(renderer);
  140.     SDL_DestroyWindow(window);
  141.     SDL_Quit();
  142.     munmap(buffer_start, buffer_length);
  143.     close(fd);
  144.     return EXIT_SUCCESS;
  145. }
复制代码
 Demo:V4l2+QtOpenGL+共享内存

[code]#include #include #include #include #include #include #include #include #include #include "DisplayOpenGLWidget.h"#include #include #define WIDTH 640#define HEIGHT 480#include #include //#define LOG qDebug()
您需要登录后才可以回帖 登录 | 立即注册