#ifndef DRONESCREW_SERVER_PRESENTER_H #define DRONESCREW_SERVER_PRESENTER_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include "IMvsDevice.h" #include "IVrFFMediaPusher.h" #include "Algo/IDroneScrewAlgo.h" /** * @brief 双板架构「板一」 Presenter(双目版本) * * 流程: * 双目海康MVS相机采集 → 算法检测(左右目输入)→ 画检测框 → 推送至 RTSP 服务 → ZMQ 异步发布结果 * * 架构特点: * - 使用 IMvsDevice 双目相机(左右目) * - 左右目图像同步等待机制(参考 BinocularMarkPresenter) * - 算法检测需要左右目双目输入 * - RTMP/RTSP 推流推送 Rtsp.streamCamera 指定的单路图像 * - ZMQ 原始图像订阅发送左右目两张图像(或仅左目单张图像,由 detectMode 控制) * * detectMode 说明: * - "binocular"(默认):ZMQ 原始图像 PUB 发送左右目两张图像(BIWI 协议头) * - "mono":ZMQ 原始图像 PUB 仅发送左目图像(BIWI 协议头,右目置空) * 注意:mono 模式下算法仍使用双目输入,仅传输层面减少右目数据量 */ class DroneScrewServerPresenter : public QObject { Q_OBJECT public: explicit DroneScrewServerPresenter(QObject* parent = nullptr); ~DroneScrewServerPresenter() override; /** * @brief 加载配置文件 */ bool loadConfiguration(const QString& configFilePath); /** * @brief 设置 saveConfiguration 的目标路径 * * 运行时调用此方法可以修改保存位置,主要用于将用户配置持久化到 * 独立文件中(如 runtime_config.xml),避免被部署包覆盖。 */ void setConfigFilePath(const QString& path) { m_configFilePath = path; } /** * @brief 保存当前配置到 m_configFilePath * * 保存 Camera(曝光/增益)和 Trigger 等运行时参数。 */ bool saveConfiguration(); /** * @brief 初始化相机 / 推流 / 算法 */ int initAll(); /** * @brief 反初始化 */ void deinitAll(); /** * @brief 当前检测中 */ bool isDetecting() const { return m_bIsDetecting.load(); } bool isLiveStreaming() const { return m_bLiveStreaming.load(); } /** * @brief 同步执行单次检测,并返回真实执行结果 * * 供 ZMQ REP 的 single 命令使用。失败时返回 success=false, * errorCode/message 描述失败原因。 */ DroneScrewResult runSingleDetection(); /** * @brief 获取 ZMQ REP / PUB 端口(供主程序启动 ZMQ 协议层) */ int getZmqControlPort() const { return m_zmqControlPort; } int getZmqResultPort() const { return m_zmqResultPort; } int getZmqRawImagePort() const { return m_zmqRawImagePort; } QString getRtspUrl() const { return m_rtspAdvertiseUrl; } QJsonObject getRuntimeInfo() const; QJsonObject getCalibrationInfo() const; int startDetectionWork(); int stopDetectionWork(); int startLiveStream(); int stopLiveStream(); int swapCameraRoles(); /** * @brief 启用 / 停用 ZMQ 原始图像 PUB * * ZMQ 协议层启动 raw PUB 端口后会调用 Enable, * 之后采集回调里会 emit rawImageReady。 */ void setRawPubEnabled(bool enabled) { m_bRawPubEnabled = enabled; } /** * @brief 设置检测模式下的图像传输模式 * @param mode "binocular"(默认,发送左右目)或 "mono"(仅发送左目) * * 仅影响 ZMQ 原始图像 PUB 通道的传输内容; * 算法内部始终使用双目输入。 */ void setDetectMode(const std::string& mode); std::string detectMode() const; public slots: /** * @brief 设置曝光(同时设置左右相机) */ void handleSetExposure(double exposureTime); /** * @brief 设置增益(同时设置左右相机) */ void handleSetGain(double gain); /** * @brief 设置左相机曝光 */ void handleSetLeftExposure(double exposureTime); /** * @brief 设置右相机曝光 */ void handleSetRightExposure(double exposureTime); /** * @brief 设置左相机增益 */ void handleSetLeftGain(double gain); /** * @brief 设置右相机增益 */ void handleSetRightGain(double gain); /** * @brief 更新算法参数 */ void handleUpdateAlgoParams(const DroneScrewAlgoParams& params); signals: /** * @brief 单帧检测结果信号(提供给 ZMQ 协议层异步发布) */ void detectionResult(const DroneScrewResult& result); /** * @brief 原始双目相机帧信号 * * 仅当 m_bRawPubEnabled 为 true 时才会 emit,供 ZMQ 协议层广播给客户端。 * 注意:MvsImageData 内部的 pData 由 presenter 管理,槽函数收到后请立即处理 * 或拷贝;本对象只保证在同步 emit 期间内有效。 */ void rawImageReady(const MvsImageData& leftImg, const MvsImageData& rightImg); /** * @brief 状态/事件信号 */ void statusChanged(const QString& msg); private slots: /** * @brief 相机重连定时器 */ void onCameraReconnectTimer(); private: int tryConnectCamera(); void closeCamera(); int configureCamera(IMvsDevice* camera, const char* role, bool ioTrigger = false, double frameRate = 2.0, int64_t binningHorizontal = 1, int64_t binningVertical = 1, int64_t decimationHorizontal = 1, int64_t decimationVertical = 1); IMvsDevice* cameraForRole(const char* role) const; const char* liveStreamCameraRole() const; IMvsDevice* liveStreamCamera() const; IMvsDevice* nonLiveStreamCamera() const; bool isLiveStreamCameraRole(const char* role) const; bool areCamerasGrabbing(); int prepareDetectionAcquisition(double frameRate, int64_t binningHorizontal, int64_t binningVertical, int64_t decimationHorizontal, int64_t decimationVertical, const char* tag); int prepareLiveStreamAcquisition(); void resetFrameReadyFlags(); int initRtspPusher(unsigned int width, unsigned int height, unsigned int fps); int startRtspPusher(); void releaseRtspPusher(); int pushRtspFrame(const MvsImageData& img); void resetRtspPushState(); void recordRtspPushResult(int ret, unsigned long long frameId); bool tryRgaScaleMonoToY(const MvsImageData& img, unsigned char* dstY, unsigned int outWidth, unsigned int outHeight); void leftCameraCallback(const MvsImageData& img); void rightCameraCallback(const MvsImageData& img); void detectThreadFunc(); double exposureForRole(const char* role) const; int applyExposureForRole(const char* role); int applyCurrentExposureForRole(const char* role); double gainForRole(const char* role) const; int pulseGpioTrigger(bool logOk = true); void startGpioTriggerLoop(); void stopGpioTriggerLoop(); void triggerThreadFunc(); bool waitAndCopyLatestBinocular(MvsImageData& leftImg, MvsImageData& rightImg, unsigned int timeoutMs); void startImageSaveThread(const QString& modeName); void stopImageSaveThread(); void imageSaveThreadFunc(int workerIndex); void enqueueImageSave(const MvsImageData& leftImg, const MvsImageData& rightImg, const DroneScrewResult& result, unsigned long long index); QString imageSaveModeName() const; private: struct ImageSaveBuffer { unsigned int width{0}; unsigned int height{0}; int pixelFormat{0}; std::vector data; bool valid{false}; }; struct ImageSaveJob { QString dirPath; unsigned long long index{0}; ImageSaveBuffer left; ImageSaveBuffer right; DroneScrewResult result; bool hasResult{false}; }; // 双目相机设备 IMvsDevice* m_pLeftCamera{nullptr}; IMvsDevice* m_pRightCamera{nullptr}; // 推流 IVrFFMediaPusher* m_pPusher{nullptr}; // 算法 std::unique_ptr m_pAlgo; DroneScrewAlgoParams m_algoParams; // 状态 std::atomic m_bCameraConnected{false}; std::atomic m_bIsDetecting{false}; QTimer* m_pReconnectTimer{nullptr}; // 配置 std::string m_strLeftCameraSerial; std::string m_strRightCameraSerial; unsigned int m_nLeftCameraIndex{0}; unsigned int m_nRightCameraIndex{1}; double m_leftExposureTime{10000.0}; double m_rightExposureTime{10000.0}; double m_leftGain{1.0}; double m_rightGain{1.0}; QString m_configFilePath; int m_zmqControlPort{5555}; int m_zmqResultPort{5556}; int m_zmqRawImagePort{5557}; int m_rtspPort{8554}; QString m_rtspPath{"/live/dronescrew"}; QString m_rtspAdvertiseUrl; int m_pushBitrateKbps{4096}; int m_pushFps{30}; int m_liveStreamFps{30}; unsigned int m_liveStreamWidth{1024}; unsigned int m_liveStreamHeight{750}; std::string m_liveStreamCameraRole{"left"}; // fixed to left for realtime stream bool m_useIoTrigger{true}; int m_triggerGpio{113}; int m_triggerSource{0}; // MVS Line0 int m_triggerActivation{0}; // MVS RisingEdge std::thread m_triggerThread; std::atomic m_bTriggerThreadExit{false}; unsigned int m_rtspWidth{0}; unsigned int m_rtspHeight{0}; std::atomic m_bRtspStarted{false}; std::atomic m_bLiveStreaming{false}; std::atomic m_detectPipelineMode{0}; // 0=precision, 1=distance std::atomic m_activeTriggerFps{2}; std::atomic m_rtspFrameCounter{0}; // 相对帧计数(推流启动时重置) std::mutex m_rtspPusherMutex; std::mutex m_rtspPushMutex; std::atomic m_rtspPushedFrameCounter{0}; std::atomic m_rtspLastPushRet{0}; std::atomic m_rtspLastPushFrameId{0}; std::vector m_rtspFrameBuffer; std::vector m_rtspScaleX; std::vector m_rtspScaleY; unsigned int m_rtspScaleSrcWidth{0}; unsigned int m_rtspScaleSrcHeight{0}; unsigned int m_rtspScaleOutWidth{0}; unsigned int m_rtspScaleOutHeight{0}; bool m_rtspUvInitialized{false}; bool m_rtspRgaScaleDisabled{false}; bool m_rtspRgaScaleLogged{false}; // 原始图像 PUB 开关 std::atomic m_bRawPubEnabled{false}; // 检测模式下的图像传输模式 mutable std::mutex m_detectModeMutex; std::string m_detectMode{"binocular"}; // "binocular" | "mono" // 检测线程 std::thread m_detectThread; std::atomic m_bThreadExit{false}; QMutex m_frameMutex; MvsImageData m_leftImageData; MvsImageData m_rightImageData; bool m_bLeftImageReady{false}; bool m_bRightImageReady{false}; // 异步图像保存 std::vector m_imageSaveThreads; std::atomic m_imageSaveThreadExit{false}; std::mutex m_imageSaveMutex; std::condition_variable m_imageSaveCv; std::deque m_imageSaveQueue; QString m_imageSaveSessionDir; unsigned long long m_imageSaveDropped{0}; }; #endif // DRONESCREW_SERVER_PRESENTER_H