763 lines
24 KiB
C++
763 lines
24 KiB
C++
|
|
#include "ANSFilePlayer_CV.h"
|
|||
|
|
#include "ANSMatRegistry.h"
|
|||
|
|
#include <memory>
|
|||
|
|
#include <cstdint>
|
|||
|
|
#include <libswscale/swscale.h>
|
|||
|
|
#include <libavutil/imgutils.h>
|
|||
|
|
#include <libavutil/frame.h>
|
|||
|
|
#include "media_codec.h"
|
|||
|
|
static bool ansfileplayerLicenceValid = false;
|
|||
|
|
// Global once_flag to protect license checking
|
|||
|
|
static std::once_flag ansfileplayerLicenseOnceFlag;
|
|||
|
|
namespace ANSCENTER {
|
|||
|
|
#define CHECK_CUDA(call) do { \
|
|||
|
|
cudaError_t err = call; \
|
|||
|
|
if (err != cudaSuccess) { \
|
|||
|
|
std::cout<<"CUDA error: " + std::string(cudaGetErrorString(err)); \
|
|||
|
|
} \
|
|||
|
|
} while (0)
|
|||
|
|
#define CHECK_NVJPEG(call) do { \
|
|||
|
|
nvjpegStatus_t status = call; \
|
|||
|
|
if (status != NVJPEG_STATUS_SUCCESS) { \
|
|||
|
|
std::cout <<"nvJPEG error: " + std::to_string(status); \
|
|||
|
|
} \
|
|||
|
|
} while (0)
|
|||
|
|
|
|||
|
|
ANSFILEPLAYER_CV::ANSFILEPLAYER_CV() :_stopThread(std::make_shared<std::atomic<bool>>(false))
|
|||
|
|
{
|
|||
|
|
_resWidth = 0;
|
|||
|
|
_resHeight = 0;
|
|||
|
|
m_bPaused = false;
|
|||
|
|
_crop = false;
|
|||
|
|
_isPlaying = false;
|
|||
|
|
_lastJpegImage = "";
|
|||
|
|
_bbox = cv::Rect(0, 0, 0, 0);
|
|||
|
|
_jpegCompressor = nullptr;
|
|||
|
|
_previousPTS = 0;
|
|||
|
|
_totalFrames = 0;
|
|||
|
|
}
|
|||
|
|
ANSFILEPLAYER_CV::~ANSFILEPLAYER_CV() noexcept {
|
|||
|
|
try {
|
|||
|
|
StopTimerThread();
|
|||
|
|
Destroy();
|
|||
|
|
}
|
|||
|
|
catch (...) {}
|
|||
|
|
}
|
|||
|
|
void ANSFILEPLAYER_CV::Destroy() {
|
|||
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|||
|
|
try {
|
|||
|
|
_previousImage.release();
|
|||
|
|
_lastJpegImage = "";
|
|||
|
|
_isPlaying = false;
|
|||
|
|
_resWidth = 0;
|
|||
|
|
_resHeight = 0;
|
|||
|
|
_currentFrame = 0;
|
|||
|
|
_previousPTS = 0;
|
|||
|
|
if (cap.isOpened()) {
|
|||
|
|
cap.release();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (const std::exception& e) {
|
|||
|
|
_logger.LogError("ANSFILEPLAYER_CV::Destroy. Exception:", e.what(), __FILE__, __LINE__);
|
|||
|
|
}
|
|||
|
|
catch (...) {
|
|||
|
|
_logger.LogError("ANSFILEPLAYER_CV::Destroy.", "Unknown exception", __FILE__, __LINE__);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void VerifyGlobalANSFPLicense(const std::string& licenseKey) {
|
|||
|
|
try {
|
|||
|
|
ansfileplayerLicenceValid = ANSCENTER::ANSLicenseHelper::LicenseVerification(licenseKey, 1007, "ANSCV");//Default productId=1005
|
|||
|
|
if (!ansfileplayerLicenceValid) { // we also support ANSTS license
|
|||
|
|
ansfileplayerLicenceValid = ANSCENTER::ANSLicenseHelper::LicenseVerification(licenseKey, 1003, "ANSVIS");//Default productId=1003 (ANSVIS)
|
|||
|
|
}
|
|||
|
|
if (!ansfileplayerLicenceValid) { // we also support ANSTS license
|
|||
|
|
ansfileplayerLicenceValid = ANSCENTER::ANSLicenseHelper::LicenseVerification(licenseKey, 1008, "ANSTS");//Default productId=1008 (ANSTS)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (std::exception& e) {
|
|||
|
|
ansfileplayerLicenceValid = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
void ANSFILEPLAYER_CV::CheckLicense() {
|
|||
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|||
|
|
try {
|
|||
|
|
// Check once globally
|
|||
|
|
std::call_once(ansfileplayerLicenseOnceFlag, [this]() {
|
|||
|
|
VerifyGlobalANSFPLicense(_licenseKey);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Update this instance's local license flag
|
|||
|
|
_licenseValid = ansfileplayerLicenceValid;
|
|||
|
|
}
|
|||
|
|
catch (const std::exception& e) {
|
|||
|
|
this->_logger.LogFatal("ANSFILEPLAYER_CV::CheckLicense. Error:", e.what(), __FILE__, __LINE__);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
bool ANSFILEPLAYER_CV::Init(std::string licenseKey, std::string url) {
|
|||
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|||
|
|
_licenseKey = licenseKey;
|
|||
|
|
CheckLicense();
|
|||
|
|
if (!_licenseValid) {
|
|||
|
|
_logger.LogError("ANSFILEPLAYER_CV::Init.", "Invalid license", __FILE__, __LINE__);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
_url = url;
|
|||
|
|
return Setup();
|
|||
|
|
}
|
|||
|
|
bool ANSFILEPLAYER_CV::Setup() {
|
|||
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|||
|
|
_currentFrame = 0;
|
|||
|
|
if (cap.isOpened()) return true;
|
|||
|
|
cap.open(_url, cv::CAP_FFMPEG); // USe FFMPEG for better codec support
|
|||
|
|
if (!cap.isOpened()) {
|
|||
|
|
cap.open(_url, cv::CAP_ANY);
|
|||
|
|
if (!cap.isOpened())return false;
|
|||
|
|
}
|
|||
|
|
try {
|
|||
|
|
_resWidth = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_WIDTH));
|
|||
|
|
_resHeight = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_HEIGHT));
|
|||
|
|
_totalFrames = static_cast<int64_t>(cap.get(cv::CAP_PROP_FRAME_COUNT));
|
|||
|
|
_fps = cap.get(cv::CAP_PROP_FPS);
|
|||
|
|
cap.set(cv::CAP_PROP_POS_FRAMES, _currentFrame);
|
|||
|
|
if (_fps > 0) {
|
|||
|
|
if (_timerThread && _timerThread->joinable()) return true; // Exit if the thread is already running
|
|||
|
|
int interval_ms = static_cast<int>(1000 / _fps);
|
|||
|
|
*_stopThread = false;
|
|||
|
|
_timerThread = std::make_shared<std::thread>(&ANSFILEPLAYER_CV::StartTimer, this, _stopThread, interval_ms);
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
catch (std::exception& e) {
|
|||
|
|
this->_logger.LogError("ANSFILEPLAYER_CV::Setup:", e.what(), __FILE__, __LINE__);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
void ANSFILEPLAYER_CV::StartTimer(std::shared_ptr<std::atomic<bool>> stopFlag, int interval_ms) {
|
|||
|
|
while (!stopFlag->load()) {
|
|||
|
|
std::unique_lock<std::mutex> lock(_cvMutex);
|
|||
|
|
// Wait for the specified interval or until stopFlag is set
|
|||
|
|
if (_cv.wait_for(lock, std::chrono::milliseconds(interval_ms), [&]() {
|
|||
|
|
return stopFlag->load();
|
|||
|
|
})) {
|
|||
|
|
break; // Exit the loop if stopFlag is true
|
|||
|
|
}
|
|||
|
|
lock.unlock(); // Unlock to avoid deadlocks in further operations
|
|||
|
|
int delayMs = 0;
|
|||
|
|
int actualSleepTime = interval_ms;
|
|||
|
|
{
|
|||
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|||
|
|
auto start = std::chrono::system_clock::now();
|
|||
|
|
if (_isPlaying) {
|
|||
|
|
try {
|
|||
|
|
_currentFrame = static_cast<int64_t>(cap.get(cv::CAP_PROP_POS_FRAMES));
|
|||
|
|
// Add check for _stopThread before blocking operations
|
|||
|
|
if (!cap.grab() || _currentFrame >= _totalFrames - 1) {
|
|||
|
|
if (cap.isOpened()) {
|
|||
|
|
cap.release();
|
|||
|
|
cap.open(_url, cv::CAP_FFMPEG); // USe FFMPEG for better codec support
|
|||
|
|
if (!cap.isOpened()) {
|
|||
|
|
cap.open(_url, cv::CAP_ANY);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (cap.isOpened()) {
|
|||
|
|
cap.set(cv::CAP_PROP_POS_FRAMES, 0);
|
|||
|
|
_currentFrame = 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (const std::exception& e) {
|
|||
|
|
this->_logger.LogError("ANSFILEPLAYER_CV::StartTimer. Exception occurred:", e.what(), __FILE__, __LINE__);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
auto end = std::chrono::system_clock::now();
|
|||
|
|
delayMs = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
|
|||
|
|
// Calculate actual sleep time based on any delay introduced
|
|||
|
|
actualSleepTime = interval_ms - delayMs;
|
|||
|
|
if (actualSleepTime < 1) actualSleepTime = 1;
|
|||
|
|
}
|
|||
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(actualSleepTime));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
void ANSFILEPLAYER_CV::StopTimerThread() {
|
|||
|
|
*_stopThread = true; // Signal to stop the thread
|
|||
|
|
_cv.notify_all(); // Notify the condition variable to unblock the thread
|
|||
|
|
if (_timerThread && _timerThread->joinable()) {
|
|||
|
|
_timerThread->join(); // Join the timer thread to ensure cleanup
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
bool ANSFILEPLAYER_CV::Reconnect() {
|
|||
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|||
|
|
try {
|
|||
|
|
_currentFrame = 0;
|
|||
|
|
if (cap.isOpened()) {
|
|||
|
|
cap.release();
|
|||
|
|
}
|
|||
|
|
Setup();
|
|||
|
|
_isPlaying = cap.isOpened() && cap.grab();
|
|||
|
|
return _isPlaying;
|
|||
|
|
}
|
|||
|
|
catch (const std::exception& e) {
|
|||
|
|
this->_logger.LogError("ANSFILEPLAYER_CV::Reconnect. Exception occurred:", e.what(), __FILE__, __LINE__);
|
|||
|
|
_currentFrame = 0;
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
bool ANSFILEPLAYER_CV::Start() {
|
|||
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|||
|
|
try {
|
|||
|
|
if (!cap.isOpened()) {
|
|||
|
|
cap.open(_url, cv::CAP_FFMPEG);
|
|||
|
|
if (!cap.isOpened()) {
|
|||
|
|
cap.open(_url, cv::CAP_ANY);
|
|||
|
|
}
|
|||
|
|
if (!cap.isOpened()) {
|
|||
|
|
this->_logger.LogError("ANSFILEPLAYER_CV::Start. Exception occurred:", "Failed to open video source: ", __FILE__, __LINE__);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
try {
|
|||
|
|
if (!cap.set(cv::CAP_PROP_POS_FRAMES, _currentFrame)) {
|
|||
|
|
this->_logger.LogError("ANSFILEPLAYER_CV::Start. Exception occurred:", "Warning: Unable to seek to frame", __FILE__, __LINE__);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (const std::exception& e) {
|
|||
|
|
this->_logger.LogError("ANSFILEPLAYER_CV::Start. Exception occurred:", e.what(), __FILE__, __LINE__);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
_isPlaying = cap.isOpened() && cap.grab();
|
|||
|
|
return _isPlaying;
|
|||
|
|
}
|
|||
|
|
catch (const std::exception& e) {
|
|||
|
|
this->_logger.LogError("ANSFILEPLAYER_CV::Start. Exception occurred:", e.what(), __FILE__, __LINE__);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
bool ANSFILEPLAYER_CV::Stop() {
|
|||
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|||
|
|
try {
|
|||
|
|
if (cap.isOpened()) {
|
|||
|
|
try {
|
|||
|
|
double frame_pos = cap.get(cv::CAP_PROP_POS_FRAMES);
|
|||
|
|
if (frame_pos >= 0) {
|
|||
|
|
_currentFrame = static_cast<int64_t>(frame_pos);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
_currentFrame = 0;
|
|||
|
|
this->_logger.LogError("ANSFILEPLAYER_CV::Stop. Exception occurred:", "Unable to retrieve current frame position", __FILE__, __LINE__);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (const std::exception& e) {
|
|||
|
|
this->_logger.LogError("ANSFILEPLAYER_CV::Stop. Exception occurred:", e.what(), __FILE__, __LINE__);
|
|||
|
|
_currentFrame = 0;
|
|||
|
|
}
|
|||
|
|
cap.release();
|
|||
|
|
}
|
|||
|
|
_isPlaying = false;
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
catch (const std::exception& e) {
|
|||
|
|
this->_logger.LogError("ANSFILEPLAYER_CV::Stop. Exception occurred:", e.what(), __FILE__, __LINE__);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
void ANSFILEPLAYER_CV::SetBBox(cv::Rect bbox) {
|
|||
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|||
|
|
_bbox = bbox;
|
|||
|
|
}
|
|||
|
|
void ANSFILEPLAYER_CV::SetCrop(bool crop) {
|
|||
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|||
|
|
_crop = crop;
|
|||
|
|
}
|
|||
|
|
bool ANSFILEPLAYER_CV::IsPaused() {
|
|||
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|||
|
|
return !cap.isOpened();
|
|||
|
|
}
|
|||
|
|
bool ANSFILEPLAYER_CV::IsPlaying() {
|
|||
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|||
|
|
return cap.isOpened();
|
|||
|
|
}
|
|||
|
|
bool ANSFILEPLAYER_CV::IsRecording() {
|
|||
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|||
|
|
return false;// do not support recording for webcam
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
cv::Mat ANSFILEPLAYER_CV::GetImage(int& width, int& height, int64_t& pts) {
|
|||
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|||
|
|
|
|||
|
|
if (!_isPlaying) {
|
|||
|
|
if (!_previousImage.empty()) {
|
|||
|
|
width = _previousImage.cols;
|
|||
|
|
height = _previousImage.rows;
|
|||
|
|
pts = _previousPTS;
|
|||
|
|
return _previousImage;
|
|||
|
|
}
|
|||
|
|
return cv::Mat();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
if (!cap.isOpened()) {
|
|||
|
|
if (!_previousImage.empty()) {
|
|||
|
|
width = _previousImage.cols;
|
|||
|
|
height = _previousImage.rows;
|
|||
|
|
pts = _previousPTS;
|
|||
|
|
return _previousImage;
|
|||
|
|
}
|
|||
|
|
return cv::Mat();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
cv::Mat frame;
|
|||
|
|
if (!cap.read(frame) || frame.empty()) {
|
|||
|
|
// Return last good frame if available
|
|||
|
|
if (!_previousImage.empty()) {
|
|||
|
|
width = _previousImage.cols;
|
|||
|
|
height = _previousImage.rows;
|
|||
|
|
pts = _previousPTS;
|
|||
|
|
return _previousImage;
|
|||
|
|
}
|
|||
|
|
return cv::Mat();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
cv::Mat result;
|
|||
|
|
|
|||
|
|
// Apply cropping if enabled
|
|||
|
|
if (_crop) {
|
|||
|
|
// Validate and clamp crop region
|
|||
|
|
_bbox.x = std::clamp(_bbox.x, 0, frame.cols - 1);
|
|||
|
|
_bbox.y = std::clamp(_bbox.y, 0, frame.rows - 1);
|
|||
|
|
_bbox.width = std::min(_bbox.width, frame.cols - _bbox.x);
|
|||
|
|
_bbox.height = std::min(_bbox.height, frame.rows - _bbox.y);
|
|||
|
|
|
|||
|
|
if (_bbox.width > 0 && _bbox.height > 0) {
|
|||
|
|
// CRITICAL: Clone to avoid dangling reference
|
|||
|
|
result = frame(_bbox).clone();
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
result = frame.clone();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
result = frame.clone();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Apply rotation if specified
|
|||
|
|
if (_imageRotateDeg > 0) {
|
|||
|
|
// Fast path for 90-degree rotations
|
|||
|
|
if (std::abs(_imageRotateDeg - 90.0) < 0.01) {
|
|||
|
|
cv::rotate(result, result, cv::ROTATE_90_CLOCKWISE);
|
|||
|
|
}
|
|||
|
|
else if (std::abs(_imageRotateDeg - 180.0) < 0.01) {
|
|||
|
|
cv::rotate(result, result, cv::ROTATE_180);
|
|||
|
|
}
|
|||
|
|
else if (std::abs(_imageRotateDeg - 270.0) < 0.01) {
|
|||
|
|
cv::rotate(result, result, cv::ROTATE_90_COUNTERCLOCKWISE);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
// Arbitrary angle rotation
|
|||
|
|
const cv::Point2f center(result.cols / 2.0f, result.rows / 2.0f);
|
|||
|
|
cv::Mat rotationMatrix = cv::getRotationMatrix2D(center, _imageRotateDeg, 1.0);
|
|||
|
|
|
|||
|
|
cv::Mat rotated;
|
|||
|
|
// Use INTER_LINEAR instead of INTER_CUBIC (2-3x faster, minimal quality loss)
|
|||
|
|
cv::warpAffine(result, rotated, rotationMatrix, result.size(),
|
|||
|
|
cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar());
|
|||
|
|
result = rotated;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Update PTS (presentation timestamp)
|
|||
|
|
if (_previousPTS < INT64_MAX) {
|
|||
|
|
_previousPTS++;
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
_previousPTS = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Update cached frame
|
|||
|
|
_previousImage = result;
|
|||
|
|
|
|||
|
|
// Set output parameters
|
|||
|
|
width = result.cols;
|
|||
|
|
height = result.rows;
|
|||
|
|
pts = _previousPTS;
|
|||
|
|
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
catch (const cv::Exception& e) {
|
|||
|
|
this->_logger.LogError("ANSFILEPLAYER_CV::GetImage. OpenCV exception:", e.what(), __FILE__, __LINE__);
|
|||
|
|
}
|
|||
|
|
catch (const std::exception& e) {
|
|||
|
|
this->_logger.LogError("ANSFILEPLAYER_CV::GetImage. Exception:", e.what(), __FILE__, __LINE__);
|
|||
|
|
}
|
|||
|
|
catch (...) {
|
|||
|
|
this->_logger.LogError("ANSFILEPLAYER_CV::GetImage. Unknown exception", "", __FILE__, __LINE__);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return cv::Mat();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void ANSFILEPLAYER_CV::EnableAudio(bool status) {
|
|||
|
|
// please support audio enable for webcam
|
|||
|
|
}
|
|||
|
|
void ANSFILEPLAYER_CV::SetAudioVolume(int volume) {
|
|||
|
|
// support audio volumne
|
|||
|
|
}
|
|||
|
|
std::string ANSFILEPLAYER_CV::encodeJpegString(const cv::Mat& img, int quality) {
|
|||
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|||
|
|
if (!_isPlaying) return _lastJpegImage;
|
|||
|
|
unsigned char* jpegBuf = nullptr;
|
|||
|
|
tjhandle jpegCompressor = nullptr;
|
|||
|
|
try {
|
|||
|
|
// Initialize TurboJPEG compressor
|
|||
|
|
jpegCompressor = tjInitCompress();
|
|||
|
|
if (!jpegCompressor) {
|
|||
|
|
this->_logger.LogError("Failed to initialize TurboJPEG compressor.", tjGetErrorStr(), __FILE__, __LINE__);
|
|||
|
|
return _lastJpegImage;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int maxBufferSize = img.cols * img.rows * 3;
|
|||
|
|
jpegBuf = tjAlloc(maxBufferSize);
|
|||
|
|
if (!jpegBuf) {
|
|||
|
|
this->_logger.LogError("Failed to allocate memory for JPEG buffer.", tjGetErrorStr(), __FILE__, __LINE__);
|
|||
|
|
tjDestroy(jpegCompressor);
|
|||
|
|
return _lastJpegImage;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
long unsigned int jpegSize = maxBufferSize;
|
|||
|
|
int subsamp = TJSAMP_444;
|
|||
|
|
int pixelFormat = img.channels() == 3 ? TJPF_BGR : TJPF_GRAY;
|
|||
|
|
|
|||
|
|
// Compress the image
|
|||
|
|
if (tjCompress2(jpegCompressor, img.data, img.cols, 0, img.rows, pixelFormat,
|
|||
|
|
&jpegBuf, &jpegSize, subsamp, quality, TJFLAG_FASTDCT) != 0) {
|
|||
|
|
this->_logger.LogError("Compression error:", tjGetErrorStr(), __FILE__, __LINE__);
|
|||
|
|
tjFree(jpegBuf);
|
|||
|
|
tjDestroy(jpegCompressor);
|
|||
|
|
return _lastJpegImage;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Create string from JPEG buffer
|
|||
|
|
std::string jpegString(reinterpret_cast<char*>(jpegBuf), jpegSize);
|
|||
|
|
_lastJpegImage = jpegString;
|
|||
|
|
}
|
|||
|
|
catch (const std::exception& e) {
|
|||
|
|
this->_logger.LogError("Exception occurred:", e.what(), __FILE__, __LINE__);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Cleanup resources
|
|||
|
|
if (jpegBuf) tjFree(jpegBuf);
|
|||
|
|
if (jpegCompressor) tjDestroy(jpegCompressor);
|
|||
|
|
|
|||
|
|
return _lastJpegImage;
|
|||
|
|
}
|
|||
|
|
std::string ANSFILEPLAYER_CV::MatToBinaryData(const cv::Mat& image) {
|
|||
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|||
|
|
// Check if the image is empty or has invalid data
|
|||
|
|
if (!_isPlaying) return _lastJpegImage;
|
|||
|
|
if (image.empty() || !image.data || !image.u) {
|
|||
|
|
return _lastJpegImage;
|
|||
|
|
}
|
|||
|
|
try {
|
|||
|
|
// Encode the image to a memory buffer
|
|||
|
|
return encodeJpegString(image, 85);
|
|||
|
|
}
|
|||
|
|
catch (const std::exception& e) {
|
|||
|
|
this->_logger.LogFatal("ANSFILEPLAYER_CV::MatToBinaryData. Exception occurred:", e.what(), __FILE__, __LINE__);
|
|||
|
|
}
|
|||
|
|
catch (...) {
|
|||
|
|
this->_logger.LogFatal("ANSFILEPLAYER_CV::MatToBinaryData.", "Unknown exception occurred.", __FILE__, __LINE__);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Return an empty string in case of failure
|
|||
|
|
return _lastJpegImage;
|
|||
|
|
}
|
|||
|
|
std::string ANSFILEPLAYER_CV::GetJpegStringImage(int& width, int& height, int64_t& pts) {
|
|||
|
|
std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|||
|
|
if (!_isPlaying) return _lastJpegImage;
|
|||
|
|
try {
|
|||
|
|
cv::Mat image = GetImage(width, height, pts);
|
|||
|
|
std::string jpegString = MatToBinaryData(image);
|
|||
|
|
image.release();
|
|||
|
|
return jpegString;
|
|||
|
|
}
|
|||
|
|
catch (const std::exception& e) {
|
|||
|
|
this->_logger.LogError("ANSFILEPLAYER_CV::GetJpegStringImage. Exception occurred:", e.what(), __FILE__, __LINE__);
|
|||
|
|
return _lastJpegImage;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
extern "C" __declspec(dllexport) int CreateANSFileCVPlayerHandle(ANSCENTER::ANSFILEPLAYER_CV** Handle, const char* licenseKey, const char* url) {
|
|||
|
|
if (!Handle || !licenseKey || !url) return -1;
|
|||
|
|
try {
|
|||
|
|
auto ptr = std::make_unique<ANSCENTER::ANSFILEPLAYER_CV>();
|
|||
|
|
bool result = ptr->Init(licenseKey, 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::ANSFILEPLAYER_CV*>(p);
|
|||
|
|
try { h->Stop(); } catch (...) {}
|
|||
|
|
try { h->Destroy(); } catch (...) {}
|
|||
|
|
try { delete h; } catch (...) {}
|
|||
|
|
});
|
|||
|
|
return 1;
|
|||
|
|
}
|
|||
|
|
*Handle = nullptr;
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
catch (...) { return 0; }
|
|||
|
|
}
|
|||
|
|
extern "C" __declspec(dllexport) int ReleaseANSFileCVPlayerHandle(ANSCENTER::ANSFILEPLAYER_CV** Handle) {
|
|||
|
|
if (Handle == nullptr || *Handle == nullptr) return -1;
|
|||
|
|
try {
|
|||
|
|
extern void anscv_unregister_handle(void*);
|
|||
|
|
anscv_unregister_handle(*Handle);
|
|||
|
|
// Destructor calls Destroy() <20> no need to call it explicitly (avoids double-destroy)
|
|||
|
|
std::unique_ptr<ANSCENTER::ANSFILEPLAYER_CV> ptr(*Handle);
|
|||
|
|
*Handle = nullptr;
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
catch (...) {
|
|||
|
|
if (Handle) *Handle = nullptr;
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
extern "C" __declspec(dllexport) int GetFileCVPlayerStrImage(ANSCENTER::ANSFILEPLAYER_CV** Handle, int& width, int& height, int64_t& timeStamp, std::string& jpegImage) {
|
|||
|
|
if (Handle == nullptr || *Handle == nullptr) return -1;
|
|||
|
|
try {
|
|||
|
|
jpegImage = (*Handle)->GetJpegStringImage(width, height, timeStamp);
|
|||
|
|
if (!jpegImage.empty()) return 1;
|
|||
|
|
else return 0;
|
|||
|
|
}
|
|||
|
|
catch (const std::exception& e) {
|
|||
|
|
std::cerr << "Error getting image data from FilePlayer client: " << e.what() << std::endl;
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
catch (...) {
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
extern "C" __declspec(dllexport) int GetFileCVPlayerImage(ANSCENTER::ANSFILEPLAYER_CV** Handle, int& width, int& height, int64_t& timeStamp, LStrHandle jpegImage) {
|
|||
|
|
if (Handle == nullptr || *Handle == nullptr) return -1;
|
|||
|
|
try {
|
|||
|
|
std::string jpegString = (*Handle)->GetJpegStringImage(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 FilePlayer client." << std::endl;
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (const std::exception& e) {
|
|||
|
|
std::cerr << "Error getting image data from FilePlayer client: " << e.what() << std::endl;
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
catch (...) {
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
extern "C" __declspec(dllexport) int GetFileCVPlayerCVImage(ANSCENTER::ANSFILEPLAYER_CV** Handle, int& width, int& height, int64_t& timeStamp, cv::Mat** image) {
|
|||
|
|
if (!Handle || !(*Handle) || !image) {
|
|||
|
|
std::cerr << "Error: Invalid input parameters in GetFilePlayerCVImage." << std::endl;
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
try {
|
|||
|
|
cv::Mat img = (*Handle)->GetImage(width, height, timeStamp);
|
|||
|
|
if (img.empty()) {
|
|||
|
|
return 0; // No valid image retrieved
|
|||
|
|
}
|
|||
|
|
// anscv_mat_replace has its own internal registry_mutex <20> no global mutex needed
|
|||
|
|
anscv_mat_replace(image, std::move(img));
|
|||
|
|
|
|||
|
|
return 1; // Success
|
|||
|
|
}
|
|||
|
|
catch (const cv::Exception& e) {
|
|||
|
|
std::cerr << "OpenCV exception in GetFilePlayerCVImage: " << e.what() << std::endl;
|
|||
|
|
return -2;
|
|||
|
|
}
|
|||
|
|
catch (const std::exception& e) {
|
|||
|
|
std::cerr << "Exception in GetFilePlayerCVImage: " << e.what() << std::endl;
|
|||
|
|
return -2;
|
|||
|
|
}
|
|||
|
|
catch (...) {
|
|||
|
|
std::cerr << "Unknown exception in GetFilePlayerCVImage" << std::endl;
|
|||
|
|
return -2;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
extern "C" __declspec(dllexport) int StartFileCVPlayer(ANSCENTER::ANSFILEPLAYER_CV** 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 file player: " << e.what() << std::endl;
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
catch (...) {
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
extern "C" __declspec(dllexport) int ReconnectFileCVPlayer(ANSCENTER::ANSFILEPLAYER_CV** 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 file player: " << e.what() << std::endl;
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
catch (...) {
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
extern "C" __declspec(dllexport) int StopFileCVPlayer(ANSCENTER::ANSFILEPLAYER_CV** 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 file player: " << e.what() << std::endl;
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
catch (...) {
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
extern "C" __declspec(dllexport) int IsFileCVPlayerPaused(ANSCENTER::ANSFILEPLAYER_CV** 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 file player is paused: " << e.what() << std::endl;
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
catch (...) {
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
extern "C" __declspec(dllexport) int IsFileCVPlayerRunning(ANSCENTER::ANSFILEPLAYER_CV** 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 file player is running: " << e.what() << std::endl;
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
catch (...) {
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
extern "C" __declspec(dllexport) int IsFileCVPlayerRecording(ANSCENTER::ANSFILEPLAYER_CV** 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 file player is recording: " << e.what() << std::endl;
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
catch (...) {
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
extern "C" __declspec(dllexport) void SetFileCVPlayerAudioVolume(ANSCENTER::ANSFILEPLAYER_CV** Handle, int volume)
|
|||
|
|
{
|
|||
|
|
if (Handle == nullptr || *Handle == nullptr) return;
|
|||
|
|
try {
|
|||
|
|
(*Handle)->SetAudioVolume(volume);
|
|||
|
|
}
|
|||
|
|
catch (...) {}
|
|||
|
|
}
|
|||
|
|
extern "C" __declspec(dllexport) void EnableFileCVPlayerAudioVolume(ANSCENTER::ANSFILEPLAYER_CV** Handle, int status)
|
|||
|
|
{
|
|||
|
|
if (Handle == nullptr || *Handle == nullptr) return;
|
|||
|
|
try {
|
|||
|
|
bool audioStatus = false;
|
|||
|
|
if (status == 1)audioStatus = true;
|
|||
|
|
(*Handle)->EnableAudio(audioStatus);
|
|||
|
|
}
|
|||
|
|
catch (const std::exception& e) {
|
|||
|
|
std::cerr << "Error enabling audio for file player: " << e.what() << std::endl;
|
|||
|
|
}
|
|||
|
|
catch (...) {}
|
|||
|
|
}
|
|||
|
|
extern "C" __declspec(dllexport) void SetFileCVPlayerImageRotation(ANSCENTER::ANSFILEPLAYER_CV** Handle, double rotationAngle) {
|
|||
|
|
if (Handle == nullptr || *Handle == nullptr) return;
|
|||
|
|
try {
|
|||
|
|
(*Handle)->SetImageRotate(rotationAngle);
|
|||
|
|
}
|
|||
|
|
catch (...) {}
|
|||
|
|
}
|
|||
|
|
extern "C" __declspec(dllexport) int SetBBoxFileCVPlayer(ANSCENTER::ANSFILEPLAYER_CV** 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 bounding box for file player: " << e.what() << std::endl;
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
catch (...) {
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
extern "C" __declspec(dllexport) int SetCropFlagFileCVPlayer(ANSCENTER::ANSFILEPLAYER_CV** 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 crop flag for file player: " << e.what() << std::endl;
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
catch (...) {
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|