1:Camera Server
主要功能,打開攝像頭,接收客戶端請求
接收到客戶端請求“R”字符后開始傳輸攝像頭圖像。
#include "mainwindow.h"
#include "ui_mainwindow.h"#include<QDebug>MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);const QList<QCameraInfo> availableCameras = QCameraInfo::availableCameras();for (const QCameraInfo &cameraInfo : availableCameras){ui->comboxCamera->addItem(cameraInfo.description());cameras.append(cameraInfo);}ca = new QCamera();// foreach (const QCameraInfo &cameraInfo, cameras) {
// if (cameraInfo.deviceName() == "mycamera")
// ca = new QCamera(cameraInfo);
// }foreach(const QCameraInfo &info, cameras){qDebug() << info.deviceName();}timer = new QTimer();connect(timer, &QTimer::timeout, [&](){cap->capture();});server = new QTcpServer(this);server->listen(QHostAddress::Any, 8888);connect(server, &QTcpServer::newConnection, this, &MainWindow::newConnect);//ServerListenSocket = new QTcpServer(this); //服務器端監聽套接字 ServerListenSocketclientRequestSize = 0;socket = nullptr;}MainWindow::~MainWindow()
{delete ui;}void MainWindow::on_pushButton_open_clicked()
{//構造一下攝像頭對象ca = new QCamera(cameras[ui->comboxCamera->currentIndex()], this);//創建截圖對象的內存 截圖軟件要和攝像頭對象相互關聯cap = new QCameraImageCapture(ca);//將截圖信號與顯示截圖的槽函數關聯一下//連接 截圖信號和顯示截圖的槽函數 一旦發出imageCaptured截圖信號,就觸發截圖的槽函數connect(cap, &QCameraImageCapture::imageCaptured, this, &MainWindow::To_client);//將QCameraViewfinder綁定到一個控件上QCameraViewfinder *v = new QCameraViewfinder(ui->label);v->resize(ui->label->size());ca->setViewfinder(v);v->show();//啟動攝像頭ca->start();}void MainWindow::on_pushButton_start_clicked()
{//啟動時間循環timer->start(20);}void MainWindow::To_client(int index, QImage ima)
{//如果沒有鏈接的情況,就直接退出,不至于閃退if(socket == nullptr){return;}QByteArray byte; //The QByteArray class provides an array of bytes.QBuffer buf(&byte); //緩存區域//QString imageSize = "image size is:" + QString::number(frame.cols*frame.rows * 3) + " Bytes";//ui.info->addItem(imageSize);//圖像的大小(字節數)ima.save(&buf, "JPEG"); //將圖像以jpeg的壓縮方式壓縮了以后保存在 buf當中//QString jpegImageSize = "jpeg image size is " + QString::number(buf.size()) + " Bytes";//ui.info->addItem(jpegImageSize); //壓縮后的jpg圖像的大小(字節數)QByteArray ss = qCompress(byte, 1);//將壓縮后的jpg圖像 再用qCompress 壓縮 ,第二個參數1-9,9是最大壓縮率//QString ssSize="ss's size is "+ QString::number(ss.size()) + " Bytes";//ui.info->addItem(ssSize);//用qCompress 壓縮后的數據大小(字節數)//將壓縮后的字節串數據編碼成Base64方式,字節數會比壓縮前稍微變多一些QByteArray vv = ss.toBase64(); // QByteArray QByteArray::toBase64() const : Returns a copy of the byte array, encoded as Base64.//QString vvSize = "vv's size is " + QString::number(vv.size()) + " Bytes";//ui.info->addItem(vvSize); //編碼后的數據的大小QByteArray ba;QDataStream out(&ba, QIODevice::WriteOnly); //二進制只寫輸出流out.setVersion(QDataStream::Qt_5_10); //輸出流的版本/* 當操作復雜數據類型時,我們就要確保讀取和寫入時的QDataStream版本是一樣的,簡單類型,比如char,short,int,char* 等不需要指定版本也行 *//* 上面這些編解碼的過程肯定是會影響 時效性的,可以考慮只使用jpeg 壓縮后就進行發送 。 */out << (quint64)0; //寫入套接字的經壓縮-編碼后的圖像數據的大小out << vv; //寫入套接字的經壓縮-編碼后的圖像數據out.device()->seek(0);out << (quint64)(ba.size() - sizeof(quint64));//寫入套接字的經壓縮-編碼后的圖像數據的大小socket->write(ba); //將整塊數據寫入套接字//update(); //更新界面}void MainWindow::newConnect()
{//ui->textEdit->append("An new client is connected!");socket = server->nextPendingConnection(); //返回已連接套接字對象connect(socket, SIGNAL(readyRead()), this, SLOT(readClientRequest())); //將已連接套接字對象的準備好可讀信號readyRead與 readClientRequest()槽函數連接//connect(socket, SIGNAL(disconnected()), socket,SLOT(deleterLater())); //已連接套接字的斷開信號與自身的稍后刪除信號相連接}void MainWindow::readClientRequest()
{QDataStream in(socket); //綁定套接字in.setVersion(QDataStream::Qt_5_10); //指定版本//如果客戶端發送過來的第一段數據塊的大小為0,說明確實是第一次交互if (clientRequestSize == 0){//客戶端發送過來的第一段數據塊的大小如果小于 64bit ,則說明:還未收到客戶端發送過來的前64bit的數據,這64bit的數據存儲了客戶端第一次請求包的大小(字節數)if (socket->bytesAvailable() < sizeof(quint16)){return; //返回,繼續等待 接收數據,數據還在套接字緩存當中}else//如果 客戶端發送過來的第一段數據塊的大小>= 64bit 了{in >> clientRequestSize;//將數據的前64bit提取出來,存儲到quint64 類型的clientRequestSize}}if (socket->bytesAvailable() < clientRequestSize)//當前套接字緩沖區中存儲的數據如果小于clientRequestSize個字節{return;//返回,繼續等待 接收數據,數據還在套接字緩存當中}quint8 requestType;in >> requestType;//從套接字緩沖區中讀取 8bit的數據解釋為quint8類型,存儲到requestType中if (requestType == 'R') //如果requestType是 'R' 字符R的ASCII值{connect(timer, &QTimer::timeout, [&](){cap->capture();});//將30ms時間到與發送數據的 SendData() 連接}}void MainWindow::on_pushButton_close_clicked()
{}void MainWindow::on_benSendImage_clicked()
{//啟動時間循環timer->start(20);
}
2客戶端
連接服務器,發送請求,接收數據后展示到界面上
#include "mainwindow.h"
#include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);imageBlockSize = 0; //一次接收的圖像數據的大小(字節數)connect(&tcpSocket, SIGNAL(disconnect()), this, SLOT(connectionCloseByServer()));//套接字的斷開信號connect(&tcpSocket, SIGNAL(readyRead()), this, SLOT(ReceiveData()));//套接字一次可讀的觸發信號connect(&tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error()));//套接字的錯誤消息信號}MainWindow::~MainWindow()
{delete ui;
}//連接
void MainWindow::on_pushButton_clicked()
{connectToServer();bool k = connect(&tcpSocket, SIGNAL(connected()), this, SLOT(tcpConnected()));}//請求
void MainWindow::on_benRequest_clicked()
{sendRequest();
}void MainWindow::connectToServer()
{//連接到本機的8888端口tcpSocket.connectToHost(QHostAddress::LocalHost, 8888);//connectToHost是異步連接函數(不阻塞),一經調用結束立刻返回ui->textEdit->append("connecting to LocalHost port 8888...");//ui.connectToServer->setEnabled(false);}void MainWindow::sendRequest()//發送請求,請求視頻圖像序列
{QByteArray requestMessage; //請求消息(字節數組)QDataStream out(&requestMessage, QIODevice::WriteOnly);//只讀輸出流out.setVersion(QDataStream::Qt_5_10);out << quint16(0) << quint8('R');//將請求消息的大小、長度(字節數)與請求消息 'R'寫入 輸出數據流outout.device()->seek(0);out << quint16(requestMessage.size() - sizeof(quint16));tcpSocket.write(requestMessage);//將輸出數據流中的數據寫入套接字ui->textEdit->append("Sending request...");//ui.requestVideo->setEnabled(false);}static int receiveCount = 0;//接收 readyRead()信號的觸發計數
static int imageCount = 0; //接收到的圖像數據的計數void MainWindow::ReceiveData()
{receiveCount++;QString rCount = QString::number(receiveCount);//ui.receiveCount->setText(rCount);QByteArray message;//存放從服務器接收到的字節流數據QDataStream in(&tcpSocket); //將客戶端套接字與輸入數據流對象in綁定in.setVersion(QDataStream::Qt_5_10);//設置數據流的版本/* 接收端的 這部分控制邏輯很重要 */if (imageBlockSize == 0){//如果imageBlockSize == 0 則說明,一幅圖像的大小信息還未傳輸過來//uint64是8字節的8 Bytes 64bit//判斷接收的數據是否有8字節(文件大小信息)//如果有則保存到basize變量中,沒有則返回,繼續接收數據if (tcpSocket.bytesAvailable() < (int)sizeof(quint64)){//一幅圖像的大小信息還未傳輸過來return;}in >> imageBlockSize;//一幅圖像的大小信息 //先接受圖片的大小if (imageBlockSize == (quint64)0xFFFFFFFFFFFFFFFF)//視頻結束的標注符{tcpSocket.close();QMessageBox::information(this, tr("warning"), tr("the video is end!"));return;}qDebug() << "imageBlockSize is " << imageBlockSize;QString imageBlockS = "imageBlockSize is " + QString::number(imageBlockSize) + "Bytes!";ui->textEdit->append(imageBlockS);message.resize(imageBlockSize);}//如果沒有得到一幅圖像的全部數據,則返回繼續接收數據if (tcpSocket.bytesAvailable() < imageBlockSize){return;}in >> message;//一幅圖像所有像素的完整字節流imageBlockSize = 0;//已經收到一幅完整的圖像,將imageBlockSize置0,等待接收下一幅圖像imageCount++; //已接收的圖像計數QString iCount = QString::number(imageCount);//ui.imageCount->setText(iCount);ShowImage(message); //顯示當前接收到的這一幅圖像}void MainWindow::connectionCloseByServer()//服務端主動斷開了已連接套接字
{ui->textEdit->append("Error:Connection closed by server!");tcpSocket.close();//關閉客戶端套接字//ui.connectToServer->setEnabled(true);
}void MainWindow::error()
{ui->textEdit->append(tcpSocket.errorString());tcpSocket.close();//ui.connectToServer->setEnabled(true);
}void MainWindow::tcpConnected()//套接字已經建立連接信號的處理槽函數
{//ui.requestVideo->setEnabled(true);
}void MainWindow::ShowImage(QByteArray ba) //從接收到了字節流中,執行與服務器斷相反的操作:解壓縮、解釋為圖像數據
{QString ss = QString::fromLatin1(ba.data(), ba.size());QByteArray rc;rc = QByteArray::fromBase64(ss.toLatin1());QByteArray rdc = qUncompress(rc);QImage img;//img.loadFromData(rdc,"JPEG");//解釋為jpg格式的圖像img.loadFromData(rdc);//解釋為jpg格式的圖像ui->label->setPixmap(QPixmap::fromImage(img));ui->label->resize(img.size());update();
}
代碼下載連接
https://download.csdn.net/download/qianshanxue11/91264982