Files
flyLink/skylink_qt_station/src/GroundReceiver.h
2026-02-02 17:06:20 +08:00

248 lines
6.8 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file GroundReceiver.h
* @brief SkyLink Qt 地面站 - UDP 接收和数据重组头文件
*
* 在单独的线程中运行,负责:
* 1. 监听 UDP 广播
* 2. 接收分片数据包
* 3. 重组完整帧
* 4. 解码 JPEG 图像
*
* @author flowzl
* @date 2026-01-19
*/
#pragma once
#include <QObject>
#include <QUdpSocket>
#include <QThread>
#include <QImage>
#include <QMap>
#include <QDebug>
#include <QString>
#include <map>
#include <vector>
#include <memory>
#include <cstdint>
#include <mutex>
// OpenCV for YUYV conversion
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>
#include "protocol.h"
/**
* @struct FrameBuffer
* @brief 帧缓冲区,用于重组分片数据
*/
struct FrameBuffer {
uint32_t frame_id;
uint16_t total_chunks;
std::map<uint16_t, std::vector<uint8_t>> 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<uint8_t> merge() const {
std::vector<uint8_t> 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<uint8_t>();
}
}
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<QUdpSocket> udp_socket_;
QThread worker_thread_;
// ========================================================================
// 帧缓冲区管理
// ========================================================================
std::map<uint32_t, FrameBuffer> 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<uint8_t>& data);
/**
* @brief 从 YUYV 数据转换为 QImage
* @param yuyv_data YUYV格式的原始数据
* @param width 图像宽度
* @param height 图像高度
* @return 转换后的QImage
*/
QImage decode_yuyv(const std::vector<uint8_t>& yuyv_data, int width, int height);
/**
* @brief 清理超时的帧缓冲区
*/
void cleanup_timeout_frames();
/**
* @brief 更新和报告统计信息
*/
void update_stats(uint32_t packet_size);
};