您好,登錄后才能下訂單哦!
本篇內容主要講解“Qt怎么實現視頻傳輸UDP版”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Qt怎么實現視頻傳輸UDP版”吧!
Qt的網絡通信類,我們平時常用的就是三個:QTcpSocket客戶端類、QTcpServer服務端類、QUdpSocket通信類,為啥沒有QUdpServer類?其實UDP是無連接的通信,占用資源很小,他既可以是客戶端也可以是服務端,如果要作為服務端則指定端口調用bind方法即可。本程序同時支持了TCP模式和UDP模式,實際測試下來,還是建議使用TCP模式,UDP模式由于無連接在短時間內發送大量的數據包發現會丟包,而且包的大小有限制,是65507字節,大約64K,所以UDP模式下實時傳輸的圖片分辨率不能太大,實測640*480的視頻文件還是挺好的,720P基本上有點慘,丟包好多,可能后期還需要從協議上改進處理。
本程序和協議約定的圖片采用base64編碼傳輸,接收到以后將base64字符串解碼出來生成圖片,QByteArray內置類toBase64方法轉成base64編碼的字符串,QByteArray::fromBase64方法將base64字符串還原成數據。在經過多次的實驗以后統計的數據顯示,編碼解碼的速度還可以,其中720P圖片編碼25ms-30ms、解碼15ms-20ms,1080P圖片編碼35ms-40ms、解碼25ms-30ms。總體上來說一秒鐘傳輸25-30張圖片和解碼25-30張圖片,還是沒有什么問題的,只是走的CPU編碼解碼,如果開的通道數比較多的話,還是很耗CPU的,但是應付一些簡單的應用場景還是如魚得水毫無壓力。
通信協議:
采用TCP長連接和UDP協議可選,默認通信端口6000。
采用自定義的xml通信協議。
所有傳輸加20個字節頭部:IIMAGE:0000000000000,IIMAGE:為固定頭部,后面接13個字節的 內容的長度(含20個頭部長度) 字符串。
下面協議部分省略了頭部字節。
服務端返回的數據中的uuid是對應接收到的消息的uuid。
服務端每次返回的時候都帶了當前時間,可用于客戶端校時。
客戶端發送心跳 <?xml version="1.0" encoding="UTF-8"?> <ImageClient Uuid="8AF12208-0356-434C-8A49-69A2170D9B5A" Flag="SHJC00000001"> <ClientHeart/> </ImageClient> 服務器收到心跳返回 <?xml version="1.0" encoding="UTF-8"?> <ImageServer Uuid="8AF12208-0356-434C-8A49-69A2170D9B5A" NowTime="2019-12-05 16:37:47"> Ok </ImageServer> 客戶端發送圖片 <?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> 服務端收到圖片返回 <?xml version="1.0" encoding="UTF-8"?> <ImageServer Uuid="66BCB44A-B567-48ED-8889-36B8FA6C4363" NowTime="2019-12-05 16:38:47"> Ack </ImageServer>
多線程收發圖片數據和解析圖片數據,不卡主界面。
同時支持TCP和UDP兩種模式,封裝了TCP模式以及UDP模式的客戶端類和服務端類。
圖片傳輸客戶端同時支持發送到多個服務端,可以作為一個教師機同屏發送到多個學生機的應用場景。
同時支持多個客戶端同時往服務端發送圖片,服務端每個連接都會自動開辟線程收發和解析圖片數據。
自定義label控件信號槽機制繪制圖片,不卡主界面。
自帶心跳機制判斷離線,自動重連服務器,可設置超時時間。
每個消息都有唯一的消息標識uuid,服務端收到以后會返回對應的uuid消息表示收到,客戶端可以根據此返回消息判斷服務端解析成功,不用再發,這樣可以確保發出去的數據服務器接收到了并解析成功。
每個消息都有唯一的圖片標識flag,相當于ID號,根據此標識判斷需要解析顯示到哪個界面。
圖片以base64的字符串格式發送,接收端接收到base64字符串的圖片數據解碼后重新生成圖片。
所有數據的收發都有信號發出去,方便輸出查看。
都提供單例類,方便只有一個的時候直接使用無需new。
采用自定義的xml協議,可以自由拓展其他屬性字段比如帶上圖片內容等。
#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) { //如果是外網請自行調整這個值的大小,外網需要調小 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())); //定時器解析收到的數據,可以自行調整間隔 timerData = new QTimer(this); connect(timerData, SIGNAL(timeout()), this, SLOT(checkData())); timerData->setInterval(100); //綁定信號啟動后啟動定時器 connect(this, SIGNAL(started()), this, SLOT(started())); //綁定發送數據信號槽 connect(this, SIGNAL(readyWrite(QString)), this, SLOT(sendImage(QString))); } UdpImageClient::~UdpImageClient() { this->stop(); } void UdpImageClient::run() { while (!stopped) { //這里采用線程去處理,其實完全可以用定時器搞定,畢竟tcp的write是異步的,操作系統自動調度 //為了后期的拓展性,比如需要判斷是否發送成功之類的,需要同步處理,所以改成的線程去處理 //圖片數據轉成base64編碼的數據也需要時間的,主要的耗時在轉碼 //取出數據發送,這里需要加鎖,避免正在插入數據 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); //接收的數據存入buffer需要加鎖 QMutexLocker locker(&mutexData); buffer.append(data); emit receiveData(data); } } void UdpImageClient::checkData() { if (buffer.length() == 0) { return; } //取出數據處理需要加鎖,防止此時正在插入數據 QMutexLocker locker(&mutexData); QDomDocument dom; if (!DeviceFun::getReceiveXmlData(buffer, dom, "IIMAGE:", 11, true)) { return; } //逐個取出節點判斷數據 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; //這里可以根據收到的數據自行增加自己的處理 } } 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) { //構建xml字符串 QStringList list; list.append(QString("<ImageClient Uuid=\"%1\" Flag=\"%2\">").arg(DeviceFun::getUuid()).arg(flag)); list.append(body); list.append("</ImageClient>"); //調用通用方法根據協議組成完整數據 QString data = DeviceFun::getSendXmlData(list.join(""), "IIMAGE:"); QByteArray buffer = data.toUtf8(); //udp最大只能發送65507字節的數據=64K 超過的話都會發送失敗 //所以這里需要手動分包,外網的話包還要小一點 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) { //這里需要加鎖,避免正在取出數據 QMutexLocker locker(&mutexImage); //限制隊列中最大消息數,避免離線的時候瘋狂插入 if (this->isRunning() && images.count() < 10) { images << image; } } void UdpImageClient::clear() { QMutexLocker locker(&mutexImage); images.clear(); }
到此,相信大家對“Qt怎么實現視頻傳輸UDP版”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。