506 lines
16 KiB
C++
Raw Normal View History

2026-06-26 17:55:15 +08:00
#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;
}