Bootstrap

Qt C++ 的电视远程投屏程序

功能需求分解

设备发现
使用局域网广播(UDP)发现支持投屏的电视设备。

广播消息内容为一个简单的字符串标识,如 “DISCOVER_TV_DEVICES”。
电视设备需响应此消息,并回传自己的 IP 地址、名称等信息。
设备连接
使用 TCP 连接选中的设备,并准备发送屏幕图像数据。

连接成功后,保持长连接,用于持续发送帧数据。
屏幕捕获
捕获当前主屏幕画面(可选部分屏幕),调整分辨率并压缩为合适的格式(如 JPEG)。

数据传输
编码后的数据通过 TCP Socket 发送到目标设备。

横屏/竖屏切换
通过旋转图像数据实现横屏与竖屏的调整。

更详细的代码实现

  1. 主程序框架
#include <QApplication>
#include <QMainWindow>
#include <QUdpSocket>
#include <QTcpSocket>
#include <QScreen>
#include <QImage>
#include <QTimer>
#include <QListWidget>
#include <QPushButton>
#include <QVBoxLayout>
#include <QMessageBox>

class ScreenCaster : public QMainWindow {
    Q_OBJECT

public:
    explicit ScreenCaster(QWidget *parent = nullptr) : QMainWindow(parent) {
        setupUI();
        setupConnections();
    }

private:
    QUdpSocket *udpSocket;      // 用于设备发现的 UDP Socket
    QTcpSocket *tcpSocket;      // 用于数据传输的 TCP Socket
    QTimer *captureTimer;       // 定时捕获屏幕的计时器
    QListWidget *deviceList;    // 用于显示发现的设备列表
    QPushButton *discoverButton, *connectButton, *landscapeButton, *portraitButton;
    QString targetIP;           // 目标设备 IP 地址
    int targetPort = 12345;     // 目标设备端口号
    bool isPortrait = false;    // 当前是否为竖屏模式

    void setupUI() {
        // 创建主窗口 UI
        QWidget *centralWidget = new QWidget(this);
        QVBoxLayout *layout = new QVBoxLayout(centralWidget);

        deviceList = new QListWidget(this);
        discoverButton = new QPushButton("发现设备", this);
        connectButton = new QPushButton("连接设备", this);
        landscapeButton = new QPushButton("横屏模式", this);
        portraitButton = new QPushButton("竖屏模式", this);

        layout->addWidget(deviceList);
        layout->addWidget(discoverButton);
        layout->addWidget(connectButton);
        layout->addWidget(landscapeButton);
        layout->addWidget(portraitButton);

        this->setCentralWidget(centralWidget);
        this->setWindowTitle("电视远程投屏");
        this->resize(400, 400);
    }

    void setupConnections() {
        udpSocket = new QUdpSocket(this);
        tcpSocket = new QTcpSocket(this);
        captureTimer = new QTimer(this);

        // 按钮事件连接
        connect(discoverButton, &QPushButton::clicked, this, &ScreenCaster::discoverDevices);
        connect(connectButton, &QPushButton::clicked, this, &ScreenCaster::connectToSelectedDevice);
        connect(landscapeButton, &QPushButton::clicked, this, &ScreenCaster::switchToLandscape);
        connect(portraitButton, &QPushButton::clicked, this, &ScreenCaster::switchToPortrait);

        // 定时器连接
        connect(captureTimer, &QTimer::timeout, this, &ScreenCaster::captureAndSendScreen);

        // 处理 UDP 消息
        connect(udpSocket, &QUdpSocket::readyRead, this, &ScreenCaster::processDeviceDiscovery);
    }

    void discoverDevices() {
        // 广播设备发现请求
        QByteArray data("DISCOVER_TV_DEVICES");
        udpSocket->writeDatagram(data, QHostAddress::Broadcast, targetPort);
        QMessageBox::information(this, "提示", "正在发现设备...");
    }

    void processDeviceDiscovery() {
        // 处理设备返回的发现信息
        while (udpSocket->hasPendingDatagrams()) {
            QByteArray buffer;
            buffer.resize(udpSocket->pendingDatagramSize());
            QHostAddress senderAddress;
            udpSocket->readDatagram(buffer.data(), buffer.size(), &senderAddress);

            QString deviceInfo = QString("%1 (%2)").arg(QString(buffer), senderAddress.toString());
            if (deviceList->findItems(deviceInfo, Qt::MatchExactly).isEmpty()) {
                deviceList->addItem(deviceInfo);
            }
        }
    }

    void connectToSelectedDevice() {
        // 获取选中的设备
        QListWidgetItem *selectedItem = deviceList->currentItem();
        if (!selectedItem) {
            QMessageBox::warning(this, "警告", "请先选择一个设备!");
            return;
        }

        QStringList parts = selectedItem->text().split(' ');
        targetIP = parts.last().remove('(').remove(')');
        tcpSocket->connectToHost(targetIP, targetPort);

        if (tcpSocket->waitForConnected()) {
            QMessageBox::information(this, "成功", "连接设备成功!");
            captureTimer->start(1000 / 30); // 每秒捕获 30 帧
        } else {
            QMessageBox::critical(this, "失败", "连接设备失败!");
        }
    }

    void captureAndSendScreen() {
        // 捕获屏幕内容
        QScreen *screen = QApplication::primaryScreen();
        if (!screen) return;

        QPixmap pixmap = screen->grabWindow(0);
        QImage image = pixmap.toImage();

        // 根据模式旋转图像
        if (isPortrait) {
            QTransform transform;
            transform.rotate(90);
            image = image.transformed(transform);
        }

        image = image.scaled(1280, 720, Qt::KeepAspectRatio);

        // 压缩并发送
        QByteArray buffer;
        QBuffer imageBuffer(&buffer);
        imageBuffer.open(QIODevice::WriteOnly);
        image.save(&imageBuffer, "JPEG", 50); // JPEG 压缩质量 50

        if (tcpSocket->state() == QAbstractSocket::ConnectedState) {
            tcpSocket->write(buffer);
        }
    }

    void switchToLandscape() {
        isPortrait = false;
        QMessageBox::information(this, "模式切换", "切换为横屏模式!");
    }

    void switchToPortrait() {
        isPortrait = true;
        QMessageBox::information(this, "模式切换", "切换为竖屏模式!");
    }
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    ScreenCaster caster;
    caster.show();
    return app.exec();
}

补充功能和注意事项

电视端支持
电视端需要运行一个简单的服务器程序,用于接收并解码图像数据,并实时显示。

优化和扩展

压缩格式:支持 H.264 等高效编码格式。
多设备支持:支持同时向多个设备投屏。
帧率优化:根据网络状况调整帧率。
错误处理

添加超时机制,处理网络不稳定或设备掉线的情况。
处理设备返回错误时的 UI 提示。
此实现是一个基础版本,可根据需求逐步扩展,例如添加更多协议支持(如 DLNA、Miracast)。

;