506 lines
16 KiB
C++
506 lines
16 KiB
C++
#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;
|
||
}
|