溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Qt怎么實現(xiàn)視頻傳輸UDP版

發(fā)布時間:2021-12-15 10:12:32 來源:億速云 閱讀:409 作者:iii 欄目:互聯(lián)網(wǎng)科技

本篇內(nèi)容主要講解“Qt怎么實現(xiàn)視頻傳輸UDP版”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“Qt怎么實現(xiàn)視頻傳輸UDP版”吧!

一、前言

Qt的網(wǎng)絡(luò)通信類,我們平時常用的就是三個:QTcpSocket客戶端類、QTcpServer服務(wù)端類、QUdpSocket通信類,為啥沒有QUdpServer類?其實UDP是無連接的通信,占用資源很小,他既可以是客戶端也可以是服務(wù)端,如果要作為服務(wù)端則指定端口調(diào)用bind方法即可。本程序同時支持了TCP模式和UDP模式,實際測試下來,還是建議使用TCP模式,UDP模式由于無連接在短時間內(nèi)發(fā)送大量的數(shù)據(jù)包發(fā)現(xiàn)會丟包,而且包的大小有限制,是65507字節(jié),大約64K,所以UDP模式下實時傳輸?shù)膱D片分辨率不能太大,實測640*480的視頻文件還是挺好的,720P基本上有點慘,丟包好多,可能后期還需要從協(xié)議上改進處理。

本程序和協(xié)議約定的圖片采用base64編碼傳輸,接收到以后將base64字符串解碼出來生成圖片,QByteArray內(nèi)置類toBase64方法轉(zhuǎn)成base64編碼的字符串,QByteArray::fromBase64方法將base64字符串還原成數(shù)據(jù)。在經(jīng)過多次的實驗以后統(tǒng)計的數(shù)據(jù)顯示,編碼解碼的速度還可以,其中720P圖片編碼25ms-30ms、解碼15ms-20ms,1080P圖片編碼35ms-40ms、解碼25ms-30ms??傮w上來說一秒鐘傳輸25-30張圖片和解碼25-30張圖片,還是沒有什么問題的,只是走的CPU編碼解碼,如果開的通道數(shù)比較多的話,還是很耗CPU的,但是應(yīng)付一些簡單的應(yīng)用場景還是如魚得水毫無壓力。

通信協(xié)議:

  1. 采用TCP長連接和UDP協(xié)議可選,默認通信端口6000。

  2. 采用自定義的xml通信協(xié)議。

  3. 所有傳輸加20個字節(jié)頭部:IIMAGE:0000000000000,IIMAGE:為固定頭部,后面接13個字節(jié)的 內(nèi)容的長度(含20個頭部長度) 字符串。

  4. 下面協(xié)議部分省略了頭部字節(jié)。

  5. 服務(wù)端返回的數(shù)據(jù)中的uuid是對應(yīng)接收到的消息的uuid。

  6. 服務(wù)端每次返回的時候都帶了當前時間,可用于客戶端校時。

客戶端發(fā)送心跳
<?xml version="1.0" encoding="UTF-8"?>
<ImageClient Uuid="8AF12208-0356-434C-8A49-69A2170D9B5A" Flag="SHJC00000001">
    <ClientHeart/>
</ImageClient>

服務(wù)器收到心跳返回
<?xml version="1.0" encoding="UTF-8"?>
<ImageServer Uuid="8AF12208-0356-434C-8A49-69A2170D9B5A" NowTime="2019-12-05 16:37:47">
    Ok
</ImageServer>

客戶端發(fā)送圖片
<?xml version="1.0" encoding="UTF-8"?>
<ImageClient Uuid="66BCB44A-B567-48ED-8889-36B8FA6C4363" Flag="SHJC00000001">
    <ClientImage>圖片base64編碼后的字符串/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAJAAtADASIAAhEBAxEB/8QAHwAAAQUBAQEB...nvWsQRlXA61mTjmtWcdazLgcmrQ0U2plSMKjpDE7UtFFAwxRRRQAUuKWigQlFFFLcD//2Q==</ClientImage>
</ImageClient>

服務(wù)端收到圖片返回
<?xml version="1.0" encoding="UTF-8"?>
<ImageServer Uuid="66BCB44A-B567-48ED-8889-36B8FA6C4363" NowTime="2019-12-05 16:38:47">
    Ack
</ImageServer>

二、功能特點

  1. 多線程收發(fā)圖片數(shù)據(jù)和解析圖片數(shù)據(jù),不卡主界面。

  2. 同時支持TCP和UDP兩種模式,封裝了TCP模式以及UDP模式的客戶端類和服務(wù)端類。

  3. 圖片傳輸客戶端同時支持發(fā)送到多個服務(wù)端,可以作為一個教師機同屏發(fā)送到多個學(xué)生機的應(yīng)用場景。

  4. 同時支持多個客戶端同時往服務(wù)端發(fā)送圖片,服務(wù)端每個連接都會自動開辟線程收發(fā)和解析圖片數(shù)據(jù)。

  5. 自定義label控件信號槽機制繪制圖片,不卡主界面。

  6. 自帶心跳機制判斷離線,自動重連服務(wù)器,可設(shè)置超時時間。

  7. 每個消息都有唯一的消息標識uuid,服務(wù)端收到以后會返回對應(yīng)的uuid消息表示收到,客戶端可以根據(jù)此返回消息判斷服務(wù)端解析成功,不用再發(fā),這樣可以確保發(fā)出去的數(shù)據(jù)服務(wù)器接收到了并解析成功。

  8. 每個消息都有唯一的圖片標識flag,相當于ID號,根據(jù)此標識判斷需要解析顯示到哪個界面。

  9. 圖片以base64的字符串格式發(fā)送,接收端接收到base64字符串的圖片數(shù)據(jù)解碼后重新生成圖片。

  10. 所有數(shù)據(jù)的收發(fā)都有信號發(fā)出去,方便輸出查看。

  11. 都提供單例類,方便只有一個的時候直接使用無需new。

  12. 采用自定義的xml協(xié)議,可以自由拓展其他屬性字段比如帶上圖片內(nèi)容等。

三、效果圖

Qt怎么實現(xiàn)視頻傳輸UDP版

四、核心代碼

#include "udpimageclient.h"
#include "devicefun.h"

QScopedPointer<UdpImageClient> UdpImageClient::self;
UdpImageClient *UdpImageClient::Instance()
{
    if (self.isNull()) {
        static QMutex mutex;
        QMutexLocker locker(&mutex);
        if (self.isNull()) {
            self.reset(new UdpImageClient);
        }
    }

    return self.data();
}

UdpImageClient::UdpImageClient(QObject *parent) : QThread(parent)
{
    //如果是外網(wǎng)請自行調(diào)整這個值的大小,外網(wǎng)需要調(diào)小
    packageSize = 10000;
    flag = "SHJC00000001";
    serverIP = "127.0.0.1";
    serverPort = 6000;

    stopped = false;

    //UDP通信對象
    udpSocket = new QUdpSocket(this);
    connect(udpSocket, SIGNAL(readyRead()), this, SLOT(readData()));

    //定時器解析收到的數(shù)據(jù),可以自行調(diào)整間隔
    timerData = new QTimer(this);
    connect(timerData, SIGNAL(timeout()), this, SLOT(checkData()));
    timerData->setInterval(100);

    //綁定信號啟動后啟動定時器
    connect(this, SIGNAL(started()), this, SLOT(started()));
    //綁定發(fā)送數(shù)據(jù)信號槽
    connect(this, SIGNAL(readyWrite(QString)), this, SLOT(sendImage(QString)));
}

UdpImageClient::~UdpImageClient()
{
    this->stop();
}

void UdpImageClient::run()
{
    while (!stopped) {
        //這里采用線程去處理,其實完全可以用定時器搞定,畢竟tcp的write是異步的,操作系統(tǒng)自動調(diào)度
        //為了后期的拓展性,比如需要判斷是否發(fā)送成功之類的,需要同步處理,所以改成的線程去處理
        //圖片數(shù)據(jù)轉(zhuǎn)成base64編碼的數(shù)據(jù)也需要時間的,主要的耗時在轉(zhuǎn)碼
        //取出數(shù)據(jù)發(fā)送,這里需要加鎖,避免正在插入數(shù)據(jù)
        if (images.count() > 0) {
            QMutexLocker locker(&mutexImage);
            QImage image = images.takeFirst();
            QString imageData = DeviceFun::getImageData(image);
            emit readyWrite(imageData);
        }

        //要稍微休息下,否則CPU會被一直占用
        msleep(1);
    }

    stopped = false;
}

void UdpImageClient::readData()
{
    QHostAddress host;
    quint16 port;
    QByteArray data;

    while (udpSocket->hasPendingDatagrams()) {
        data.resize(udpSocket->pendingDatagramSize());
        udpSocket->readDatagram(data.data(), data.size(), &host, &port);

        //接收的數(shù)據(jù)存入buffer需要加鎖
        QMutexLocker locker(&mutexData);
        buffer.append(data);
        emit receiveData(data);
    }
}

void UdpImageClient::checkData()
{
    if (buffer.length() == 0) {
        return;
    }

    //取出數(shù)據(jù)處理需要加鎖,防止此時正在插入數(shù)據(jù)
    QMutexLocker locker(&mutexData);
    QDomDocument dom;
    if (!DeviceFun::getReceiveXmlData(buffer, dom, "IIMAGE:", 11, true)) {
        return;
    }

    //逐個取出節(jié)點判斷數(shù)據(jù)
    QDomElement element = dom.documentElement();
    if (element.tagName() == "ImageServer") {
        QString uuid = element.attribute("Uuid");
        QDomNode childNode = element.firstChild();
        QString name = childNode.nodeName();
        QString value = element.text();
        //qDebug() << uuid << name << value;
        //這里可以根據(jù)收到的數(shù)據(jù)自行增加自己的處理
    }
}

void UdpImageClient::started()
{
    if (!timerData->isActive()) {
        timerData->start();
    }
}

void UdpImageClient::stop()
{
    buffer.clear();
    images.clear();
    stopped = true;
    this->wait();
    udpSocket->disconnectFromHost();

    if (timerData->isActive()) {
        timerData->stop();
    }
}

void UdpImageClient::setPackageSize(int packageSize)
{
    if (packageSize <= 65507) {
        this->packageSize = packageSize;
    }
}

void UdpImageClient::setFlag(const QString &flag)
{
    this->flag = flag;
}

void UdpImageClient::setServerIP(const QString &serverIP)
{
    this->serverIP = serverIP;
}

void UdpImageClient::setServerPort(int serverPort)
{
    this->serverPort = serverPort;
}

void UdpImageClient::writeData(const QString &body)
{
    //構(gòu)建xml字符串
    QStringList list;
    list.append(QString("<ImageClient Uuid=\"%1\" Flag=\"%2\">").arg(DeviceFun::getUuid()).arg(flag));
    list.append(body);
    list.append("</ImageClient>");

    //調(diào)用通用方法根據(jù)協(xié)議組成完整數(shù)據(jù)
    QString data = DeviceFun::getSendXmlData(list.join(""), "IIMAGE:");
    QByteArray buffer = data.toUtf8();

    //udp最大只能發(fā)送65507字節(jié)的數(shù)據(jù)=64K 超過的話都會發(fā)送失敗
    //所以這里需要手動分包,外網(wǎng)的話包還要小一點
    if (packageSize == 65500) {
        udpSocket->writeDatagram(buffer, QHostAddress(serverIP), serverPort);
    } else {
        int len = buffer.length();
        int count = len / packageSize + 1;
        for (int i = 0; i < count; i++) {
            QByteArray temp = buffer.mid(i * packageSize, packageSize);
            udpSocket->writeDatagram(temp, QHostAddress(serverIP), serverPort);
        }
    }

    emit sendData(buffer);
}

void UdpImageClient::sendImage(const QString &body)
{
    writeData(QString("<ClientImage>%1</ClientImage>").arg(body));
}

void UdpImageClient::append(const QImage &image)
{
    //這里需要加鎖,避免正在取出數(shù)據(jù)
    QMutexLocker locker(&mutexImage);
    //限制隊列中最大消息數(shù),避免離線的時候瘋狂插入
    if (this->isRunning() && images.count() < 10) {
        images << image;
    }
}

void UdpImageClient::clear()
{
    QMutexLocker locker(&mutexImage);
    images.clear();
}

到此,相信大家對“Qt怎么實現(xiàn)視頻傳輸UDP版”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

qt
AI