91 lines
2.9 KiB
C#
91 lines
2.9 KiB
C#
using SHH.CameraSdk;
|
||
using System.Collections.Concurrent;
|
||
|
||
namespace SHH.CameraService;
|
||
|
||
/// <summary>
|
||
/// 网络推流管理器
|
||
/// 职责:管理 ZeroMQ 推流任务的生命周期
|
||
/// 类似于 DisplayWindowManager,它负责订阅数据并将其桥接到传输层
|
||
/// </summary>
|
||
public class NetworkStreamManager
|
||
{
|
||
private readonly VideoDataChannel _channel;
|
||
// 记录当前活跃的推流任务,防止重复订阅
|
||
private readonly ConcurrentDictionary<string, bool> _activeStreams = new();
|
||
|
||
public NetworkStreamManager(VideoDataChannel channel)
|
||
{
|
||
_channel = channel;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 启动推流任务
|
||
/// </summary>
|
||
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}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止推流任务
|
||
/// </summary>
|
||
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<byte>();
|
||
}
|
||
} |