2026-06-26 17:55:15 +08:00

506 lines
16 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.

#include "DroneScrewCtrlPresenter.h"
#include <QPainter>
#include <QFont>
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<WDRemoteDeviceInfo> 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<const uchar*>(img.leftData),
img.leftWidth, img.leftHeight, img.leftStride,
QImage::Format_Grayscale8).copy();
break;
case WDRemotePixelFormat::RGB8:
q = QImage(reinterpret_cast<const uchar*>(img.leftData),
img.leftWidth, img.leftHeight, img.leftStride,
QImage::Format_RGB888).copy();
break;
case WDRemotePixelFormat::BGR8:
q = QImage(reinterpret_cast<const uchar*>(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<qint64>(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<const uchar*>(frame.data),
frame.width, frame.height, frame.stride,
QImage::Format_Grayscale8).copy();
}
else if (frame.format == EFFPullPixelFormat::RGBA32)
{
img = QImage(static_cast<const uchar*>(frame.data),
frame.width, frame.height, frame.stride,
QImage::Format_RGBA8888).copy();
}
else if (frame.format == EFFPullPixelFormat::BGR24)
{
img = QImage(static_cast<const uchar*>(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<double>(img.width()) / frame.imageWidth
: 1.0;
const double scaleY = (frame.imageHeight > 0)
? static_cast<double>(img.height()) / frame.imageHeight
: 1.0;
for (const auto& b : frame.boxes)
{
QRect r(static_cast<int>(b.x * scaleX),
static_cast<int>(b.y * scaleY),
static_cast<int>(b.width * scaleX),
static_cast<int>(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;
}