视频SDK新协议签入

This commit is contained in:
2026-01-15 09:31:57 +08:00
parent 3f8e42e560
commit 81580a8f55
14 changed files with 844 additions and 472 deletions

View File

@@ -1,7 +1,10 @@
using Microsoft.AspNetCore.Builder;
using Grpc.Net.Client;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using SHH.CameraSdk;
using SHH.Contracts.Grpc;
using Microsoft.Extensions.Logging;
namespace SHH.CameraService;
@@ -9,131 +12,138 @@ public class Program
{
public static async Task Main(string[] args)
{
// 1. 理由:缓冲时间 10 秒, 供附加调试工具使用
for (var i = 1; i < 10; i++)
Thread.Sleep(1000);
// =============================================================
// 2. 基础环境与配置 (理由:明确身份 ID 和 监听端口)
// =============================================================
var config = ServiceConfig.BuildFromArgs(args);
// 硬件预热 (理由:确保底层驱动库在 Web 容器启动前完全就绪)
// 2. 硬件预热 (静态方法保留)
HikNativeMethods.NET_DVR_Init();
HikSdkManager.ForceWarmUp();
// 1. [核心环境] 必须在所有网络操作前开启
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
// 2. 模拟/解析配置
if (args.Length == 0)
{
string serviceArgs = "--appid CameraApp_01 " +
"--uris localhost,9001,video,调试PC; " +
"--uris localhost,9001,command,调试PC; " +
"--mode 1 --ports 5000,100";
args = serviceArgs.Split(' ', StringSplitOptions.RemoveEmptyEntries);
}
var config = ServiceConfig.BuildFromArgs(args);
// =============================================================
// 3. 【强行复刻成功逻辑】 在 Web 容器启动前直接执行注册
// =============================================================
if (config.CommandEndpoints.Any())
{
try
{
// 将 tcp:// 转换为 http:// 以适配 gRPC
string targetUrl = config.CommandEndpoints.First().Uri.Replace("tcp://", "http://");
using var channel = GrpcChannel.ForAddress(targetUrl);
var client = new GatewayProvider.GatewayProviderClient(channel);
Console.WriteLine($"[gRPC] 正在执行预注册 (环境: 纯净): {targetUrl}");
var resp = await client.RegisterInstanceAsync(new RegisterRequest
{
InstanceId = config.AppId,
Version = "2.0.0-grpc",
ServerIp = "127.0.0.1",
WebApiPort = config.BasePort,
StartTimeTicks = DateTime.Now.Ticks,
ProcessId = Environment.ProcessId,
Description = "Camera Service"
});
Console.WriteLine($"[gRPC] 预注册成功: {resp.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"[gRPC] 预注册尝试失败 (不影响启动): {ex.Message}");
}
}
// =============================================================
// 4. 构建 Web 主机环境
// =============================================================
var builder = WebApplication.CreateBuilder(args);
// =============================================================
// 3. 依赖注入注册 (DI)
// =============================================================
// 基础业务单例注册
builder.Services.AddSingleton(config);
// 注册缩放与增亮业务(不注册则不实现)
builder.Services.AddSingleton<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>();
// 接入 SDK 核心逻辑
builder.Services.AddCameraSdk(config.NumericId);
// 注册后台引擎 (理由:托管长周期的硬件状态监控)
builder.Services.AddHostedService<CameraEngineWorker>();
// ★ 注册 gRPC 版本的状态监控工作者 (不讲道理,直接注册)
builder.Services.AddHostedService<DeviceStateMonitorWorker>();
// 配置 Web 相关的服务
ConfigureWebServices(builder, config);
// 配置进程守护
builder.Services.AddHostedService<ParentProcessSentinel>();
builder.Services.AddHostedService<GrpcCommandReceiverWorker>();
// =============================================================
// 4. 接受启动传参, 并支持将视频进行网络广播
// 5. 视频流 Target 注册 (gRPC 模式)
// =============================================================
// 1. 读取配置创建 targets (可以是 1 个,也可以是 10 个)
var netTargets = new List<StreamTarget>();
if (config.VideoEndpoints != null)
{
foreach(var cfgVideo in config.VideoEndpoints)
foreach (var cfgVideo in config.VideoEndpoints)
{
netTargets.Add(new StreamTarget(new PushTargetConfig
{
Name = cfgVideo.Description, Endpoint = cfgVideo.Uri, QueueCapacity = 10,
Name = cfgVideo.Description,
Endpoint = cfgVideo.Uri,
QueueCapacity = 10,
}));
}
}
// 2. 注册 Targets (供采集者用)
builder.Services.AddSingleton<IEnumerable<StreamTarget>>(netTargets);
// 3. 注册采集者 (它会注入上面的 targets进行编码和分发)
builder.Services.AddHostedService<NetworkStreamingWorker>();
// 5. 为每个 Target 注册一个独立的发送者
// 为每个 Target 绑定一个 gRPC 流发送者
foreach (var target in netTargets)
{
builder.Services.AddHostedService(sp => new NetMqSenderWorker(target));
builder.Services.AddHostedService(sp =>
new GrpcSenderWorker(target, sp.GetRequiredService<ILogger<GrpcSenderWorker>>()));
}
// =============================================================
// 5. 命令管道配置
// =============================================================
// 2. 注册管道管理器
// 注册指令分发 (不再使用 NetMQ 的 CommandClientWorker)
builder.Services.AddSingleton<InterceptorPipeline>();
// 负责连接 Dashboard注册身份接收重启/控制指令
builder.Services.AddHostedService<CommandClientWorker>();
// 1. 注册分发器
builder.Services.AddSingleton<CommandDispatcher>();
// 2. 注册具体的指令处理器 (每写一个新的 Handler就在这里注册一下或者用反射批量注册)
builder.Services.AddSingleton<ICommandHandler, SyncCameraHandler>();
builder.Services.AddSingleton<ICommandHandler, RemoveCameraHandler>();
ConfigureWebServices(builder, config);
// =============================================================
// 6. 构建与管道配置
// 6. 启动服务
// =============================================================
var app = builder.Build();
// 核心修复:同步点火逻辑 (理由:在 Web 开启前完成设备池的初步构建)
// 激活 SDK 管理器并启动业务点火
await StartBusinessLogic(app);
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", $"SHH Gateway #{config.AppId}");
});
app.MapGet("/", () => $"SHH Gateway {config.AppId} is running.");
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", $"SHH Gateway #{config.AppId}"));
app.MapGet("/", () => $"SHH Gateway {config.AppId} is running (gRPC Mode).");
app.UseCors("AllowAll");
// 理由:正式映射控制器路由
app.MapControllers();
// =============================================================
// 5. 正式启动
// =============================================================
Console.WriteLine($"[System] 正在启动 Web 服务,监听端口: {config.BasePort}");
await app.RunAsync($"http://0.0.0.0:{config.BasePort}");
}
/// <summary>
/// 对齐业务启动:激活单例并启动相机管理器
/// 激活单例并启动相机管理器
/// </summary>
static async Task StartBusinessLogic(WebApplication app)
{
var manager = app.Services.GetRequiredService<CameraManager>();
// 激活哨兵逻辑 (理由:显式 Get 触发单例构造,否则不工作)
_ = app.Services.GetRequiredService<ConnectivitySentinel>();
// 启动相机任务加载
await manager.StartAsync();
Console.WriteLine("[System] 核心业务逻辑已激活。");
}
@@ -147,14 +157,12 @@ public class Program
options.AddPolicy("AllowAll", p => p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
});
// ★★★★★ 补全点:跨项目控制器加载 ★★★★★
// 理由Controller 定义在 SDK 项目中,必须通过 AddApplicationPart 显式挂载
builder.Services.AddControllers(options =>
{
options.Filters.Add<UserActionFilter>();
})
.AddApplicationPart(typeof(CamerasController).Assembly) // 必备:加载相机控制接口
.AddApplicationPart(typeof(MonitorController).Assembly); // 必备:加载监控接口
.AddApplicationPart(typeof(CamerasController).Assembly)
.AddApplicationPart(typeof(MonitorController).Assembly);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>