打开摄像头,服务器和客户端传输摄像头图像数据

发布于:2025-07-06 ⋅ 阅读:(18) ⋅ 点赞:(0)

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);	//服务器端监听套接字 ServerListenSocket

    clientRequestSize = 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'写入 输出数据流out
    out.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


网站公告

今日签到

点亮在社区的每一天
去签到