From 2ee25a4f7c1884f6582d302953fe85294ea25a2a Mon Sep 17 00:00:00 2001 From: twice109 <3518499@qq.com> Date: Sun, 28 Dec 2025 08:07:55 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E9=80=9A=E8=BF=87=E7=BD=91?= =?UTF-8?q?=E9=A1=B5=E5=A2=9E=E5=8A=A0=E3=80=81=E5=88=A0=E9=99=A4=E3=80=81?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=91=84=E5=83=8F=E5=A4=B4=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=20=E6=94=AF=E6=8C=81=E6=91=84=E5=83=8F?= =?UTF-8?q?=E5=A4=B4=E9=85=8D=E7=BD=AE=E4=BF=A1=E6=81=AF=E4=B8=AD=E5=8F=A5?= =?UTF-8?q?=E6=9F=84=E7=9A=84=E8=AE=BE=E7=BD=AE=EF=BC=8C=E5=B9=B6=E5=AE=9E?= =?UTF-8?q?=E6=B5=8B=E6=9C=89=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Abstractions/Models/PreprocessConfig.cs | 69 +++ .../Abstractions/Models/ProcessingOptions.cs | 36 +- .../Abstractions/Models/VideoSourceConfig.cs | 12 +- .../Controllers/CamerasController.cs | 76 +++- .../Controllers/Dto/CameraConfigDto.cs | 2 + .../Controllers/Dto/DeviceUpdateDto.cs | 10 +- .../Dto/UpdateProcessingRequest.cs | 14 +- .../Controllers/MonitorController.cs | 39 +- SHH.CameraSdk/Core/Manager/CameraManager.cs | 8 +- .../Core/Scheduling/FrameController.cs | 29 +- .../Core/Scheduling/FrameRequirement.cs | 50 ++- .../Core/Services/ImageEnhanceCluster.cs | 18 +- .../Core/Services/ImageScaleCluster.cs | 7 +- .../Core/Services/ProcessingConfigManager.cs | 2 +- SHH.CameraSdk/Drivers/BaseVideoSource.cs | 11 + .../Drivers/HikVision/HikVideoSource.cs | 2 +- SHH.CameraSdk/Htmls/CameraControl.html | 147 ++++++ SHH.CameraSdk/Htmls/CameraEdit.html | 422 ++++++++++++++++++ SHH.CameraSdk/Htmls/Diagnostic.html | 133 ++++++ SHH.CameraSdk/Htmls/Editor.html | 99 ++++ SHH.CameraSdk/Htmls/EditorTop.html | 208 +++++++++ SHH.CameraSdk/Htmls/List.html | 175 ++++++++ SHH.CameraSdk/Htmls/Main.html | 148 ++++++ SHH.CameraSdk/Htmls/Preprocessing.html | 375 ++++++++++++++++ SHH.CameraSdk/Htmls/Subscription.html | 281 ++++++++++++ 25 files changed, 2298 insertions(+), 75 deletions(-) create mode 100644 SHH.CameraSdk/Abstractions/Models/PreprocessConfig.cs create mode 100644 SHH.CameraSdk/Htmls/CameraControl.html create mode 100644 SHH.CameraSdk/Htmls/CameraEdit.html create mode 100644 SHH.CameraSdk/Htmls/Diagnostic.html create mode 100644 SHH.CameraSdk/Htmls/Editor.html create mode 100644 SHH.CameraSdk/Htmls/EditorTop.html create mode 100644 SHH.CameraSdk/Htmls/List.html create mode 100644 SHH.CameraSdk/Htmls/Main.html create mode 100644 SHH.CameraSdk/Htmls/Preprocessing.html create mode 100644 SHH.CameraSdk/Htmls/Subscription.html diff --git a/SHH.CameraSdk/Abstractions/Models/PreprocessConfig.cs b/SHH.CameraSdk/Abstractions/Models/PreprocessConfig.cs new file mode 100644 index 0000000..4213301 --- /dev/null +++ b/SHH.CameraSdk/Abstractions/Models/PreprocessConfig.cs @@ -0,0 +1,69 @@ +namespace SHH.CameraSdk +{ + /// + /// 图像预处理配置模型 + /// 用于定义相机采集后的原始帧在分发给订阅者之前的通用处理参数 + /// + public class PreprocessConfig + { + #region --- 分辨率与缩放控制 (Resolution & Scale) --- + + /// + /// 目标输出宽度(单位:像素) + /// 范围约束:176 - 1920 px + /// + public int Width { get; set; } + + /// + /// 目标输出高度(单位:像素) + /// 范围约束:44 - 1080 px + /// + public int Height { get; set; } + + /// + /// 等比缩放倍率(基于原始分辨率的系数) + /// 例如:0.5 表示缩小一半,1.2 表示放大 20% + /// + public double Scale { get; set; } = 1.0; + + /// + /// 是否锁定等比缩放 + /// true: 修改宽度时高度按比例自动调整;false: 允许拉伸或压缩变形 + /// + public bool IsAspectRatio { get; set; } = true; + + #endregion + + #region --- 业务权限约束 (Business Constraints) --- + + /// + /// 是否允许缩小图像 + /// 默认为 true。若为 false,则 Target 尺寸不得低于原始分辨率 + /// + public bool AllowShrink { get; set; } = true; + + /// + /// 是否启用放大功能 + /// 默认为 false。若未开启,当目标尺寸大于原始分辨率时将强制回退到原始尺寸 + /// + public bool AllowEnlarge { get; set; } = false; + + #endregion + + #region --- 图像质量增强 (Image Enhancement) --- + + /// + /// 是否开启图像增量(亮度/对比度补偿) + /// 只有此项为 true 时,Brightness 增益参数才会生效 + /// + public bool EnableGain { get; set; } = false; + + /// + /// 图像增量百分比(Gain/Gamma 调节) + /// 范围:0 - 100%。用于在暗光环境下提升画面可见度 + /// + public int Brightness { get; set; } = 0; + + #endregion + } +} \ No newline at end of file diff --git a/SHH.CameraSdk/Abstractions/Models/ProcessingOptions.cs b/SHH.CameraSdk/Abstractions/Models/ProcessingOptions.cs index d94f5f3..ccc9c99 100644 --- a/SHH.CameraSdk/Abstractions/Models/ProcessingOptions.cs +++ b/SHH.CameraSdk/Abstractions/Models/ProcessingOptions.cs @@ -2,28 +2,36 @@ { public class ProcessingOptions { - // --- 缩放控制 --- - - /// 是否允许缩小 (默认 True: 节约性能与带宽) - public bool EnableShrink { get; set; } = true; - - /// 是否允许放大 (默认 False: 防止性能浪费与失真) - public bool EnableExpand { get; set; } = false; + // ========================================== + // 1. 尺寸控制参数 + // ========================================== /// 目标宽度 - public int TargetWidth { get; set; } = 640; + public int TargetWidth { get; set; } = 1280; /// 目标高度 - public int TargetHeight { get; set; } = 360; + public int TargetHeight { get; set; } = 720; - // --- 增亮控制 --- + /// 仅允许缩小 (如果原图比目标大,则缩放;否则不处理) + public bool EnableShrink { get; set; } = true; - /// 是否启用图像增强 - public bool EnableEnhance { get; set; } = false; // 默认关闭,按需开启 + /// 仅允许放大 (如果原图比目标小,则缩放;否则不处理) + public bool EnableExpand { get; set; } = false; - /// 增亮强度 (0-100, 默认30) - public double BrightnessLevel { get; set; } = 30.0; + // ========================================== + // 2. 画质增强参数 + // ========================================== + + /// 是否启用图像增亮 + public bool EnableBrightness { get; set; } = false; + + /// 增亮百分比 (建议范围 0-100,对应增加的像素亮度值) + public int Brightness { get; set; } = 0; + + + // 默认实例 + [JsonIgnore] public static ProcessingOptions Default => new ProcessingOptions(); } } \ No newline at end of file diff --git a/SHH.CameraSdk/Abstractions/Models/VideoSourceConfig.cs b/SHH.CameraSdk/Abstractions/Models/VideoSourceConfig.cs index 68bd92f..1b218e3 100644 --- a/SHH.CameraSdk/Abstractions/Models/VideoSourceConfig.cs +++ b/SHH.CameraSdk/Abstractions/Models/VideoSourceConfig.cs @@ -35,8 +35,7 @@ public class VideoSourceConfig public string Password { get; set; } = string.Empty; /// 渲染句柄(可选):用于硬解码时直接绑定显示窗口,提升渲染性能 - [JsonIgnore] - public IntPtr RenderHandle { get; set; } = IntPtr.Zero; + public long RenderHandle { get; set; } /// 物理通道号(IPC 通常为 1;NVR 对应接入的摄像头通道索引) public int ChannelIndex { get; set; } = 1; @@ -44,6 +43,13 @@ public class VideoSourceConfig /// 默认码流类型(0 = 主码流(高清),1 = 子码流(低带宽)) public int StreamType { get; set; } = 0; + /// 关联的主板IP + public string MainboardIp { get; set; } + = string.Empty; + + /// 关联的主板端口 + public int MainboardPort { get; set; } + /// Rtsp 播放路径 public string RtspPath { get; set; } = string.Empty; @@ -136,6 +142,8 @@ public class VideoSourceConfig Password = this.Password, RenderHandle = this.RenderHandle, ChannelIndex = this.ChannelIndex, + MainboardIp = this.MainboardIp, + MainboardPort = this.MainboardPort, RtspPath = this.RtspPath, StreamType = this.StreamType, Transport = this.Transport, diff --git a/SHH.CameraSdk/Controllers/CamerasController.cs b/SHH.CameraSdk/Controllers/CamerasController.cs index 5b3c326..33c446b 100644 --- a/SHH.CameraSdk/Controllers/CamerasController.cs +++ b/SHH.CameraSdk/Controllers/CamerasController.cs @@ -8,11 +8,15 @@ public class CamerasController : ControllerBase { private readonly CameraManager _manager; + // 1. 新增:我们需要配置管理器 + private readonly ProcessingConfigManager _configManager; + // 构造函数注入管理器 - public CamerasController(CameraManager manager, DisplayWindowManager displayManager) + public CamerasController(CameraManager manager, DisplayWindowManager displayManager, ProcessingConfigManager configManager) { _manager = manager; _displayManager = displayManager; + _configManager = configManager; } // ========================================================================== @@ -191,7 +195,10 @@ public class CamerasController : ControllerBase Password = dto.Password, ChannelIndex = dto.ChannelIndex, StreamType = dto.StreamType, - RtspPath = dto.RtspPath + RtspPath = dto.RtspPath, + MainboardPort = dto.MainboardPort, + MainboardIp = dto.MainboardIp, + RenderHandle =dto.RenderHandle, }; } @@ -212,8 +219,7 @@ public class CamerasController : ControllerBase ChannelIndex = dto.ChannelIndex, Brand = dto.Brand, RtspPath = dto.RtspPath, - MainboardIp = dto.MainboardIp, - MainboardPort = dto.MainboardPort, + // ========================================== // 2. 热更新参数 (运行时属性) @@ -222,6 +228,9 @@ public class CamerasController : ControllerBase Location = dto.Location, StreamType = dto.StreamType, + MainboardIp = dto.MainboardIp, + MainboardPort = dto.MainboardPort, + RenderHandle = dto.RenderHandle, // 注意:通常句柄是通过 bind-handle 接口单独绑定的, // 但如果 ConfigDto 里包含了上次保存的句柄,也可以映射 // RenderHandle = dto.RenderHandle, @@ -281,7 +290,7 @@ public class CamerasController : ControllerBase } // 5. 将需求注册到流控控制器 - controller.Register(dto.AppId, dto.DisplayFps); + controller.Register(new FrameRequirement(dto)); // 6. 路由显示逻辑 (核心整合点) if (dto.Type == SubscriptionType.LocalWindow) @@ -333,4 +342,61 @@ public class CamerasController : ControllerBase // var bytes = await cam.CaptureCurrentFrameAsync(); // return File(bytes, "image/jpeg"); //} + + // ============================================================= + // 3. 新增:更新图像处理/分辨率参数的接口 + // URL 示例: POST /api/cameras/1001/processing + // ============================================================= + [HttpPost("{id}/processing")] + public IActionResult UpdateProcessingOptions(long id, [FromBody] ProcessingOptions options) + { + // A. 检查相机是否存在 + var camera = _manager.GetDevice(id); + if (camera == null) + { + return NotFound(new { error = $"Camera {id} not found." }); + } + + // B. 参数校验 (防止宽高为0导致报错) + if (options.TargetWidth <= 0 || options.TargetHeight <= 0) + { + return BadRequest(new { error = "Target dimensions must be greater than 0." }); + } + + // C. 执行更新 (热更,立即生效) + // ScaleWorker 下一帧处理时会自动读取这个新配置 + _configManager.UpdateOptions(id, options); + + return Ok(new + { + success = true, + message = "Image processing options updated.", + currentConfig = options + }); + } + + // 在 CamerasController 类中添加 + + // ============================================================= + // 新增:获取/回显图像处理参数的接口 + // URL: GET /api/cameras/{id}/processing + // ============================================================= + [HttpGet("{id}/processing")] + public IActionResult GetProcessingOptions(long id) + { + // 1. 检查相机是否存在 + var camera = _manager.GetDevice(id); + if (camera == null) + { + return NotFound(new { error = $"Camera {id} not found." }); + } + + // 2. 从配置管理器中获取当前配置 + // 注意:ProcessingConfigManager 内部应该处理好逻辑: + // 如果该设备还没配过,它会自动返回 new ProcessingOptions() (默认值) + var options = _configManager.GetOptions(id); + + // 3. 返回 JSON 给前端 + return Ok(options); + } } \ No newline at end of file diff --git a/SHH.CameraSdk/Controllers/Dto/CameraConfigDto.cs b/SHH.CameraSdk/Controllers/Dto/CameraConfigDto.cs index f661119..a7d3c85 100644 --- a/SHH.CameraSdk/Controllers/Dto/CameraConfigDto.cs +++ b/SHH.CameraSdk/Controllers/Dto/CameraConfigDto.cs @@ -61,6 +61,8 @@ public class CameraConfigDto [MaxLength(64, ErrorMessage = "密码长度不能超过64个字符")] public string Password { get; set; } = string.Empty; + public long RenderHandle { get; set; } + /// /// 通道号 (通常为1) /// diff --git a/SHH.CameraSdk/Controllers/Dto/DeviceUpdateDto.cs b/SHH.CameraSdk/Controllers/Dto/DeviceUpdateDto.cs index fb23efc..558194f 100644 --- a/SHH.CameraSdk/Controllers/Dto/DeviceUpdateDto.cs +++ b/SHH.CameraSdk/Controllers/Dto/DeviceUpdateDto.cs @@ -41,16 +41,18 @@ public class DeviceUpdateDto /// RTSP流地址 (非SDK模式下使用) [MaxLength(256, ErrorMessage = "RTSP地址长度不能超过 256 个字符")] - public string? RtspPath { get; set; } + public string RtspPath { get; set; } + = string.Empty; /// 关联的主板IP (用于联动控制) [RegularExpression(@"^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)?$", ErrorMessage = "请输入合法的IPv4地址")] - public string? MainboardIp { get; set; } + public string MainboardIp { get; set; } + = string.Empty; /// 关联的主板端口 [Range(1, 65535, ErrorMessage = "主板端口号必须在 1-65535 范围内")] - public int? MainboardPort { get; set; } + public int MainboardPort { get; set; } // ============================================================================== // 2. 热更新参数 (Hot Update) @@ -71,7 +73,7 @@ public class DeviceUpdateDto /// 渲染句柄 (IntPtr 的 Long 形式) [Range(0, long.MaxValue, ErrorMessage = "渲染句柄必须是非负整数")] - public long? RenderHandle { get; set; } + public long RenderHandle { get; set; } // ============================================================================== // 3. 图像处理参数 (Image Processing - Hot Update) diff --git a/SHH.CameraSdk/Controllers/Dto/UpdateProcessingRequest.cs b/SHH.CameraSdk/Controllers/Dto/UpdateProcessingRequest.cs index a204eb2..95558c7 100644 --- a/SHH.CameraSdk/Controllers/Dto/UpdateProcessingRequest.cs +++ b/SHH.CameraSdk/Controllers/Dto/UpdateProcessingRequest.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SHH.CameraSdk +namespace SHH.CameraSdk { public class UpdateProcessingRequest { @@ -13,7 +7,7 @@ namespace SHH.CameraSdk public bool EnableExpand { get; set; } public int TargetWidth { get; set; } public int TargetHeight { get; set; } - public bool EnableEnhance { get; set; } - public double BrightnessLevel { get; set; } + public bool EnableBrightness { get; set; } + public int Brightness { get; set; } } -} +} \ No newline at end of file diff --git a/SHH.CameraSdk/Controllers/MonitorController.cs b/SHH.CameraSdk/Controllers/MonitorController.cs index eb30ace..c610c9d 100644 --- a/SHH.CameraSdk/Controllers/MonitorController.cs +++ b/SHH.CameraSdk/Controllers/MonitorController.cs @@ -43,6 +43,8 @@ public class MonitorController : ControllerBase Status = c.Status.ToString(), c.IsPhysicalOnline, c.RealFps, + c.Width, + c.Height, c.TotalFrames, c.Config.Name, c.Config.IpAddress, @@ -69,23 +71,34 @@ public class MonitorController : ControllerBase [HttpGet("{id}")] public IActionResult GetDeviceDetail(long id) { - var device = _cameraManager.GetDevice(id); - if (device == null) return NotFound($"设备 ID: {id} 不存在"); + var d = _cameraManager.GetDevice(id); + if (d == null) return NotFound($"设备 ID: {id} 不存在"); return Ok(new { - device.Id, - Status = device.Status.ToString(), - device.IsOnline, - device.RealFps, - device.TotalFrames, - device.Config.Name, - device.Config.IpAddress, + d.Id, + Status = d.Status.ToString(), + d.IsOnline, + d.IsPhysicalOnline, + d.RealFps, + d.Width, + d.Height, + d.TotalFrames, + d.Config.Name, + d.Config.IpAddress, // --- 新增:将内存中的订阅需求列表传给前端 --- - Requirements = device.Controller.GetCurrentRequirements().Select(r => new { + Requirements = d.Controller.GetCurrentRequirements().Select(r => new { r.AppId, r.TargetFps, - r.LastActive + r.LastActive, + r.RealFps, + r.Memo, + r.SavePath, + r.Handle, + r.TargetIp, + r.TargetPort, + r.Protocol, + r.Type, }) }); } @@ -171,8 +184,8 @@ public class MonitorController : ControllerBase EnableExpand = request.EnableExpand, TargetWidth = request.TargetWidth, TargetHeight = request.TargetHeight, - EnableEnhance = request.EnableEnhance, - BrightnessLevel = request.BrightnessLevel + EnableBrightness = request.EnableBrightness, + Brightness = request.Brightness }; // 3. 提交给配置管理器 (实时生效) diff --git a/SHH.CameraSdk/Core/Manager/CameraManager.cs b/SHH.CameraSdk/Core/Manager/CameraManager.cs index b5356a5..1047998 100644 --- a/SHH.CameraSdk/Core/Manager/CameraManager.cs +++ b/SHH.CameraSdk/Core/Manager/CameraManager.cs @@ -267,9 +267,13 @@ public class CameraManager : IDisposable, IAsyncDisposable if (dto.ChannelIndex != null) newConfig.ChannelIndex = dto.ChannelIndex.Value; if (dto.StreamType != null) newConfig.StreamType = dto.StreamType.Value; if (dto.Name != null) newConfig.Name = dto.Name; - if (dto.RenderHandle != null) newConfig.RenderHandle = (IntPtr)dto.RenderHandle.Value; if (dto.Brand != null) newConfig.Brand = (DeviceBrand)dto.Brand; + newConfig.RtspPath = dto.RtspPath; + newConfig.MainboardIp = dto.MainboardIp; + newConfig.MainboardPort = dto.MainboardPort; + newConfig.RenderHandle = dto.RenderHandle; + // 4. 判定冷热更新 bool needColdRestart = newConfig.IpAddress != oldConfig.IpAddress || @@ -306,7 +310,7 @@ public class CameraManager : IDisposable, IAsyncDisposable var options = new DynamicStreamOptions { StreamType = dto.StreamType, - RenderHandle = dto.RenderHandle.HasValue ? (IntPtr)dto.RenderHandle : null + RenderHandle = (IntPtr)dto.RenderHandle }; device.ApplyOptions(options); } diff --git a/SHH.CameraSdk/Core/Scheduling/FrameController.cs b/SHH.CameraSdk/Core/Scheduling/FrameController.cs index e3f2f79..fc61b75 100644 --- a/SHH.CameraSdk/Core/Scheduling/FrameController.cs +++ b/SHH.CameraSdk/Core/Scheduling/FrameController.cs @@ -34,6 +34,30 @@ public class FrameController _accumulators.TryRemove(appId, out _); } + // 修改 Register 方法,接收整个 Requirement 对象或多个参数 + public void Register(FrameRequirement req) + { + _requirements.AddOrUpdate(req.AppId, + _ => req, // 如果不存在,直接添加整个对象 + (_, old) => + { + // 如果已存在,更新关键业务字段,同时保留统计状态 + old.TargetFps = req.TargetFps; + old.Memo = req.Memo; + old.Handle = req.Handle; + old.Type = req.Type; + old.SavePath = req.SavePath; + // 注意:不要覆盖 old.RealFps,保留之前的统计值 + return old; + }); + + // 如果是降频(<=20),确保积分器存在 + if (req.TargetFps <= 20) + { + _accumulators.GetOrAdd(req.AppId, 0); + } + } + public void Unregister(string appId) { if (string.IsNullOrWhiteSpace(appId)) return; @@ -100,6 +124,9 @@ public class FrameController // 扣除成本,保留余数 (余数是精度的关键) acc -= LOGICAL_BASE_FPS; + // 【核心修复】在此处触发统计,RealFps 才会开始跳动 + req.UpdateRealFps(); + req.LastCaptureTick = currentTick; } @@ -120,6 +147,6 @@ public class FrameController // --------------------------------------------------------- public List GetCurrentRequirements() { - return _requirements.Values.Select(r => new { r.AppId, r.TargetFps, LastActive = r.LastCaptureTick }).ToList(); + return _requirements.Values.Select(r => new { r.AppId, r.TargetFps, r.RealFps, LastActive = r.LastCaptureTick, r.Memo, r.SavePath, r.Handle, r.TargetIp, r.TargetPort, r.Protocol, r.Type }).ToList(); } } \ No newline at end of file diff --git a/SHH.CameraSdk/Core/Scheduling/FrameRequirement.cs b/SHH.CameraSdk/Core/Scheduling/FrameRequirement.cs index 4b6f8a3..6cffd5a 100644 --- a/SHH.CameraSdk/Core/Scheduling/FrameRequirement.cs +++ b/SHH.CameraSdk/Core/Scheduling/FrameRequirement.cs @@ -7,6 +7,36 @@ /// public class FrameRequirement { + public FrameRequirement() + { + } + + public FrameRequirement(SubscriptionDto dto) + { + // 1. 核心标识 + this.AppId = dto.AppId; + this.Type = dto.Type; + + // 2. 流控参数 + this.TargetFps = dto.DisplayFps; + + // 3. 业务动态参数 + this.Memo = dto.Memo; + this.Handle = dto.Handle; + this.RecordDuration = dto.RecordDuration; + this.SavePath = dto.SavePath; + + // 4. 网络转发相关参数 (补全) + this.Protocol = dto.Protocol; + this.TargetIp = dto.TargetIp; + this.TargetPort = dto.TargetPort; + + // 5. 初始化内部统计状态 + this.LastCaptureTick = Environment.TickCount64; + this._lastFpsCalcTick = Environment.TickCount64; + this.IsActive = true; + } + #region --- 订阅者核心标识 (Subscriber Core Identification) --- /// 订阅者唯一ID(如 "Client_A"、"AI_Service"、"WPF_Display_Main") @@ -81,16 +111,20 @@ public class FrameRequirement /// 每当成功分发一帧后调用,内部自动按秒计算 RealFps public void UpdateRealFps() { - _frameCount++; - long currentTick = Environment.TickCount64; - long elapsed = currentTick - _lastFpsCalcTick; - - if (elapsed >= 1000) // 达到 1 秒周期 + try { - RealFps = Math.Round(_frameCount / (elapsed / 1000.0), 1); - _frameCount = 0; - _lastFpsCalcTick = currentTick; + _frameCount++; + long currentTick = Environment.TickCount64; + long elapsed = currentTick - _lastFpsCalcTick; + + if (elapsed >= 1000) // 达到 1 秒周期 + { + RealFps = Math.Round(_frameCount / (elapsed / 1000.0), 1); + _frameCount = 0; + _lastFpsCalcTick = currentTick; + } } + catch{ } } #endregion diff --git a/SHH.CameraSdk/Core/Services/ImageEnhanceCluster.cs b/SHH.CameraSdk/Core/Services/ImageEnhanceCluster.cs index 5b4dc41..73c9099 100644 --- a/SHH.CameraSdk/Core/Services/ImageEnhanceCluster.cs +++ b/SHH.CameraSdk/Core/Services/ImageEnhanceCluster.cs @@ -28,7 +28,7 @@ public class EnhanceWorker : BaseWorker var options = _configManager.GetOptions(deviceId); // 2. 检查开关:如果没开启增强,直接跳过 - if (!options.EnableEnhance) return; + if (!options.EnableBrightness) return; // 3. 确定操作对象 // 策略:如果上一站生成了 TargetMat (缩放图),我们处理缩放图; @@ -36,22 +36,16 @@ public class EnhanceWorker : BaseWorker // 通常 UI 预览场景下,如果不缩放,直接处理 4K 原图会非常卡。 // 建议:仅当 TargetMat 存在时处理,或者强制 clone 一份原图作为 TargetMat - Mat srcMat = frame.TargetMat; - bool createdNew = false; - - // 如果没有 TargetMat (上一站透传了),但开启了增亮 - // 我们必须基于原图生成一个 TargetMat,否则下游 UI 拿不到处理结果 - if (srcMat == null || srcMat.IsDisposed) - { - // 注意:处理 4K 原图非常耗时,生产环境建议这里做个限制 + Mat srcMat; + if (frame.TargetMat != null) + srcMat = frame.TargetMat; + else srcMat = frame.InternalMat; - createdNew = true; // 标记我们需要 Attach 新的 - } // 4. 执行增亮 Mat brightMat = new Mat(); // Alpha=1.0, Beta=配置值 - srcMat.ConvertTo(brightMat, -1, 1.0, options.BrightnessLevel); + srcMat.ConvertTo(brightMat, -1, 1.0, options.Brightness); // 5. 挂载结果 // 这会自动释放上一站生成的旧 TargetMat (如果存在) diff --git a/SHH.CameraSdk/Core/Services/ImageScaleCluster.cs b/SHH.CameraSdk/Core/Services/ImageScaleCluster.cs index 5472571..b1f86d1 100644 --- a/SHH.CameraSdk/Core/Services/ImageScaleCluster.cs +++ b/SHH.CameraSdk/Core/Services/ImageScaleCluster.cs @@ -31,9 +31,12 @@ namespace SHH.CameraSdk // 1. 获取实时配置 (极快,内存读取) var options = _configManager.GetOptions(deviceId); + Mat sourceMat = frame.InternalMat; + if (sourceMat.Empty()) return; + // 2. 原始尺寸 - int srcW = frame.InternalMat.Width; - int srcH = frame.InternalMat.Height; + int srcW = sourceMat.Width; + int srcH = sourceMat.Height; // 3. 目标尺寸 int targetW = options.TargetWidth; diff --git a/SHH.CameraSdk/Core/Services/ProcessingConfigManager.cs b/SHH.CameraSdk/Core/Services/ProcessingConfigManager.cs index a9c9b29..c851d49 100644 --- a/SHH.CameraSdk/Core/Services/ProcessingConfigManager.cs +++ b/SHH.CameraSdk/Core/Services/ProcessingConfigManager.cs @@ -32,6 +32,6 @@ public class ProcessingConfigManager Console.WriteLine($"[ConfigManager] 设备 {deviceId} 预处理参数已更新: " + $"Expand={newOptions.EnableExpand} Shrink:{newOptions.EnableShrink} 分辨率:({newOptions.TargetWidth}x{newOptions.TargetHeight}), " + - $"Enhance={newOptions.EnableEnhance}"); + $"EnableBrightness}}={newOptions.EnableBrightness}"); } } \ No newline at end of file diff --git a/SHH.CameraSdk/Drivers/BaseVideoSource.cs b/SHH.CameraSdk/Drivers/BaseVideoSource.cs index 1fa4de3..0ff2a8f 100644 --- a/SHH.CameraSdk/Drivers/BaseVideoSource.cs +++ b/SHH.CameraSdk/Drivers/BaseVideoSource.cs @@ -18,6 +18,13 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC private volatile bool _isPhysicalOnline; public bool IsPhysicalOnline => _isPhysicalOnline; + /// + /// 图像预处理配置(缩放、增量等) + /// 放置在基类中,确保所有接入协议(HIK/DH/RTSP)均可共享处理逻辑 + /// + public PreprocessConfig PreprocessSettings { get; set; } + = new PreprocessConfig(); + string IDeviceConnectivity.IpAddress => _config.IpAddress; // 允许哨兵从外部更新 _isOnline 字段 @@ -240,6 +247,10 @@ public abstract class BaseVideoSource : IVideoSource, IAsyncDisposable, IDeviceC StreamType = source.StreamType, Transport = source.Transport, ConnectionTimeoutMs = source.ConnectionTimeoutMs, + MainboardIp = source.MainboardIp, + MainboardPort = source.MainboardPort, + RtspPath = source.RtspPath, + RenderHandle = source.RenderHandle, // Dictionary 深拷贝:防止外部修改影响内部 VendorArguments = source.VendorArguments != null ? new Dictionary(source.VendorArguments) diff --git a/SHH.CameraSdk/Drivers/HikVision/HikVideoSource.cs b/SHH.CameraSdk/Drivers/HikVision/HikVideoSource.cs index 2153e56..4a80e1f 100644 --- a/SHH.CameraSdk/Drivers/HikVision/HikVideoSource.cs +++ b/SHH.CameraSdk/Drivers/HikVision/HikVideoSource.cs @@ -228,7 +228,7 @@ public class HikVideoSource : BaseVideoSource { var previewInfo = new HikNativeMethods.NET_DVR_PREVIEWINFO { - hPlayWnd = IntPtr.Zero, + hPlayWnd = (IntPtr)_config.RenderHandle, lChannel = _config.ChannelIndex, dwStreamType = (uint)_config.StreamType, bBlocked = false diff --git a/SHH.CameraSdk/Htmls/CameraControl.html b/SHH.CameraSdk/Htmls/CameraControl.html new file mode 100644 index 0000000..346daa9 --- /dev/null +++ b/SHH.CameraSdk/Htmls/CameraControl.html @@ -0,0 +1,147 @@ + + + + + 设备控制台 + + + + + + +
+ + +
+
+
云台控制 (PTZ)
+
+ + + + + +
+
+ + +
+
长按移动,松开停止
+
+ +
+
+
时间同步
+
设备当前时间:
+
{{ deviceTime || '--:--:--' }}
+
+ + +
+
+ +
+
系统维护
+ +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/SHH.CameraSdk/Htmls/CameraEdit.html b/SHH.CameraSdk/Htmls/CameraEdit.html new file mode 100644 index 0000000..671a4a8 --- /dev/null +++ b/SHH.CameraSdk/Htmls/CameraEdit.html @@ -0,0 +1,422 @@ + + + + + 设备配置 + + + + + + +
+ + +
+
+ +

正在读取配置...

+
+ +
+ +
+
基础信息
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+ +
+
网络与连接 (Network)
+
+ +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+ + +
+
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+
RTSP 流配置
+
+
+ +
+ URL + + + +
+
+ SDK 模式下此为备用地址。如果字段为空,系统将自动生成标准路径。 +
+
+
+ +
+
+
+ + +
+ + + + + + \ No newline at end of file diff --git a/SHH.CameraSdk/Htmls/Diagnostic.html b/SHH.CameraSdk/Htmls/Diagnostic.html new file mode 100644 index 0000000..3f240ac --- /dev/null +++ b/SHH.CameraSdk/Htmls/Diagnostic.html @@ -0,0 +1,133 @@ + + + + + + + + + +
+
+
+ + 全路径诊断中控 ({{ filteredLogs.length }} 条) +
+
+ + + {{ isExpanded ? '▼ 收起' : '▲ 展开' }} + + +
+
+ +
+
+ [{{ log.time }}] + {{ log.method }} + {{ log.url }} + {{ log.status }} + + + 复制 + +
+
+ + 等待数据请求... +
+
+
+ + + + + \ No newline at end of file diff --git a/SHH.CameraSdk/Htmls/Editor.html b/SHH.CameraSdk/Htmls/Editor.html new file mode 100644 index 0000000..aa72d4c --- /dev/null +++ b/SHH.CameraSdk/Htmls/Editor.html @@ -0,0 +1,99 @@ + + + + + + + + +
+
+ +

请选择设备以载入配置

+
+ +
+
+
AI 配置 (ID: {{ deviceId }})
+ +
+ +
+
+ +
+ + +
+
+ + x + +
+
+
+ +
+ + +
+ +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/SHH.CameraSdk/Htmls/EditorTop.html b/SHH.CameraSdk/Htmls/EditorTop.html new file mode 100644 index 0000000..bac86c0 --- /dev/null +++ b/SHH.CameraSdk/Htmls/EditorTop.html @@ -0,0 +1,208 @@ + + + + + 顶部控制栏 + + + + + + + +
+
+ +
+
+
+
{{ statusStyle.text }}
+
ID: {{ cam.id }}
+
+
+
+ +
+
+ 名称 + {{ truncate(cam.name) }} +
+
+ 地址 + {{ cam.ipAddress }} +
+
+
+ +
+
+
分辨率
+
{{ cam.width }}x{{cam.height}}
+
+
+
实时帧率
+
{{ cam.realFps }} FPS
+
+
+ +
+ + + + + + + + + +
+
+ +
+ 请从左侧列表选择设备进行操作 +
+
+ + + + + \ No newline at end of file diff --git a/SHH.CameraSdk/Htmls/List.html b/SHH.CameraSdk/Htmls/List.html new file mode 100644 index 0000000..ff18836 --- /dev/null +++ b/SHH.CameraSdk/Htmls/List.html @@ -0,0 +1,175 @@ + + + + + + + + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/SHH.CameraSdk/Htmls/Main.html b/SHH.CameraSdk/Htmls/Main.html new file mode 100644 index 0000000..00a84c2 --- /dev/null +++ b/SHH.CameraSdk/Htmls/Main.html @@ -0,0 +1,148 @@ + + + + + SHH 视频网关 - 控制底座 + + + + + + + +
+ + +
+
+ +
+ +
+ +
+ +
+ +
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/SHH.CameraSdk/Htmls/Preprocessing.html b/SHH.CameraSdk/Htmls/Preprocessing.html new file mode 100644 index 0000000..10a4dd2 --- /dev/null +++ b/SHH.CameraSdk/Htmls/Preprocessing.html @@ -0,0 +1,375 @@ + + + + + 图像预处理配置 + + + + + + +
+ + +
+ +
+
分辨率控制 (Resolution)
+
+ +
+
+
+ 源画面尺寸: + {{ baseRes.w }} x {{ baseRes.h }} + 缩放比: {{ currentScale }}x +
+
+ +
+
+ + +
当源分辨率大于目标时生效,通常建议开启以节省带宽。
+
+
+
+
+ + +
允许输出比源画面更大的分辨率(可能会增加系统负载)。
+
+
+
+ +
+
+
+ +
+ + +
+
+ +
+
+
+ W + +
+
+
+
+
+ H + +
+
+
+
{{ inputError.msg }}
+
+ +
+ + + {{ p.label }} {{ p.w }}x{{ p.h }} + +
+
+ +
+ +
+ 0.1x + + 2.0x +
+
+ +
+
图像增强 (Enhancement)
+
+ +
+
+
+ + +
+
+ +
+
+
+ 强度 (Intensity) + {{ config.brightness }} +
+
+ + + +
+
+
+
+ +
+
+ + + +
+ + + + + + \ No newline at end of file diff --git a/SHH.CameraSdk/Htmls/Subscription.html b/SHH.CameraSdk/Htmls/Subscription.html new file mode 100644 index 0000000..cdb8587 --- /dev/null +++ b/SHH.CameraSdk/Htmls/Subscription.html @@ -0,0 +1,281 @@ + + + + + 流控订阅配置中心 + + + + + +
+
+
+
+ 订阅策略分发 +
+ +
+
+ + +
+
+ +
+ + FPS +
+
+
+ + +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ + 时长: + + 分钟 +
+
+ +
+ +
+ + : + +
+
+ +
+ +
+ + +
+
+
+
+ +
+
+ 活跃订阅列表 {{ activeSubs.length }} + 定时刷新: {{ lastUpdateTime }} +
+ +
+ +
+
+ {{ sub.appId }} + {{ sub.memo }} +
+ +
+ +
+ {{ translateType(sub.type) }} + 目标: {{ sub.targetFps }} FPS + 实际: {{ (sub.realFps || 0).toFixed(1) }} FPS + +
+ + {{ sub.savePath }} + + + HWND: {{ sub.handle }} + + + {{ sub.targetIp }}:{{ sub.targetPort }} + + + 本地渲染 + +
+
+
+ +
+ 暂无活跃订阅需求,请在上方录入策略 +
+
+
+
+ + + + + + + \ No newline at end of file