首页 > 编程语言 >Qt/C++音视频开发52-采集本地屏幕桌面的终极设计

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

时间:2023-09-12 09:00:24浏览次数:63  
标签:Qt int 分辨率 C++ 音视频 屏幕 推流 resolution rect

一、前言

最开始设计的时候,只考虑了一个屏幕的情况,这种当然是最理想的情况,实际上双屏或者多屏的用户也不在少数,比如我这两个屏幕,屏幕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;
}

标签:Qt,int,分辨率,C++,音视频,屏幕,推流,resolution,rect
From: https://www.cnblogs.com/feiyangqingyun/p/17695083.html

相关文章

  • C++算法之旅、06 基础篇 | 第四章 动态规划 详解
    常见问题闫式DP分析法状态表示集合满足一定条件的所有方案属性集合(所有方案)的某种属性(Max、Min、Count等)状态计算(集合划分)如何将当前集合划分成多个子集合状态计算相当于集合的划分:把当前集合划分成若干个子集,使得每个子集的状态可以先算出来,从而推导当前......
  • C++ Primer 第一章:一些尝试和认识
    Warning以下是一些非常无聊的知识点,附以肤浅的理解和解释,仅供参考,切勿轻信。C++Primer1.4.4示例代码PS:这段代码没什么用。#include<iostream>intmain(){ intcurrVal=0,val=0; //接收输入流的第一个数 if(std::cin>>currVal){ intcnt=1; //......
  • 音视频
    计算机音视频技术在现代社会中发挥着重要的作用。从在线娱乐到远程教育,从会议交流到虚拟现实,计算机音视频技术已经深入到我们的日常生活和工作中。本文将简要介绍计算机音视频技术的发展历程、应用领域以及未来的发展方向。计算机音视频技术的起源可以追溯到20世纪90年代,当时计算机......
  • C++重载输入和输出运算符
    在C++中,标准库本身已经对左移运算符<<和右移运算符>>分别进行了重载,使其能够用于不同数据的输入输出,但是输入输出的对象只能是C++内置的数据类型(例如bool、int、double等)和标准库所包含的类类型(例如string、complex、ofstream、ifstream等)。如果我们自己定义了一种新的数据类......
  • C++ 优先队列 priority_queue
    既然是队列那么先要包含头文件#include<queue>,他和queue不同的就在于我们可以自定义其中数据的优先级,让优先级高的排在队列前面,优先出队优先队列具有队列的所有特性,包括基本操作,只是在这基础上添加了内部的一个排序,它本质是一个堆实现的和队列基本操作相同:top访问队头......
  • C笔记--c++编译过程
    c++编译过程 参考资料:尚硅谷bilibili视频2023版......
  • C++ 围炉札记
    文章目录内存检测ProtoBufCMake、vscode、clion、Qt右值1、临时变量右值引用2、右值引用本质函数返回std::functionPOD(PlainOldData)thread_localnew/delete1、定位new运算符可变参数模板typename和class1、C++模板类头文件和实现文件分离的方法2、函数显示实例化3、类显示实例......
  • VC++ 知识小结(续)
    1)当文档被修改时,如何在标题上加上标志'*'?重载CDocument类的虚函数virtualSetModifiedFlag:voidCTest2Doc::SetModifiedFlag(BOOLbModified){CStringstrTitle=GetTitle();CStringstrDirtyFlag="*";//notespacebeforethe'*'......
  • Python - PyQt5环境搭建
    前期准备:PyQt5以及其他组件的下载与安装    在python的图形界面开发过程中,我们需要三个组件,分别是:PyQt5、pyqt5-tools、PyQt5Designer,我们直接在命令行输入下面的代码进行安装即可:pipinstallPyQt5pipinstallpyqt5-toolspipinstallPyQt5Designer环境的设置:......
  • 七、PCL&C++相关小知识
    1、智能指针初始化(pcl库)智能指针在用的时候一定要初始化①在函数里面进行初始化pcl::PointCloud<pcl::PointXYZ>::Ptrcloud_source(newpcl::PointCloud<pcl::PointXYZ>)这里的Ptr就是智能指针,所以只看到过cloud的创建部分,而通常没有cloud的delete部分。②在类里面初始化类内部初......