/** * @file GroundReceiver.h * @brief SkyLink Qt 地面站 - UDP 接收和数据重组头文件 * * 在单独的线程中运行,负责: * 1. 监听 UDP 广播 * 2. 接收分片数据包 * 3. 重组完整帧 * 4. 解码 JPEG 图像 * * @author flowzl * @date 2026-01-19 */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include // OpenCV for YUYV conversion #include #include #include "protocol.h" /** * @struct FrameBuffer * @brief 帧缓冲区,用于重组分片数据 */ struct FrameBuffer { uint32_t frame_id; uint16_t total_chunks; std::map> chunks; // chunk_index -> 数据 double timestamp; double lat, lon, alt; uint64_t receive_time_ms; uint32_t photo_number = 0; // 照片编号(从reserve字段提取) /** * @brief 检查是否所有分片都已接收 */ bool is_complete() const { if (total_chunks == 0) return false; for (uint16_t i = 0; i < total_chunks; i++) { if (chunks.find(i) == chunks.end()) { return false; } } return true; } /** * @brief 将所有分片合并为一个字节数组 * @return 合并后的数据,如果分片不完整返回空vector */ std::vector merge() const { std::vector result; // 预分配空间以提高性能 size_t estimated_size = chunks.size() * 500; // 估算每个分片约500字节 result.reserve(estimated_size); // 调试:检查分片顺序 QString chunk_indices; for (const auto& pair : chunks) { chunk_indices += QString::number(pair.first) + " "; } qDebug() << "帧" << frame_id << "合并前,分片索引:" << chunk_indices; for (uint16_t i = 0; i < total_chunks; i++) { auto it = chunks.find(i); if (it != chunks.end()) { // 调试:如果是第一个分片,打印前几个字节 if (i == 0 && !it->second.empty()) { QString hex_preview; for (size_t j = 0; j < std::min((size_t)8, it->second.size()); j++) { hex_preview += QString::number(it->second[j], 16).rightJustified(2, '0') + " "; } qDebug() << "帧" << frame_id << "合并时,分片0前8字节:" << hex_preview; } result.insert(result.end(), it->second.begin(), it->second.end()); } else { // 如果缺少分片,返回空vector表示不完整 qWarning() << "帧" << frame_id << "合并时缺少分片" << i; return std::vector(); } } return result; } }; /** * @class GroundReceiver * @brief UDP 接收和数据重组引擎 */ class GroundReceiver : public QObject { Q_OBJECT public: /** * @brief 构造函数 * @param parent 父对象 * @param port UDP 监听端口 (默认 9999) */ explicit GroundReceiver(QObject* parent = nullptr, uint16_t port = 9999); /** * @brief 析构函数 */ ~GroundReceiver(); /** * @brief 启动接收器 */ void start(); /** * @brief 停止接收器 */ void stop(); /** * @brief 获取是否正在运行 */ bool is_running() const; signals: /** * @brief 完整帧解码完成信号 * @param img 解码后的 QImage * @param lat 纬度 * @param lon 经度 * @param alt 海拔 * @param photo_number 照片编号 */ void frameDecoded(const QImage& img, double lat, double lon, double alt, uint32_t photo_number); /** * @brief 统计信息更新信号 * @param fps 每秒帧数 * @param kbs 每秒 KB 数 */ void statsUpdated(int fps, int kbs); /** * @brief 错误信号 * @param error_msg 错误信息 */ void error(const QString& error_msg); private slots: /** * @brief UDP 有数据可读时的槽函数 */ void on_udp_read_ready(); /** * @brief 处理 UDP 错误 */ void on_udp_error(); /** * @brief 定期清理超时的帧 */ void on_cleanup_timeout(); private: // ======================================================================== // UDP 套接字和网络 // ======================================================================== uint16_t listen_port_; std::unique_ptr udp_socket_; QThread worker_thread_; // ======================================================================== // 帧缓冲区管理 // ======================================================================== std::map frame_buffers_; // frame_id -> FrameBuffer std::mutex frame_buffers_mutex_; uint32_t last_complete_frame_id_; // ======================================================================== // 统计信息 // ======================================================================== struct { uint32_t total_packets = 0; ///< 总接收包数 uint32_t total_bytes = 0; ///< 总接收字节数 uint32_t total_frames = 0; ///< 完整帧数 uint32_t dropped_frames = 0; ///< 丢弃的帧数 uint32_t frames_this_second = 0; ///< 本秒帧数 uint32_t bytes_this_second = 0; ///< 本秒字节数 uint64_t last_stats_time = 0; ///< 上次统计时刻 } stats_; std::mutex stats_mutex_; // ======================================================================== // 私有方法 // ======================================================================== /** * @brief 处理接收到的 UDP 数据包 */ void process_packet(const uint8_t* data, size_t len); /** * @brief 处理完整的帧 */ void handle_complete_frame(const FrameBuffer& frame); /** * @brief 从 JPEG 数据解码为 QImage */ QImage decode_jpeg(const std::vector& data); /** * @brief 从 YUYV 数据转换为 QImage * @param yuyv_data YUYV格式的原始数据 * @param width 图像宽度 * @param height 图像高度 * @return 转换后的QImage */ QImage decode_yuyv(const std::vector& yuyv_data, int width, int height); /** * @brief 清理超时的帧缓冲区 */ void cleanup_timeout_frames(); /** * @brief 更新和报告统计信息 */ void update_stats(uint32_t packet_size); };