Files
ANSCORE/modules/ANSCV/ANSRTSP.cpp

1753 lines
69 KiB
C++
Raw Normal View History

2026-03-28 16:54:11 +11:00
#include "ANSRTSP.h"
#include "ANSMatRegistry.h"
#include "ANSGpuFrameOps.h"
#include "GpuNV12SlotPool.h"
2026-03-28 16:54:11 +11:00
#include <memory>
#include <format>
2026-03-28 16:54:11 +11:00
#include "media_codec.h"
#include <cstdint>
#include <cuda_runtime.h>
#if defined(_WIN32)
#include <dxgi1_2.h>
#pragma comment(lib, "dxgi.lib")
#elif defined(__linux__)
#include <dirent.h>
#include <fstream>
#include <sstream>
#endif
extern "C"
{
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libavutil/frame.h>
}
// 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
2026-03-28 16:54:11 +11:00
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();
}
2026-03-28 16:54:11 +11:00
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<std::recursive_mutex> 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();
2026-03-28 16:54:11 +11:00
}
}
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<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> lock(_mutex);
if(_useFullURL){
return _playerClient->open(_url);
}
else {
return _playerClient->open(_username, _password, _url);
}
}
void ANSRTSPClient::SetBBox(cv::Rect bbox) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
_playerClient->setBbox(bbox);
}
void ANSRTSPClient::SetCrop(bool crop) {
std::lock_guard<std::recursive_mutex> 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 (no new NV12 attachments).
{
std::unique_lock<std::recursive_mutex> lock(_mutex);
_isPlaying = false;
// --- Inference guard: wait for ALL in-flight inference to finish ---
// _inFlightFrames tracks frames from GetRTSPCVImage through to the
// end of inference (DecrementInFlight fires when last clone is released).
// We MUST wait for this to reach 0 before calling close(), because
// inference may still be reading NV12 pool buffer data that depends
// on the NVDEC decoder context being alive.
//
// DO NOT force-reset _inFlightFrames or invalidate onReleaseFn —
// let inference finish naturally so DecrementInFlight fires correctly.
int inFlight = _inFlightFrames.load(std::memory_order_acquire);
if (inFlight > 0) {
_logger.LogInfo("ANSRTSPClient::Reconnect",
std::format("waiting for {} in-flight inference(s) to complete...", inFlight),
__FILE__, __LINE__);
bool done = _inFlightDone.wait_for(lock, std::chrono::seconds(10), [this] {
return _inFlightFrames.load(std::memory_order_acquire) <= 0;
});
if (!done) {
_logger.LogWarn("ANSRTSPClient::Reconnect",
std::format("timed out — still {} in-flight, proceeding with close()",
_inFlightFrames.load()),
__FILE__, __LINE__);
// Force-reset only on timeout as last resort
ANSGpuFrameRegistry::instance().invalidateOwner(this);
_inFlightFrames.store(0, std::memory_order_release);
}
}
}
// 2. close() destroys NVDEC decoder ONLY — run outside _mutex to
// avoid deadlocking with nvcuda64 SRW lock held by other cameras.
// At this point, all inference using this camera's NV12 data has
// completed (or timed out), so close() is safe.
_logger.LogInfo("ANSRTSPClient::Reconnect",
"calling close() — NVDEC decoder will be destroyed", __FILE__, __LINE__);
RTSP_DBG("[Reconnect] BEFORE close() this=%p", (void*)this);
2026-03-28 16:54:11 +11:00
_playerClient->close();
RTSP_DBG("[Reconnect] AFTER close() this=%p", (void*)this);
// 3. Re-setup and play under the mutex.
std::lock_guard<std::recursive_mutex> lock(_mutex);
_logger.LogInfo("ANSRTSPClient::Reconnect",
"calling Setup() + play()", __FILE__, __LINE__);
2026-03-28 16:54:11 +11:00
Setup();
_isPlaying = _playerClient->play();
RTSP_DBG("[Reconnect] DONE isPlaying=%d this=%p", (int)_isPlaying, (void*)this);
2026-03-28 16:54:11 +11:00
return _isPlaying;
}
void ANSRTSPClient::EnableAudio(bool status) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
_playerClient->enableAudio(status);
}
void ANSRTSPClient::SetAudioVolume(int volume) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
_playerClient->setVolume(volume);
}
bool ANSRTSPClient::Start() {
std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> lock(_mutex);
if (_isPlaying) {
_isPlaying = false;
player = _playerClient.get();
}
}
if (player) {
player->stop();
}
2026-03-28 16:54:11 +11:00
return true;
}
bool ANSRTSPClient::Pause() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
_isPlaying = false;
return _playerClient->pause();
}
bool ANSRTSPClient::IsPaused() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
return _playerClient->isPaused();
}
bool ANSRTSPClient::IsPlaying() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
return _playerClient->isPlaying();
}
bool ANSRTSPClient::IsRecording() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
return _playerClient->isRecording();
}
std::string ANSRTSPClient::GetJpegImage(int& width, int& height, int64_t& pts) {
std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> 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<uint32>(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<int>(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<int>(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<uint32>(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<uint32>(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<int> 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<std::recursive_mutex> lock(_mutex);
_playerClient->setHWDecoding(hwMode, preferredGpu);
}
bool ANSRTSPClient::IsHWDecodingActive() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
return _playerClient->isHWDecodingActive();
}
int ANSRTSPClient::GetHWDecodingGpuIndex() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
return _playerClient->getHWDecodingGpuIndex();
}
void ANSRTSPClient::SetDisplayResolution(int width, int height) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
_displayWidth = width;
_displayHeight = height;
}
void ANSRTSPClient::SetImageQuality(int mode) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
_playerClient->setImageQuality(mode); // 0=fast (AI), 1=quality (display)
}
void ANSRTSPClient::SetTargetFPS(double intervalMs) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
_playerClient->setTargetFPS(intervalMs); // 0=no limit, 100=~10FPS, 200=~5FPS
}
void ANSRTSPClient::SetNV12FastPath(bool enable) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
_useNV12FastPath = enable;
}
2026-03-28 16:54:11 +11:00
AVFrame* ANSRTSPClient::GetNV12Frame() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
if (!_isPlaying) return nullptr; // Player may be mid-reconnect (CUDA resources freed)
2026-03-28 16:54:11 +11:00
return _playerClient->getNV12Frame(); // Returns clone, caller must av_frame_free
}
AVFrame* ANSRTSPClient::GetCudaHWFrame() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
if (!_isPlaying) return nullptr; // Player may be mid-reconnect (CUDA resources freed)
2026-03-28 16:54:11 +11:00
return _playerClient->getCudaHWFrame();
}
bool ANSRTSPClient::IsCudaHWAccel() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
return _playerClient->isCudaHWAccel();
}
std::string ANSRTSPClient::MatToBinaryData(const cv::Mat& image) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
if (!image.empty()) {
if ((image.data != nullptr) && (image.u != nullptr)) {
try {
// Encode the image to a memory buffer
std::vector<uchar> 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<ANSCENTER::ANSRTSPClient>();
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
2026-03-28 16:54:11 +11:00
*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<ANSCENTER::ANSRTSPClient*>(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;
2026-03-28 16:54:11 +11:00
*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();
2026-03-28 16:54:11 +11:00
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();
2026-03-28 16:54:11 +11:00
// 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();
// NV12 GPU fast path: attach NV12 frame data for zero-copy inference.
// When disabled (_useNV12FastPath=false), the original stable CPU path is used:
// GetImage() returns BGR cv::Mat in CPU RAM → no CUDA calls → no SRW lock contention.
// When enabled, D2D copies NV12 from NVDEC to pool buffers for GPU inference.
if ((*Handle)->IsNV12FastPath()) {
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]);
// Acquire a slot from the global pool — survives camera Destroy.
GpuNV12Slot* slot = GpuNV12SlotPool::instance().acquire(
gpuIdx, cudaHW->width, cudaHW->height);
// Only fetch CPU NV12 if pool slot unavailable (cross-GPU fallback).
AVFrame* cpuNV12 = slot ? nullptr : (*Handle)->GetNV12Frame();
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.
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<ANSCENTER::ANSRTSPClient*>(client)->DecrementInFlight();
};
} else {
(*Handle)->DecrementInFlight();
}
} else {
RTSP_DBG("[GetRTSPCVImage] SKIP CUDA — player not playing (reconnecting?)");
}
2026-03-28 16:54:11 +11:00
}
// else: original CPU path — cv::Mat** contains BGR data in CPU RAM.
// No CUDA calls, no pool slots, no GPU frame registry.
// Inference uses cv::Mat directly (upload to GPU in engine).
2026-03-28 16:54:11 +11:00
2026-04-03 15:16:26 +11:00
// Lightweight timing — logs only when frame grab + D2D exceeds 50ms.
// Goes to both spdlog (console/file) AND OutputDebugString (DebugView)
// but ONLY for slow frames, so the overhead is negligible (<1 call/sec
// under normal conditions vs 500+/sec with full debug logging).
auto t2 = std::chrono::steady_clock::now();
double getImageMs = std::chrono::duration<double, std::milli>(t1 - t0).count();
double cudaMs = std::chrono::duration<double, std::milli>(t2 - t1).count();
double totalMs = getImageMs + cudaMs;
if (totalMs > 500.0) {
2026-04-03 15:16:26 +11:00
auto msg = std::format("SLOW FRAME: total={:.1f}ms (getImage={:.1f}ms cuda={:.1f}ms) {}x{}",
totalMs, getImageMs, cudaMs, width, height);
(*Handle)->_logger.LogWarn("GetRTSPCVImage", msg, __FILE__, __LINE__);
#ifdef _WIN32
auto dbg = std::format("[GetRTSPCVImage] {}\n", msg);
OutputDebugStringA(dbg.c_str());
#endif
}
2026-03-28 16:54:11 +11:00
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<std::mutex> 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<std::mutex> 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) void SetRTSPTargetFPS(ANSCENTER::ANSRTSPClient** Handle, double intervalMs) {
if (Handle == nullptr || *Handle == nullptr) return;
try {
(*Handle)->SetTargetFPS(intervalMs); // 0=no limit, 100=~10FPS, 200=~5FPS
} catch (...) { }
}
extern "C" __declspec(dllexport) void SetRTSPNV12FastPath(ANSCENTER::ANSRTSPClient** Handle, int enable) {
if (Handle == nullptr || *Handle == nullptr) return;
try {
(*Handle)->SetNV12FastPath(enable != 0); // 0=original CPU path (stable), 1=NV12 GPU fast path
} catch (...) { }
}
2026-03-28 16:54:11 +11:00
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<ANSCENTER::ANSRTSPClient*>(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<ANSCENTER::ANSRTSPClient*>(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<ANSCENTER::ANSRTSPClient*>(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<ANSCENTER::ANSRTSPClient*>(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<ANSCENTER::ANSRTSPClient*>(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<ANSCENTER::ANSRTSPClient*>(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<ANSCENTER::ANSRTSPClient*>(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<ANSCENTER::ANSRTSPClient*>(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<ANSCENTER::ANSRTSPClient*>(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<ANSCENTER::ANSRTSPClient*>(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<ANSCENTER::ANSRTSPClient*>(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<ANSCENTER::ANSRTSPClient*>(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<ANSCENTER::ANSRTSPClient*>(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<ANSCENTER::ANSRTSPClient*>(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<ANSCENTER::ANSRTSPClient*>(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<ANSCENTER::ANSRTSPClient*>(handleVal); if (!h) return -1;
try {
h->SetImageQuality(mode); // 0=fast (AI, default), 1=quality (display BT.709)
return 1;
} catch (...) { return 0; }
}