2026-03-28 16:54:11 +11:00
|
|
|
|
#include "ANSRTSP.h"
|
|
|
|
|
|
#include "ANSMatRegistry.h"
|
|
|
|
|
|
#include "ANSGpuFrameOps.h"
|
|
|
|
|
|
#include <memory>
|
2026-04-02 22:07:27 +11:00
|
|
|
|
#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
|
2026-04-02 22:07:27 +11:00
|
|
|
|
|
|
|
|
|
|
// Debug logging — goes to both stderr AND OutputDebugString (DebugView).
|
|
|
|
|
|
#ifndef RTSP_DBG
|
|
|
|
|
|
#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
|
|
|
|
|
|
#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();
|
|
|
|
|
|
}
|
|
|
|
|
|
void ANSRTSPClient::Destroy() {
|
2026-04-02 22:07:27 +11:00
|
|
|
|
// 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 frames to finish ---
|
|
|
|
|
|
// GetRTSPCVImage increments _inFlightFrames when it hands out
|
|
|
|
|
|
// a GPU frame; the registry decrements it when the frame is
|
|
|
|
|
|
// released after inference completes. We wait here so that
|
|
|
|
|
|
// close() doesn't free NVDEC surfaces while TensorRT is
|
|
|
|
|
|
// still reading from them (the LabVIEW crash root cause).
|
|
|
|
|
|
int inFlight = _inFlightFrames.load(std::memory_order_acquire);
|
|
|
|
|
|
if (inFlight > 0) {
|
|
|
|
|
|
_logger.LogInfo("ANSRTSPClient::Destroy",
|
|
|
|
|
|
std::format("waiting for {} in-flight inference 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 waiting for in-flight frames "
|
|
|
|
|
|
"(still {} in-flight) — force-releasing GPU frames",
|
|
|
|
|
|
_inFlightFrames.load()),
|
|
|
|
|
|
__FILE__, __LINE__);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Force-release ALL GPU frames owned by this client BEFORE close().
|
|
|
|
|
|
// Unreleased clones (e.g. LabVIEW AI tasks still holding cloned
|
|
|
|
|
|
// cv::Mat*) keep gpuCacheY/gpuCacheUV allocated. We must cudaFree
|
|
|
|
|
|
// them NOW while the CUDA context is still alive. After close()
|
|
|
|
|
|
// destroys the context, cudaFree would crash.
|
|
|
|
|
|
int forceReleased = ANSGpuFrameRegistry::instance().forceReleaseByOwner(this);
|
|
|
|
|
|
if (forceReleased > 0) {
|
|
|
|
|
|
_logger.LogWarn("ANSRTSPClient::Destroy",
|
|
|
|
|
|
std::format("force-released {} GPU frame(s) with unreleased clones", forceReleased),
|
|
|
|
|
|
__FILE__, __LINE__);
|
|
|
|
|
|
// Drain and cudaFree the GPU buffers while CUDA context is alive
|
|
|
|
|
|
// Sync all GPU streams before freeing to avoid illegal access
|
|
|
|
|
|
cudaDeviceSynchronize();
|
|
|
|
|
|
auto gpuPending = ANSGpuFrameRegistry::instance().drain_gpu_pending();
|
|
|
|
|
|
if (!gpuPending.empty()) {
|
|
|
|
|
|
RTSP_DBG("[Destroy] cudaFree %zu GPU ptrs before close()", gpuPending.size());
|
|
|
|
|
|
int prevDev = -1;
|
|
|
|
|
|
cudaGetDevice(&prevDev);
|
|
|
|
|
|
for (auto& entry : gpuPending) {
|
|
|
|
|
|
if (entry.ptr) {
|
|
|
|
|
|
if (entry.deviceIdx >= 0) cudaSetDevice(entry.deviceIdx);
|
|
|
|
|
|
cudaFree(entry.ptr);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (prevDev >= 0) cudaSetDevice(prevDev);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Also drain any pending AVFrames
|
|
|
|
|
|
auto avPending = ANSGpuFrameRegistry::instance().drain_pending();
|
|
|
|
|
|
for (void* p : avPending) {
|
|
|
|
|
|
AVFrame* f = static_cast<AVFrame*>(p);
|
|
|
|
|
|
av_frame_free(&f);
|
|
|
|
|
|
}
|
2026-03-28 16:54:11 +11:00
|
|
|
|
}
|
2026-04-02 22:07:27 +11:00
|
|
|
|
ANSGpuFrameRegistry::instance().invalidateOwner(this);
|
|
|
|
|
|
_inFlightFrames.store(0, std::memory_order_release);
|
|
|
|
|
|
|
|
|
|
|
|
clientToClose = std::move(_playerClient);
|
|
|
|
|
|
}
|
|
|
|
|
|
// CUDA cleanup happens here, outside the mutex — now safe.
|
|
|
|
|
|
// All GPU frames owned by this client have been force-freed above.
|
|
|
|
|
|
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() {
|
2026-04-02 22:07:27 +11:00
|
|
|
|
// 1. Mark as not-playing under the mutex FIRST. This makes GetImage()
|
|
|
|
|
|
// return the cached _pLastFrame instead of calling into the player,
|
|
|
|
|
|
// preventing use-after-free when close() destroys CUDA resources.
|
|
|
|
|
|
{
|
|
|
|
|
|
std::unique_lock<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
|
_isPlaying = false;
|
|
|
|
|
|
|
|
|
|
|
|
// --- Inference guard: wait for in-flight frames to finish ---
|
|
|
|
|
|
// Same guard as Destroy(): close() will free NVDEC surfaces, so
|
|
|
|
|
|
// we must wait for any inference engines still reading NV12 data
|
|
|
|
|
|
// via zero-copy CUDA device pointers.
|
|
|
|
|
|
int inFlight = _inFlightFrames.load(std::memory_order_acquire);
|
|
|
|
|
|
if (inFlight > 0) {
|
|
|
|
|
|
_logger.LogInfo("ANSRTSPClient::Reconnect",
|
|
|
|
|
|
std::format("waiting for {} in-flight inference 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 waiting for in-flight frames "
|
|
|
|
|
|
"(still {} in-flight) — force-releasing GPU frames",
|
|
|
|
|
|
_inFlightFrames.load()),
|
|
|
|
|
|
__FILE__, __LINE__);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Force-release GPU frames before close() — same as Destroy().
|
|
|
|
|
|
int forceReleased = ANSGpuFrameRegistry::instance().forceReleaseByOwner(this);
|
|
|
|
|
|
if (forceReleased > 0) {
|
|
|
|
|
|
_logger.LogWarn("ANSRTSPClient::Reconnect",
|
|
|
|
|
|
std::format("force-released {} GPU frame(s) with unreleased clones", forceReleased),
|
|
|
|
|
|
__FILE__, __LINE__);
|
|
|
|
|
|
// Sync all GPU streams before freeing
|
|
|
|
|
|
cudaDeviceSynchronize();
|
|
|
|
|
|
auto gpuPending = ANSGpuFrameRegistry::instance().drain_gpu_pending();
|
|
|
|
|
|
if (!gpuPending.empty()) {
|
|
|
|
|
|
int prevDev = -1;
|
|
|
|
|
|
cudaGetDevice(&prevDev);
|
|
|
|
|
|
for (auto& entry : gpuPending) {
|
|
|
|
|
|
if (entry.ptr) {
|
|
|
|
|
|
if (entry.deviceIdx >= 0) cudaSetDevice(entry.deviceIdx);
|
|
|
|
|
|
cudaFree(entry.ptr);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (prevDev >= 0) cudaSetDevice(prevDev);
|
|
|
|
|
|
}
|
|
|
|
|
|
auto avPending = ANSGpuFrameRegistry::instance().drain_pending();
|
|
|
|
|
|
for (void* p : avPending) {
|
|
|
|
|
|
AVFrame* f = static_cast<AVFrame*>(p);
|
|
|
|
|
|
av_frame_free(&f);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
ANSGpuFrameRegistry::instance().invalidateOwner(this);
|
|
|
|
|
|
_inFlightFrames.store(0, std::memory_order_release);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. close() does CUDA cleanup (cuArrayDestroy/cuMemFree) — run outside
|
|
|
|
|
|
// _mutex to avoid deadlocking with nvcuda64 SRW lock held by inference.
|
|
|
|
|
|
// Safe now because GetImage()/GetNV12Frame() won't touch the player
|
|
|
|
|
|
// while _isPlaying == false, and all in-flight frames have been released.
|
|
|
|
|
|
_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();
|
2026-04-02 22:07:27 +11:00
|
|
|
|
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();
|
2026-04-02 22:07:27 +11:00
|
|
|
|
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() {
|
2026-04-02 22:07:27 +11:00
|
|
|
|
// 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)
|
|
|
|
|
|
}
|
|
|
|
|
|
AVFrame* ANSRTSPClient::GetNV12Frame() {
|
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
2026-04-02 22:07:27 +11:00
|
|
|
|
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);
|
2026-04-02 22:07:27 +11:00
|
|
|
|
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) {
|
2026-04-02 22:07:27 +11:00
|
|
|
|
// 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);
|
2026-04-02 22:07:27 +11:00
|
|
|
|
|
|
|
|
|
|
// 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;
|
2026-04-02 22:07:27 +11:00
|
|
|
|
|
|
|
|
|
|
// 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 {
|
|
|
|
|
|
// 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));
|
|
|
|
|
|
|
|
|
|
|
|
// Attach NV12 frame for GPU fast-path inference (side-table registry)
|
|
|
|
|
|
// attach() takes ownership — do NOT av_frame_free here
|
2026-04-02 22:07:27 +11:00
|
|
|
|
//
|
|
|
|
|
|
// 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().
|
2026-03-28 16:54:11 +11:00
|
|
|
|
int gpuIdx = (*Handle)->GetHWDecodingGpuIndex();
|
2026-04-02 22:07:27 +11:00
|
|
|
|
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();
|
|
|
|
|
|
gpu_frame_attach_cuda(*image, cudaHW, gpuIdx, timeStamp, cpuNV12);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// HW decode not active — try CPU NV12
|
|
|
|
|
|
AVFrame* nv12 = (*Handle)->GetNV12Frame();
|
|
|
|
|
|
if (nv12) {
|
|
|
|
|
|
gpu_frame_attach(*image, nv12, gpuIdx, timeStamp);
|
|
|
|
|
|
}
|
2026-03-28 16:54:11 +11:00
|
|
|
|
}
|
2026-04-02 22:07:27 +11:00
|
|
|
|
|
|
|
|
|
|
// 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 gpuCacheY=%p",
|
|
|
|
|
|
(void*)gpuData,
|
|
|
|
|
|
gpuData ? (void*)gpuData->yPlane : nullptr,
|
|
|
|
|
|
gpuData ? (int)gpuData->isCudaDevicePtr : -1,
|
|
|
|
|
|
gpuData ? gpuData->gpuCacheY : nullptr);
|
|
|
|
|
|
if (gpuData) {
|
|
|
|
|
|
gpuData->ownerClient = *Handle;
|
|
|
|
|
|
gpuData->onReleaseFn = [](void* client) {
|
|
|
|
|
|
static_cast<ANSCENTER::ANSRTSPClient*>(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?)");
|
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) 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; }
|
|
|
|
|
|
}
|