using SHH.CameraSdk; using System.Collections.Concurrent; namespace SHH.CameraService; /// /// 网络推流管理器 /// 职责:管理 ZeroMQ 推流任务的生命周期 /// 类似于 DisplayWindowManager,它负责订阅数据并将其桥接到传输层 /// public class NetworkStreamManager { private readonly VideoDataChannel _channel; // 记录当前活跃的推流任务,防止重复订阅 private readonly ConcurrentDictionary _activeStreams = new(); public NetworkStreamManager(VideoDataChannel channel) { _channel = channel; } /// /// 启动推流任务 /// public void StartStream(string appId, long deviceId) { // 1. 防止重复启动 if (_activeStreams.ContainsKey(appId)) return; // 2. 向全局分发器订阅精准数据 // 这里实现了业务逻辑的闭环:只有被 Manager 管理的任务才会消耗 CPU 去转码 GlobalStreamDispatcher.Subscribe(appId, deviceId, (frame) => { // --- 这里的代码运行在分发线程中 --- // A. 转码 (耗时操作封装在这里,不污染 Controller) byte[] jpgBytes = EncodeFrameToJpg(frame); if (jpgBytes != null && jpgBytes.Length > 0) { var payload = new VideoPayload { CameraId = appId, // 使用 AppId 作为 Topic (给 Dashboard 订阅用) OriginalImageBytes = jpgBytes, CaptureTime = DateTime.Now, OriginalWidth = frame.TargetWidth, OriginalHeight = frame.TargetHeight }; // B. 写入传输通道 _ = _channel.WriteAsync(payload); } }); _activeStreams.TryAdd(appId, true); Console.WriteLine($"[Network] 推流任务已启动: {appId} -> Device {deviceId}"); } /// /// 停止推流任务 /// public void StopStream(string appId) { if (_activeStreams.TryRemove(appId, out _)) { // 1. 从全局分发器注销 GlobalStreamDispatcher.Unsubscribe(appId); Console.WriteLine($"[Network] 推流任务已停止: {appId}"); } } // --- 辅助方法 --- private byte[] EncodeFrameToJpg(SmartFrame frame) { try { // 优先使用处理后的 TargetMat,如果没有则用原始的 InternalMat var mat = frame.TargetMat ?? frame.InternalMat; if (mat != null && !mat.Empty()) { // 80 质量平衡体积与画质 return mat.ImEncode(".jpg", new int[] { 1, 80 }); } } catch (Exception ex) { Console.WriteLine($"[Network] 转码失败: {ex.Message}"); } return Array.Empty(); } }