#include "ANSRTSP.h" #include "ANSMatRegistry.h" #include "ANSGpuFrameOps.h" #include "GpuNV12SlotPool.h" #include #include #include "media_codec.h" #include #include #if defined(_WIN32) #include #pragma comment(lib, "dxgi.lib") #elif defined(__linux__) #include #include #include #endif extern "C" { #include #include #include } // Note: per-instance thread safety is handled by ANSRTSPClient::_mutex // Mat registry thread safety is handled by anscv_mat_replace's internal registry_mutex // Debug logging. Define ANSCORE_GPU_DEBUG=1 to enable verbose per-frame logging. #ifndef RTSP_DBG #if defined(ANSCORE_GPU_DEBUG) && ANSCORE_GPU_DEBUG #ifdef _WIN32 #define RTSP_DBG(fmt, ...) do { \ char _rtsp_buf[512]; \ snprintf(_rtsp_buf, sizeof(_rtsp_buf), fmt "\n", ##__VA_ARGS__); \ OutputDebugStringA(_rtsp_buf); \ fprintf(stderr, "%s", _rtsp_buf); \ } while(0) #else #define RTSP_DBG(fmt, ...) fprintf(stderr, fmt "\n", ##__VA_ARGS__) #endif #else #define RTSP_DBG(fmt, ...) ((void)0) #endif #endif static bool ansrtspLicenceValid = false; // Global once_flag to protect license checking static std::once_flag ansrtspLicenseOnceFlag; static std::once_flag hwDecoderAutoConfigOnceFlag; namespace ANSCENTER { ANSRTSPClient::ANSRTSPClient() { _useFullURL = false; _username = ""; _password = ""; _url = ""; _imageWidth=0; _imageHeight=0; _pts=0; _lastJpegImage = ""; _isPlaying=false; // Auto-configure HW decoder pool on first client creation. // This detects GPUs and sets per-GPU NVDEC session limits automatically. // No action needed from LabVIEW or any third-party caller. std::call_once(hwDecoderAutoConfigOnceFlag, []() { AutoConfigureHWDecoders(0); }); } ANSRTSPClient::~ANSRTSPClient() noexcept { Destroy(); } void ANSRTSPClient::Destroy() { // Move the player client pointer out of the lock scope, then // close it OUTSIDE the mutex. close() calls cuArrayDestroy / // cuMemFree which acquire an EXCLUSIVE SRW lock inside nvcuda64. // If we hold _mutex during close(), and another thread holds // the nvcuda64 SRW lock (e.g. cuStreamSynchronize during // inference), we get a deadlock: Stop() → _mutex → nvcuda64 // vs inference → nvcuda64 → (blocked by exclusive waiter). decltype(_playerClient) clientToClose; { std::unique_lock lock(_mutex); if (_playerClient) { if (_isPlaying) { _playerClient->stop(); _isPlaying = false; } } // --- Inference guard: wait for in-flight D2D copies to finish --- // With synchronous D2D copy, in-flight means "currently inside // GetRTSPCVImage between TryIncrementInFlight and attach_cuda". // This is typically <1ms, so the wait is very fast. int inFlight = _inFlightFrames.load(std::memory_order_acquire); if (inFlight > 0) { _logger.LogInfo("ANSRTSPClient::Destroy", std::format("waiting for {} in-flight frame(s)...", inFlight), __FILE__, __LINE__); bool done = _inFlightDone.wait_for(lock, std::chrono::seconds(5), [this] { return _inFlightFrames.load(std::memory_order_acquire) <= 0; }); if (!done) { _logger.LogWarn("ANSRTSPClient::Destroy", std::format("timed out — still {} in-flight", _inFlightFrames.load()), __FILE__, __LINE__); } } // Invalidate owner callbacks so stale GpuFrameData don't try to // call DecrementInFlight on this (soon-to-be-deleted) object. // The GpuFrameData and their global pool slots remain alive — // inference engines can safely keep reading from them. ANSGpuFrameRegistry::instance().invalidateOwner(this); _inFlightFrames.store(0, std::memory_order_release); // NO forceReleaseByOwner — frames survive camera deletion. // Pool slot buffers are global (GpuNV12SlotPool) — NOT owned // by this camera. They are recycled when inference finishes // (GpuFrameData refcount → 0 → slot.inUse = false). // NO cudaDeviceSynchronize — no GPU buffers to free here. // NO DestroyGpuPool — per-camera pool has been removed. clientToClose = std::move(_playerClient); } // close() destroys the NVDEC decoder ONLY. Pool slot buffers // (regular cudaMallocPitch allocations) are untouched — they // belong to the global GpuNV12SlotPool, not the decoder. if (clientToClose) { clientToClose->close(); } } static void VerifyGlobalANSRTSPLicense(const std::string& licenseKey) { try { ansrtspLicenceValid = ANSCENTER::ANSLicenseHelper::LicenseVerification(licenseKey, 1007, "ANSCV");//Default productId=1005 if (!ansrtspLicenceValid) { // we also support ANSTS license ansrtspLicenceValid = ANSCENTER::ANSLicenseHelper::LicenseVerification(licenseKey, 1003, "ANSVIS");//Default productId=1003 (ANSVIS) } if (!ansrtspLicenceValid) { // we also support ANSTS license ansrtspLicenceValid = ANSCENTER::ANSLicenseHelper::LicenseVerification(licenseKey, 1008, "ANSTS");//Default productId=1008 (ANSTS) } } catch (std::exception& e) { ansrtspLicenceValid = false; } } void ANSRTSPClient::CheckLicense() { std::lock_guard lock(_mutex); try { // Check once globally std::call_once(ansrtspLicenseOnceFlag, [this]() { VerifyGlobalANSRTSPLicense(_licenseKey); }); // Update this instance's local license flag _licenseValid = ansrtspLicenceValid; } catch (const std::exception& e) { this->_logger.LogFatal("ANSODBase::CheckLicense. Error:", e.what(), __FILE__, __LINE__); } } bool ANSRTSPClient::Init(std::string licenseKey, std::string url) { std::lock_guard lock(_mutex); CheckLicense(); if (!_licenseValid) { this->_logger.LogError("ANSRTSPClient::Init.", "Invalid license", __FILE__, __LINE__); return false; } /* network_init(); sys_buf_init(200); rtsp_parse_buf_init(200); http_msg_buf_init(200);*/ _url = url; _useFullURL = true; return Setup(); } bool ANSRTSPClient::Init(std::string licenseKey, std::string username, std::string password, std::string url) { std::lock_guard lock(_mutex); CheckLicense(); if (!_licenseValid) { this->_logger.LogError("ANSRTSPClient::Init.", "Invalid license", __FILE__, __LINE__); return false; } //network_init(); //sys_buf_init(200); //rtsp_parse_buf_init(200); //http_msg_buf_init(200); _url = url; _username = username; _password = password; _useFullURL = false; _isPlaying = false; return Setup(); } bool ANSRTSPClient::Setup() { std::lock_guard lock(_mutex); if(_useFullURL){ return _playerClient->open(_url); } else { return _playerClient->open(_username, _password, _url); } } void ANSRTSPClient::SetBBox(cv::Rect bbox) { std::lock_guard lock(_mutex); _playerClient->setBbox(bbox); } void ANSRTSPClient::SetCrop(bool crop) { std::lock_guard lock(_mutex); _playerClient->setCrop(crop); } bool ANSRTSPClient::Reconnect() { // 1. Mark as not-playing under the mutex FIRST. This makes GetImage() // return the cached _pLastFrame instead of calling into the player, // and blocks new TryIncrementInFlight calls. { std::unique_lock lock(_mutex); _isPlaying = false; // --- Inference guard: wait for in-flight D2D copies to finish --- // With synchronous D2D copy, in-flight means "currently inside // GetRTSPCVImage between TryIncrementInFlight and attach_cuda". // This is typically <1ms, so the wait is very fast. int inFlight = _inFlightFrames.load(std::memory_order_acquire); if (inFlight > 0) { _logger.LogInfo("ANSRTSPClient::Reconnect", std::format("waiting for {} in-flight frame(s)...", inFlight), __FILE__, __LINE__); bool done = _inFlightDone.wait_for(lock, std::chrono::seconds(5), [this] { return _inFlightFrames.load(std::memory_order_acquire) <= 0; }); if (!done) { _logger.LogWarn("ANSRTSPClient::Reconnect", std::format("timed out — still {} in-flight", _inFlightFrames.load()), __FILE__, __LINE__); } } // Invalidate owner callbacks — prevents stale DecrementInFlight // calls after Reconnect re-creates the decoder. // Frames and their global pool slots remain alive for inference. ANSGpuFrameRegistry::instance().invalidateOwner(this); _inFlightFrames.store(0, std::memory_order_release); // NO forceReleaseByOwner — frames survive reconnect. // NO cudaDeviceSynchronize — no GPU buffers to free. // NO DestroyGpuPool — per-camera pool has been removed. } // 2. close() destroys NVDEC decoder ONLY — run outside _mutex to // avoid deadlocking with nvcuda64 SRW lock held by inference. // Pool slot buffers are global and untouched. _logger.LogInfo("ANSRTSPClient::Reconnect", "calling close() — NVDEC decoder will be destroyed", __FILE__, __LINE__); RTSP_DBG("[Reconnect] BEFORE close() this=%p", (void*)this); _playerClient->close(); RTSP_DBG("[Reconnect] AFTER close() this=%p", (void*)this); // 3. Re-setup and play under the mutex. std::lock_guard lock(_mutex); _logger.LogInfo("ANSRTSPClient::Reconnect", "calling Setup() + play()", __FILE__, __LINE__); Setup(); _isPlaying = _playerClient->play(); RTSP_DBG("[Reconnect] DONE isPlaying=%d this=%p", (int)_isPlaying, (void*)this); return _isPlaying; } void ANSRTSPClient::EnableAudio(bool status) { std::lock_guard lock(_mutex); _playerClient->enableAudio(status); } void ANSRTSPClient::SetAudioVolume(int volume) { std::lock_guard lock(_mutex); _playerClient->setVolume(volume); } bool ANSRTSPClient::Start() { std::lock_guard lock(_mutex); Setup(); _isPlaying= _playerClient->play(); return _isPlaying; } bool ANSRTSPClient::Stop() { // Grab the player pointer and clear _isPlaying under the lock, // then call stop() OUTSIDE the mutex. stop() internally calls // StopVideoDecoder -> decoder->flush() which does CUDA calls // that can block on the nvcuda64 SRW lock. Holding _mutex // during that time blocks all other operations on this client // and contributes to the convoy when many clients stop at once. CRtspPlayer* player = nullptr; { std::lock_guard lock(_mutex); if (_isPlaying) { _isPlaying = false; player = _playerClient.get(); } } if (player) { player->stop(); } return true; } bool ANSRTSPClient::Pause() { std::lock_guard lock(_mutex); _isPlaying = false; return _playerClient->pause(); } bool ANSRTSPClient::IsPaused() { std::lock_guard lock(_mutex); return _playerClient->isPaused(); } bool ANSRTSPClient::IsPlaying() { std::lock_guard lock(_mutex); return _playerClient->isPlaying(); } bool ANSRTSPClient::IsRecording() { std::lock_guard lock(_mutex); return _playerClient->isRecording(); } std::string ANSRTSPClient::GetJpegImage(int& width, int& height, int64_t& pts) { std::lock_guard lock(_mutex); // If the player is playing, process the frame if (_isPlaying) { // Get a new frame from the player client _lastJpegImage = _playerClient->getJpegImage(width, height, pts); // Update internal state variables _pts = pts; _imageWidth = width; _imageHeight = height; // Return the frame return _lastJpegImage; } // If the player is not playing, return the last known frame else { width = _imageWidth; height = _imageHeight; pts = _pts; return _lastJpegImage; } } bool ANSRTSPClient::areImagesIdentical(const cv::Mat& img1, const cv::Mat& img2) { // Quick size and type checks if (img1.size() != img2.size() || img1.type() != img2.type()) { return false; } // Handle empty images if (img1.empty()) { return img2.empty(); } if (img1.isContinuous() && img2.isContinuous()) { const size_t totalBytes = img1.total() * img1.elemSize(); // Fast rejection: sample 5 positions across contiguous memory // Catches 99.99% of different frames immediately const size_t quarter = totalBytes / 4; const size_t half = totalBytes / 2; const size_t threeQuarter = 3 * totalBytes / 4; if (img1.data[0] != img2.data[0] || img1.data[quarter] != img2.data[quarter] || img1.data[half] != img2.data[half] || img1.data[threeQuarter] != img2.data[threeQuarter] || img1.data[totalBytes - 1] != img2.data[totalBytes - 1]) { return false; } // Full comparison return std::memcmp(img1.data, img2.data, totalBytes) == 0; } // Row-by-row comparison for non-continuous images (e.g., ROI sub-matrices) const size_t rowSize = img1.cols * img1.elemSize(); for (int i = 0; i < img1.rows; i++) { if (std::memcmp(img1.ptr(i), img2.ptr(i), rowSize) != 0) { return false; } } return true; } cv::Mat ANSRTSPClient::GetImage(int& width, int& height, int64_t& pts) { std::lock_guard lock(_mutex); // Return last known frame if not playing if (!_isPlaying) { width = _imageWidth; height = _imageHeight; pts = _pts; return _pLastFrame; // Shallow copy (fast) } int imageW = 0, imageH = 0; int64_t currentPts = 0; // Get image directly without intermediate assignment cv::Mat currentImage = _playerClient->getImage(imageW, imageH, currentPts); // Check if image is valid first (cheaper than comparison) if (currentImage.empty()) { width = _imageWidth; height = _imageHeight; pts = _pts; return _pLastFrame; } // Use PTS to detect duplicate frames (much faster than pixel comparison) // The sequence-based check in getImage() already skips conversion for same frames, // so if PTS hasn't changed, it's the same frame — no need for expensive memcmp if (currentPts == _pts && !_pLastFrame.empty()) { width = _imageWidth; height = _imageHeight; pts = _pts; return _pLastFrame; } // Update PTS first _pts = currentPts; pts = currentPts; // Handle non-rotated case if (_imageRotateDeg == 0) { // Apply display resize if configured if (_displayWidth > 0 && _displayHeight > 0 && (currentImage.cols != _displayWidth || currentImage.rows != _displayHeight)) { cv::Mat displayResult; cv::resize(currentImage, displayResult, cv::Size(_displayWidth, _displayHeight), 0, 0, cv::INTER_LINEAR); currentImage = displayResult; imageW = _displayWidth; imageH = _displayHeight; } _imageWidth = imageW; _imageHeight = imageH; width = imageW; height = imageH; _pLastFrame = currentImage; // Shallow copy (reference counted) return currentImage; } // Handle rotation case // Calculate proper rotated dimensions for 90/270 degree rotations int rotatedWidth, rotatedHeight; double absAngle = std::abs(_imageRotateDeg); if (absAngle == 90.0 || absAngle == 270.0) { // Swap dimensions for 90/270 degree rotations rotatedWidth = imageH; rotatedHeight = imageW; } else { rotatedWidth = imageW; rotatedHeight = imageH; } // Calculate rotation matrix cv::Point2f center(imageW / 2.0f, imageH / 2.0f); cv::Mat rotationMatrix = cv::getRotationMatrix2D(center, _imageRotateDeg, 1.0); // Apply rotation with optimized interpolation cv::Mat rotatedImage; cv::warpAffine(currentImage, rotatedImage, rotationMatrix, cv::Size(rotatedWidth, rotatedHeight), cv::INTER_LINEAR, // Faster than INTER_CUBIC, still good quality cv::BORDER_CONSTANT, cv::Scalar(0, 0, 0)); // Apply display resize if configured if (_displayWidth > 0 && _displayHeight > 0 && (rotatedImage.cols != _displayWidth || rotatedImage.rows != _displayHeight)) { cv::Mat displayResult; cv::resize(rotatedImage, displayResult, cv::Size(_displayWidth, _displayHeight), 0, 0, cv::INTER_LINEAR); rotatedImage = displayResult; rotatedWidth = _displayWidth; rotatedHeight = _displayHeight; } // Update dimensions - use calculated dimensions, not cols/rows _imageWidth = rotatedWidth; _imageHeight = rotatedHeight; width = rotatedWidth; height = rotatedHeight; // Store and return rotated image _pLastFrame = rotatedImage; return rotatedImage; } //cv::Mat ANSRTSPClient::GetImage(int& width, int& height, int64_t& pts) { // std::lock_guard lock(_mutex); // // Return last known frame if not playing // if (!_isPlaying) { // width = _imageWidth; // height = _imageHeight; // pts = _pts; // return _pLastFrame; // } // int imageW = 0, imageH = 0; // int64_t currentPts = 0; // cv::Mat currentImage; // currentImage = _playerClient->getImage(imageW, imageH, currentPts); // // If we still don't have a valid image, return last frame // if (currentImage.empty() || areImagesIdentical(currentImage, _pLastFrame)) { // width = _imageWidth; // height = _imageHeight; // pts = _pts; // return _pLastFrame; // } // // Update internal state // width = imageW; // height = imageH; // pts = currentPts; // _pts = currentPts; // _imageWidth = imageW; // _imageHeight = imageH; // if (_imageRotateDeg == 0) { // _pLastFrame = currentImage; // return currentImage; // } // else { // // Rotate image if required // cv::Point2f center(width / 2.0f, height / 2.0f); // cv::Mat rotationMatrix = cv::getRotationMatrix2D(center, _imageRotateDeg, 1.0); // cv::Mat rotatedImage; // cv::warpAffine(currentImage, rotatedImage, rotationMatrix, // cv::Size(width, height), cv::INTER_CUBIC, // cv::BORDER_CONSTANT, cv::Scalar()); // // Update dimensions and store result // width = rotatedImage.cols; // height = rotatedImage.rows; // _imageWidth = width; // _imageHeight = height; // _pLastFrame = rotatedImage; // return rotatedImage; // } //} void ANSRTSPClient::SetMaxHWDecoders(int maxDecoders) { if (maxDecoders >= 0) { g_hw_decoder_max = static_cast(maxDecoders); } } // Estimate max NVDEC decode sessions from SM count and VRAM. // NVDEC has NO driver-enforced session limit (unlike NVENC which caps at 3-5). // Per NVIDIA Video Codec SDK 13.0 docs: "NVDEC natively supports multiple // hardware decoding contexts with negligible context-switching penalty." // // Two factors determine the soft cap: // 1. SM count — proxy for decode throughput (more SMs = more parallel capacity) // 2. VRAM — each 4K HEVC session uses ~80MB (decode surfaces + buffers) // We reserve 1GB for OS/display/inference workloads. // The lower of the two estimates is used. // // GPU Example | SMs | VRAM | SM-cap | VRAM-cap | NVDEC | Final // -------------------------|-----|-------|--------|----------|-------|------ // RTX 5090 (Blackwell) | 170 | 32GB | 64 | 393 | 32 | 32 // RTX 5080 (Blackwell) | 84 | 16GB | 48 | 192 | 32 | 32 // RTX 4090 (Ada) | 128 | 24GB | 64 | 294 | 32 | 32 // RTX 4070 Laptop (Ada) | 36 | 8GB | 32 | 89 | 32 | 32 // RTX 3060 (Ampere) | 28 | 12GB | 16 | 140 | 32 | 16 // GTX 1650 (Turing) | 14 | 4GB | 8 | 38 | 32 | 8 // Estimate max decode sessions from SM count and VRAM. // Uses the minimum of THREE estimates to avoid overloading any subsystem. // // SM-based estimate: proxy for decode throughput // VRAM-based estimate: each 4K HEVC session uses ~80MB (decode surfaces + context) // Reserve 1GB for OS/display/AI inference, rest available for decode sessions. // NVDEC hardware cap: NVDEC is a fixed-function ASIC independent of SM count. // NVIDIA consumer GPUs (GeForce) have a hardware surface pool limit of // ~32 concurrent decode sessions regardless of GPU tier. Exceeding this // causes CUVID Error 205 ("Error mapping a picture"), which triggers a // sticky CUDA_ERROR_ILLEGAL_ADDRESS that permanently corrupts the CUDA // context — crashing ALL GPU operations (inference, display, etc.). static constexpr int NVDEC_HW_SESSION_CAP = 32; static int estimateMaxSessions(int smCount, size_t totalVramBytes) { // SM-based throughput estimate int smBased; if (smCount >= 80) smBased = 64; else if (smCount >= 50) smBased = 48; else if (smCount >= 30) smBased = 32; else if (smCount >= 20) smBased = 16; else smBased = 8; // VRAM-based memory estimate size_t totalVramMB = totalVramBytes / (1024 * 1024); size_t reservedMB = 1024; // Reserve 1GB for OS/display/inference size_t availableMB = (totalVramMB > reservedMB) ? (totalVramMB - reservedMB) : 256; int vramBased = static_cast(availableMB / 80); // ~80MB per 4K HEVC session if (vramBased < 4) vramBased = 4; // Minimum 4 sessions // NVDEC hardware surface pool cap — prevents CUVID Error 205 int result = (smBased < vramBased) ? smBased : vramBased; if (result > NVDEC_HW_SESSION_CAP) result = NVDEC_HW_SESSION_CAP; return result; } // --------------------------------------------------------------------------- // Platform-specific GPU detection for non-NVIDIA systems (Intel/AMD) // Estimates max VAAPI/D3D11VA decode sessions based on vendor and VRAM. // // Vendor IDs: 0x10DE = NVIDIA, 0x8086 = Intel, 0x1002 = AMD // // Intel Quick Sync (QSV) — via D3D11VA (Windows) or VAAPI (Linux): // - Modern Intel (12th gen+, Iris Xe/Arc): ~16 concurrent sessions // - Older Intel (8th-11th gen, UHD 630): ~8 concurrent sessions // // AMD VCN (Video Core Next) — via D3D11VA (Windows) or VAAPI (Linux): // - RDNA 2/3 (RX 6000/7000, Ryzen 7000 iGPU): ~16 concurrent sessions // - Older Vega/Polaris: ~8 concurrent sessions // // Neither Intel nor AMD publish hard session limits for decode. // --------------------------------------------------------------------------- // Helper: estimate max sessions from vendor ID and available VRAM static int estimateSessionsByVendor(uint32_t vendorId, size_t dedicatedMB, size_t sharedMB) { if (vendorId == 0x10DE) { // NVIDIA — CUDA path failed, estimate from VRAM size_t availMB = (dedicatedMB > 1024) ? (dedicatedMB - 1024) : 512; int sessions = static_cast(availMB / 80); if (sessions < 8) sessions = 8; if (sessions > 64) sessions = 64; return sessions; } else if (vendorId == 0x8086) { // Intel — Quick Sync via D3D11VA/VAAPI // iGPUs have little/no dedicated VRAM; use shared memory as proxy size_t effectiveMB = (dedicatedMB > 0) ? dedicatedMB : (sharedMB / 4); if (effectiveMB >= 2048) return 16; // Arc A770/A750 (discrete) else if (effectiveMB >= 512) return 12; // Iris Xe, 12th gen+ else return 8; // UHD 630, older } else if (vendorId == 0x1002) { // AMD — VCN via D3D11VA/VAAPI if (dedicatedMB >= 4096) return 32; // RX 6000/7000 discrete else if (dedicatedMB >= 1024) return 16; // RX 6500, older discrete else return 8; // Ryzen iGPU (Vega/RDNA) } return 4; // Unknown vendor } static const char* vendorIdToName(uint32_t vendorId) { if (vendorId == 0x10DE) return "NVIDIA"; if (vendorId == 0x8086) return "Intel"; if (vendorId == 0x1002) return "AMD"; return "Unknown"; } #if defined(_WIN32) // ---- Windows: enumerate GPUs via DXGI ---- static int AutoConfigureHWDecoders_Platform() { IDXGIFactory1* pFactory = nullptr; HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&pFactory); if (FAILED(hr) || !pFactory) { g_hw_decoder_max = 4; fprintf(stderr, "[HWDecode] AutoConfigure: DXGI unavailable, defaulting to %d sessions\n", g_hw_decoder_max); return g_hw_decoder_max; } IDXGIAdapter1* pAdapter = nullptr; int totalSessions = 0; bool foundHwGpu = false; for (UINT i = 0; pFactory->EnumAdapters1(i, &pAdapter) != DXGI_ERROR_NOT_FOUND; i++) { DXGI_ADAPTER_DESC1 desc; pAdapter->GetDesc1(&desc); pAdapter->Release(); // Skip software/remote adapters if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) continue; char gpuName[128] = {}; wcstombs(gpuName, desc.Description, sizeof(gpuName) - 1); uint32_t vendorId = desc.VendorId; size_t dedicatedMB = desc.DedicatedVideoMemory / (1024 * 1024); size_t sharedMB = desc.SharedSystemMemory / (1024 * 1024); int maxSessions = estimateSessionsByVendor(vendorId, dedicatedMB, sharedMB); fprintf(stderr, "[HWDecode] AutoConfigure: GPU[%d] \"%s\" Vendor=%s(0x%04X) VRAM=%zuMB Shared=%zuMB -> max %d decode sessions\n", i, gpuName, vendorIdToName(vendorId), vendorId, dedicatedMB, sharedMB, maxSessions); totalSessions += maxSessions; foundHwGpu = true; } pFactory->Release(); if (!foundHwGpu) { g_hw_decoder_max = 4; fprintf(stderr, "[HWDecode] AutoConfigure: No hardware GPU found, defaulting to %d sessions (software fallback)\n", g_hw_decoder_max); return g_hw_decoder_max; } g_hw_decoder_max = static_cast(totalSessions); fprintf(stderr, "[HWDecode] AutoConfigure: Total %d decode sessions across all GPUs\n", totalSessions); return totalSessions; } #elif defined(__linux__) // ---- Linux: enumerate GPUs via /sys/class/drm and /proc/driver ---- // Reads vendor ID from sysfs and estimates VRAM from DRM memory info. static int AutoConfigureHWDecoders_Platform() { int totalSessions = 0; bool foundHwGpu = false; int gpuIndex = 0; // Scan /sys/class/drm/card0, card1, ... for GPU info for (int cardNum = 0; cardNum < 16; cardNum++) { // Read vendor ID from PCI device info char vendorPath[256]; snprintf(vendorPath, sizeof(vendorPath), "/sys/class/drm/card%d/device/vendor", cardNum); std::ifstream vendorFile(vendorPath); if (!vendorFile.is_open()) continue; uint32_t vendorId = 0; vendorFile >> std::hex >> vendorId; vendorFile.close(); // Skip non-GPU devices (vendor 0 = invalid) if (vendorId == 0) continue; // Try to get GPU name from /sys/class/drm/cardN/device/label or // fall back to /sys/class/drm/cardN/device/uevent char gpuName[128] = "Unknown GPU"; char ueventPath[256]; snprintf(ueventPath, sizeof(ueventPath), "/sys/class/drm/card%d/device/uevent", cardNum); std::ifstream ueventFile(ueventPath); if (ueventFile.is_open()) { std::string line; while (std::getline(ueventFile, line)) { if (line.find("PCI_SLOT_NAME=") == 0) { snprintf(gpuName, sizeof(gpuName), "PCI %s", line.substr(14).c_str()); break; } } ueventFile.close(); } // Try to read VRAM size from DRM memory info // Intel iGPU: /sys/class/drm/card0/device/resource reports BAR sizes // Discrete GPU: reported via resource file (BAR 0 size) size_t dedicatedMB = 0; size_t sharedMB = 0; // Read BAR 0 from PCI resource file to estimate VRAM char resourcePath[256]; snprintf(resourcePath, sizeof(resourcePath), "/sys/class/drm/card%d/device/resource", cardNum); std::ifstream resourceFile(resourcePath); if (resourceFile.is_open()) { std::string line; if (std::getline(resourceFile, line)) { // Format: "start end flags" — BAR 0 (usually VRAM) unsigned long long start = 0, end = 0; if (sscanf(line.c_str(), "%llx %llx", &start, &end) == 2 && end > start) { dedicatedMB = (end - start + 1) / (1024 * 1024); } } resourceFile.close(); } // For Intel iGPU, BAR size is small (~256MB) but system memory is shared // Use total system memory / 4 as shared memory estimate if (vendorId == 0x8086 && dedicatedMB < 512) { std::ifstream meminfo("/proc/meminfo"); if (meminfo.is_open()) { std::string line; while (std::getline(meminfo, line)) { if (line.find("MemTotal:") == 0) { unsigned long totalKB = 0; sscanf(line.c_str(), "MemTotal: %lu", &totalKB); sharedMB = totalKB / 1024; // Total system RAM in MB break; } } meminfo.close(); } } int maxSessions = estimateSessionsByVendor(vendorId, dedicatedMB, sharedMB); fprintf(stderr, "[HWDecode] AutoConfigure: GPU[%d] \"%s\" Vendor=%s(0x%04X) VRAM=%zuMB Shared=%zuMB -> max %d VAAPI decode sessions\n", gpuIndex, gpuName, vendorIdToName(vendorId), vendorId, dedicatedMB, sharedMB, maxSessions); totalSessions += maxSessions; foundHwGpu = true; gpuIndex++; } if (!foundHwGpu) { g_hw_decoder_max = 4; fprintf(stderr, "[HWDecode] AutoConfigure: No hardware GPU found in /sys/class/drm, defaulting to %d sessions (software fallback)\n", g_hw_decoder_max); return g_hw_decoder_max; } g_hw_decoder_max = static_cast(totalSessions); fprintf(stderr, "[HWDecode] AutoConfigure: Total %d VAAPI decode sessions across all GPUs\n", totalSessions); return totalSessions; } #else // ---- Unsupported platform: conservative default ---- static int AutoConfigureHWDecoders_Platform() { g_hw_decoder_max = 4; fprintf(stderr, "[HWDecode] AutoConfigure: Unsupported platform, defaulting to %d sessions\n", g_hw_decoder_max); return g_hw_decoder_max; } #endif int ANSRTSPClient::AutoConfigureHWDecoders(int maxPerGpuOverride) { int gpuCount = 0; cudaError_t err = cudaGetDeviceCount(&gpuCount); if (err != cudaSuccess || gpuCount <= 0) { // No NVIDIA GPU — detect Intel/AMD via platform API (DXGI on Windows, sysfs on Linux) return AutoConfigureHWDecoders_Platform(); } // Detect each GPU's SM count and set per-GPU session limits std::vector maxPerGpuList(gpuCount); int total = 0; for (int i = 0; i < gpuCount; i++) { cudaDeviceProp prop; cudaGetDeviceProperties(&prop, i); int smCount = prop.multiProcessorCount; if (maxPerGpuOverride > 0) { // Manual override: same limit for all GPUs maxPerGpuList[i] = maxPerGpuOverride; } else { // Auto-detect based on SM count + VRAM maxPerGpuList[i] = estimateMaxSessions(smCount, prop.totalGlobalMem); } total += maxPerGpuList[i]; size_t vramMB = prop.totalGlobalMem / (1024 * 1024); fprintf(stderr, "[NVDEC] AutoConfigure: GPU[%d] \"%s\" SM=%d VRAM=%zuMB -> max %d decode sessions\n", i, prop.name, smCount, vramMB, maxPerGpuList[i]); } // Configure the per-GPU pool with individual limits HWDecoderPool::instance().configure(maxPerGpuList); return total; } void ANSRTSPClient::SetHWDecoding(int hwMode, int preferredGpu) { std::lock_guard lock(_mutex); _playerClient->setHWDecoding(hwMode, preferredGpu); } bool ANSRTSPClient::IsHWDecodingActive() { std::lock_guard lock(_mutex); return _playerClient->isHWDecodingActive(); } int ANSRTSPClient::GetHWDecodingGpuIndex() { std::lock_guard lock(_mutex); return _playerClient->getHWDecodingGpuIndex(); } void ANSRTSPClient::SetDisplayResolution(int width, int height) { std::lock_guard lock(_mutex); _displayWidth = width; _displayHeight = height; } void ANSRTSPClient::SetImageQuality(int mode) { std::lock_guard lock(_mutex); _playerClient->setImageQuality(mode); // 0=fast (AI), 1=quality (display) } AVFrame* ANSRTSPClient::GetNV12Frame() { std::lock_guard lock(_mutex); if (!_isPlaying) return nullptr; // Player may be mid-reconnect (CUDA resources freed) return _playerClient->getNV12Frame(); // Returns clone, caller must av_frame_free } AVFrame* ANSRTSPClient::GetCudaHWFrame() { std::lock_guard lock(_mutex); if (!_isPlaying) return nullptr; // Player may be mid-reconnect (CUDA resources freed) return _playerClient->getCudaHWFrame(); } bool ANSRTSPClient::IsCudaHWAccel() { std::lock_guard lock(_mutex); return _playerClient->isCudaHWAccel(); } std::string ANSRTSPClient::MatToBinaryData(const cv::Mat& image) { std::lock_guard lock(_mutex); if (!image.empty()) { if ((image.data != nullptr) && (image.u != nullptr)) { try { // Encode the image to a memory buffer std::vector imageData; bool success = cv::imencode(".jpg", image, imageData); if (!success) { this->_logger.LogError("ANSRTSPClient::MatToBinaryData. Error:", "Failed to encode the image.", __FILE__, __LINE__); return ""; } std::string binaryData(imageData.begin(), imageData.end()); return binaryData; } catch (const std::exception& e) { this->_logger.LogFatal("ANSRTSPClient::MatToBinaryData. Error:", e.what(), __FILE__, __LINE__); return ""; } catch (...) { this->_logger.LogFatal("ANSRTSPClient::MatToBinaryData. Error:", "Caught unknown exception!", __FILE__, __LINE__); return ""; } } else return ""; } else return ""; } } extern "C" __declspec(dllexport) int CreateANSRTSPHandle(ANSCENTER::ANSRTSPClient * *Handle, const char* licenseKey, const char* username, const char* password, const char* url) { if (!Handle || !licenseKey || !url) return -1; try { auto ptr = std::make_unique(); std::string _username = username ? username : ""; std::string _password = password ? password : ""; bool result = false; if (_username.empty() && _password.empty()) result = ptr->Init(licenseKey, url); else result = ptr->Init(licenseKey, username, password, url); if (result) { // Default to CUDA/NVDEC HW decoding (mode 7) for NV12 zero-copy // fast path. LabVIEW may not call SetRTSPHWDecoding after // destroy+recreate cycles, so this ensures the new handle always // uses the GPU decode path instead of falling back to D3D11VA/CPU. ptr->SetHWDecoding(7); // HW_DECODING_CUDA *Handle = ptr.release(); extern void anscv_unregister_handle(void*); extern void anscv_register_handle(void*, void(*)(void*)); anscv_register_handle(*Handle, [](void* p) { auto* h = static_cast(p); try { h->Stop(); } catch (...) {} try { h->Destroy(); } catch (...) {} try { delete h; } catch (...) {} }); return 1; } *Handle = nullptr; return 0; } catch (...) { return -1; } } extern "C" __declspec(dllexport) int ReleaseANSRTSPHandle(ANSCENTER::ANSRTSPClient * *Handle) { if (Handle == nullptr || *Handle == nullptr) return -1; try { extern void anscv_unregister_handle(void*); anscv_unregister_handle(*Handle); // Grab the raw pointer and NULL the caller's handle immediately. // This prevents the caller (LabVIEW) from issuing new calls. ANSCENTER::ANSRTSPClient* raw = *Handle; *Handle = nullptr; // Mark as not-playing under _mutex ONLY. This makes // GetImage()/GetNV12Frame()/GetCudaHWFrame() return empty/null // on any subsequent call, and prevents NEW NV12 GPU surface // pointers from being handed out. // // Do NOT call Destroy()/close() here — close() frees the // NVDEC GPU surfaces (cuArrayDestroy/cuMemFree) which may // still be in use by a CUDA inference kernel that received // the NV12 pointer from a GetRTSPCVImage call that already // completed before this Release was called. { // Use the client's _mutex to safely set _isPlaying = false. // This is the same lock GetImage/GetNV12Frame acquire. raw->Stop(); // sets _isPlaying = false, stops playback } // Defer the full cleanup (Destroy + delete) to a background thread // so LabVIEW's UI thread is not blocked. Destroy() now waits // precisely for in-flight inference to finish (via _inFlightFrames // counter + condition variable) instead of the old 500ms sleep hack. std::thread([raw]() { try { raw->Destroy(); } catch (...) {} try { delete raw; } catch (...) {} }).detach(); return 0; } catch (...) { if (Handle) *Handle = nullptr; return -1; } } extern "C" __declspec(dllexport) int GetRTSPStrImage(ANSCENTER::ANSRTSPClient * *Handle, int& width, int& height, int64_t & timeStamp, std::string & jpegImage) { if (Handle == nullptr || *Handle == nullptr) return -1; try { jpegImage = (*Handle)->GetJpegImage(width, height, timeStamp); if (!jpegImage.empty()) return 1; else return 0; } catch (const std::exception& e) { std::cerr << "Error getting RTSP image: " << e.what() << std::endl; return 0; } catch (...) { std::cerr << "Error getting RTSP image: Unknown exception." << std::endl; return 0; } } extern "C" __declspec(dllexport) int GetRTSPCVImage( ANSCENTER::ANSRTSPClient** Handle, int& width, int& height, int64_t& timeStamp, cv::Mat** image) { // Validate input parameters if (!Handle || !*Handle || !image) { std::cerr << "Error: Invalid input parameters in GetRTSPCVImage" << std::endl; return -1; } try { auto t0 = std::chrono::steady_clock::now(); // Get image (shallow copy - reference counted, fast) cv::Mat img = (*Handle)->GetImage(width, height, timeStamp); // Check if valid image was retrieved if (img.empty()) { return 0; // No valid image available } // Thread-safe Mat pointer swap (anscv_mat_replace has its own internal lock) anscv_mat_replace(image, std::move(img)); auto t1 = std::chrono::steady_clock::now(); // Attach NV12 frame for GPU fast-path inference (side-table registry) // attach() takes ownership — do NOT av_frame_free here // // CRITICAL: TryIncrementInFlight() MUST be called BEFORE GetCudaHWFrame(). // It atomically checks _isPlaying and increments _inFlightFrames under // the same mutex, so Reconnect() cannot call close() while we're doing // the D2D copy from NVDEC surfaces inside gpu_frame_attach_cuda(). int gpuIdx = (*Handle)->GetHWDecodingGpuIndex(); bool inFlightGuardHeld = (*Handle)->TryIncrementInFlight(); RTSP_DBG("[GetRTSPCVImage] mat=%p gpuIdx=%d inFlightGuard=%d", (void*)*image, gpuIdx, (int)inFlightGuardHeld); if (inFlightGuardHeld) { AVFrame* cudaHW = (*Handle)->GetCudaHWFrame(); if (cudaHW) { RTSP_DBG("[GetRTSPCVImage] cudaHW: %dx%d data[0]=%p data[1]=%p", cudaHW->width, cudaHW->height, (void*)cudaHW->data[0], (void*)cudaHW->data[1]); AVFrame* cpuNV12 = (*Handle)->GetNV12Frame(); // Acquire a slot from the global pool — survives camera Destroy. GpuNV12Slot* slot = GpuNV12SlotPool::instance().acquire( gpuIdx, cudaHW->width, cudaHW->height); gpu_frame_attach_cuda(*image, cudaHW, gpuIdx, timeStamp, cpuNV12, slot); } else { // HW decode not active — try CPU NV12 AVFrame* nv12 = (*Handle)->GetNV12Frame(); if (nv12) { gpu_frame_attach(*image, nv12, gpuIdx, timeStamp); } } // Wire up the registry callback to release the in-flight guard. // TryIncrementInFlight already incremented; DecrementInFlight fires // when the last clone of this frame is released after inference. auto* gpuData = ANSGpuFrameRegistry::instance().lookup(*image); RTSP_DBG("[GetRTSPCVImage] after attach: gpuData=%p yPlane=%p isCuda=%d poolSlot=%p", (void*)gpuData, gpuData ? (void*)gpuData->yPlane : nullptr, gpuData ? (int)gpuData->isCudaDevicePtr : -1, gpuData ? (void*)gpuData->poolSlot : nullptr); if (gpuData) { gpuData->ownerClient = *Handle; gpuData->onReleaseFn = [](void* client) { static_cast(client)->DecrementInFlight(); }; // NOTE: Do NOT call IncrementInFlight() again here — // TryIncrementInFlight() already did it above. } else { // No gpuData registered (attach failed?) — release the guard (*Handle)->DecrementInFlight(); } } else { // Player is stopping/reconnecting — skip CUDA path entirely. // GetImage() already returned a cached BGR frame, which is safe. RTSP_DBG("[GetRTSPCVImage] SKIP CUDA — player not playing (reconnecting?)"); } // Lightweight timing via spdlog (no OutputDebugString). // Logs only when the frame grab + D2D exceeds 50ms — helps diagnose stalls // without the overhead of per-frame debug logging. auto t2 = std::chrono::steady_clock::now(); double getImageMs = std::chrono::duration(t1 - t0).count(); double cudaMs = std::chrono::duration(t2 - t1).count(); double totalMs = getImageMs + cudaMs; if (totalMs > 50.0) { (*Handle)->_logger.LogWarn("GetRTSPCVImage", std::format("SLOW FRAME: total={:.1f}ms (getImage={:.1f}ms cuda={:.1f}ms) {}x{}", totalMs, getImageMs, cudaMs, width, height), __FILE__, __LINE__); } return 1; // Success } catch (const cv::Exception& e) { std::cerr << "OpenCV exception in GetRTSPCVImage: " << e.what() << std::endl; return -2; } catch (const std::exception& e) { std::cerr << "Exception in GetRTSPCVImage: " << e.what() << std::endl; return -2; } catch (...) { std::cerr << "Unknown exception in GetRTSPCVImage" << std::endl; return -2; } } //extern "C" __declspec(dllexport) int GetRTSPCVImage( // ANSCENTER::ANSRTSPClient** Handle, // int& width, // int& height, // int64_t& timeStamp, // cv::Mat** image) //{ // // Validate input parameters // if (!Handle || !(*Handle) || !image) { // std::cerr << "Error: Invalid input parameters in GetRTSPCVImage." << std::endl; // return -1; // Error code for invalid parameters // } // // try { // // Ensure thread safety before calling GetImage // std::lock_guard lock(rtspMutex); // // // Get image (shallow copy - fast, reference counted) // cv::Mat img = (*Handle)->GetImage(width, height, timeStamp); // // // Check if valid image was retrieved // if (img.empty()) { // return 0; // No valid image available // } // // // Exception-safe allocation: create new image BEFORE deleting old one // // This ensures if clone() throws, we don't lose the old image pointer // cv::Mat* newImage = new cv::Mat(img.clone()); // Single deep copy at DLL boundary // // // Now safe to delete old image // if (*image) { // delete* image; // } // // // Assign new image // *image = newImage; // // return 1; // Success // } // catch (const cv::Exception& e) { // std::cerr << "OpenCV exception in GetRTSPCVImage: " << e.what() << std::endl; // return -2; // Error code for OpenCV exceptions // } // catch (const std::exception& e) { // std::cerr << "Exception in GetRTSPCVImage: " << e.what() << std::endl; // return -2; // Error code for standard exceptions // } // catch (...) { // std::cerr << "Unknown exception in GetRTSPCVImage." << std::endl; // return -2; // Error code for unknown exceptions // } //} //extern "C" __declspec(dllexport) int GetRTSPCVImage(ANSCENTER::ANSRTSPClient** Handle, int& width, int& height, int64_t& timeStamp, cv::Mat** image) { // if (!Handle || !(*Handle) || !image) { // std::cerr << "Error: Invalid input parameters in GetRTSPCVImage." << std::endl; // return -1; // Error code for invalid parameters // } // // try { // std::lock_guard lock(rtspMutex); // Ensure thread safety before calling GetImage // // cv::Mat img = (*Handle)->GetImage(width, height, timeStamp); // // if (img.empty()) { // return 0; // No valid image retrieved // } // // // Properly release previous memory // if (*image) { // delete* image; // *image = nullptr; // } // // // Allocate new cv::Mat and ensure memory is properly managed // *image = new cv::Mat(img.clone()); // Avoid std::move to prevent releasing shared data // // return 1; // Success // } // catch (const std::exception& e) { // std::cerr << "Exception in GetRTSPCVImage: " << e.what() << std::endl; // return -2; // Error code for exceptions // } // catch (...) { // std::cerr << "Exception in GetRTSPCVImage: Unknown exception." << std::endl; // return -2; // Generic error code for exceptions // } //} extern "C" __declspec(dllexport) int GetRTSPImage(ANSCENTER::ANSRTSPClient** Handle, int& width, int& height, int64_t& timeStamp, LStrHandle jpegImage) { if (Handle == nullptr || *Handle == nullptr) return -1; try { std::string jpegString = (*Handle)->GetJpegImage(width, height, timeStamp); int size = jpegString.length(); if (size > 0) { MgErr error; // Resize the jpegImage handle to hold the image data error = DSSetHandleSize(jpegImage, sizeof(int32) + size * sizeof(uChar)); // Check if resizing the handle was successful if (error == noErr) { // Set the size of the image in the handle (*jpegImage)->cnt = size; // Use memcpy to copy the data from the std::string to the LStrHandle's str buffer memcpy((*jpegImage)->str, jpegString.c_str(), size); // Return success return 1; } else { // Return failure if there was an error in resizing the handle std::cerr << "Error resizing jpegImage handle: " << error << std::endl; return 0; } } else { // If the JPEG image string is empty, return failure std::cerr << "No image data retrieved from FLV client." << std::endl; return 0; } } catch (const std::exception& e) { std::cerr << "Error getting RTSP image: " << e.what() << std::endl; return 0; } catch (...) { std::cerr << "Error getting RTSP image: Unknown exception." << std::endl; return 0; } } extern "C" __declspec(dllexport) int StartRTSP(ANSCENTER::ANSRTSPClient **Handle) { if (Handle == nullptr || *Handle == nullptr) return -1; try { bool result = (*Handle)->Start(); if (result) return 1; else return 0; } catch (const std::exception& e) { std::cerr << "Error starting RTSP client: " << e.what() << std::endl; return 0; } catch (...) { std::cerr << "Error starting RTSP client: Unknown exception." << std::endl; return 0; } } extern "C" __declspec(dllexport) int ReconnectRTSP(ANSCENTER::ANSRTSPClient * *Handle) { if (Handle == nullptr || *Handle == nullptr) return -1; try { bool result = (*Handle)->Reconnect(); if (result) return 1; else return 0; } catch (const std::exception& e) { std::cerr << "Error reconnecting RTSP client: " << e.what() << std::endl; return 0; } catch (...) { std::cerr << "Error reconnecting RTSP client: Unknown exception." << std::endl; return 0; } } extern "C" __declspec(dllexport) int StopRTSP(ANSCENTER::ANSRTSPClient * *Handle) { if (Handle == nullptr || *Handle == nullptr) return -1; try { bool result = (*Handle)->Stop(); if (result) return 1; else return 0; } catch (const std::exception& e) { std::cerr << "Error stopping RTSP client: " << e.what() << std::endl; return 0; } catch (...) { std::cerr << "Error stopping RTSP client: Unknown exception." << std::endl; return 0; } } extern "C" __declspec(dllexport) int PauseRTSP(ANSCENTER::ANSRTSPClient** Handle) { if (Handle == nullptr || *Handle == nullptr) return -1; try { bool result = (*Handle)->Pause(); if (result) return 1; else return 0; } catch (const std::exception& e) { std::cerr << "Error pausing RTSP client: " << e.what() << std::endl; return 0; } catch (...) { std::cerr << "Error pausing RTSP client: Unknown exception." << std::endl; return 0; } } extern "C" __declspec(dllexport) int IsRTSPPaused(ANSCENTER::ANSRTSPClient** Handle) { if (Handle == nullptr || *Handle == nullptr) return -1; try { bool result = (*Handle)->IsPaused(); if (result) return 1; else return 0; } catch (const std::exception& e) { std::cerr << "Error checking if RTSP client is paused: " << e.what() << std::endl; return 0; } catch (...) { std::cerr << "Error checking if RTSP client is paused: Unknown exception." << std::endl; return 0; } } extern "C" __declspec(dllexport) int IsRTSPRunning(ANSCENTER::ANSRTSPClient **Handle) { if (Handle == nullptr || *Handle == nullptr) return -1; try { bool result = (*Handle)->IsPlaying(); if (result) return 1; else return 0; } catch (const std::exception& e) { std::cerr << "Error checking if RTSP client is running: " << e.what() << std::endl; return 0; } catch (...) { std::cerr << "Error checking if RTSP client is running: Unknown exception." << std::endl; return 0; } } extern "C" __declspec(dllexport) int IsRTSPRecording(ANSCENTER::ANSRTSPClient **Handle) { if (Handle == nullptr || *Handle == nullptr) return -1; try { bool result = (*Handle)->IsRecording(); if (result) return 1; else return 0; } catch (const std::exception& e) { std::cerr << "Error checking if RTSP client is recording: " << e.what() << std::endl; return 0; } catch (...) { std::cerr << "Error checking if RTSP client is recording: Unknown exception." << std::endl; return 0; } } extern "C" __declspec(dllexport) void SetRTSPAudioVolume(ANSCENTER::ANSRTSPClient * *Handle, int volume) { if (Handle == nullptr || *Handle == nullptr) return; try { (*Handle)->SetAudioVolume(volume); } catch (...) { } } extern "C" __declspec(dllexport) void EnableRTSPAudioVolume(ANSCENTER::ANSRTSPClient * *Handle, int status) { if (Handle == nullptr || *Handle == nullptr) return; try { bool audioStatus = false; if (status == 1)audioStatus = true; (*Handle)->EnableAudio(audioStatus); } catch (...) { } } extern "C" __declspec(dllexport) void SetRTSPImageRotation(ANSCENTER::ANSRTSPClient * *Handle, double rotationAngle) { if (Handle == nullptr || *Handle == nullptr) return; try { (*Handle)->SetImageRotate(rotationAngle); } catch (...) { } } extern "C" __declspec(dllexport) int SetBBoxRTSP(ANSCENTER::ANSRTSPClient** Handle, int x, int y, int width, int height) { if (Handle == nullptr || *Handle == nullptr) return -1; try { cv::Rect bbox(x, y, width, height); (*Handle)->SetBBox(bbox); return 1; } catch (const std::exception& e) { std::cerr << "Error setting RTSP bounding box: " << e.what() << std::endl; return 0; } catch (...) { std::cerr << "Error setting RTSP bounding box: Unknown exception." << std::endl; return 0; } } extern "C" __declspec(dllexport) void SetMaxHWDecoders(int maxDecoders) { ANSCENTER::ANSRTSPClient::SetMaxHWDecoders(maxDecoders); } extern "C" __declspec(dllexport) int AutoConfigureHWDecoders(int maxPerGpuOverride) { return ANSCENTER::ANSRTSPClient::AutoConfigureHWDecoders(maxPerGpuOverride); } extern "C" __declspec(dllexport) void SetRTSPHWDecoding(ANSCENTER::ANSRTSPClient** Handle, int hwMode, int preferredGpu) { if (Handle == nullptr || *Handle == nullptr) return; try { (*Handle)->SetHWDecoding(hwMode, preferredGpu); } catch (...) { } } extern "C" __declspec(dllexport) int IsRTSPHWDecodingActive(ANSCENTER::ANSRTSPClient** Handle) { if (Handle == nullptr || *Handle == nullptr) return 0; try { return (*Handle)->IsHWDecodingActive() ? 1 : 0; } catch (...) { return 0; } } extern "C" __declspec(dllexport) int GetRTSPHWDecodingGpuIndex(ANSCENTER::ANSRTSPClient** Handle) { if (Handle == nullptr || *Handle == nullptr) return -1; try { return (*Handle)->GetHWDecodingGpuIndex(); } catch (...) { return -1; } } extern "C" __declspec(dllexport) void SetRTSPImageQuality(ANSCENTER::ANSRTSPClient** Handle, int mode) { if (Handle == nullptr || *Handle == nullptr) return; try { (*Handle)->SetImageQuality(mode); // 0=fast (AI, default), 1=quality (display BT.709) } catch (...) { } } extern "C" __declspec(dllexport) void SetRTSPDisplayResolution(ANSCENTER::ANSRTSPClient** Handle, int width, int height) { if (Handle == nullptr || *Handle == nullptr) return; try { (*Handle)->SetDisplayResolution(width, height); } catch (...) { } } extern "C" __declspec(dllexport) int SetCropFlagRTSP(ANSCENTER::ANSRTSPClient** Handle, int cropFlag) { if (Handle == nullptr || *Handle == nullptr) return -1; try { bool crop = false; if (cropFlag == 1) crop = true; (*Handle)->SetCrop(crop); return 1; } catch (const std::exception& e) { std::cerr << "Error setting RTSP crop flag: " << e.what() << std::endl; return 0; } catch (...) { std::cerr << "Error setting RTSP crop flag: Unknown exception." << std::endl; return 0; } } // ============================================================================ // V2 entry points: accept uint64_t handle-by-value instead of Handle** // This eliminates a LabVIEW buffer reuse bug when concurrent calls share the // same Handle** buffer address. // ============================================================================ extern "C" __declspec(dllexport) int GetRTSPImage_V2(uint64_t handleVal, int& width, int& height, int64_t& timeStamp, LStrHandle jpegImage) { auto* h = reinterpret_cast(handleVal); if (!h) return -1; try { std::string jpegString = h->GetJpegImage(width, height, timeStamp); int size = jpegString.length(); if (size > 0) { MgErr error; error = DSSetHandleSize(jpegImage, sizeof(int32) + size * sizeof(uChar)); if (error == noErr) { (*jpegImage)->cnt = size; memcpy((*jpegImage)->str, jpegString.c_str(), size); return 1; } else { std::cerr << "Error resizing jpegImage handle: " << error << std::endl; return 0; } } else { std::cerr << "No image data retrieved from FLV client." << std::endl; return 0; } } catch (const std::exception& e) { std::cerr << "Error getting RTSP image: " << e.what() << std::endl; return 0; } catch (...) { std::cerr << "Error getting RTSP image: Unknown exception." << std::endl; return 0; } } extern "C" __declspec(dllexport) int GetRTSPCVImage_V2( uint64_t handleVal, int& width, int& height, int64_t& timeStamp, cv::Mat** image) { auto* h = reinterpret_cast(handleVal); if (!h) return -1; if (!image) { std::cerr << "Error: Invalid input parameters in GetRTSPCVImage_V2" << std::endl; return -1; } try { cv::Mat img = h->GetImage(width, height, timeStamp); if (img.empty()) { return 0; } anscv_mat_replace(image, std::move(img)); return 1; } catch (const cv::Exception& e) { std::cerr << "OpenCV exception in GetRTSPCVImage_V2: " << e.what() << std::endl; return -2; } catch (const std::exception& e) { std::cerr << "Exception in GetRTSPCVImage_V2: " << e.what() << std::endl; return -2; } catch (...) { std::cerr << "Unknown exception in GetRTSPCVImage_V2" << std::endl; return -2; } } extern "C" __declspec(dllexport) int StartRTSP_V2(uint64_t handleVal) { auto* h = reinterpret_cast(handleVal); if (!h) return -1; try { bool result = h->Start(); if (result) return 1; else return 0; } catch (const std::exception& e) { std::cerr << "Error starting RTSP client: " << e.what() << std::endl; return 0; } catch (...) { std::cerr << "Error starting RTSP client: Unknown exception." << std::endl; return 0; } } extern "C" __declspec(dllexport) int ReconnectRTSP_V2(uint64_t handleVal) { auto* h = reinterpret_cast(handleVal); if (!h) return -1; try { bool result = h->Reconnect(); if (result) return 1; else return 0; } catch (const std::exception& e) { std::cerr << "Error reconnecting RTSP client: " << e.what() << std::endl; return 0; } catch (...) { std::cerr << "Error reconnecting RTSP client: Unknown exception." << std::endl; return 0; } } extern "C" __declspec(dllexport) int StopRTSP_V2(uint64_t handleVal) { auto* h = reinterpret_cast(handleVal); if (!h) return -1; try { bool result = h->Stop(); if (result) return 1; else return 0; } catch (const std::exception& e) { std::cerr << "Error stopping RTSP client: " << e.what() << std::endl; return 0; } catch (...) { std::cerr << "Error stopping RTSP client: Unknown exception." << std::endl; return 0; } } extern "C" __declspec(dllexport) int PauseRTSP_V2(uint64_t handleVal) { auto* h = reinterpret_cast(handleVal); if (!h) return -1; try { bool result = h->Pause(); if (result) return 1; else return 0; } catch (const std::exception& e) { std::cerr << "Error pausing RTSP client: " << e.what() << std::endl; return 0; } catch (...) { std::cerr << "Error pausing RTSP client: Unknown exception." << std::endl; return 0; } } extern "C" __declspec(dllexport) int IsRTSPPaused_V2(uint64_t handleVal) { auto* h = reinterpret_cast(handleVal); if (!h) return -1; try { bool result = h->IsPaused(); if (result) return 1; else return 0; } catch (const std::exception& e) { std::cerr << "Error checking if RTSP client is paused: " << e.what() << std::endl; return 0; } catch (...) { std::cerr << "Error checking if RTSP client is paused: Unknown exception." << std::endl; return 0; } } extern "C" __declspec(dllexport) int IsRTSPRunning_V2(uint64_t handleVal) { auto* h = reinterpret_cast(handleVal); if (!h) return -1; try { bool result = h->IsPlaying(); if (result) return 1; else return 0; } catch (const std::exception& e) { std::cerr << "Error checking if RTSP client is running: " << e.what() << std::endl; return 0; } catch (...) { std::cerr << "Error checking if RTSP client is running: Unknown exception." << std::endl; return 0; } } extern "C" __declspec(dllexport) int IsRTSPRecording_V2(uint64_t handleVal) { auto* h = reinterpret_cast(handleVal); if (!h) return -1; try { bool result = h->IsRecording(); if (result) return 1; else return 0; } catch (const std::exception& e) { std::cerr << "Error checking if RTSP client is recording: " << e.what() << std::endl; return 0; } catch (...) { std::cerr << "Error checking if RTSP client is recording: Unknown exception." << std::endl; return 0; } } extern "C" __declspec(dllexport) int SetRTSPAudioVolume_V2(uint64_t handleVal, int volume) { auto* h = reinterpret_cast(handleVal); if (!h) return -1; try { h->SetAudioVolume(volume); return 1; } catch (...) { return 0; } } extern "C" __declspec(dllexport) int EnableRTSPAudioVolume_V2(uint64_t handleVal, int status) { auto* h = reinterpret_cast(handleVal); if (!h) return -1; try { bool audioStatus = false; if (status == 1) audioStatus = true; h->EnableAudio(audioStatus); return 1; } catch (...) { return 0; } } extern "C" __declspec(dllexport) int SetRTSPImageRotation_V2(uint64_t handleVal, double rotationAngle) { auto* h = reinterpret_cast(handleVal); if (!h) return -1; try { h->SetImageRotate(rotationAngle); return 1; } catch (...) { return 0; } } extern "C" __declspec(dllexport) int SetBBoxRTSP_V2(uint64_t handleVal, int x, int y, int width, int height) { auto* h = reinterpret_cast(handleVal); if (!h) return -1; try { cv::Rect bbox(x, y, width, height); h->SetBBox(bbox); return 1; } catch (const std::exception& e) { std::cerr << "Error setting RTSP bounding box: " << e.what() << std::endl; return 0; } catch (...) { std::cerr << "Error setting RTSP bounding box: Unknown exception." << std::endl; return 0; } } extern "C" __declspec(dllexport) int SetCropFlagRTSP_V2(uint64_t handleVal, int cropFlag) { auto* h = reinterpret_cast(handleVal); if (!h) return -1; try { bool crop = false; if (cropFlag == 1) crop = true; h->SetCrop(crop); return 1; } catch (const std::exception& e) { std::cerr << "Error setting RTSP crop flag: " << e.what() << std::endl; return 0; } catch (...) { std::cerr << "Error setting RTSP crop flag: Unknown exception." << std::endl; return 0; } } extern "C" __declspec(dllexport) int SetRTSPHWDecoding_V2(uint64_t handleVal, int hwMode, int preferredGpu) { auto* h = reinterpret_cast(handleVal); if (!h) return -1; try { h->SetHWDecoding(hwMode, preferredGpu); return 1; } catch (...) { return 0; } } extern "C" __declspec(dllexport) int SetRTSPImageQuality_V2(uint64_t handleVal, int mode) { auto* h = reinterpret_cast(handleVal); if (!h) return -1; try { h->SetImageQuality(mode); // 0=fast (AI, default), 1=quality (display BT.709) return 1; } catch (...) { return 0; } }