QT学习笔记——利用QTcpSocket类接收视频并显示问题(python与c++交互)

QTcpSocket 是 Qt 框架中的一个类,它提供了基于 TCP(传输控制协议)的套接字,用于在网络上进行通信。它适用于需要在客户端和服务器之间建立可靠连接的网络应用程序。

在 pro文件 中加上 network

QT += network

头文件:
#include <QTcpSocket>

常用函数:

  • connectToHost()

    • 签名void connectToHost(const QString &hostName, quint16 port)
    • 描述:建立到指定主机 hostName 和端口 port 的连接。此方法是异步的,即它立即返回,并且连接在后台尝试。
    • 用法socket->connectToHost("example.com", 80);
  • disconnectFromHost()

    • 签名void disconnectFromHost()
    • 描述:关闭与主机的连接。此函数也是异步的;它启动关闭连接的过程,一旦连接关闭,套接字会发出 disconnected() 信号。
    • 用法socket->disconnectFromHost();
  • write()

    • 签名qint64 write(const QByteArray &data)
    • 描述:通过套接字发送指定的 data。该函数返回实际写入的字节数。
    • 用法socket->write("Hello, server!");
  • read()

    • 签名QByteArray read(qint64 maxSize)
    • 描述:从套接字读取最多 maxSize 字节的数据,并将其返回为 QByteArray
    • 用法QByteArray responseData = socket->read(1024);
  • waitForConnected()

    • 签名bool waitForConnected(int msecs = 30000)
    • 描述:阻塞直到套接字连接到主机,或直到 msecs 毫秒过去。如果连接成功建立,则返回 true;否则返回 false
    • 用法if (socket->waitForConnected()) { /* Connected */ }
  • waitForDisconnected()

    • 签名bool waitForDisconnected(int msecs = 30000)
    • 描述:阻塞直到套接字与主机断开连接,或直到 msecs 毫秒过去。如果套接字成功断开连接,则返回 true
    • 用法if (socket->waitForDisconnected()) { /* Disconnected */ }
  • bytesAvailable()

    • 签名qint64 bytesAvailable() const
    • 描述:返回可供读取的字节数。
    • 用法if (socket->bytesAvailable() > 0) { /* Data is available */ }
  • state()

    • 签名QAbstractSocket::SocketState state() const
    • 描述:返回套接字的当前状态。它可以处于 UnconnectedStateHostLookupStateConnectingStateConnectedStateBoundStateListeningStateClosingState 状态。
    • 用法if (socket->state() == QTcpSocket::ConnectedState) { /* Connected */ }
  • connected()

    • 信号void connected()
    • 描述:当套接字成功连接到主机时发出此信号。
    • 用法connect(socket, &QTcpSocket::connected, this, &MyClass::onConnected);
  • disconnected()

    • 信号void disconnected()
    • 描述:当套接字与主机断开连接时发出此信号。
    • 用法connect(socket, &QTcpSocket::disconnected, this, &MyClass::onDisconnected);
  • readyRead()

    • 信号void readyRead()
    • 描述:当有数据可以读取时发出此信号。
    • 用法connect(socket, &QTcpSocket::readyRead, this, &MyClass::onReadyRead);
  • errorOccurred()

    • 信号void errorOccurred(QAbstractSocket::SocketError socketError)
    • 描述:当发生错误时发出此信号。可以使用 socketError 参数检查错误类型。
    • 用法connect(socket, &QTcpSocket::errorOccurred, this, &MyClass::onError);


1、通过Python作为服务端发送摄像头捕捉的视频数据

import cv2
import numpy as np
import socket

# 创建一个socket连接
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('192.168.xx.xx',8088))
server_socket.listen(1)

# 接收来自C++/Qt客户端的连接
capture = cv2.VideoCapture(0)  # 0 表示摄像头设备号,也可以改为视频文件路径
while True:
    print("等待连接……")
    client_socket, addr = server_socket.accept()
    print("连接成功!")
    try:
        while True:
            # 读取摄像头数据
            ret, frame = capture.read()
            if ret is not None:
                ret, encoded_frame = cv2.imencode(".jpg", frame)  # 使用JPEG编码格式
                frame_string = encoded_frame.tobytes()  # 将编码后的图像转换为字符串
                # 发送图像数据给C++/Qt客户端
                client_socket.sendall(frame_string)
                confirm_msg = client_socket.recv(1024)
                if confirm_msg.decode() == "received":
                    continue
            else:
                continue
    except Exception as e:
        print("发送数据错误:", e)
client_socket.close()
server_socket.close()
capture.release()

2、qt作为客户端进行接收并显示在label上。
 

#include <QTcpSocket> // 提供TCP套接字功能
#include <QByteArray> // 提供字节数组功能
#include <QDataStream> // 提供数据流读写功能
clientSocket=new QTcpSocket();
clientSocket->connectToHost("192.168.XX.XX", 8088);

QObject::connect(clientSocket, &QTcpSocket::readyRead, [=]() {
    QByteArray data = clientSocket->readAll();
    // 将接收到的数据转换为图像格式(例如使用QImage)
    QImage image;
    // 限制图像大小

    image.loadFromData(data, "JPG");  // 指定图像格式为JPEG

    label_image[i]->setPixmap(QPixmap::fromImage(image));
    // 向服务器发送确认信号,以便服务器继续发送下一帧数据
    clientSocket->write("received");

    });

3.判断qt客户端是否连接。
 

    clientSocket=new QTcpSocket();
    QObject::connect(clientSocket, &QTcpSocket::stateChanged, [=](QAbstractSocket::SocketState socketState) {
        if (socketState == QAbstractSocket::ConnectedState) {
            qDebug() << "Connected to the server.";
        } else if (socketState == QAbstractSocket::UnconnectedState) {
            qDebug() << "Disconnected from the server.";
            label_image[i]->setText("无视频信号");
            label_image[i]->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
            // 在连接断开时重新连接
            clientSocket->abort(); // 中断当前连接
            clientSocket->connectToHost("192.168.XX.XX", 8088); // 重新发起连接
        }
    });
    clientSocket->connectToHost("192.168.XX.XX", 8088);//初始化连接

4、在这个过程中有时发送数据不完整会出现数据不完整,导致视频界面闪烁。
   解决方法一:只显示完整数据
          缺点:画面不流畅、很卡顿。

    QObject::connect(clientSocket, &QTcpSocket::readyRead, [=]() {
        QByteArray data = clientSocket->readAll();
        QImage image;
        image.loadFromData(data, "JPG");
        bool success = image.loadFromData(data, "JPG");
        if (!success) {
            qDebug() << "Failed to load image";
        }
        else{
            QPixmap tempPixmap = QPixmap::fromImage(image);
            QPixmap optimizedPixmap = tempPixmap.scaled(label_image[i]->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
            label_image[i]->setPixmap(optimizedPixmap);

            label_image[i]->setVisible(true); // 设置可见

            // 向服务器发送确认信号
            clientSocket->write("received");
        }

   解决方法二:将一帧数据分包发送,qt分包接收
  python 端分包发送将一帧长度发送给qt

def python2qt(q_frame):
    while True:
        try:
            qt_client_socket, _ = qt_server_socket.accept()
            print("成功连接qt")
            while True:
                frame = q_frame.get()
                print("获得新frame")
                _, encoded_frame = cv2.imencode(".jpg", frame)  # 使用JPEG编码格式
                size = len(encoded_frame)  # 确定一帧数据长度
                # 发送帧长度+图像数据给C++/Qt客户端
                qt_client_socket.sendall(struct.pack('!i', size) + encoded_frame.tobytes())  # 发送帧大小和帧数据
                confirm_msg = qt_client_socket.recv(1024)
                if confirm_msg.decode() == "received":
                    print("接收到qt消息")

        except Exception as e:
            print("发送数据到qt错误:", e)
            qt_client_socket.close()

qt分段接收
 

//.h
private:
    QTcpSocket *clientSocket = nullptr;

    QByteArray receivedData;
    quint32 expectedSize = 0;
//.cpp
    // 在 readyRead 信号的槽函数中接收数据
    QObject::connect(clientSocket, &QTcpSocket::readyRead, [=]() {
        if (expectedSize == 0) {
              // 判断是否接收到了期望的帧大小数据
              if (clientSocket->bytesAvailable() < sizeof(quint32)) {
                  return;
              }

              // 读取四个字节的帧大小数据
              QByteArray sizeData = clientSocket->read(sizeof(quint32));

              // 将网络字节顺序(大端序)的数据转换为主机字节顺序
              QDataStream sizeStream(sizeData);
              sizeStream.setByteOrder(QDataStream::BigEndian);
              sizeStream >> expectedSize;

              // 清空已接收的数据
              receivedData.clear();
              receivedData.reserve(expectedSize); // 预分配接收数据的大小
          }

          // 继续接收剩余数据
          receivedData.append(clientSocket->readAll());

          // 检查是否已接收到完整的一帧数据
          if (receivedData.size() >= expectedSize) {
              // 处理完整的一帧数据
              // 将数据转换为图像或视频
              // 在 QLabel 上显示图像或视频
              QImage image;
              image.loadFromData(receivedData, "JPG");

              // 在此处进行图像处理或显示
              label_image[i]->setPixmap(QPixmap::fromImage(image));

              // 清空已接收的数据和帧大小
              receivedData.clear();
              expectedSize = 0;
              clientSocket->write("received");
          }

    });

Qt端完整代码如下:
创建一个接受视频流的类DataReceiverThread 

class DataReceiverThread : public QObject
{
    Q_OBJECT
public:
    explicit DataReceiverThread(int PORT, QObject *parent = nullptr)
        : QObject(parent), m_PORT(PORT)
    {
    }

public:
    void run()
    {

        m_clientSocket = new QTcpSocket(this);
        QObject::connect(m_clientSocket, &QTcpSocket::stateChanged, [=](QAbstractSocket::SocketState socketState) {

            if (socketState == QAbstractSocket::ConnectedState) {
                isConnect=true;

                qDebug() << "Connected to the server.";
            } else if (socketState == QAbstractSocket::UnconnectedState) {

                isConnect=false;
                m_clientSocket->abort(); // 中断当前连接
                if (m_clientSocket->state() == QAbstractSocket::ConnectedState) {
                    m_clientSocket->waitForDisconnected(); // 等待连接关闭完成
                }
                qDebug() <<"disConnected to the server.";
                QMetaObject::invokeMethod(this, [this]() {
                           m_clientSocket->connectToHost("192.168.31.XX", m_PORT); // 重新发起连接
                       }, Qt::QueuedConnection);
//                m_clientSocket->connectToHost("192.168.31.XX", m_PORT); // 重新发起连接
                emit no_connect();

                // 重新启动接收数据的线程
            }
        });
        m_clientSocket->connectToHost("192.168.31.XX", m_PORT);
   QObject::connect(m_clientSocket, &QTcpSocket::readyRead, [=]() {

       if(isConnect){
        if (m_expectedSize == 0) {
            // 判断是否接收到了期望的帧大小数据
            if (m_clientSocket->bytesAvailable() < sizeof(quint32)) {
                return;
            }

            // 读取四个字节的帧大小数据
            QByteArray sizeData = m_clientSocket->read(sizeof(quint32));

            // 将网络字节顺序(大端序)的数据转换为主机字节顺序
            QDataStream sizeStream(sizeData);
            sizeStream.setByteOrder(QDataStream::BigEndian);
            sizeStream >> m_expectedSize;

            // 清空已接收的数据
            m_receivedData.clear();
            m_receivedData.reserve(m_expectedSize); // 预分配接收数据的大小
        }

        // 继续接收剩余数据
        m_receivedData.append(m_clientSocket->readAll());

        // 检查是否已接收到完整的一帧数据
        if (m_receivedData.size() >= m_expectedSize) {
            emit dataReceived(m_receivedData);
        }}}
   );
    }

public slots:
    void clearData(){
        m_receivedData.clear();
        m_expectedSize = 0;
        m_clientSocket->write("received");
    }
    void stopThread() {
            // 停止线程的运行
            m_stopThread = true;
            // 关闭套接字连接
            m_clientSocket->disconnectFromHost();
            qDebug()<<"destruction";
            // 将线程从事件循环中退出
            QCoreApplication::postEvent(this, new QEvent(QEvent::Quit));

       }
    void deleteObject() {
            delete this;
        }

signals:
    void dataReceived(const QByteArray& data);
    void no_connect();


private:
    QTcpSocket* m_clientSocket;
    int m_PORT;
    quint32 m_expectedSize=0;
    QByteArray m_receivedData;
    bool isConnect;
    bool m_stopThread;
    void stopConnection()
      {
          m_stopThread = true;
          m_clientSocket->disconnectFromHost();
          m_clientSocket->deleteLater();
          m_clientSocket = nullptr;
      }
};

 mianwindows.h

    QTcpSocket *clientSocket = nullptr;
    DataReceiverThread* DreceiverThread= new DataReceiverThread(0000);

mainwindows .cpp

void Video_Status::updatePixmap(int i)
{
  DreceiverThread = new DataReceiverThread(12101);
  receiverThread = new QThread();
  DreceiverThread->moveToThread(receiverThread);
  QObject::connect(this, &Video_Status::stopThread, DreceiverThread, &DataReceiverThread::stopThread);
//当接受数据后显示在label上
  QObject::connect(DreceiverThread, &DataReceiverThread::dataReceived,this,[=](const QByteArray& data) {

                         QImage image;
                        image.loadFromData(data, "JPG");
                        // 在此处进行图像处理或显示
                        label_image[i][0]->setPixmap(QPixmap::fromImage(image));
                        emit over();
                });
//显示完图像后清楚数据
 QObject::connect(this, &Video_Status::over, DreceiverThread, &DataReceiverThread::clearData);
//断开连接时显示无视频信号
 QObject::connect(DreceiverThread, &DataReceiverThread::no_connect, this,[=]() {
                    label_image[i][0]->setPalette(pe);
                    label_image[i][0]->setText("无视频信号");
                });
//将线程和类连接在一起
 QObject::connect(receiverThread, &QThread::started, DreceiverThread, &DataReceiverThread::run);
 receiverThread->start();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值