前言

  前面测试了多种技术路线,本篇补全剩下的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 480 int 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;
} // 初始化 SDL
if (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/v4l2+sdl2/v4l2+QtOpengl打摄像头延迟和内存的更多相关文章

  1. Django开发笔记三

    Django开发笔记一 Django开发笔记二 Django开发笔记三 Django开发笔记四 Django开发笔记五 Django开发笔记六 1.基于类的方式重写登录:views.py: from ...

  2. openwrt gstreamer实例学习笔记(六. gstreamer Pads及其功能)

    一:概述 如我们在Elements一章中看到的那样,Pads是element对外的接口.数据流从一个element的source pad到另一个element的sink pad.pads的功能(cap ...

  3. openwrt gstreamer实例学习笔记(五. gstreamer BUS)

    1)概述 BUS(总线) 是一个简单的系统,它采用自己的线程机制将一个管道线程的消息分发到一个应用程序当中.总线的优势是:当使用GStreamer的时候,应用程序不需要线程识别,即便GStreamer ...

  4. openwrt gstreamer实例学习笔记(七. gstreamer 缓冲区(Buffers)和事件(Events))

    1)概述 管道的数据流由一组缓冲区和事件组成,缓冲区包括实际的管道数据,事件包括控制信息,如寻找信息和流的终止信号.所有这些数据流在运行的时候自动的流过管道. 2) 缓冲区(Buffers) 缓冲区包 ...

  5. openwrt gstreamer实例学习笔记(四. gstreamer Bins)

    1)概述 Bins是一种容器element.你可以往Bins中添加element.由于Bins本身也是一种element,所以你可以像普通element一样 操作Bins.因此,先前关element的 ...

  6. openwrt gstreamer实例学习笔记(二.gstreamer 的 Element)

    对程序员来说,GStreamer 中最重要的一个概念就是 GstElement 对象.该对象是构建一个媒体管道的基本块.所有上层(high-level)部件都源自GstElement对象.任何一个解码 ...

  7. RBL开发笔记三

    2014-08-26 20:06:24 今天就是在开发这个EPOLL来处理网络事件 封装较为健壮的EPOLL模型来处理基本的网络IO 1) 超时这个主题先没有弄 在开发EPOLL包括select/po ...

  8. Vue-cli开发笔记三----------引入外部插件

    (一)绝对路径直接引入: (1)主入口页面index.html中头部script标签引入: <script type="text/javascript" src=" ...

  9. openwrt开发笔记三:uci移植及API调用

    1.uci编译安装.移植 安装依赖 libubox #安装cmake sudo apt-get install cmake #下载依赖库libubox git clone http://git.nbd ...

  10. 钉钉开发笔记(三)MySQL的配置

    最近在编写web的过程中,经常需要与后台工作人员互动.由于比较麻烦.没有效率. 就果断的请教了,公司的后台大牛,学习下数据库的一些简单操作,现在就把利用MySQL连接服务器, 进行可视化操作的简单步骤 ...

随机推荐

  1. centos 8 mysql 更改数据存储位置

    登录mysql后,先切换到myql数据库下通过show global variables like '%datadir%'; 可以查看数据默认的存储路径(一般在 /var/lib/mysql) 新建数 ...

  2. 推荐一款最新开源,基于AI人工智能UI自动化测试工具!支持自然语言编写脚本!

    随着互联网技术的飞速发展,Web应用越来越普及,前端页面也越来越复杂.为了确保产品质量,UI自动化测试成为了开发过程中不可或缺的一环.然而,传统的UI自动化测试工具往往存在学习成本高.维护困难等问题. ...

  3. Deepseek学习随笔(12)--- 清华大学发布第4弹:DeepSeek+DeepResearch让科研像聊天一样简单(附网盘链接)

    一.文档简介 清华大学发布的<DeepSeek+DeepResearch让科研像聊天一样简单>介绍了如何通过DeepSeek和DeepResearch工具简化科研流程,提升研究效率.文件分 ...

  4. 2024CSP-S邮寄

    前言 去年被沉重打击到了,不过从此以后心态就好很多了,不会因为什么考试动不动就崩溃了. 考前 一直在认真复习,也停了课,甚至差点错过运动会.从国庆开始听了几天课,消化课件,然后考试.考试的稳定性不高, ...

  5. Vulnhub-DC-9靶机-SQL注入拿到账户+利用端口敲门连接ssh+信息泄露利用root脚本追加提权

    一.环境搭建 选择扫描虚拟机 选择靶机路径 如果出现以下信息 如下修改,修改和虚拟机一样的版本 二.信息收集 扫ip nmap -sn 192.168.108.0/24 得到靶机ip:192.168. ...

  6. 面试题58 - I. 翻转单词顺序

    地址:https://leetcode-cn.com/problems/fan-zhuan-dan-ci-shun-xu-lcof/ <?php/**输入一个英文句子,翻转句子中单词的顺序,但单 ...

  7. 安卓线性布局LinearLayout

    1.weight权重解读 用法归纳: 按比例划分水平方向:将涉及到的View的android:width属性设置为0dp,然后设置为android weight属性设置比例即可. ` <Line ...

  8. gorm插入报错Error 1292 (22007): Incorrect datetime value: ‘0000-00-00‘ for column ‘xxx‘ at row 1

    在MySQL中,'0000-00-00 00:00:00'不是一个合法的DATETIME值.从MySQL 5.7.5开始,默认情况下不允许插入零日期或零时间值到DATETIME或 TIMESTAMP列 ...

  9. MySql 主从(备)部署 | 冷备份

    前言 MySQL 主从复制(Master-Slave Replication)是一种常见的数据库架构设计,用于提高数据可用性.实现读写分离以及支持备份策略.冷备份是指在数据库关闭状态下进行的数据备份方 ...

  10. PVE虚拟平台常用简明操作,三分钟搞定虚拟机更换安装配置

    Proxmox Virtual Environment是一个基于QEMU/KVM和LXC的开源服务器虚拟化管理解决方案,本文简称PVE,与之相类似的虚拟化平台是VMWARE的ESXi虚拟平台,相较于商 ...