QT打造图片直播服务器
基礎(chǔ)說明
最近接到一個需求,就是編寫一個服務(wù)器,然后瀏覽器可以通過image標簽直接訪問這個服務(wù)器的內(nèi)容,瀏覽器看到的image標簽的圖片是一個動圖,并且根據(jù)服務(wù)器的內(nèi)容時時刷新。(這不就是直播嗎?)為了方便我使用QT打造了mjpeg服務(wù)器。
?mjpeg服務(wù)器的基本原理就是將圖片按照一張張的傳給客戶,然后客戶端就像在播放動畫一樣但是其實他還是圖片。瀏覽器的image標簽可以通過http協(xié)議直接訪問到該服務(wù)的圖片流,看起來就像image標簽有了video的功能,其實是一張一張的圖片進行拼接的。
數(shù)據(jù)流的格式很重要,我在剛開始編寫服務(wù)器的時候,發(fā)現(xiàn)有一個空格的錯誤客戶端都無法解析到傳給他的圖片。
mjpeg是在tcp協(xié)議的基礎(chǔ)上的應(yīng)用。
使用的消息頭為: x-mixed-replace
Content-Type: multipart/x-mixed-replace;boundary=myboundary?注意:在編寫代碼的時候空格都不能錯。否則客戶端無法解析。
myboundary是服務(wù)器隨機生成的,我這里僅僅是做實驗,所以將數(shù)據(jù)寫成固定值,并且該值也將作為數(shù)據(jù)切分的標識,比如有兩張圖需要傳遞,那么使用--myboundary切分兩張圖的數(shù)據(jù),然后image標簽就能夠分別獲取兩張圖片的內(nèi)容進行展示。
內(nèi)容格式如下:
--myboundary Content-Type: image/jpeg Content-length: 2918{圖片二進制原始數(shù)據(jù)1} --myboundary Content-Type: image/jpeg Content-length: 2918{圖片二進制原始數(shù)據(jù)2} --myboundary源碼
以下代碼的設(shè)計思路如下:
1. 啟動tcp監(jiān)聽端口,設(shè)置用戶請求信號slotNewConnection。
2. 當(dāng)有用戶進行請求的時候?
- ?使用Qist結(jié)構(gòu)記錄客戶端socket以方便數(shù)據(jù)發(fā)送。
- ?啟動定時器
- ?創(chuàng)建圖片
- ?將圖片數(shù)據(jù)按照mjpeg的格式發(fā)送給客戶端。
3. 當(dāng)客戶端斷鏈時清理Qlist的列表。其實這里應(yīng)該會有一個BUG因為定時器和清理是并行的,也就是說也許已經(jīng)被清理,但是socket可能存留。
mainwindow.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include <QTcpServer> class MainWindow : public QMainWindow {Q_OBJECTpublic:MainWindow(QWidget* parent = nullptr);~MainWindow();QByteArray createImage();private:QTcpServer* m_pTcpServer;QList<QTcpSocket*> m_listTcpClient;QTimer* m_fTimer;QByteArray getReciveData();public slots:void slotNewConnection();void slotDisconnected();void slotDisplayError(QAbstractSocket::SocketError socketError);void slotUpdateImage();// void slotTimerTimeout();signals:void sendMessage(const QString& msg);void sendMessageBinary(const QByteArray& msg); };#endif // MAINWINDOW_Hmainwindow.cpp
#include "mainwindow.h" #include <QTcpSocket> #include <QPen> #include <QPainter> #include <QDateTime> #include <QBuffer> #include <QDebug> #include <QTimer> MainWindow::MainWindow(QWidget* parent): QMainWindow(parent), m_pTcpServer(new QTcpServer(this)), m_fTimer(new QTimer(this)) {connect(m_pTcpServer, &QTcpServer::newConnection, this,&MainWindow::slotNewConnection);connect(m_pTcpServer, &QTcpServer::acceptError, this,&MainWindow::slotDisplayError);m_fTimer->setInterval(1000);connect(m_fTimer, &QTimer::timeout, this, &MainWindow::slotUpdateImage);if (!m_pTcpServer->listen(QHostAddress::Any, 8884)) {qDebug() << "Server Could Not be Started";return;} else {qDebug() << "Server Started";} }MainWindow::~MainWindow() {} // 創(chuàng)建圖片,將當(dāng)前時間記錄到圖片中 QByteArray MainWindow::createImage() {QImage image(400, 100, QImage::Format_RGB32);image.fill(Qt::transparent);QPainter p(&image);QString str = QDateTime::currentDateTimeUtc().toString();// p.setRenderHint(QPainter::Antialiasing, true);p.setRenderHint(QPainter::SmoothPixmapTransform, true);p.setPen(QPen(QColor(255, 0, 0, 255)));p.drawText(10, 10, QFontMetrics(str).width(str), QFontMetrics(str).height(),1, str);QByteArray ba;QBuffer buffer(&ba);buffer.open(QIODevice::WriteOnly);image.save(&buffer, "JPEG", 80);return ba; } // 獲取返回給客戶端的數(shù)據(jù)。 QByteArray MainWindow::getReciveData() {QByteArray text;auto blob = createImage();text += "Content-Type: image/jpeg\r\n";text += "Content-length: " + QString::number(blob.size()) + "\r\n\r\n";text.append(blob);text += "\r\n--myboundary\r\n";// text += "\r\n\r\n";return text; }void MainWindow::slotNewConnection() {// 獲取客戶端的socket鏈接QTcpSocket* pClientConnection = m_pTcpServer->nextPendingConnection();m_listTcpClient.push_back(pClientConnection);m_fTimer->start();connect(pClientConnection, SIGNAL(disconnected()), this,SLOT(slotDisconnected()));qInfo() << "slotNewConnection" << QDateTime::currentDateTimeUtc();QByteArray text ="HTTP/1.1 200 OK\r\nContent-Type: multipart/x-mixed-replace;""boundary=myboundary\r\n";text += "\r\n--myboundary\r\n";text += getReciveData();pClientConnection->write(text); // 將數(shù)據(jù)返回給鏈接的客戶端。 }void MainWindow::slotDisconnected() {QTcpSocket* pQTcpSocket = static_cast<QTcpSocket*>(sender());m_listTcpClient.removeOne(pQTcpSocket);qInfo() << "receive disconnect!" << pQTcpSocket;pQTcpSocket->deleteLater(); }void MainWindow::slotDisplayError(QAbstractSocket::SocketError socketError) {qInfo() << "SimpleTcpSocketServerDemo::displayError " << socketError; } // 定時更新圖片數(shù)據(jù)。 void MainWindow::slotUpdateImage() {QByteArray text = getReciveData();foreach (auto item, m_listTcpClient) {item->write(text);} }?最終實現(xiàn)效果
我們可以看到在瀏覽器中可以直接訪問該服務(wù),他是以圖片的方式傳輸給瀏覽器,并且一張一張的自動顯示,是不是有點像動畫。結(jié)合攝像頭的圖片抓取,可以做一個簡易的攝像頭直播服務(wù)器。
?當(dāng)然你也可以使用<image src="http://127.0.0.1:884" />來訪問。
代碼中還未實現(xiàn)的部分線程啟動服務(wù),定時器的管理。
?git源碼傳送門https://codechina.csdn.net/arv002/qt/-/tree/master/QHttpVideoServer
總結(jié)
以上是生活随笔為你收集整理的QT打造图片直播服务器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微信公众号测试号接入微信公众平台开发--
- 下一篇: Google发布了Google Sket