|
@@ -28,6 +28,54 @@ namespace IvfTl.ControlHost.Debug
|
|
|
private readonly ConcurrentDictionary<string, CalibrationCoordinator> _bySid
|
|
private readonly ConcurrentDictionary<string, CalibrationCoordinator> _bySid
|
|
|
= new ConcurrentDictionary<string, CalibrationCoordinator>();
|
|
= new ConcurrentDictionary<string, CalibrationCoordinator>();
|
|
|
|
|
|
|
|
|
|
+ // (Task3.2b) 标定时预览用的"每 sid 最新一帧 BGR + 宽高"缓冲。
|
|
|
|
|
+ // 标定中相机被 CalibrationEngine 独占抓帧,推流线程绝不能再 GrabStable(会和标定争相机原生锁);
|
|
|
|
|
+ // 改由引擎 OnFrame 每次抓帧把原始帧塞进这里,推流线程只读这块缓冲、不碰相机。
|
|
|
|
|
+ // byte[] 整块引用赋值是原子的,读端拿到的要么是整块旧帧要么是整块新帧、不会撕裂,故无需锁。
|
|
|
|
|
+ private sealed class FrameSlot
|
|
|
|
|
+ {
|
|
|
|
|
+ public volatile byte[] Bgr;
|
|
|
|
|
+ public int Width;
|
|
|
|
|
+ public int Height;
|
|
|
|
|
+ }
|
|
|
|
|
+ private readonly ConcurrentDictionary<string, FrameSlot> _lastFrameBySid
|
|
|
|
|
+ = new ConcurrentDictionary<string, FrameSlot>();
|
|
|
|
|
+
|
|
|
|
|
+ // OnFrame 高频触发(标定线程每次 Grab),存帧要轻:只做一次整块引用赋值 + 记宽高。
|
|
|
|
|
+ private void StoreLatestFrame(string sid, byte[] bgr, int w, int h)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (bgr == null) return;
|
|
|
|
|
+ var slot = _lastFrameBySid.GetOrAdd(sid, _ => new FrameSlot());
|
|
|
|
|
+ slot.Width = w;
|
|
|
|
|
+ slot.Height = h;
|
|
|
|
|
+ slot.Bgr = bgr; // 最后赋 volatile 引用,保证宽高先就位再发布该帧
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// (Task3.2b) 该 sid 是否正在标定(有活跃协作器且 IsRunning)。推流线程据此决定:
|
|
|
|
|
+ /// true→读 OnFrame 缓冲帧(不 GrabStable);false→走原 GrabStable。
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ public bool IsCalibrating(string sid)
|
|
|
|
|
+ {
|
|
|
|
|
+ return sid != null
|
|
|
|
|
+ && _bySid.TryGetValue(sid, out var c)
|
|
|
|
|
+ && c.GetProgress().IsRunning;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// (Task3.2b) 取该 sid 最新一帧(引擎 OnFrame 喂的原始 BGR)。无帧返回 false。
|
|
|
|
|
+ /// 标定中即使此刻无帧,推流线程也绝不 GrabStable(避免争相机锁),跳过等下一帧即可。
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ public bool TryGetLatestFrame(string sid, out byte[] bgr, out int w, out int h)
|
|
|
|
|
+ {
|
|
|
|
|
+ bgr = null; w = 0; h = 0;
|
|
|
|
|
+ if (sid == null || !_lastFrameBySid.TryGetValue(sid, out var slot)) return false;
|
|
|
|
|
+ var buf = slot.Bgr; // 读 volatile 引用一次
|
|
|
|
|
+ if (buf == null) return false;
|
|
|
|
|
+ bgr = buf; w = slot.Width; h = slot.Height;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
public CalibrationManager(DebugSessionManager debug, Func<int, HouseBin> houseBinOf, Action<string> log = null)
|
|
public CalibrationManager(DebugSessionManager debug, Func<int, HouseBin> houseBinOf, Action<string> log = null)
|
|
|
{
|
|
{
|
|
|
_debug = debug ?? throw new ArgumentNullException(nameof(debug));
|
|
_debug = debug ?? throw new ArgumentNullException(nameof(debug));
|
|
@@ -71,6 +119,8 @@ namespace IvfTl.ControlHost.Debug
|
|
|
var engine = new CalibrationEngine(s.Lease.Serial, s.Lease.Camera)
|
|
var engine = new CalibrationEngine(s.Lease.Serial, s.Lease.Camera)
|
|
|
{
|
|
{
|
|
|
Log = msg => _log($"[calib][舱{s.HouseSn}][well{well}]{msg}"),
|
|
Log = msg => _log($"[calib][舱{s.HouseSn}][well{well}]{msg}"),
|
|
|
|
|
+ // (Task3.2b) 引擎每次抓帧把原始 BGR 帧喂进 per-sid 缓冲,供推流线程标定时读取(替代 GrabStable,避免争相机锁)。
|
|
|
|
|
+ OnFrame = buf => StoreLatestFrame(sid, buf, s.Lease.Camera.Width, s.Lease.Camera.Height),
|
|
|
HFineRange = range.HHalf, // 水平微调半幅(围绕 HCenter)
|
|
HFineRange = range.HHalf, // 水平微调半幅(围绕 HCenter)
|
|
|
ZCoarseCenter = vCenter, // Z 粗扫中心=该 well 清晰位(取代引擎固定 90000)
|
|
ZCoarseCenter = vCenter, // Z 粗扫中心=该 well 清晰位(取代引擎固定 90000)
|
|
|
ZCoarseHalf = range.VHalf, // Z 粗扫半幅
|
|
ZCoarseHalf = range.VHalf, // Z 粗扫半幅
|
|
@@ -123,6 +173,7 @@ namespace IvfTl.ControlHost.Debug
|
|
|
if (sid != null && _bySid.TryRemove(sid, out var c))
|
|
if (sid != null && _bySid.TryRemove(sid, out var c))
|
|
|
{
|
|
{
|
|
|
c.Stop();
|
|
c.Stop();
|
|
|
|
|
+ _lastFrameBySid.TryRemove(sid, out _); // (Task3.2b) 清该 sid 帧缓冲,避免陈旧帧 + 释放内存
|
|
|
_log($"[calib] 停标定 sid={sid}");
|
|
_log($"[calib] 停标定 sid={sid}");
|
|
|
}
|
|
}
|
|
|
return DebugCommandResult.Okay();
|
|
return DebugCommandResult.Okay();
|