2026-03-28 16:54:11 +11:00
|
|
|
#include "ANSRTMP.h"
|
|
|
|
|
#include "ANSMatRegistry.h"
|
|
|
|
|
#include "ANSGpuFrameOps.h"
|
|
|
|
|
#include <memory>
|
|
|
|
|
#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 ANSRTMPClient::_mutex
|
|
|
|
|
// Mat registry thread safety is handled by anscv_mat_replace's internal registry_mutex
|
|
|
|
|
static bool ansrtmpLicenceValid = false;
|
|
|
|
|
// Global once_flag to protect license checking
|
|
|
|
|
static std::once_flag ansrtmpLicenseOnceFlag;
|
|
|
|
|
static std::once_flag rtmpHwDecoderAutoConfigOnceFlag;
|
|
|
|
|
namespace ANSCENTER {
|
|
|
|
|
ANSRTMPClient::ANSRTMPClient() {
|
|
|
|
|
_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 decode session limits automatically.
|
|
|
|
|
// No action needed from LabVIEW or any third-party caller.
|
|
|
|
|
std::call_once(rtmpHwDecoderAutoConfigOnceFlag, []() {
|
|
|
|
|
AutoConfigureHWDecoders(0);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
ANSRTMPClient::~ANSRTMPClient() noexcept {
|
|
|
|
|
Destroy();
|
|
|
|
|
}
|
|
|
|
|
void ANSRTMPClient::Destroy() {
|
2026-04-02 22:07:27 +11:00
|
|
|
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
|
|
|
}
|
2026-04-02 22:07:27 +11:00
|
|
|
clientToClose = std::move(_playerClient);
|
|
|
|
|
}
|
|
|
|
|
if (clientToClose) {
|
|
|
|
|
clientToClose->close();
|
2026-03-28 16:54:11 +11:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
static void VerifyGlobalANSRTMPLicense(const std::string& licenseKey) {
|
|
|
|
|
try {
|
|
|
|
|
ansrtmpLicenceValid = ANSCENTER::ANSLicenseHelper::LicenseVerification(licenseKey, 1007, "ANSCV");//Default productId=1005
|
|
|
|
|
if (!ansrtmpLicenceValid) { // we also support ANSTS license
|
|
|
|
|
ansrtmpLicenceValid = ANSCENTER::ANSLicenseHelper::LicenseVerification(licenseKey, 1003, "ANSVIS");//Default productId=1003 (ANSVIS)
|
|
|
|
|
}
|
|
|
|
|
if (!ansrtmpLicenceValid) { // we also support ANSTS license
|
|
|
|
|
ansrtmpLicenceValid = ANSCENTER::ANSLicenseHelper::LicenseVerification(licenseKey, 1008, "ANSTS");//Default productId=1008 (ANSTS)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
|
|
|
|
ansrtmpLicenceValid = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
void ANSRTMPClient::CheckLicense() {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
try {
|
|
|
|
|
// Check once globally
|
|
|
|
|
std::call_once(ansrtmpLicenseOnceFlag, [this]() {
|
|
|
|
|
VerifyGlobalANSRTMPLicense(_licenseKey);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Update this instance's local license flag
|
|
|
|
|
_licenseValid = ansrtmpLicenceValid;
|
|
|
|
|
}
|
|
|
|
|
catch (const std::exception& e) {
|
|
|
|
|
this->_logger.LogFatal("ANSRTMPClient::CheckLicense. Error:", e.what(), __FILE__, __LINE__);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
bool ANSRTMPClient::Init(std::string licenseKey, std::string url) {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
_licenseKey = licenseKey;
|
|
|
|
|
CheckLicense();
|
|
|
|
|
if (!_licenseValid) {
|
|
|
|
|
this->_logger.LogError("ANSRTMPClient::Init.", "Invalid license", __FILE__, __LINE__);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
network_init();
|
|
|
|
|
_url = url;
|
|
|
|
|
_useFullURL = true;
|
|
|
|
|
return Setup();
|
|
|
|
|
}
|
|
|
|
|
bool ANSRTMPClient::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("ANSRTMPClient::Init.", "Invalid license", __FILE__, __LINE__);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
//network_init();
|
|
|
|
|
_url = url;
|
|
|
|
|
_username = username;
|
|
|
|
|
_password = password;
|
|
|
|
|
_useFullURL = false;
|
|
|
|
|
return Setup();
|
|
|
|
|
}
|
|
|
|
|
bool ANSRTMPClient::Setup() {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
|
|
|
|
|
if (_useFullURL) {
|
|
|
|
|
return _playerClient->open(_url);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
return _playerClient->open(_username, _password, _url);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ANSRTMPClient::Reconnect() {
|
2026-04-02 22:07:27 +11:00
|
|
|
{
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
_isPlaying = false;
|
|
|
|
|
}
|
2026-03-28 16:54:11 +11:00
|
|
|
_playerClient->close();
|
2026-04-02 22:07:27 +11:00
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
2026-03-28 16:54:11 +11:00
|
|
|
Setup();
|
|
|
|
|
_isPlaying = _playerClient->play();
|
|
|
|
|
return _isPlaying;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ANSRTMPClient::Start() {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
Setup();
|
|
|
|
|
_isPlaying = _playerClient->play();
|
|
|
|
|
return _isPlaying;
|
|
|
|
|
}
|
|
|
|
|
bool ANSRTMPClient::Stop() {
|
2026-04-02 22:07:27 +11:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
void ANSRTMPClient::SetBBox(cv::Rect bbox) {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
_playerClient->setBbox(bbox);
|
|
|
|
|
}
|
|
|
|
|
void ANSRTMPClient::SetCrop(bool crop) {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
_playerClient->setCrop(crop);
|
|
|
|
|
}
|
|
|
|
|
bool ANSRTMPClient::Pause() {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
_isPlaying = false;
|
|
|
|
|
return _playerClient->pause();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ANSRTMPClient::IsPaused() {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
return _playerClient->isPaused();
|
|
|
|
|
}
|
|
|
|
|
bool ANSRTMPClient::IsPlaying() {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
return _playerClient->isPlaying();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ANSRTMPClient::IsRecording() {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
return _playerClient->isRecording();
|
|
|
|
|
}
|
|
|
|
|
std::string ANSRTMPClient::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 ANSRTMPClient::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 ANSRTMPClient::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;
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ANSRTMPClient::EnableAudio(bool status) {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
_playerClient->enableAudio(status);
|
|
|
|
|
}
|
|
|
|
|
void ANSRTMPClient::SetAudioVolume(int volume) {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
_playerClient->setVolume(volume);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ANSRTMPClient::SetMaxHWDecoders(int maxDecoders) {
|
|
|
|
|
if (maxDecoders >= 0) {
|
|
|
|
|
g_hw_decoder_max = static_cast<uint32>(maxDecoders);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Estimate max decode sessions from SM count and VRAM.
|
|
|
|
|
// NVDEC hardware cap: fixed-function ASIC with ~32 concurrent session limit.
|
|
|
|
|
// Exceeding this causes CUVID Error 205 → sticky CUDA context corruption.
|
|
|
|
|
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_RTMP() {
|
|
|
|
|
IDXGIFactory1* pFactory = nullptr;
|
|
|
|
|
HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&pFactory);
|
|
|
|
|
if (FAILED(hr) || !pFactory) {
|
|
|
|
|
g_hw_decoder_max = 4;
|
|
|
|
|
fprintf(stderr, "[HWDecode] RTMP 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] RTMP 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] RTMP 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] RTMP AutoConfigure: Total %d decode sessions across all GPUs\n", totalSessions);
|
|
|
|
|
return totalSessions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#elif defined(__linux__)
|
|
|
|
|
static int AutoConfigureHWDecoders_Platform_RTMP() {
|
|
|
|
|
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] RTMP 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] RTMP 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] RTMP AutoConfigure: Total %d VAAPI decode sessions across all GPUs\n", totalSessions);
|
|
|
|
|
return totalSessions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
static int AutoConfigureHWDecoders_Platform_RTMP() {
|
|
|
|
|
g_hw_decoder_max = 4;
|
|
|
|
|
fprintf(stderr, "[HWDecode] RTMP AutoConfigure: Unsupported platform, defaulting to %d sessions\n",
|
|
|
|
|
g_hw_decoder_max);
|
|
|
|
|
return g_hw_decoder_max;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
int ANSRTMPClient::AutoConfigureHWDecoders(int maxPerGpuOverride) {
|
|
|
|
|
int gpuCount = 0;
|
|
|
|
|
cudaError_t err = cudaGetDeviceCount(&gpuCount);
|
|
|
|
|
if (err != cudaSuccess || gpuCount <= 0) {
|
|
|
|
|
return AutoConfigureHWDecoders_Platform_RTMP();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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] RTMP 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 ANSRTMPClient::SetHWDecoding(int hwMode, int preferredGpu) {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
_playerClient->setHWDecoding(hwMode, preferredGpu);
|
|
|
|
|
}
|
|
|
|
|
bool ANSRTMPClient::IsHWDecodingActive() {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
return _playerClient->isHWDecodingActive();
|
|
|
|
|
}
|
|
|
|
|
int ANSRTMPClient::GetHWDecodingGpuIndex() {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
return _playerClient->getHWDecodingGpuIndex();
|
|
|
|
|
}
|
|
|
|
|
void ANSRTMPClient::SetDisplayResolution(int width, int height) {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
_displayWidth = width;
|
|
|
|
|
_displayHeight = height;
|
|
|
|
|
}
|
|
|
|
|
void ANSRTMPClient::SetImageQuality(int mode) {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
_playerClient->setImageQuality(mode); // 0=fast (AI), 1=quality (display)
|
|
|
|
|
}
|
|
|
|
|
AVFrame* ANSRTMPClient::GetNV12Frame() {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
return _playerClient->getNV12Frame(); // Returns clone, caller must av_frame_free
|
|
|
|
|
}
|
|
|
|
|
AVFrame* ANSRTMPClient::GetCudaHWFrame() {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
return _playerClient->getCudaHWFrame();
|
|
|
|
|
}
|
|
|
|
|
bool ANSRTMPClient::IsCudaHWAccel() {
|
|
|
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
|
|
|
return _playerClient->isCudaHWAccel();
|
|
|
|
|
}
|
|
|
|
|
std::string ANSRTMPClient::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("ANSRTMPClient::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("ANSRTMPClient::MatToBinaryData. Error:", e.what(), __FILE__, __LINE__);
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
this->_logger.LogFatal("ANSRTMPClient::MatToBinaryData. Error:", "Caught unknown exception!", __FILE__, __LINE__);
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else return "";
|
|
|
|
|
}
|
|
|
|
|
else return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) int CreateANSRTMPHandle(ANSCENTER::ANSRTMPClient * *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::ANSRTMPClient>();
|
|
|
|
|
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::ANSRTMPClient*>(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 ReleaseANSRTMPHandle(ANSCENTER::ANSRTMPClient * *Handle) {
|
|
|
|
|
if (Handle == nullptr || *Handle == nullptr) return -1;
|
|
|
|
|
try {
|
|
|
|
|
extern void anscv_unregister_handle(void*);
|
|
|
|
|
anscv_unregister_handle(*Handle);
|
|
|
|
|
// unique_ptr destructor calls ~ANSRTMPClient which calls Destroy() — no need to call Destroy() separately
|
|
|
|
|
std::unique_ptr<ANSCENTER::ANSRTMPClient> ptr(*Handle);
|
|
|
|
|
*Handle = nullptr;
|
|
|
|
|
return 0;
|
|
|
|
|
} catch (...) {
|
|
|
|
|
if (Handle) *Handle = nullptr;
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
extern "C" __declspec(dllexport) int GetRTMPtrImage(ANSCENTER::ANSRTMPClient * *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 RTMP image: " << e.what() << std::endl;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
std::cerr << "Error getting RTMP image: Unknown exception." << std::endl;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
extern "C" __declspec(dllexport) int GetRTMPImage(ANSCENTER::ANSRTMPClient * *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 RTMP client." << std::endl;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (const std::exception& e) {
|
|
|
|
|
std::cerr << "Error getting RTMP image: " << e.what() << std::endl;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
std::cerr << "Error getting RTMP image: Unknown exception." << std::endl;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) int GetRTMPCVImage(ANSCENTER::ANSRTMPClient** 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 GetRTMPCVImage" << 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
|
|
|
|
|
int gpuIdx = (*Handle)->GetHWDecodingGpuIndex();
|
|
|
|
|
AVFrame* cudaHW = (*Handle)->GetCudaHWFrame();
|
|
|
|
|
if (cudaHW) {
|
|
|
|
|
// CUDA zero-copy: frame data[0]/data[1] are CUDA device pointers.
|
|
|
|
|
// Also attach CPU NV12 as fallback for cross-GPU inference
|
|
|
|
|
// (when decode GPU != inference GPU, CUDA ptrs aren't accessible).
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 1; // Success
|
|
|
|
|
}
|
|
|
|
|
catch (const cv::Exception& e) {
|
|
|
|
|
std::cerr << "OpenCV exception in GetRTMPCVImage: " << e.what() << std::endl;
|
|
|
|
|
return -2;
|
|
|
|
|
}
|
|
|
|
|
catch (const std::exception& e) {
|
|
|
|
|
std::cerr << "Exception in GetRTMPCVImage: " << e.what() << std::endl;
|
|
|
|
|
return -2;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
std::cerr << "Unknown exception in GetRTMPCVImage" << std::endl;
|
|
|
|
|
return -2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
extern "C" __declspec(dllexport) int StartRTMP(ANSCENTER::ANSRTMPClient * *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 RTMP client: " << e.what() << std::endl;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
std::cerr << "Error starting RTMP client: Unknown exception." << std::endl;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
extern "C" __declspec(dllexport) int ReconnectRTMP(ANSCENTER::ANSRTMPClient * *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 RTMP client: " << e.what() << std::endl;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
std::cerr << "Error reconnecting RTMP client: Unknown exception." << std::endl;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
extern "C" __declspec(dllexport) int StopRTMP(ANSCENTER::ANSRTMPClient * *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 RTMP client: " << e.what() << std::endl;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
std::cerr << "Error stopping RTMP client: Unknown exception." << std::endl;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
extern "C" __declspec(dllexport) int PauseRTMP(ANSCENTER::ANSRTMPClient** 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 IsRTMPPaused(ANSCENTER::ANSRTMPClient * *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 IsRTMPRunning(ANSCENTER::ANSRTMPClient * *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 IsRTMPRecording(ANSCENTER::ANSRTMPClient * *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 SetRTMPAudioVolume(ANSCENTER::ANSRTMPClient * *Handle, int volume) {
|
|
|
|
|
if (Handle == nullptr || *Handle == nullptr) return;
|
|
|
|
|
try {
|
|
|
|
|
(*Handle)->SetAudioVolume(volume);
|
|
|
|
|
} catch (...) { }
|
|
|
|
|
}
|
|
|
|
|
extern "C" __declspec(dllexport) void EnableRTMPAudioVolume(ANSCENTER::ANSRTMPClient * *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 SetRTMPImageRotation(ANSCENTER::ANSRTMPClient * *Handle, double rotationAngle) {
|
|
|
|
|
if (Handle == nullptr || *Handle == nullptr) return;
|
|
|
|
|
try {
|
|
|
|
|
(*Handle)->SetImageRotate(rotationAngle);
|
|
|
|
|
} catch (...) { }
|
|
|
|
|
}
|
|
|
|
|
extern "C" __declspec(dllexport) int SetBBoxRTMP(ANSCENTER::ANSRTMPClient** 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 SetCropFlagRTMP(ANSCENTER::ANSRTMPClient** 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 SetRTMPMaxHWDecoders(int maxDecoders) {
|
|
|
|
|
ANSCENTER::ANSRTMPClient::SetMaxHWDecoders(maxDecoders);
|
|
|
|
|
}
|
|
|
|
|
extern "C" __declspec(dllexport) int AutoConfigureRTMPHWDecoders(int maxPerGpuOverride) {
|
|
|
|
|
return ANSCENTER::ANSRTMPClient::AutoConfigureHWDecoders(maxPerGpuOverride);
|
|
|
|
|
}
|
|
|
|
|
extern "C" __declspec(dllexport) void SetRTMPHWDecoding(ANSCENTER::ANSRTMPClient** Handle, int hwMode, int preferredGpu) {
|
|
|
|
|
if (Handle == nullptr || *Handle == nullptr) return;
|
|
|
|
|
try {
|
|
|
|
|
(*Handle)->SetHWDecoding(hwMode, preferredGpu);
|
|
|
|
|
} catch (...) { }
|
|
|
|
|
}
|
|
|
|
|
extern "C" __declspec(dllexport) int IsRTMPHWDecodingActive(ANSCENTER::ANSRTMPClient** Handle) {
|
|
|
|
|
if (Handle == nullptr || *Handle == nullptr) return -1;
|
|
|
|
|
try {
|
|
|
|
|
return (*Handle)->IsHWDecodingActive() ? 1 : 0;
|
|
|
|
|
} catch (...) { return -1; }
|
|
|
|
|
}
|
|
|
|
|
extern "C" __declspec(dllexport) int GetRTMPHWDecodingGpuIndex(ANSCENTER::ANSRTMPClient** Handle) {
|
|
|
|
|
if (Handle == nullptr || *Handle == nullptr) return -1;
|
|
|
|
|
try {
|
|
|
|
|
return (*Handle)->GetHWDecodingGpuIndex();
|
|
|
|
|
} catch (...) { return -1; }
|
|
|
|
|
}
|
|
|
|
|
extern "C" __declspec(dllexport) void SetRTMPImageQuality(ANSCENTER::ANSRTMPClient** Handle, int mode) {
|
|
|
|
|
if (Handle == nullptr || *Handle == nullptr) return;
|
|
|
|
|
try {
|
|
|
|
|
(*Handle)->SetImageQuality(mode);
|
|
|
|
|
} catch (...) { }
|
|
|
|
|
}
|
|
|
|
|
extern "C" __declspec(dllexport) void SetRTMPDisplayResolution(ANSCENTER::ANSRTMPClient** Handle, int width, int height) {
|
|
|
|
|
if (Handle == nullptr || *Handle == nullptr) return;
|
|
|
|
|
try {
|
|
|
|
|
(*Handle)->SetDisplayResolution(width, height);
|
|
|
|
|
} catch (...) { }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// V2 entry points: accept handle by value (uint64_t) to avoid LabVIEW
|
|
|
|
|
// double-pointer buffer reuse bug in concurrent calls.
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) int GetRTMPImage_V2(uint64_t handleVal, int& width, int& height, int64_t& timeStamp, LStrHandle jpegImage) {
|
|
|
|
|
auto* h = reinterpret_cast<ANSCENTER::ANSRTMPClient*>(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 RTMP client." << std::endl;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (const std::exception& e) {
|
|
|
|
|
std::cerr << "Error getting RTMP image: " << e.what() << std::endl;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
std::cerr << "Error getting RTMP image: Unknown exception." << std::endl;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) int GetRTMPCVImage_V2(uint64_t handleVal, int& width, int& height, int64_t& timeStamp, cv::Mat** image) {
|
|
|
|
|
auto* h = reinterpret_cast<ANSCENTER::ANSRTMPClient*>(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 GetRTMPCVImage_V2: " << e.what() << std::endl;
|
|
|
|
|
return -2;
|
|
|
|
|
}
|
|
|
|
|
catch (const std::exception& e) {
|
|
|
|
|
std::cerr << "Exception in GetRTMPCVImage_V2: " << e.what() << std::endl;
|
|
|
|
|
return -2;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
std::cerr << "Unknown exception in GetRTMPCVImage_V2" << std::endl;
|
|
|
|
|
return -2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) int StartRTMP_V2(uint64_t handleVal) {
|
|
|
|
|
auto* h = reinterpret_cast<ANSCENTER::ANSRTMPClient*>(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 RTMP client: " << e.what() << std::endl;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
std::cerr << "Error starting RTMP client: Unknown exception." << std::endl;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) int ReconnectRTMP_V2(uint64_t handleVal) {
|
|
|
|
|
auto* h = reinterpret_cast<ANSCENTER::ANSRTMPClient*>(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 RTMP client: " << e.what() << std::endl;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
std::cerr << "Error reconnecting RTMP client: Unknown exception." << std::endl;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) int StopRTMP_V2(uint64_t handleVal) {
|
|
|
|
|
auto* h = reinterpret_cast<ANSCENTER::ANSRTMPClient*>(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 RTMP client: " << e.what() << std::endl;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
|
|
|
|
std::cerr << "Error stopping RTMP client: Unknown exception." << std::endl;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) int PauseRTMP_V2(uint64_t handleVal) {
|
|
|
|
|
auto* h = reinterpret_cast<ANSCENTER::ANSRTMPClient*>(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 IsRTMPPaused_V2(uint64_t handleVal) {
|
|
|
|
|
auto* h = reinterpret_cast<ANSCENTER::ANSRTMPClient*>(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 IsRTMPRunning_V2(uint64_t handleVal) {
|
|
|
|
|
auto* h = reinterpret_cast<ANSCENTER::ANSRTMPClient*>(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 IsRTMPRecording_V2(uint64_t handleVal) {
|
|
|
|
|
auto* h = reinterpret_cast<ANSCENTER::ANSRTMPClient*>(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 SetRTMPAudioVolume_V2(uint64_t handleVal, int volume) {
|
|
|
|
|
auto* h = reinterpret_cast<ANSCENTER::ANSRTMPClient*>(handleVal); if (!h) return;
|
|
|
|
|
try {
|
|
|
|
|
h->SetAudioVolume(volume);
|
|
|
|
|
} catch (...) { }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) void EnableRTMPAudioVolume_V2(uint64_t handleVal, int status) {
|
|
|
|
|
auto* h = reinterpret_cast<ANSCENTER::ANSRTMPClient*>(handleVal); if (!h) return;
|
|
|
|
|
try {
|
|
|
|
|
bool audioStatus = false;
|
|
|
|
|
if (status == 1) audioStatus = true;
|
|
|
|
|
h->EnableAudio(audioStatus);
|
|
|
|
|
} catch (...) { }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) void SetRTMPImageRotation_V2(uint64_t handleVal, double rotationAngle) {
|
|
|
|
|
auto* h = reinterpret_cast<ANSCENTER::ANSRTMPClient*>(handleVal); if (!h) return;
|
|
|
|
|
try {
|
|
|
|
|
h->SetImageRotate(rotationAngle);
|
|
|
|
|
} catch (...) { }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) int SetBBoxRTMP_V2(uint64_t handleVal, int x, int y, int width, int height) {
|
|
|
|
|
auto* h = reinterpret_cast<ANSCENTER::ANSRTMPClient*>(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 SetCropFlagRTMP_V2(uint64_t handleVal, int cropFlag) {
|
|
|
|
|
auto* h = reinterpret_cast<ANSCENTER::ANSRTMPClient*>(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 SetRTMPHWDecoding_V2(uint64_t handleVal, int hwMode, int preferredGpu) {
|
|
|
|
|
auto* h = reinterpret_cast<ANSCENTER::ANSRTMPClient*>(handleVal); if (!h) return;
|
|
|
|
|
try {
|
|
|
|
|
h->SetHWDecoding(hwMode, preferredGpu);
|
|
|
|
|
} catch (...) { }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) int IsRTMPHWDecodingActive_V2(uint64_t handleVal) {
|
|
|
|
|
auto* h = reinterpret_cast<ANSCENTER::ANSRTMPClient*>(handleVal); if (!h) return -1;
|
|
|
|
|
try {
|
|
|
|
|
return h->IsHWDecodingActive() ? 1 : 0;
|
|
|
|
|
} catch (...) { return -1; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) int GetRTMPHWDecodingGpuIndex_V2(uint64_t handleVal) {
|
|
|
|
|
auto* h = reinterpret_cast<ANSCENTER::ANSRTMPClient*>(handleVal); if (!h) return -1;
|
|
|
|
|
try {
|
|
|
|
|
return h->GetHWDecodingGpuIndex();
|
|
|
|
|
} catch (...) { return -1; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) void SetRTMPImageQuality_V2(uint64_t handleVal, int mode) {
|
|
|
|
|
auto* h = reinterpret_cast<ANSCENTER::ANSRTMPClient*>(handleVal); if (!h) return;
|
|
|
|
|
try {
|
|
|
|
|
h->SetImageQuality(mode);
|
|
|
|
|
} catch (...) { }
|
|
|
|
|
}
|