using System; using System.Threading; using ivf_tl_CameraHelper; namespace IvfTl.Hardware.Impl { /// /// ICamera 实现,内部包装 control 的 ivf_tl_CameraHelper.Camera(不改其内部逻辑)。 /// 所有进 native 的调用统一经 ICameraGate 串行化(全进程一把锁,13 §3.5/§②)。 /// 旧 Camera 内部自带的 static locker 保留(死代码隔离,01 §5);同一线程经两把锁不死锁, /// 真正跨调用者互斥由本层 _gate 保证。 /// 蓝本 af Camera:GetFrameBuffer 做 null 保护;GrabStable 内置"丢残留帧 + 到位延时 + 重试"。 /// public sealed class CameraImpl : ICamera { private readonly Camera _camera; // 包装的旧具体相机 private readonly ICameraGate _gate; // 全进程相机锁 private readonly int _index; private readonly int _width; private readonly int _height; public CameraImpl(int index, int width, int height, int exposure, ICameraGate gate) { _index = index; _width = width; _height = height; _gate = gate ?? throw new ArgumentNullException(nameof(gate)); _camera = new Camera(index, width, height, exposure); } public int Index => _index; public int Width => _width; public int Height => _height; public int Exposure => _camera.Exposure; public string SerialNumber => _camera.NumBer; public bool IsInit => _camera.IsInit; public bool IsStart => _camera.IsStart; public object RawCamera => _camera; // ── 生命周期 ── public int Init(byte redGain = 25, byte greenGain = 14, byte blueGain = 25) { // 注:旧 Camera.Init() 固定增益(25/14/25),未暴露参数;此处沿用旧实现。 // 增益自定义经 SetGain(旧 Camera 暂无该 API,见 SetGain 的 TODO)。 return _gate.Invoke(() => _camera.Init()); } public int SetOpMode(byte mode = 0, bool strobe = false) { return _gate.Invoke(() => _camera.SetOpMode()); } public int ReadSerial() { return _gate.Invoke(() => _camera.GetNumbet()); } public int UnInit() { return _gate.Invoke(() => _camera.UnInit()); } // ── 参数 ── public int SetExposure(int exposure100us) { return _gate.Invoke(() => _camera.SetPartOfCapInfo(exposure100us)); } public int SetGain(byte red, byte green, byte blue) { // TODO(M2): control 旧 Camera 未暴露独立 SetGain(增益随 Init 固定); // 自动对焦标定若需调增益,需向旧 Camera 补 native SetPartOfCapInfo(含 Gain) 包装, // 或在 HAL 内自行构造 capInfoStruct。M1 不需要,留 TODO + 待验证。 return -1; } // ── 抓帧(单帧)── public int GrabRgb() { return _gate.Invoke(() => _camera.GetRgbData()); } public byte[] GetFrameBuffer() { // _pDest 空保护语义(af Camera.cs:137):旧 Camera.SourceBuffer 内部直接 Marshal.Copy(pDest,...), // pDest 已释放时会抛异常——此处用 _gate 锁内取并捕获异常返回 null(按抓帧失败重试)。 return _gate.Invoke(() => { try { return _camera.SourceBuffer; } catch { return null; } }); } public byte[] GrabStable(int preDelayMs = 0, bool discardStale = true, int retry = 2) { // 13 §3.5(1)(2)(4):抓前到位延时 + 丢残留帧 + 失败重试。 if (preDelayMs > 0) Thread.Sleep(preDelayMs); for (int attempt = 0; attempt <= retry; attempt++) { // 丢残留帧:运动后旧帧可能滞留缓冲,先抓一帧丢弃(对应 af 双 Grab)。 if (discardStale) { GrabRgb(); } int r = GrabRgb(); if (r == 0) { var buf = GetFrameBuffer(); if (buf != null) return buf; } // 抓帧失败:短暂等待后重试 Thread.Sleep(50); } return null; } // ── 实时预览 ── public int StartPreview(IntPtr hostControl, int left, int top, int width, int height) { return _gate.Invoke(() => _camera.Usb2Start(hostControl, left, top, width, height)); } public int StopPreview() { return _gate.Invoke(() => _camera.Usb2Stop()); } // ── operate 调试页"无图模式"两步抓帧 + 存图(M1-B2)── // 转发到底层具体 Camera 既有方法(GetRawData / RawToRgb / SaveBmpPic),全部经 _gate 串行化。 public int GrabRaw() { return _gate.Invoke(() => _camera.GetRawData()); } public int RawToRgb() { return _gate.Invoke(() => _camera.RawToRgb()); } public bool SavePic(string fullName, int width, int height) { // 底层 control Camera 无 MVCAPI.SavePic;用其既有 SaveBmpPic(从 SourceBuffer 取 24bpp、 // RotateFlipY 后按文件扩展名落盘,.jpg 即存 JPEG),与 operate 调试页存图语义等价。 // 取像素 + 落盘都涉及 native 内存(SourceBuffer→Marshal.Copy),经 _gate 与抓帧/释放互斥。 return _gate.Invoke(() => _camera.SaveBmpPic(fullName)); } public void Dispose() { try { UnInit(); } catch { } try { _gate.Invoke(() => _camera.DisPose()); } catch { } } } }