文章
问答
冒泡
基于Qt和mqtt实现一个非常简单的屏幕共享
背景
为什么会选择Qt+mqtt来实现呢?
其实最开始的时候是用electron+webrtc+srs实现的,但是由于webrtc需要交换协议,而electron中的webrtc获取协议内容是通过chromium做的,这块就很难控制,最关键的问题是,electron的包很难下的动,所以就放弃了electron。
后来调研了javafx的方案,基于javaffx+javacv+Zlmediakit的方案,这里的问题是图片合并推流的时候,会形成很大的延迟,除非去优化Zlmediakit ,但是这个是一个很小的功能,不光要考虑开发成本,还需要考虑运维成本。并且,javafx的托盘中文乱码,以及打包成exe的问题,不太好处理,反正我这边是没成功。wix的环境明明安装了,就是说找不到。
flutter的windows方案也简单调研过,但是考虑到这里需要进行桌面截屏,而flutter目前方案还不是很成熟,也放弃了。
最终还是决定用qt的方案,由于需求,只是屏幕共享,之前也调研过远程桌面控制的方案,其实也是基于桌面截屏方案,所以,这里就直接用mqtt的方案来做。
 
Mqtt
Qt 的环境默认是没有mqtt的,这里我们需要自己编译,并加入到环境中
切换到与本地Qt版本号一致的分支,进行构建
构建完成之后,只需要将相关的文件同步赋复制到Qt的环境下就可以。
主要有如下几个目录
  • bin
  • include
  • lib
  • mkspecs
  • modules 
    注意:bin 文件夹下,只需要复制dll文件即可

 
程序代码
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QLabel>
#include <QMainWindow>
#include <QMenu>
#include <QMqttClient>
#include <QScreen>
#include <QSettings>
#include <QSystemTrayIcon>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    QString ipV4();
    void connectMqtt();
    QSettings *settings;
    void screenMonitor();

protected:
    void closeEvent(QCloseEvent *event) override;

private:
    Ui::MainWindow *ui;
    QScreen *screen;
    QLabel *imageLabel;
    QSystemTrayIcon *trayIcon;
    QMenu *trayMenu;
    QString ipv4;
    QMqttClient *mqttClient;
    bool monitoring;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "./ui_mainwindow.h"

#include <QSystemTrayIcon>
#include <QMenu>
#include <QCloseEvent>
#include <QDebug>
#include <QHostInfo>
#include <QLabel>
#include <QCoreApplication>
#include <QGuiApplication>
#include <QTimer>
#include <QBuffer>
#include <QMqttTopicName>
#include <QMqttClient>
#include <QImage>

MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent), ui(new Ui::MainWindow) {
    ui->setupUi(this);
    settings = new QSettings("config.ini", QSettings::IniFormat);
    qDebug() << "mqtt hostname:" << settings->value("mqtt/hostname").toString();
    ipv4 = this->ipV4();
    mqttClient = new QMqttClient(this);
    this->setWindowTitle("屏幕共享:" + ipv4);

    imageLabel = new QLabel(this);


    imageLabel->setScaledContents(true);
    // 设置 QLabel 的大小策略为自动调整大小
    imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
    // 设置 QLabel 在布局中的对齐方式
    imageLabel->setAlignment(Qt::AlignCenter);


    trayIcon = new QSystemTrayIcon(this);
    trayIcon->setIcon(QIcon(":/resources/assets/logo.png"));
    trayIcon->setToolTip("屏幕共享");
    trayMenu = new QMenu(this);

    trayMenu->addAction("打开窗口", this, [&]() {
        this->show();
    });
    trayMenu->addAction("开始", this, [&]() {
        monitoring = true;
    });
    trayMenu->addAction("停止", this, [&]() {
        monitoring = false;
    });
    trayMenu->addAction("退出", this, [&]() {

        QCoreApplication::quit();
    });

    trayIcon->setContextMenu(trayMenu);
    trayIcon->show();

    screen = QGuiApplication::primaryScreen();
    QPixmap screenshot = screen->grabWindow(0);

    imageLabel->setPixmap(screenshot);
    screenshot.scaled(this->size(), Qt::KeepAspectRatio);


    this->setCentralWidget(imageLabel);
    this->setMinimumWidth(920);
    this->setMinimumHeight(540);

    connectMqtt();
    screenMonitor();
}


MainWindow::~MainWindow() {
    delete ui;
}

void MainWindow::closeEvent(QCloseEvent *event) {
    if (trayIcon->isVisible()) {
        hide();
        event->ignore();
    } else {
        event->accept();
    }
}

void MainWindow::screenMonitor() {
    QTimer *timer = new QTimer(this);
    QObject::connect(timer, &QTimer::timeout, [&]() {


        // 截取屏幕内容L
        QPixmap screenshot = screen->grabWindow(0);

        // 设置 QLabel 的图片
        imageLabel->setPixmap(screenshot);
        if (monitoring) {
            QByteArray byteArray;
            QBuffer buffer(&byteArray);
            buffer.open(QIODevice::WriteOnly);
            QImage image = screenshot.toImage();

            qDebug() << "origin size" << image.sizeInBytes();
            int ratio = settings->value("image/ratio").toInt();
            screenshot.save(&buffer, "JPEG", (ratio > 0 ? ratio : 20));  // 可替换为其他图片格式,如 "PNG"

            QString base64 = byteArray.toBase64();
            //qDebug() << base64;
            QMqttTopicName topic("desktop/" + ipv4);
            qDebug() << mqttClient->state();
            qDebug() << base64.toUtf8().size();
            if (mqttClient->state() == QMqttClient::Connected) {
                mqttClient->publish(topic, base64.toUtf8());
            }

        }


    });

    int interval = settings->value("timer/interval").toInt();
    timer->setInterval(interval > 0 ? interval : 200);
    timer->start();
}

QString MainWindow::ipV4() {
    QString hostName = QHostInfo::localHostName();

    // 根据主机名获取主机信息
    QHostInfo hostInfo = QHostInfo::fromName(hostName);

    // 获取IP地址列表
    QList <QHostAddress> addressList = hostInfo.addresses();

    // 遍历IP地址列表
    foreach(
    const QHostAddress &address, addressList) {
        // 判断是否是IPv4地址
        if (address.protocol() == QAbstractSocket::IPv4Protocol) {
            qDebug() << "IPv4 Address:" << address.toString();
            return address.toString();
        }
    }
    return NULL;
}

void MainWindow::connectMqtt() {

    mqttClient->setClientId("desktop_" + ipv4);
    mqttClient->setHostname(settings->value("mqtt/hostname").toString());
    mqttClient->setPort(settings->value("mqtt/port").toInt());
    mqttClient->setUsername(settings->value("mqtt/username").toString());
    mqttClient->setPassword(settings->value("mqtt/password").toString());
    mqttClient->setAutoKeepAlive(true);

    mqttClient->connectToHost();
    QObject::connect(mqttClient, &QMqttClient::connected, [&]() {
        qDebug() << "连接成功";
    });
}
前端使用react + mqttjs 展示图片即可
import {useEffect, useState} from "react";
import * as mqtt from "mqtt";
import {Image} from "antd";

export const IndexPage = () => {
    const [client, setClient] = useState<mqtt.MqttClient>()
    const [image,setImage] = useState<any>()

    useEffect(() => {

        const mqttClient = mqtt.connect("mqtt://localhost:8083/mqtt")
        mqttClient.on("connect", () => {
            mqttClient.subscribe("desktop/192.168.247.1", (err) => {

            });
        });
        mqttClient.on('message',(topic, payload, packet)=>{
            setImage(`data:image/jpeg;base64,${payload}`);
        })


        setClient(mqttClient)
    }, [])

    return <>
        <Image src={image}/>
    </>
}
效果
左侧是前端获得的效果,右侧是屏幕捕捉的效果
0
 
部署
配置windowdeploy的环境变量
设置好部署文件夹
执行部署
然后在目标文件夹下就会看到生成的exe文件
通过windeployqt6 自动复制需要的依赖文件
Qt

关于作者

落雁沙
非典型码农
获得点赞
文章被阅读