feat: 修改视频流后端服务,使其适配jpeg格式
This commit is contained in:
parent
aff9da2a60
commit
d2508f6484
|
@ -100,6 +100,51 @@ class HdmiIn
|
||||||
return result.Value;
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取当前分辨率
|
/// 获取当前分辨率
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -32,46 +32,38 @@ public class HttpHdmiVideoStreamService : BackgroundService
|
||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
try
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
while (!stoppingToken.IsCancellationRequested)
|
if (_httpListener == null) continue;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
HttpListenerContext? context = null;
|
logger.Debug("Waiting for HTTP request...");
|
||||||
try
|
var contextTask = _httpListener.GetContextAsync();
|
||||||
|
var completedTask = await Task.WhenAny(contextTask, Task.Delay(-1, stoppingToken));
|
||||||
|
if (completedTask == contextTask)
|
||||||
{
|
{
|
||||||
logger.Debug("Waiting for HTTP request...");
|
var context = contextTask.Result;
|
||||||
context = await _httpListener.GetContextAsync();
|
|
||||||
logger.Debug($"Received request: {context.Request.Url?.AbsolutePath}");
|
logger.Debug($"Received request: {context.Request.Url?.AbsolutePath}");
|
||||||
|
if (context != null)
|
||||||
|
_ = HandleRequestAsync(context, stoppingToken);
|
||||||
}
|
}
|
||||||
catch (ObjectDisposedException)
|
else
|
||||||
{
|
{
|
||||||
// Listener closed, exit loop
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch (HttpListenerException)
|
|
||||||
{
|
|
||||||
// Listener closed, exit loop
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.Error(ex, "Error in GetContextAsync");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (context != null)
|
|
||||||
_ = HandleRequestAsync(context, stoppingToken);
|
|
||||||
}
|
}
|
||||||
}
|
catch (Exception ex)
|
||||||
finally
|
{
|
||||||
{
|
logger.Error(ex, "Error in GetContextAsync");
|
||||||
_httpListener?.Close();
|
break;
|
||||||
logger.Info("HDMI Video Stream Service stopped.");
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task StopAsync(CancellationToken cancellationToken)
|
public override async Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
logger.Info("Stopping HDMI Video Stream Service...");
|
logger.Info("Stopping HDMI Video Stream Service...");
|
||||||
|
_httpListener?.Close();
|
||||||
|
|
||||||
// 禁用所有活跃的HDMI传输
|
// 禁用所有活跃的HDMI传输
|
||||||
var disableTasks = new List<Task>();
|
var disableTasks = new List<Task>();
|
||||||
|
@ -229,9 +221,6 @@ public class HttpHdmiVideoStreamService : BackgroundService
|
||||||
{
|
{
|
||||||
logger.Debug("处理HDMI快照请求");
|
logger.Debug("处理HDMI快照请求");
|
||||||
|
|
||||||
const int frameWidth = 960; // HDMI输入分辨率
|
|
||||||
const int frameHeight = 540;
|
|
||||||
|
|
||||||
// 从HDMI读取RGB565数据
|
// 从HDMI读取RGB565数据
|
||||||
var frameResult = await hdmiIn.ReadFrame();
|
var frameResult = await hdmiIn.ReadFrame();
|
||||||
if (!frameResult.IsSuccessful || frameResult.Value == null)
|
if (!frameResult.IsSuccessful || frameResult.Value == null)
|
||||||
|
@ -244,41 +233,7 @@ public class HttpHdmiVideoStreamService : BackgroundService
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var rgb565Data = frameResult.Value;
|
var jpegData = 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版本)
|
// 设置响应头(参考Camera版本)
|
||||||
response.ContentType = "image/jpeg";
|
response.ContentType = "image/jpeg";
|
||||||
|
@ -314,8 +269,6 @@ public class HttpHdmiVideoStreamService : BackgroundService
|
||||||
logger.Debug("开始HDMI MJPEG流传输");
|
logger.Debug("开始HDMI MJPEG流传输");
|
||||||
|
|
||||||
int frameCounter = 0;
|
int frameCounter = 0;
|
||||||
const int frameWidth = 960; // HDMI输入分辨率
|
|
||||||
const int frameHeight = 540;
|
|
||||||
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
|
@ -323,61 +276,13 @@ public class HttpHdmiVideoStreamService : BackgroundService
|
||||||
{
|
{
|
||||||
var frameStartTime = DateTime.UtcNow;
|
var frameStartTime = DateTime.UtcNow;
|
||||||
|
|
||||||
// 从HDMI读取RGB565数据
|
var ret = await hdmiIn.GetMJpegFrame();
|
||||||
var readStartTime = DateTime.UtcNow;
|
if (ret == null) continue;
|
||||||
var frameResult = await hdmiIn.ReadFrame();
|
var frame = ret.Value;
|
||||||
var readEndTime = DateTime.UtcNow;
|
|
||||||
var readTime = (readEndTime - readStartTime).TotalMilliseconds;
|
|
||||||
|
|
||||||
if (!frameResult.IsSuccessful || frameResult.Value == null)
|
await response.OutputStream.WriteAsync(frame.header, 0, frame.header.Length, cancellationToken);
|
||||||
{
|
await response.OutputStream.WriteAsync(frame.data, 0, frame.data.Length, cancellationToken);
|
||||||
logger.Warn("HDMI帧读取失败或为空");
|
await response.OutputStream.WriteAsync(frame.footer, 0, frame.footer.Length, cancellationToken);
|
||||||
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);
|
await response.OutputStream.FlushAsync(cancellationToken);
|
||||||
|
|
||||||
frameCounter++;
|
frameCounter++;
|
||||||
|
@ -387,8 +292,8 @@ public class HttpHdmiVideoStreamService : BackgroundService
|
||||||
// 性能统计日志(每30帧记录一次)
|
// 性能统计日志(每30帧记录一次)
|
||||||
if (frameCounter % 30 == 0)
|
if (frameCounter % 30 == 0)
|
||||||
{
|
{
|
||||||
logger.Debug("HDMI帧 {FrameNumber} 性能统计 - 读取: {ReadTime:F1}ms, RGB转换: {ConvertTime:F1}ms, JPEG转换: {JpegTime:F1}ms, 总计: {TotalTime:F1}ms, JPEG大小: {JpegSize} 字节",
|
logger.Debug("HDMI帧 {FrameNumber} 性能统计 - 总计: {TotalTime:F1}ms, JPEG大小: {JpegSize} 字节",
|
||||||
frameCounter, readTime, convertTime, jpegTime, totalTime, jpegData.Length);
|
frameCounter, totalTime, frame.data.Length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
#define USB_CAMERA
|
|
||||||
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Peripherals.CameraClient; // 添加摄像头客户端引用
|
using Peripherals.CameraClient; // 添加摄像头客户端引用
|
||||||
|
|
||||||
#if USB_CAMERA
|
#if USB_CAMERA
|
||||||
|
@ -414,7 +411,7 @@ public class HttpVideoStreamService : BackgroundService
|
||||||
{
|
{
|
||||||
_usbCamera = new VideoCapture(1);
|
_usbCamera = new VideoCapture(1);
|
||||||
_usbCamera.Fps = _frameRate;
|
_usbCamera.Fps = _frameRate;
|
||||||
_usbCamera.FrameWidth = _frameWidth;
|
_usbCamera.FrameWidth = _frameWidth;
|
||||||
_usbCamera.FrameHeight = _frameHeight;
|
_usbCamera.FrameHeight = _frameHeight;
|
||||||
_usbCameraEnable = _usbCamera.IsOpened();
|
_usbCameraEnable = _usbCamera.IsOpened();
|
||||||
}
|
}
|
||||||
|
@ -1003,38 +1000,6 @@ public class HttpVideoStreamService : BackgroundService
|
||||||
logger.Info("HTTP 视频流服务已停止");
|
logger.Info("HTTP 视频流服务已停止");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 释放资源
|
|
||||||
/// </summary>
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置视频流分辨率
|
/// 设置视频流分辨率
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -190,7 +190,7 @@ public class ProgressTrackerService : BackgroundService
|
||||||
{
|
{
|
||||||
logger.Error(ex, "Error during ProgressTracker cleanup");
|
logger.Error(ex, "Error during ProgressTracker cleanup");
|
||||||
}
|
}
|
||||||
await Task.Delay(TimeSpan.FromSeconds(30));
|
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue