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

若该文为原创文章,转载请注明原文出处
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/147714800

长沙红胖子Qt(长沙创微智科)博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中…

FFmpeg、SDL和流媒体开发专栏


上一篇:《GStreamer开发笔记(二):GStreamer在ubnutn平台部署安装,测试gstreamer/cheese/ffmpeg/fmplayer打摄像头延迟和内存》
下一篇:持续补充中…


前言

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


Demo

  在这里插入图片描述


注意

  存在色彩空间不准确,不进行细究。


延迟和内存对比

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

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

sudo apt-get install libsdl2-dev libsdl2-2.0-0

  然后写代码,代码贴在Demo里面
  在这里插入图片描述

  在这里插入图片描述

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

  在这里插入图片描述

  查看内存:
  在这里插入图片描述

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

  在这里插入图片描述


最终总结

  到这里,我们得出结论,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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include <errno.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_pixels.h>#define WIDTH 640
#define HEIGHT 480int main() {setbuf(stdout, NULL);int fd;struct v4l2_format fmt;struct v4l2_requestbuffers req;struct v4l2_buffer buf;void *buffer_start;unsigned int buffer_length;// 打开摄像头设备fd = open("/dev/video0", O_RDWR);if (fd == -1) {perror("打开摄像头设备失败");return EXIT_FAILURE;}// 设置视频格式memset(&fmt, 0, sizeof(fmt));fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;fmt.fmt.pix.width = WIDTH;fmt.fmt.pix.height = HEIGHT;fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {perror("设置视频格式失败");close(fd);return EXIT_FAILURE;}// 请求缓冲区memset(&req, 0, sizeof(req));req.count = 1;req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;req.memory = V4L2_MEMORY_MMAP;if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {perror("请求缓冲区失败");close(fd);return EXIT_FAILURE;}// 映射缓冲区memset(&buf, 0, sizeof(buf));buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;buf.index = 0;if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {perror("查询缓冲区失败");close(fd);return EXIT_FAILURE;}buffer_length = buf.length;buffer_start = mmap(NULL, buffer_length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);if (buffer_start == MAP_FAILED) {perror("映射缓冲区失败");close(fd);return EXIT_FAILURE;}// 将缓冲区放入队列if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {perror("缓冲区入队失败");munmap(buffer_start, buffer_length);close(fd);return EXIT_FAILURE;}// 开始视频捕获enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if (ioctl(fd, VIDIOC_STREAMON, &type) == -1) {perror("开始视频捕获失败");munmap(buffer_start, buffer_length);close(fd);return EXIT_FAILURE;}// 初始化 SDLif (SDL_Init(SDL_INIT_VIDEO) < 0) {fprintf(stderr, "SDL 初始化失败: %s\n", SDL_GetError());munmap(buffer_start, buffer_length);close(fd);return EXIT_FAILURE;}SDL_Window *window = SDL_CreateWindow("V4L2 Camera", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WIDTH, HEIGHT, 0);if (!window) {fprintf(stderr, "创建 SDL 窗口失败: %s\n", SDL_GetError());SDL_Quit();munmap(buffer_start, buffer_length);close(fd);return EXIT_FAILURE;}SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, 0);// SDL_PIXELFORMAT_YV12 =      /**< Planar mode: Y + V + U  (3 planes) */// SDL_PIXELFORMAT_IYUV =      /**< Planar mode: Y + U + V  (3 planes) */// SDL_PIXELFORMAT_YUY2 =      /**< Packed mode: Y0+U0+Y1+V0 (1 plane) */// SDL_PIXELFORMAT_UYVY =      /**< Packed mode: U0+Y0+V0+Y1 (1 plane) */// SDL_PIXELFORMAT_YVYU =      /**< Packed mode: Y0+V0+Y1+U0 (1 plane) *///    SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);
//    SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YUY2, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);
//    SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_UYVY, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);
//    SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YVYU, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);int running = 1;SDL_Event event;while (running) {// 处理事件while (SDL_PollEvent(&event)) {if (event.type == SDL_QUIT) {running = 0;}}// 捕获帧memset(&buf, 0, sizeof(buf));buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) {perror("出队缓冲区失败");break;}// 更新 SDL 纹理SDL_UpdateTexture(texture, NULL, buffer_start, WIDTH);// 渲染纹理SDL_RenderClear(renderer);SDL_RenderCopy(renderer, texture, NULL, NULL);SDL_RenderPresent(renderer);// 将缓冲区重新入队if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {perror("缓冲区入队失败");break;}}// 清理资源SDL_DestroyTexture(texture);SDL_DestroyRenderer(renderer);SDL_DestroyWindow(window);SDL_Quit();munmap(buffer_start, buffer_length);close(fd);return EXIT_SUCCESS;
}

Demo:V4l2+QtOpenGL+共享内存

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include <errno.h>
#include "DisplayOpenGLWidget.h"
#include <QApplication>
#include <QElapsedTimer>#define WIDTH 640
#define HEIGHT 480#include <QDebug>
#include <QDateTime>
//#define LOG qDebug()<<__FILE__<<__LINE__
//#define LOG qDebug()<<__FILE__<<__LINE__<<__FUNCTION__
//#define LOG qDebug()<<__FILE__<<__LINE__<<QThread()::currentThread()
//#define LOG qDebug()<<__FILE__<<__LINE__<<QDateTime::currentDateTime().toString("yyyy-MM-dd")
#define LOG qDebug()<<__FILE__<<__LINE__<<QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz")int main(int argc, char *argv[])
{QApplication a(argc, argv);DisplayOpenGLWidget displayOpenGLWidget;displayOpenGLWidget.show();setbuf(stdout, NULL);int fd;struct v4l2_format fmt;struct v4l2_requestbuffers req;struct v4l2_buffer buf;void *buffer_start;unsigned int buffer_length;// 打开摄像头设备fd = open("/dev/video0", O_RDWR);if (fd == -1) {perror("打开摄像头设备失败");return EXIT_FAILURE;}// 设置视频格式memset(&fmt, 0, sizeof(fmt));fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;fmt.fmt.pix.width = WIDTH;fmt.fmt.pix.height = HEIGHT;fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {perror("设置视频格式失败");close(fd);return EXIT_FAILURE;}// 请求缓冲区memset(&req, 0, sizeof(req));req.count = 1;req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;req.memory = V4L2_MEMORY_MMAP;if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {perror("请求缓冲区失败");close(fd);return EXIT_FAILURE;}// 映射缓冲区memset(&buf, 0, sizeof(buf));buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;buf.index = 0;if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {perror("查询缓冲区失败");close(fd);return EXIT_FAILURE;}buffer_length = buf.length;buffer_start = mmap(NULL, buffer_length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);if (buffer_start == MAP_FAILED) {perror("映射缓冲区失败");close(fd);return EXIT_FAILURE;}displayOpenGLWidget.initDrawBuffer(WIDTH, HEIGHT, true, (char *)buffer_start);// 将缓冲区放入队列if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {perror("缓冲区入队失败");munmap(buffer_start, buffer_length);close(fd);return EXIT_FAILURE;}// 开始视频捕获enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if (ioctl(fd, VIDIOC_STREAMON, &type) == -1) {perror("开始视频捕获失败");munmap(buffer_start, buffer_length);close(fd);return EXIT_FAILURE;}int running = 1;while (running) {// 捕获帧memset(&buf, 0, sizeof(buf));buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) {perror("出队缓冲区失败");break;}// 渲染
//        memcpy(drawBuffer, buffer_start, buffer_length);displayOpenGLWidget.displayVideoFrame();QApplication::processEvents();QApplication::processEvents();QApplication::processEvents();// 将缓冲区重新入队if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {perror("缓冲区入队失败");break;}}// 清理资源munmap(buffer_start, buffer_length);close(fd);return EXIT_SUCCESS;
}

入坑

入坑一:v4l2打开视频代码不对

问题

  V4l2打开视频代码数据错位
  在这里插入图片描述

原因

  纹理格式不同,但是笔者测试了SDL所有支持的,都不行,不钻了,是需要进行色彩空间转换下才可以(会额外消耗一定延迟,预估10ms以内),我们选个可以的,测试延迟内存即可。

// SDL_PIXELFORMAT_YV12 =      /**< Planar mode: Y + V + U  (3 planes) */
// SDL_PIXELFORMAT_IYUV =      /**< Planar mode: Y + U + V  (3 planes) */
// SDL_PIXELFORMAT_YUY2 =      /**< Packed mode: Y0+U0+Y1+V0 (1 plane) */
// SDL_PIXELFORMAT_UYVY =      /**< Packed mode: U0+Y0+V0+Y1 (1 plane) */
// SDL_PIXELFORMAT_YVYU =      /**< Packed mode: Y0+V0+Y1+U0 (1 plane) *///    SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);
//    SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YUY2, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);
//    SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_UYVY, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);
//    SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YVYU, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);

解决

  不解决,选择能看清楚的就好了。


上一篇:《GStreamer开发笔记(二):GStreamer在ubnutn平台部署安装,测试gstreamer/cheese/ffmpeg/fmplayer打摄像头延迟和内存》
下一篇:持续补充中…


本文章博客地址:https://blog.csdn.net/qq21497936/article/details/147714800

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/78863.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CATIA高效工作指南——零件建模篇(二)

一、PowerCopy特征复用技术 1.1 智能特征封装 通过​​几何图形集(Geometrical Set)​​构建参数化特征组&#xff0c;将关联的草图、曲面、实体等元素进行逻辑封装。操作流程如下&#xff1a; 创建新几何图形集并完成特征建模激活PowerCopy命令&#xff0c;选择目标几何集定…

CentOS 7 安装OpenJDK 17 JRE

CentOS 7 自带的java 版本为&#xff1a;java version "1.8.0_311"&#xff0c; 有些软件的运行需要更高的java版本。CentOS 7 自带的默认仓库里 没有 OpenJDK 17&#xff0c;但是 Adoptium 项目&#xff08;前身 AdoptOpenJDK&#xff09;提供了稳定的 OpenJDK 17 版…

【c++】 我的世界

太久没更新小游戏了 给个赞和收藏吧&#xff0c;求求了 要游戏的请私聊我 #include <iostream> #include <vector>// 定义世界大小 const int WORLD_WIDTH 20; const int WORLD_HEIGHT 10;// 定义方块类型 enum BlockType {AIR,GRASS,DIRT,STONE };// 定义世界…

angular的cdk组件库

目录 一、虚拟滚动 一、虚拟滚动 <!-- itemSize相当于每个项目的高度为30px --><!-- 需要给虚拟滚动设置宽高&#xff0c;否则无法正常显示 --> <cdk-virtual-scroll-viewport [itemSize]"40" class"view_scroll"><div class"m…

视频编解码学习三之显示器

整理自&#xff1a;显示器_百度百科&#xff0c;触摸屏_百度百科,百度安全验证 分为阴极射线管显示器&#xff08;CRT&#xff09;&#xff0c;等离子显示器PDP&#xff0c;液晶显示器LCD 液晶显示器的组成。一般来说&#xff0c;液晶显示器由以下几个部分组成&#xff1a; […

尚硅谷-硅谷甄选项目记录

一、Vue3 1 基础配置 1.1 路径别名 vite.config.ts import { defineConfig } from vite import vue from vitejs/plugin-vue// 引入path&#xff0c;node提供的模块&#xff0c;可以获取文件或文件夹的路径 import path from pathexport default defineConfig({plugins: […

ThinkPHP5 case when的使用

主表的类型不同&#xff0c;需要连接查询不同的表时&#xff0c;可以使用case when 在主表(上面的表中)的模型里面&#xff0c;写如下代码来获取列表&#xff1a; $res $this->alias("p")->field([p.*,"CASE WHEN p.pd_ticket_type 1 THEN (select s.s…

Android 10.0 SharedPreferences in credential encrypted storage are not avai

1.前言 在10.0的系统rom定制化开发中,在开机的过程中,由于某些应用在开机解锁阶段就开始访问查询短信和联系人等功能,所以 会出现抛异常的情况出现,接下来分析下相关的情况,然后来解决这些问题 2.SharedPreferences in credential encrypted storage are not available …

LeetCode[102]二叉树的层序遍历

思路&#xff1a; 题目描述从左到右一层一层的进行遍历&#xff0c;就遍历二叉树的这种题我更喜欢用递归来做&#xff0c; 我使用java来做的&#xff0c;结果集是两个List集合&#xff0c;那么我们是不是应该每到新的一层就给这个结果集添加一个内部的List&#xff0c;那么怎么…

二种MVCC对比分析

文章目录 前言MVCCInnodb的MVCC版本链回滚与提交可见性判断 Oracle的MVCC版本链 PostgreSQL的MVCCMVCC实现可见性判断特点 前言 ​ MVCC&#xff08;多版本并发控制&#xff0c;Multi-Version Concurrency Control&#xff09;是一种数据库管理系统&#xff08;DBMS&#x…

FAST-LIO笔记

1.FAST-LIO FAST-LIO 是一个计算效率高、鲁棒性强的激光-惯性里程计系统。该系统通过紧耦合的迭代扩展卡尔曼滤波器&#xff08;IEKF&#xff09;将激光雷达特征点与IMU数据进行融合&#xff0c;使其在快速运动、噪声较大或环境复杂、存在退化的情况下仍能实现稳定的导航。 1…

C++并发编程完全指南:从基础到实践

在当今多核处理器普及的时代&#xff0c;充分利用硬件并发能力已成为高性能编程的关键。C11引入的现代并发编程支持使得开发者能够以标准化、可移植的方式编写多线程程序。本文将全面介绍C并发编程的各个方面&#xff0c;从基础概念到实际应用&#xff0c;帮助您掌握这一重要技…

如何使用docker配置ros-noetic环境并使用rviz,gazebo

参考链接&#xff1a;【Ubuntu】Docker中配置ROS并可视化Rviz及Gazebo_docker ros-CSDN博客 前言&#xff1a; 其实这个东西是相当必要的&#xff0c;因为我们有时候需要在一台电脑上跑好几个项目&#xff0c;每个项目都有不同的依赖&#xff0c;这些依赖冲突搞得人头皮发麻&…

使用 Java 11 的 HttpClient 处理 RESTful Web 服务

在现代 Web 开发中,与 RESTful Web 服务交互是一项核心任务。Java 作为一种广泛使用的编程语言,提供了多种处理 HTTP 请求的方法。在 Java 11 之前,开发者通常使用 HttpURLConnection 或第三方库(如 Apache HttpClient)。然而,这些方法要么过于底层,要么需要额外依赖。J…

JVM——垃圾回收

垃圾回收 在Java虚拟机&#xff08;JVM&#xff09;的自动内存管理中&#xff0c;垃圾回收&#xff08;Garbage Collection, GC&#xff09;是其核心组件之一。它负责回收堆内存中不再使用的对象所占用的内存空间&#xff0c;以供新对象的分配使用。下面我们将深入探讨JVM中的…

经典密码学算法实现

# AES-128 加密算法的规范实现&#xff08;不使用外部库&#xff09; # ECB模式S_BOX [0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B,0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0,0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0x…

追踪大型语言模型的思想(下)(来自针对Claude的分析)

多步推理 正如我们上面所讨论的&#xff0c;语言模型回答复杂问题的一种方式就是简单地记住答案。例如&#xff0c;如果问“达拉斯所在州的首府是哪里&#xff1f;”&#xff0c;一个“机械”的模型可以直接学会输出“奥斯汀”&#xff0c;而无需知道德克萨斯州&#xff0c;达拉…

【Hive入门】Hive增量数据导入:基于Sqoop的关系型数据库同步方案深度解析

目录 引言 1 增量数据导入概述 1.1 增量同步与全量同步对比 1.2 增量同步技术选型矩阵 2 Sqoop增量导入原理剖析 2.1 Sqoop架构设计 2.2 增量同步核心机制 3 Sqoop增量模式详解 3.1 append模式&#xff08;基于自增ID&#xff09; 3.2 lastmodified模式&#xff08;基…

[Windows] 蓝山看图王 1.0.3.21021

[Windows] 蓝山看图王 链接&#xff1a;https://pan.xunlei.com/s/VOPejo0dRLFd38dbpR7XA6djA1?pwddq9v# 由蓝山软件出品的一款免费高清看图软件&#xff0c;支持快速查看高清大图&#xff0c;支持大部分市面图片格式。 软件特点 1、体积小巧&#xff0c;图像显示效果清晰…

通配符 DNS 记录:应用场景与相关风险

随着组织的互联网基础设施不断扩展&#xff0c;其对配置、设置和决策的需求也随之增加——从选择一个可靠的名称服务器&#xff0c;到确定合适的 DNS 记录类型以及设置合适的 TTL&#xff08;生存时间&#xff09;值。其中一项关键决策就是是否要创建通配符 DNS 记录&#xff0…