增加了通过网络主动上报图像的支持

增加了指令维护通道的支持
This commit is contained in:
2026-01-07 10:59:03 +08:00
parent a697aab3e0
commit 3d47c8f009
47 changed files with 1613 additions and 1734 deletions

View File

@@ -1,7 +1,7 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using SHH.CameraSdk; // 引用你的业务核心
using SHH.CameraSdk;
namespace SHH.CameraService;
@@ -9,163 +9,152 @@ public class Program
{
public static async Task Main(string[] args)
{
// 缓冲时间 (您之前写了20000ms即20秒可能是为了附加调试器。如果觉得太慢可以改回 2000)
for(var i=1; i<10; i++)
// 1. 理由:缓冲时间 10 秒, 供附加调试工具使用
for (var i = 1; i < 10; i++)
Thread.Sleep(1000);
// 1. 解析配置
// =============================================================
// 2. 基础环境与配置 (理由:明确身份 ID 和 监听端口)
// =============================================================
var config = ServiceConfig.BuildFromArgs(args);
// ---【补全变量定义】---
// A. 补全 webPort (统一使用 config.BasePort)
int webPort = config.BasePort;
// B. 补全 processIdInt (用于 FileStorage 和 CameraSdk)
// 逻辑:尝试将 AppId 解析为数字;如果 AppId 是字符串(如"CameraApp_01"),则默认给 1或者根据 BasePort 推算
int processIdInt = config.NumericId;
Console.Title = $"SHH Gateway - {config.AppId} (Web: {webPort})";
#region --- 2. ---
InitHardwareEnv();
#endregion
#region --- 3. WebHost ---
// 硬件预热 (理由:确保底层驱动库在 Web 容器启动前完全就绪)
HikNativeMethods.NET_DVR_Init();
HikSdkManager.ForceWarmUp();
var builder = WebApplication.CreateBuilder(args);
// ★★★ 核心:注入全局配置 ★★★
// =============================================================
// 3. 依赖注入注册 (DI)
// =============================================================
builder.Services.AddSingleton(config);
// -------------------------------------------------------------
// A. 注册新架构组件
// -------------------------------------------------------------
builder.Services.AddSingleton<VideoDataChannel>();
// 推流服务 (连接 config.TargetClients 里的 :6002)
builder.Services.AddHostedService<ZeroMQBridgeWorker>();
// 指令客户端 (连接 config.TargetClients 里的 :6001)
builder.Services.AddHostedService<CommandClientWorker>();
// 进程守护
builder.Services.AddHostedService<ParentProcessSentinel>();
// -------------------------------------------------------------
// B. 注册 SDK 业务服务
// -------------------------------------------------------------
// 使用刚刚补全的 processIdInt
builder.Services.AddSingleton<IStorageService>(new FileStorageService(processIdInt));
builder.Services.AddSingleton<CameraManager>();
// 注册缩放与增亮业务(不注册则不实现)
builder.Services.AddSingleton<ProcessingConfigManager>();
builder.Services.AddSingleton<DisplayWindowManager>();
builder.Services.AddSingleton<NetworkStreamManager>();
builder.Services.AddSingleton<ImageScaleCluster>(sp => new ImageScaleCluster(4, sp.GetRequiredService<ProcessingConfigManager>()));
builder.Services.AddSingleton<ImageEnhanceCluster>(sp => new ImageEnhanceCluster(4, sp.GetRequiredService<ProcessingConfigManager>()));
builder.Services.AddSingleton(sp => new ImageScaleCluster(4, sp.GetRequiredService<ProcessingConfigManager>()));
builder.Services.AddSingleton(sp => new ImageEnhanceCluster(4, sp.GetRequiredService<ProcessingConfigManager>()));
builder.Services.AddHostedService<PipelineConfigurator>();
// 使用补全的 processIdInt
builder.Services.AddCameraSdk(processIdInt);
// 接入 SDK 核心逻辑
builder.Services.AddCameraSdk(config.NumericId);
// 注册后台引擎 (理由:托管长周期的硬件状态监控)
builder.Services.AddHostedService<CameraEngineWorker>();
builder.Services.AddSingleton<ConnectivitySentinel>();
builder.Services.AddControllers().AddApplicationPart(typeof(CamerasController).Assembly);
builder.Services.AddControllers().AddApplicationPart(typeof(MonitorController).Assembly);
// 配置 Web 相关的服务
ConfigureWebServices(builder, config);
// -------------------------------------------------------------
// C. Web API 基础
// -------------------------------------------------------------
builder.Services.AddControllers().AddControllersAsServices();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
// 配置进程守护
builder.Services.AddHostedService<ParentProcessSentinel>();
// =============================================================
// 4. 接受启动传参, 并支持将视频进行网络广播
// =============================================================
// 1. 读取配置创建 targets (可以是 1 个,也可以是 10 个)
var netTargets = new List<StreamTarget>();
if (config.VideoEndpoints != null)
{
// 【修正】使用 config.AppId
c.SwaggerDoc("v1", new OpenApiInfo { Title = $"Gateway {config.AppId}", Version = "v1" });
});
foreach(var cfgVideo in config.VideoEndpoints)
{
netTargets.Add(new StreamTarget(new PushTargetConfig
{
Name = cfgVideo.Description, Endpoint = cfgVideo.Uri, QueueCapacity = 10,
}));
}
}
// 2. 注册 Targets (供采集者用)
builder.Services.AddSingleton<IEnumerable<StreamTarget>>(netTargets);
builder.Services.AddCors(o => o.AddPolicy("AllowAll", p => p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()));
// 3. 注册采集者 (它会注入上面的 targets进行编码和分发)
builder.Services.AddHostedService<NetworkStreamingWorker>();
// 4. 为每个 Target 注册一个独立的发送者
foreach (var target in netTargets)
{
builder.Services.AddHostedService(sp => new NetMqSenderWorker(target));
}
// =============================================================
// 5. 命令管道配置
// =============================================================
// 负责连接 Dashboard注册身份接收重启/控制指令
builder.Services.AddHostedService<CommandClientWorker>();
// 1. 注册分发器
builder.Services.AddSingleton<CommandDispatcher>();
// 2. 注册具体的指令处理器 (每写一个新的 Handler就在这里注册一下或者用反射批量注册)
builder.Services.AddSingleton<ICommandHandler, SyncCameraHandler>();
// =============================================================
// 6. 构建与管道配置
// =============================================================
var app = builder.Build();
//// =======================================================================
//// ★★★ 核心接入点:连接 [现有分发器] 与 [新推流通道] ★★★
//// =======================================================================
//// 1. 获取刚刚注册的数据通道
//var videoChannel = app.Services.GetRequiredService<VideoDataChannel>();
////var config = app.Services.GetRequiredService<ServiceConfig>();
//// 2. 订阅你现有的全局事件 (这里就是“取货”的地方)
//// 每当 HikVideoSource 采集到一帧并调用 Dispatch 时,这里就会触发
//GlobalStreamDispatcher.OnGlobalFrame += (deviceId, smartFrame) =>
//{
// // 3. 数据处理:将 OpenCvSharp Mat 转为 JPG 字节流 (网络传输必须压缩)
// byte[] jpgData = EncodeToJpg(smartFrame);
// if (jpgData != null && jpgData.Length > 0)
// {
// // 4. 封装载荷
// var payload = new VideoPayload
// {
// // 使用 AppId 或 DeviceId 作为标识
// CameraId = config.AppId,
// OriginalImageBytes = jpgData,
// CaptureTime = DateTime.Now,
// OriginalWidth = smartFrame.TargetWidth,
// OriginalHeight = smartFrame.TargetHeight
// };
// // 5. 扔进通道 (Fire-and-Forget不阻塞你原来的显示逻辑)
// // WriteAsync 是 ValueTask这里忽略等待追求最高吞吐
// _ = videoChannel.WriteAsync(payload);
// }
//};
//Console.WriteLine("[System] 全局流已桥接到 ZeroMQ 推流通道");
// 核心修复:同步点火逻辑 (理由:在 Web 开启前完成设备池的初步构建)
await StartBusinessLogic(app);
app.UseSwagger();
app.UseSwaggerUI();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", $"SHH Gateway #{config.AppId}");
});
app.MapGet("/", () => $"SHH Gateway {config.AppId} is running.");
app.UseCors("AllowAll");
// 理由:正式映射控制器路由
app.MapControllers();
// 【修正】使用 webPort
Console.WriteLine($"[System] Web API 已启动: http://0.0.0.0:{webPort}");
await app.RunAsync($"http://0.0.0.0:{webPort}");
#endregion
}
static void InitHardwareEnv()
{
Console.WriteLine("=== 工业级视频接入服务启动 ===");
// =============================================================
// 5. 正式启动
// =============================================================
await app.RunAsync($"http://0.0.0.0:{config.BasePort}");
}
/// <summary>
/// 内存转码Mat -> Jpg Bytes
/// 对齐业务启动:激活单例并启动相机管理器
/// </summary>
static byte[] EncodeToJpg(SmartFrame frame)
static async Task StartBusinessLogic(WebApplication app)
{
try
var manager = app.Services.GetRequiredService<CameraManager>();
// 激活哨兵逻辑 (理由:显式 Get 触发单例构造,否则不工作)
_ = app.Services.GetRequiredService<ConnectivitySentinel>();
// 启动相机任务加载
await manager.StartAsync();
Console.WriteLine("[System] 核心业务逻辑已激活。");
}
/// <summary>
/// 注册 Web API 支持
/// </summary>
static void ConfigureWebServices(WebApplicationBuilder builder, ServiceConfig cfg)
{
builder.Services.AddCors(options =>
{
// 假设 SmartFrame 内部持有 OpenCvSharp.Mat 类型的 InternalMat
if (frame != null && frame.InternalMat != null && !frame.InternalMat.Empty())
{
// 80 是 JPG 质量参数,平衡画质与带宽
return frame.InternalMat.ImEncode(".jpg", new int[] { 1, 80 });
}
}
catch
options.AddPolicy("AllowAll", p => p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
});
// ★★★★★ 补全点:跨项目控制器加载 ★★★★★
// 理由Controller 定义在 SDK 项目中,必须通过 AddApplicationPart 显式挂载
builder.Services.AddControllers(options =>
{
// 容错处理,防止一帧损坏导致程序崩溃
}
return Array.Empty<byte>();
options.Filters.Add<UserActionFilter>();
})
.AddApplicationPart(typeof(CamerasController).Assembly) // 必备:加载相机控制接口
.AddApplicationPart(typeof(MonitorController).Assembly); // 必备:加载监控接口
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = $"SHH Gateway #{cfg.AppId}", Version = "v1" });
});
}
}