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