使用 Qt 获取 UDP 数据并显示成图片 (3)

CameraData 类继承自 DataObj 类,而 DataObj 类又继承自 QObject,这样方便进行内存管理和对象上的操作。DataObj 是为了方便复用对象而定义的基类,详细代码可参考 github 上的完整代码。

C++ 部分的 CameraData 类与 Python 中定义的 CameraData 类是对应的,不过 C++ 部分的 CameraData 类只需要调用 CameraData::setRawData 传入一个 QByteArray 对象后就可以自动将其中包含的数据解析出来,并且它只提供获取数据的接口而不提供修改数据的接口。

另外我们还需要定义一个类 PreProcessData,来表示一行数据:

/** * @brief The PreProcessData class * 预处理数据 */ class PreProcessData: public DataObj { Q_OBJECT public: static const int PacketSize; static const int PacketPerLine; explicit PreProcessData(QObject *parent = 0, int line = -1); void put(CameraData *cd); bool isReady(); void reset(); int line() const; void setLine(int line); const QByteArrayList &getDataList() const; QByteArray repr(); private: /** * @brief cameraData * 每 2 个 CameraData 构成一行的单通道数据,有序存放 RGB 通道数据 * 0-1 存放 R,2-3 存放 G, 4-5 存放 B */ QByteArrayList dataList; int m_line; int m_readyCount = 0; int m_duplicateCount = 0; bool *dataPlaced = 0; };

目前的协议中,每 2 个数据包(对应 2 个 CameraData 对象)构成某一行的单通道数据,所以 PreProcessData 中至少会包含 6 个 CameraData 对象,处理完 CameraData 对象后,只需要存储 Data 部分即可,所以这里没有用 QList 列表,而是直接使用 QByteArrayList 来存储数据。当三个通道的数据都准备好后,PreProcessData::isReady 就会返回 true,表示该行数据已经准备好,可以显示在窗口中。

在子线程中执行接收 UDP 包和处理过程

我们定义一个 Controller 类用来操作数据接收对象和子线程。用 Qt 的事件槽机制和 QObject::moveToThread 实现多线程非常方便,不重写 QThread 的 run 方法就可以让对象的方法在子线程中执行。

class Controller : public QObject { Q_OBJECT public: explicit Controller(QObject *parent = 0); ~Controller(); static const int DataPort; static const int CONTROL_PORT; static const QStringList BOARD_IP; void start(); void stop(); DataProcessor *getDataProcessor() const; signals: public slots: private: CameraDataReceiver *cdr; QThread recvThread; QThread recvProcessThread; QByteArrayList rawdataList; DataProcessor *dp = 0; QTimer *statsTimer; int statsInterval; };

其中 CameraDataReceiver 对象会被实例化,在子线程中接收 UDP 数据包(因为发送和接收数据的端口是不同的,操作和数据是分离的)。这里将 DataProcessor 通过 getDataProcessor 暴露给上层应用,以便上层应用连接信号槽接收图像。仅到接收数据,就用到了三个线程:分别是 GUI 线程,用于接收 UDP 包的 recvThread 线程和处理 UDP 的 recvProcessThread。

为什么接收 UDP 包和处理 UDP 包不是放在一个线程中执行呢?因为这里的数据量实在太多,最开始实现的时候这两个逻辑代码确实是在同一个线程中执行,然而由于处理数据的代码执行起来也要消耗时间,将会导致无法接收其他的 UDP 包,这样的话就会导致比较严重的丢包。为了保证接收端不会丢包,只好将处理逻辑放在其他的线程中执行。

Qt 接收 UDP 包

将接收数据和处理数据放在不同的线程中执行,确实可以解决丢包问题了,但是会出现新的问题:接收到的包如果不能够及时处理完,并且释放掉相应的资源,那么可能会出现程序将数据缓存下来但无法处理,程序占用的内存越来越大,导致程序运行起来越来越慢。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsxfdy.html