Qt/C++音视频开发52-采集本地屏幕桌面的终极设计

news2025/2/26 21:55:54

一、前言

最开始设计的时候,只考虑了一个屏幕的情况,这种当然是最理想的情况,实际上双屏或者多屏的用户也不在少数,比如我这两个屏幕,屏幕1是1080P,屏幕2是2K分辨率,打印两个屏幕的区域是 QRect(0,0 1920x1030), QRect(1920,-208 2560x1390),可以看到有个负数值(可以在操作系统中的排列显示器拖动调整),而且如果屏幕左右的顺序调换下,比如2K的分辨率在前面,打印的屏幕区域是 QRect(0,0 1920x1030), QRect(-2560,-185 2560x1390),可以看到2K的这个屏幕XY坐标都是负数,你以为这就是所有的情况了吗?那就想错了,还有可能是上下屏幕排列的,2K屏幕在下面打印区域 QRect(0,0 1920x1030), QRect(-639,1080 2560x1390),2K屏幕在上面打印区域是 QRect(0,0 1920x1030), QRect(-270,-1440 2560x1390),这还支持两个屏幕的情况,如果是4个或者更多呢,如果要用户获取到对应屏幕的区域然后填入桌面录制参数中,无异于难于上青天,这肯定是不可能的事情,而ffmpeg默认的参数就是要传入真实的偏移值坐标和分辨率,而用户呢又习惯于在哪个屏幕打开的程序就以当前屏幕的分辨率为基准,偏移值以左上角(0, 0)为基准,所以约定用户只需要填入分辨率和相对偏移值就行,不填入就以当前屏幕整体分辨率为准,这就需要搞一个专门的转换函数,专门获取当前屏幕区域并计算各种情况。

经过上面大费周折的计算,以为可以关机回家吃饭加鸡腿了,又想多了,用户可能输入了超过当前分辨率的区域,或者偏移值加上采集分辨率超过了当前屏幕的分辨率,这样是无法打开的,无法正常采集,程序不会执行,为了能够增强健壮性兼容性,有需要做一些调整,比如计算后发现设定的采集区域尺寸超过了屏幕的真实分辨率尺寸,就以设定的偏移值开始到右下角为准裁剪,这样无论用户怎么错,程序就是不会错,都能正常采集,以合理的方式进行调整,这才是一个好的程序设计。

演示视频:https://www.bilibili.com/video/BV1D8411B7eP

至此已实现的采集桌面的功能:

  • 支持多屏幕,可以指定屏幕索引。
  • 支持左右排列和上下排列以及自由调整屏幕位置。
  • 支持指定采集区域。
  • 自动校正超过屏幕区域的参数设定。
  • 指定相对偏移值采集,以桌面左上角为基准。
  • 支持指定采集帧率。
  • 不填写分辨率和各种参数,自动计算默认值。
  • 不指定屏幕则以鼠标所在当前屏幕为准。
  • 还有更多的细节在代码中体现。

格式说明:

  1. url地址格式说明:desktop=desktop|800x600|25|0|0|0。
  2. 参数用英文竖杠隔开,其中desktop=是固定前缀,用于区分当前地址是用来采集桌面。
  3. 第一个参数表示设备标识,比如win上填desktop,linux填:0.0+0,0,mac填0:0。
  4. 第二个参数表示采集的分辨率,不填则默认取屏幕分辨率。
  5. 第三个参数表示帧率,基本上在2-30之间,不填的话默认ffmpeg会设定一个值,有时候是30。
  6. 第四/五个参数表示偏移值XY坐标,从屏幕的左上角(0,0)开始。
  7. 第六个参数表示屏幕索引,不填的话则默认取当前鼠标所在屏幕。
  8. 写法1:desktop=desktop,当前屏幕全屏采集。
  9. 写法2:desktop=desktop||15|0|0|1,屏幕2全屏采集,帧率15。
  10. 写法3:desktop=desktop|800x600|10|50|100,鼠标所在当前屏幕采集,采集区域rect(50,100,800,600),帧率10。

二、效果图

在这里插入图片描述

三、体验地址

  1. 国内站点:https://gitee.com/feiyangqingyun
  2. 国际站点:https://github.com/feiyangqingyun
  3. 个人作品:https://blog.csdn.net/feiyangqingyun/article/details/97565652
  4. 体验地址:https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g 提取码:01jf 文件名:bin_video_push。

四、功能特点

  1. 支持各种本地视频文件和网络视频文件。
  2. 支持各种网络视频流,网络摄像头,协议包括rtsp、rtmp、http。
  3. 支持将本地摄像头设备推流,可指定分辨率和帧率等。
  4. 支持将本地桌面推流,可指定屏幕区域和帧率等。
  5. 自动启动流媒体服务程序,默认mediamtx(原rtsp-simple-server),可选用srs、EasyDarwin、LiveQing、ZLMediaKit等。
  6. 可实时切换预览视频文件,可切换视频文件播放进度,切换到哪里就推流到哪里。
  7. 推流的清晰度和质量可调。
  8. 可动态添加文件、目录、地址。
  9. 视频文件自动循环推流,如果视频源是视频流,在掉线后会自动重连。
  10. 网络视频流自动重连,重连成功自动继续推流。
  11. 网络视频流实时性极高,延迟极低,延迟时间大概在100ms左右。
  12. 极低CPU占用,4路主码流推流只需要占用0.2%CPU。理论上常规普通PC机器推100路毫无压力,主要性能瓶颈在网络。
  13. 推流可选推流到rtsp/rtmp两种,推流后的数据支持直接rtsp/rtmp/hls/webrtc四种方式访问,可以直接浏览器打开看实时画面。
  14. 可以推流到外网服务器,然后通过手机、电脑、平板等设备播放对应的视频流。
  15. 每个推流都可以手动指定唯一标识符(方便拉流/用户无需记忆复杂的地址),没有指定则按照策略随机生成hash值。
  16. 自动生成测试网页直接打开播放,可以看到实时效果,自动按照数量对应宫格显示。
  17. 推流过程中可以在表格中切换对应推流项,实时预览正在推流的视频,并可以切换视频文件的播放进度。
  18. 音视频同步推流,符合264/265/aac格式的自动原数据推流,不符合的自动转码再推流(会占用一定CPU)。
  19. 转码策略支持三种,自动处理(符合要求的原数据/不符合的转码),仅限文件(文件类型的转码视频),所有转码。
  20. 表格中实时显示每一路推流的分辨率和音视频数据状态,灰色表示没有输入流,黑色表示没有输出流,绿色表示原数据推流,红色表示转码后的数据推流。
  21. 自动重连视频源,自动重连流媒体服务器,保证启动后,推流地址和打开地址都实时重连,只要恢复后立即连上继续采集和推流。
  22. 提供循环推流示例,一个视频源同时推流到多个流媒体服务器,比如打开一个视频同时推流到抖音/快手/B站等,可以作为录播推流,列表循环,非常方便实用。
  23. 根据不同的流媒体服务器类型,自动生成对应的rtsp/rtmp/hls/flv/ws-flv/webrtc地址,用户可以直接复制该地址到播放器或者网页中预览查看。
  24. 编码视频格式可以选择自动处理(源头是264就264/源头是265就265),转H264(强制转264),转H265(强制转265)。
  25. 支持Qt4/Qt5/Qt6任意版本,支持任意系统(windows/linux/macos/android/嵌入式linux等)。

五、相关代码

void AbstractVideoThread::checkDeviceUrl(const QString &url, QString &deviceName, QString &resolution, int &frameRate, int &offsetX, int &offsetY, QString &encodeScale)
{
    //无论是否带分隔符第一个约定是设备名称
    QStringList list = url.split("|");
    int count = list.count();
    deviceName = list.at(0);

    //默认不指定屏幕索引
    int screenIndex = -1;
    //用一个无用的参数作为是否是本地摄像头的标志位
    bool isCamera = (encodeScale == "camera");

    //带分隔符说明还指定了分辨率或帧率
    if (count > 1) {
        QStringList sizes = WidgetHelper::getSizes(list.at(1));
        if (sizes.count() == 2) {
            int width = sizes.at(0).toInt();
            int height = sizes.at(1).toInt();
            resolution = QString("%1x%2").arg(width).arg(height);
        } else {
            resolution = "0x0";
        }

        //第三个参数是帧率
        if (count >= 3) {
            frameRate = list.at(2).toInt();
        }

        //桌面采集还需要取出其他几个参数
        if (!isCamera) {
            //XY坐标偏移值
            if (count >= 5) {
                offsetX = list.at(3).toInt();
                offsetY = list.at(4).toInt();
            }

            //屏幕索引
            if (count >= 6) {
                screenIndex = list.at(5).toInt();
            }

            //视频缩放
            if (count >= 7) {
                encodeScale = list.at(6);
            }

            WidgetHelper::checkRect(screenIndex, resolution, offsetX, offsetY);
        }
    }

    //没有设置分辨率则重新处理
    if (resolution == "0x0") {
        if (isCamera) {
            resolution = "640x480";
        } else {
            WidgetHelper::checkRect(screenIndex, resolution, offsetX, offsetY);
        }
    }
}

QList<QRect> WidgetHelper::getScreenRects()
{
    QList<QRect> rects;
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
    int screenCount = qApp->screens().count();
    QList<QScreen *> screens = qApp->screens();
    for (int i = 0; i < screenCount; ++i) {
        QScreen *screen = screens.at(i);
        rects << screen->geometry();
    }
#else
    int screenCount = qApp->desktop()->screenCount();
    QDesktopWidget *desk = qApp->desktop();
    for (int i = 0; i < screenCount; ++i) {
        rects << desk->screenGeometry(i);
    }
#endif
    return rects;
}

QRect WidgetHelper::getScreenRect(int screenIndex)
{
    //指定了屏幕索引则取指定的(没有指定则取当前鼠标所在屏幕)
    QList<QRect> rects = WidgetHelper::getScreenRects();
    if (screenIndex >= 0 && screenIndex < rects.count()) {
        return rects.at(screenIndex);
    } else {
        //当前屏幕区域包含当前鼠标所在坐标则说明是当前屏幕
        QPoint pos = QCursor::pos();
        foreach (QRect rect, rects) {
            if (rect.contains(pos)) {
                return rect;
            }
        }
    }
}

QString WidgetHelper::getResolution(int width, int height)
{
    //取偶数(虚拟机中很可能是奇数的分辨率)
    if (width % 2 != 0) {
        width--;
    }

    if (height % 2 != 0) {
        height--;
    }

    return QString("%1x%2").arg(width).arg(height);
}

QString WidgetHelper::getResolution(const QString &resolution)
{
    QStringList sizes = WidgetHelper::getSizes(resolution);
    return getResolution(sizes.at(0).toInt(), sizes.at(1).toInt());
}

void WidgetHelper::checkRect(int screenIndex, QString &resolution, int &offsetX, int &offsetY)
{
    QRect rect = WidgetHelper::getScreenRect(screenIndex);
    if (resolution == "0x0") {
        resolution = WidgetHelper::getResolution(rect.width(), rect.height());
    } else {
        resolution = WidgetHelper::getResolution(resolution);
    }

    //偏移值必须小于分辨率否则重置
    if (offsetX > rect.width()) {
        offsetX = 0;
    }
    if (offsetY > rect.height()) {
        offsetY = 0;
    }

    //判断设定的偏移值加上设定的分辨率是否超出了真实的分辨率
    QStringList sizes = WidgetHelper::getSizes(resolution);
    int width = sizes.at(0).toInt();
    int height = sizes.at(1).toInt();

    if (offsetX + width > rect.width()) {
        width = rect.width() - offsetX;
    }
    if (offsetY + height > rect.height()) {
        height = rect.height() - offsetY;
    }

    //如果超出了分辨率则重新设置采集的分辨率
    resolution = WidgetHelper::getResolution(width, height);

    //多个屏幕需要加上屏幕起始坐标
    if (offsetX == 0) {
        offsetX = rect.x();
    } else {
        offsetX += rect.x();
    }
    if (offsetY == 0) {
        offsetY = rect.y();
    } else {
        offsetY += rect.y();
    }

    //qDebug() << TIMEMS << screenIndex << offsetX << offsetY << resolution;
}

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

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

相关文章

【EI会议征稿】2023年智能科学与计算机工程国际学术会议(ISCE 2023)

2023年智能科学与计算机工程国际学术会议&#xff08;ISCE 2023&#xff09; 2023 International Conference on Intelligence Scicence andComputer Engineering 2023年11月3-5日 中国-西双版纳 迄今为止&#xff0c;人工智能研究在一些特殊领域取得了一定的实质性进展。然…

单相并联下垂控原理

Part1 上述有个核心的piont是等效阻抗上的电压一般时很小的&#xff0c;这就导致逆变器输出电压矢量E和负载电压矢量UL之间的夹角很小 》基于上述的结论有助于我们去简化下垂控制的公式&#xff01;&#xff01;&#xff01; Part2 上述得到负载电流&#xff0c;接着乘以负载…

计算机算法分析与设计(1)---求算法时间复杂性(手写例题)

文章目录 一、主定理求解二、递归树求解三、递归树求解含O的递归方程 一、主定理求解 二、递归树求解 三、递归树求解含O的递归方程

后端|一个分布式锁「失效」的案例分析

小猿最近很苦恼&#xff1a;明明加了分布式锁&#xff0c;为什么并发还是会出问题呢&#xff1f; 故事从接到需求开始说起。 接到需求 小猿前一阵接到一个小任务&#xff0c;里面有一个功能对应的场景如下&#xff1a; 封装一个对账户余额进行加减操作的方法&#xff1b;所属服…

shell指令,通过函数实现数组求和,通过函数获取用户uid和gid

一、实现一个对数组求和的函数&#xff0c;数组通过实参传递给函数 num0 read -p "请输入一组数据&#xff1a;" -a arr function add() {for ((i0; i<${#arr[*]}; i))do ((numarr[i]))donereturn $num } add ${arr[*]} echo $? 二、写一个函数&#xff0c;输出…

基于STM32智能环境系统

摘要 本系统采用stm32f407作为主控芯片&#xff0c;实现对环境的监测。并且通过和手机通信&#xff0c;获取当前的天气预报信息&#xff0c;结合当前测得的温湿度&#xff0c;可以为用户提供出行建议。利用stm32自带的RTC可以实现时间及闹钟功能。此外RTC还可以用于电子日历的…

《信息系统项目管理师教程(第4版)》第19章 配置与变更管理 知识点整理 xmind思维导图

已上传xmind思维导图&#xff0c;需要可下载 一、配置管理 基于配置库的变更控制(经常考) 二、变更管理

华为OD七日集训第4期 - 按算法分类,由易到难,循序渐进,玩转OD

目录 一、适合人群二、本期训练时间三、如何参加四、7日集训第4期五、精心挑选21道高频100分经典题目&#xff0c;作为入门。第1天、数据结构第2天、滑动窗口第3天、贪心算法第4天、二分查找第5天、分治递归第6天、深度优先搜索dfs算法第7天、宽度优选算法&#xff0c;回溯法 六…

Mybatis-Plus 使用教程

01-Mybatis-Plus介绍 1.1 什么是mybatis-plus 官网: 简介 | MyBatis-Plus MyBatis-Plus&#xff08;简称 MP&#xff09;是一个 MyBatis 的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 1.2 官方愿景 1.3 特性 无侵入&…

伪谱法地震波场数值模拟

本文实现内容 各向同性介质波动方程伪谱法波场求解。各项异性介质(VTI、HTI)介质伪谱法波场求解。实现了衰减边界条件、拓展周期边界法。一种波场模拟的数据存储格式.sfd&#xff0c;提供二进制或文本输入输出。对波场模拟得到的存储数据进行.gif绘制、.png绘制、地震剖面绘制…

MyBatis核心配置文件解析: 一步步深入理解mybatis-config.xml

&#x1f600;前言 在进行MyBatis项目开发时&#xff0c;合理和高效的配置是确保项目顺利进行的基础。其中&#xff0c;mybatis-config.xml配置文件扮演着极其重要的角色&#xff0c;它包含了MyBatis运行时的各种必要配置信息&#xff0c;如数据库连接属性、事务管理器配置、别…

vector容器的详解与分析

简介&#xff1a; vector容器在高级语言中运用非常广泛&#xff0c;此容器可看成C语言中的动态数组结构用来存储一系列数据&#xff0c;它不仅支持C语言数组中的所有使用方式&#xff0c;还支持vector在C中还有更高级的使用。在C往后的高级运用时&#xff0c;通常把一些常用的操…

基于Java web的医院分诊管理系统文档

摘要 医院分诊管理系统是适应时代发展的需要&#xff0c;提高管理的效率而开发设计的&#xff0c;有效的减少了患者排队取号的时间&#xff0c;增加了医生的工作效率。通过对信息的收集、存储、传递、统计、分析、综合查询、报表输出和信息共享&#xff0c;及时为医院领导及各部…

报错处理:Redis无法连接

报错环境&#xff1a; Linux Redis 具体报错&#xff1a; redis.exceptions.ConnectionError: Error 111 connecting to 127.0.0.1:6379. Connection refused. 排错思路&#xff1a; 当尝试连接Redis服务时&#xff0c;如果出现连接拒绝的错误&#xff0c;可能是由于Redis服务…

修正能力是智能的关键之一

智能包括事前预测、事中干预和事后反馈。这些方面相互关联&#xff0c;共同构成了一个完整的智能系统。 事前预测&#xff1a;智能系统可以通过分析数据、模式识别和机器学习等方法&#xff0c;进行事前预测。它可以根据已有的信息和历史数据&#xff0c;推测未来可能发生的情况…

csdn如何删除已发布的博客内容

首先&#xff0c;将鼠标移动到自己的头像&#xff0c;会显示内容管理 点击内容管理进入下方界面&#xff0c;选择文章&#xff0c;在想要删除的文章的后边的浏览旁边有三个点&#xff0c;点击后选择删除&#xff0c;删除后回到主页面刷新页面&#xff0c;会发现已发布的文章已经…

饲料添加剂 微生物 植物乳杆菌 学习记录

声明 本文是学习GB 7300.502-2023 饲料添加剂 第5部分&#xff1a;微生物 植物乳杆菌. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本文件规定了饲料添加剂植物乳杆菌的技术要求、采样、检验规则、标签、包装、运输、贮存和保质 期&#…

K8S 二进制部署

一、准备规划二、操作系统初始化配置2.1 关闭防火墙2.2 关闭selinux2.3 关闭swap2.4 根据规划设置主机名2.5 在master添加hosts2.6 调整内核参数 三、部署 docker引擎四、部署 etcd 集群4.1 准备签发证书环境4.2 生成Etcd证书4.3 创建用于存放 etcd 配置文件&#xff0c;命令文…

Java“牵手”淘宝商品列表页数据采集+商品价格数据排序,商品销量排序数据采集方法

采集场景 在淘宝首页&#xff08;https://s.taobao.com/&#xff09;输入关键词搜索&#xff0c;采集搜索后得到的商品列表页数据。示例中关键词为【耐克】&#xff0c;可根据需求进行更换&#xff0c;同时支持自动批量输入多个关键词。 采集字段 采集字段包括关键字文本值…

js如何实现一个简单的节流函数?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 实现简单的节流函数⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入…