Files
ANSCORE/modules/ANSCV/ANSMJPEG.cpp

1188 lines
43 KiB
C++
Raw Normal View History

2026-03-28 16:54:11 +11:00
#include "ANSMJPEG.h"
#include "ANSMatRegistry.h"
#include "ANSGpuFrameOps.h"
#include <memory>
#include <cstdint>
#include "media_codec.h"
#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 ANSMJPEGClient::_mutex
// Mat registry thread safety is handled by anscv_mat_replace's internal registry_mutex
static bool ansmjpegLicenceValid = false;
// Global once_flag to protect license checking
static std::once_flag ansmjpegLicenseOnceFlag;
static std::once_flag mjpegHwDecoderAutoConfigOnceFlag;
namespace ANSCENTER {
ANSMJPEGClient::ANSMJPEGClient() {
_useFullURL = false;
_username = "";
_password = "";
_url = "";
_imageWidth = 0;
_imageHeight = 0;
_pts = 0;
_lastJpegImage = "";
_isPlaying = false;
// Auto-configure HW decoder pool on first client creation.
std::call_once(mjpegHwDecoderAutoConfigOnceFlag, []() {
AutoConfigureHWDecoders(0);
});
}
ANSMJPEGClient::~ANSMJPEGClient() noexcept {
Destroy();
}
void ANSMJPEGClient::Destroy() {
decltype(_playerClient) clientToClose;
{
std::lock_guard<std::recursive_mutex> lock(_mutex);
if (_playerClient) {
if (_isPlaying) {
_playerClient->stop();
_isPlaying = false;
}
2026-03-28 16:54:11 +11:00
}
clientToClose = std::move(_playerClient);
}
if (clientToClose) {
clientToClose->close();
2026-03-28 16:54:11 +11:00
}
}
static void VerifyGlobalANSMJPEGLicense(const std::string& licenseKey) {
try {
ansmjpegLicenceValid = ANSCENTER::ANSLicenseHelper::LicenseVerification(licenseKey, 1007, "ANSCV");//Default productId=1005
if (!ansmjpegLicenceValid) { // we also support ANSTS license
ansmjpegLicenceValid = ANSCENTER::ANSLicenseHelper::LicenseVerification(licenseKey, 1003, "ANSVIS");//Default productId=1003 (ANSVIS)
}
if (!ansmjpegLicenceValid) { // we also support ANSTS license
ansmjpegLicenceValid = ANSCENTER::ANSLicenseHelper::LicenseVerification(licenseKey, 1008, "ANSTS");//Default productId=1008 (ANSTS)
}
}
catch (std::exception& e) {
ansmjpegLicenceValid = false;
}
}
void ANSMJPEGClient::CheckLicense() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
try {
// Check once globally
std::call_once(ansmjpegLicenseOnceFlag, [this]() {
VerifyGlobalANSMJPEGLicense(_licenseKey);
});
// Update this instance's local license flag
_licenseValid = ansmjpegLicenceValid;
}
catch (const std::exception& e) {
this->_logger.LogFatal("ANSMJPEGClient::CheckLicense. Error:", e.what(), __FILE__, __LINE__);
}
}
bool ANSMJPEGClient::Init(std::string licenseKey, std::string url) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
_licenseKey = licenseKey;
CheckLicense();
if (!_licenseValid) {
this->_logger.LogError("ANSMJPEGClient::Init.", "Invalid license", __FILE__, __LINE__);
return false;
}
_url = url;
_useFullURL = true;
return Setup();
}
bool ANSMJPEGClient::Init(std::string licenseKey, std::string username, std::string password, std::string url) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
_licenseKey = licenseKey;
CheckLicense();
if (!_licenseValid) {
this->_logger.LogError("ANSMJPEGClient::Init.", "Invalid license", __FILE__, __LINE__);
return false;
}
_url = url;
_username = username;
_password = password;
_useFullURL = false;
return Setup();
}
void ANSMJPEGClient::SetBBox(cv::Rect bbox) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
_playerClient->setBbox(bbox);
}
void ANSMJPEGClient::SetCrop(bool crop) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
_playerClient->setCrop(crop);
}
bool ANSMJPEGClient::Setup() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
if (_useFullURL) {
return _playerClient->open(_url);
}
else {
return _playerClient->open(_username, _password, _url);
}
}
bool ANSMJPEGClient::Reconnect() {
{
std::lock_guard<std::recursive_mutex> lock(_mutex);
_isPlaying = false;
}
2026-03-28 16:54:11 +11:00
_playerClient->close();
std::lock_guard<std::recursive_mutex> lock(_mutex);
2026-03-28 16:54:11 +11:00
Setup();
_isPlaying = _playerClient->play();
return _isPlaying;
}
bool ANSMJPEGClient::Start() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
Setup();
_isPlaying = _playerClient->play();
return _isPlaying;
}
bool ANSMJPEGClient::Stop() {
decltype(_playerClient.get()) 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 ANSMJPEGClient::Pause() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
_isPlaying = false;
return _playerClient->pause();
}
bool ANSMJPEGClient::IsPaused() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
return _playerClient->isPaused();
}
bool ANSMJPEGClient::IsPlaying() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
return _playerClient->isPlaying();
}
bool ANSMJPEGClient::IsRecording() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
return _playerClient->isRecording();
}
std::string ANSMJPEGClient::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 ANSMJPEGClient::areImagesIdentical(const cv::Mat& img1, const cv::Mat& img2) {
2026-04-04 20:19:54 +11:00
double ageMs = _playerClient->getLastFrameAgeMs();
if (ageMs > 5000.0) return true;
if (ageMs > 0.0) return false;
2026-03-28 16:54:11 +11:00
2026-04-04 20:19:54 +11:00
if (img1.empty() && img2.empty()) return true;
if (img1.empty() || img2.empty()) return false;
if (img1.size() != img2.size() || img1.type() != img2.type()) return false;
if (img1.data == img2.data) return true;
2026-03-28 16:54:11 +11:00
if (img1.isContinuous() && img2.isContinuous()) {
const size_t totalBytes = img1.total() * img1.elemSize();
return std::memcmp(img1.data, img2.data, totalBytes) == 0;
}
const size_t rowSize = img1.cols * img1.elemSize();
for (int i = 0; i < img1.rows; i++) {
2026-04-04 20:19:54 +11:00
if (std::memcmp(img1.ptr(i), img2.ptr(i), rowSize) != 0) return false;
2026-03-28 16:54:11 +11:00
}
return true;
}
cv::Mat ANSMJPEGClient::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)
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;
return currentImage;
}
// Handle rotation case
int rotatedWidth, rotatedHeight;
double absAngle = std::abs(_imageRotateDeg);
if (absAngle == 90.0 || absAngle == 270.0) {
rotatedWidth = imageH;
rotatedHeight = imageW;
}
else {
rotatedWidth = imageW;
rotatedHeight = imageH;
}
cv::Point2f center(imageW / 2.0f, imageH / 2.0f);
cv::Mat rotationMatrix = cv::getRotationMatrix2D(center, _imageRotateDeg, 1.0);
cv::Mat rotatedImage;
cv::warpAffine(currentImage, rotatedImage, rotationMatrix,
cv::Size(rotatedWidth, rotatedHeight),
cv::INTER_LINEAR,
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;
}
void ANSMJPEGClient::EnableAudio(bool status) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
_playerClient->enableAudio(status);
}
void ANSMJPEGClient::SetAudioVolume(int volume) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
_playerClient->setVolume(volume);
}
void ANSMJPEGClient::SetMaxHWDecoders(int maxDecoders) {
if (maxDecoders >= 0) {
g_hw_decoder_max = static_cast<uint32>(maxDecoders);
}
}
// NVDEC hardware cap: fixed-function ASIC with ~32 concurrent session limit.
static constexpr int NVDEC_HW_SESSION_CAP = 32;
static int estimateMaxSessions(int smCount, size_t totalVramBytes) {
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;
size_t totalVramMB = totalVramBytes / (1024 * 1024);
size_t reservedMB = 1024;
size_t availableMB = (totalVramMB > reservedMB) ? (totalVramMB - reservedMB) : 256;
int vramBased = static_cast<int>(availableMB / 80);
if (vramBased < 4) vramBased = 4;
int result = (smBased < vramBased) ? smBased : vramBased;
if (result > NVDEC_HW_SESSION_CAP) result = NVDEC_HW_SESSION_CAP;
return result;
}
static int estimateSessionsByVendor(uint32_t vendorId, size_t dedicatedMB, size_t sharedMB) {
if (vendorId == 0x10DE) {
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) {
size_t effectiveMB = (dedicatedMB > 0) ? dedicatedMB : (sharedMB / 4);
if (effectiveMB >= 2048) return 16;
else if (effectiveMB >= 512) return 12;
else return 8;
}
else if (vendorId == 0x1002) {
if (dedicatedMB >= 4096) return 32;
else if (dedicatedMB >= 1024) return 16;
else return 8;
}
return 4;
}
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)
static int AutoConfigureHWDecoders_Platform_MJPEG() {
IDXGIFactory1* pFactory = nullptr;
HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&pFactory);
if (FAILED(hr) || !pFactory) {
g_hw_decoder_max = 4;
fprintf(stderr, "[HWDecode] MJPEG 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();
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] MJPEG 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] MJPEG 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] MJPEG AutoConfigure: Total %d decode sessions across all GPUs\n", totalSessions);
return totalSessions;
}
#elif defined(__linux__)
static int AutoConfigureHWDecoders_Platform_MJPEG() {
int totalSessions = 0;
bool foundHwGpu = false;
int gpuIndex = 0;
for (int cardNum = 0; cardNum < 16; cardNum++) {
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();
if (vendorId == 0) continue;
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();
}
size_t dedicatedMB = 0;
size_t sharedMB = 0;
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)) {
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();
}
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;
break;
}
}
meminfo.close();
}
}
int maxSessions = estimateSessionsByVendor(vendorId, dedicatedMB, sharedMB);
fprintf(stderr, "[HWDecode] MJPEG 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] MJPEG 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] MJPEG AutoConfigure: Total %d VAAPI decode sessions across all GPUs\n", totalSessions);
return totalSessions;
}
#else
static int AutoConfigureHWDecoders_Platform_MJPEG() {
g_hw_decoder_max = 4;
fprintf(stderr, "[HWDecode] MJPEG AutoConfigure: Unsupported platform, defaulting to %d sessions\n",
g_hw_decoder_max);
return g_hw_decoder_max;
}
#endif
int ANSMJPEGClient::AutoConfigureHWDecoders(int maxPerGpuOverride) {
int gpuCount = 0;
cudaError_t err = cudaGetDeviceCount(&gpuCount);
if (err != cudaSuccess || gpuCount <= 0) {
return AutoConfigureHWDecoders_Platform_MJPEG();
}
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) {
maxPerGpuList[i] = maxPerGpuOverride;
} else {
maxPerGpuList[i] = estimateMaxSessions(smCount, prop.totalGlobalMem);
}
total += maxPerGpuList[i];
size_t vramMB = prop.totalGlobalMem / (1024 * 1024);
fprintf(stderr, "[HWDecode] MJPEG AutoConfigure: GPU[%d] \"%s\" SM=%d VRAM=%zuMB -> max %d decode sessions\n",
i, prop.name, smCount, vramMB, maxPerGpuList[i]);
}
HWDecoderPool::instance().configure(maxPerGpuList);
return total;
}
void ANSMJPEGClient::SetHWDecoding(int hwMode, int preferredGpu) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
_playerClient->setHWDecoding(hwMode, preferredGpu);
}
bool ANSMJPEGClient::IsHWDecodingActive() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
return _playerClient->isHWDecodingActive();
}
int ANSMJPEGClient::GetHWDecodingGpuIndex() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
return _playerClient->getHWDecodingGpuIndex();
}
void ANSMJPEGClient::SetDisplayResolution(int width, int height) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
_displayWidth = width;
_displayHeight = height;
}
void ANSMJPEGClient::SetImageQuality(int mode) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
_playerClient->setImageQuality(mode); // 0=fast (AI), 1=quality (display)
}
void ANSMJPEGClient::SetTargetFPS(double intervalMs) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
_playerClient->setTargetFPS(intervalMs); // 0=no limit, 100=~10FPS, 200=~5FPS
}
void ANSMJPEGClient::SetNV12FastPath(bool enable) {
std::lock_guard<std::recursive_mutex> lock(_mutex);
_useNV12FastPath = enable;
}
2026-03-28 16:54:11 +11:00
AVFrame* ANSMJPEGClient::GetNV12Frame() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
return _playerClient->getNV12Frame(); // Returns clone, caller must av_frame_free
}
AVFrame* ANSMJPEGClient::GetCudaHWFrame() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
return _playerClient->getCudaHWFrame();
}
bool ANSMJPEGClient::IsCudaHWAccel() {
std::lock_guard<std::recursive_mutex> lock(_mutex);
return _playerClient->isCudaHWAccel();
}
std::string ANSMJPEGClient::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("ANSMJPEGClient::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("ANSMJPEGClient::MatToBinaryData. Error:", e.what(), __FILE__, __LINE__);
return "";
}
catch (...) {
this->_logger.LogFatal("ANSMJPEGClient::MatToBinaryData. Error:", "Caught unknown exception!", __FILE__, __LINE__);
return "";
}
}
else return "";
}
else return "";
}
}
extern "C" __declspec(dllexport) int CreateANSMJPEGHandle(ANSCENTER::ANSMJPEGClient * *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::ANSMJPEGClient>();
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) {
*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::ANSMJPEGClient*>(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 ReleaseANSMJPEGHandle(ANSCENTER::ANSMJPEGClient * *Handle) {
if (Handle == nullptr || *Handle == nullptr) return -1;
try {
extern void anscv_unregister_handle(void*);
anscv_unregister_handle(*Handle);
// unique_ptr destructor calls ~ANSMJPEGClient which calls Destroy()
std::unique_ptr<ANSCENTER::ANSMJPEGClient> ptr(*Handle);
*Handle = nullptr;
return 0;
} catch (...) {
if (Handle) *Handle = nullptr;
return -1;
}
}
extern "C" __declspec(dllexport) int GetMJPEGStrImage(ANSCENTER::ANSMJPEGClient * *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 MJPEG image: " << e.what() << std::endl;
return 0;
}
catch (...) {
std::cerr << "Error getting MJPEG image: Unknown exception." << std::endl;
return 0;
}
}
extern "C" __declspec(dllexport) int GetMJPEGImage(ANSCENTER::ANSMJPEGClient * *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;
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 MJPEG client." << std::endl;
return 0;
}
}
catch (const std::exception& e) {
std::cerr << "Error getting MJPEG image: " << e.what() << std::endl;
return 0;
}
catch (...) {
std::cerr << "Error getting MJPEG image: Unknown exception." << std::endl;
return 0;
}
}
extern "C" __declspec(dllexport) int GetMJPEGCVImage(ANSCENTER::ANSMJPEGClient** Handle, int& width, int& height, int64_t& timeStamp, cv::Mat** image) {
if (!Handle || !*Handle || !image) {
std::cerr << "Error: Invalid input parameters in GetMJPEGCVImage" << std::endl;
return -1;
}
try {
cv::Mat img = (*Handle)->GetImage(width, height, timeStamp);
if (img.empty()) {
return 0;
}
// Thread-safe Mat pointer swap (anscv_mat_replace has its own internal lock)
anscv_mat_replace(image, std::move(img));
// NV12 GPU fast path (optional — disabled by default for stability)
if ((*Handle)->IsNV12FastPath()) {
int gpuIdx = (*Handle)->GetHWDecodingGpuIndex();
AVFrame* cudaHW = (*Handle)->GetCudaHWFrame();
if (cudaHW) {
AVFrame* cpuNV12 = (*Handle)->GetNV12Frame();
gpu_frame_attach_cuda(*image, cudaHW, gpuIdx, timeStamp, cpuNV12);
} else {
AVFrame* nv12 = (*Handle)->GetNV12Frame();
if (nv12) {
gpu_frame_attach(*image, nv12, gpuIdx, timeStamp);
}
2026-03-28 16:54:11 +11:00
}
}
return 1;
}
catch (const cv::Exception& e) {
std::cerr << "OpenCV exception in GetMJPEGCVImage: " << e.what() << std::endl;
return -2;
}
catch (const std::exception& e) {
std::cerr << "Exception in GetMJPEGCVImage: " << e.what() << std::endl;
return -2;
}
catch (...) {
std::cerr << "Unknown exception in GetMJPEGCVImage" << std::endl;
return -2;
}
}
extern "C" __declspec(dllexport) int StartMJPEG(ANSCENTER::ANSMJPEGClient * *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 MJPEG client: " << e.what() << std::endl;
return 0;
}
catch (...) {
std::cerr << "Error starting MJPEG client: Unknown exception." << std::endl;
return 0;
}
}
extern "C" __declspec(dllexport) int ReconnectMJPEG(ANSCENTER::ANSMJPEGClient * *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 MJPEG client: " << e.what() << std::endl;
return 0;
}
catch (...) {
std::cerr << "Error reconnecting MJPEG client: Unknown exception." << std::endl;
return 0;
}
}
extern "C" __declspec(dllexport) int StopMJPEG(ANSCENTER::ANSMJPEGClient * *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 MJPEG client: " << e.what() << std::endl;
return 0;
}
catch (...) {
std::cerr << "Error stopping MJPEG client: Unknown exception." << std::endl;
return 0;
}
}
extern "C" __declspec(dllexport) int PauseMJPEG(ANSCENTER::ANSMJPEGClient** Handle) {
if (Handle == nullptr || *Handle == nullptr) return -1;
try {
bool result = (*Handle)->Pause();
if (result) return 1;
else return 0;
} catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int IsMJPEGPaused(ANSCENTER::ANSMJPEGClient * *Handle) {
if (Handle == nullptr || *Handle == nullptr) return -1;
try {
bool result = (*Handle)->IsPaused();
if (result) return 1;
else return 0;
} catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int IsMJPEGRunning(ANSCENTER::ANSMJPEGClient * *Handle) {
if (Handle == nullptr || *Handle == nullptr) return -1;
try {
bool result = (*Handle)->IsPlaying();
if (result) return 1;
else return 0;
} catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int IsMJPEGRecording(ANSCENTER::ANSMJPEGClient * *Handle) {
if (Handle == nullptr || *Handle == nullptr) return -1;
try {
bool result = (*Handle)->IsRecording();
if (result) return 1;
else return 0;
} catch (...) { return -1; }
}
extern "C" __declspec(dllexport) void SetMJPEGAudioVolume(ANSCENTER::ANSMJPEGClient * *Handle, int volume) {
if (Handle == nullptr || *Handle == nullptr) return;
try {
(*Handle)->SetAudioVolume(volume);
} catch (...) { }
}
extern "C" __declspec(dllexport) void EnableMJPEGAudioVolume(ANSCENTER::ANSMJPEGClient * *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 SetMJPEGImageRotation(ANSCENTER::ANSMJPEGClient * *Handle, double rotationAngle) {
if (Handle == nullptr || *Handle == nullptr) return;
try {
(*Handle)->SetImageRotate(rotationAngle);
} catch (...) { }
}
extern "C" __declspec(dllexport) int SetBBoxMJPEG(ANSCENTER::ANSMJPEGClient** 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 (...) { return -1; }
}
extern "C" __declspec(dllexport) int SetCropFlagMJPEG(ANSCENTER::ANSMJPEGClient** 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 (...) { return -1; }
}
extern "C" __declspec(dllexport) void SetMJPEGMaxHWDecoders(int maxDecoders) {
ANSCENTER::ANSMJPEGClient::SetMaxHWDecoders(maxDecoders);
}
extern "C" __declspec(dllexport) int AutoConfigureMJPEGHWDecoders(int maxPerGpuOverride) {
return ANSCENTER::ANSMJPEGClient::AutoConfigureHWDecoders(maxPerGpuOverride);
}
extern "C" __declspec(dllexport) void SetMJPEGHWDecoding(ANSCENTER::ANSMJPEGClient** Handle, int hwMode, int preferredGpu) {
if (Handle == nullptr || *Handle == nullptr) return;
try {
(*Handle)->SetHWDecoding(hwMode, preferredGpu);
} catch (...) { }
}
extern "C" __declspec(dllexport) int IsMJPEGHWDecodingActive(ANSCENTER::ANSMJPEGClient** Handle) {
if (Handle == nullptr || *Handle == nullptr) return -1;
try {
return (*Handle)->IsHWDecodingActive() ? 1 : 0;
} catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int GetMJPEGHWDecodingGpuIndex(ANSCENTER::ANSMJPEGClient** Handle) {
if (Handle == nullptr || *Handle == nullptr) return -1;
try {
return (*Handle)->GetHWDecodingGpuIndex();
} catch (...) { return -1; }
}
extern "C" __declspec(dllexport) void SetMJPEGImageQuality(ANSCENTER::ANSMJPEGClient** Handle, int mode) {
if (Handle == nullptr || *Handle == nullptr) return;
try {
(*Handle)->SetImageQuality(mode);
} catch (...) { }
}
extern "C" __declspec(dllexport) void SetMJPEGDisplayResolution(ANSCENTER::ANSMJPEGClient** Handle, int width, int height) {
if (Handle == nullptr || *Handle == nullptr) return;
try {
(*Handle)->SetDisplayResolution(width, height);
} catch (...) { }
}
extern "C" __declspec(dllexport) void SetMJPEGTargetFPS(ANSCENTER::ANSMJPEGClient** Handle, double intervalMs) {
if (Handle == nullptr || *Handle == nullptr) return;
try {
(*Handle)->SetTargetFPS(intervalMs);
} catch (...) { }
}
extern "C" __declspec(dllexport) void SetMJPEGNV12FastPath(ANSCENTER::ANSMJPEGClient** Handle, int enable) {
if (Handle == nullptr || *Handle == nullptr) return;
try {
(*Handle)->SetNV12FastPath(enable != 0);
} catch (...) { }
}
2026-03-28 16:54:11 +11:00
// ============================================================================
// V2 entry points — accept handle as uint64_t by value (LabVIEW safe)
// ============================================================================
extern "C" __declspec(dllexport) int GetMJPEGImage_V2(uint64_t handleVal, int& width, int& height, int64_t& timeStamp, LStrHandle jpegImage) {
auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(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 MJPEG client." << std::endl;
return 0;
}
}
catch (const std::exception& e) {
std::cerr << "Error getting MJPEG image: " << e.what() << std::endl;
return 0;
}
catch (...) {
std::cerr << "Error getting MJPEG image: Unknown exception." << std::endl;
return 0;
}
}
extern "C" __declspec(dllexport) int GetMJPEGCVImage_V2(uint64_t handleVal, int& width, int& height, int64_t& timeStamp, cv::Mat** image) {
auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return -1;
if (!image) return -1;
try {
cv::Mat img = h->GetImage(width, height, timeStamp);
if (img.empty()) {
return 0;
}
anscv_mat_replace(image, std::move(img));
// Attach NV12 frame for GPU fast-path inference (side-table registry)
int gpuIdx = h->GetHWDecodingGpuIndex();
AVFrame* cudaHW = h->GetCudaHWFrame();
if (cudaHW) {
AVFrame* cpuNV12 = h->GetNV12Frame();
gpu_frame_attach_cuda(*image, cudaHW, gpuIdx, timeStamp, cpuNV12);
} else {
AVFrame* nv12 = h->GetNV12Frame();
if (nv12) {
gpu_frame_attach(*image, nv12, gpuIdx, timeStamp);
}
}
return 1;
}
catch (const cv::Exception& e) {
std::cerr << "OpenCV exception in GetMJPEGCVImage_V2: " << e.what() << std::endl;
return -2;
}
catch (const std::exception& e) {
std::cerr << "Exception in GetMJPEGCVImage_V2: " << e.what() << std::endl;
return -2;
}
catch (...) {
std::cerr << "Unknown exception in GetMJPEGCVImage_V2" << std::endl;
return -2;
}
}
extern "C" __declspec(dllexport) int StartMJPEG_V2(uint64_t handleVal) {
auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(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 MJPEG client: " << e.what() << std::endl;
return 0;
}
catch (...) {
std::cerr << "Error starting MJPEG client: Unknown exception." << std::endl;
return 0;
}
}
extern "C" __declspec(dllexport) int ReconnectMJPEG_V2(uint64_t handleVal) {
auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(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 MJPEG client: " << e.what() << std::endl;
return 0;
}
catch (...) {
std::cerr << "Error reconnecting MJPEG client: Unknown exception." << std::endl;
return 0;
}
}
extern "C" __declspec(dllexport) int StopMJPEG_V2(uint64_t handleVal) {
auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(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 MJPEG client: " << e.what() << std::endl;
return 0;
}
catch (...) {
std::cerr << "Error stopping MJPEG client: Unknown exception." << std::endl;
return 0;
}
}
extern "C" __declspec(dllexport) int PauseMJPEG_V2(uint64_t handleVal) {
auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return -1;
try {
bool result = h->Pause();
if (result) return 1;
else return 0;
} catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int IsMJPEGPaused_V2(uint64_t handleVal) {
auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return -1;
try {
bool result = h->IsPaused();
if (result) return 1;
else return 0;
} catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int IsMJPEGRunning_V2(uint64_t handleVal) {
auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return -1;
try {
bool result = h->IsPlaying();
if (result) return 1;
else return 0;
} catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int IsMJPEGRecording_V2(uint64_t handleVal) {
auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return -1;
try {
bool result = h->IsRecording();
if (result) return 1;
else return 0;
} catch (...) { return -1; }
}
extern "C" __declspec(dllexport) void SetMJPEGAudioVolume_V2(uint64_t handleVal, int volume) {
auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return;
try {
h->SetAudioVolume(volume);
} catch (...) { }
}
extern "C" __declspec(dllexport) void EnableMJPEGAudioVolume_V2(uint64_t handleVal, int status) {
auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return;
try {
bool audioStatus = false;
if (status == 1) audioStatus = true;
h->EnableAudio(audioStatus);
} catch (...) { }
}
extern "C" __declspec(dllexport) void SetMJPEGImageRotation_V2(uint64_t handleVal, double rotationAngle) {
auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return;
try {
h->SetImageRotate(rotationAngle);
} catch (...) { }
}
extern "C" __declspec(dllexport) int SetBBoxMJPEG_V2(uint64_t handleVal, int x, int y, int width, int height) {
auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return -1;
try {
cv::Rect bbox(x, y, width, height);
h->SetBBox(bbox);
return 1;
} catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int SetCropFlagMJPEG_V2(uint64_t handleVal, int cropFlag) {
auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return -1;
try {
bool crop = false;
if (cropFlag == 1) crop = true;
h->SetCrop(crop);
return 1;
} catch (...) { return -1; }
}
extern "C" __declspec(dllexport) void SetMJPEGHWDecoding_V2(uint64_t handleVal, int hwMode, int preferredGpu) {
auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return;
try {
h->SetHWDecoding(hwMode, preferredGpu);
} catch (...) { }
}
extern "C" __declspec(dllexport) int IsMJPEGHWDecodingActive_V2(uint64_t handleVal) {
auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return -1;
try {
return h->IsHWDecodingActive() ? 1 : 0;
} catch (...) { return -1; }
}
extern "C" __declspec(dllexport) int GetMJPEGHWDecodingGpuIndex_V2(uint64_t handleVal) {
auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return -1;
try {
return h->GetHWDecodingGpuIndex();
} catch (...) { return -1; }
}
extern "C" __declspec(dllexport) void SetMJPEGImageQuality_V2(uint64_t handleVal, int mode) {
auto* h = reinterpret_cast<ANSCENTER::ANSMJPEGClient*>(handleVal); if (!h) return;
try {
h->SetImageQuality(mode);
} catch (...) { }
}