From d2508f64840a5f28f972b0869aa6b2cda94e6b40 Mon Sep 17 00:00:00 2001 From: SikongJueluo Date: Thu, 7 Aug 2025 15:16:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E6=B5=81=E5=90=8E=E7=AB=AF=E6=9C=8D=E5=8A=A1=EF=BC=8C=E4=BD=BF?= =?UTF-8?q?=E5=85=B6=E9=80=82=E9=85=8Djpeg=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/Peripherals/HdmiInClient.cs | 45 ++++++ .../Services/HttpHdmiVideoStreamService.cs | 147 ++++-------------- server/src/Services/HttpVideoStreamService.cs | 37 +---- server/src/Services/ProgressTrackerService.cs | 2 +- 4 files changed, 73 insertions(+), 158 deletions(-) diff --git a/server/src/Peripherals/HdmiInClient.cs b/server/src/Peripherals/HdmiInClient.cs index 1c1090f..9b5080c 100644 --- a/server/src/Peripherals/HdmiInClient.cs +++ b/server/src/Peripherals/HdmiInClient.cs @@ -100,6 +100,51 @@ class HdmiIn return result.Value; } + public async ValueTask<(byte[] header, byte[] data, byte[] footer)?> GetMJpegFrame() + { + // 从HDMI读取RGB24数据 + var readStartTime = DateTime.UtcNow; + var frameResult = await ReadFrame(); + var readEndTime = DateTime.UtcNow; + var readTime = (readEndTime - readStartTime).TotalMilliseconds; + + if (!frameResult.IsSuccessful || frameResult.Value == null) + { + logger.Warn("HDMI帧读取失败或为空"); + return null; + } + + var rgb24Data = frameResult.Value; + + // 验证数据长度是否正确 (RGB24为每像素2字节) + var expectedLength = _currentWidth * _currentHeight * 2; + if (rgb24Data.Length != expectedLength) + { + logger.Warn("HDMI数据长度不匹配,期望: {Expected}, 实际: {Actual}", + expectedLength, rgb24Data.Length); + } + + // 将RGB24转换为JPEG(参考Camera版本的处理) + var jpegStartTime = DateTime.UtcNow; + var jpegResult = Common.Image.ConvertRGB24ToJpeg(rgb24Data, _currentWidth, _currentHeight, 80); + var jpegEndTime = DateTime.UtcNow; + var jpegTime = (jpegEndTime - jpegStartTime).TotalMilliseconds; + + if (!jpegResult.IsSuccessful) + { + logger.Error("HDMI RGB24转JPEG失败: {Error}", jpegResult.Error); + return null; + } + + var jpegData = jpegResult.Value; + + // 发送MJPEG帧(使用Camera版本的格式) + var mjpegFrameHeader = Common.Image.CreateMjpegFrameHeader(jpegData.Length); + var mjpegFrameFooter = Common.Image.CreateMjpegFrameFooter(); + + return (mjpegFrameHeader, jpegData, mjpegFrameFooter); + } + /// /// 获取当前分辨率 /// diff --git a/server/src/Services/HttpHdmiVideoStreamService.cs b/server/src/Services/HttpHdmiVideoStreamService.cs index c773d57..a19aacf 100644 --- a/server/src/Services/HttpHdmiVideoStreamService.cs +++ b/server/src/Services/HttpHdmiVideoStreamService.cs @@ -32,46 +32,38 @@ public class HttpHdmiVideoStreamService : BackgroundService protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - try + while (!stoppingToken.IsCancellationRequested) { - while (!stoppingToken.IsCancellationRequested) + if (_httpListener == null) continue; + try { - HttpListenerContext? context = null; - try + logger.Debug("Waiting for HTTP request..."); + var contextTask = _httpListener.GetContextAsync(); + var completedTask = await Task.WhenAny(contextTask, Task.Delay(-1, stoppingToken)); + if (completedTask == contextTask) { - logger.Debug("Waiting for HTTP request..."); - context = await _httpListener.GetContextAsync(); + var context = contextTask.Result; logger.Debug($"Received request: {context.Request.Url?.AbsolutePath}"); + if (context != null) + _ = HandleRequestAsync(context, stoppingToken); } - catch (ObjectDisposedException) + else { - // Listener closed, exit loop break; } - catch (HttpListenerException) - { - // Listener closed, exit loop - break; - } - catch (Exception ex) - { - logger.Error(ex, "Error in GetContextAsync"); - break; - } - if (context != null) - _ = HandleRequestAsync(context, stoppingToken); } - } - finally - { - _httpListener?.Close(); - logger.Info("HDMI Video Stream Service stopped."); + catch (Exception ex) + { + logger.Error(ex, "Error in GetContextAsync"); + break; + } } } public override async Task StopAsync(CancellationToken cancellationToken) { logger.Info("Stopping HDMI Video Stream Service..."); + _httpListener?.Close(); // 禁用所有活跃的HDMI传输 var disableTasks = new List(); @@ -229,9 +221,6 @@ public class HttpHdmiVideoStreamService : BackgroundService { logger.Debug("处理HDMI快照请求"); - const int frameWidth = 960; // HDMI输入分辨率 - const int frameHeight = 540; - // 从HDMI读取RGB565数据 var frameResult = await hdmiIn.ReadFrame(); if (!frameResult.IsSuccessful || frameResult.Value == null) @@ -244,41 +233,7 @@ public class HttpHdmiVideoStreamService : BackgroundService return; } - var rgb565Data = frameResult.Value; - - // 验证数据长度 - var expectedLength = frameWidth * frameHeight * 2; - if (rgb565Data.Length != expectedLength) - { - logger.Warn("HDMI快照数据长度不匹配,期望: {Expected}, 实际: {Actual}", - expectedLength, rgb565Data.Length); - } - - // 将RGB565转换为RGB24 - var rgb24Result = Common.Image.ConvertRGB565ToRGB24(rgb565Data, frameWidth, frameHeight, isLittleEndian: false); - if (!rgb24Result.IsSuccessful) - { - logger.Error("HDMI快照RGB565转RGB24失败: {Error}", rgb24Result.Error); - response.StatusCode = 500; - var errorBytes = System.Text.Encoding.UTF8.GetBytes("Failed to process HDMI snapshot"); - await response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length, cancellationToken); - response.Close(); - return; - } - - // 将RGB24转换为JPEG - var jpegResult = Common.Image.ConvertRGB24ToJpeg(rgb24Result.Value, frameWidth, frameHeight, 80); - if (!jpegResult.IsSuccessful) - { - logger.Error("HDMI快照RGB24转JPEG失败: {Error}", jpegResult.Error); - response.StatusCode = 500; - var errorBytes = System.Text.Encoding.UTF8.GetBytes("Failed to encode HDMI snapshot"); - await response.OutputStream.WriteAsync(errorBytes, 0, errorBytes.Length, cancellationToken); - response.Close(); - return; - } - - var jpegData = jpegResult.Value; + var jpegData = frameResult.Value; // 设置响应头(参考Camera版本) response.ContentType = "image/jpeg"; @@ -314,8 +269,6 @@ public class HttpHdmiVideoStreamService : BackgroundService logger.Debug("开始HDMI MJPEG流传输"); int frameCounter = 0; - const int frameWidth = 960; // HDMI输入分辨率 - const int frameHeight = 540; while (!cancellationToken.IsCancellationRequested) { @@ -323,61 +276,13 @@ public class HttpHdmiVideoStreamService : BackgroundService { var frameStartTime = DateTime.UtcNow; - // 从HDMI读取RGB565数据 - var readStartTime = DateTime.UtcNow; - var frameResult = await hdmiIn.ReadFrame(); - var readEndTime = DateTime.UtcNow; - var readTime = (readEndTime - readStartTime).TotalMilliseconds; + var ret = await hdmiIn.GetMJpegFrame(); + if (ret == null) continue; + var frame = ret.Value; - if (!frameResult.IsSuccessful || frameResult.Value == null) - { - logger.Warn("HDMI帧读取失败或为空"); - continue; - } - - var rgb565Data = frameResult.Value; - - // 验证数据长度是否正确 (RGB565为每像素2字节) - var expectedLength = frameWidth * frameHeight * 2; - if (rgb565Data.Length != expectedLength) - { - logger.Warn("HDMI数据长度不匹配,期望: {Expected}, 实际: {Actual}", - expectedLength, rgb565Data.Length); - } - - // 将RGB565转换为RGB24(参考Camera版本的处理) - var convertStartTime = DateTime.UtcNow; - var rgb24Result = Common.Image.ConvertRGB565ToRGB24(rgb565Data, frameWidth, frameHeight, isLittleEndian: false); - var convertEndTime = DateTime.UtcNow; - var convertTime = (convertEndTime - convertStartTime).TotalMilliseconds; - - if (!rgb24Result.IsSuccessful) - { - logger.Error("HDMI RGB565转RGB24失败: {Error}", rgb24Result.Error); - continue; - } - - // 将RGB24转换为JPEG(参考Camera版本的处理) - var jpegStartTime = DateTime.UtcNow; - var jpegResult = Common.Image.ConvertRGB24ToJpeg(rgb24Result.Value, frameWidth, frameHeight, 80); - var jpegEndTime = DateTime.UtcNow; - var jpegTime = (jpegEndTime - jpegStartTime).TotalMilliseconds; - - if (!jpegResult.IsSuccessful) - { - logger.Error("HDMI RGB24转JPEG失败: {Error}", jpegResult.Error); - continue; - } - - var jpegData = jpegResult.Value; - - // 发送MJPEG帧(使用Camera版本的格式) - var mjpegFrameHeader = Common.Image.CreateMjpegFrameHeader(jpegData.Length); - var mjpegFrameFooter = Common.Image.CreateMjpegFrameFooter(); - - await response.OutputStream.WriteAsync(mjpegFrameHeader, 0, mjpegFrameHeader.Length, cancellationToken); - await response.OutputStream.WriteAsync(jpegData, 0, jpegData.Length, cancellationToken); - await response.OutputStream.WriteAsync(mjpegFrameFooter, 0, mjpegFrameFooter.Length, cancellationToken); + 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++; @@ -387,8 +292,8 @@ public class HttpHdmiVideoStreamService : BackgroundService // 性能统计日志(每30帧记录一次) if (frameCounter % 30 == 0) { - logger.Debug("HDMI帧 {FrameNumber} 性能统计 - 读取: {ReadTime:F1}ms, RGB转换: {ConvertTime:F1}ms, JPEG转换: {JpegTime:F1}ms, 总计: {TotalTime:F1}ms, JPEG大小: {JpegSize} 字节", - frameCounter, readTime, convertTime, jpegTime, totalTime, jpegData.Length); + logger.Debug("HDMI帧 {FrameNumber} 性能统计 - 总计: {TotalTime:F1}ms, JPEG大小: {JpegSize} 字节", + frameCounter, totalTime, frame.data.Length); } } catch (Exception ex) diff --git a/server/src/Services/HttpVideoStreamService.cs b/server/src/Services/HttpVideoStreamService.cs index 3fc1b3e..16dbd34 100644 --- a/server/src/Services/HttpVideoStreamService.cs +++ b/server/src/Services/HttpVideoStreamService.cs @@ -1,8 +1,5 @@ -#define USB_CAMERA - using System.Net; using System.Text; -using System.Threading.Tasks; using Peripherals.CameraClient; // 添加摄像头客户端引用 #if USB_CAMERA @@ -414,7 +411,7 @@ public class HttpVideoStreamService : BackgroundService { _usbCamera = new VideoCapture(1); _usbCamera.Fps = _frameRate; - _usbCamera.FrameWidth = _frameWidth; + _usbCamera.FrameWidth = _frameWidth; _usbCamera.FrameHeight = _frameHeight; _usbCameraEnable = _usbCamera.IsOpened(); } @@ -1003,38 +1000,6 @@ public class HttpVideoStreamService : BackgroundService logger.Info("HTTP 视频流服务已停止"); } - /// - /// 释放资源 - /// - public override void Dispose() - { - if (_httpListener != null) - { - if (_httpListener.IsListening) - { - _httpListener.Stop(); - } - _httpListener.Close(); - } - - lock (_clientsLock) - { - foreach (var client in _activeClients) - { - try { client.Close(); } - catch { /* 忽略关闭错误 */ } - } - _activeClients.Clear(); - } - - lock (_cameraLock) - { - _camera = null; - } - - base.Dispose(); - } - /// /// 设置视频流分辨率 /// diff --git a/server/src/Services/ProgressTrackerService.cs b/server/src/Services/ProgressTrackerService.cs index bf555a7..2283017 100644 --- a/server/src/Services/ProgressTrackerService.cs +++ b/server/src/Services/ProgressTrackerService.cs @@ -190,7 +190,7 @@ public class ProgressTrackerService : BackgroundService { logger.Error(ex, "Error during ProgressTracker cleanup"); } - await Task.Delay(TimeSpan.FromSeconds(30)); + await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken); } }