add: 完善HDMI输入前后端,现在无法关闭

This commit is contained in:
alivender
2025-08-04 16:35:42 +08:00
parent e86cd5464e
commit a331494fde
5 changed files with 769 additions and 27 deletions

View File

@@ -16,7 +16,7 @@ public class HttpHdmiVideoStreamService : BackgroundService
{
private readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private HttpListener? _httpListener;
private readonly int _serverPort = 6666;
private readonly int _serverPort = 4322;
private readonly ConcurrentDictionary<string, HdmiIn> _hdmiInDict = new();
private bool _isEnabled = true;
@@ -39,7 +39,9 @@ public class HttpHdmiVideoStreamService : BackgroundService
HttpListenerContext? context = null;
try
{
logger.Debug("Waiting for HTTP request...");
context = await _httpListener.GetContextAsync();
logger.Debug($"Received request: {context.Request.Url?.AbsolutePath}");
}
catch (ObjectDisposedException)
{
@@ -71,12 +73,46 @@ public class HttpHdmiVideoStreamService : BackgroundService
{
logger.Info("Stopping HDMI Video Stream Service...");
_isEnabled = false;
// 禁用所有活跃的HDMI传输
var disableTasks = new List<Task>();
foreach (var hdmiIn in _hdmiInDict.Values)
{
disableTasks.Add(DisableHdmiTransmissionAsync(hdmiIn));
}
// 等待所有禁用操作完成
await Task.WhenAll(disableTasks);
// 清空字典
_hdmiInDict.Clear();
_httpListener?.Close(); // 立即关闭监听器,唤醒阻塞
await base.StopAsync(cancellationToken);
}
private async Task DisableHdmiTransmissionAsync(HdmiIn hdmiIn)
{
try
{
var disableResult = await hdmiIn.EnableTrans(false);
if (disableResult.IsSuccessful)
{
logger.Info("Successfully disabled HDMI transmission");
}
else
{
logger.Error($"Failed to disable HDMI transmission: {disableResult.Error}");
}
}
catch (Exception ex)
{
logger.Error(ex, "Exception occurred while disabling HDMI transmission");
}
}
// 获取/创建 HdmiIn 实例
private HdmiIn? GetOrCreateHdmiIn(string boardId)
private async Task<HdmiIn?> GetOrCreateHdmiInAsync(string boardId)
{
if (_hdmiInDict.TryGetValue(boardId, out var hdmiIn))
return hdmiIn;
@@ -98,6 +134,24 @@ public class HttpHdmiVideoStreamService : BackgroundService
var board = boardRet.Value.Value;
hdmiIn = new HdmiIn(board.IpAddr, board.Port, 0); // taskID 可根据实际需求调整
// 启用HDMI传输
try
{
var enableResult = await hdmiIn.EnableTrans(true);
if (!enableResult.IsSuccessful)
{
logger.Error($"Failed to enable HDMI transmission for board {boardId}: {enableResult.Error}");
return null;
}
logger.Info($"Successfully enabled HDMI transmission for board {boardId}");
}
catch (Exception ex)
{
logger.Error(ex, $"Exception occurred while enabling HDMI transmission for board {boardId}");
return null;
}
_hdmiInDict[boardId] = hdmiIn;
return hdmiIn;
}
@@ -112,7 +166,7 @@ public class HttpHdmiVideoStreamService : BackgroundService
return;
}
var hdmiIn = GetOrCreateHdmiIn(boardId);
var hdmiIn = await GetOrCreateHdmiInAsync(boardId);
if (hdmiIn == null)
{
await SendErrorAsync(context.Response, "Invalid boardId or board not available");
@@ -139,36 +193,205 @@ public class HttpHdmiVideoStreamService : BackgroundService
private async Task HandleSnapshotRequestAsync(HttpListenerResponse response, HdmiIn hdmiIn, CancellationToken cancellationToken)
{
var frameResult = await hdmiIn.ReadFrame();
if (frameResult.IsSuccessful)
try
{
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)
{
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 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;
// 设置响应头参考Camera版本
response.ContentType = "image/jpeg";
await response.OutputStream.WriteAsync(frameResult.Value, 0, frameResult.Value.Length, cancellationToken);
response.ContentLength64 = jpegData.Length;
response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate");
await response.OutputStream.WriteAsync(jpegData, 0, jpegData.Length, cancellationToken);
await response.OutputStream.FlushAsync(cancellationToken);
logger.Debug("已发送HDMI快照图像大小{Size} 字节", jpegData.Length);
}
else
catch (Exception ex)
{
logger.Error(ex, "处理HDMI快照请求时出错");
response.StatusCode = 500;
await response.OutputStream.WriteAsync(System.Text.Encoding.UTF8.GetBytes("Failed to get snapshot"));
}
response.Close();
finally
{
response.Close();
}
}
private async Task HandleMjpegStreamAsync(HttpListenerResponse response, HdmiIn hdmiIn, CancellationToken cancellationToken)
{
response.ContentType = "multipart/x-mixed-replace; boundary=--frame";
while (!cancellationToken.IsCancellationRequested && _isEnabled)
try
{
var frameResult = await hdmiIn.ReadFrame();
if (frameResult.IsSuccessful)
// 设置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;
const int frameWidth = 960; // HDMI输入分辨率
const int frameHeight = 540;
while (!cancellationToken.IsCancellationRequested && _isEnabled)
{
var header = $"--frame\r\nContent-Type: image/jpeg\r\nContent-Length: {frameResult.Value.Length}\r\n\r\n";
await response.OutputStream.WriteAsync(System.Text.Encoding.ASCII.GetBytes(header));
await response.OutputStream.WriteAsync(frameResult.Value, 0, frameResult.Value.Length, cancellationToken);
await response.OutputStream.WriteAsync(System.Text.Encoding.ASCII.GetBytes("\r\n"));
try
{
var frameStartTime = DateTime.UtcNow;
// 从HDMI读取RGB565数据
var readStartTime = DateTime.UtcNow;
var frameResult = await hdmiIn.ReadFrame();
var readEndTime = DateTime.UtcNow;
var readTime = (readEndTime - readStartTime).TotalMilliseconds;
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.FlushAsync(cancellationToken);
frameCounter++;
var totalTime = (DateTime.UtcNow - frameStartTime).TotalMilliseconds;
// 性能统计日志每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);
}
}
catch (Exception ex)
{
logger.Error(ex, "处理HDMI帧时发生错误");
}
}
await Task.Delay(33, cancellationToken); // ~30fps
}
response.Close();
catch (Exception ex)
{
logger.Error(ex, "HDMI MJPEG流处理异常");
}
finally
{
try
{
// 停止传输时禁用HDMI传输
await hdmiIn.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)
@@ -200,6 +423,10 @@ public class HttpHdmiVideoStreamService : BackgroundService
response.Close();
}
/// <summary>
/// 获取所有可用的HDMI视频流终端点
/// </summary>
/// <returns>返回所有可用的HDMI视频流终端点列表</returns>
public List<HdmiVideoStreamEndpoint>? GetAllVideoEndpoints()
{
var db = new Database.AppDataConnection();
@@ -221,6 +448,11 @@ public class HttpHdmiVideoStreamService : BackgroundService
return endpoints;
}
/// <summary>
/// 获取指定板卡ID的HDMI视频流终端点
/// </summary>
/// <param name="boardId">板卡ID</param>
/// <returns>返回指定板卡的HDMI视频流终端点</returns>
public HdmiVideoStreamEndpoint GetVideoEndpoint(string boardId)
{
return new HdmiVideoStreamEndpoint

View File

@@ -465,7 +465,8 @@ public class UDPClientPool
CommandID = Convert.ToByte(taskID),
IsWrite = false,
BurstLength = (byte)(currentSegmentSize - 1),
Address = devAddr + (uint)(i * max4BytesPerRead)
Address = (burstType == BurstType.ExtendBurst)?(devAddr + (uint)(i * max4BytesPerRead)):(devAddr),
// Address = devAddr + (uint)(i * max4BytesPerRead),
};
pkgList.Add(new SendAddrPackage(opts));
}