這篇文章主要講解了“Qt怎么實(shí)現(xiàn)視頻傳輸TCP版”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“Qt怎么實(shí)現(xiàn)視頻傳輸TCP版”吧!
漢源網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)建站!從網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開(kāi)發(fā)、APP開(kāi)發(fā)、成都響應(yīng)式網(wǎng)站建設(shè)公司等網(wǎng)站項(xiàng)目制作,到程序開(kāi)發(fā),運(yùn)營(yíng)維護(hù)。創(chuàng)新互聯(lián)建站于2013年創(chuàng)立到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來(lái)保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)建站。
做音視頻開(kāi)發(fā),會(huì)遇到將音視頻重新轉(zhuǎn)發(fā)出去的需求,當(dāng)然終極大法是推流轉(zhuǎn)發(fā),還有一些簡(jiǎn)單的場(chǎng)景是直接自定義協(xié)議將視頻傳出去就行,局域網(wǎng)的話速度還是不錯(cuò)的。很多年前就做過(guò)類似的項(xiàng)目,無(wú)非就是將本地的圖片上傳到服務(wù)器,就這么簡(jiǎn)單,其實(shí)用http的post上傳比較簡(jiǎn)單容易,無(wú)需自定義協(xié)議,直接設(shè)置好二進(jìn)制數(shù)據(jù)即可,而采用TCP或者UDP通信的話,必須自定義協(xié)議,因?yàn)椴恢朗裁磿r(shí)候數(shù)據(jù)接收完了是完整的圖片數(shù)據(jù),可能同時(shí)在發(fā)送很多圖片數(shù)據(jù),而且還不能區(qū)分收到的圖片是哪個(gè)客戶端發(fā)來(lái)的,TCP長(zhǎng)連接的話,還需要有心跳來(lái)檢測(cè)連接,所以必須自定義一套協(xié)議來(lái)支撐通信,這套協(xié)議采用的是上海監(jiān)管平臺(tái)的通信協(xié)議格式,拓展性比較強(qiáng),其中頭部信息包括了類型+當(dāng)前完整包的數(shù)據(jù)長(zhǎng)度,這個(gè)類型就是通信協(xié)議的標(biāo)識(shí),這樣下次來(lái)一個(gè)其他類型的比如樓宇對(duì)講可以叫IDOOR,服務(wù)端根據(jù)這個(gè)標(biāo)識(shí)就能知道采用何種解析算法來(lái)處理后面的數(shù)據(jù),而當(dāng)前完整包的數(shù)據(jù)長(zhǎng)度可以用來(lái)處理收到的數(shù)據(jù),只有該長(zhǎng)度的數(shù)據(jù)才表示接收完成一個(gè)完整的圖片數(shù)據(jù),再去解碼處理。當(dāng)傳輸?shù)膱D片到了一定速度的時(shí)候比如一秒鐘傳輸20張圖片,其實(shí)就相當(dāng)于傳輸視頻了,一般人的肉眼看到一秒鐘20張圖片基本上認(rèn)識(shí)就是視頻了。
TCP理論上是穩(wěn)定的連接,不會(huì)丟包,也不會(huì)隨便一個(gè)包插入到一個(gè)包的中間,肯定能保證一個(gè)數(shù)據(jù)包的完整性,TCP連接也分兩種,一種是長(zhǎng)連接,一旦連接了就一直通信,主要用在頻繁通信的場(chǎng)景中比如實(shí)時(shí)上傳,還有一種叫短連接,客戶端發(fā)完數(shù)據(jù)或者服務(wù)端接收完數(shù)據(jù)就立即斷開(kāi)連接,主要用在不頻繁的通信場(chǎng)景中比如報(bào)警上傳,畢竟報(bào)警的情況在一天中很少發(fā)生,采用短連接為佳,可以省去很多系統(tǒng)的開(kāi)銷,Qt對(duì)TCP的通信也是封裝的很好用,在一些小并發(fā)的就幾個(gè)幾十個(gè)連接的項(xiàng)目中,效率還是可以的,據(jù)說(shuō)Qt5的QNetwork組件底層重新改寫(xiě)了,效率比Qt4更高一些,本人也沒(méi)用去詳細(xì)的查看對(duì)應(yīng)的源碼,只是聽(tīng)說(shuō)。
通信協(xié)議:
采用TCP長(zhǎng)連接和UDP協(xié)議可選,默認(rèn)通信端口6000。
采用自定義的xml通信協(xié)議。
所有傳輸加20個(gè)字節(jié)頭部:IIMAGE:0000000000000,IIMAGE:為固定頭部,后面接13個(gè)字節(jié)的 內(nèi)容的長(zhǎng)度(含20個(gè)頭部長(zhǎng)度) 字符串。
下面協(xié)議部分省略了頭部字節(jié)。
服務(wù)端返回的數(shù)據(jù)中的uuid是對(duì)應(yīng)接收到的消息的uuid。
服務(wù)端每次返回的時(shí)候都帶了當(dāng)前時(shí)間,可用于客戶端校時(shí)。
客戶端發(fā)送心跳服務(wù)器收到心跳返回 Ok 客戶端發(fā)送圖片服務(wù)端收到圖片返回 圖片base64編碼后的字符串/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAJAAtADASIAAhEBAxEB/8QAHwAAAQUBAQEB...nvWsQRlXA61mTjmtWcdazLgcmrQ0U2plSMKjpDE7UtFFAwxRRRQAUuKWigQlFFFLcD//2Q== Ack
多線程收發(fā)圖片數(shù)據(jù)和解析圖片數(shù)據(jù),不卡主界面。
同時(shí)支持TCP和UDP兩種模式,封裝了TCP模式以及UDP模式的客戶端類和服務(wù)端類。
圖片傳輸客戶端同時(shí)支持發(fā)送到多個(gè)服務(wù)端,可以作為一個(gè)教師機(jī)同屏發(fā)送到多個(gè)學(xué)生機(jī)的應(yīng)用場(chǎng)景。
同時(shí)支持多個(gè)客戶端同時(shí)往服務(wù)端發(fā)送圖片,服務(wù)端每個(gè)連接都會(huì)自動(dòng)開(kāi)辟線程收發(fā)和解析圖片數(shù)據(jù)。
自定義label控件信號(hào)槽機(jī)制繪制圖片,不卡主界面。
自帶心跳機(jī)制判斷離線,自動(dòng)重連服務(wù)器,可設(shè)置超時(shí)時(shí)間。
每個(gè)消息都有唯一的消息標(biāo)識(shí)uuid,服務(wù)端收到以后會(huì)返回對(duì)應(yīng)的uuid消息表示收到,客戶端可以根據(jù)此返回消息判斷服務(wù)端解析成功,不用再發(fā),這樣可以確保發(fā)出去的數(shù)據(jù)服務(wù)器接收到了并解析成功。
每個(gè)消息都有唯一的圖片標(biāo)識(shí)flag,相當(dāng)于ID號(hào),根據(jù)此標(biāo)識(shí)判斷需要解析顯示到哪個(gè)界面。
圖片以base64的字符串格式發(fā)送,接收端接收到base64字符串的圖片數(shù)據(jù)解碼后重新生成圖片。
所有數(shù)據(jù)的收發(fā)都有信號(hào)發(fā)出去,方便輸出查看。
都提供單例類,方便只有一個(gè)的時(shí)候直接使用無(wú)需new。
采用自定義的xml協(xié)議,可以自由拓展其他屬性字段比如帶上圖片內(nèi)容等。
#include "tcpimagesocket.h" #include "devicefun.h" TcpImageSocket::TcpImageSocket(QObject *parent) : QThread(parent) { stopped = false; tcpSocket = 0; //定時(shí)器解析收到的數(shù)據(jù),可以自行調(diào)整間隔 timerData = new QTimer(this); connect(timerData, SIGNAL(timeout()), this, SLOT(checkData())); timerData->start(30); } TcpImageSocket::~TcpImageSocket() { } void TcpImageSocket::run() { while(!stopped) { //這里采用線程去處理,其實(shí)完全可以用定時(shí)器搞定,畢竟tcp的write是異步的,操作系統(tǒng)自動(dòng)調(diào)度 //為了后期的拓展性,比如需要判斷是否發(fā)送成功之類的,需要同步處理,所以改成的線程去處理 //base64編碼數(shù)據(jù)轉(zhuǎn)圖片數(shù)據(jù)也需要時(shí)間的,主要的耗時(shí)在轉(zhuǎn)碼 //取出數(shù)據(jù)發(fā)送,這里需要加鎖,避免正在插入數(shù)據(jù) if (imageFlags.count() > 0) { QMutexLocker locker(&mutexImage); QString imageFlag = imageFlags.takeFirst(); QString imageData = imageDatas.takeFirst(); QImage image = DeviceFun::getImage(imageData); emit receiveImage(imageFlag, image); } //要稍微休息下,否則CPU會(huì)被一直占用 msleep(1); } stopped = false; } void TcpImageSocket::readData() { //接收的數(shù)據(jù)存入buffer需要加鎖 QMutexLocker locker(&mutexData); //接收到的數(shù)據(jù)存入隊(duì)列,排隊(duì)處理 QByteArray data = tcpSocket->readAll(); buffer.append(data); emit receiveData(data); } void TcpImageSocket::checkData() { if (buffer.length() == 0) { return; } //取出數(shù)據(jù)處理需要加鎖,防止此時(shí)正在插入數(shù)據(jù) QMutexLocker locker(&mutexData); QDomDocument dom; if (!DeviceFun::getReceiveXmlData(buffer, dom, "IIMAGE:", 11, true)) { return; } //逐個(gè)取出節(jié)點(diǎn)判斷數(shù)據(jù) QDomElement element = dom.documentElement(); if (element.tagName() == "ImageClient") { QString uuid = element.attribute("Uuid"); QString flag = element.attribute("Flag"); QDomNode childNode = element.firstChild(); QString name = childNode.nodeName(); QString value = element.text(); //qDebug() << uuid << name << value; if (name == "ClientHeart") { sendHeart(uuid); } else if (name == "ClientImage") { sendAck(uuid); element = childNode.toElement(); value = element.text(); QMutexLocker locker(&mutexImage); if (this->isRunning() && imageFlags.count() < 10) { imageFlags << flag; imageDatas << value; } } } } void TcpImageSocket::stop() { buffer.clear(); imageFlags.clear(); imageDatas.clear(); stopped = true; this->wait(); if (tcpSocket != 0) { tcpSocket->disconnectFromHost(); } } void TcpImageSocket::setTcpSocket(QTcpSocket *tcpSocket) { if (this->tcpSocket == 0) { this->tcpSocket = tcpSocket; connect(tcpSocket, SIGNAL(disconnected()), this, SLOT(stop())); connect(tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(stop())); connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(readData())); } } void TcpImageSocket::writeData(const QString &body) { QString data = DeviceFun::getSendXmlData(body, "IIMAGE:"); QByteArray buffer = data.toUtf8(); tcpSocket->write(buffer); tcpSocket->flush(); emit sendData(buffer); } void TcpImageSocket::sendHeart(const QString &uuid) { //構(gòu)建xml字符串 QStringList list; list.append(QString("").arg(uuid).arg(DATETIME)); list.append("Ok"); list.append(" "); writeData(list.join("")); } void TcpImageSocket::sendAck(const QString &uuid) { //構(gòu)建xml字符串 QStringList list; list.append(QString("").arg(uuid).arg(DATETIME)); list.append("Ack"); list.append(" "); writeData(list.join("")); }
感謝各位的閱讀,以上就是“Qt怎么實(shí)現(xiàn)視頻傳輸TCP版”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)Qt怎么實(shí)現(xiàn)視頻傳輸TCP版這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!