117 lines
3.9 KiB
C++
117 lines
3.9 KiB
C++
#pragma once
|
|
// ANSMatRegistry.h — Thread-safe cv::Mat* pointer registry.
|
|
// Prevents double-free crashes when LabVIEW calls ReleaseImage on a pointer
|
|
// that a stream source (RTSP, VideoPlayer, etc.) has already freed and replaced.
|
|
//
|
|
// Every cv::Mat* allocated through these functions is tracked in a global set.
|
|
// Deletion only proceeds if the pointer is still in the set, guaranteeing each
|
|
// pointer is freed exactly once regardless of which thread calls delete first.
|
|
//
|
|
// Usage:
|
|
// Allocate: cv::Mat* p = anscv_mat_new(std::move(img));
|
|
// Free: anscv_mat_delete(&p); // safe, no-op if already freed
|
|
// Swap: anscv_mat_replace(&p, std::move(newImg)); // atomic old-delete + new-alloc
|
|
|
|
#include <opencv2/core/mat.hpp>
|
|
#include <mutex>
|
|
#include <unordered_set>
|
|
#include "ANSGpuFrameOps.h"
|
|
|
|
namespace anscv {
|
|
namespace detail {
|
|
|
|
inline std::mutex& registry_mutex() {
|
|
static std::mutex m;
|
|
return m;
|
|
}
|
|
|
|
inline std::unordered_set<cv::Mat*>& registry() {
|
|
static std::unordered_set<cv::Mat*> s;
|
|
return s;
|
|
}
|
|
|
|
} // namespace detail
|
|
} // namespace anscv
|
|
|
|
// Allocate a new cv::Mat on the heap and register it.
|
|
inline cv::Mat* anscv_mat_new(cv::Mat&& src) {
|
|
cv::Mat* p = new cv::Mat(std::move(src));
|
|
{
|
|
std::lock_guard<std::mutex> lk(anscv::detail::registry_mutex());
|
|
anscv::detail::registry().insert(p);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
inline cv::Mat* anscv_mat_new(const cv::Mat& src) {
|
|
cv::Mat* p = new cv::Mat(src);
|
|
{
|
|
std::lock_guard<std::mutex> lk(anscv::detail::registry_mutex());
|
|
anscv::detail::registry().insert(p);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
// Delete a cv::Mat* only if it is still in the registry.
|
|
// Returns true if deleted, false if pointer was null or already freed.
|
|
// Thread-safe: the pointer is removed from the registry AND nulled AND deleted
|
|
// all inside the lock, so no other thread can race on the same pointer.
|
|
inline bool anscv_mat_delete(cv::Mat** pp) {
|
|
if (!pp || !(*pp))
|
|
return false;
|
|
cv::Mat* toDelete = nullptr;
|
|
{
|
|
std::lock_guard<std::mutex> lk(anscv::detail::registry_mutex());
|
|
// Re-check under lock — another thread may have nulled *pp
|
|
if (!*pp)
|
|
return false;
|
|
auto& reg = anscv::detail::registry();
|
|
auto it = reg.find(*pp);
|
|
if (it == reg.end()) {
|
|
// Not in registry — already freed by another thread.
|
|
*pp = nullptr;
|
|
return false;
|
|
}
|
|
toDelete = *pp;
|
|
reg.erase(it);
|
|
*pp = nullptr; // Null the caller's pointer while still under lock
|
|
}
|
|
// Release GPU frame data (refcount--, frees NV12+GPU cache when refcount=0)
|
|
gpu_frame_remove(toDelete);
|
|
// Safe to delete outside lock: pointer is removed from registry and
|
|
// *pp is nullptr, so no other thread can reach toDelete.
|
|
delete toDelete;
|
|
return true;
|
|
}
|
|
|
|
// Atomic replace: delete old image (if still registered), allocate new, assign to *pp.
|
|
// The old pointer is only deleted if it is in the registry (prevents double-free
|
|
// when LabVIEW already freed it via anscv_mat_delete).
|
|
inline void anscv_mat_replace(cv::Mat** pp, cv::Mat&& newImg) {
|
|
cv::Mat* newPtr = new cv::Mat(std::move(newImg));
|
|
cv::Mat* toDelete = nullptr;
|
|
{
|
|
std::lock_guard<std::mutex> lk(anscv::detail::registry_mutex());
|
|
auto& reg = anscv::detail::registry();
|
|
if (pp && *pp) {
|
|
auto it = reg.find(*pp);
|
|
if (it != reg.end()) {
|
|
toDelete = *pp;
|
|
reg.erase(it);
|
|
}
|
|
// If not in registry, another thread already freed it — skip delete
|
|
}
|
|
*pp = newPtr;
|
|
reg.insert(newPtr);
|
|
}
|
|
if (toDelete) {
|
|
// Release GPU frame ref for old Mat (safe with refcounting —
|
|
// only frees NV12/GPU cache when refcount reaches 0)
|
|
gpu_frame_remove(toDelete);
|
|
delete toDelete;
|
|
}
|
|
|
|
// Periodically run TTL eviction (piggybacked on camera thread activity)
|
|
gpu_frame_evict_stale();
|
|
}
|