#include "DroneScrewCtrlPresenter.h" #include #include DroneScrewCtrlPresenter::DroneScrewCtrlPresenter(QObject* parent) : QObject(parent) { m_pReconnectTimer = new QTimer(this); m_pReconnectTimer->setInterval(3000); connect(m_pReconnectTimer, &QTimer::timeout, this, &DroneScrewCtrlPresenter::onReconnectTimer); connect(this, &DroneScrewCtrlPresenter::rtspFrameReady, this, &DroneScrewCtrlPresenter::onOverlayFrameReady, Qt::QueuedConnection); connect(this, &DroneScrewCtrlPresenter::detectionResultReady, this, &DroneScrewCtrlPresenter::onResultReceived, Qt::QueuedConnection); } DroneScrewCtrlPresenter::~DroneScrewCtrlPresenter() { DeinitApp(); } void DroneScrewCtrlPresenter::ApplyConfig(const DroneScrewCtrlConfigResult& cfg) { m_cfg = cfg; if (m_bRunning.load()) { DeinitApp(); InitApp(); } } int DroneScrewCtrlPresenter::InitApp() { if (m_bRunning.load()) return 0; // 1) 创建 WDRemoteReceiver 并注入回调(控制 + 结果 + 原始图) if (IWDRemoteReceiver::CreateInstance(&m_pRemote) != 0 || !m_pRemote) { m_bConnected = false; emit serverConnectionChanged(false); m_pReconnectTimer->start(); return -1; } m_pRemote->SetEventCallback( [this](WDRemoteEventType ev, const std::string& msg) { this->onRemoteEvent(ev, msg); }); m_pRemote->SetDetectionCallback( [this](const WDRemoteDetectionFrame& f) { this->onRemoteDetection(f); }); // 检测模式下板一关闭 RTSP、改用 ZMQ 原始图通道发图;订阅之以显示检测结果图像 m_pRemote->SetRawImageCallback( [this](const WDRemoteBinocularRawImage& img) { this->onRemoteRawImage(img); }); // 2) UDP 搜索设备 → 默认自动打开第一个;搜不到不再按固定 IP 直接交互。 bool opened = false; std::vector devices; if (m_pRemote->SearchDevices(devices, 500) == 0 && !devices.empty()) { const WDRemoteDeviceInfo& d = devices.front(); if (m_pRemote->Open(d.machineCode) == 0) { // 用发现到的设备信息更新连接参数,供 RTSP 拉流使用 if (!d.serverIp.empty()) m_cfg.server.serverIp = d.serverIp; if (d.controlPort > 0) m_cfg.server.zmqControlPort = d.controlPort; if (d.resultPort > 0) m_cfg.server.zmqResultPort = d.resultPort; m_cfg.server.zmqRawImagePort = d.rawImagePort; if (!d.rtspUrl.empty()) m_streamUrl = QString::fromStdString(d.rtspUrl); opened = true; emit statusMessage(QStringLiteral("已自动打开设备 %1 (%2)") .arg(QString::fromStdString(d.deviceName), QString::fromStdString(d.serverIp))); } } if (!opened) { emit statusMessage(QStringLiteral("未发现 DroneScrewServer,等待自动搜索")); m_bConnected = false; emit serverConnectionChanged(false); delete m_pRemote; m_pRemote = nullptr; m_pReconnectTimer->start(); return -1; } // 拉流器等 start_stream 成功后再打开;地址以 Server get_info/discovery 返回为准。 m_bConnected = true; m_bRunning = true; emit serverConnectionChanged(true); emit statusMessage(QStringLiteral("已连接到服务器")); // 4) 延迟从 Server 获取参数(相机参数、算法参数)并更新 UI QTimer::singleShot(200, this, [this]() { if (m_pRemote && m_bConnected.load()) { WDRemoteServerInfo info = m_pRemote->GetServerInfo(); if (info.ok) { updateStreamInfo(info); emit serverInfoReceived(info); } } }); return 0; } void DroneScrewCtrlPresenter::DeinitApp() { if (m_pReconnectTimer && m_pReconnectTimer->isActive()) m_pReconnectTimer->stop(); releasePuller(); if (m_pRemote) { m_pRemote->Disconnect(); delete m_pRemote; m_pRemote = nullptr; } m_bRunning = false; m_bConnected = false; m_streamUrl.clear(); } void DroneScrewCtrlPresenter::onReconnectTimer() { if (m_bRunning.load() && m_bConnected.load()) { m_pReconnectTimer->stop(); return; } emit statusMessage(QStringLiteral("尝试重新连接...")); DeinitApp(); if (InitApp() == 0) m_pReconnectTimer->stop(); } void DroneScrewCtrlPresenter::updateStreamInfo(const WDRemoteServerInfo& info) { if (!info.ok) return; if (!info.rtspUrl.empty()) m_streamUrl = QString::fromStdString(info.rtspUrl).trimmed(); } void DroneScrewCtrlPresenter::releasePuller() { if (!m_pPuller) return; m_pPuller->Stop(); m_pPuller->UnInit(); delete m_pPuller; m_pPuller = nullptr; } bool DroneScrewCtrlPresenter::initPuller(const QString& streamUrl) { releasePuller(); const QString url = streamUrl.trimmed(); if (url.isEmpty()) { emit statusMessage(QStringLiteral("Server 未返回拉流地址")); return false; } if (!IVrFFMediaPuller::CreateObject(&m_pPuller) || !m_pPuller) { emit statusMessage(QStringLiteral("创建拉流器失败")); return false; } VrFFPullConfig pc; pc.rtspUrl = url.toStdString(); pc.useTcp = m_cfg.server.rtspUseTcp; pc.outputFormat = EFFPullPixelFormat::NV12; m_pPuller->SetFrameCallback([this](const VrFFPulledFrame& f) { this->onRtspFrame(f); }); const int ret = m_pPuller->Init(pc); if (ret != 0) { emit statusMessage(QStringLiteral("打开拉流地址失败: %1").arg(url)); releasePuller(); return false; } return true; } bool DroneScrewCtrlPresenter::TriggerSingleDetection() { return m_pRemote && m_pRemote->RequestSingleDetection() == 0; } bool DroneScrewCtrlPresenter::StartLiveStream() { if (!m_pRemote) return false; releasePuller(); m_pRemote->StopWork(WDRemoteWorkMode::Detection); bool ok = m_pRemote->StartWork(WDRemoteWorkMode::LiveStream) == 0; if (ok) { WDRemoteServerInfo info = m_pRemote->GetServerInfo(); if (info.ok) { updateStreamInfo(info); emit serverInfoReceived(info); } if (!initPuller(m_streamUrl)) { m_pRemote->StopWork(WDRemoteWorkMode::LiveStream); return false; } const int startRet = m_pPuller ? m_pPuller->Start() : -1; if (startRet != 0) { emit statusMessage(QStringLiteral("启动拉流失败: %1").arg(m_streamUrl)); releasePuller(); m_pRemote->StopWork(WDRemoteWorkMode::LiveStream); return false; } emit statusMessage(QStringLiteral("Server 已开流: %1").arg(m_streamUrl)); } return ok; } bool DroneScrewCtrlPresenter::StopLiveStream() { if (!m_pRemote) return false; // 停止 RTSP 拉流 releasePuller(); bool ok = m_pRemote->StopWork(WDRemoteWorkMode::LiveStream) == 0; if (ok) emit statusMessage(QStringLiteral("Server 已关流")); return ok; } bool DroneScrewCtrlPresenter::StartDetection() { if (!m_pRemote) return false; // 检测模式下停止 RTSP 拉流(改用 ZMQ 原始图像通道) releasePuller(); m_pRemote->StopWork(WDRemoteWorkMode::LiveStream); // 使用单目传输模式:仅接收左目图像,减少网络带宽消耗 m_pRemote->SetDetectMode("mono"); bool ok = m_pRemote->StartWork(WDRemoteWorkMode::Detection) == 0; if (ok) emit statusMessage(QStringLiteral("Server 已开始检测(单目传输)")); return ok; } bool DroneScrewCtrlPresenter::StopDetection() { if (!m_pRemote) return false; bool ok = m_pRemote->StopWork(WDRemoteWorkMode::Detection) == 0; if (ok) emit statusMessage(QStringLiteral("Server 已停止检测")); return ok; } bool DroneScrewCtrlPresenter::StopAll() { if (!m_pRemote) return false; // 停止 RTSP 拉流 releasePuller(); // 两种模式都停掉,保证服务端回到空闲(幂等) m_pRemote->StopWork(WDRemoteWorkMode::Detection, 1000); m_pRemote->StopWork(WDRemoteWorkMode::LiveStream, 1000); emit statusMessage(QStringLiteral("Server 已停止(实时/检测)")); return true; } bool DroneScrewCtrlPresenter::SetServerExposure(double exposureTime) { return m_pRemote && m_pRemote->SetExposure(exposureTime) == 0; } bool DroneScrewCtrlPresenter::SetServerGain(double gain) { return m_pRemote && m_pRemote->SetGain(gain) == 0; } bool DroneScrewCtrlPresenter::PushAlgoParams(const DroneScrewAlgoUiParams& params) { if (!m_pRemote) return false; WDRemoteAlgoParams p; p.scoreThreshold = params.scoreThreshold; p.nmsThreshold = params.nmsThreshold; p.inputWidth = params.inputWidth; p.inputHeight = params.inputHeight; p.modelType = params.modelType; p.modelPath = params.modelPath; return m_pRemote->SetAlgoParams(p) == 0; } // ===================================================================== // WDRemoteReceiver 回调 // ===================================================================== void DroneScrewCtrlPresenter::onRemoteDetection(const WDRemoteDetectionFrame& f) { CtrlDetectionFrame frame = toCtrlFrame(f); { QMutexLocker lk(&m_lastResultMutex); m_lastResult = frame; m_bHasResult = true; } emit detectionResultReady(frame); } void DroneScrewCtrlPresenter::onRemoteRawImage(const WDRemoteBinocularRawImage& img) { // 检测模式下板一通过 ZMQ 原始图通道发来左目图像(此时 RTSP 已关闭)。 // 转为 QImage,走与 RTSP 相同的叠加+显示通路(onOverlayFrameReady 会叠加检测框)。 if (!img.leftData || img.leftWidth <= 0 || img.leftHeight <= 0) return; QImage q; switch (img.leftPixelFormat) { case WDRemotePixelFormat::MONO8: q = QImage(reinterpret_cast(img.leftData), img.leftWidth, img.leftHeight, img.leftStride, QImage::Format_Grayscale8).copy(); break; case WDRemotePixelFormat::RGB8: q = QImage(reinterpret_cast(img.leftData), img.leftWidth, img.leftHeight, img.leftStride, QImage::Format_RGB888).copy(); break; case WDRemotePixelFormat::BGR8: q = QImage(reinterpret_cast(img.leftData), img.leftWidth, img.leftHeight, img.leftStride, QImage::Format_RGB888).rgbSwapped(); break; default: return; } if (!q.isNull()) emit rtspFrameReady(q); } void DroneScrewCtrlPresenter::onRemoteEvent(WDRemoteEventType ev, const std::string& msg) { switch (ev) { case WDRemoteEventType::CONNECTED: m_bConnected = true; emit serverConnectionChanged(true); emit statusMessage(QStringLiteral("控制通道已连接")); break; case WDRemoteEventType::DISCONNECTED: m_bConnected = false; emit serverConnectionChanged(false); emit statusMessage(QStringLiteral("控制通道已断开")); if (m_bRunning.load()) m_pReconnectTimer->start(); break; case WDRemoteEventType::CONNECTION_ERROR: m_bConnected = false; emit serverConnectionChanged(false); emit statusMessage(QStringLiteral("连接错误: %1").arg(QString::fromStdString(msg))); if (m_bRunning.load()) m_pReconnectTimer->start(); break; case WDRemoteEventType::REQUEST_TIMEOUT: emit statusMessage(QStringLiteral("请求超时")); break; default: break; } } CtrlDetectionFrame DroneScrewCtrlPresenter::toCtrlFrame(const WDRemoteDetectionFrame& f) { CtrlDetectionFrame frame; frame.frameId = static_cast(f.frameId); frame.timestampUs = f.timestampUs; frame.imageWidth = f.imageWidth; frame.imageHeight = f.imageHeight; frame.success = f.success; frame.errorCode = f.errorCode; frame.message = QString::fromStdString(f.message); frame.boxes.reserve(f.boxes.size()); for (const auto& b : f.boxes) { CtrlDetectionBox cb; cb.classId = b.classId; cb.score = b.score; cb.x = b.x; cb.y = b.y; cb.width = b.width; cb.height = b.height; frame.boxes.push_back(cb); } frame.distances.reserve(f.distances.size()); for (const auto& d : f.distances) { CtrlDetectionDistance cd; cd.fromId = d.fromId; cd.toId = d.toId; cd.distanceMm = d.distanceMm; frame.distances.push_back(cd); } return frame; } // ===================================================================== // RTSP // ===================================================================== void DroneScrewCtrlPresenter::onRtspFrame(const VrFFPulledFrame& frame) { if (!frame.data || frame.width == 0 || frame.height == 0) return; QImage img; if (frame.format == EFFPullPixelFormat::NV12) { img = QImage(static_cast(frame.data), frame.width, frame.height, frame.stride, QImage::Format_Grayscale8).copy(); } else if (frame.format == EFFPullPixelFormat::RGBA32) { img = QImage(static_cast(frame.data), frame.width, frame.height, frame.stride, QImage::Format_RGBA8888).copy(); } else if (frame.format == EFFPullPixelFormat::BGR24) { img = QImage(static_cast(frame.data), frame.width, frame.height, frame.stride, QImage::Format_BGR888).copy(); } else { return; } emit rtspFrameReady(img); } void DroneScrewCtrlPresenter::onOverlayFrameReady(const QImage& img) { QImage frame = img; CtrlDetectionFrame det; bool hasDet = false; { QMutexLocker lk(&m_lastResultMutex); hasDet = m_bHasResult; if (hasDet) det = m_lastResult; } if (hasDet) overlayDetectionBoxes(frame, det); { QMutexLocker lk(&m_lastImgMutex); m_lastDisplay = frame; } if (m_pReceiver) m_pReceiver->OnRtspFrame(frame); } void DroneScrewCtrlPresenter::onResultReceived(const CtrlDetectionFrame& result) { if (m_pReceiver) m_pReceiver->OnDetectionResult(result); } void DroneScrewCtrlPresenter::overlayDetectionBoxes(QImage& img, const CtrlDetectionFrame& frame) { if (!m_cfg.display.drawBoxes || frame.boxes.empty()) return; QPainter p(&img); QPen pen(QColor(0, 220, 0), m_cfg.display.boxThickness); p.setPen(pen); QFont f = p.font(); f.setPointSize(10); p.setFont(f); const double scaleX = (frame.imageWidth > 0) ? static_cast(img.width()) / frame.imageWidth : 1.0; const double scaleY = (frame.imageHeight > 0) ? static_cast(img.height()) / frame.imageHeight : 1.0; for (const auto& b : frame.boxes) { QRect r(static_cast(b.x * scaleX), static_cast(b.y * scaleY), static_cast(b.width * scaleX), static_cast(b.height * scaleY)); p.drawRect(r); if (m_cfg.display.drawScores || m_cfg.display.drawClassId) { QString label; if (m_cfg.display.drawClassId) label += QString("cls=%1 ").arg(b.classId); if (m_cfg.display.drawScores) label += QString("%1").arg(b.score, 0, 'f', 2); p.drawText(r.topLeft() + QPoint(0, -3), label); } } } QImage DroneScrewCtrlPresenter::GetLastDisplayImage() const { QMutexLocker lk(&m_lastImgMutex); return m_lastDisplay; }