using System.Net; using System.Collections.Concurrent; using Peripherals.HdmiInClient; using Peripherals.JpegClient; namespace server.Services; public class HdmiVideoStreamEndpoint { public string BoardId { get; set; } = ""; public string MjpegUrl { get; set; } = ""; public string VideoUrl { get; set; } = ""; public string SnapshotUrl { get; set; } = ""; } public class HdmiVideoStreamClient { public required HdmiIn HdmiInClient { get; set; } public required Jpeg JpegClient { get; set; } public required CancellationTokenSource CTS { get; set; } public required int Offset { get; set; } public int Width { get; set; } public int Height { get; set; } } public class HttpHdmiVideoStreamService : BackgroundService { private readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); private HttpListener? _httpListener; private readonly int _serverPort = 4322; private readonly ConcurrentDictionary _clientDict = new(); public override async Task StartAsync(CancellationToken cancellationToken) { _httpListener = new HttpListener(); _httpListener.Prefixes.Add($"http://{Global.LocalHost}:{_serverPort}/"); _httpListener.Start(); logger.Info($"HDMI Video Stream Service started on port {_serverPort}"); await base.StartAsync(cancellationToken); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { if (_httpListener == null) continue; try { logger.Debug("Waiting for HTTP request..."); var contextTask = _httpListener.GetContextAsync(); var completedTask = await Task.WhenAny(contextTask, Task.Delay(-1, stoppingToken)); if (completedTask == contextTask) { var context = contextTask.Result; logger.Debug($"Received request: {context.Request.Url?.AbsolutePath}"); if (context != null) _ = HandleRequestAsync(context, stoppingToken); } else { break; } } catch (Exception ex) { logger.Error(ex, "Error in GetContextAsync"); break; } } } public override async Task StopAsync(CancellationToken cancellationToken) { logger.Info("Stopping HDMI Video Stream Service..."); // 禁用所有活跃的HDMI传输 var disableTasks = new List(); foreach (var hdmiKey in _clientDict.Keys) { disableTasks.Add(DisableHdmiTransmissionAsync(hdmiKey)); } // 等待所有禁用操作完成 await Task.WhenAll(disableTasks); // 清空字典 _clientDict.Clear(); await base.StopAsync(cancellationToken); } public async Task DisableHdmiTransmissionAsync(string key) { try { var client = _clientDict[key]; client.CTS.Cancel(); var disableResult = await client.JpegClient.SetEnable(false); if (disableResult) { logger.Info("Successfully disabled HDMI transmission"); } else { logger.Error($"Failed to disable HDMI transmission"); } } catch (Exception ex) { logger.Error(ex, "Exception occurred while disabling HDMI transmission"); } } private async Task GetOrCreateClientAsync(string boardId) { if (_clientDict.TryGetValue(boardId, out var client)) { client.Width = client.JpegClient.Width; client.Height = client.JpegClient.Height; return client; } var userManager = new Database.UserManager(); var boardRet = userManager.GetBoardByID(Guid.Parse(boardId)); if (!boardRet.IsSuccessful || !boardRet.Value.HasValue) { logger.Error($"Failed to get board with ID {boardId}"); return null; } var board = boardRet.Value.Value; client = new HdmiVideoStreamClient() { HdmiInClient = new HdmiIn(board.IpAddr, board.Port, 1), JpegClient = new Jpeg(board.IpAddr, board.Port, 1), CTS = new CancellationTokenSource(), Offset = 0 }; // 启用HDMI传输 try { // var hdmiEnableRet = await client.JpegClient.EnableTrans(true); // if (!hdmiEnableRet.IsSuccessful) // { // logger.Error($"Failed to enable HDMI transmission for board {boardId}: {hdmiEnableRet.Error}"); // return null; // } // logger.Info($"Successfully enabled HDMI transmission for board {boardId}"); var jpegEnableRet = await client.JpegClient.Init(true); if (!jpegEnableRet.IsSuccessful) { logger.Error($"Failed to enable JPEG transmission for board {boardId}: {jpegEnableRet.Error}"); return null; } logger.Info($"Successfully enabled JPEG transmission for board {boardId}"); client.Width = client.JpegClient.Width; client.Height = client.JpegClient.Height; } catch (Exception ex) { logger.Error(ex, $"Exception occurred while enabling HDMI transmission for board {boardId}"); return null; } _clientDict[boardId] = client; return client; } private async Task HandleRequestAsync(HttpListenerContext context, CancellationToken cancellationToken) { var path = context.Request.Url?.AbsolutePath ?? "/"; var boardId = context.Request.QueryString["boardId"]; if (string.IsNullOrEmpty(boardId)) { await SendErrorAsync(context.Response, "Missing boardId"); return; } var client = await GetOrCreateClientAsync(boardId); if (client == null) { await SendErrorAsync(context.Response, "Invalid boardId or board not available"); return; } var hdmiInToken = _clientDict[boardId].CTS.Token; if (path == "/snapshot") { await HandleSnapshotRequestAsync(context.Response, client, hdmiInToken); } else if (path == "/mjpeg") { await HandleMjpegStreamAsync(context.Response, client, hdmiInToken); } else if (path == "/video") { await SendVideoHtmlPageAsync(context.Response, boardId); } else { await SendIndexHtmlPageAsync(context.Response, boardId); } } private async Task HandleSnapshotRequestAsync( HttpListenerResponse response, HdmiVideoStreamClient client, CancellationToken cancellationToken) { try { logger.Debug("处理HDMI快照请求"); // 从HDMI读取RGB565数据 var frameResult = await client.JpegClient.GetMultiFrames((uint)client.Offset); if (!frameResult.IsSuccessful || frameResult.Value == null || frameResult.Value.Count == 0) { logger.Error("HDMI快照获取失败"); response.StatusCode = 500; var errorBytes = System.Text.Encoding.UTF8.GetBytes("Failed to get HDMI snapshot"); await response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length, cancellationToken); response.Close(); return; } var jpegData = frameResult.Value[0]; var jpegImage = Common.Image.CompleteJpegData(jpegData, client.Width, client.Height); if (!jpegImage.IsSuccessful) { logger.Error("JPEG数据补全失败"); response.StatusCode = 500; var errorBytes = System.Text.Encoding.UTF8.GetBytes("Failed to complete JPEG data"); await response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length, cancellationToken); response.Close(); return; } // 设置响应头(参考Camera版本) response.ContentType = "image/jpeg"; response.ContentLength64 = jpegImage.Value.Length; response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate"); await response.OutputStream.WriteAsync(jpegImage.Value, 0, jpegImage.Value.Length, cancellationToken); await response.OutputStream.FlushAsync(cancellationToken); logger.Debug("已发送HDMI快照图像,大小:{Size} 字节", jpegImage.Value.Length); } catch (Exception ex) { logger.Error(ex, "处理HDMI快照请求时出错"); response.StatusCode = 500; } finally { response.Close(); } } private async Task HandleMjpegStreamAsync( HttpListenerResponse response, HdmiVideoStreamClient client, CancellationToken cancellationToken) { try { // 设置MJPEG流的响应头(参考Camera版本) response.ContentType = "multipart/x-mixed-replace; boundary=--boundary"; response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate"); response.Headers.Add("Pragma", "no-cache"); response.Headers.Add("Expires", "0"); logger.Debug("开始HDMI MJPEG流传输"); int frameCounter = 0; while (!cancellationToken.IsCancellationRequested) { var frameStartTime = DateTime.UtcNow; var frameResult = await client.JpegClient.GetMultiFrames((uint)client.Offset); if (!frameResult.IsSuccessful || frameResult.Value == null || frameResult.Value.Count == 0) { logger.Error("获取HDMI帧失败"); await Task.Delay(100, cancellationToken); continue; } foreach (var framebytes in frameResult.Value) { var jpegImage = Common.Image.CompleteJpegData(framebytes, client.Width, client.Height); if (!jpegImage.IsSuccessful) { logger.Error("JPEG数据不完整"); await Task.Delay(100, cancellationToken); continue; } var frameRet = Common.Image.CreateMjpegFrameFromJpeg(jpegImage.Value); if (!frameRet.IsSuccessful) { logger.Error("创建MJPEG帧失败"); await Task.Delay(100, cancellationToken); continue; } var frame = frameRet.Value; await response.OutputStream.WriteAsync(frame.header, 0, frame.header.Length, cancellationToken); await response.OutputStream.WriteAsync(frame.data, 0, frame.data.Length, cancellationToken); await response.OutputStream.WriteAsync(frame.footer, 0, frame.footer.Length, cancellationToken); await response.OutputStream.FlushAsync(cancellationToken); frameCounter++; var totalTime = (DateTime.UtcNow - frameStartTime).TotalMilliseconds; // 性能统计日志(每30帧记录一次) if (frameCounter % 30 == 0) { logger.Debug("HDMI帧 {FrameNumber} 性能统计 - 总计: {TotalTime:F1}ms, JPEG大小: {JpegSize} 字节", frameCounter, totalTime, frame.data.Length); } } } } catch (Exception ex) { logger.Error(ex, "HDMI MJPEG流处理异常"); } finally { try { // 停止传输时禁用HDMI传输 await client.HdmiInClient.EnableTrans(false); logger.Info("已禁用HDMI传输"); } catch (Exception ex) { logger.Error(ex, "禁用HDMI传输时出错"); } try { response.Close(); } catch { // 忽略关闭时的错误 } logger.Debug("HDMI MJPEG流连接已关闭"); } } private async Task SendVideoHtmlPageAsync(HttpListenerResponse response, string boardId) { string html = $@"

HDMI Video Stream for Board {boardId}

"; response.ContentType = "text/html"; await response.OutputStream.WriteAsync(System.Text.Encoding.UTF8.GetBytes(html)); response.Close(); } private async Task SendIndexHtmlPageAsync(HttpListenerResponse response, string boardId) { string html = $@"

Welcome to HDMI Video Stream Service

View Video Stream for Board {boardId} "; response.ContentType = "text/html"; await response.OutputStream.WriteAsync(System.Text.Encoding.UTF8.GetBytes(html)); response.Close(); } private async Task SendErrorAsync(HttpListenerResponse response, string message) { response.StatusCode = 400; await response.OutputStream.WriteAsync(System.Text.Encoding.UTF8.GetBytes(message)); response.Close(); } /// /// 获取所有可用的HDMI视频流终端点 /// /// 返回所有可用的HDMI视频流终端点列表 public List? GetAllVideoEndpoints() { var userManager = new Database.UserManager(); var boards = userManager.GetAllBoard(); if (boards == null) return null; var endpoints = new List(); foreach (var board in boards) { endpoints.Add(new HdmiVideoStreamEndpoint { BoardId = board.ID.ToString(), MjpegUrl = $"http://{Global.LocalHost}:{_serverPort}/mjpeg?boardId={board.ID}", VideoUrl = $"http://{Global.LocalHost}:{_serverPort}/video?boardId={board.ID}", SnapshotUrl = $"http://{Global.LocalHost}:{_serverPort}/snapshot?boardId={board.ID}" }); } return endpoints; } /// /// 获取指定板卡ID的HDMI视频流终端点 /// /// 板卡ID /// 返回指定板卡的HDMI视频流终端点 public HdmiVideoStreamEndpoint GetVideoEndpoint(string boardId) { return new HdmiVideoStreamEndpoint { BoardId = boardId, MjpegUrl = $"http://{Global.LocalHost}:{_serverPort}/mjpeg?boardId={boardId}", VideoUrl = $"http://{Global.LocalHost}:{_serverPort}/video?boardId={boardId}", SnapshotUrl = $"http://{Global.LocalHost}:{_serverPort}/snapshot?boardId={boardId}" }; } }