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();
}
}